Skip to content

Commit

Permalink
[WFS provider] Try to detect geometry type if unknown for WFS 1.0 (#5…
Browse files Browse the repository at this point in the history
  • Loading branch information
pathmapper committed Nov 21, 2022
1 parent 934774a commit bfde785
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 32 deletions.
75 changes: 45 additions & 30 deletions src/providers/wfs/qgswfsprovider.cpp
Expand Up @@ -176,30 +176,8 @@ QgsWFSProvider::QgsWFSProvider( const QString &uri, const ProviderOptions &optio
mShared->setCurrentRect( QgsRectangle() );
};

// For WFS >= 1.1, by default, issue a GetFeature on one feature to check
// if gml:description, gml:identifier, gml:name attributes are
// present (unless mShared->mURI.skipInitialGetFeature() returns false)
// Another reason to issue it if we do not known the exact geometry type
// from describeFeatureType()
if ( !mShared->mWFSVersion.startsWith( QLatin1String( "1.0" ) ) &&
!mShared->mURI.skipInitialGetFeature() )
{
// Try to see if gml:description, gml:identifier, gml:name attributes are
// present. So insert them temporarily in mShared->mFields so that the
// GML parser can detect them.
const auto addGMLFields = [ = ]( bool forceAdd )
{
if ( mShared->mFields.indexOf( QLatin1String( "description" ) ) < 0 && ( forceAdd || mSampleFeatureHasDescription ) )
mShared->mFields.append( QgsField( QStringLiteral( "description" ), QVariant::String, QStringLiteral( "xsd:string" ) ) );
if ( mShared->mFields.indexOf( QLatin1String( "identifier" ) ) < 0 && ( forceAdd || mSampleFeatureHasIdentifier ) )
mShared->mFields.append( QgsField( QStringLiteral( "identifier" ), QVariant::String, QStringLiteral( "xsd:string" ) ) );
if ( mShared->mFields.indexOf( QLatin1String( "name" ) ) < 0 && ( forceAdd || mSampleFeatureHasName ) )
mShared->mFields.append( QgsField( QStringLiteral( "name" ), QVariant::String, QStringLiteral( "xsd:string" ) ) );
};

const QgsFields fieldsBackup = mShared->mFields;
addGMLFields( true );

const auto TryToDetectGeometryType = [&]()
{
const QgsWkbTypes::Type initialGeometryType = mShared->mWKBType;

// try first without a BBOX, because some servers exhibit very poor
Expand Down Expand Up @@ -237,13 +215,50 @@ QgsWFSProvider::QgsWFSProvider( const QString &uri, const ProviderOptions &optio
{
mShared->mWKBType = initialGeometryType;
}
};

// Edit the final mFields to add the description, identifier, name fields
// if they were actually found.
mShared->mFields.clear();
addGMLFields( false );
for ( const QgsField &field : fieldsBackup )
mShared->mFields.append( field );
if ( !mShared->mURI.skipInitialGetFeature() )
{
// For WFS = 1.0, issue a GetFeature on one feature to check
// if we do not known the exact geometry type from
// describeFeatureType()
if ( mShared->mWFSVersion.startsWith( QLatin1String( "1.0" ) ) &&
mShared->mWKBType == QgsWkbTypes::Unknown )
{
TryToDetectGeometryType();
}
// For WFS >= 1.1, by default, issue a GetFeature on one feature to check
// if gml:description, gml:identifier, gml:name attributes are
// present (unless mShared->mURI.skipInitialGetFeature() returns false)
// Another reason to issue it if we do not known the exact geometry type
// from describeFeatureType()
else if ( !mShared->mWFSVersion.startsWith( QLatin1String( "1.0" ) ) )
{
// Try to see if gml:description, gml:identifier, gml:name attributes are
// present. So insert them temporarily in mShared->mFields so that the
// GML parser can detect them.
const auto addGMLFields = [ = ]( bool forceAdd )
{
if ( mShared->mFields.indexOf( QLatin1String( "description" ) ) < 0 && ( forceAdd || mSampleFeatureHasDescription ) )
mShared->mFields.append( QgsField( QStringLiteral( "description" ), QVariant::String, QStringLiteral( "xsd:string" ) ) );
if ( mShared->mFields.indexOf( QLatin1String( "identifier" ) ) < 0 && ( forceAdd || mSampleFeatureHasIdentifier ) )
mShared->mFields.append( QgsField( QStringLiteral( "identifier" ), QVariant::String, QStringLiteral( "xsd:string" ) ) );
if ( mShared->mFields.indexOf( QLatin1String( "name" ) ) < 0 && ( forceAdd || mSampleFeatureHasName ) )
mShared->mFields.append( QgsField( QStringLiteral( "name" ), QVariant::String, QStringLiteral( "xsd:string" ) ) );
};

const QgsFields fieldsBackup = mShared->mFields;
addGMLFields( true );

TryToDetectGeometryType();

// Edit the final mFields to add the description, identifier, name fields
// if they were actually found.
mShared->mFields.clear();
addGMLFields( false );
for ( const QgsField &field : fieldsBackup )
mShared->mFields.append( field );
}
}
}

Expand Down
85 changes: 83 additions & 2 deletions tests/src/python/test_provider_wfs.py
Expand Up @@ -5363,10 +5363,10 @@ def testLayerWithGeometryFieldButNoGeom(self):
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.NoGeometry)

def testLayerWithGeometryFieldButInitialFeatureHasNoGeom(self):
def testWFS20LayerWithGeometryFieldButInitialFeatureHasNoGeom(self):
""" https://github.com/qgis/QGIS/pull/50237 """

endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_layer_with_geometry_field_but_initial_filter_has_no_geom'
endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_wfs20_layer_with_geometry_field_but_initial_filter_has_no_geom'

with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=2.0.0'), 'wb') as f:
f.write("""
Expand Down Expand Up @@ -5442,6 +5442,87 @@ def testLayerWithGeometryFieldButInitialFeatureHasNoGeom(self):
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.Point)

def testWFS10LayerWithGeometryFieldButInitialFeatureHasNoGeom(self):
""" https://github.com/qgis/QGIS/issues/50935 """

endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_wfs10_layer_with_geometry_field_but_initial_filter_has_no_geom'

with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=1.0.0'), 'wb') as f:
f.write("""
<WFS_Capabilities version="1.0.0" xmlns="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc">
<FeatureTypeList>
<FeatureType>
<Name>my:typename</Name>
<Title>Title</Title>
<Abstract>Abstract</Abstract>
<SRS>EPSG:32631</SRS>
<!-- in WFS 1.0, LatLongBoundingBox is in SRS units, not necessarily lat/long... -->
<LatLongBoundingBox minx="400000" miny="5400000" maxx="450000" maxy="5500000"/>
</FeatureType>
</FeatureTypeList>
</WFS_Capabilities>""".encode('UTF-8'))

with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.0.0&TYPENAME=my:typename'),
'wb') as f:
f.write("""
<xsd:schema xmlns:my="http://my" xmlns:gml="http://www.opengis.net/gml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://my">
<xsd:import namespace="http://www.opengis.net/gml"/>
<xsd:complexType name="typenameType">
<xsd:complexContent>
<xsd:extension base="gml:AbstractFeatureType">
<xsd:sequence>
<xsd:element maxOccurs="1" minOccurs="0" name="INTFIELD" nillable="true" type="xsd:int"/>
<!-- use geometry that is the default SpatiaLite geometry name -->
<xsd:element maxOccurs="1" minOccurs="0" name="geometry" nillable="true" type="gml:GeometryPropertyType"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="typename" substitutionGroup="gml:_Feature" type="my:typenameType"/>
</xsd:schema>
""".encode('UTF-8'))

with open(
sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&MAXFEATURES=1&SRSNAME=EPSG:32631'),
'wb') as f:
f.write("""
<wfs:FeatureCollection
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:gml="http://www.opengis.net/gml"
xmlns:my="http://my">
<gml:boundedBy><gml:null>unknown</gml:null></gml:boundedBy>
<gml:featureMember>
<my:typename fid="typename.0">
<my:INTFIELD>1</my:INTFIELD>
</my:typename>
</gml:featureMember>
</wfs:FeatureCollection>""".encode('UTF-8'))

with open(
sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&MAXFEATURES=1&SRSNAME=EPSG:32631&BBOX=-100000000,-100000000,100000000,100000000'),
'wb') as f:
f.write("""
<wfs:FeatureCollection
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:gml="http://www.opengis.net/gml"
xmlns:my="http://my">
<gml:boundedBy><gml:null>unknown</gml:null></gml:boundedBy>
<gml:featureMember>
<my:typename fid="typename.0">
<my:geometry>
<gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:coordinates decimal="." cs="," ts=" ">426858,5427937</gml:coordinates></gml:Point>
</my:geometry>
<my:INTFIELD>1</my:INTFIELD>
</my:typename>
</gml:featureMember>
</wfs:FeatureCollection>""".encode('UTF-8'))

vl = QgsVectorLayer(
"url='http://" + endpoint + "' typename='my:typename' version='1.0.0'",
'test', 'WFS')
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.Point)

def testLayerWithFieldsInGMLNamespace(self):
""" https://github.com/qgis/QGIS/issues/42660 """

Expand Down

0 comments on commit bfde785

Please sign in to comment.