diff --git a/python/gui/auto_generated/qgsnewhttpconnection.sip.in b/python/gui/auto_generated/qgsnewhttpconnection.sip.in index 37b0a2e24577..6e6429f6b32f 100644 --- a/python/gui/auto_generated/qgsnewhttpconnection.sip.in +++ b/python/gui/auto_generated/qgsnewhttpconnection.sip.in @@ -92,6 +92,11 @@ Returns the "test connection" button. .. versionadded:: 3.0 %End + + + + + virtual QString wfsSettingsKey( const QString &base, const QString &connectionName ) const; %Docstring Returns the QSettings key for WFS related settings for the connection. diff --git a/src/gui/qgsnewhttpconnection.cpp b/src/gui/qgsnewhttpconnection.cpp index 63d83f6f9788..820e1fd65fa9 100644 --- a/src/gui/qgsnewhttpconnection.cpp +++ b/src/gui/qgsnewhttpconnection.cpp @@ -62,10 +62,16 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ cmbDpiMode->addItem( tr( "GeoServer" ) ); cmbVersion->clear(); - cmbVersion->addItem( tr( "Auto-detect" ) ); + cmbVersion->addItem( tr( "Maximum" ) ); cmbVersion->addItem( tr( "1.0" ) ); cmbVersion->addItem( tr( "1.1" ) ); cmbVersion->addItem( tr( "2.0" ) ); + connect( cmbVersion, + static_cast( &QComboBox::currentIndexChanged ), + this, &QgsNewHttpConnection::wfsVersionCurrentIndexChanged ); + + connect( cbxWfsFeaturePaging, &QCheckBox::stateChanged, + this, &QgsNewHttpConnection::wfsFeaturePagingStateChanged ); if ( !connectionName.isEmpty() ) { @@ -86,6 +92,7 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ mAuthSettings->setPassword( settings.value( credentialsKey + "/password" ).toString() ); mAuthSettings->setConfigId( settings.value( credentialsKey + "/authcfg" ).toString() ); } + mWfsVersionDetectButton->setDisabled( txtUrl->text().isEmpty() ); if ( !( mTypes & ConnectionWms ) && !( mTypes & ConnectionWcs ) ) { @@ -148,6 +155,20 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ nameChanged( connectionName ); } +void QgsNewHttpConnection::wfsVersionCurrentIndexChanged( int index ) +{ + cbxWfsFeaturePaging->setEnabled( index == 0 || index == 3 ); + lblPageSize->setEnabled( index == 0 || index == 3 ); + txtPageSize->setEnabled( index == 0 || index == 3 ); + cbxWfsIgnoreAxisOrientation->setEnabled( index != 1 ); +} + +void QgsNewHttpConnection::wfsFeaturePagingStateChanged( int state ) +{ + lblPageSize->setEnabled( state == Qt::Checked ); + txtPageSize->setEnabled( state == Qt::Checked ); +} + QString QgsNewHttpConnection::name() const { return txtName->text(); @@ -168,6 +189,7 @@ void QgsNewHttpConnection::urlChanged( const QString &text ) { Q_UNUSED( text ); buttonBox->button( QDialogButtonBox::Ok )->setDisabled( txtName->text().isEmpty() || txtUrl->text().isEmpty() ); + mWfsVersionDetectButton->setDisabled( txtUrl->text().isEmpty() ); } void QgsNewHttpConnection::updateOkButtonState() @@ -209,6 +231,26 @@ QPushButton *QgsNewHttpConnection::testConnectButton() return mTestConnectionButton; } +QPushButton *QgsNewHttpConnection::wfsVersionDetectButton() +{ + return mWfsVersionDetectButton; +} + +QComboBox *QgsNewHttpConnection::wfsVersionComboBox() +{ + return cmbVersion; +} + +QCheckBox *QgsNewHttpConnection::wfsPagingEnabledCheckBox() +{ + return cbxWfsFeaturePaging; +} + +QLineEdit *QgsNewHttpConnection::wfsPageSizeLineEdit() +{ + return txtPageSize; +} + QString QgsNewHttpConnection::wfsSettingsKey( const QString &base, const QString &connectionName ) const { return base + connectionName; @@ -266,24 +308,18 @@ void QgsNewHttpConnection::updateServiceSpecificSettings() txtReferer->setText( settings.value( wmsKey + "/referer" ).toString() ); txtMaxNumFeatures->setText( settings.value( wfsKey + "/maxnumfeatures" ).toString() ); -} -void QgsNewHttpConnection::accept() -{ - QgsSettings settings; - QString key = mBaseKey + txtName->text(); - QString credentialsKey = "qgis/" + mCredentialsBaseKey + '/' + txtName->text(); + bool pagingEnabled = settings.value( wfsKey + "/pagingenabled", true ).toBool(); + txtPageSize->setText( settings.value( wfsKey + "/pagesize" ).toString() ); + cbxWfsFeaturePaging->setChecked( pagingEnabled ); - if ( !validate() ) - return; + txtPageSize->setEnabled( pagingEnabled ); + lblPageSize->setEnabled( pagingEnabled ); + cbxWfsFeaturePaging->setEnabled( pagingEnabled ); +} - // on rename delete original entry first - if ( !mOriginalConnName.isNull() && mOriginalConnName != key ) - { - settings.remove( mBaseKey + mOriginalConnName ); - settings.remove( "qgis/" + mCredentialsBaseKey + '/' + mOriginalConnName ); - settings.sync(); - } +QUrl QgsNewHttpConnection::urlTrimmed() const +{ QUrl url( txtUrl->text().trimmed() ); const QList< QPair > &items = url.encodedQueryItems(); @@ -306,7 +342,27 @@ void QgsNewHttpConnection::accept() { url.setEncodedPath( "/" ); } + return url; +} +void QgsNewHttpConnection::accept() +{ + QgsSettings settings; + QString key = mBaseKey + txtName->text(); + QString credentialsKey = "qgis/" + mCredentialsBaseKey + '/' + txtName->text(); + + if ( !validate() ) + return; + + // on rename delete original entry first + if ( !mOriginalConnName.isNull() && mOriginalConnName != key ) + { + settings.remove( mBaseKey + mOriginalConnName ); + settings.remove( "qgis/" + mCredentialsBaseKey + '/' + mOriginalConnName ); + settings.sync(); + } + + QUrl url( urlTrimmed() ); settings.setValue( key + "/url", url.toString() ); QString wfsKey = wfsSettingsKey( mBaseKey, txtName->text() ); @@ -374,6 +430,9 @@ void QgsNewHttpConnection::accept() settings.setValue( wfsKey + "/version", version ); settings.setValue( wfsKey + "/maxnumfeatures", txtMaxNumFeatures->text() ); + + settings.setValue( wfsKey + "/pagesize", txtPageSize->text() ); + settings.setValue( wfsKey + "/pagingenabled", cbxWfsFeaturePaging->isChecked() ); } settings.setValue( credentialsKey + "/username", mAuthSettings->username() ); diff --git a/src/gui/qgsnewhttpconnection.h b/src/gui/qgsnewhttpconnection.h index c495a7762db4..fc079a3dd893 100644 --- a/src/gui/qgsnewhttpconnection.h +++ b/src/gui/qgsnewhttpconnection.h @@ -97,6 +97,8 @@ class GUI_EXPORT QgsNewHttpConnection : public QDialog, private Ui::QgsNewHttpCo void nameChanged( const QString & ); void urlChanged( const QString & ); void updateOkButtonState(); + void wfsVersionCurrentIndexChanged( int index ); + void wfsFeaturePagingStateChanged( int state ); protected: @@ -113,6 +115,36 @@ class GUI_EXPORT QgsNewHttpConnection : public QDialog, private Ui::QgsNewHttpCo */ QPushButton *testConnectButton(); + /** + * Returns the "WFS version detect" button. + * \since QGIS 3.2 + */ + QPushButton *wfsVersionDetectButton() SIP_SKIP; + + /** + * Returns the "WFS version" combobox. + * \since QGIS 3.2 + */ + QComboBox *wfsVersionComboBox() SIP_SKIP; + + /** + * Returns the "WFS paging enabled" checkbox + * \since QGIS 3.2 + */ + QCheckBox *wfsPagingEnabledCheckBox() SIP_SKIP; + + /** + * Returns the "WFS page size" edit + * \since QGIS 3.2 + */ + QLineEdit *wfsPageSizeLineEdit() SIP_SKIP; + + /** + * Returns the url. + * \since QGIS 3.2 + */ + QUrl urlTrimmed() const SIP_SKIP; + /** * Returns the QSettings key for WFS related settings for the connection. * \see wmsSettingsKey() diff --git a/src/providers/wfs/CMakeLists.txt b/src/providers/wfs/CMakeLists.txt index 2387416f8450..fe0cc75676c5 100644 --- a/src/providers/wfs/CMakeLists.txt +++ b/src/providers/wfs/CMakeLists.txt @@ -33,9 +33,11 @@ SET (WFS_MOC_HDRS IF (WITH_GUI) SET(WFS_SRCS ${WFS_SRCS} qgswfssourceselect.cpp + qgswfsnewconnection.cpp ) SET(WFS_MOC_HDRS ${WFS_MOC_HDRS} qgswfssourceselect.h + qgswfsnewconnection.h ) ENDIF () diff --git a/src/providers/wfs/qgswfsconnection.cpp b/src/providers/wfs/qgswfsconnection.cpp index e25a004ac8b3..c73d8113d1e1 100644 --- a/src/providers/wfs/qgswfsconnection.cpp +++ b/src/providers/wfs/qgswfsconnection.cpp @@ -39,6 +39,20 @@ QgsWfsConnection::QgsWfsConnection( const QString &connName ) mUri.setParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES, maxnumfeatures ); } + const QString &pagesize = settings.value( key + "/" + QgsWFSConstants::SETTINGS_PAGE_SIZE ).toString(); + if ( !pagesize.isEmpty() ) + { + mUri.removeParam( QgsWFSConstants::URI_PARAM_PAGE_SIZE ); // setParam allow for duplicates! + mUri.setParam( QgsWFSConstants::URI_PARAM_PAGE_SIZE, pagesize ); + } + + if ( settings.contains( key + "/" + QgsWFSConstants::SETTINGS_PAGING_ENABLED ) ) + { + mUri.removeParam( QgsWFSConstants::URI_PARAM_PAGING_ENABLED ); // setParam allow for duplicates! + mUri.setParam( QgsWFSConstants::URI_PARAM_PAGING_ENABLED, + settings.value( key + "/" + QgsWFSConstants::SETTINGS_PAGING_ENABLED, true ).toBool() ? "true" : "false" ); + } + QgsDebugMsg( QString( "WFS full uri: '%1'." ).arg( QString( mUri.uri() ) ) ); } diff --git a/src/providers/wfs/qgswfsconstants.cpp b/src/providers/wfs/qgswfsconstants.cpp index 02d09127da88..c6fde5fb8f85 100644 --- a/src/providers/wfs/qgswfsconstants.cpp +++ b/src/providers/wfs/qgswfsconstants.cpp @@ -38,12 +38,16 @@ const QString QgsWFSConstants::URI_PARAM_IGNOREAXISORIENTATION( QStringLiteral( const QString QgsWFSConstants::URI_PARAM_INVERTAXISORIENTATION( QStringLiteral( "InvertAxisOrientation" ) ); const QString QgsWFSConstants::URI_PARAM_VALIDATESQLFUNCTIONS( QStringLiteral( "validateSQLFunctions" ) ); const QString QgsWFSConstants::URI_PARAM_HIDEDOWNLOADPROGRESSDIALOG( QStringLiteral( "hideDownloadProgressDialog" ) ); +const QString QgsWFSConstants::URI_PARAM_PAGING_ENABLED( "pagingEnabled" ); +const QString QgsWFSConstants::URI_PARAM_PAGE_SIZE( "pageSize" ); const QString QgsWFSConstants::VERSION_AUTO( QStringLiteral( "auto" ) ); const QString QgsWFSConstants::CONNECTIONS_WFS( QStringLiteral( "qgis/connections-wfs/" ) ); const QString QgsWFSConstants::SETTINGS_VERSION( QStringLiteral( "version" ) ); const QString QgsWFSConstants::SETTINGS_MAXNUMFEATURES( QStringLiteral( "maxnumfeatures" ) ); +const QString QgsWFSConstants::SETTINGS_PAGING_ENABLED( QStringLiteral( "pagingenabled" ) ); +const QString QgsWFSConstants::SETTINGS_PAGE_SIZE( QStringLiteral( "pagesize" ) ); const QString QgsWFSConstants::FIELD_GEN_COUNTER( QStringLiteral( "__qgis_gen_counter" ) ); const QString QgsWFSConstants::FIELD_GMLID( QStringLiteral( "__qgis_gmlid" ) ); diff --git a/src/providers/wfs/qgswfsconstants.h b/src/providers/wfs/qgswfsconstants.h index c5d99a24a267..f3360dc58a5b 100644 --- a/src/providers/wfs/qgswfsconstants.h +++ b/src/providers/wfs/qgswfsconstants.h @@ -46,6 +46,8 @@ struct QgsWFSConstants static const QString URI_PARAM_INVERTAXISORIENTATION; static const QString URI_PARAM_VALIDATESQLFUNCTIONS; static const QString URI_PARAM_HIDEDOWNLOADPROGRESSDIALOG; + static const QString URI_PARAM_PAGING_ENABLED; + static const QString URI_PARAM_PAGE_SIZE; // static const QString VERSION_AUTO; @@ -54,6 +56,8 @@ struct QgsWFSConstants static const QString CONNECTIONS_WFS; static const QString SETTINGS_VERSION; static const QString SETTINGS_MAXNUMFEATURES; + static const QString SETTINGS_PAGING_ENABLED; + static const QString SETTINGS_PAGE_SIZE; // Special fields of the cache static const QString FIELD_GEN_COUNTER; diff --git a/src/providers/wfs/qgswfsdatasourceuri.cpp b/src/providers/wfs/qgswfsdatasourceuri.cpp index 4702bd8bacf5..86123686c00e 100644 --- a/src/providers/wfs/qgswfsdatasourceuri.cpp +++ b/src/providers/wfs/qgswfsdatasourceuri.cpp @@ -207,6 +207,20 @@ void QgsWFSDataSourceURI::setMaxNumFeatures( int maxNumFeatures ) mURI.setParam( QgsWFSConstants::URI_PARAM_MAXNUMFEATURES, QString( maxNumFeatures ) ); } +int QgsWFSDataSourceURI::pageSize() const +{ + if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_PAGE_SIZE ) ) + return 0; + return mURI.param( QgsWFSConstants::URI_PARAM_PAGE_SIZE ).toInt(); +} + +bool QgsWFSDataSourceURI::pagingEnabled() const +{ + if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_PAGING_ENABLED ) ) + return true; + return mURI.param( QgsWFSConstants::URI_PARAM_PAGING_ENABLED ) == QStringLiteral( "true" ); +} + void QgsWFSDataSourceURI::setTypeName( const QString &typeName ) { mURI.removeParam( QgsWFSConstants::URI_PARAM_TYPENAME ); diff --git a/src/providers/wfs/qgswfsdatasourceuri.h b/src/providers/wfs/qgswfsdatasourceuri.h index 8afeb3be3b80..f2f0cc4dba71 100644 --- a/src/providers/wfs/qgswfsdatasourceuri.h +++ b/src/providers/wfs/qgswfsdatasourceuri.h @@ -102,6 +102,12 @@ class QgsWFSDataSourceURI //! Sets user defined limit of features to download void setMaxNumFeatures( int maxNumFeatures ); + //! Returns user defined limit page size. 0=server udefault + int pageSize() const; + + //! Returns whether paging is enabled. + bool pagingEnabled() const; + //! Gets typename (with prefix) QString typeName() const; diff --git a/src/providers/wfs/qgswfsfeatureiterator.cpp b/src/providers/wfs/qgswfsfeatureiterator.cpp index 26cf0f8b078f..f38a2df9aa14 100644 --- a/src/providers/wfs/qgswfsfeatureiterator.cpp +++ b/src/providers/wfs/qgswfsfeatureiterator.cpp @@ -29,6 +29,7 @@ #include "qgsexception.h" #include "qgsfeedback.h" +#include #include #include #include @@ -81,7 +82,7 @@ QgsWFSFeatureDownloader::QgsWFSFeatureDownloader( QgsWFSSharedData *shared ) , mShared( shared ) , mStop( false ) , mProgressDialogShowImmediately( false ) - , mSupportsPaging( shared->mCaps.supportsPaging ) + , mPageSize( shared->mPageSize ) , mRemoveNSPrefix( false ) , mNumberMatched( -1 ) , mFeatureHitsAsyncRequest( shared->mURI ) @@ -185,7 +186,7 @@ QString QgsWFSFeatureDownloader::sanitizeFilter( QString filter ) return filter; } -QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool forHits ) +QUrl QgsWFSFeatureDownloader::buildURL( qint64 startIndex, int maxFeatures, bool forHits ) { QUrl getFeatureUrl( mShared->mURI.requestUrl( QStringLiteral( "GetFeature" ) ) ); getFeatureUrl.addQueryItem( QStringLiteral( "VERSION" ), mShared->mWFSVersion ); @@ -231,7 +232,7 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo } else if ( maxFeatures > 0 ) { - if ( mSupportsPaging ) + if ( mPageSize > 0 ) { // Note: always include the STARTINDEX, even for zero, has some (likely buggy) // implementations do not return the same results if STARTINDEX=0 is specified @@ -383,6 +384,10 @@ QUrl QgsWFSFeatureDownloader::buildURL( int startIndex, int maxFeatures, bool fo void QgsWFSFeatureDownloader::gotHitsResponse() { mNumberMatched = mFeatureHitsAsyncRequest.numberMatched(); + if ( mShared->mMaxFeatures > 0 ) + { + mNumberMatched = std::min( mNumberMatched, mShared->mMaxFeatures ); + } if ( mNumberMatched >= 0 ) { if ( mTotalDownloadedFeatureCount == 0 ) @@ -456,14 +461,41 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures ) int pagingIter = 1; QString gmlIdFirstFeatureFirstIter; bool disablePaging = false; + qint64 maxTotalFeatures = 0; + if ( maxFeatures > 0 && mShared->mMaxFeatures > 0 ) + { + maxTotalFeatures = std::min( maxFeatures, mShared->mMaxFeatures ); + } + else if ( maxFeatures > 0 ) + { + maxTotalFeatures = maxFeatures; + } + else + { + maxTotalFeatures = mShared->mMaxFeatures; + } // Top level loop to do feature paging in WFS 2.0 while ( true ) { success = true; QgsGmlStreamingParser *parser = mShared->createParser(); + int maxFeaturesThisRequest = static_cast( + std::min( maxTotalFeatures - mTotalDownloadedFeatureCount, + static_cast( std::numeric_limits::max() ) ) ); + if ( mShared->mPageSize > 0 ) + { + if ( maxFeaturesThisRequest > 0 ) + { + maxFeaturesThisRequest = std::min( maxFeaturesThisRequest, mShared->mPageSize ); + } + else + { + maxFeaturesThisRequest = mShared->mPageSize; + } + } QUrl url( buildURL( mTotalDownloadedFeatureCount, - maxFeatures ? maxFeatures : mShared->mMaxFeatures, false ) ); + maxFeaturesThisRequest, false ) ); // Small hack for testing purposes if ( retryIter > 0 && url.toString().contains( QLatin1String( "fake_qgis_http_endpoint" ) ) ) @@ -527,11 +559,11 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures ) // Some GeoServer instances in WFS 2.0 with paging throw an exception // e.g. http://ows.region-bretagne.fr/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=rb:etudes&STARTINDEX=0&COUNT=1 // Disabling paging helps in those cases - if ( mSupportsPaging && mTotalDownloadedFeatureCount == 0 && + if ( mPageSize > 0 && mTotalDownloadedFeatureCount == 0 && parser->exceptionText().contains( QLatin1String( "Cannot do natural order without a primary key" ) ) ) { QgsDebugMsg( QString( "Got exception %1. Re-trying with paging disabled" ).arg( parser->exceptionText() ) ); - mSupportsPaging = false; + mPageSize = 0; } // GeoServer doesn't like typenames prefixed by namespace prefix, despite // the examples in the WFS 2.0 spec showing that @@ -560,11 +592,15 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures ) if ( parser->numberMatched() > 0 && mTotalDownloadedFeatureCount == 0 ) mNumberMatched = parser->numberMatched(); // The number returned can only be used if we aren't in paging mode - else if ( parser->numberReturned() > 0 && !mSupportsPaging ) + else if ( parser->numberReturned() > 0 && mPageSize == 0 ) mNumberMatched = parser->numberMatched(); // We can only use the layer feature count if we don't apply a BBOX else if ( mShared->isFeatureCountExact() && mShared->mRect.isNull() ) mNumberMatched = mShared->getFeatureCount( false ); + if ( mNumberMatched > 0 && mShared->mMaxFeatures > 0 ) + { + mNumberMatched = std::min( mNumberMatched, mShared->mMaxFeatures ); + } // If we didn't get a valid mNumberMatched, we will possibly issue // a explicit RESULTTYPE=hits request 4 second after the beginning of @@ -686,7 +722,7 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures ) if ( finished ) { - if ( parser->isTruncatedResponse() && !mSupportsPaging ) + if ( parser->isTruncatedResponse() && mPageSize == 0 ) { // e.g: http://services.cuzk.cz/wfs/inspire-cp-wfs.asp?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cp:CadastralParcel truncatedResponse = true; @@ -715,19 +751,20 @@ void QgsWFSFeatureDownloader::run( bool serializeFeatures, int maxFeatures ) retryIter = 0; lastValidTotalDownloadedFeatureCount = mTotalDownloadedFeatureCount; - if ( !mSupportsPaging ) + if ( mPageSize == 0 ) break; if ( maxFeatures == 1 ) break; // Detect if we are at the last page - if ( ( mShared->mMaxFeatures > 0 && featureCountForThisResponse < mShared->mMaxFeatures ) || featureCountForThisResponse == 0 ) + if ( ( mShared->mPageSize > 0 && featureCountForThisResponse < mShared->mPageSize ) || featureCountForThisResponse == 0 ) break; ++ pagingIter; if ( disablePaging ) { - mSupportsPaging = mShared->mCaps.supportsPaging = false; + mShared->mPageSize = mPageSize = 0; mTotalDownloadedFeatureCount = 0; - if ( mShared->mMaxFeaturesWasSetFromDefaultForPaging ) + mShared->mPageSize = 0; + if ( mShared->mMaxFeatures == mShared->mURI.maxNumFeatures() ) { mShared->mMaxFeatures = 0; } diff --git a/src/providers/wfs/qgswfsfeatureiterator.h b/src/providers/wfs/qgswfsfeatureiterator.h index 45c26a0bb71f..46dfcc229ad2 100644 --- a/src/providers/wfs/qgswfsfeatureiterator.h +++ b/src/providers/wfs/qgswfsfeatureiterator.h @@ -135,7 +135,7 @@ class QgsWFSFeatureDownloader: public QgsWfsRequest void hideProgressDialog(); private: - QUrl buildURL( int startIndex, int maxFeatures, bool forHits ); + QUrl buildURL( qint64 startIndex, int maxFeatures, bool forHits ); void pushError( const QString &errorMsg ); QString sanitizeFilter( QString filter ); @@ -150,13 +150,13 @@ class QgsWFSFeatureDownloader: public QgsWfsRequest * If the progress dialog should be shown immediately, or if it should be let to QProgressDialog logic to decide when to show it */ bool mProgressDialogShowImmediately; - bool mSupportsPaging; + int mPageSize; bool mRemoveNSPrefix; int mNumberMatched; QWidget *mMainWindow = nullptr; QTimer *mTimer = nullptr; QgsWFSFeatureHitsAsyncRequest mFeatureHitsAsyncRequest; - int mTotalDownloadedFeatureCount; + qint64 mTotalDownloadedFeatureCount; }; //! Downloader thread diff --git a/src/providers/wfs/qgswfsnewconnection.cpp b/src/providers/wfs/qgswfsnewconnection.cpp new file mode 100644 index 000000000000..a7adf6a6e89d --- /dev/null +++ b/src/providers/wfs/qgswfsnewconnection.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** + qgswfsnewconnection.cpp + --------------------- + begin : June 2018 + copyright : (C) 2018 by Even Rouault + email : even.rouault at spatialys.com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgswfsnewconnection.h" + +#include + +QgsWFSNewConnection::QgsWFSNewConnection( QWidget *parent, const QString &connName ): + QgsNewHttpConnection( parent, QgsNewHttpConnection::ConnectionWfs, QgsWFSConstants::CONNECTIONS_WFS, connName ) +{ + connect( wfsVersionDetectButton(), &QPushButton::clicked, this, &QgsWFSNewConnection::versionDetectButton ); +} + +QgsWFSNewConnection::~QgsWFSNewConnection() +{ + if ( mCapabilities ) + { + QApplication::restoreOverrideCursor(); + delete mCapabilities; + } +} + +void QgsWFSNewConnection::versionDetectButton() +{ + delete mCapabilities; + mCapabilities = new QgsWfsCapabilities( urlTrimmed().toString() ); + connect( mCapabilities, &QgsWfsCapabilities::gotCapabilities, this, &QgsWFSNewConnection::capabilitiesReplyFinished ); + const bool synchronous = false; + const bool forceRefresh = true; + if ( mCapabilities->requestCapabilities( synchronous, forceRefresh ) ) + { + QApplication::setOverrideCursor( Qt::WaitCursor ); + } + else + { + QMessageBox *box = new QMessageBox( QMessageBox::Critical, tr( "Error" ), tr( "Could not get capabilities" ), QMessageBox::Ok, this ); + box->setAttribute( Qt::WA_DeleteOnClose ); + box->setModal( true ); + box->open(); + + delete mCapabilities; + mCapabilities = nullptr; + } +} + +void QgsWFSNewConnection::capabilitiesReplyFinished() +{ + if ( !mCapabilities ) + return; + + QApplication::restoreOverrideCursor(); + + QgsWfsCapabilities::ErrorCode err = mCapabilities->errorCode(); + if ( err != QgsWfsCapabilities::NoError ) + { + QString title; + switch ( err ) + { + case QgsWfsCapabilities::NetworkError: + title = tr( "Network Error" ); + break; + case QgsWfsCapabilities::XmlError: + title = tr( "Capabilities document is not valid" ); + break; + case QgsWfsCapabilities::ServerExceptionError: + title = tr( "Server Exception" ); + break; + default: + title = tr( "Error" ); + break; + } + // handle errors + QMessageBox *box = new QMessageBox( QMessageBox::Critical, title, mCapabilities->errorMessage(), QMessageBox::Ok, this ); + box->setAttribute( Qt::WA_DeleteOnClose ); + box->setModal( true ); + box->open(); + + delete mCapabilities; + mCapabilities = nullptr; + return; + } + + const auto &caps = mCapabilities->capabilities(); + int versionIdx = 0; + wfsPageSizeLineEdit()->clear(); + if ( caps.version.startsWith( QLatin1String( "1.0" ) ) ) + { + versionIdx = 1; + } + else if ( caps.version.startsWith( QLatin1String( "1.1" ) ) ) + { + versionIdx = 2; + } + else if ( caps.version.startsWith( QLatin1String( "2.0" ) ) ) + { + versionIdx = 3; + wfsPageSizeLineEdit()->setText( QString::number( caps.maxFeatures ) ); + } + wfsVersionComboBox()->setCurrentIndex( versionIdx ); + wfsPagingEnabledCheckBox()->setChecked( caps.supportsPaging ); + + delete mCapabilities; + mCapabilities = nullptr; +} diff --git a/src/providers/wfs/qgswfsnewconnection.h b/src/providers/wfs/qgswfsnewconnection.h new file mode 100644 index 000000000000..f1e2b4aa488e --- /dev/null +++ b/src/providers/wfs/qgswfsnewconnection.h @@ -0,0 +1,41 @@ +/*************************************************************************** + qgswfsnewconnection.h + --------------------- + begin : June 2018 + copyright : (C) 2018 by Even Rouault + email : even.rouault at spatialys.com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSWFSNEWCONNECTION_H +#define QGSWFSNEWCONNECTION_H + +#include "qgsnewhttpconnection.h" +#include "qgswfsconstants.h" +#include "qgswfscapabilities.h" + +class QgsWFSNewConnection : public QgsNewHttpConnection +{ + Q_OBJECT + + public: + //! Constructor + QgsWFSNewConnection( QWidget *parent = nullptr, const QString &connName = QString::null ); + ~QgsWFSNewConnection(); + + private slots: + void versionDetectButton(); + void capabilitiesReplyFinished(); + + private: + QgsWfsCapabilities *mCapabilities = nullptr; + +}; + +#endif //QGSWFSNEWCONNECTION_H diff --git a/src/providers/wfs/qgswfsprovider.cpp b/src/providers/wfs/qgswfsprovider.cpp index dc732a13f875..37fd93ddf7cc 100644 --- a/src/providers/wfs/qgswfsprovider.cpp +++ b/src/providers/wfs/qgswfsprovider.cpp @@ -1677,17 +1677,46 @@ bool QgsWFSProvider::getCapabilities() } mShared->mWFSVersion = mShared->mCaps.version; - if ( mShared->mURI.maxNumFeatures() > 0 ) + if ( mShared->mURI.maxNumFeatures() > 0 && mShared->mCaps.maxFeatures > 0 ) + { + mShared->mMaxFeatures = std::min( mShared->mURI.maxNumFeatures(), mShared->mCaps.maxFeatures ); + } + else if ( mShared->mURI.maxNumFeatures() > 0 ) + { mShared->mMaxFeatures = mShared->mURI.maxNumFeatures(); - else + } + else if ( mShared->mCaps.maxFeatures > 0 ) + { mShared->mMaxFeatures = mShared->mCaps.maxFeatures; + } + else + { + mShared->mMaxFeatures = 0; + } - if ( mShared->mMaxFeatures <= 0 && mShared->mCaps.supportsPaging ) + if ( mShared->mCaps.supportsPaging && mShared->mURI.pagingEnabled() ) + { + if ( mShared->mURI.pageSize() > 0 ) + { + if ( mShared->mMaxFeatures > 0 ) + { + mShared->mPageSize = std::min( mShared->mURI.pageSize(), mShared->mMaxFeatures ); + } + else + { + mShared->mPageSize = mShared->mURI.pageSize(); + } + } + else + { + QgsSettings settings; + mShared->mPageSize = settings.value( QStringLiteral( "wfs/max_feature_count_if_not_provided" ), "1000" ).toInt(); + QgsDebugMsg( QString( "Server declares paging but does not advertize max feature count and user did not specify it. Using %1" ).arg( mShared->mMaxFeatures ) ); + } + } + else { - QgsSettings settings; - mShared->mMaxFeatures = settings.value( QStringLiteral( "wfs/max_feature_count_if_not_provided" ), "1000" ).toInt(); - mShared->mMaxFeaturesWasSetFromDefaultForPaging = true; - QgsDebugMsg( QString( "Server declares paging but does not advertize max feature count and user did not specify it. Using %1" ).arg( mShared->mMaxFeatures ) ); + mShared->mPageSize = 0; } //find the for this layer diff --git a/src/providers/wfs/qgswfsshareddata.cpp b/src/providers/wfs/qgswfsshareddata.cpp index f11dd6f94b7f..1ef5b83b72b1 100644 --- a/src/providers/wfs/qgswfsshareddata.cpp +++ b/src/providers/wfs/qgswfsshareddata.cpp @@ -39,7 +39,7 @@ QgsWFSSharedData::QgsWFSSharedData( const QString &uri ) : mURI( uri ) , mSourceCRS( 0 ) , mMaxFeatures( 0 ) - , mMaxFeaturesWasSetFromDefaultForPaging( false ) + , mPageSize( 0 ) , mRequestLimit( 0 ) , mHideProgressDialog( mURI.hideDownloadProgressDialog() ) , mDistinctSelect( false ) diff --git a/src/providers/wfs/qgswfsshareddata.h b/src/providers/wfs/qgswfsshareddata.h index 59d3e392630b..ef07036c09ad 100644 --- a/src/providers/wfs/qgswfsshareddata.h +++ b/src/providers/wfs/qgswfsshareddata.h @@ -155,11 +155,11 @@ class QgsWFSSharedData : public QObject //! Current BBOX used by the downloader QgsRectangle mRect; - //! Server-side or user-side limit of downloaded features (in a single GetFeature()). Valid if > 0 + //! Server-side or user-side limit of downloaded features (including with paging). Valid if > 0 int mMaxFeatures; - //! Whether mMaxFeatures was set to a non 0 value for the purpose of paging - bool mMaxFeaturesWasSetFromDefaultForPaging; + //! Page size for WFS 2.0. 0 = disabled + int mPageSize; //! Limit of retrieved number of features for the current request int mRequestLimit; diff --git a/src/providers/wfs/qgswfssourceselect.cpp b/src/providers/wfs/qgswfssourceselect.cpp index 0ace274a726c..48e1c1b74f9c 100644 --- a/src/providers/wfs/qgswfssourceselect.cpp +++ b/src/providers/wfs/qgswfssourceselect.cpp @@ -22,7 +22,7 @@ #include "qgswfsprovider.h" #include "qgswfsdatasourceuri.h" #include "qgswfsutils.h" -#include "qgsnewhttpconnection.h" +#include "qgswfsnewconnection.h" #include "qgsprojectionselectiondialog.h" #include "qgsproject.h" #include "qgscoordinatereferencesystem.h" @@ -108,6 +108,8 @@ QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget *parent, Qt::WindowFlags fl, Qgs QgsWFSSourceSelect::~QgsWFSSourceSelect() { + QApplication::restoreOverrideCursor(); + QgsSettings settings; QgsDebugMsg( "saving settings" ); settings.setValue( QStringLiteral( "Windows/WFSSourceSelect/geometry" ), saveGeometry() ); @@ -204,6 +206,7 @@ void QgsWFSSourceSelect::refresh() void QgsWFSSourceSelect::capabilitiesReplyFinished() { + QApplication::restoreOverrideCursor(); btnConnect->setEnabled( true ); if ( !mCapabilities ) @@ -290,7 +293,7 @@ void QgsWFSSourceSelect::capabilitiesReplyFinished() void QgsWFSSourceSelect::addEntryToServerList() { - QgsNewHttpConnection *nc = new QgsNewHttpConnection( this, QgsNewHttpConnection::ConnectionWfs, QgsWFSConstants::CONNECTIONS_WFS ); + auto nc = new QgsWFSNewConnection( this ); nc->setAttribute( Qt::WA_DeleteOnClose ); nc->setWindowTitle( tr( "Create a New WFS Connection" ) ); @@ -303,7 +306,7 @@ void QgsWFSSourceSelect::addEntryToServerList() void QgsWFSSourceSelect::modifyEntryOfServerList() { - QgsNewHttpConnection *nc = new QgsNewHttpConnection( this, QgsNewHttpConnection::ConnectionWfs, QgsWFSConstants::CONNECTIONS_WFS, cmbConnections->currentText() ); + auto nc = new QgsWFSNewConnection( this, cmbConnections->currentText() ); nc->setAttribute( Qt::WA_DeleteOnClose ); nc->setWindowTitle( tr( "Modify WFS Connection" ) ); @@ -356,6 +359,7 @@ void QgsWFSSourceSelect::connectToServer() const bool synchronous = false; const bool forceRefresh = true; mCapabilities->requestCapabilities( synchronous, forceRefresh ); + QApplication::setOverrideCursor( Qt::WaitCursor ); } } diff --git a/src/ui/qgsnewhttpconnectionbase.ui b/src/ui/qgsnewhttpconnectionbase.ui index 58a26c678ec4..82af51ca209f 100644 --- a/src/ui/qgsnewhttpconnectionbase.ui +++ b/src/ui/qgsnewhttpconnectionbase.ui @@ -32,14 +32,14 @@ WFS Options - + Ignore axis orientation (WFS 1.1/WFS 2.0) - + Invert axis orientation @@ -60,17 +60,48 @@ - - + + - <html><head/><body><p>Select protocol version</p></body></html> + <html><head/><body><p>Enter a number to limit the maximum number of features retrieved per feature request. If let to empty, no limit is set.</p></body></html> - - + + + + + + + + + Detect + + + + + + + + + Enable feature paging + + + true + + + + + + + Page size + + + + + - <html><head/><body><p>Enter a number to limit the maximum number of features retrieved in a single GetFeature request. If let to empty, server default will apply.</p></body></html> + <html><head/><body><p>Enter a number to limit the maximum number of features retrieved in a single GetFeature request when paging is enabled. If let to empty, server default will apply.</p></body></html> @@ -158,7 +189,7 @@ - &DPI-Mode + DPI-&Mode cmbDpiMode @@ -168,7 +199,7 @@ - Referer + &Referer txtReferer @@ -289,6 +320,25 @@ 1 + + txtName + txtUrl + cmbVersion + mWfsVersionDetectButton + txtMaxNumFeatures + cbxWfsFeaturePaging + txtPageSize + cbxWfsIgnoreAxisOrientation + cbxWfsInvertAxisOrientation + txtReferer + cmbDpiMode + cbxIgnoreGetMapURI + cbxIgnoreGetFeatureInfoURI + cbxWmsIgnoreAxisOrientation + cbxWmsInvertAxisOrientation + cbxSmoothPixmapTransform + mTestConnectionButton + diff --git a/tests/src/python/test_provider_wfs.py b/tests/src/python/test_provider_wfs.py index 8228a7c7ff29..4e5e64a15ce4 100644 --- a/tests/src/python/test_provider_wfs.py +++ b/tests/src/python/test_provider_wfs.py @@ -1088,6 +1088,174 @@ def testWFS20Paging(self): """.encode('UTF-8')) self.assertEqual(vl.featureCount(), 2) + def testWFS20PagingPageSizeOverride(self): + """Test WFS 2.0 paging""" + + endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_WFS_2.0_paging_override' + + with open(sanitize(endpoint, '?SERVICE=WFS?REQUEST=GetCapabilities?ACCEPTVERSIONS=2.0.0,1.1.0,1.0.0'), 'wb') as f: + f.write(""" + + + + + + 10 + + + + + TRUE + + + + + my:typename + Title + Abstract + urn:ogc:def:crs:EPSG::4326 + + -71.123 66.33 + -65.32 78.3 + + + +""".encode('UTF-8')) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAME=my:typename'), 'wb') as f: + f.write(""" + + + + + + + + + + + + + + +""".encode('UTF-8')) + + # user pageSize < user maxNumFeatures < server pagesize + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' maxNumFeatures='3' pageSize='2'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + self.assertEqual(vl.wkbType(), QgsWkbTypes.Point) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=2&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: + f.write(""" + + + + 66.33 -70.332 + 1 + + + 66.33 -70.332 + 2 + + +""".encode('UTF-8')) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=2&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: + f.write(""" + + + + 66.33 -70.332 + 3 + + +""".encode('UTF-8')) + + values = [f['id'] for f in vl.getFeatures()] + self.assertEqual(values, [1, 2, 3]) + + os.unlink(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=2&SRSNAME=urn:ogc:def:crs:EPSG::4326')) + os.unlink(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=2&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326')) + + # user maxNumFeatures < user pageSize < server pagesize + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' maxNumFeatures='1' pageSize='2'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + self.assertEqual(vl.wkbType(), QgsWkbTypes.Point) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: + f.write(""" + + + + 66.33 -70.332 + 1 + + +""".encode('UTF-8')) + + values = [f['id'] for f in vl.getFeatures()] + self.assertEqual(values, [1]) + + os.unlink(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326')) + + # user user pageSize > server pagesize + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' pageSize='100'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + self.assertEqual(vl.wkbType(), QgsWkbTypes.Point) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=10&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: + f.write(""" + + + + 66.33 -70.332 + 1 + + +""".encode('UTF-8')) + + values = [f['id'] for f in vl.getFeatures()] + self.assertEqual(values, [1]) + + os.unlink(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=10&SRSNAME=urn:ogc:def:crs:EPSG::4326')) + + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' pagingEnabled='false' maxNumFeatures='3'", 'test', 'WFS') + self.assertTrue(vl.isValid()) + self.assertEqual(vl.wkbType(), QgsWkbTypes.Point) + + with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&COUNT=3&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: + f.write(""" + + + + 66.33 -70.332 + 1000 + + + 66.33 -70.332 + 2000 + + +""".encode('UTF-8')) + + values = [f['id'] for f in vl.getFeatures()] + self.assertEqual(values, [1000, 2000]) + def testWFSGetOnlyFeaturesInViewExtent(self): """Test 'get only features in view extent' """