Skip to content

Commit c89a542

Browse files
authoredOct 15, 2018
Merge pull request #8185 from rouault/fix_19571
[WFS client] Try to handle layers of type GeometryCollection if the first GeometryCollection is made of geometries of the same type (fixes #19571)
2 parents 4cc4bab + 9014285 commit c89a542

File tree

5 files changed

+210
-10
lines changed

5 files changed

+210
-10
lines changed
 

‎src/providers/wfs/qgswfsfeatureiterator.cpp

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
#include "qgsmessagelog.h"
1919
#include "qgsgeometry.h"
2020
#include "qgsgml.h"
21+
#include "qgsgeometrycollection.h"
22+
#include "qgsmultipoint.h"
23+
#include "qgsmultilinestring.h"
24+
#include "qgsmultipolygon.h"
2125
#include "qgsogcutils.h"
2226
#include "qgswfsconstants.h"
2327
#include "qgswfsfeatureiterator.h"
@@ -329,10 +333,20 @@ QUrl QgsWFSFeatureDownloader::buildURL( qint64 startIndex, int maxFeatures, bool
329333
else if ( !mShared->mRect.isNull() )
330334
{
331335
bool invertAxis = false;
332-
if ( !mShared->mWFSVersion.startsWith( QLatin1String( "1.0" ) ) && !mShared->mURI.ignoreAxisOrientation() &&
333-
mShared->mSourceCRS.hasAxisInverted() )
336+
if ( !mShared->mWFSVersion.startsWith( QLatin1String( "1.0" ) ) &&
337+
!mShared->mURI.ignoreAxisOrientation() )
334338
{
335-
invertAxis = true;
339+
// This is a bit nasty, but if the server reports OGC::CRS84
340+
// mSourceCRS will report hasAxisInverted() == false, but srsName()
341+
// will be urn:ogc:def:crs:EPSG::4326, so axis inversion is needed...
342+
if ( mShared->srsName() == QLatin1String( "urn:ogc:def:crs:EPSG::4326" ) )
343+
{
344+
invertAxis = true;
345+
}
346+
else if ( mShared->mSourceCRS.hasAxisInverted() )
347+
{
348+
invertAxis = true;
349+
}
336350
}
337351
if ( mShared->mURI.invertAxisOrientation() )
338352
{
@@ -712,6 +726,49 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures )
712726
f.setGeometry( g );
713727
}
714728

729+
// If receiving a geometry collection, but expecting a multipoint/...,
730+
// then try to convert it
731+
if ( f.hasGeometry() &&
732+
f.geometry().wkbType() == QgsWkbTypes::GeometryCollection &&
733+
( mShared->mWKBType == QgsWkbTypes::MultiPoint ||
734+
mShared->mWKBType == QgsWkbTypes::MultiLineString ||
735+
mShared->mWKBType == QgsWkbTypes::MultiPolygon ) )
736+
{
737+
QgsWkbTypes::Type singleType = QgsWkbTypes::singleType( mShared->mWKBType );
738+
auto g = f.geometry().constGet();
739+
auto gc = qgsgeometry_cast<QgsGeometryCollection *>( g );
740+
bool allExpectedType = true;
741+
for ( int i = 0; i < gc->numGeometries(); ++i )
742+
{
743+
if ( gc->geometryN( i )->wkbType() != singleType )
744+
{
745+
allExpectedType = false;
746+
break;
747+
}
748+
}
749+
if ( allExpectedType )
750+
{
751+
QgsGeometryCollection *newGC;
752+
if ( mShared->mWKBType == QgsWkbTypes::MultiPoint )
753+
{
754+
newGC = new QgsMultiPoint();
755+
}
756+
else if ( mShared->mWKBType == QgsWkbTypes::MultiLineString )
757+
{
758+
newGC = new QgsMultiLineString();
759+
}
760+
else
761+
{
762+
newGC = new QgsMultiPolygon();
763+
}
764+
for ( int i = 0; i < gc->numGeometries(); ++i )
765+
{
766+
newGC->addGeometry( gc->geometryN( i )->clone() );
767+
}
768+
f.setGeometry( QgsGeometry( newGC ) );
769+
}
770+
}
771+
715772
featureList.push_back( QgsWFSFeatureGmlIdPair( f, gmlId ) );
716773
delete featPair.first;
717774
if ( ( i > 0 && ( i % 1000 ) == 0 ) || i + 1 == featurePtrList.size() )

‎src/providers/wfs/qgswfsprovider.cpp

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ QgsWFSProvider::QgsWFSProvider( const QString &uri, const ProviderOptions &optio
104104
//fetch attributes of layer and type of its geometry attribute
105105
//WBC 111221: extracting geometry type here instead of getFeature allows successful
106106
//layer creation even when no features are retrieved (due to, e.g., BBOX or FILTER)
107-
if ( !describeFeatureType( mShared->mGeometryAttribute, mShared->mFields, mWKBType ) )
107+
if ( !describeFeatureType( mShared->mGeometryAttribute, mShared->mFields, mShared->mWKBType ) )
108108
{
109109
mValid = false;
110110
return;
@@ -120,7 +120,7 @@ QgsWFSProvider::QgsWFSProvider( const QString &uri, const ProviderOptions &optio
120120
}
121121

122122
//Failed to detect feature type from describeFeatureType -> get first feature from layer to detect type
123-
if ( mWKBType == QgsWkbTypes::Unknown )
123+
if ( mShared->mWKBType == QgsWkbTypes::Unknown )
124124
{
125125
QgsWFSFeatureDownloader downloader( mShared.get() );
126126
connect( &downloader, static_cast<void ( QgsWFSFeatureDownloader::* )( QVector<QgsWFSFeatureGmlIdPair> )>( &QgsWFSFeatureDownloader::featureReceived ),
@@ -478,7 +478,7 @@ bool QgsWFSProvider::processSQL( const QString &sqlString, QString &errorMsg, QS
478478
if ( typeName == mShared->mURI.typeName() )
479479
{
480480
mShared->mGeometryAttribute = geometryAttribute;
481-
mWKBType = geomType;
481+
mShared->mWKBType = geomType;
482482
mThisTypenameFields = fields;
483483
}
484484

@@ -665,7 +665,53 @@ void QgsWFSProvider::featureReceivedAnalyzeOneFeature( QVector<QgsWFSFeatureGmlI
665665
QgsGeometry geometry = feat.geometry();
666666
if ( !geometry.isNull() )
667667
{
668-
mWKBType = geometry.wkbType();
668+
mShared->mWKBType = geometry.wkbType();
669+
670+
// Fragile heuristics that helps for
671+
// https://sampleservices.luciad.com/ogc/wfs/sampleswfs?REQUEST=GetFeature&SERVICE=WFS&TYPENAME=rivers&VERSION=1.1.0
672+
// In case the geometry is a geometry collection, analyze its members to
673+
// see if they are of the same type. If then, assume that all features
674+
// will be similar, and report the proper MultiPoint/MultiLineString/
675+
// MultiPolygon type instead.
676+
if ( mShared->mWKBType == QgsWkbTypes::GeometryCollection )
677+
{
678+
auto geomColl = geometry.asGeometryCollection();
679+
mShared->mWKBType = QgsWkbTypes::Unknown;
680+
for ( const auto &geom : geomColl )
681+
{
682+
if ( mShared->mWKBType == QgsWkbTypes::Unknown )
683+
{
684+
mShared->mWKBType = geom.wkbType();
685+
}
686+
else if ( mShared->mWKBType != geom.wkbType() )
687+
{
688+
mShared->mWKBType = QgsWkbTypes::Unknown;
689+
break;
690+
}
691+
}
692+
if ( mShared->mWKBType != QgsWkbTypes::Unknown )
693+
{
694+
if ( mShared->mWKBType == QgsWkbTypes::Point )
695+
{
696+
QgsDebugMsg( QStringLiteral( "Layer of unknown type. First element is a GeometryCollection of Point. Advertizing optimistically as MultiPoint" ) );
697+
mShared->mWKBType = QgsWkbTypes::MultiPoint;
698+
}
699+
else if ( mShared->mWKBType == QgsWkbTypes::LineString )
700+
{
701+
QgsDebugMsg( QStringLiteral( "Layer of unknown type. First element is a GeometryCollection of LineString. Advertizing optimistically as MultiLineString" ) );
702+
mShared->mWKBType = QgsWkbTypes::MultiLineString;
703+
}
704+
else if ( mShared->mWKBType == QgsWkbTypes::Polygon )
705+
{
706+
QgsDebugMsg( QStringLiteral( "Layer of unknown type. First element is a GeometryCollection of Polygon. Advertizing optimistically as MultiPolygon" ) );
707+
mShared->mWKBType = QgsWkbTypes::MultiPolygon;
708+
}
709+
else
710+
{
711+
mShared->mWKBType = QgsWkbTypes::Unknown;
712+
}
713+
}
714+
}
669715
}
670716
}
671717
}
@@ -744,7 +790,7 @@ void QgsWFSProvider::reloadData()
744790

745791
QgsWkbTypes::Type QgsWFSProvider::wkbType() const
746792
{
747-
return mWKBType;
793+
return mShared->mWKBType;
748794
}
749795

750796
long QgsWFSProvider::featureCount() const

‎src/providers/wfs/qgswfsprovider.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ class QgsWFSProvider : public QgsVectorDataProvider
134134
//! String used to define a subset of the layer
135135
QString mSubsetString;
136136

137-
//! Geometry type of the features in this layer
138-
mutable QgsWkbTypes::Type mWKBType = QgsWkbTypes::Unknown;
139137
//! Flag if provider is valid
140138
bool mValid = true;
141139
//! Namespace URL of the server (comes from DescribeFeatureDocument)

‎src/providers/wfs/qgswfsshareddata.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ class QgsWFSSharedData : public QObject
187187
EPSG:XXXX srsName and not EPSG urns */
188188
bool mGetFeatureEPSGDotHonoursEPSGOrder;
189189

190+
//! Geometry type of the features in this layer
191+
QgsWkbTypes::Type mWKBType = QgsWkbTypes::Unknown;
192+
190193
private:
191194

192195
//! Main mutex to protect most data members that can be modified concurrently

‎tests/src/python/test_provider_wfs.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3485,6 +3485,102 @@ def testDescribeFeatureTypeWithSingleInclude(self):
34853485
vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename'", 'test', 'WFS')
34863486
self.assertTrue(vl.isValid())
34873487

3488+
def testGeometryCollectionAsMultiLineString(self):
3489+
"""Test https://issues.qgis.org/issues/19571 """
3490+
3491+
endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_gc_as_mls'
3492+
3493+
with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=1.1.0'), 'wb') as f:
3494+
f.write("""
3495+
<wfs:WFS_Capabilities version="1.1.0" xmlns="http://www.opengis.net/wfs" xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows" xmlns:gml="http://schemas.opengis.net/gml">
3496+
<FeatureTypeList>
3497+
<FeatureType>
3498+
<Name>my:typename</Name>
3499+
<Title>Title</Title>
3500+
<Abstract>Abstract</Abstract>
3501+
<DefaultCRS>urn:ogc:def:crs:OGC:1.3:CRS84</DefaultCRS>
3502+
<WGS84BoundingBox>
3503+
<LowerCorner>2 49</LowerCorner>
3504+
<UpperCorner>3 50</UpperCorner>
3505+
</WGS84BoundingBox>
3506+
</FeatureType>
3507+
</FeatureTypeList>
3508+
</wfs:WFS_Capabilities>""".encode('UTF-8'))
3509+
3510+
with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.1.0&TYPENAME=my:typename'), 'wb') as f:
3511+
f.write("""
3512+
<schema
3513+
targetNamespace="http://my"
3514+
xmlns:my="http://my"
3515+
xmlns:ogc="http://www.opengis.net/ogc"
3516+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
3517+
xmlns="http://www.w3.org/2001/XMLSchema"
3518+
xmlns:gml="http://www.opengis.net/gml"
3519+
elementFormDefault="qualified" version="0.1" >
3520+
<import namespace="http://www.opengis.net/gml"
3521+
schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/gml.xsd" />
3522+
<element name="typename"
3523+
type="my:typenameType"
3524+
substitutionGroup="gml:_Feature" />
3525+
<complexType name="typenameType">
3526+
<complexContent>
3527+
<extension base="gml:AbstractFeatureType">
3528+
<sequence>
3529+
<element name="geometryProperty" type="gml:GeometryPropertyType" minOccurs="0" maxOccurs="1"/>
3530+
</sequence>
3531+
</extension>
3532+
</complexContent>
3533+
</complexType>
3534+
</schema>
3535+
""".encode('UTF-8'))
3536+
3537+
get_features = """
3538+
<wfs:FeatureCollection
3539+
xmlns:my="http://my"
3540+
xmlns:gml="http://www.opengis.net/gml"
3541+
xmlns:wfs="http://www.opengis.net/wfs"
3542+
xmlns:ogc="http://www.opengis.net/ogc">
3543+
<gml:boundedBy>
3544+
<gml:Envelope srsName="urn:ogc:def:crs:OGC:1.3:CRS84">
3545+
<gml:lowerCorner>2 49</gml:lowerCorner>
3546+
<gml:upperCorner>3 50</gml:upperCorner>
3547+
</gml:Envelope>
3548+
</gml:boundedBy>
3549+
<gml:featureMember>
3550+
<my:typename gml:id="typename.1">
3551+
<my:geometryProperty>
3552+
<gml:MultiGeometry srsName="urn:ogc:def:crs:OGC:1.3:CRS84">
3553+
<gml:geometryMember>
3554+
<gml:LineString>
3555+
<gml:coordinates>2,49 3,50</gml:coordinates>
3556+
</gml:LineString>
3557+
</gml:geometryMember>
3558+
</gml:MultiGeometry>
3559+
</my:geometryProperty>
3560+
</my:typename>
3561+
</gml:featureMember>
3562+
</wfs:FeatureCollection>
3563+
"""
3564+
3565+
with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.1.0&TYPENAME=my:typename&MAXFEATURES=1&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), 'wb') as f:
3566+
f.write(get_features.encode('UTF-8'))
3567+
3568+
with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.1.0&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), 'wb') as f:
3569+
f.write(get_features.encode('UTF-8'))
3570+
3571+
vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.1.0'", 'test', 'WFS')
3572+
self.assertTrue(vl.isValid())
3573+
3574+
got_f = [f for f in vl.getFeatures()]
3575+
geom = got_f[0].geometry().constGet()
3576+
geom_string = geom.asWkt()
3577+
geom_string = re.sub(r'\.\d+', '', geom_string)
3578+
self.assertEqual(geom_string, 'MultiLineString ((2 49, 3 50))')
3579+
3580+
reference = QgsGeometry.fromRect(QgsRectangle(2, 49, 3, 50))
3581+
vl_extent = QgsGeometry.fromRect(vl.extent())
3582+
assert QgsGeometry.compare(vl_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001), 'Expected {}, got {}'.format(reference.asWkt(), vl_extent.asWkt())
3583+
34883584

34893585
if __name__ == '__main__':
34903586
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.