From 7511d1ffdb867f3bd19bf0697afa5c49ae6b621d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 22 May 2018 21:38:54 +0200 Subject: [PATCH] Revert "[bugfix][wfs] Expand support for 2.0.0 TYPENAMES" (fixes #18882) This reverts *partially* commit ccb4c80f8a6d2bb179258f1ffec0dc9a447ca465. The plural form of TYPENAMES is non conformant for the DescribeFeatureType request of WFS 2.0 The logic of ccb4c80f8a6d2bb179258f1ffec0dc9a447ca465 breaks conformant servers because they ignore the TYPENAMES parameter and thus return a DescribeFeatureType response covering all layers of the service, which can be extremely time consuming. Changes related to better geometry handling have been kept. --- .../wfs/qgswfsdescribefeaturetype.cpp | 11 +- src/providers/wfs/qgswfsdescribefeaturetype.h | 3 +- src/providers/wfs/qgswfsprovider.cpp | 29 +- src/providers/wfs/qgswfsprovider.h | 2 +- src/providers/wfs/qgswfsutils.cpp | 13 - src/providers/wfs/qgswfsutils.h | 4 - tests/src/python/test_provider_wfs.py | 695 +++++++----------- 7 files changed, 299 insertions(+), 458 deletions(-) diff --git a/src/providers/wfs/qgswfsdescribefeaturetype.cpp b/src/providers/wfs/qgswfsdescribefeaturetype.cpp index 375b8741ae54..1f085d2e4fa3 100644 --- a/src/providers/wfs/qgswfsdescribefeaturetype.cpp +++ b/src/providers/wfs/qgswfsdescribefeaturetype.cpp @@ -14,7 +14,6 @@ ***************************************************************************/ #include "qgswfsdescribefeaturetype.h" -#include "qgswfsutils.h" QgsWFSDescribeFeatureType::QgsWFSDescribeFeatureType( QgsWFSDataSourceURI &uri ) : QgsWfsRequest( uri ) @@ -22,16 +21,12 @@ QgsWFSDescribeFeatureType::QgsWFSDescribeFeatureType( QgsWFSDataSourceURI &uri ) } bool QgsWFSDescribeFeatureType::requestFeatureType( const QString &WFSVersion, - const QString &typeName, bool forceSingularTypeName ) + const QString &typeName ) { QUrl url( mUri.requestUrl( QStringLiteral( "DescribeFeatureType" ) ) ); url.addQueryItem( QStringLiteral( "VERSION" ), WFSVersion ); - // The specs are not consistent: is it singular in 1.0.x and plural in 2.0.0? - // see http://docs.opengeospatial.org/is/09-025r2/09-025r2.html#147 - if ( ! forceSingularTypeName ) - url.addQueryItem( QgsWFSUtils::typeNameParameterForVersion( WFSVersion ).toUpper( ), typeName ); - else - url.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); + url.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); + return sendGET( url, true, false ); } diff --git a/src/providers/wfs/qgswfsdescribefeaturetype.h b/src/providers/wfs/qgswfsdescribefeaturetype.h index ad929e66ce80..f81a97727dab 100644 --- a/src/providers/wfs/qgswfsdescribefeaturetype.h +++ b/src/providers/wfs/qgswfsdescribefeaturetype.h @@ -25,8 +25,7 @@ class QgsWFSDescribeFeatureType : public QgsWfsRequest explicit QgsWFSDescribeFeatureType( QgsWFSDataSourceURI &uri ); //! Issue the request - bool requestFeatureType( const QString &WFSVersion, const QString &typeName, - bool forceSingularTypeName = false ); + bool requestFeatureType( const QString &WFSVersion, const QString &typeName ); protected: QString errorMessageWithReason( const QString &reason ) override; diff --git a/src/providers/wfs/qgswfsprovider.cpp b/src/providers/wfs/qgswfsprovider.cpp index 67e8488490c3..12e91cce19e4 100644 --- a/src/providers/wfs/qgswfsprovider.cpp +++ b/src/providers/wfs/qgswfsprovider.cpp @@ -919,7 +919,7 @@ bool QgsWFSProvider::deleteFeatures( const QgsFeatureIds &id ) transactionDoc.appendChild( transactionElem ); //delete element QDomElement deleteElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Delete" ) ); - deleteElem.setAttribute( QgsWFSUtils::typeNameParameterForVersion( mShared->mWFSVersion ), tname ); + deleteElem.setAttribute( QStringLiteral( "typeName" ), tname ); QDomElement filterElem = transactionDoc.createElementNS( QgsWFSConstants::OGC_NAMESPACE, QStringLiteral( "Filter" ) ); @@ -984,7 +984,7 @@ bool QgsWFSProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) continue; } QDomElement updateElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Update" ) ); - updateElem.setAttribute( QgsWFSUtils::typeNameParameterForVersion( mShared->mWFSVersion ), tname ); + updateElem.setAttribute( QStringLiteral( "typeName" ), tname ); //Property QDomElement propertyElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Property" ) ); QDomElement nameElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Name" ) ); @@ -1040,7 +1040,6 @@ QString QgsWFSProvider::convertToXML( const QVariant &value ) return valueStr; } - bool QgsWFSProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map ) { //find out typename from uri and strip namespace prefix @@ -1066,7 +1065,7 @@ bool QgsWFSProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ } QDomElement updateElem = transactionDoc.createElementNS( QgsWFSConstants::WFS_NAMESPACE, QStringLiteral( "Update" ) ); - updateElem.setAttribute( QgsWFSUtils::typeNameParameterForVersion( mShared->mWFSVersion ), tname ); + updateElem.setAttribute( QStringLiteral( "typeName" ), tname ); QgsAttributeMap::const_iterator attMapIt = attIt.value().constBegin(); for ( ; attMapIt != attIt.value().constEnd(); ++attMapIt ) @@ -1159,18 +1158,15 @@ QString QgsWFSProvider::translateMetadataValue( const QString &mdKey, const QVar { return value.toString(); } -} +}; -bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, - QgsFields &fields, - QgsWkbTypes::Type &geomType, - bool forceSingularTypeNames ) +bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, QgsFields &fields, QgsWkbTypes::Type &geomType ) { fields.clear(); QgsWFSDescribeFeatureType describeFeatureType( mShared->mURI ); if ( !describeFeatureType.requestFeatureType( mShared->mWFSVersion, - mShared->mURI.typeName(), forceSingularTypeNames ) ) + mShared->mURI.typeName() ) ) { QgsMessageLog::logMessage( tr( "DescribeFeatureType network request failed for url %1: %2" ). arg( dataSourceUri(), describeFeatureType.errorMessage() ), tr( "WFS" ) ); @@ -1193,19 +1189,11 @@ bool QgsWFSProvider::describeFeatureType( QString &geometryAttribute, mShared->mURI.typeName(), geometryAttribute, fields, geomType, errorMsg ) ) { - // If 2.0.0, let's assume it was a server that only accepted TYPENAME singular form - // and try with that ... - if ( ! forceSingularTypeNames && mShared->mWFSVersion.startsWith( '2' ) ) - { - return QgsWFSProvider::describeFeatureType( geometryAttribute, - fields, - geomType, - true ); - } QgsMessageLog::logMessage( tr( "Analysis of DescribeFeatureType response failed for url %1: %2" ). arg( dataSourceUri(), errorMsg ), tr( "WFS" ) ); return false; } + return true; } @@ -1479,8 +1467,7 @@ QDomElement QgsWFSProvider::createTransactionElement( QDomDocument &doc ) const describeFeatureTypeURL.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) ); } describeFeatureTypeURL.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) ); - //TODO: proper support of 2.0.0, for now hardcoded - describeFeatureTypeURL.addQueryItem( QgsWFSUtils::typeNameParameterForVersion( WfsVersion ).toUpper(), mShared->mURI.typeName() ); + describeFeatureTypeURL.addQueryItem( QStringLiteral( "TYPENAME" ), mShared->mURI.typeName() ); transactionElem.setAttribute( QStringLiteral( "xsi:schemaLocation" ), mApplicationNamespace + ' ' + describeFeatureTypeURL.toEncoded() ); diff --git a/src/providers/wfs/qgswfsprovider.h b/src/providers/wfs/qgswfsprovider.h index 5a82f5d8c47c..3b7f9119c17f 100644 --- a/src/providers/wfs/qgswfsprovider.h +++ b/src/providers/wfs/qgswfsprovider.h @@ -151,7 +151,7 @@ class QgsWFSProvider : public QgsVectorDataProvider The method gives back the name of the geometry attribute and the thematic attributes with their types*/ bool describeFeatureType( QString &geometryAttribute, - QgsFields &fields, QgsWkbTypes::Type &geomType, bool forceSingularTypeNames = false ); + QgsFields &fields, QgsWkbTypes::Type &geomType ); /** * For a given typename, reads the name of the geometry attribute, the diff --git a/src/providers/wfs/qgswfsutils.cpp b/src/providers/wfs/qgswfsutils.cpp index fe362ea3f955..de946fa255d8 100644 --- a/src/providers/wfs/qgswfsutils.cpp +++ b/src/providers/wfs/qgswfsutils.cpp @@ -138,19 +138,6 @@ bool QgsWFSUtils::removeDir( const QString &dirName ) return dir.rmdir( dirName ); } -QString QgsWFSUtils::typeNameParameterForVersion( const QString &WfsVersion ) -{ - // WFS 2.0 uses the plural form TYPENAMES - if ( WfsVersion.startsWith( '2' ) ) - { - return QString( "typeNames" ); - } - else - { - return QString( "typeName" ); - } -} - // We use a keep alive mechanism where every KEEP_ALIVE_DELAY ms we update // a shared memory segment with the current timestamp. This way, other QGIS diff --git a/src/providers/wfs/qgswfsutils.h b/src/providers/wfs/qgswfsutils.h index eedd12b578cb..bd984da6d591 100644 --- a/src/providers/wfs/qgswfsutils.h +++ b/src/providers/wfs/qgswfsutils.h @@ -45,9 +45,6 @@ class QgsWFSUtils //! Return a unique identifier made from feature content static QString getMD5( const QgsFeature &f ); - //! Return the correct form of typeName(s) according to the specified \a WfsVersion - static QString typeNameParameterForVersion( const QString &WfsVersion ); - protected: friend class QgsWFSUtilsKeepAlive; static QSharedMemory *createAndAttachSHM(); @@ -65,7 +62,6 @@ class QgsWFSUtils //! Remove (recursively) a directory. static bool removeDir( const QString &dirName ); - }; //! For internal use of QgsWFSUtils diff --git a/tests/src/python/test_provider_wfs.py b/tests/src/python/test_provider_wfs.py index 5d8e6dc60668..f00d4698416c 100644 --- a/tests/src/python/test_provider_wfs.py +++ b/tests/src/python/test_provider_wfs.py @@ -15,8 +15,8 @@ import hashlib import os import re -import tempfile import shutil +import tempfile # Needed on Qt 5 so that the serialization of XML is consistent among all executions os.environ['QT_HASH_SEED'] = '1' @@ -106,7 +106,7 @@ def setUpClass(cls): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -998,7 +998,7 @@ def testWFS20Paging(self): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -1321,7 +1321,7 @@ def testWFS20TruncatedResponse(self): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -1600,7 +1600,7 @@ def testJoins(self): """ - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename,my:othertypename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename,my:othertypename'), 'wb') as f: f.write(schema.encode('UTF-8')) with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename,my:othertypename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= @@ -1666,7 +1666,7 @@ def testJoins(self): # main table not appearing in first - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:othertypename,my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:othertypename,my:typename'), 'wb') as f: f.write(schema.encode('UTF-8')) vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='2.0.0' sql=SELECT * FROM othertypename o, typename WHERE typename.id = o.main_id AND typename.id > 0 ORDER BY typename.id DESC", 'test', 'WFS') @@ -1716,7 +1716,7 @@ def testJoins(self): self.assertEqual(len(fields), 1, fields) self.assertEqual(fields[0].name(), 'id') - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(schema.encode('UTF-8')) # Duplicate fields @@ -1920,7 +1920,7 @@ def testFunctionValidation(self): f.write(schema.encode('UTF-8')) with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.1.0&TYPENAME=my:typename'), 'wb') as f: f.write(schema.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(schema.encode('UTF-8')) # Existing function and validation enabled @@ -1985,7 +1985,7 @@ def testSelectDistinct(self): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2102,7 +2102,7 @@ def testWrongCapabilityExtent(self): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2210,7 +2210,7 @@ def testGeomedia(self): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2506,7 +2506,7 @@ def testWFS20TransactionsDisabled(self): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2582,7 +2582,7 @@ def testWFS20TransactionsEnabled(self): """.format(endpoint=endpoint).encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2698,7 +2698,7 @@ def testGetFeatureWithNamespaces(self): """.encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2746,155 +2746,299 @@ def testExtent(self): self.assertAlmostEqual(provider_extent.yMinimum(), 66.33, 3) self.assertAlmostEqual(provider_extent.yMaximum(), 78.3, 3) - def testWfs20DescribeFeatureTypeSingularForm(self): - """Specs are inconsistent and some 2.0 servers use the TYPENAME singular form""" - - endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_describefeaturetype_singular_form' + def testWFS10DCP(self): + """Test a server with different DCP endpoints""" + endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS_DCP_1.0' + endpoint_alternate = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS_DCP_1.0_alternate' - with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=2.0.0'), 'wb') as f: + with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=1.0.0'), 'wb') as f: f.write(""" - + my:typename Title Abstract - urn:ogc:def:crs:EPSG::4326 - - -71.123 66.33 - -65.32 78.3 - + EPSG:32631 + + -""".encode('UTF-8')) - - # plural form not supported! - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: - f.write(b"") + + + + + + + + + + + + + + + + + + """.format(endpoint_alternate).encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: + with open(sanitize(endpoint_alternate, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.0.0&TYPENAME=my:typename'), 'wb') as f: f.write(""" - - + + - - - - - - - - - + + + + + + + - """.encode('UTF-8')) - # Create test layer - vl = QgsVectorLayer("url='http://" + endpoint + "' version='2.0.0' typename='my:typename'", 'test', 'WFS') + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.0.0'", 'test', 'WFS') self.assertTrue(vl.isValid()) + self.assertEqual(vl.wkbType(), QgsWkbTypes.Point) + self.assertEqual(len(vl.fields()), 5) + self.assertEqual(vl.featureCount(), 0) + reference = QgsGeometry.fromRect(QgsRectangle(400000.0, 5400000.0, 450000.0, 5500000.0)) + vl_extent = QgsGeometry.fromRect(vl.extent()) + assert QgsGeometry.compare(vl_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001), 'Expected {}, got {}'.format(reference.asWkt(), vl_extent.asWkt()) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: + with open(sanitize(endpoint_alternate, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631'), 'wb') as f: f.write(""" - - - - 66.33 -70.33266.33 -70.332 - 66.33 -70.332 - 1 - 100 - Orange - oranGe - 1 - - - - - 70.8 -68.270.8 -68.2 - 70.8 -68.2 - 2 - 200 - Apple - Apple - 2 - - - - - 78.3 -65.3278.3 -65.32 - 78.3 -65.32 - 4 - 400 - Honey - Honey - 4 - - - - - 3 - 300 - Pear - PEaR - 3 - - - - - 78.23 -71.12378.23 -71.123 - 78.23 -71.123 - 5 - -200 - NuLl - 5 - - -""".encode('UTF-8')) + + unknown + + + + 426858,5427937 + + 1 + 2 + 1234567890123 + foo + 2016-04-10T12:34:56.789Z + + + """.encode('UTF-8')) - features = list(vl.getFeatures()) - self.assertEqual(len(features), 5) - geom = features[0].geometry() - self.assertAlmostEqual(geom.asPoint().x(), -70.332, 1) - self.assertAlmostEqual(geom.asPoint().y(), 66.33, 1) + # Also test that on file iterator works + os.environ['QGIS_WFS_ITERATOR_TRANSFER_THRESHOLD'] = '0' - def testWfs20SamServer(self): - """Unknown russian WFS 2.0.0 http://geoportal.samregion.ru/wfs12""" + values = [f['INTFIELD'] for f in vl.getFeatures()] + self.assertEqual(values, [1]) - endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_sam' + del os.environ['QGIS_WFS_ITERATOR_TRANSFER_THRESHOLD'] - with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=2.0.0'), 'wb') as f: - f.write(""" + values = [f['GEOMETRY'] for f in vl.getFeatures()] + self.assertEqual(values, [2]) + + values = [f['longfield'] for f in vl.getFeatures()] + self.assertEqual(values, [1234567890123]) + + values = [f['stringfield'] for f in vl.getFeatures()] + self.assertEqual(values, ['foo']) + + values = [f['datetimefield'] for f in vl.getFeatures()] + self.assertEqual(values, [QDateTime(2016, 4, 10, 12, 34, 56, 789, Qt.TimeSpec(Qt.UTC))]) + + got_f = [f for f in vl.getFeatures()] + got = got_f[0].geometry().constGet() + self.assertEqual((got.x(), got.y()), (426858.0, 5427937.0)) + + self.assertEqual(vl.featureCount(), 1) + + self.assertEqual(vl.dataProvider().capabilities(), QgsVectorDataProvider.SelectAtId) + + (ret, _) = vl.dataProvider().addFeatures([QgsFeature()]) + self.assertFalse(ret) + + self.assertFalse(vl.dataProvider().deleteFeatures([0])) + + # Test with restrictToRequestBBOX=1 + with open(sanitize(endpoint_alternate, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&BBOX=400000,5400000,450000,5500000'), 'wb') as f: + f.write(""" + + unknown + + + + 426858,5427937 + + 100 + + + """.encode('UTF-8')) + + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.0.0' restrictToRequestBBOX=1", 'test', 'WFS') + + extent = QgsRectangle(400000.0, 5400000.0, 450000.0, 5500000.0) + request = QgsFeatureRequest().setFilterRect(extent) + values = [f['INTFIELD'] for f in vl.getFeatures(request)] + self.assertEqual(values, [100]) + + def testWFS10_outputformat_GML3(self): + """Test WFS 1.0 with OUTPUTFORMAT=GML3""" + # We also test attribute fields in upper-case, and a field named GEOMETRY + + endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS1.0_gml3' + + with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=1.0.0'), 'wb') as f: + f.write(""" + + + + + + + + + + + - EC422 + my:typename Title Abstract - urn:ogc:def:crs:EPSG::4326 + EPSG:32631 + + -""".encode('UTF-8')) +""".encode('UTF-8')) - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=EC422'), 'wb') as f: - f.write(""" - - + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.0.0&TYPENAME=my:typename'), 'wb') as f: + f.write(""" + + + + + + + + + + + + + +""".encode('UTF-8')) + + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.0.0'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&OUTPUTFORMAT=GML3'), 'wb') as f: + f.write(""" + + unknown + + + + 426858,5427937 + + + +""".encode('UTF-8')) + + got_f = [f for f in vl.getFeatures()] + got = got_f[0].geometry().constGet() + self.assertEqual((got.x(), got.y()), (426858.0, 5427937.0)) + + # Test with explicit OUTPUTFORMAT as parameter + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.0.0' outputformat='GML2'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&OUTPUTFORMAT=GML2'), 'wb') as f: + f.write(""" + + unknown + + + + 1,2 + + + +""".encode('UTF-8')) + + got_f = [f for f in vl.getFeatures()] + got = got_f[0].geometry().constGet() + self.assertEqual((got.x(), got.y()), (1.0, 2.0)) + + # Test with explicit OUTPUTFORMAT in URL + vl = QgsVectorLayer("url='http://" + endpoint + "?OUTPUTFORMAT=GML2' typename='my:typename' version='1.0.0'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&OUTPUTFORMAT=GML2'), 'wb') as f: + f.write(""" + + unknown + + + + 3,4 + + + +""".encode('UTF-8')) + + got_f = [f for f in vl.getFeatures()] + got = got_f[0].geometry().constGet() + self.assertEqual((got.x(), got.y()), (3.0, 4.0)) + + def testWfs20SamServer(self): + """Unknown russian WFS 2.0.0 http://geoportal.samregion.ru/wfs12""" + + endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_sam' + + with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=2.0.0'), 'wb') as f: + f.write(""" + + + EC422 + Title + Abstract + urn:ogc:def:crs:EPSG::4326 + + +""".encode('UTF-8')) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=EC422'), 'wb') as f: + f.write(""" + + @@ -2919,7 +3063,7 @@ def testWfs20SamServer(self): - + @@ -2952,7 +3096,7 @@ def testWfs20SamServer(self): http://schemas.opengis.net/wfs/2.0.0/wfs.xsd http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd"> - + 13172455 @@ -2966,7 +3110,7 @@ def testWfs20SamServer(self): - + checked @@ -2978,17 +3122,17 @@ def testWfs20SamServer(self): false - + - 9540051.88156246 5997366.8135842243 9539934.21894572 5997127.7749966066 9539822.1483417116 5996862.6127466606 9539504.179093 5996097.3096572906 9539529.9650254529 5996093.5547346519 9539584.6281441431 5996148.1036405731 9539709.8322301712 5996306.1476564864 9539514.2094426583 5996393.6699969191 9539315.5400513224 5996461.6283053206 9539418.05506746 5996708.1687648129 9539601.002140563 5996948.5162237845 9539715.6644945163 5997103.3323533693 9539806.8714339714 5997185.6346932752 9539980.407018993 5997401.3173021814 9540034.9262902886 5997461.1185212145 9540144.520062916 5997647.082066942 9540205.6388517376 5997752.865820759 9540413.93051952 5998022.2412844934 9540636.78114721 5998289.1650444875 9540652.2228743583 5998292.3310790323 9540739.3873799425 5998253.7093249289 9540742.8882315382 5998264.8666193094 9540928.0440610871 5998447.0783388717 9540964.2606249321 5998465.5247420967 9540992.4471577276 5998468.9503919454 9541266.994057592 5998700.44280944 9541489.2456994727 5998870.8459224468 9542015.9473830853 5999245.7052185535 9542481.7197104339 5999585.458734531 9542594.2400093842 5999581.2418252891 9542791.0368823726 5999731.6623752853 9543204.6598267779 6000066.4150706194 9543245.7990274262 6000086.6195615921 9543303.6317887139 6000098.2128836326 9543392.7859923933 6000084.2088917186 9543473.2299142312 6000041.19114427 9543582.34122052 5999959.7280100482 9543796.5102230646 5999788.4518707721 9544237.3357650079 6000148.6245372053 9544242.356376797 6000146.87913009 + 9540051.88156246 5997366.8135842243 9539934.21894572 5997127.7749966066 9539822.1483417116 5996862.6127466606 9539504.179093 5996097.3096572906 9539529.9650254529 5996093.5547346519 9539584.6281441431 5996148.1036405731 9539709.8322301712 5996306.1476564864 9539514.2094426583 5996393.6699969191 9539315.5400513224 5996461.6283053206 9539418.05506746 5996708.1687648129 9539601.002140563 5996948.5162237845 9539715.6644945163 5997103.3323533693 9539806.8714339714 5997185.6346932752 9539980.407018993 5997401.3173021814 9540034.9262902886 5997461.1185212145 9540144.520062916 5997647.082066942 9540205.6388517376 5997752.865820759 9540413.93051952 5998022.2412844934 9540636.78114721 5998289.1650444875 9540652.2228743583 5998292.3310790323 9540739.3873799425 5998253.7093249289 9540742.8882315382 5998264.8666193094 9540928.0440610871 5998447.0783388717 9540964.2606249321 5998465.5247420967 9540992.4471577276 5998468.9503919454 9541266.994057592 5998700.44280944 9541489.2456994727 5998870.8459224468 9542015.9473830853 5999245.7052185535 9542481.7197104339 5999585.458734531 9542594.2400093842 5999581.2418252891 9542791.0368823726 5999731.6623752853 9543204.6598267779 6000066.4150706194 9543245.7990274262 6000086.6195615921 9543303.6317887139 6000098.2128836326 9543392.7859923933 6000084.2088917186 9543473.2299142312 6000041.19114427 9543582.34122052 5999959.7280100482 9543796.5102230646 5999788.4518707721 9544237.3357650079 6000148.6245372053 9544242.356376797 6000146.87913009 - + - + 13172458 @@ -3002,7 +3146,7 @@ def testWfs20SamServer(self): - + checked @@ -3014,17 +3158,17 @@ def testWfs20SamServer(self): false - + - 9540865.6444970388 5998183.9317809641 9540775.852046784 5997947.0331188506 9540680.5655983184 5997718.6045682346 9540569.58023185 5997466.57064837 9540466.8184371851 5997200.4314374486 9540244.6014337484 5996676.7638938017 9540169.35653367 5996705.7142945267 9540148.0577711649 5996682.1794316517 9540111.4476553015 5996665.8381209867 9540077.9721918479 5996676.0606173435 9540043.4139141534 5996711.499830937 9540043.9248592574 5996752.0983768944 9540045.5009976458 5996766.0802566176 9539966.0914670844 5996797.8834326165 9539818.507223323 5996864.1487550773 9539610.9541244339 5996949.3710925905 + 9540865.6444970388 5998183.9317809641 9540775.852046784 5997947.0331188506 9540680.5655983184 5997718.6045682346 9540569.58023185 5997466.57064837 9540466.8184371851 5997200.4314374486 9540244.6014337484 5996676.7638938017 9540169.35653367 5996705.7142945267 9540148.0577711649 5996682.1794316517 9540111.4476553015 5996665.8381209867 9540077.9721918479 5996676.0606173435 9540043.4139141534 5996711.499830937 9540043.9248592574 5996752.0983768944 9540045.5009976458 5996766.0802566176 9539966.0914670844 5996797.8834326165 9539818.507223323 5996864.1487550773 9539610.9541244339 5996949.3710925905 - + - + 13172454 @@ -3038,7 +3182,7 @@ def testWfs20SamServer(self): - + checked @@ -3050,13 +3194,13 @@ def testWfs20SamServer(self): false - + - 9542485.8345187921 5998971.7039023517 9542453.32676163 5998980.1340847686 9542423.4887301736 5998965.47791457 9541839.9022341352 5998539.8925312571 9541447.9249842446 5998260.2124332683 9541414.5872483123 5998203.50518699 9541330.9900199063 5998001.1229150137 9540804.3199365921 5996757.0503186928 9540212.17485467 5997003.9579582512 9539930.5850430559 5997125.116400606 9539816.1399206612 5997176.6829545 9539806.7162561137 5997183.794419908 + 9542485.8345187921 5998971.7039023517 9542453.32676163 5998980.1340847686 9542423.4887301736 5998965.47791457 9541839.9022341352 5998539.8925312571 9541447.9249842446 5998260.2124332683 9541414.5872483123 5998203.50518699 9541330.9900199063 5998001.1229150137 9540804.3199365921 5996757.0503186928 9540212.17485467 5997003.9579582512 9539930.5850430559 5997125.116400606 9539816.1399206612 5997176.6829545 9539806.7162561137 5997183.794419908 - + @@ -3077,273 +3221,6 @@ def testWfs20SamServer(self): geom_string = re.sub(r'\.\d+', '', geom_string)[:100] self.assertEqual(geom_string, "LineString (9540051 5997366, 9539934 5997127, 9539822 5996862, 9539504 5996097, 9539529 5996093, 953") - def testWFS10DCP(self): - """Test a server with different DCP endpoints""" - endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS_DCP_1.0' - endpoint_alternate = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS_DCP_1.0_alternate' - - with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=1.0.0'), 'wb') as f: - f.write(""" - - - - my:typename - Title - Abstract - EPSG:32631 - - - - - - - - - - - - - - - - - - - - - - """.format(endpoint_alternate).encode('UTF-8')) - - with open(sanitize(endpoint_alternate, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.0.0&TYPENAME=my:typename'), 'wb') as f: - f.write(""" - - - - - - - - - - - - - - - - - - - -""".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) - self.assertEqual(len(vl.fields()), 5) - self.assertEqual(vl.featureCount(), 0) - reference = QgsGeometry.fromRect(QgsRectangle(400000.0, 5400000.0, 450000.0, 5500000.0)) - vl_extent = QgsGeometry.fromRect(vl.extent()) - assert QgsGeometry.compare(vl_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001), 'Expected {}, got {}'.format(reference.asWkt(), vl_extent.asWkt()) - - with open(sanitize(endpoint_alternate, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631'), 'wb') as f: - f.write(""" - - unknown - - - - 426858,5427937 - - 1 - 2 - 1234567890123 - foo - 2016-04-10T12:34:56.789Z - - - """.encode('UTF-8')) - - # Also test that on file iterator works - os.environ['QGIS_WFS_ITERATOR_TRANSFER_THRESHOLD'] = '0' - - values = [f['INTFIELD'] for f in vl.getFeatures()] - self.assertEqual(values, [1]) - - del os.environ['QGIS_WFS_ITERATOR_TRANSFER_THRESHOLD'] - - values = [f['GEOMETRY'] for f in vl.getFeatures()] - self.assertEqual(values, [2]) - - values = [f['longfield'] for f in vl.getFeatures()] - self.assertEqual(values, [1234567890123]) - - values = [f['stringfield'] for f in vl.getFeatures()] - self.assertEqual(values, ['foo']) - - values = [f['datetimefield'] for f in vl.getFeatures()] - self.assertEqual(values, [QDateTime(2016, 4, 10, 12, 34, 56, 789, Qt.TimeSpec(Qt.UTC))]) - - got_f = [f for f in vl.getFeatures()] - got = got_f[0].geometry().constGet() - self.assertEqual((got.x(), got.y()), (426858.0, 5427937.0)) - - self.assertEqual(vl.featureCount(), 1) - - self.assertEqual(vl.dataProvider().capabilities(), QgsVectorDataProvider.SelectAtId) - - (ret, _) = vl.dataProvider().addFeatures([QgsFeature()]) - self.assertFalse(ret) - - self.assertFalse(vl.dataProvider().deleteFeatures([0])) - - # Test with restrictToRequestBBOX=1 - with open(sanitize(endpoint_alternate, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&BBOX=400000,5400000,450000,5500000'), 'wb') as f: - f.write(""" - - unknown - - - - 426858,5427937 - - 100 - - - """.encode('UTF-8')) - - vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.0.0' restrictToRequestBBOX=1", 'test', 'WFS') - - extent = QgsRectangle(400000.0, 5400000.0, 450000.0, 5500000.0) - request = QgsFeatureRequest().setFilterRect(extent) - values = [f['INTFIELD'] for f in vl.getFeatures(request)] - self.assertEqual(values, [100]) - - def testWFS10_outputformat_GML3(self): - """Test WFS 1.0 with OUTPUTFORMAT=GML3""" - # We also test attribute fields in upper-case, and a field named GEOMETRY - - endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS1.0_gml3' - - with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?VERSION=1.0.0'), 'wb') as f: - f.write(""" - - - - - - - - - - - - - - my:typename - Title - Abstract - EPSG:32631 - - - - -""".encode('UTF-8')) - - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=1.0.0&TYPENAME=my:typename'), 'wb') as f: - f.write(""" - - - - - - - - - - - - - -""".encode('UTF-8')) - - vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.0.0'", 'test', 'WFS') - self.assertTrue(vl.isValid()) - - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&OUTPUTFORMAT=GML3'), 'wb') as f: - f.write(""" - - unknown - - - - 426858,5427937 - - - -""".encode('UTF-8')) - - got_f = [f for f in vl.getFeatures()] - got = got_f[0].geometry().constGet() - self.assertEqual((got.x(), got.y()), (426858.0, 5427937.0)) - - # Test with explicit OUTPUTFORMAT as parameter - vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.0.0' outputformat='GML2'", 'test', 'WFS') - self.assertTrue(vl.isValid()) - - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&OUTPUTFORMAT=GML2'), 'wb') as f: - f.write(""" - - unknown - - - - 1,2 - - - -""".encode('UTF-8')) - - got_f = [f for f in vl.getFeatures()] - got = got_f[0].geometry().constGet() - self.assertEqual((got.x(), got.y()), (1.0, 2.0)) - - # Test with explicit OUTPUTFORMAT in URL - vl = QgsVectorLayer("url='http://" + endpoint + "?OUTPUTFORMAT=GML2' typename='my:typename' version='1.0.0'", 'test', 'WFS') - self.assertTrue(vl.isValid()) - - with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&TYPENAME=my:typename&SRSNAME=EPSG:32631&OUTPUTFORMAT=GML2'), 'wb') as f: - f.write(""" - - unknown - - - - 3,4 - - - -""".encode('UTF-8')) - - got_f = [f for f in vl.getFeatures()] - got = got_f[0].geometry().constGet() - self.assertEqual((got.x(), got.y()), (3.0, 4.0)) - if __name__ == '__main__': unittest.main()