diff --git a/python/server/auto_generated/qgsservercachefilter.sip.in b/python/server/auto_generated/qgsservercachefilter.sip.in new file mode 100644 index 00000000000..aadf3b60361 --- /dev/null +++ b/python/server/auto_generated/qgsservercachefilter.sip.in @@ -0,0 +1,133 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/server/qgsservercachefilter.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + + +class QgsServerCacheFilter +{ +%Docstring +Class defining cache interface for QGIS Server plugins. + +.. versionadded:: 3.4 +%End + +%TypeHeaderCode +#include "qgsservercachefilter.h" +%End + public: + + QgsServerCacheFilter( const QgsServerInterface *serverInterface ); +%Docstring +Constructor +QgsServerInterface passed to plugins constructors +and must be passed to QgsServerCacheFilter instances. +%End + + virtual ~QgsServerCacheFilter(); + + virtual QByteArray getCachedDocument( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; +%Docstring +Returns cached document (or 0 if document not in cache) like capabilities + +:param project: the project used to generate the document to provide path +:param request: the request used to generate the document to provider parameters or data +:param key: the key provided by the access control to identify different documents for the same request + +:return: QByteArray of the cached document or an empty one if no corresponding document found +%End + + virtual bool setCachedDocument( const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; +%Docstring +Updates or inserts the document in cache like capabilities + +:param doc: the document to cache +:param project: the project used to generate the document to provide path +:param request: the request used to generate the document to provider parameters or data +:param key: the key provided by the access control to identify different documents for the same request + +:return: true if the document has been cached +%End + + virtual bool deleteCachedDocument( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; +%Docstring +Deletes the cached document + +:param project: the project used to generate the document to provide path +:param request: the request used to generate the document to provider parameters or data +:param key: the key provided by the access control to identify different documents for the same request + +:return: true if the document has been deleted +%End + + virtual bool deleteCachedDocuments( const QgsProject *project ) const; +%Docstring +Deletes all cached documents for a QGIS project + +:param project: the project used to generate the documents to provide path + +:return: true if the documents have been deleted +%End + + virtual QByteArray getCachedImage( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; +%Docstring +Returns cached image (or 0 if document not in cache) like tiles + +:param project: the project used to generate the image to provide path +:param request: the request used to generate the image to provider parameters or data +:param key: the key provided by the access control to identify different images for the same request + +:return: QByteArray of the cached image or an empty one if no corresponding image found +%End + + virtual bool setCachedImage( const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; +%Docstring +Updates or inserts the image in cache like tiles + +:param img: the document to cache +:param project: the project used to generate the image to provide path +:param request: the request used to generate the image to provider parameters or data +:param key: the key provided by the access control to identify different images for the same request + +:return: true if the image has been cached +%End + + virtual bool deleteCachedImage( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; +%Docstring +Deletes the cached image + +:param project: the project used to generate the image to provide path +:param request: the request used to generate the image to provider parameters or data +:param key: the key provided by the access control to identify different images for the same request + +:return: true if the image has been deleted +%End + + virtual bool deleteCachedImages( const QgsProject *project ) const; +%Docstring +Deletes all cached images for a QGIS project + +:param project: the project used to generate the images to provide path + +:return: true if the images have been deleted +%End + +}; + +typedef QMultiMap QgsServerCacheFilterMap; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/server/qgsservercachefilter.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/server/auto_generated/qgsservercachemanager.sip.in b/python/server/auto_generated/qgsservercachemanager.sip.in new file mode 100644 index 00000000000..c08238d23d2 --- /dev/null +++ b/python/server/auto_generated/qgsservercachemanager.sip.in @@ -0,0 +1,143 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/server/qgsservercachemanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +class QgsServerCacheManager +{ +%Docstring +A helper class that centralizes caches accesses given by all the server cache filter plugins. + +.. versionadded:: 3.4 +%End + +%TypeHeaderCode +#include "qgsservercachemanager.h" +#include "qgsservercachefilter.h" +%End + public: + QgsServerCacheManager(); +%Docstring +Constructor +%End + + QgsServerCacheManager( const QgsServerCacheManager © ); +%Docstring +Copy constructor +%End + + + ~QgsServerCacheManager(); + + bool getCachedDocument( QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; +%Docstring +Returns cached document (or 0 if document not in cache) like capabilities + +:param doc: the document to update by content found in cache +:param project: the project used to generate the document to provide path +:param request: the request used to generate the document to provider parameters or data +:param accessControl: the access control to identify different documents for the same request provided by server interface + +:return: true if the document has been found in cache and the document's content set +%End + + bool setCachedDocument( const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; +%Docstring +Updates or inserts the document in cache like capabilities + +:param doc: the document to cache +:param project: the project used to generate the document to provide path +:param request: the request used to generate the document to provider parameters or data +:param accessControl: the access control to identify different documents for the same request provided by server interface + +:return: true if the document has been cached +%End + + bool deleteCachedDocument( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; +%Docstring +Deletes the cached document + +:param project: the project used to generate the document to provide path +:param request: the request used to generate the document to provider parameters or data +:param accessControl: the access control to identify different documents for the same request provided by server interface + +:return: true if the document has been deleted +%End + + bool deleteCachedDocuments( const QgsProject *project ) const; +%Docstring +Deletes all cached documents for a QGIS project + +:param project: the project used to generate the document to provide path + +:return: true if the document has been deleted +%End + + QByteArray getCachedImage( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; +%Docstring +Returns cached image (or 0 if image not in cache) like tiles + +:param project: the project used to generate the image to provide path +:param request: the request used to generate the image to provider parameters or data +:param accessControl: the access control to identify different documents for the same request provided by server interface + +:return: the cached image or 0 if no corresponding image found +%End + + bool setCachedImage( const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; +%Docstring +Updates or inserts the image in cache like tiles + +:param img: the image to cache +:param project: the project used to generate the image to provide path +:param request: the request used to generate the image to provider parameters or data +:param accessControl: the access control to identify different documents for the same request provided by server interface + +:return: true if the image has been cached +%End + + bool deleteCachedImage( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; +%Docstring +Deletes the cached image + +:param project: the project used to generate the image to provide path +:param request: the request used to generate the image to provider parameters or data +:param accessControl: the access control to identify different documents for the same request provided by server interface + +:return: true if the image has been deleted +%End + + bool deleteCachedImages( const QgsProject *project ) const; +%Docstring +Deletes all cached images for a QGIS project + +:param project: the project used to generate the images to provide path + +:return: true if the images have been deleted +%End + + void registerServerCache( QgsServerCacheFilter *serverCache, int priority = 0 ); +%Docstring +Register a server cache filter + +:param serverCache: the server cache to add +:param priority: the priority used to define the order +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/server/qgsservercachemanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/server/auto_generated/qgsserverinterface.sip.in b/python/server/auto_generated/qgsserverinterface.sip.in index 2146213ead9..53c82e353db 100644 --- a/python/server/auto_generated/qgsserverinterface.sip.in +++ b/python/server/auto_generated/qgsserverinterface.sip.in @@ -83,6 +83,23 @@ Register an access control filter virtual QgsAccessControl *accessControls() const = 0; %Docstring Gets the registered access control filters +%End + + virtual void registerServerCache( QgsServerCacheFilter *serverCache /Transfer/, int priority = 0 ) = 0; +%Docstring +Register a server cache filter + +:param serverCache: the server cache to register +:param priority: the priority used to order them + +.. versionadded:: 3.4 +%End + + virtual QgsServerCacheManager *cacheManager() const = 0; +%Docstring +Gets the registered server cache filters + +.. versionadded:: 3.4 %End virtual QString getEnv( const QString &name ) const = 0; diff --git a/python/server/auto_generated/qgsserverprojectutils.sip.in b/python/server/auto_generated/qgsserverprojectutils.sip.in index 5d976f083f5..8b8fe7d6b14 100644 --- a/python/server/auto_generated/qgsserverprojectutils.sip.in +++ b/python/server/auto_generated/qgsserverprojectutils.sip.in @@ -413,6 +413,17 @@ Returns the Layer ids list defined in a QGIS project as published in WCS. :param project: the QGIS project :return: the Layer ids list. +%End + + QString wmtsServiceUrl( const QgsProject &project ); +%Docstring +Returns the WMTS service url defined in a QGIS project. + +:param project: the QGIS project + +:return: url if defined in project, an empty string otherwise. + +.. versionadded:: 3.4 %End }; diff --git a/python/server/server_auto.sip b/python/server/server_auto.sip index 7c6a879dc9c..248908e5049 100644 --- a/python/server/server_auto.sip +++ b/python/server/server_auto.sip @@ -29,3 +29,9 @@ %If ( HAVE_SERVER_PYTHON_PLUGINS ) %Include auto_generated/qgsaccesscontrol.sip %End +%If ( HAVE_SERVER_PYTHON_PLUGINS ) +%Include auto_generated/qgsservercachefilter.sip +%End +%If ( HAVE_SERVER_PYTHON_PLUGINS ) +%Include auto_generated/qgsservercachemanager.sip +%End diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp index c221fcea46a..003f1950b39 100644 --- a/src/app/qgsprojectproperties.cpp +++ b/src/app/qgsprojectproperties.cpp @@ -56,6 +56,7 @@ #include "qgslayertreemodel.h" #include "qgsunittypes.h" #include "qgstablewidgetitem.h" +#include "qgstreewidgetitem.h" #include "qgslayertree.h" #include "qgsprintlayout.h" #include "qgsmetadatawidget.h" @@ -662,6 +663,49 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa mWMSImageQualitySpinBox->setValue( imageQuality ); } + mWMTSUrlLineEdit->setText( QgsProject::instance()->readEntry( QStringLiteral( "WMTSUrl" ), QStringLiteral( "/" ), QLatin1String( "" ) ) ); + mWMTSMinScaleLineEdit->setValue( QgsProject::instance()->readNumEntry( QStringLiteral( "WMTSMinScale" ), QStringLiteral( "/" ), 5000 ) ); + + bool wmtsProject = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Project" ) ); + bool wmtsPngProject = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Project" ) ); + bool wmtsJpegProject = QgsProject::instance()->readBoolEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Project" ) ); + QStringList wmtsGroupNameList = QgsProject::instance()->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Group" ) ); + QStringList wmtsPngGroupNameList = QgsProject::instance()->readListEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Group" ) ); + QStringList wmtsJpegGroupNameList = QgsProject::instance()->readListEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Group" ) ); + QStringList wmtsLayerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Layer" ) ); + QStringList wmtsPngLayerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Layer" ) ); + QStringList wmtsJpegLayerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Layer" ) ); + + QgsTreeWidgetItem *projItem = new QgsTreeWidgetItem( QStringList() << QStringLiteral( "Project" ) ); + projItem->setFlags( projItem->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable ); + projItem->setCheckState( 1, wmtsProject ? Qt::Checked : Qt::Unchecked ); + projItem->setCheckState( 2, wmtsPngProject ? Qt::Checked : Qt::Unchecked ); + projItem->setCheckState( 3, wmtsJpegProject ? Qt::Checked : Qt::Unchecked ); + projItem->setData( 0, Qt::UserRole, QStringLiteral( "project" ) ); + twWmtsLayers->addTopLevelItem( projItem ); + populateWmtsTree( QgsProject::instance()->layerTreeRoot(), projItem ); + projItem->setExpanded( true ); + twWmtsLayers->header()->resizeSections( QHeaderView::ResizeToContents ); + for ( QTreeWidgetItem *item : twWmtsLayers->findItems( QString(), Qt::MatchContains | Qt::MatchRecursive, 1 ) ) + { + QString itemType = item->data( 0, Qt::UserRole ).toString(); + if ( itemType == QLatin1String( "group" ) ) + { + QString gName = item->data( 0, Qt::UserRole + 1 ).toString(); + item->setCheckState( 1, wmtsGroupNameList.contains( gName ) ? Qt::Checked : Qt::Unchecked ); + item->setCheckState( 2, wmtsPngGroupNameList.contains( gName ) ? Qt::Checked : Qt::Unchecked ); + item->setCheckState( 3, wmtsJpegGroupNameList.contains( gName ) ? Qt::Checked : Qt::Unchecked ); + } + else if ( itemType == QLatin1String( "layer" ) ) + { + QString lId = item->data( 0, Qt::UserRole + 1 ).toString(); + item->setCheckState( 1, wmtsLayerIdList.contains( lId ) ? Qt::Checked : Qt::Unchecked ); + item->setCheckState( 2, wmtsPngLayerIdList.contains( lId ) ? Qt::Checked : Qt::Unchecked ); + item->setCheckState( 3, wmtsJpegLayerIdList.contains( lId ) ? Qt::Checked : Qt::Unchecked ); + } + } + connect( twWmtsLayers, &QTreeWidget::itemChanged, this, &QgsProjectProperties::twWmtsItemChanged ); + mWFSUrlLineEdit->setText( QgsProject::instance()->readEntry( QStringLiteral( "WFSUrl" ), QStringLiteral( "/" ), QLatin1String( "" ) ) ); QStringList wfsLayerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "WFSLayers" ), QStringLiteral( "/" ) ); QStringList wfstUpdateLayerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "WFSTLayers" ), QStringLiteral( "Update" ) ); @@ -1222,6 +1266,58 @@ void QgsProjectProperties::apply() QgsProject::instance()->writeEntry( QStringLiteral( "WMSImageQuality" ), QStringLiteral( "/" ), imageQualityValue ); } + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSUrl" ), QStringLiteral( "/" ), mWMTSUrlLineEdit->text() ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSMinScale" ), QStringLiteral( "/" ), mWMTSMinScaleLineEdit->value() ); + bool wmtsProject = false; + bool wmtsPngProject = false; + bool wmtsJpegProject = false; + QStringList wmtsGroupList; + QStringList wmtsPngGroupList; + QStringList wmtsJpegGroupList; + QStringList wmtsLayerList; + QStringList wmtsPngLayerList; + QStringList wmtsJpegLayerList; + for ( const QTreeWidgetItem *item : twWmtsLayers->findItems( QString(), Qt::MatchContains | Qt::MatchRecursive, 1 ) ) + { + if ( !item->checkState( 1 ) ) + continue; + + QString itemType = item->data( 0, Qt::UserRole ).toString(); + if ( itemType == QLatin1String( "project" ) ) + { + wmtsProject = true; + wmtsPngProject = item->checkState( 2 ); + wmtsJpegProject = item->checkState( 3 ); + } + else if ( itemType == QLatin1String( "group" ) ) + { + QString gName = item->data( 0, Qt::UserRole + 1 ).toString(); + wmtsGroupList << gName; + if ( item->checkState( 2 ) ) + wmtsPngGroupList << gName; + if ( item->checkState( 3 ) ) + wmtsJpegGroupList << gName; + } + else if ( itemType == QLatin1String( "layer" ) ) + { + QString lId = item->data( 0, Qt::UserRole + 1 ).toString(); + wmtsLayerList << lId; + if ( item->checkState( 2 ) ) + wmtsPngLayerList << lId; + if ( item->checkState( 3 ) ) + wmtsJpegLayerList << lId; + } + } + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Project" ), wmtsProject ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Project" ), wmtsPngProject ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Project" ), wmtsJpegProject ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Group" ), wmtsGroupList ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Group" ), wmtsPngGroupList ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Group" ), wmtsJpegGroupList ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Layer" ), wmtsLayerList ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Layer" ), wmtsPngLayerList ); + QgsProject::instance()->writeEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Layer" ), wmtsJpegLayerList ); + QgsProject::instance()->writeEntry( QStringLiteral( "WFSUrl" ), QStringLiteral( "/" ), mWFSUrlLineEdit->text() ); QStringList wfsLayerList; QStringList wfstUpdateLayerList; @@ -1316,6 +1412,31 @@ void QgsProjectProperties::showProjectionsTab() mOptionsListWidget->setCurrentRow( 2 ); } +void QgsProjectProperties::twWmtsItemChanged( QTreeWidgetItem *item, int column ) +{ + if ( column == 1 && !item->checkState( 1 ) ) + { + item->setCheckState( 2, Qt::Unchecked ); + item->setCheckState( 3, Qt::Unchecked ); + } + else if ( column == 1 && item->checkState( 1 ) && + !item->checkState( 2 ) && !item->checkState( 3 ) ) + { + item->setCheckState( 2, Qt::Checked ); + item->setCheckState( 3, Qt::Checked ); + } + else if ( ( column == 2 && item->checkState( 2 ) ) || + ( column == 3 && item->checkState( 3 ) ) ) + { + item->setCheckState( 1, Qt::Checked ); + } + else if ( ( column == 2 && !item->checkState( 2 ) && !item->checkState( 3 ) ) || + ( column == 3 && !item->checkState( 2 ) && !item->checkState( 3 ) ) ) + { + item->setCheckState( 1, Qt::Unchecked ); + } +} + void QgsProjectProperties::cbxWFSPubliedStateChanged( int aIdx ) { QCheckBox *cb = qobject_cast( twWFSLayers->cellWidget( aIdx, 1 ) ); @@ -1913,6 +2034,49 @@ void QgsProjectProperties::resetPythonMacros() "def closeProject():\n pass\n" ); } +void QgsProjectProperties::populateWmtsTree( const QgsLayerTreeGroup *treeGroup, QgsTreeWidgetItem *treeItem ) +{ + for ( QgsLayerTreeNode *treeNode : treeGroup->children() ) + { + QgsTreeWidgetItem *childItem = nullptr; + if ( treeNode->nodeType() == QgsLayerTreeNode::NodeGroup ) + { + QgsLayerTreeGroup *treeGroupChild = static_cast( treeNode ); + QString gName = treeGroupChild->name(); + + childItem = new QgsTreeWidgetItem( QStringList() << gName ); + childItem->setFlags( childItem->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable ); + + childItem->setData( 0, Qt::UserRole, QStringLiteral( "group" ) ); + childItem->setData( 0, Qt::UserRole + 1, gName ); + + treeItem->addChild( childItem ); + + populateWmtsTree( treeGroupChild, childItem ); + + treeItem->setExpanded( true ); + } + else + { + QgsLayerTreeLayer *treeLayer = static_cast( treeNode ); + QgsMapLayer *l = treeLayer->layer(); + + if ( !l ) + { + continue; + } + + childItem = new QgsTreeWidgetItem( QStringList() << l->name() ); + childItem->setFlags( childItem->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable ); + + childItem->setData( 0, Qt::UserRole, QStringLiteral( "layer" ) ); + childItem->setData( 0, Qt::UserRole + 1, l->id() ); + + treeItem->addChild( childItem ); + } + } +} + void QgsProjectProperties::checkOWS( QgsLayerTreeGroup *treeGroup, QStringList &owsNames, QStringList &encodingMessages ) { QList< QgsLayerTreeNode * > treeGroupChildren = treeGroup->children(); diff --git a/src/app/qgsprojectproperties.h b/src/app/qgsprojectproperties.h index f691e307639..60a51037b59 100644 --- a/src/app/qgsprojectproperties.h +++ b/src/app/qgsprojectproperties.h @@ -31,6 +31,7 @@ class QgsStyle; class QgsExpressionContext; class QgsLayerTreeGroup; class QgsMetadataWidget; +class QgsTreeWidgetItem; /** * Dialog to set project level properties @@ -135,6 +136,11 @@ class APP_EXPORT QgsProjectProperties : public QgsOptionsDialogBase, private Ui: void pbtnStyleFill_clicked(); void pbtnStyleColorRamp_clicked(); + /** + * Slot to link WMTS checkboxes in tree widget + */ + void twWmtsItemChanged( QTreeWidgetItem *item, int column ); + /** * Slot to link WFS checkboxes */ @@ -213,6 +219,8 @@ class APP_EXPORT QgsProjectProperties : public QgsOptionsDialogBase, private Ui: QList mEllipsoidList; int mEllipsoidIndex; + //! populate WMTS tree + void populateWmtsTree( const QgsLayerTreeGroup *treeGroup, QgsTreeWidgetItem *treeItem ); //! Check OWS configuration void checkOWS( QgsLayerTreeGroup *treeGroup, QStringList &owsNames, QStringList &encodingMessages ); diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 7722404a945..247d2dcf429 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -73,6 +73,8 @@ IF (WITH_SERVER_PLUGINS) qgsserverfilter.cpp qgsaccesscontrolfilter.cpp qgsaccesscontrol.cpp + qgsservercachefilter.cpp + qgsservercachemanager.cpp ) ENDIF (WITH_SERVER_PLUGINS) diff --git a/src/server/qgsaccesscontrol.cpp b/src/server/qgsaccesscontrol.cpp index fdd8d8ac8c6..0ef5a80b67c 100644 --- a/src/server/qgsaccesscontrol.cpp +++ b/src/server/qgsaccesscontrol.cpp @@ -24,7 +24,7 @@ void QgsAccessControl::resolveFilterFeatures( const QList &layers ) { - Q_FOREACH ( QgsMapLayer *l, layers ) + for ( QgsMapLayer *l : layers ) { if ( l->type() == QgsMapLayer::LayerType::VectorLayer ) { diff --git a/src/server/qgsrequesthandler.cpp b/src/server/qgsrequesthandler.cpp index 07b5914c6b4..b6080f48fe7 100644 --- a/src/server/qgsrequesthandler.cpp +++ b/src/server/qgsrequesthandler.cpp @@ -228,7 +228,7 @@ void QgsRequestHandler::parseInput() typedef QPair pair_t; QUrlQuery query( inputString ); QList items = query.queryItems(); - Q_FOREACH ( pair_t pair, items ) + for ( pair_t pair : items ) { // QUrl::fromPercentEncoding doesn't replace '+' with space const QString key = QUrl::fromPercentEncoding( pair.first.replace( '+', ' ' ).toUtf8() ); diff --git a/src/server/qgsservercachefilter.cpp b/src/server/qgsservercachefilter.cpp new file mode 100644 index 00000000000..81c446431c5 --- /dev/null +++ b/src/server/qgsservercachefilter.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + qgsservercachefilter.cpp + ------------------------ + Cache interface for Qgis Server plugins + + begin : 2018-07-05 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 "qgsservercachefilter.h" + +#include + +QgsServerCacheFilter::QgsServerCacheFilter( const QgsServerInterface *serverInterface ) + : mServerInterface( serverInterface ) +{ +} + +QByteArray QgsServerCacheFilter::getCachedDocument( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const +{ + Q_UNUSED( project ); + Q_UNUSED( request ); + Q_UNUSED( key ); + return QByteArray(); +} + +bool QgsServerCacheFilter::setCachedDocument( const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, const QString &key ) const +{ + Q_UNUSED( doc ); + Q_UNUSED( project ); + Q_UNUSED( request ); + Q_UNUSED( key ); + return false; +} + +bool QgsServerCacheFilter::deleteCachedDocument( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const +{ + Q_UNUSED( project ); + Q_UNUSED( request ); + Q_UNUSED( key ); + return false; +} + +bool QgsServerCacheFilter::deleteCachedDocuments( const QgsProject *project ) const +{ + Q_UNUSED( project ); + return false; +} + +QByteArray QgsServerCacheFilter::getCachedImage( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const +{ + Q_UNUSED( project ); + Q_UNUSED( request ); + Q_UNUSED( key ); + return QByteArray(); +} + +bool QgsServerCacheFilter::setCachedImage( const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, const QString &key ) const +{ + Q_UNUSED( img ); + Q_UNUSED( project ); + Q_UNUSED( request ); + Q_UNUSED( key ); + return false; +} + +bool QgsServerCacheFilter::deleteCachedImage( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const +{ + Q_UNUSED( project ); + Q_UNUSED( request ); + Q_UNUSED( key ); + return false; +} + +bool QgsServerCacheFilter::deleteCachedImages( const QgsProject *project ) const +{ + Q_UNUSED( project ); + return false; +} diff --git a/src/server/qgsservercachefilter.h b/src/server/qgsservercachefilter.h new file mode 100644 index 00000000000..65d17814654 --- /dev/null +++ b/src/server/qgsservercachefilter.h @@ -0,0 +1,136 @@ +/*************************************************************************** + qgsservercachefilter.h + ------------------------ + Cache interface for Qgis Server plugins + + begin : 2018-07-05 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 QGSSERVERCACHEFILTER_H +#define QGSSERVERCACHEFILTER_H + +#include +#include +#include +#include "qgsproject.h" +#include "qgsserverrequest.h" +#include "qgis_server.h" +#include "qgis_sip.h" + +SIP_IF_MODULE( HAVE_SERVER_PYTHON_PLUGINS ) + +class QgsServerInterface; + + +/** + * \ingroup server + * \class QgsServerCacheFilter + * \brief Class defining cache interface for QGIS Server plugins. + * \since QGIS 3.4 + */ +class SERVER_EXPORT QgsServerCacheFilter +{ + + public: + + /** + * Constructor + * QgsServerInterface passed to plugins constructors + * and must be passed to QgsServerCacheFilter instances. + */ + QgsServerCacheFilter( const QgsServerInterface *serverInterface ); + + virtual ~QgsServerCacheFilter() = default; + + /** + * Returns cached document (or 0 if document not in cache) like capabilities + * \param project the project used to generate the document to provide path + * \param request the request used to generate the document to provider parameters or data + * \param key the key provided by the access control to identify different documents for the same request + * \returns QByteArray of the cached document or an empty one if no corresponding document found + */ + virtual QByteArray getCachedDocument( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; + + /** + * Updates or inserts the document in cache like capabilities + * \param doc the document to cache + * \param project the project used to generate the document to provide path + * \param request the request used to generate the document to provider parameters or data + * \param key the key provided by the access control to identify different documents for the same request + * \returns true if the document has been cached + */ + virtual bool setCachedDocument( const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; + + /** + * Deletes the cached document + * \param project the project used to generate the document to provide path + * \param request the request used to generate the document to provider parameters or data + * \param key the key provided by the access control to identify different documents for the same request + * \returns true if the document has been deleted + */ + virtual bool deleteCachedDocument( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; + + /** + * Deletes all cached documents for a QGIS project + * \param project the project used to generate the documents to provide path + * \returns true if the documents have been deleted + */ + virtual bool deleteCachedDocuments( const QgsProject *project ) const; + + /** + * Returns cached image (or 0 if document not in cache) like tiles + * \param project the project used to generate the image to provide path + * \param request the request used to generate the image to provider parameters or data + * \param key the key provided by the access control to identify different images for the same request + * \returns QByteArray of the cached image or an empty one if no corresponding image found + */ + virtual QByteArray getCachedImage( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; + + /** + * Updates or inserts the image in cache like tiles + * \param img the document to cache + * \param project the project used to generate the image to provide path + * \param request the request used to generate the image to provider parameters or data + * \param key the key provided by the access control to identify different images for the same request + * \returns true if the image has been cached + */ + virtual bool setCachedImage( const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; + + /** + * Deletes the cached image + * \param project the project used to generate the image to provide path + * \param request the request used to generate the image to provider parameters or data + * \param key the key provided by the access control to identify different images for the same request + * \returns true if the image has been deleted + */ + virtual bool deleteCachedImage( const QgsProject *project, const QgsServerRequest &request, const QString &key ) const; + + /** + * Deletes all cached images for a QGIS project + * \param project the project used to generate the images to provide path + * \returns true if the images have been deleted + */ + virtual bool deleteCachedImages( const QgsProject *project ) const; + + private: + + //! The server interface + const QgsServerInterface *mServerInterface = nullptr; + +}; + +//! The registry definition +typedef QMultiMap QgsServerCacheFilterMap; + +#endif // QGSSERVERCACHEFILTER_H diff --git a/src/server/qgsservercachemanager.cpp b/src/server/qgsservercachemanager.cpp new file mode 100644 index 00000000000..6d00915e433 --- /dev/null +++ b/src/server/qgsservercachemanager.cpp @@ -0,0 +1,218 @@ +/*************************************************************************** + qgsservercachemanager.cpp + ------------------------- + + begin : 2018-07-05 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 "qgsservercachemanager.h" + +QgsServerCacheManager::QgsServerCacheManager() +{ + mPluginsServerCaches.reset( new QgsServerCacheFilterMap() ); +} + +QgsServerCacheManager::QgsServerCacheManager( const QgsServerCacheManager © ) +{ + if ( copy.mPluginsServerCaches ) + { + mPluginsServerCaches.reset( new QgsServerCacheFilterMap( *copy.mPluginsServerCaches ) ); + } + else + { + mPluginsServerCaches.reset( nullptr ); + } +} + +QgsServerCacheManager &QgsServerCacheManager::operator=( const QgsServerCacheManager © ) +{ + if ( copy.mPluginsServerCaches ) + { + mPluginsServerCaches.reset( new QgsServerCacheFilterMap( *copy.mPluginsServerCaches ) ); + } + else + { + mPluginsServerCaches.reset( nullptr ); + } + return *this; +} + +QgsServerCacheManager::~QgsServerCacheManager() +{ + mPluginsServerCaches.reset(); +} + +bool QgsServerCacheManager::getCachedDocument( QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const +{ + bool cache = true; + QString key = getCacheKey( cache, accessControl ); + + if ( !cache ) + { + return false; + } + + QByteArray content; + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + content = scIterator.value()->getCachedDocument( project, request, key ); + if ( !content.isEmpty() ) + { + break; + } + } + if ( content.isEmpty() ) + { + return false; + } + + if ( !doc->setContent( content ) ) + { + return false; + } + + return true; +} + +bool QgsServerCacheManager::setCachedDocument( const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const +{ + bool cache = true; + QString key = getCacheKey( cache, accessControl ); + + if ( !cache ) + { + return false; + } + + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + if ( scIterator.value()->setCachedDocument( doc, project, request, key ) ) + { + return true; + } + } + return false; +} + +bool QgsServerCacheManager::deleteCachedDocument( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const +{ + bool cache = true; + QString key = getCacheKey( cache, accessControl ); + + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + if ( scIterator.value()->deleteCachedDocument( project, request, key ) ) + { + return true; + } + } + return false; +} + +bool QgsServerCacheManager::deleteCachedDocuments( const QgsProject *project ) const +{ + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + if ( scIterator.value()->deleteCachedDocuments( project ) ) + { + return true; + } + } + return false; +} + +QByteArray QgsServerCacheManager::getCachedImage( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const +{ + bool cache = true; + QString key = getCacheKey( cache, accessControl ); + + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + QByteArray content = scIterator.value()->getCachedImage( project, request, key ); + if ( !content.isEmpty() ) + { + return content; + } + } + return QByteArray(); +} + +bool QgsServerCacheManager::setCachedImage( const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const +{ + bool cache = true; + QString key = getCacheKey( cache, accessControl ); + + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + if ( scIterator.value()->setCachedImage( img, project, request, key ) ) + { + return true; + } + } + return false; +} + +bool QgsServerCacheManager::deleteCachedImage( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const +{ + bool cache = true; + QString key = getCacheKey( cache, accessControl ); + + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + if ( scIterator.value()->deleteCachedImage( project, request, key ) ) + { + return true; + } + } + return false; +} + +bool QgsServerCacheManager::deleteCachedImages( const QgsProject *project ) const +{ + QgsServerCacheFilterMap::const_iterator scIterator; + for ( scIterator = mPluginsServerCaches->constBegin(); scIterator != mPluginsServerCaches->constEnd(); ++scIterator ) + { + if ( scIterator.value()->deleteCachedImages( project ) ) + { + return true; + } + } + return false; +} + +void QgsServerCacheManager::registerServerCache( QgsServerCacheFilter *serverCache, int priority ) +{ + mPluginsServerCaches->insert( priority, serverCache ); +} + +QString QgsServerCacheManager::getCacheKey( bool &cache, QgsAccessControl *accessControl ) const +{ + QStringList cacheKeyList; + if ( accessControl ) + { + cache = accessControl->fillCacheKey( cacheKeyList ); + } + else + { + cache = true; + } + return cacheKeyList.join( '-' ); +} diff --git a/src/server/qgsservercachemanager.h b/src/server/qgsservercachemanager.h new file mode 100644 index 00000000000..5e1c315f794 --- /dev/null +++ b/src/server/qgsservercachemanager.h @@ -0,0 +1,143 @@ +/*************************************************************************** + qgsservercachemanager.h + ----------------------- + + begin : 2018-07-05 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 QGSSERVERCACHEMANAGER_H +#define QGSSERVERCACHEMANAGER_H + +#include "qgsservercachefilter.h" +#include "qgsaccesscontrol.h" +#include "qgsserverrequest.h" + +#include +#include +#include "qgsproject.h" +#include "qgis_server.h" +#include "qgis_sip.h" + +SIP_IF_MODULE( HAVE_SERVER_PYTHON_PLUGINS ) + +/** + * \ingroup server + * \class QgsServerCacheManager + * \brief A helper class that centralizes caches accesses given by all the server cache filter plugins. + * \since QGIS 3.4 + */ +class SERVER_EXPORT QgsServerCacheManager +{ +#ifdef SIP_RUN +#include "qgsservercachefilter.h" +#endif + + public: + //! Constructor + QgsServerCacheManager(); + + //! Copy constructor + QgsServerCacheManager( const QgsServerCacheManager © ); + + //! Assignment operator + QgsServerCacheManager &operator=( const QgsServerCacheManager © ); + + //! Destructor + ~QgsServerCacheManager(); + + /** + * Returns cached document (or 0 if document not in cache) like capabilities + * \param doc the document to update by content found in cache + * \param project the project used to generate the document to provide path + * \param request the request used to generate the document to provider parameters or data + * \param accessControl the access control to identify different documents for the same request provided by server interface + * \returns true if the document has been found in cache and the document's content set + */ + bool getCachedDocument( QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; + + /** + * Updates or inserts the document in cache like capabilities + * \param doc the document to cache + * \param project the project used to generate the document to provide path + * \param request the request used to generate the document to provider parameters or data + * \param accessControl the access control to identify different documents for the same request provided by server interface + * \returns true if the document has been cached + */ + bool setCachedDocument( const QDomDocument *doc, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; + + /** + * Deletes the cached document + * \param project the project used to generate the document to provide path + * \param request the request used to generate the document to provider parameters or data + * \param accessControl the access control to identify different documents for the same request provided by server interface + * \returns true if the document has been deleted + */ + bool deleteCachedDocument( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; + + /** + * Deletes all cached documents for a QGIS project + * \param project the project used to generate the document to provide path + * \returns true if the document has been deleted + */ + bool deleteCachedDocuments( const QgsProject *project ) const; + + /** + * Returns cached image (or 0 if image not in cache) like tiles + * \param project the project used to generate the image to provide path + * \param request the request used to generate the image to provider parameters or data + * \param accessControl the access control to identify different documents for the same request provided by server interface + * \returns the cached image or 0 if no corresponding image found + */ + QByteArray getCachedImage( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; + + /** + * Updates or inserts the image in cache like tiles + * \param img the image to cache + * \param project the project used to generate the image to provide path + * \param request the request used to generate the image to provider parameters or data + * \param accessControl the access control to identify different documents for the same request provided by server interface + * \returns true if the image has been cached + */ + bool setCachedImage( const QByteArray *img, const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; + + /** + * Deletes the cached image + * \param project the project used to generate the image to provide path + * \param request the request used to generate the image to provider parameters or data + * \param accessControl the access control to identify different documents for the same request provided by server interface + * \returns true if the image has been deleted + */ + bool deleteCachedImage( const QgsProject *project, const QgsServerRequest &request, QgsAccessControl *accessControl ) const; + + /** + * Deletes all cached images for a QGIS project + * \param project the project used to generate the images to provide path + * \returns true if the images have been deleted + */ + bool deleteCachedImages( const QgsProject *project ) const; + + /** + * Register a server cache filter + * \param serverCache the server cache to add + * \param priority the priority used to define the order + */ + void registerServerCache( QgsServerCacheFilter *serverCache, int priority = 0 ); + + private: + QString getCacheKey( bool &cache, QgsAccessControl *accessControl ) const; + //! The ServerCache plugins registry + std::unique_ptr mPluginsServerCaches = nullptr; +}; + +#endif diff --git a/src/server/qgsserverinterface.h b/src/server/qgsserverinterface.h index 03aa0cc54bf..89e5e6ff690 100644 --- a/src/server/qgsserverinterface.h +++ b/src/server/qgsserverinterface.h @@ -30,9 +30,13 @@ #ifdef HAVE_SERVER_PYTHON_PLUGINS #include "qgsaccesscontrolfilter.h" #include "qgsaccesscontrol.h" +#include "qgsservercachefilter.h" +#include "qgsservercachemanager.h" #else class QgsAccessControl; class QgsAccessControlFilter; +class QgsServerCacheManager; +class QgsServerCacheFilter; #endif #include "qgsserviceregistry.h" #include "qgis_server.h" @@ -118,6 +122,20 @@ class SERVER_EXPORT QgsServerInterface //! Gets the registered access control filters virtual QgsAccessControl *accessControls() const = 0; + /** + * Register a server cache filter + * \param serverCache the server cache to register + * \param priority the priority used to order them + * \since QGIS 3.4 + */ + virtual void registerServerCache( QgsServerCacheFilter *serverCache SIP_TRANSFER, int priority = 0 ) = 0; + + /** + * Gets the registered server cache filters + * \since QGIS 3.4 + */ + virtual QgsServerCacheManager *cacheManager() const = 0; + //! Returns an enrironment variable, used to pass environment variables to Python virtual QString getEnv( const QString &name ) const = 0; diff --git a/src/server/qgsserverinterfaceimpl.cpp b/src/server/qgsserverinterfaceimpl.cpp index de7832a105d..29aca9ac531 100644 --- a/src/server/qgsserverinterfaceimpl.cpp +++ b/src/server/qgsserverinterfaceimpl.cpp @@ -29,8 +29,7 @@ QgsServerInterfaceImpl::QgsServerInterfaceImpl( QgsCapabilitiesCache *capCache, mRequestHandler = nullptr; #ifdef HAVE_SERVER_PYTHON_PLUGINS mAccessControls = new QgsAccessControl(); -#else - mAccessControls = nullptr; + mCacheManager.reset( new QgsServerCacheManager() ); #endif } @@ -44,6 +43,7 @@ QgsServerInterfaceImpl::~QgsServerInterfaceImpl() { #ifdef HAVE_SERVER_PYTHON_PLUGINS delete mAccessControls; + mCacheManager.reset(); #endif } @@ -84,6 +84,17 @@ void QgsServerInterfaceImpl::registerAccessControl( QgsAccessControlFilter *acce #endif } +//! Register a new access control filter +void QgsServerInterfaceImpl::registerServerCache( QgsServerCacheFilter *serverCache, int priority ) +{ +#ifdef HAVE_SERVER_PYTHON_PLUGINS + mCacheManager->registerServerCache( serverCache, priority ); +#else + Q_UNUSED( serverCache ); + Q_UNUSED( priority ); +#endif +} + void QgsServerInterfaceImpl::removeConfigCacheEntry( const QString &path ) { diff --git a/src/server/qgsserverinterfaceimpl.h b/src/server/qgsserverinterfaceimpl.h index 39f19b4ff4f..1bad84c4abf 100644 --- a/src/server/qgsserverinterfaceimpl.h +++ b/src/server/qgsserverinterfaceimpl.h @@ -50,8 +50,8 @@ class QgsServerInterfaceImpl : public QgsServerInterface QgsRequestHandler *requestHandler() override { return mRequestHandler; } void registerFilter( QgsServerFilter *filter, int priority = 0 ) override; QgsServerFiltersMap filters() override { return mFilters; } + //! Register an access control filter - // void registerAccessControl( QgsAccessControlFilter *accessControl, int priority = 0 ) override; /** @@ -59,6 +59,23 @@ class QgsServerInterfaceImpl : public QgsServerInterface * \returns the access control helper */ QgsAccessControl *accessControls() const override { return mAccessControls; } + + + /** + * Registers a server cache filter + * \param serverCache the server cache to register + * \param priority the priority used to order them + * \since QGIS 3.4 + */ + void registerServerCache( QgsServerCacheFilter *serverCache SIP_TRANSFER, int priority = 0 ) override; + + /** + * Gets the helper over all the registered server cache filters + * \returns the server cache helper + * \since QGIS 3.4 + */ + QgsServerCacheManager *cacheManager() const override { return mCacheManager.get(); } + QString getEnv( const QString &name ) const override; QString configFilePath() override { return mConfigFilePath; } void setConfigFilePath( const QString &configFilePath ) override; @@ -74,6 +91,7 @@ class QgsServerInterfaceImpl : public QgsServerInterface QString mConfigFilePath; QgsServerFiltersMap mFilters; QgsAccessControl *mAccessControls = nullptr; + std::unique_ptr mCacheManager = nullptr; QgsCapabilitiesCache *mCapabilitiesCache = nullptr; QgsRequestHandler *mRequestHandler = nullptr; QgsServiceRegistry *mServiceRegistry = nullptr; diff --git a/src/server/qgsserverplugins.cpp b/src/server/qgsserverplugins.cpp index b735c7ac80a..62216b15f8f 100644 --- a/src/server/qgsserverplugins.cpp +++ b/src/server/qgsserverplugins.cpp @@ -90,7 +90,7 @@ bool QgsServerPlugins::initPlugins( QgsServerInterface *interface ) //Init plugins: loads a list of installed plugins and filter them //for "server" metadata bool atLeastOneEnabled = false; - Q_FOREACH ( const QString &pluginName, sPythonUtils->pluginList() ) + for ( const QString &pluginName : sPythonUtils->pluginList() ) { QString pluginService = sPythonUtils->getPluginMetadata( pluginName, QStringLiteral( "server" ) ); if ( pluginService == QLatin1String( "True" ) ) diff --git a/src/server/qgsserverprojectutils.cpp b/src/server/qgsserverprojectutils.cpp index 9ec7ca1facd..d89fb3d53ea 100644 --- a/src/server/qgsserverprojectutils.cpp +++ b/src/server/qgsserverprojectutils.cpp @@ -331,3 +331,8 @@ QStringList QgsServerProjectUtils::wcsLayerIds( const QgsProject &project ) { return project.readListEntry( QStringLiteral( "WCSLayers" ), QStringLiteral( "/" ) ); } + +QString QgsServerProjectUtils::wmtsServiceUrl( const QgsProject &project ) +{ + return project.readEntry( QStringLiteral( "WMTSSUrl" ), QStringLiteral( "/" ), "" ); +} diff --git a/src/server/qgsserverprojectutils.h b/src/server/qgsserverprojectutils.h index 9bc4bcdaee5..21a320b26be 100644 --- a/src/server/qgsserverprojectutils.h +++ b/src/server/qgsserverprojectutils.h @@ -40,313 +40,321 @@ namespace QgsServerProjectUtils /** * Returns if owsService capabilities are enabled. - * \param project the QGIS project - * \returns if owsService capabilities are enabled. - */ + * \param project the QGIS project + * \returns if owsService capabilities are enabled. + */ SERVER_EXPORT bool owsServiceCapabilities( const QgsProject &project ); /** * Returns the owsService title defined in project. - * \param project the QGIS project - * \returns the owsService title if defined in project. - */ + * \param project the QGIS project + * \returns the owsService title if defined in project. + */ SERVER_EXPORT QString owsServiceTitle( const QgsProject &project ); /** * Returns the owsService abstract defined in project. - * \param project the QGIS project - * \returns the owsService abstract if defined in project. - */ + * \param project the QGIS project + * \returns the owsService abstract if defined in project. + */ SERVER_EXPORT QString owsServiceAbstract( const QgsProject &project ); /** * Returns the owsService keywords defined in project. - * \param project the QGIS project - * \returns the owsService keywords if defined in project. - */ + * \param project the QGIS project + * \returns the owsService keywords if defined in project. + */ SERVER_EXPORT QStringList owsServiceKeywords( const QgsProject &project ); /** * Returns the owsService online resource defined in project. - * \param project the QGIS project - * \returns the owsService online resource if defined in project. - */ + * \param project the QGIS project + * \returns the owsService online resource if defined in project. + */ SERVER_EXPORT QString owsServiceOnlineResource( const QgsProject &project ); /** * Returns the owsService contact organization defined in project. - * \param project the QGIS project - * \returns the owsService contact organization if defined in project. - */ + * \param project the QGIS project + * \returns the owsService contact organization if defined in project. + */ SERVER_EXPORT QString owsServiceContactOrganization( const QgsProject &project ); /** * Returns the owsService contact position defined in project. - * \param project the QGIS project - * \returns the owsService contact position if defined in project. - */ + * \param project the QGIS project + * \returns the owsService contact position if defined in project. + */ SERVER_EXPORT QString owsServiceContactPosition( const QgsProject &project ); /** * Returns the owsService contact person defined in project. - * \param project the QGIS project - * \returns the owsService contact person if defined in project. - */ + * \param project the QGIS project + * \returns the owsService contact person if defined in project. + */ SERVER_EXPORT QString owsServiceContactPerson( const QgsProject &project ); /** * Returns the owsService contact mail defined in project. - * \param project the QGIS project - * \returns the owsService contact mail if defined in project. - */ + * \param project the QGIS project + * \returns the owsService contact mail if defined in project. + */ SERVER_EXPORT QString owsServiceContactMail( const QgsProject &project ); /** * Returns the owsService contact phone defined in project. - * \param project the QGIS project - * \returns the owsService contact phone if defined in project. - */ + * \param project the QGIS project + * \returns the owsService contact phone if defined in project. + */ SERVER_EXPORT QString owsServiceContactPhone( const QgsProject &project ); /** * Returns the owsService fees defined in project. - * \param project the QGIS project - * \returns the owsService fees if defined in project. - */ + * \param project the QGIS project + * \returns the owsService fees if defined in project. + */ SERVER_EXPORT QString owsServiceFees( const QgsProject &project ); /** * Returns the owsService access constraints defined in project. - * \param project the QGIS project - * \returns the owsService access constraints if defined in project. - */ + * \param project the QGIS project + * \returns the owsService access constraints if defined in project. + */ SERVER_EXPORT QString owsServiceAccessConstraints( const QgsProject &project ); /** * Returns the maximum width for WMS images defined in a QGIS project. - * \param project the QGIS project - * \returns width if defined in project, -1 otherwise. - */ + * \param project the QGIS project + * \returns width if defined in project, -1 otherwise. + */ SERVER_EXPORT int wmsMaxWidth( const QgsProject &project ); /** * Returns the maximum height for WMS images defined in a QGIS project. - * \param project the QGIS project - * \returns height if defined in project, -1 otherwise. - */ + * \param project the QGIS project + * \returns height if defined in project, -1 otherwise. + */ SERVER_EXPORT int wmsMaxHeight( const QgsProject &project ); /** * Returns the quality for WMS images defined in a QGIS project. - * \param project the QGIS project - * \returns quality if defined in project, -1 otherwise. - */ + * \param project the QGIS project + * \returns quality if defined in project, -1 otherwise. + */ SERVER_EXPORT int wmsImageQuality( const QgsProject &project ); /** * Returns if layer ids are used as name in WMS. - * \param project the QGIS project - * \returns if layer ids are used as name. - */ + * \param project the QGIS project + * \returns if layer ids are used as name. + */ SERVER_EXPORT bool wmsUseLayerIds( const QgsProject &project ); /** * Returns if the info format is SIA20145. - * \param project the QGIS project - * \returns if the info format is SIA20145. - */ + * \param project the QGIS project + * \returns if the info format is SIA20145. + */ SERVER_EXPORT bool wmsInfoFormatSia2045( const QgsProject &project ); /** * Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request. - * \param project the QGIS project - * \returns if the geometry is displayed as Well Known Text in GetFeatureInfo request. - */ + * \param project the QGIS project + * \returns if the geometry is displayed as Well Known Text in GetFeatureInfo request. + */ SERVER_EXPORT bool wmsFeatureInfoAddWktGeometry( const QgsProject &project ); /** * Returns if the geometry has to be segmentize in GetFeatureInfo request. - * \param project the QGIS project - * \returns if the geometry has to be segmentize in GetFeatureInfo request. - */ + * \param project the QGIS project + * \returns if the geometry has to be segmentize in GetFeatureInfo request. + */ SERVER_EXPORT bool wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project ); /** * Returns the geometry precision for GetFeatureInfo request. - * \param project the QGIS project - * \returns the geometry precision for GetFeatureInfo request. - */ + * \param project the QGIS project + * \returns the geometry precision for GetFeatureInfo request. + */ SERVER_EXPORT int wmsFeatureInfoPrecision( const QgsProject &project ); /** * Returns the document element name for XML GetFeatureInfo request. - * \param project the QGIS project - * \returns the document element name for XML GetFeatureInfo request. - */ + * \param project the QGIS project + * \returns the document element name for XML GetFeatureInfo request. + */ SERVER_EXPORT QString wmsFeatureInfoDocumentElement( const QgsProject &project ); /** * Returns the document element namespace for XML GetFeatureInfo request. - * \param project the QGIS project - * \returns the document element namespace for XML GetFeatureInfo request. - */ + * \param project the QGIS project + * \returns the document element namespace for XML GetFeatureInfo request. + */ SERVER_EXPORT QString wmsFeatureInfoDocumentElementNs( const QgsProject &project ); /** * Returns the schema URL for XML GetFeatureInfo request. - * \param project the QGIS project - * \returns the schema URL for XML GetFeatureInfo request. - */ + * \param project the QGIS project + * \returns the schema URL for XML GetFeatureInfo request. + */ SERVER_EXPORT QString wmsFeatureInfoSchema( const QgsProject &project ); /** * Returns the mapping between layer name and wms layer name for GetFeatureInfo request. - * \param project the QGIS project - * \returns the mapping between layer name and wms layer name for GetFeatureInfo request. - */ + * \param project the QGIS project + * \returns the mapping between layer name and wms layer name for GetFeatureInfo request. + */ SERVER_EXPORT QHash wmsFeatureInfoLayerAliasMap( const QgsProject &project ); /** * Returns if Inspire is activated. - * \param project the QGIS project - * \returns if Inspire is activated. - */ + * \param project the QGIS project + * \returns if Inspire is activated. + */ SERVER_EXPORT bool wmsInspireActivate( const QgsProject &project ); /** * Returns the Inspire language. - * \param project the QGIS project - * \returns the Inspire language if defined in project. - */ + * \param project the QGIS project + * \returns the Inspire language if defined in project. + */ SERVER_EXPORT QString wmsInspireLanguage( const QgsProject &project ); /** * Returns the Inspire metadata URL. - * \param project the QGIS project - * \returns the Inspire metadata URL if defined in project. - */ + * \param project the QGIS project + * \returns the Inspire metadata URL if defined in project. + */ SERVER_EXPORT QString wmsInspireMetadataUrl( const QgsProject &project ); /** * Returns the Inspire metadata URL type. - * \param project the QGIS project - * \returns the Inspire metadata URL type if defined in project. - */ + * \param project the QGIS project + * \returns the Inspire metadata URL type if defined in project. + */ SERVER_EXPORT QString wmsInspireMetadataUrlType( const QgsProject &project ); /** * Returns the Inspire temporal reference. - * \param project the QGIS project - * \returns the Inspire temporal reference if defined in project. - */ + * \param project the QGIS project + * \returns the Inspire temporal reference if defined in project. + */ SERVER_EXPORT QString wmsInspireTemporalReference( const QgsProject &project ); /** * Returns the Inspire metadata date. - * \param project the QGIS project - * \returns the Inspire metadata date if defined in project. - */ + * \param project the QGIS project + * \returns the Inspire metadata date if defined in project. + */ SERVER_EXPORT QString wmsInspireMetadataDate( const QgsProject &project ); /** * Returns the restricted composer list. - * \param project the QGIS project - * \returns the restricted composer list if defined in project. - */ + * \param project the QGIS project + * \returns the restricted composer list if defined in project. + */ SERVER_EXPORT QStringList wmsRestrictedComposers( const QgsProject &project ); /** * Returns the WMS service url defined in a QGIS project. - * \param project the QGIS project - * \returns url if defined in project, an empty string otherwise. - */ + * \param project the QGIS project + * \returns url if defined in project, an empty string otherwise. + */ SERVER_EXPORT QString wmsServiceUrl( const QgsProject &project ); /** * Returns the WMS root layer name defined in a QGIS project. - * \param project the QGIS project - * \returns root layer name if defined in project, an empty string otherwise. - */ + * \param project the QGIS project + * \returns root layer name if defined in project, an empty string otherwise. + */ SERVER_EXPORT QString wmsRootName( const QgsProject &project ); /** * Returns the restricted layer name list. - * \param project the QGIS project - * \returns the restricted layer name list if defined in project. - */ + * \param project the QGIS project + * \returns the restricted layer name list if defined in project. + */ SERVER_EXPORT QStringList wmsRestrictedLayers( const QgsProject &project ); /** * Returns the WMS output CRS list. - * \param project the QGIS project - * \returns the WMS output CRS list. - */ + * \param project the QGIS project + * \returns the WMS output CRS list. + */ SERVER_EXPORT QStringList wmsOutputCrsList( const QgsProject &project ); /** * Returns the WMS Extent restriction. - * \param project the QGIS project - * \returns the WMS Extent restriction. - */ + * \param project the QGIS project + * \returns the WMS Extent restriction. + */ SERVER_EXPORT QgsRectangle wmsExtent( const QgsProject &project ); /** * Returns the WFS service url defined in a QGIS project. - * \param project the QGIS project - * \returns url if defined in project, an empty string otherwise. - */ + * \param project the QGIS project + * \returns url if defined in project, an empty string otherwise. + */ SERVER_EXPORT QString wfsServiceUrl( const QgsProject &project ); /** * Returns the Layer ids list defined in a QGIS project as published in WFS. - * \param project the QGIS project - * \return the Layer ids list. - */ + * \param project the QGIS project + * \return the Layer ids list. + */ SERVER_EXPORT QStringList wfsLayerIds( const QgsProject &project ); /** * Returns the Layer precision defined in a QGIS project for the WFS GetFeature. - * \param project the QGIS project - * \param layerId the layer id in the project - * \return the layer precision for WFS GetFeature. - */ + * \param project the QGIS project + * \param layerId the layer id in the project + * \return the layer precision for WFS GetFeature. + */ SERVER_EXPORT int wfsLayerPrecision( const QgsProject &project, const QString &layerId ); /** * Returns the Layer ids list defined in a QGIS project as published as WFS-T with update capabilities. - * \param project the QGIS project - * \return the Layer ids list. - */ + * \param project the QGIS project + * \return the Layer ids list. + */ SERVER_EXPORT QStringList wfstUpdateLayerIds( const QgsProject &project ); /** * Returns the Layer ids list defined in a QGIS project as published as WFS-T with insert capabilities. - * \param project the QGIS project - * \return the Layer ids list. - */ + * \param project the QGIS project + * \return the Layer ids list. + */ SERVER_EXPORT QStringList wfstInsertLayerIds( const QgsProject &project ); /** * Returns the Layer ids list defined in a QGIS project as published as WFS-T with delete capabilities. - * \param project the QGIS project - * \return the Layer ids list. - */ + * \param project the QGIS project + * \return the Layer ids list. + */ SERVER_EXPORT QStringList wfstDeleteLayerIds( const QgsProject &project ); /** * Returns the WCS service url defined in a QGIS project. - * \param project the QGIS project - * \returns url if defined in project, an empty string otherwise. - */ + * \param project the QGIS project + * \returns url if defined in project, an empty string otherwise. + */ SERVER_EXPORT QString wcsServiceUrl( const QgsProject &project ); /** * Returns the Layer ids list defined in a QGIS project as published in WCS. - * \param project the QGIS project - * \returns the Layer ids list. - */ + * \param project the QGIS project + * \returns the Layer ids list. + */ SERVER_EXPORT QStringList wcsLayerIds( const QgsProject &project ); + + /** + * Returns the WMTS service url defined in a QGIS project. + * \param project the QGIS project + * \returns url if defined in project, an empty string otherwise. + * \since QGIS 3.4 + */ + SERVER_EXPORT QString wmtsServiceUrl( const QgsProject &project ); }; #endif diff --git a/src/server/qgsservicenativeloader.cpp b/src/server/qgsservicenativeloader.cpp index cc12df4eaa8..fc50267b2c7 100644 --- a/src/server/qgsservicenativeloader.cpp +++ b/src/server/qgsservicenativeloader.cpp @@ -70,7 +70,7 @@ void QgsServiceNativeLoader::loadModules( const QString &modulePath, QgsServiceR qDebug() << QString( "Checking %1 for native services modules" ).arg( moduleDir.path() ); //QgsDebugMsg( QString( "Checking %1 for native services modules" ).arg( moduleDir.path() ) ); - Q_FOREACH ( const QFileInfo &fi, moduleDir.entryInfoList() ) + for ( const QFileInfo &fi : moduleDir.entryInfoList() ) { QgsServiceModule *module = loadNativeModule( fi.filePath() ); if ( module ) diff --git a/src/server/services/CMakeLists.txt b/src/server/services/CMakeLists.txt index 470eff3bd02..c48165eeb76 100644 --- a/src/server/services/CMakeLists.txt +++ b/src/server/services/CMakeLists.txt @@ -10,4 +10,5 @@ ADD_SUBDIRECTORY(DummyService) ADD_SUBDIRECTORY(wms) ADD_SUBDIRECTORY(wfs) ADD_SUBDIRECTORY(wcs) +ADD_SUBDIRECTORY(wmts) diff --git a/src/server/services/wcs/qgswcs.cpp b/src/server/services/wcs/qgswcs.cpp index 49d64537d8f..7d513b5f245 100644 --- a/src/server/services/wcs/qgswcs.cpp +++ b/src/server/services/wcs/qgswcs.cpp @@ -102,7 +102,7 @@ namespace QgsWcs }; -} // namespace QgsWfs +} // namespace QgsWcs /** * \ingroup server diff --git a/src/server/services/wcs/qgswcsdescribecoverage.cpp b/src/server/services/wcs/qgswcsdescribecoverage.cpp index e8e4204af31..eb516adc8c9 100644 --- a/src/server/services/wcs/qgswcsdescribecoverage.cpp +++ b/src/server/services/wcs/qgswcsdescribecoverage.cpp @@ -120,7 +120,7 @@ namespace QgsWcs if ( coveNameList.size() == 0 || coveNameList.contains( name ) ) { QgsRasterLayer *rLayer = qobject_cast( layer ); - coveDescElement.appendChild( getCoverageOffering( doc, const_cast( rLayer ) ) ); + coveDescElement.appendChild( getCoverageOffering( doc, const_cast( rLayer ), project ) ); } } return doc; diff --git a/src/server/services/wcs/qgswcsgetcapabilities.cpp b/src/server/services/wcs/qgswcsgetcapabilities.cpp index 1020205da28..01da88ce2c0 100644 --- a/src/server/services/wcs/qgswcsgetcapabilities.cpp +++ b/src/server/services/wcs/qgswcsgetcapabilities.cpp @@ -37,10 +37,29 @@ namespace QgsWcs void writeGetCapabilities( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response ) { - QDomDocument doc = createGetCapabilitiesDocument( serverIface, project, version, request ); + QgsAccessControl *accessControl = serverIface->accessControls(); - response.setHeader( "Content-Type", "text/xml; charset=utf-8" ); - response.write( doc.toByteArray() ); + QDomDocument doc; + const QDomDocument *capabilitiesDocument = nullptr; + + QgsServerCacheManager *cacheManager = serverIface->cacheManager(); + if ( cacheManager && cacheManager->getCachedDocument( &doc, project, request, accessControl ) ) + { + capabilitiesDocument = &doc; + } + else //capabilities xml not in cache. Create a new one + { + doc = createGetCapabilitiesDocument( serverIface, project, version, request ); + + if ( cacheManager ) + { + cacheManager->setCachedDocument( &doc, project, request, accessControl ); + } + capabilitiesDocument = &doc; + } + + response.setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/xml; charset=utf-8" ) ); + response.write( capabilitiesDocument->toByteArray() ); } @@ -280,7 +299,7 @@ namespace QgsWcs #endif QgsRasterLayer *rLayer = qobject_cast( layer ); - QDomElement layerElem = getCoverageOffering( doc, const_cast( rLayer ), true ); + QDomElement layerElem = getCoverageOffering( doc, const_cast( rLayer ), project, true ); contentMetadataElement.appendChild( layerElem ); } diff --git a/src/server/services/wcs/qgswcsgetcoverage.cpp b/src/server/services/wcs/qgswcsgetcoverage.cpp index 312e54ef1f4..aeccfb8cb45 100644 --- a/src/server/services/wcs/qgswcsgetcoverage.cpp +++ b/src/server/services/wcs/qgswcsgetcoverage.cpp @@ -165,9 +165,7 @@ namespace QgsWcs // transform rect if ( requestCRS != rLayer->crs() ) { - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform t( requestCRS, rLayer->crs() ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform t( requestCRS, rLayer->crs(), project ); rect = t.transformBoundingBox( rect ); } diff --git a/src/server/services/wcs/qgswcsutils.cpp b/src/server/services/wcs/qgswcsutils.cpp index 2127cd40960..ae9b32fdff0 100644 --- a/src/server/services/wcs/qgswcsutils.cpp +++ b/src/server/services/wcs/qgswcsutils.cpp @@ -32,7 +32,7 @@ namespace QgsWcs return QStringLiteral( "1.0.0" ); } - QDomElement getCoverageOffering( QDomDocument &doc, const QgsRasterLayer *layer, bool brief ) + QDomElement getCoverageOffering( QDomDocument &doc, const QgsRasterLayer *layer, const QgsProject *project, bool brief ) { QDomElement layerElem; if ( brief ) @@ -73,9 +73,7 @@ namespace QgsWcs //lonLatEnvelope QgsCoordinateReferenceSystem layerCrs = layer->crs(); - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform t( layerCrs, QgsCoordinateReferenceSystem( 4326 ) ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform t( layerCrs, QgsCoordinateReferenceSystem( 4326 ), project ); //transform QgsRectangle BBox; try @@ -253,7 +251,7 @@ namespace QgsWcs q.removeAllQueryItems( QStringLiteral( "_DC" ) ); url.setQuery( q ); - href = url.toString( QUrl::FullyDecoded ); + href = url.toString(); } diff --git a/src/server/services/wcs/qgswcsutils.h b/src/server/services/wcs/qgswcsutils.h index cbd6a3689fe..16cc5196490 100644 --- a/src/server/services/wcs/qgswcsutils.h +++ b/src/server/services/wcs/qgswcsutils.h @@ -45,7 +45,7 @@ namespace QgsWcs /** * CoverageOffering or CoverageOfferingBrief element */ - QDomElement getCoverageOffering( QDomDocument &doc, const QgsRasterLayer *layer, bool brief = false ); + QDomElement getCoverageOffering( QDomDocument &doc, const QgsRasterLayer *layer, const QgsProject *project, bool brief = false ); /** * Service URL string @@ -58,7 +58,7 @@ namespace QgsWcs //XXX At some point, should be moved to common library QgsRectangle parseBbox( const QString &bboxStr ); - // Define namespaces used in WFS documents + // Define namespaces used in WCS documents const QString WCS_NAMESPACE = QStringLiteral( "http://www.opengis.net/wcs" ); const QString GML_NAMESPACE = QStringLiteral( "http://www.opengis.net/gml" ); const QString OGC_NAMESPACE = QStringLiteral( "http://www.opengis.net/ogc" ); diff --git a/src/server/services/wfs/qgswfsgetcapabilities.cpp b/src/server/services/wfs/qgswfsgetcapabilities.cpp index df63ed486a5..31199878746 100644 --- a/src/server/services/wfs/qgswfsgetcapabilities.cpp +++ b/src/server/services/wfs/qgswfsgetcapabilities.cpp @@ -41,10 +41,29 @@ namespace QgsWfs void writeGetCapabilities( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response ) { - QDomDocument doc = createGetCapabilitiesDocument( serverIface, project, version, request ); + QgsAccessControl *accessControl = serverIface->accessControls(); - response.setHeader( "Content-Type", "text/xml; charset=utf-8" ); - response.write( doc.toByteArray() ); + QDomDocument doc; + const QDomDocument *capabilitiesDocument = nullptr; + + QgsServerCacheManager *cacheManager = serverIface->cacheManager(); + if ( cacheManager && cacheManager->getCachedDocument( &doc, project, request, accessControl ) ) + { + capabilitiesDocument = &doc; + } + else //capabilities xml not in cache. Create a new one + { + doc = createGetCapabilitiesDocument( serverIface, project, version, request ); + + if ( cacheManager ) + { + cacheManager->setCachedDocument( &doc, project, request, accessControl ); + } + capabilitiesDocument = &doc; + } + + response.setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/xml; charset=utf-8" ) ); + response.write( capabilitiesDocument->toByteArray() ); } @@ -516,9 +535,7 @@ namespace QgsWfs QgsRectangle wgs84BoundingRect; if ( !layerExtent.isNull() ) { - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform exGeoTransform( layer->crs(), wgs84 ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform exGeoTransform( layer->crs(), wgs84, project ); try { wgs84BoundingRect = exGeoTransform.transformBoundingBox( layerExtent ); diff --git a/src/server/services/wfs/qgswfsgetcapabilities_1_0_0.cpp b/src/server/services/wfs/qgswfsgetcapabilities_1_0_0.cpp index 77423fcfd6f..dc0e9c4a28a 100644 --- a/src/server/services/wfs/qgswfsgetcapabilities_1_0_0.cpp +++ b/src/server/services/wfs/qgswfsgetcapabilities_1_0_0.cpp @@ -43,10 +43,29 @@ namespace QgsWfs void writeGetCapabilities( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, const QgsServerRequest &request, QgsServerResponse &response ) { - QDomDocument doc = createGetCapabilitiesDocument( serverIface, project, version, request ); + QgsAccessControl *accessControl = serverIface->accessControls(); - response.setHeader( "Content-Type", "text/xml; charset=utf-8" ); - response.write( doc.toByteArray() ); + QDomDocument doc; + const QDomDocument *capabilitiesDocument = nullptr; + + QgsServerCacheManager *cacheManager = serverIface->cacheManager(); + if ( cacheManager && cacheManager->getCachedDocument( &doc, project, request, accessControl ) ) + { + capabilitiesDocument = &doc; + } + else //capabilities xml not in cache. Create a new one + { + doc = createGetCapabilitiesDocument( serverIface, project, version, request ); + + if ( cacheManager ) + { + cacheManager->setCachedDocument( &doc, project, request, accessControl ); + } + capabilitiesDocument = &doc; + } + + response.setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/xml; charset=utf-8" ) ); + response.write( capabilitiesDocument->toByteArray() ); } diff --git a/src/server/services/wfs/qgswfsgetfeature.cpp b/src/server/services/wfs/qgswfsgetfeature.cpp index 208bd09f079..45c1000c159 100644 --- a/src/server/services/wfs/qgswfsgetfeature.cpp +++ b/src/server/services/wfs/qgswfsgetfeature.cpp @@ -62,9 +62,9 @@ namespace QgsWfs QString createFeatureGeoJSON( QgsFeature *feat, const createFeatureParams ¶ms ); - QDomElement createFeatureGML2( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms ); + QDomElement createFeatureGML2( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms, const QgsProject *project ); - QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms ); + QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms, const QgsProject *project ); void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format, int numberOfFeatures, const QStringList &typeNames ); @@ -74,7 +74,7 @@ namespace QgsWfs QgsRectangle *rect, const QStringList &typeNames ); void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, QgsFeature *feat, int featIdx, - const createFeatureParams ¶ms ); + const createFeatureParams ¶ms, const QgsProject *project ); void endGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format ); @@ -155,9 +155,7 @@ namespace QgsWfs } else { - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform transform( layer->crs(), requestCrs ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform transform( layer->crs(), requestCrs, project ); try { if ( requestRect.isEmpty() ) @@ -319,7 +317,7 @@ namespace QgsWfs accessControl->filterFeatures( vlayer, featureRequest ); QStringList attributes = QStringList(); - Q_FOREACH ( int idx, attrIndexes ) + for ( int idx : attrIndexes ) { attributes.append( vlayer->fields().field( idx ).name() ); } @@ -357,9 +355,7 @@ namespace QgsWfs if ( !featureRequest.filterRect().isEmpty() ) { - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform transform( outputCrs, vlayer->crs() ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform transform( outputCrs, vlayer->crs(), project ); try { featureRequest.setFilterRect( transform.transform( featureRequest.filterRect() ) ); @@ -405,7 +401,7 @@ namespace QgsWfs if ( iteratedFeatures >= aRequest.startIndex ) { - setGetFeature( response, aRequest.outputFormat, &feature, sentFeatures, cfp ); + setGetFeature( response, aRequest.outputFormat, &feature, sentFeatures, cfp, project ); ++sentFeatures; } ++iteratedFeatures; @@ -1169,7 +1165,7 @@ namespace QgsWfs } void setGetFeature( QgsServerResponse &response, QgsWfsParameters::Format format, QgsFeature *feat, int featIdx, - const createFeatureParams ¶ms ) + const createFeatureParams ¶ms, const QgsProject *project ) { if ( !feat->isValid() ) return; @@ -1196,12 +1192,12 @@ namespace QgsWfs QDomElement featureElement; if ( format == QgsWfsParameters::Format::GML3 ) { - featureElement = createFeatureGML3( feat, gmlDoc, params ); + featureElement = createFeatureGML3( feat, gmlDoc, params, project ); gmlDoc.appendChild( featureElement ); } else { - featureElement = createFeatureGML2( feat, gmlDoc, params ); + featureElement = createFeatureGML2( feat, gmlDoc, params, project ); gmlDoc.appendChild( featureElement ); } response.write( gmlDoc.toByteArray() ); @@ -1255,7 +1251,7 @@ namespace QgsWfs } - QDomElement createFeatureGML2( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms ) + QDomElement createFeatureGML2( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms, const QgsProject *project ) { //gml:FeatureMember QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ ); @@ -1271,9 +1267,7 @@ namespace QgsWfs { int prec = params.precision; QgsCoordinateReferenceSystem crs = params.crs; - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform mTransform( crs, params.outputCrs ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform mTransform( crs, params.outputCrs, project ); try { QgsGeometry transformed = geom; @@ -1352,7 +1346,7 @@ namespace QgsWfs return featureElement; } - QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms ) + QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams ¶ms, const QgsProject *project ) { //gml:FeatureMember QDomElement featureElement = doc.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ ); @@ -1368,9 +1362,7 @@ namespace QgsWfs { int prec = params.precision; QgsCoordinateReferenceSystem crs = params.crs; - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform mTransform( crs, params.outputCrs ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform mTransform( crs, params.outputCrs, project ); try { QgsGeometry transformed = geom; diff --git a/src/server/services/wfs/qgswfsutils.cpp b/src/server/services/wfs/qgswfsutils.cpp index 84051b1c5c1..238eaddbe2b 100644 --- a/src/server/services/wfs/qgswfsutils.cpp +++ b/src/server/services/wfs/qgswfsutils.cpp @@ -53,7 +53,7 @@ namespace QgsWfs params.remove( QgsServerParameter::SERVICE ); url.setQuery( params.urlQuery() ); - href = url.toString( QUrl::FullyDecoded ); + href = url.toString(); } return href; diff --git a/src/server/services/wms/qgslayerrestorer.cpp b/src/server/services/wms/qgslayerrestorer.cpp index 78f9585121d..5f51579c321 100644 --- a/src/server/services/wms/qgslayerrestorer.cpp +++ b/src/server/services/wms/qgslayerrestorer.cpp @@ -23,7 +23,7 @@ QgsLayerRestorer::QgsLayerRestorer( const QList &layers ) { - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { QgsLayerSettings settings; settings.name = layer->name(); diff --git a/src/server/services/wms/qgswmsdescribelayer.cpp b/src/server/services/wms/qgswmsdescribelayer.cpp index 2007b8bb833..a533aa17edb 100644 --- a/src/server/services/wms/qgswmsdescribelayer.cpp +++ b/src/server/services/wms/qgswmsdescribelayer.cpp @@ -84,7 +84,7 @@ namespace QgsWms // get the wms service url defined in project or keep the one from the // request url - QString wmsHrefString = serviceUrl( request, project ).toString( QUrl::FullyDecoded ); + QString wmsHrefString = serviceUrl( request, project ).toString(); // get the wfs service url defined in project or take the same as the // wms service url @@ -113,7 +113,7 @@ namespace QgsWms // WCS layers QStringList wcsLayerIds = QgsServerProjectUtils::wcsLayerIds( *project ); - Q_FOREACH ( QgsMapLayer *layer, project->mapLayers() ) + for ( QgsMapLayer *layer : project->mapLayers() ) { QString name = layer->name(); if ( useLayerIds ) diff --git a/src/server/services/wms/qgswmsgetcapabilities.cpp b/src/server/services/wms/qgswmsgetcapabilities.cpp index 0785ed82c85..15de9541749 100644 --- a/src/server/services/wms/qgswmsgetcapabilities.cpp +++ b/src/server/services/wms/qgswmsgetcapabilities.cpp @@ -61,11 +61,12 @@ namespace QgsWms const QgsProject *project ); void appendLayerBoundingBox( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &layerExtent, - const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText ); + const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText, + const QgsProject *project ); void appendLayerBoundingBoxes( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &lExtent, const QgsCoordinateReferenceSystem &layerCRS, const QStringList &crsList, - const QStringList &constrainedCrsList ); + const QStringList &constrainedCrsList, const QgsProject *project ); void appendCrsElementToLayer( QDomDocument &doc, QDomElement &layerElement, const QDomElement &precedingElement, const QString &crsText ); @@ -92,43 +93,61 @@ namespace QgsWms const QString &version, const QgsServerRequest &request, QgsServerResponse &response, bool projectSettings ) { + QgsAccessControl *accessControl = serverIface->accessControls(); + + QDomDocument doc; + const QDomDocument *capabilitiesDocument = nullptr; + + // Data for WMS capabilities server memory cache QString configFilePath = serverIface->configFilePath(); QgsCapabilitiesCache *capabilitiesCache = serverIface->capabilitiesCache(); - QStringList cacheKeyList; cacheKeyList << ( projectSettings ? QStringLiteral( "projectSettings" ) : version ); cacheKeyList << request.url().host(); bool cache = true; - -#ifdef HAVE_SERVER_PYTHON_PLUGINS - QgsAccessControl *accessControl = serverIface->accessControls(); if ( accessControl ) cache = accessControl->fillCacheKey( cacheKeyList ); -#endif + QString cacheKey = cacheKeyList.join( '-' ); + + QgsServerCacheManager *cacheManager = serverIface->cacheManager(); + if ( cacheManager && cacheManager->getCachedDocument( &doc, project, request, accessControl ) ) + { + capabilitiesDocument = &doc; + } + + if ( !capabilitiesDocument && cache ) //capabilities xml not in cache plugins + { + capabilitiesDocument = capabilitiesCache->searchCapabilitiesDocument( configFilePath, cacheKey ); + } - QDomDocument doc; - QString cacheKey = cacheKeyList.join( QStringLiteral( "-" ) ); - const QDomDocument *capabilitiesDocument = capabilitiesCache->searchCapabilitiesDocument( configFilePath, cacheKey ); if ( !capabilitiesDocument ) //capabilities xml not in cache. Create a new one { - QgsMessageLog::logMessage( QStringLiteral( "Capabilities document not found in cache" ) ); + QgsMessageLog::logMessage( QStringLiteral( "WMS capabilities document not found in cache" ) ); doc = getCapabilities( serverIface, project, version, request, projectSettings ); - if ( cache ) + if ( cacheManager && + cacheManager->setCachedDocument( &doc, project, request, accessControl ) ) + { + capabilitiesDocument = &doc; + } + else if ( cache ) { capabilitiesCache->insertCapabilitiesDocument( configFilePath, cacheKey, &doc ); capabilitiesDocument = capabilitiesCache->searchCapabilitiesDocument( configFilePath, cacheKey ); } + if ( !capabilitiesDocument ) + { + capabilitiesDocument = &doc; + } else { - doc = doc.cloneNode().toDocument(); - capabilitiesDocument = &doc; + QgsMessageLog::logMessage( QStringLiteral( "Set WMS capabilities document in cache" ) ); } } else { - QgsMessageLog::logMessage( QStringLiteral( "Found capabilities document in cache" ) ); + QgsMessageLog::logMessage( QStringLiteral( "Found WMS capabilities document in cache" ) ); } response.setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/xml; charset=utf-8" ) ); @@ -148,7 +167,7 @@ namespace QgsWms QUrl href = serviceUrl( request, project ); //href needs to be a prefix - QString hrefString = href.toString( QUrl::FullyDecoded ); + QString hrefString = href.toString(); hrefString.append( href.hasQuery() ? "&" : "?" ); // XML declaration @@ -388,7 +407,7 @@ namespace QgsWms QUrl href = serviceUrl( request, project ); //href needs to be a prefix - QString hrefString = href.toString( QUrl::FullyDecoded ); + QString hrefString = href.toString(); hrefString.append( href.hasQuery() ? "&" : "?" ); QDomElement capabilityElem = doc.createElement( QStringLiteral( "Capability" )/*wms:Capability*/ ); @@ -992,7 +1011,7 @@ namespace QgsWms appendCrsElementsToLayer( doc, layerElem, crsList, outputCrsList ); //Ex_GeographicBoundingBox - appendLayerBoundingBoxes( doc, layerElem, l->extent(), l->crs(), crsList, outputCrsList ); + appendLayerBoundingBoxes( doc, layerElem, l->extent(), l->crs(), crsList, outputCrsList, project ); } // add details about supported styles of the layer @@ -1121,9 +1140,9 @@ namespace QgsWms QUrl href = serviceUrl( request, project ); //href needs to be a prefix - QString hrefString = href.toString( QUrl::FullyDecoded ); + QString hrefString = href.toString(); hrefString.append( href.hasQuery() ? "&" : "?" ); - Q_FOREACH ( QString styleName, currentLayer->styleManager()->styles() ) + for ( const QString &styleName : currentLayer->styleManager()->styles() ) { QDomElement styleElem = doc.createElement( QStringLiteral( "Style" ) ); QDomElement styleNameElem = doc.createElement( QStringLiteral( "Name" ) ); @@ -1222,7 +1241,7 @@ namespace QgsWms } else //no crs constraint { - Q_FOREACH ( const QString &crs, crsList ) + for ( const QString &crs : crsList ) { appendCrsElementToLayer( doc, layerElement, CRSPrecedingElement, crs ); } @@ -1246,7 +1265,7 @@ namespace QgsWms void appendLayerBoundingBoxes( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &lExtent, const QgsCoordinateReferenceSystem &layerCRS, const QStringList &crsList, - const QStringList &constrainedCrsList ) + const QStringList &constrainedCrsList, const QgsProject *project ) { if ( layerElem.isNull() ) { @@ -1270,9 +1289,7 @@ namespace QgsWms QgsRectangle wgs84BoundingRect; if ( !layerExtent.isNull() ) { - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform exGeoTransform( layerCRS, wgs84 ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform exGeoTransform( layerCRS, wgs84, project ); try { wgs84BoundingRect = exGeoTransform.transformBoundingBox( layerExtent ); @@ -1330,21 +1347,22 @@ namespace QgsWms { for ( int i = constrainedCrsList.size() - 1; i >= 0; --i ) { - appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, constrainedCrsList.at( i ) ); + appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, constrainedCrsList.at( i ), project ); } } else //no crs constraint { - Q_FOREACH ( const QString &crs, crsList ) + for ( const QString &crs : crsList ) { - appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, crs ); + appendLayerBoundingBox( doc, layerElem, layerExtent, layerCRS, crs, project ); } } } void appendLayerBoundingBox( QDomDocument &doc, QDomElement &layerElem, const QgsRectangle &layerExtent, - const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText ) + const QgsCoordinateReferenceSystem &layerCRS, const QString &crsText, + const QgsProject *project ) { if ( layerElem.isNull() ) { @@ -1364,9 +1382,7 @@ namespace QgsWms QgsRectangle crsExtent; if ( !layerExtent.isNull() ) { - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform crsTransform( layerCRS, crs ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform crsTransform( layerCRS, crs, project ); try { crsExtent = crsTransform.transformBoundingBox( layerExtent ); @@ -1479,9 +1495,7 @@ namespace QgsWms } //get project crs - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform t( layerCrs, project->crs() ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform t( layerCrs, project->crs(), project ); //transform try @@ -1584,7 +1598,7 @@ namespace QgsWms combinedBBox = mapRect; } } - appendLayerBoundingBoxes( doc, groupElem, combinedBBox, groupCRS, combinedCRSSet.toList(), outputCrsList ); + appendLayerBoundingBoxes( doc, groupElem, combinedBBox, groupCRS, combinedCRSSet.toList(), outputCrsList, project ); } diff --git a/src/server/services/wms/qgswmsgetcontext.cpp b/src/server/services/wms/qgswmsgetcontext.cpp index 41347fe0c26..2c215e374eb 100644 --- a/src/server/services/wms/qgswmsgetcontext.cpp +++ b/src/server/services/wms/qgswmsgetcontext.cpp @@ -311,7 +311,7 @@ namespace QgsWms QUrl href = serviceUrl( request, project ); //href needs to be a prefix - QString hrefString = href.toString( QUrl::FullyDecoded ); + QString hrefString = href.toString(); hrefString.append( href.hasQuery() ? "&" : "?" ); // COntext Server Element with WMS service URL @@ -405,9 +405,7 @@ namespace QgsWms // update combineBBox try { - Q_NOWARN_DEPRECATED_PUSH - QgsCoordinateTransform t( l->crs(), project->crs() ); - Q_NOWARN_DEPRECATED_POP + QgsCoordinateTransform t( l->crs(), project->crs(), project ); QgsRectangle BBox = t.transformBoundingBox( l->extent() ); if ( combinedBBox.isEmpty() ) { @@ -437,7 +435,7 @@ namespace QgsWms void appendOwsLayerStyles( QDomDocument &doc, QDomElement &layerElem, QgsMapLayer *currentLayer ) { - Q_FOREACH ( QString styleName, currentLayer->styleManager()->styles() ) + for ( const QString &styleName : currentLayer->styleManager()->styles() ) { QDomElement styleListElem = doc.createElement( QStringLiteral( "StyleList" ) ); //only one default style in project file mode diff --git a/src/server/services/wms/qgswmsgetstyles.cpp b/src/server/services/wms/qgswmsgetstyles.cpp index 2ae53b20c6a..a338730281d 100644 --- a/src/server/services/wms/qgswmsgetstyles.cpp +++ b/src/server/services/wms/qgswmsgetstyles.cpp @@ -133,7 +133,7 @@ namespace QgsWms // WMS restricted layers QStringList restrictedLayers = QgsServerProjectUtils::wmsRestrictedLayers( *project ); - Q_FOREACH ( QgsMapLayer *layer, project->mapLayers() ) + for ( QgsMapLayer *layer : project->mapLayers() ) { QString name = layer->name(); if ( useLayerIds ) @@ -172,7 +172,7 @@ namespace QgsWms if ( vlayer->isSpatial() ) { QString currentStyle = vlayer->styleManager()->currentStyle(); - Q_FOREACH ( QString styleName, vlayer->styleManager()->styles() ) + for ( const QString &styleName : vlayer->styleManager()->styles() ) { vlayer->styleManager()->setCurrentStyle( styleName ); QDomElement styleElem = vlayer->renderer()->writeSld( myDocument, styleName ); diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index e0e5286c0b3..8d6d2b1de51 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -111,9 +111,9 @@ namespace QgsWms QgsLayerTreeModelLegendNode *_findLegendNodeForRule( QgsLayerTreeModel *legendModel, const QString &rule ) { - Q_FOREACH ( QgsLayerTreeLayer *nodeLayer, legendModel->rootGroup()->findLayers() ) + for ( QgsLayerTreeLayer *nodeLayer : legendModel->rootGroup()->findLayers() ) { - Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, legendModel->layerLegendNodes( nodeLayer ) ) + for ( QgsLayerTreeModelLegendNode *legendNode : legendModel->layerLegendNodes( nodeLayer ) ) { if ( legendNode->data( Qt::DisplayRole ).toString() == rule ) return legendNode; @@ -181,7 +181,7 @@ namespace QgsWms std::reverse( layers.begin(), layers.end() ); // check permissions - Q_FOREACH ( QgsMapLayer *ml, layers ) + for ( QgsMapLayer *ml : layers ) checkLayerReadPermissions( ml ); // build layer tree model for legend @@ -241,7 +241,7 @@ namespace QgsWms { QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings ); - Q_FOREACH ( const QString &id, mapSettings.layerIds() ) + for ( const QString &id : mapSettings.layerIds() ) { QgsVectorLayer *vl = qobject_cast( mProject->mapLayer( id ) ); if ( !vl || !vl->renderer() ) @@ -276,7 +276,7 @@ namespace QgsWms context.expressionContext().setFeature( f ); if ( moreSymbolsPerFeature ) { - Q_FOREACH ( QgsSymbol *s, r->originalSymbolsForFeature( f, context ) ) + for ( QgsSymbol *s : r->originalSymbolsForFeature( f, context ) ) usedSymbols.insert( QgsSymbolLayerUtils::symbolProperties( s ) ); } else @@ -328,11 +328,11 @@ namespace QgsWms // configure each layer with opacity, selection filter, ... bool updateMapExtent = mWmsParameters.bbox().isEmpty(); - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { checkLayerReadPermissions( layer ); - Q_FOREACH ( QgsWmsParametersLayer param, params ) + for ( const QgsWmsParametersLayer ¶m : params ) { if ( param.mNickname == layerNickname( *layer ) ) { @@ -677,11 +677,11 @@ namespace QgsWms // configure each layer with opacity, selection filter, ... bool updateMapExtent = mWmsParameters.bbox().isEmpty(); - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { checkLayerReadPermissions( layer ); - Q_FOREACH ( QgsWmsParametersLayer param, params ) + for ( const QgsWmsParametersLayer ¶m : params ) { if ( param.mNickname == layerNickname( *layer ) ) { @@ -774,7 +774,7 @@ namespace QgsWms // get dxf layers QList< QgsDxfExport::DxfLayer > dxfLayers; int layerIdx = -1; - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { layerIdx++; if ( layer->type() != QgsMapLayer::VectorLayer ) @@ -784,7 +784,7 @@ namespace QgsWms checkLayerReadPermissions( layer ); - Q_FOREACH ( QgsWmsParametersLayer param, params ) + for ( const QgsWmsParametersLayer ¶m : params ) { if ( param.mNickname == layerNickname( *layer ) ) { @@ -951,11 +951,11 @@ namespace QgsWms // remove non identifiable layers //removeNonIdentifiableLayers( layers ); - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { checkLayerReadPermissions( layer ); - Q_FOREACH ( QgsWmsParametersLayer param, params ) + for ( const QgsWmsParametersLayer ¶m : params ) { if ( param.mNickname == layerNickname( *layer ) ) { @@ -1244,11 +1244,11 @@ namespace QgsWms //layers can have assigned a different name for GetCapabilities QHash layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject ); - Q_FOREACH ( QString queryLayer, queryLayers ) + for ( const QString &queryLayer : queryLayers ) { bool validLayer = false; bool queryableLayer = true; - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { if ( queryLayer == layerNickname( *layer ) ) { @@ -1456,8 +1456,7 @@ namespace QgsWms mAccessControl->filterFeatures( layer, fReq ); QStringList attributes; - QgsField field; - Q_FOREACH ( field, layer->fields().toList() ) + for ( const QgsField &field : layer->fields().toList() ) { attributes.append( field.name() ); } @@ -2368,13 +2367,13 @@ namespace QgsWms QStringList restrictedLayersNames; QgsLayerTreeGroup *root = mProject->layerTreeRoot(); - Q_FOREACH ( QString l, restricted ) + for ( const QString &l : restricted ) { QgsLayerTreeGroup *group = root->findGroup( l ); if ( group ) { QList groupLayers = group->findLayers(); - Q_FOREACH ( QgsLayerTreeLayer *treeLayer, groupLayers ) + for ( QgsLayerTreeLayer *treeLayer : groupLayers ) { restrictedLayersNames.append( treeLayer->name() ); } @@ -2387,7 +2386,7 @@ namespace QgsWms // build output with names, ids or short name according to the configuration QList layers = root->findLayers(); - Q_FOREACH ( QgsLayerTreeLayer *layer, layers ) + for ( QgsLayerTreeLayer *layer : layers ) { if ( restrictedLayersNames.contains( layer->name() ) ) { @@ -2398,7 +2397,7 @@ namespace QgsWms void QgsRenderer::initNicknameLayers() { - Q_FOREACH ( QgsMapLayer *ml, mProject->mapLayers() ) + for ( QgsMapLayer *ml : mProject->mapLayers() ) { mNicknameLayers[ layerNickname( *ml ) ] = ml; } @@ -2469,7 +2468,7 @@ namespace QgsWms // try to create highlight layer for each geometry QString crs = mWmsParameters.crs(); - Q_FOREACH ( QgsWmsParametersHighlightLayer param, params ) + for ( const QgsWmsParametersHighlightLayer ¶m : params ) { // create sld document from symbology QDomDocument sldDoc; @@ -2675,7 +2674,7 @@ namespace QgsWms { QList layers; - Q_FOREACH ( QgsWmsParametersLayer param, params ) + for ( const QgsWmsParametersLayer ¶m : params ) { QString nickname = param.mNickname; QString style = param.mStyle; @@ -2798,7 +2797,7 @@ namespace QgsWms if ( layer->type() == QgsMapLayer::VectorLayer ) { QgsVectorLayer *filteredLayer = qobject_cast( layer ); - Q_FOREACH ( QString filter, filters ) + for ( const QString &filter : filters ) { if ( filter.startsWith( QStringLiteral( "<" ) ) && filter.endsWith( QStringLiteral( "Filter>" ) ) ) { @@ -2850,7 +2849,7 @@ namespace QgsWms { QgsFeatureIds selectedIds; - Q_FOREACH ( const QString &id, fids ) + for ( const QString &id : fids ) { selectedIds.insert( STRING_TO_FID( id ) ); } @@ -2927,7 +2926,7 @@ namespace QgsWms { QList wantedLayers; - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { if ( !layerScaleVisibility( *layer, scaleDenominator ) ) continue; @@ -2948,7 +2947,7 @@ namespace QgsWms { QList wantedLayers; - Q_FOREACH ( QgsMapLayer *layer, layers ) + for ( QgsMapLayer *layer : layers ) { if ( nonIdentifiableLayers.contains( layer->id() ) ) continue; @@ -2989,7 +2988,7 @@ namespace QgsWms // build layer tree rootGroup.clear(); QList counters; - Q_FOREACH ( QgsMapLayer *ml, layers ) + for ( QgsMapLayer *ml : layers ) { QgsLayerTreeLayer *lt = rootGroup.addLayer( ml ); lt->setCustomProperty( QStringLiteral( "showFeatureCount" ), showFeatureCount ); @@ -3018,7 +3017,7 @@ namespace QgsWms HitTest hitTest; getMap( contentBasedMapSettings, &hitTest ); - Q_FOREACH ( QgsLayerTreeNode *node, rootGroup.children() ) + for ( QgsLayerTreeNode *node : rootGroup.children() ) { Q_ASSERT( QgsLayerTree::isLayer( node ) ); QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node ); @@ -3030,7 +3029,7 @@ namespace QgsWms const SymbolSet &usedSymbols = hitTest[vl]; QList order; int i = 0; - Q_FOREACH ( const QgsLegendSymbolItem &legendItem, vl->renderer()->legendSymbolItems() ) + for ( const QgsLegendSymbolItem &legendItem : vl->renderer()->legendSymbolItems() ) { QString sProp = QgsSymbolLayerUtils::symbolProperties( legendItem.legacyRuleKey() ); if ( usedSymbols.contains( sProp ) ) @@ -3053,7 +3052,7 @@ namespace QgsWms if ( ! ruleDefined ) { QList rootChildren = rootGroup.children(); - Q_FOREACH ( QgsLayerTreeNode *node, rootChildren ) + for ( QgsLayerTreeNode *node : rootChildren ) { if ( QgsLayerTree::isLayer( node ) ) { @@ -3065,14 +3064,14 @@ namespace QgsWms // rule item titles if ( !drawLegendItemLabel ) { - Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, legendModel->layerLegendNodes( nodeLayer ) ) + for ( QgsLayerTreeModelLegendNode *legendNode : legendModel->layerLegendNodes( nodeLayer ) ) { legendNode->setUserLabel( QStringLiteral( " " ) ); // empty string = no override, so let's use one space } } else if ( !drawLegendLayerLabel ) { - Q_FOREACH ( QgsLayerTreeModelLegendNode *legendNode, legendModel->layerLegendNodes( nodeLayer ) ) + for ( QgsLayerTreeModelLegendNode *legendNode : legendModel->layerLegendNodes( nodeLayer ) ) { if ( legendNode->isEmbeddedInParent() ) legendNode->setEmbeddedInParent( false ); diff --git a/src/server/services/wmts/CMakeLists.txt b/src/server/services/wmts/CMakeLists.txt new file mode 100644 index 00000000000..f9d28d92c46 --- /dev/null +++ b/src/server/services/wmts/CMakeLists.txt @@ -0,0 +1,65 @@ + +######################################################## +# Files + +SET (wmts_SRCS + qgswmts.cpp + qgswmtsutils.cpp + qgswmtsgetcapabilities.cpp + qgswmtsgettile.cpp + qgswmtsgetfeatureinfo.cpp + qgswmtsparameters.cpp +) + +SET (wmts_MOC_HDRS + qgswmtsparameters.h +) + +######################################################## +# Build + +QT5_WRAP_CPP(wmts_MOC_SRCS ${wmts_MOC_HDRS}) + +ADD_LIBRARY (wmts MODULE ${wmts_SRCS} ${wmts_MOC_SRCS} ${wmts_MOC_HDRS}) + + +INCLUDE_DIRECTORIES(SYSTEM + ${GDAL_INCLUDE_DIR} + ${POSTGRES_INCLUDE_DIR} +) + +INCLUDE_DIRECTORIES( + ${CMAKE_BINARY_DIR}/src/core + ${CMAKE_BINARY_DIR}/src/python + ${CMAKE_BINARY_DIR}/src/analysis + ${CMAKE_BINARY_DIR}/src/server + ${CMAKE_CURRENT_BINARY_DIR} + ../wms + ../../../core + ../../../core/dxf + ../../../core/expression + ../../../core/geometry + ../../../core/metadata + ../../../core/raster + ../../../core/symbology + ../../../core/layertree + ../.. + .. + . +) + + +TARGET_LINK_LIBRARIES(wmts + qgis_core + qgis_server +) + + +######################################################## +# Install + +INSTALL(TARGETS wmts + RUNTIME DESTINATION ${QGIS_SERVER_MODULE_DIR} + LIBRARY DESTINATION ${QGIS_SERVER_MODULE_DIR} +) + diff --git a/src/server/services/wmts/qgswmts.cpp b/src/server/services/wmts/qgswmts.cpp new file mode 100644 index 00000000000..7fa58ca99ca --- /dev/null +++ b/src/server/services/wmts/qgswmts.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + qgswmts.cpp + ------------------------- + begin : July 23 , 2018 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 "qgsmodule.h" +#include "qgswmtsutils.h" +#include "qgswmtsgetcapabilities.h" +#include "qgswmtsgettile.h" +#include "qgswmtsgetfeatureinfo.h" + +#define QSTR_COMPARE( str, lit )\ + (str.compare( QStringLiteral( lit ), Qt::CaseInsensitive ) == 0) + +namespace QgsWmts +{ + + /** + * \ingroup server + * \class QgsWmts::Service + * \brief OGC web service specialized for WMTS + * \since QGIS 3.4 + */ + class Service: public QgsService + { + public: + + /** + * Constructor for WMTS service. + * \param serverIface Interface for plugins. + */ + Service( QgsServerInterface *serverIface ) + : mServerIface( serverIface ) + {} + + QString name() const override { return QStringLiteral( "WMTS" ); } + QString version() const override { return implementationVersion(); } + + bool allowMethod( QgsServerRequest::Method method ) const override + { + return method == QgsServerRequest::GetMethod || method == QgsServerRequest::PostMethod; + } + + void executeRequest( const QgsServerRequest &request, QgsServerResponse &response, + const QgsProject *project ) override + { + Q_UNUSED( project ); + + const QgsWmtsParameters params( QUrlQuery( request.url() ) ); + + // Set the default version + QString versionString = params.version(); + if ( versionString.isEmpty() ) + { + versionString = version(); // defined in qgswfsutils.h + } + + // Get the request + QString req = params.value( QgsServerParameter::name( QgsServerParameter::REQUEST ) ); + if ( req.isEmpty() ) + { + throw QgsServiceException( QStringLiteral( "OperationNotSupported" ), + QStringLiteral( "Please check the value of the REQUEST parameter" ) ); + } + + if ( QSTR_COMPARE( req, "GetCapabilities" ) ) + { + writeGetCapabilities( mServerIface, project, versionString, request, response ); + } + else if ( QSTR_COMPARE( req, "GetTile" ) ) + { + writeGetTile( mServerIface, project, versionString, request, response ); + } + else if ( QSTR_COMPARE( req, "GetFeatureInfo" ) ) + { + writeGetFeatureInfo( mServerIface, project, versionString, request, response ); + } + else + { + // Operation not supported + throw QgsServiceException( QStringLiteral( "OperationNotSupported" ), + QStringLiteral( "Request %1 is not supported" ).arg( req ) ); + } + } + + private: + QgsServerInterface *mServerIface = nullptr; + }; + + +} // namespace QgsWmts + +/** + * \ingroup server + * \class QgsWmtsModule + * \brief Service module specialized for WMTS + * \since QGIS 3.4 + */ +class QgsWmtsModule: public QgsServiceModule +{ + public: + void registerSelf( QgsServiceRegistry ®istry, QgsServerInterface *serverIface ) override + { + QgsDebugMsg( QStringLiteral( "WMTSModule::registerSelf called" ) ); + registry.registerService( new QgsWmts::Service( serverIface ) ); + } +}; + + +// Entry points +QGISEXTERN QgsServiceModule *QGS_ServiceModule_Init() +{ + static QgsWmtsModule module; + return &module; +} +QGISEXTERN void QGS_ServiceModule_Exit( QgsServiceModule * ) +{ + // Nothing to do +} diff --git a/src/server/services/wmts/qgswmtsgetcapabilities.cpp b/src/server/services/wmts/qgswmtsgetcapabilities.cpp new file mode 100644 index 00000000000..a5a79248913 --- /dev/null +++ b/src/server/services/wmts/qgswmtsgetcapabilities.cpp @@ -0,0 +1,571 @@ +/*************************************************************************** + qgswmtsgecapabilities.cpp + ------------------------- + begin : July 23 , 2017 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 "qgswmtsutils.h" +#include "qgsserverprojectutils.h" +#include "qgswmtsgetcapabilities.h" + +#include "qgsproject.h" +#include "qgsexception.h" +#include "qgsmapserviceexception.h" +#include "qgscoordinatereferencesystem.h" +#include "qgslayertree.h" +#include "qgslayertreemodel.h" +#include "qgslayertreemodellegendnode.h" + +#include + +namespace QgsWmts +{ + namespace + { + void appendLayerElements( QDomDocument &doc, QDomElement &contentsElement, + QList< layerDef > wmtsLayers, QList< tileMatrixSetDef > tmsList, + const QgsProject *project ); + + void appendTileMatrixSetElements( QDomDocument &doc, QDomElement &contentsElement, + QList< tileMatrixSetDef > tmsList ); + } + + /** + * Output WMTS GetCapabilities response + */ + void writeGetCapabilities( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, + const QgsServerRequest &request, QgsServerResponse &response ) + { + QgsAccessControl *accessControl = serverIface->accessControls(); + + QDomDocument doc; + const QDomDocument *capabilitiesDocument = nullptr; + + QgsServerCacheManager *cacheManager = serverIface->cacheManager(); + if ( cacheManager && cacheManager->getCachedDocument( &doc, project, request, accessControl ) ) + { + capabilitiesDocument = &doc; + } + else //capabilities xml not in cache. Create a new one + { + doc = createGetCapabilitiesDocument( serverIface, project, version, request ); + + if ( cacheManager ) + { + cacheManager->setCachedDocument( &doc, project, request, accessControl ); + } + capabilitiesDocument = &doc; + } + + response.setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/xml; charset=utf-8" ) ); + response.write( capabilitiesDocument->toByteArray() ); + } + + + QDomDocument createGetCapabilitiesDocument( QgsServerInterface *serverIface, const QgsProject *project, const QString &version, + const QgsServerRequest &request ) + { + Q_UNUSED( version ); + + QDomDocument doc; + + //wmts:Capabilities element + QDomElement wmtsCapabilitiesElement = doc.createElement( QStringLiteral( "Capabilities" )/*wmts:Capabilities*/ ); + wmtsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns" ), WMTS_NAMESPACE ); + wmtsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:gml" ), GML_NAMESPACE ); + wmtsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:ows" ), OWS_NAMESPACE ); + wmtsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) ); + wmtsCapabilitiesElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); + wmtsCapabilitiesElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), WMTS_NAMESPACE + " http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" ); + wmtsCapabilitiesElement.setAttribute( QStringLiteral( "version" ), implementationVersion() ); + doc.appendChild( wmtsCapabilitiesElement ); + + //INSERT ServiceIdentification + wmtsCapabilitiesElement.appendChild( getServiceIdentificationElement( doc, project ) ); + + //INSERT ServiceProvider + wmtsCapabilitiesElement.appendChild( getServiceProviderElement( doc, project ) ); + + //INSERT OperationsMetadata + wmtsCapabilitiesElement.appendChild( getOperationsMetadataElement( doc, project, request ) ); + + //INSERT Contents + wmtsCapabilitiesElement.appendChild( getContentsElement( doc, serverIface, project ) ); + + return doc; + + } + + QDomElement getServiceIdentificationElement( QDomDocument &doc, const QgsProject *project ) + { + //Service identification + QDomElement serviceElem = doc.createElement( QStringLiteral( "ows:ServiceIdentification" ) ); + + //Service type + QDomElement typeElem = doc.createElement( QStringLiteral( "ows:ServiceType" ) ); + QDomText typeText = doc.createTextNode( QStringLiteral( "OGC WMTS" ) ); + typeElem.appendChild( typeText ); + serviceElem.appendChild( typeElem ); + + //Service type version + QDomElement typeVersionElem = doc.createElement( QStringLiteral( "ows:ServiceTypeVersion" ) ); + QDomText typeVersionText = doc.createTextNode( implementationVersion() ); + typeVersionElem.appendChild( typeVersionText ); + serviceElem.appendChild( typeVersionElem ); + + QString title = QgsServerProjectUtils::owsServiceTitle( *project ); + if ( !title.isEmpty() ) + { + QDomElement titleElem = doc.createElement( QStringLiteral( "ows:Title" ) ); + QDomText titleText = doc.createTextNode( title ); + titleElem.appendChild( titleText ); + serviceElem.appendChild( titleElem ); + } + + QString abstract = QgsServerProjectUtils::owsServiceAbstract( *project ); + if ( !abstract.isEmpty() ) + { + QDomElement abstractElem = doc.createElement( QStringLiteral( "ows:Abstract" ) ); + QDomText abstractText = doc.createCDATASection( abstract ); + abstractElem.appendChild( abstractText ); + serviceElem.appendChild( abstractElem ); + } + + QStringList keywords = QgsServerProjectUtils::owsServiceKeywords( *project ); + if ( !keywords.isEmpty() ) + { + QDomElement keywordsElem = doc.createElement( QStringLiteral( "ows:Keywords" ) ); + for ( const QString &k : keywords ) + { + QDomElement keywordElem = doc.createElement( QStringLiteral( "ows:Keyword" ) ); + QDomText keywordText = doc.createTextNode( k ); + keywordElem.appendChild( keywordText ); + keywordsElem.appendChild( keywordElem ); + } + serviceElem.appendChild( keywordsElem ); + } + + QDomElement feesElem = doc.createElement( QStringLiteral( "ows:Fees" ) ); + QDomText feesText = doc.createTextNode( QStringLiteral( "None" ) ); // default value if fees are unknown + QString fees = QgsServerProjectUtils::owsServiceFees( *project ); + if ( !fees.isEmpty() ) + { + feesText = doc.createTextNode( fees ); + } + feesElem.appendChild( feesText ); + serviceElem.appendChild( feesElem ); + + QDomElement accessConstraintsElem = doc.createElement( QStringLiteral( "ows:AccessConstraints" ) ); + QDomText accessConstraintsText = doc.createTextNode( QStringLiteral( "None" ) ); // default value if access constraints are unknown + QString accessConstraints = QgsServerProjectUtils::owsServiceAccessConstraints( *project ); + if ( !accessConstraints.isEmpty() ) + { + accessConstraintsText = doc.createTextNode( accessConstraints ); + } + accessConstraintsElem.appendChild( accessConstraintsText ); + serviceElem.appendChild( accessConstraintsElem ); + + //End + return serviceElem; + } + + QDomElement getServiceProviderElement( QDomDocument &doc, const QgsProject *project ) + { + //Service provider + QDomElement serviceElem = doc.createElement( QStringLiteral( "ows:ServiceProvider" ) ); + + QString contactOrganization = QgsServerProjectUtils::owsServiceContactOrganization( *project ); + if ( !contactOrganization.isEmpty() ) + { + QDomElement contactOrganizationElem = doc.createElement( QStringLiteral( "ows:ProviderName" ) ); + QDomText contactOrganizationText = doc.createTextNode( contactOrganization ); + contactOrganizationElem.appendChild( contactOrganizationText ); + serviceElem.appendChild( contactOrganizationElem ); + } + + QString onlineResource = QgsServerProjectUtils::owsServiceOnlineResource( *project ); + if ( !onlineResource.isEmpty() ) + { + QDomElement onlineResourceElem = doc.createElement( QStringLiteral( "ows:ProviderSite" ) ); + onlineResourceElem.setAttribute( QStringLiteral( "xlink:href" ), onlineResource ); + serviceElem.appendChild( onlineResourceElem ); + } + + //Contact information + QString contactPerson = QgsServerProjectUtils::owsServiceContactPerson( *project ); + QString contactPosition = QgsServerProjectUtils::owsServiceContactPosition( *project ); + QString contactMail = QgsServerProjectUtils::owsServiceContactMail( *project ); + QString contactPhone = QgsServerProjectUtils::owsServiceContactPhone( *project ); + if ( !contactPerson.isEmpty() || + !contactPosition.isEmpty() || + !contactMail.isEmpty() || + !contactPhone.isEmpty() ) + { + QDomElement serviceContactElem = doc.createElement( QStringLiteral( "ows:ServiceContact" ) ); + if ( !contactPerson.isEmpty() ) + { + QDomElement contactPersonElem = doc.createElement( QStringLiteral( "ows:IndividualName" ) ); + QDomText contactPersonText = doc.createTextNode( contactPerson ); + contactPersonElem.appendChild( contactPersonText ); + serviceContactElem.appendChild( contactPersonElem ); + } + if ( !contactPosition.isEmpty() ) + { + QDomElement contactPositionElem = doc.createElement( QStringLiteral( "ows:PositionName" ) ); + QDomText contactPositionText = doc.createTextNode( contactPosition ); + contactPositionElem.appendChild( contactPositionText ); + serviceContactElem.appendChild( contactPositionElem ); + } + if ( !contactMail.isEmpty() || + !contactPhone.isEmpty() ) + { + QDomElement contactInfoElem = doc.createElement( QStringLiteral( "ows:ContactInfo" ) ); + if ( !contactMail.isEmpty() ) + { + QDomElement contactAddressElem = doc.createElement( QStringLiteral( "ows:Address" ) ); + QDomElement contactAddressMailElem = doc.createElement( QStringLiteral( "ows:ElectronicMailAddress" ) ); + QDomText contactAddressMailText = doc.createTextNode( contactMail ); + contactAddressMailElem.appendChild( contactAddressMailText ); + contactAddressElem.appendChild( contactAddressMailElem ); + contactInfoElem.appendChild( contactAddressElem ); + } + if ( !contactPhone.isEmpty() ) + { + QDomElement contactPhoneElem = doc.createElement( QStringLiteral( "ows:Phone" ) ); + QDomElement contactVoiceElem = doc.createElement( QStringLiteral( "ows:Voice" ) ); + QDomText contactVoiceText = doc.createTextNode( contactPhone ); + contactVoiceElem.appendChild( contactVoiceText ); + contactPhoneElem.appendChild( contactVoiceElem ); + contactInfoElem.appendChild( contactPhoneElem ); + } + serviceContactElem.appendChild( contactInfoElem ); + } + serviceElem.appendChild( serviceContactElem ); + } + + //End + return serviceElem; + } + + QDomElement getOperationsMetadataElement( QDomDocument &doc, const QgsProject *project, const QgsServerRequest &request ) + { + //ows:OperationsMetadata element + QDomElement operationsMetadataElement = doc.createElement( QStringLiteral( "ows:OperationsMetadata" )/*ows:OperationsMetadata*/ ); + + //ows:Operation element with name GetCapabilities + QDomElement getCapabilitiesElement = doc.createElement( QStringLiteral( "ows:Operation" )/*ows:Operation*/ ); + getCapabilitiesElement.setAttribute( QStringLiteral( "name" ), QStringLiteral( "GetCapabilities" ) ); + operationsMetadataElement.appendChild( getCapabilitiesElement ); + + //ows:DCP + QDomElement dcpElement = doc.createElement( QStringLiteral( "ows:DCP" )/*ows:DCP*/ ); + getCapabilitiesElement.appendChild( dcpElement ); + QDomElement httpElement = doc.createElement( QStringLiteral( "ows:HTTP" )/*ows:HTTP*/ ); + dcpElement.appendChild( httpElement ); + + //Prepare url + QString hrefString = serviceUrl( request, project ); + + //ows:Get + QDomElement getElement = doc.createElement( QStringLiteral( "ows:Get" )/*ows:Get*/ ); + getElement.setAttribute( QStringLiteral( "xlink:href" ), hrefString ); + httpElement.appendChild( getElement ); + + //ows:Operation element with name GetTile + QDomElement getTileElement = getCapabilitiesElement.cloneNode().toElement();//this is the same as 'GetCapabilities' + getTileElement.setAttribute( QStringLiteral( "name" ), QStringLiteral( "GetTile" ) ); + operationsMetadataElement.appendChild( getTileElement ); + + //ows:Operation element with name GetFeatureInfo + QDomElement getFeatureInfoElement = getCapabilitiesElement.cloneNode().toElement();//this is the same as 'GetCapabilities' + getFeatureInfoElement.setAttribute( QStringLiteral( "name" ), QStringLiteral( "GetFeatureInfo" ) ); + operationsMetadataElement.appendChild( getFeatureInfoElement ); + + // End + return operationsMetadataElement; + } + + QDomElement getContentsElement( QDomDocument &doc, QgsServerInterface *serverIface, const QgsProject *project ) + { + /* + * Adding layer list in ContentMetadata + */ + QDomElement contentsElement = doc.createElement( QStringLiteral( "Contents" )/*wmts:Contents*/ ); + + QList< tileMatrixSetDef > tmsList = getTileMatrixSetList( project ); + if ( !tmsList.isEmpty() ) + { + // get layer list + QList< layerDef > wmtsLayers = getWmtsLayerList( serverIface, project ); + if ( !wmtsLayers.isEmpty() ) + { + appendLayerElements( doc, contentsElement, wmtsLayers, tmsList, project ); + } + + appendTileMatrixSetElements( doc, contentsElement, tmsList ); + } + + //End + return contentsElement; + } + namespace + { + void appendLayerElements( QDomDocument &doc, QDomElement &contentsElement, + QList< layerDef > wmtsLayers, QList< tileMatrixSetDef > tmsList, + const QgsProject *project ) + { + QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( GEO_EPSG_CRS_AUTHID ); + // Define InfoFormat helper + std::function < void ( QDomElement &, const QString & ) > appendInfoFormat = [&doc]( QDomElement & elem, const QString & format ) + { + QDomElement formatElem = doc.createElement( QStringLiteral( "InfoFormat" )/*wmts:InfoFormat*/ ); + formatElem.appendChild( doc.createTextNode( format ) ); + elem.appendChild( formatElem ); + }; + + for ( const layerDef &wmtsLayer : wmtsLayers ) + { + if ( wmtsLayer.id.isEmpty() ) + continue; + + QDomElement layerElem = doc.createElement( QStringLiteral( "Layer" ) ); + + QDomElement layerIdElem = doc.createElement( QStringLiteral( "ows:Identifier" ) ); + QDomText layerIdText = doc.createTextNode( wmtsLayer.id ); + layerIdElem.appendChild( layerIdText ); + layerElem.appendChild( layerIdElem ); + + if ( !wmtsLayer.title.isEmpty() ) + { + // Layer title + QDomElement layerTitleElem = doc.createElement( QStringLiteral( "ows:Title" ) ); + QDomText layerTitleText = doc.createTextNode( wmtsLayer.title ); + layerTitleElem.appendChild( layerTitleText ); + layerElem.appendChild( layerTitleElem ); + } + + if ( !wmtsLayer.abstract.isEmpty() ) + { + // Layer abstract + QDomElement layerAbstElem = doc.createElement( QStringLiteral( "ows:Abstract" ) ); + QDomText layerAbstText = doc.createTextNode( project->title() ); + layerAbstElem.appendChild( layerAbstText ); + layerElem.appendChild( layerAbstElem ); + } + + // WGS84 bounding box + QDomElement wgs84BBoxElement = doc.createElement( QStringLiteral( "ows:WGS84BoundingBox" ) ); + QDomElement wgs84LowerCornerElement = doc.createElement( QStringLiteral( "LowerCorner" ) ); + QDomText wgs84LowerCornerText = doc.createTextNode( qgsDoubleToString( wmtsLayer.wgs84BoundingRect.xMinimum(), 6 ) + ' ' + qgsDoubleToString( wmtsLayer.wgs84BoundingRect.yMinimum(), 6 ) ); + wgs84LowerCornerElement.appendChild( wgs84LowerCornerText ); + wgs84BBoxElement.appendChild( wgs84LowerCornerElement ); + QDomElement wgs84UpperCornerElement = doc.createElement( QStringLiteral( "UpperCorner" ) ); + QDomText wgs84UpperCornerText = doc.createTextNode( qgsDoubleToString( wmtsLayer.wgs84BoundingRect.xMaximum(), 6 ) + ' ' + qgsDoubleToString( wmtsLayer.wgs84BoundingRect.yMaximum(), 6 ) ); + wgs84UpperCornerElement.appendChild( wgs84UpperCornerText ); + wgs84BBoxElement.appendChild( wgs84UpperCornerElement ); + layerElem.appendChild( wgs84BBoxElement ); + + // Other bounding boxes + for ( const tileMatrixSetDef &tms : tmsList ) + { + if ( tms.ref == QLatin1String( "EPSG:4326" ) ) + continue; + + QgsRectangle rect; + QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( tms.ref ); + QgsCoordinateTransform exGeoTransform( wgs84, crs, project ); + try + { + rect = exGeoTransform.transformBoundingBox( wmtsLayer.wgs84BoundingRect ); + } + catch ( const QgsCsException & ) + { + continue; + } + + QDomElement bboxElement = doc.createElement( QStringLiteral( "ows:BoundingBox" ) ); + bboxElement.setAttribute( QStringLiteral( "crs" ), tms.ref ); + QDomElement lowerCornerElement = doc.createElement( QStringLiteral( "LowerCorner" ) ); + QDomText lowerCornerText = doc.createTextNode( qgsDoubleToString( rect.xMinimum(), 6 ) + ' ' + qgsDoubleToString( rect.yMinimum(), 6 ) ); + lowerCornerElement.appendChild( lowerCornerText ); + bboxElement.appendChild( lowerCornerElement ); + QDomElement upperCornerElement = doc.createElement( QStringLiteral( "UpperCorner" ) ); + QDomText upperCornerText = doc.createTextNode( qgsDoubleToString( rect.xMaximum(), 6 ) + ' ' + qgsDoubleToString( rect.yMaximum(), 6 ) ); + upperCornerElement.appendChild( upperCornerText ); + bboxElement.appendChild( upperCornerElement ); + layerElem.appendChild( bboxElement ); + } + + // Layer Style + QDomElement layerStyleElem = doc.createElement( QStringLiteral( "Style" ) ); + layerStyleElem.setAttribute( QStringLiteral( "isDefault" ), QStringLiteral( "true" ) ); + QDomElement layerStyleIdElem = doc.createElement( QStringLiteral( "ows:Identifier" ) ); + QDomText layerStyleIdText = doc.createTextNode( QStringLiteral( "default" ) ); + layerStyleIdElem.appendChild( layerStyleIdText ); + layerStyleElem.appendChild( layerStyleIdElem ); + QDomElement layerStyleTitleElem = doc.createElement( QStringLiteral( "ows:Title" ) ); + QDomText layerStyleTitleText = doc.createTextNode( QStringLiteral( "default" ) ); + layerStyleTitleElem.appendChild( layerStyleTitleText ); + layerStyleElem.appendChild( layerStyleTitleElem ); + layerElem.appendChild( layerStyleElem ); + + for ( const QString &format : wmtsLayer.formats ) + { + QDomElement layerFormatElem = doc.createElement( QStringLiteral( "Format" ) ); + QDomText layerFormatText = doc.createTextNode( format ); + layerFormatElem.appendChild( layerFormatText ); + layerElem.appendChild( layerFormatElem ); + } + + if ( wmtsLayer.queryable ) + { + appendInfoFormat( layerElem, QStringLiteral( "text/plain" ) ); + appendInfoFormat( layerElem, QStringLiteral( "text/html" ) ); + appendInfoFormat( layerElem, QStringLiteral( "text/xml" ) ); + appendInfoFormat( layerElem, QStringLiteral( "application/vnd.ogc.gml" ) ); + appendInfoFormat( layerElem, QStringLiteral( "application/vnd.ogc.gml/3.1.1" ) ); + } + + for ( const tileMatrixSetDef &tms : tmsList ) + { + tileMatrixSetLinkDef tmsl = getLayerTileMatrixSetLink( wmtsLayer, tms, project ); + if ( tmsl.ref.isEmpty() || tmsl.ref != tms.ref ) + { + continue; + } + + //wmts:TileMatrixSetLink + QDomElement tmslElement = doc.createElement( QStringLiteral( "TileMatrixSetLink" )/*wmts:TileMatrixSetLink*/ ); + + QDomElement identifierElem = doc.createElement( QStringLiteral( "TileMatrixSet" ) ); + QDomText identifierText = doc.createTextNode( tms.ref ); + identifierElem.appendChild( identifierText ); + tmslElement.appendChild( identifierElem ); + + //wmts:TileMatrixSetLimits + QDomElement tmsLimitsElement = doc.createElement( QStringLiteral( "TileMatrixSetLimits" )/*wmts:TileMatrixSetLimits*/ ); + for ( int tmIdx : tmsl.tileMatrixLimits.keys() ) + { + QDomElement tmLimitsElement = doc.createElement( QStringLiteral( "TileMatrixLimits" )/*wmts:TileMatrixLimits*/ ); + + QDomElement tmIdentifierElem = doc.createElement( QStringLiteral( "TileMatrix" ) ); + QDomText tmIdentifierText = doc.createTextNode( QString::number( tmIdx ) ); + tmIdentifierElem.appendChild( tmIdentifierText ); + tmLimitsElement.appendChild( tmIdentifierElem ); + + tileMatrixLimitDef tml = tmsl.tileMatrixLimits[tmIdx]; + + QDomElement minTileColElem = doc.createElement( QStringLiteral( "MinTileCol" ) ); + QDomText minTileColText = doc.createTextNode( QString::number( tml.minCol ) ); + minTileColElem.appendChild( minTileColText ); + tmLimitsElement.appendChild( minTileColElem ); + + QDomElement maxTileColElem = doc.createElement( QStringLiteral( "MaxTileCol" ) ); + QDomText maxTileColText = doc.createTextNode( QString::number( tml.maxCol ) ); + maxTileColElem.appendChild( maxTileColText ); + tmLimitsElement.appendChild( maxTileColElem ); + + QDomElement minTileRowElem = doc.createElement( QStringLiteral( "MinTileRow" ) ); + QDomText minTileRowText = doc.createTextNode( QString::number( tml.minRow ) ); + minTileRowElem.appendChild( minTileRowText ); + tmLimitsElement.appendChild( minTileRowElem ); + + QDomElement maxTileRowElem = doc.createElement( QStringLiteral( "MaxTileRow" ) ); + QDomText maxTileRowText = doc.createTextNode( QString::number( tml.maxRow ) ); + maxTileRowElem.appendChild( maxTileRowText ); + tmLimitsElement.appendChild( maxTileRowElem ); + + tmsLimitsElement.appendChild( tmLimitsElement ); + } + tmslElement.appendChild( tmsLimitsElement ); + + layerElem.appendChild( tmslElement ); + } + + contentsElement.appendChild( layerElem ); + } + } + + void appendTileMatrixSetElements( QDomDocument &doc, QDomElement &contentsElement, + QList< tileMatrixSetDef > tmsList ) + { + for ( const tileMatrixSetDef &tms : tmsList ) + { + //wmts:TileMatrixSet + QDomElement tmsElement = doc.createElement( QStringLiteral( "TileMatrixSet" )/*wmts:TileMatrixSet*/ ); + + QDomElement identifierElem = doc.createElement( QStringLiteral( "ows:Identifier" ) ); + QDomText identifierText = doc.createTextNode( tms.ref ); + identifierElem.appendChild( identifierText ); + tmsElement.appendChild( identifierElem ); + + QDomElement crsElem = doc.createElement( QStringLiteral( "ows:SupportedCRS" ) ); + QDomText crsText = doc.createTextNode( tms.ref ); + crsElem.appendChild( crsText ); + tmsElement.appendChild( crsElem ); + + //wmts:TileMatrix + int tmIdx = 0; + for ( const tileMatrixDef &tm : tms.tileMatrixList ) + { + QDomElement tmElement = doc.createElement( QStringLiteral( "TileMatrix" )/*wmts:TileMatrix*/ ); + + QDomElement tmIdentifierElem = doc.createElement( QStringLiteral( "ows:Identifier" ) ); + QDomText tmIdentifierText = doc.createTextNode( QString::number( tmIdx ) ); + tmIdentifierElem.appendChild( tmIdentifierText ); + tmElement.appendChild( tmIdentifierElem ); + + QDomElement tmScaleDenomElem = doc.createElement( QStringLiteral( "ScaleDenominator" ) ); + QDomText tmScaleDenomText = doc.createTextNode( qgsDoubleToString( tm.scaleDenominator, 6 ) ); + tmScaleDenomElem.appendChild( tmScaleDenomText ); + tmElement.appendChild( tmScaleDenomElem ); + + QDomElement tmTopLeftCornerElem = doc.createElement( QStringLiteral( "TopLeftCorner" ) ); + QDomText tmTopLeftCornerText = doc.createTextNode( qgsDoubleToString( tm.left, 6 ) + ' ' + qgsDoubleToString( tm.top, 6 ) ); + tmTopLeftCornerElem.appendChild( tmTopLeftCornerText ); + tmElement.appendChild( tmTopLeftCornerElem ); + + QDomElement tmTileWidthElem = doc.createElement( QStringLiteral( "TileWidth" ) ); + QDomText tmTileWidthText = doc.createTextNode( QString::number( 256 ) ); + tmTileWidthElem.appendChild( tmTileWidthText ); + tmElement.appendChild( tmTileWidthElem ); + + QDomElement tmTileHeightElem = doc.createElement( QStringLiteral( "TileHeight" ) ); + QDomText tmTileHeightText = doc.createTextNode( QString::number( 256 ) ); + tmTileHeightElem.appendChild( tmTileHeightText ); + tmElement.appendChild( tmTileHeightElem ); + + QDomElement tmColElem = doc.createElement( QStringLiteral( "MatrixWidth" ) ); + QDomText tmColText = doc.createTextNode( QString::number( tm.col ) ); + tmColElem.appendChild( tmColText ); + tmElement.appendChild( tmColElem ); + + QDomElement tmRowElem = doc.createElement( QStringLiteral( "MatrixHeight" ) ); + QDomText tmRowText = doc.createTextNode( QString::number( tm.row ) ); + tmRowElem.appendChild( tmRowText ); + tmElement.appendChild( tmRowElem ); + + tmsElement.appendChild( tmElement ); + ++tmIdx; + } + + contentsElement.appendChild( tmsElement ); + } + } + + } // namespace + +} // namespace QgsWmts + + + diff --git a/src/server/services/wmts/qgswmtsgetcapabilities.h b/src/server/services/wmts/qgswmtsgetcapabilities.h new file mode 100644 index 00000000000..d2dece425ea --- /dev/null +++ b/src/server/services/wmts/qgswmtsgetcapabilities.h @@ -0,0 +1,62 @@ +/*************************************************************************** + qgswmtsgecapabilities.h + ------------------------- + begin : July 23 , 2017 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 QGSWMTSGETCAPABILITIES_H +#define QGSWMTSGETCAPABILITIES_H + +#include + +namespace QgsWmts +{ + + /** + * Create Contents element for get capabilities document + */ + QDomElement getContentsElement( QDomDocument &doc, QgsServerInterface *serverIface, const QgsProject *project ); + + /** + * Create OperationsMetadata element for get capabilities document + */ + QDomElement getOperationsMetadataElement( QDomDocument &doc, const QgsProject *project, const QgsServerRequest &request ); + + /** + * Create ServiceProvider element for get capabilities document + */ + QDomElement getServiceProviderElement( QDomDocument &doc, const QgsProject *project ); + + /** + * Create ServiceIdentification element for get capabilities document + */ + QDomElement getServiceIdentificationElement( QDomDocument &doc, const QgsProject *project ); + + /** + * Create get capabilities document + */ + QDomDocument createGetCapabilitiesDocument( QgsServerInterface *serverIface, + const QgsProject *project, const QString &version, + const QgsServerRequest &request ); + + /** + * Output WCS GetCapabilities response + */ + void writeGetCapabilities( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request, + QgsServerResponse &response ); + +} // namespace QgsWcs + +#endif + diff --git a/src/server/services/wmts/qgswmtsgetfeatureinfo.cpp b/src/server/services/wmts/qgswmtsgetfeatureinfo.cpp new file mode 100644 index 00000000000..307291b49f4 --- /dev/null +++ b/src/server/services/wmts/qgswmtsgetfeatureinfo.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** + qgswmtsgetfeatureinfo.cpp + ------------------------- + begin : July 23 , 2017 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 "qgswmtsutils.h" +#include "qgswmtsparameters.h" +#include "qgswmtsgetfeatureinfo.h" + +#include + +namespace QgsWmts +{ + + void writeGetFeatureInfo( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request, + QgsServerResponse &response ) + { + Q_UNUSED( version ); + const QgsWmtsParameters params( QUrlQuery( request.url() ) ); + + // WMS query + QUrlQuery query = translateWmtsParamToWmsQueryItem( QStringLiteral( "GetFeatureInfo" ), params, project, serverIface ); + + // GetFeatureInfo query items + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::QUERY_LAYERS ), params.layer() ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::I ), params.i() ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::J ), params.j() ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::INFO_FORMAT ), params.infoFormatAsString() ); + + QgsServerParameters wmsParams( query ); + QgsServerRequest wmsRequest( "?" + query.query( QUrl::FullyDecoded ) ); + QgsService *service = serverIface->serviceRegistry()->getService( wmsParams.service(), wmsParams.version() ); + service->executeRequest( wmsRequest, response, project ); + } + +} // namespace QgsWmts + + + + diff --git a/src/server/services/wmts/qgswmtsgetfeatureinfo.h b/src/server/services/wmts/qgswmtsgetfeatureinfo.h new file mode 100644 index 00000000000..af1e3fd1f63 --- /dev/null +++ b/src/server/services/wmts/qgswmtsgetfeatureinfo.h @@ -0,0 +1,28 @@ +/*************************************************************************** + qgswmtsgetfeatureinfo.h + ------------------------- + begin : July 23 , 2017 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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. * + * * + ***************************************************************************/ + +namespace QgsWmts +{ + + /** + * Output GetFeatureInfo response + */ + void writeGetFeatureInfo( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request, + QgsServerResponse &response ); + +} // namespace QgsWmts diff --git a/src/server/services/wmts/qgswmtsgettile.cpp b/src/server/services/wmts/qgswmtsgettile.cpp new file mode 100644 index 00000000000..e2d64a6dcb3 --- /dev/null +++ b/src/server/services/wmts/qgswmtsgettile.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** + qgswmtsgettile.cpp + ------------------------- + begin : July 23 , 2017 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 "qgswmtsutils.h" +#include "qgswmtsparameters.h" +#include "qgswmtsgettile.h" + +#include + +namespace QgsWmts +{ + + void writeGetTile( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request, + QgsServerResponse &response ) + { + Q_UNUSED( version ); + const QgsWmtsParameters params( QUrlQuery( request.url() ) ); + + // WMS query + QUrlQuery query = translateWmtsParamToWmsQueryItem( QStringLiteral( "GetMap" ), params, project, serverIface ); + + // Get cached image + QgsAccessControl *accessControl = serverIface->accessControls(); + QgsServerCacheManager *cacheManager = serverIface->cacheManager(); + if ( cacheManager ) + { + QgsWmtsParameters::Format f = params.format(); + QString contentType; + QString saveFormat; + std::unique_ptr image; + if ( f == QgsWmtsParameters::Format::JPG ) + { + contentType = QStringLiteral( "image/jpeg" ); + saveFormat = QStringLiteral( "JPEG" ); + image = qgis::make_unique( 256, 256, QImage::Format_RGB32 ); + } + else + { + contentType = QStringLiteral( "image/png" ); + saveFormat = QStringLiteral( "PNG" ); + image = qgis::make_unique( 256, 256, QImage::Format_ARGB32_Premultiplied ); + } + + QByteArray content = cacheManager->getCachedImage( project, request, accessControl ); + if ( !content.isEmpty() && image->loadFromData( content ) ) + { + response.setHeader( QStringLiteral( "Content-Type" ), contentType ); + image->save( response.io(), qPrintable( saveFormat ) ); + return; + } + } + + + QgsServerParameters wmsParams( query ); + QgsServerRequest wmsRequest( "?" + query.query( QUrl::FullyDecoded ) ); + QgsService *service = serverIface->serviceRegistry()->getService( wmsParams.service(), wmsParams.version() ); + service->executeRequest( wmsRequest, response, project ); + if ( cacheManager ) + { + QByteArray content = response.data(); + if ( !content.isEmpty() ) + cacheManager->setCachedImage( &content, project, request, accessControl ); + } + } + +} // namespace QgsWmts + + + + diff --git a/src/server/services/wmts/qgswmtsgettile.h b/src/server/services/wmts/qgswmtsgettile.h new file mode 100644 index 00000000000..376ee6b4cfe --- /dev/null +++ b/src/server/services/wmts/qgswmtsgettile.h @@ -0,0 +1,28 @@ +/*************************************************************************** + qgswmtsgettile.h + ------------------------- + begin : July 23 , 2017 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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. * + * * + ***************************************************************************/ + +namespace QgsWmts +{ + + /** + * Output GetTile response + */ + void writeGetTile( QgsServerInterface *serverIface, const QgsProject *project, + const QString &version, const QgsServerRequest &request, + QgsServerResponse &response ); + +} // namespace QgsWmts diff --git a/src/server/services/wmts/qgswmtsparameters.cpp b/src/server/services/wmts/qgswmtsparameters.cpp new file mode 100644 index 00000000000..ddaa05f75b0 --- /dev/null +++ b/src/server/services/wmts/qgswmtsparameters.cpp @@ -0,0 +1,310 @@ +/*************************************************************************** + qgswmtsparameters.cpp + -------------------- + begin : Aug 10, 2018 + copyright : (C) 2018 by René-Luc Dhont + email : rldhont at 3liz dot 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 "qgswmtsparameters.h" +#include "qgsmessagelog.h" +#include + +namespace QgsWmts +{ + // + // QgsWmsParameterForWmts + // + QString QgsWmsParameterForWmts::name( const QgsWmsParameterForWmts::Name name ) + { + const QMetaEnum metaEnum( QMetaEnum::fromType() ); + return metaEnum.valueToKey( name ); + } + + QgsWmsParameterForWmts::Name QgsWmsParameterForWmts::name( const QString &name ) + { + const QMetaEnum metaEnum( QMetaEnum::fromType() ); + return ( QgsWmsParameterForWmts::Name ) metaEnum.keyToValue( name.toUpper().toStdString().c_str() ); + } + + // + // QgsWmtsParameter + // + QgsWmtsParameter::QgsWmtsParameter( const QgsWmtsParameter::Name name, + const QVariant::Type type, + const QVariant defaultValue ) + : QgsServerParameterDefinition( type, defaultValue ) + , mName( name ) + { + } + + int QgsWmtsParameter::toInt() const + { + bool ok = false; + const int val = QgsServerParameterDefinition::toInt( ok ); + + if ( !ok ) + { + raiseError(); + } + + return val; + } + + void QgsWmtsParameter::raiseError() const + { + const QString msg = QString( "%1 ('%2') cannot be converted into %3" ).arg( name( mName ), toString(), typeName() ); + QgsServerParameterDefinition::raiseError( msg ); + } + + QString QgsWmtsParameter::name( const QgsWmtsParameter::Name name ) + { + const QMetaEnum metaEnum( QMetaEnum::fromType() ); + return metaEnum.valueToKey( name ); + } + + QgsWmtsParameter::Name QgsWmtsParameter::name( const QString &name ) + { + const QMetaEnum metaEnum( QMetaEnum::fromType() ); + return ( QgsWmtsParameter::Name ) metaEnum.keyToValue( name.toUpper().toStdString().c_str() ); + } + + // + // QgsWmtsParameters + // + QgsWmtsParameters::QgsWmtsParameters() + : QgsServerParameters() + { + // Available version number + mVersions.append( QgsProjectVersion( 1, 0, 0 ) ); + + const QgsWmtsParameter pLayer = QgsWmtsParameter( QgsWmtsParameter::LAYER ); + save( pLayer ); + + const QgsWmtsParameter pFormat = QgsWmtsParameter( QgsWmtsParameter::FORMAT ); + save( pFormat ); + + const QgsWmtsParameter pTileMatrix = QgsWmtsParameter( QgsWmtsParameter::TILEMATRIX, + QVariant::Int, + QVariant( -1 ) ); + save( pTileMatrix ); + + const QgsWmtsParameter pTileRow = QgsWmtsParameter( QgsWmtsParameter::TILEROW, + QVariant::Int, + QVariant( -1 ) ); + save( pTileRow ); + + const QgsWmtsParameter pTileCol = QgsWmtsParameter( QgsWmtsParameter::TILECOL, + QVariant::Int, + QVariant( -1 ) ); + save( pTileCol ); + + const QgsWmtsParameter pInfoFormat( QgsWmtsParameter::INFOFORMAT ); + save( pInfoFormat ); + + const QgsWmtsParameter pI( QgsWmtsParameter::I, + QVariant::Int, + QVariant( -1 ) ); + save( pI ); + + const QgsWmtsParameter pJ( QgsWmtsParameter::J, + QVariant::Int, + QVariant( -1 ) ); + save( pJ ); + } + + QgsWmtsParameters::QgsWmtsParameters( const QgsServerParameters ¶meters ) + : QgsWmtsParameters() + { + load( parameters.urlQuery() ); + } + + bool QgsWmtsParameters::loadParameter( const QString &key, const QString &value ) + { + bool loaded = false; + + const QgsWmtsParameter::Name name = QgsWmtsParameter::name( key ); + if ( name >= 0 ) + { + mWmtsParameters[name].mValue = value; + if ( ! mWmtsParameters[name].isValid() ) + { + mWmtsParameters[name].raiseError(); + } + + loaded = true; + } + + return loaded; + } + + void QgsWmtsParameters::save( const QgsWmtsParameter ¶meter ) + { + mWmtsParameters[ parameter.mName ] = parameter; + } + + void QgsWmtsParameters::dump() const + { + log( "WMTS Request parameters:" ); + for ( auto parameter : mWmtsParameters.toStdMap() ) + { + const QString value = parameter.second.toString(); + + if ( ! value.isEmpty() ) + { + const QString name = QgsWmtsParameter::name( parameter.first ); + log( QStringLiteral( " - %1 : %2" ).arg( name, value ) ); + } + } + + if ( !version().isEmpty() ) + log( QStringLiteral( " - VERSION : %1" ).arg( version() ) ); + } + + QString QgsWmtsParameters::layer() const + { + return mWmtsParameters[ QgsWmtsParameter::LAYER ].toString(); + } + + QString QgsWmtsParameters::formatAsString() const + { + return mWmtsParameters[ QgsWmtsParameter::FORMAT ].toString(); + } + + QgsWmtsParameters::Format QgsWmtsParameters::format() const + { + QString fStr = formatAsString(); + + if ( fStr.isEmpty() ) + return Format::NONE; + + Format f = Format::PNG; + if ( fStr.compare( QLatin1String( "jpg" ), Qt::CaseInsensitive ) == 0 + || fStr.compare( QLatin1String( "jpeg" ), Qt::CaseInsensitive ) == 0 + || fStr.compare( QLatin1String( "image/jpeg" ), Qt::CaseInsensitive ) == 0 ) + f = Format::JPG; + + return f; + } + + QString QgsWmtsParameters::tileMatrixSet() const + { + return mWmtsParameters[ QgsWmtsParameter::TILEMATRIXSET ].toString(); + } + + QString QgsWmtsParameters::tileMatrix() const + { + return mWmtsParameters[ QgsWmtsParameter::TILEMATRIX ].toString(); + } + + int QgsWmtsParameters::tileMatrixAsInt() const + { + return mWmtsParameters[ QgsWmtsParameter::TILEMATRIX ].toInt(); + } + + QString QgsWmtsParameters::tileRow() const + { + return mWmtsParameters[ QgsWmtsParameter::TILEROW ].toString(); + } + + int QgsWmtsParameters::tileRowAsInt() const + { + return mWmtsParameters[ QgsWmtsParameter::TILEROW ].toInt(); + } + + QString QgsWmtsParameters::tileCol() const + { + return mWmtsParameters[ QgsWmtsParameter::TILECOL ].toString(); + } + + int QgsWmtsParameters::tileColAsInt() const + { + return mWmtsParameters[ QgsWmtsParameter::TILECOL ].toInt(); + } + + QString QgsWmtsParameters::infoFormatAsString() const + { + return mWmtsParameters[ QgsWmtsParameter::INFOFORMAT ].toString(); + } + + QgsWmtsParameters::Format QgsWmtsParameters::infoFormat() const + { + QString fStr = infoFormatAsString(); + + Format f = Format::TEXT; + if ( fStr.isEmpty() ) + return f; + + if ( fStr.startsWith( QLatin1String( "text/xml" ), Qt::CaseInsensitive ) ) + f = Format::XML; + else if ( fStr.startsWith( QLatin1String( "text/html" ), Qt::CaseInsensitive ) ) + f = Format::HTML; + else if ( fStr.startsWith( QLatin1String( "text/plain" ), Qt::CaseInsensitive ) ) + f = Format::TEXT; + else if ( fStr.startsWith( QLatin1String( "application/vnd.ogc.gml" ), Qt::CaseInsensitive ) ) + f = Format::GML; + else + f = Format::NONE; + + return f; + } + + int QgsWmtsParameters::infoFormatVersion() const + { + if ( infoFormat() != Format::GML ) + return -1; + + QString fStr = infoFormatAsString(); + if ( fStr.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ), Qt::CaseInsensitive ) ) + return 3; + else + return 2; + } + + QString QgsWmtsParameters::i() const + { + return mWmtsParameters[ QgsWmtsParameter::I ].toString(); + } + + QString QgsWmtsParameters::j() const + { + return mWmtsParameters[ QgsWmtsParameter::J ].toString(); + } + + int QgsWmtsParameters::iAsInt() const + { + return mWmtsParameters[ QgsWmtsParameter::I ].toInt(); + } + + int QgsWmtsParameters::jAsInt() const + { + return mWmtsParameters[ QgsWmtsParameter::J ].toInt(); + } + + QgsProjectVersion QgsWmtsParameters::versionAsNumber() const + { + QString vStr = version(); + QgsProjectVersion version; + + if ( vStr.isEmpty() ) + version = QgsProjectVersion( 1, 0, 0 ); // default value + else if ( mVersions.contains( QgsProjectVersion( vStr ) ) ) + version = QgsProjectVersion( vStr ); + + return version; + } + + void QgsWmtsParameters::log( const QString &msg ) const + { + QgsMessageLog::logMessage( msg, "Server", Qgis::Info ); + } +} diff --git a/src/server/services/wmts/qgswmtsparameters.h b/src/server/services/wmts/qgswmtsparameters.h new file mode 100644 index 00000000000..412bc41adc2 --- /dev/null +++ b/src/server/services/wmts/qgswmtsparameters.h @@ -0,0 +1,328 @@ +/*************************************************************************** + qgswmtsparameters.h + ------------------- + begin : Aug 10, 2018 + copyright : (C) 2018 by René-Luc Dhont + email : rldhont at 3liz dot 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 QGSWMTSPARAMETERS_H +#define QGSWMTSPARAMETERS_H + +#include +#include +#include + +#include "qgswmtsserviceexception.h" +#include "qgsserverrequest.h" +#include "qgsprojectversion.h" +#include "qgsserverparameters.h" + +namespace QgsWmts +{ + + /** + * \ingroup server + * \class QgsWmts::QgsWmsParameterForWmts + * \brief WMS parameter used by WMTS service. + * \since QGIS 3.4 + */ + class QgsWmsParameterForWmts : public QgsServerParameterDefinition + { + Q_GADGET + + public: + //! Available parameters for translating WMTS requests to WMS requests + enum Name + { + UNKNOWN, + LAYERS, + STYLES, + CRS, + BBOX, + WIDTH, + HEIGHT, + FORMAT, + TRANSPARENT, + DPI, + QUERY_LAYERS, + I, + J, + INFO_FORMAT + }; + Q_ENUM( Name ) + + /** + * Converts a parameter's name into its string representation. + */ + static QString name( const QgsWmsParameterForWmts::Name ); + + /** + * Converts a string into a parameter's name (UNKNOWN in case of an + * invalid string). + */ + static QgsWmsParameterForWmts::Name name( const QString &name ); + }; + + /** + * \ingroup server + * \class QgsWmts::QgsWmtsParameter + * \brief WMTS parameter received from the client. + * \since QGIS 3.4 + */ + class QgsWmtsParameter : public QgsServerParameterDefinition + { + Q_GADGET + + public: + //! Available parameters for WMTS requests + enum Name + { + UNKNOWN, + LAYER, + FORMAT, + TILEMATRIXSET, + TILEMATRIX, + TILEROW, + TILECOL, + INFOFORMAT, + I, + J + }; + Q_ENUM( Name ) + + /** + * Constructor for QgsWmtsParameter. + * \param name Name of the WMS parameter + * \param type Type of the parameter + * \param defaultValue Default value of the parameter + */ + QgsWmtsParameter( const QgsWmtsParameter::Name name = QgsWmtsParameter::UNKNOWN, + const QVariant::Type type = QVariant::String, + const QVariant defaultValue = QVariant( "" ) ); + + /** + * Default destructor for QgsWmtsParameter. + */ + virtual ~QgsWmtsParameter() = default; + + /** + * Converts the parameter into an integer. + * \returns An integer + * \throws QgsBadRequestException Invalid parameter exception + */ + int toInt() const; + + /** + * Raises an error in case of an invalid conversion. + * \throws QgsBadRequestException Invalid parameter exception + */ + void raiseError() const; + + /** + * Converts a parameter's name into its string representation. + */ + static QString name( const QgsWmtsParameter::Name ); + + /** + * Converts a string into a parameter's name (UNKNOWN in case of an + * invalid string). + */ + static QgsWmtsParameter::Name name( const QString &name ); + + QgsWmtsParameter::Name mName; + }; + + + /** + * \ingroup server + * \class QgsWmts::QgsWmtsParameters + * \brief Provides an interface to retrieve and manipulate WMTS parameters received from the client. + * \since QGIS 3.4 + */ + class QgsWmtsParameters : public QgsServerParameters + { + Q_GADGET + + public: + + //! Output format for the response + enum Format + { + NONE, + JPG, + PNG, + TEXT, + XML, + HTML, + GML + }; + + /** + * Constructor for WMTS parameters with specific values. + * \param parameters Map of parameters where keys are parameters' names. + */ + QgsWmtsParameters( const QgsServerParameters ¶meters ); + + /** + * Constructor for WMTS parameters with default values only. + */ + QgsWmtsParameters(); + + /** + * Default destructor for QgsWmtsParameters. + */ + virtual ~QgsWmtsParameters() = default; + + /** + * Dumps parameters. + */ + void dump() const; + + /** + * Returns VERSION parameter if defined or its default value. + * \returns version + */ + QgsProjectVersion versionAsNumber() const; + + /** + * Returns LAYER parameter as a string. + * \returns layer parameter as string + */ + QString layer() const; + + /** + * Returns FORMAT parameter as a string. + * \returns Format parameter as string + */ + QString formatAsString() const; + + /** + * Returns format. If the FORMAT parameter is not used, then the + * default value is NONE. + * \returns format + */ + Format format() const; + + /** + * Returns TILEMATRIXSET parameter as a string. + * \returns tileMatrixSet parameter as string + */ + QString tileMatrixSet() const; + + /** + * Returns TILEMATRIX parameter as a string. + * \returns tileMatrix parameter as string + */ + QString tileMatrix() const; + + /** + * Returns TILEMATRIX parameter as an int or its default value if not + * defined. An exception is raised if TILEMATRIX is defined and cannot be + * converted. + * \returns tileMatrix parameter + * \throws QgsBadRequestException + */ + int tileMatrixAsInt() const; + + /** + * Returns TILEROW parameter as a string. + * \returns tileRow parameter as string + */ + QString tileRow() const; + + /** + * Returns TILEROW parameter as an int or its default value if not + * defined. An exception is raised if TILEROW is defined and cannot be + * converted. + * \returns tileRow parameter + * \throws QgsBadRequestException + */ + int tileRowAsInt() const; + + /** + * Returns TILECOL parameter as a string. + * \returns tileCol parameter as string + */ + QString tileCol() const; + + /** + * Returns TILECOL parameter as an int or its default value if not + * defined. An exception is raised if TILECOL is defined and cannot be + * converted. + * \returns tileCol parameter + * \throws QgsBadRequestException + */ + int tileColAsInt() const; + + /** + * Returns INFO_FORMAT parameter as a string. + * \returns INFO_FORMAT parameter as string + */ + QString infoFormatAsString() const; + + /** + * Returns infoFormat. If the INFO_FORMAT parameter is not used, then the + * default value is text/plain. + * \returns infoFormat + */ + Format infoFormat() const; + + /** + * Returns the infoFormat version for GML. If the INFO_FORMAT is not GML, + * then the default value is -1. + * \returns infoFormat version + */ + int infoFormatVersion() const; + + /** + * Returns I parameter or an empty string if not defined. + * \returns i parameter + */ + QString i() const; + + /** + * Returns I parameter as an int or its default value if not + * defined. An exception is raised if I is defined and cannot be + * converted. + * \returns i parameter + * \throws QgsBadRequestException + */ + int iAsInt() const; + + /** + * Returns J parameter or an empty string if not defined. + * \returns j parameter + */ + QString j() const; + + /** + * Returns J parameter as an int or its default value if not + * defined. An exception is raised if J is defined and cannot be + * converted. + * \returns j parameter + * \throws QgsBadRequestException + */ + int jAsInt() const; + + private: + bool loadParameter( const QString &name, const QString &key ) override; + void save( const QgsWmtsParameter ¶meter ); + + void log( const QString &msg ) const; + + QList mVersions; + QMap mWmtsParameters; + }; +} + +#endif diff --git a/src/server/services/wmts/qgswmtsserviceexception.h b/src/server/services/wmts/qgswmtsserviceexception.h new file mode 100644 index 00000000000..6426059d1e6 --- /dev/null +++ b/src/server/services/wmts/qgswmtsserviceexception.h @@ -0,0 +1,105 @@ +/*************************************************************************** + qgswmtsserviceexception.h + ------------------------ + begin : July 23, 2018 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 QGSWMTSSERVICEEXCEPTION_H +#define QGSWMTSSERVICEEXCEPTION_H + +#include + +#include "qgsserverexception.h" + +namespace QgsWmts +{ + + /** + * \ingroup server + * \class QgsWmts::QgsServiceException + * \brief Exception class for WMTS services + * \since QGIS 3.4 + */ + class QgsServiceException : public QgsOgcServiceException + { + public: + + /** + * Constructor for QgsServiceException (empty locator attribute). + * \param code Error code name + * \param message Exception message to return to the client + * \param responseCode HTTP error code + */ + QgsServiceException( const QString &code, const QString &message, + int responseCode = 200 ) + : QgsOgcServiceException( code, message, QString(), responseCode, QStringLiteral( "1.0.0" ) ) + {} + + /** + * Constructor for QgsServiceException. + * \param code Error code name + * \param message Exception message to return to the client + * \param locator Locator attribute according to OGC specifications + * \param responseCode HTTP error code + */ + QgsServiceException( const QString &code, const QString &message, const QString &locator, + int responseCode = 200 ) + : QgsOgcServiceException( code, message, locator, responseCode, QStringLiteral( "1.0.0" ) ) + {} + + }; + + /** + * \ingroup server + * \class QgsWmts::QgsSecurityAccessException + * \brief Exception thrown when data access violates access controls + * \since QGIS 3.4 + */ + class QgsSecurityAccessException: public QgsServiceException + { + public: + + /** + * Constructor for QgsSecurityAccessException (Security code name). + * \param message Exception message to return to the client + * \param locator Locator attribute according to OGC specifications + */ + QgsSecurityAccessException( const QString &message, const QString &locator = QString() ) + : QgsServiceException( QStringLiteral( "Security" ), message, locator, 403 ) + {} + }; + + /** + * \ingroup server + * \class QgsWmts::QgsRequestNotWellFormedException + * \brief Exception thrown in case of malformed request + * \since QGIS 3.4 + */ + class QgsRequestNotWellFormedException: public QgsServiceException + { + public: + + /** + * Constructor for QgsRequestNotWellFormedException (RequestNotWellFormed code name). + * \param message Exception message to return to the client + * \param locator Locator attribute according to OGC specifications + */ + QgsRequestNotWellFormedException( const QString &message, const QString &locator = QString() ) + : QgsServiceException( QStringLiteral( "RequestNotWellFormed" ), message, locator, 400 ) + {} + }; +} // namespace QgsWmts + +#endif + diff --git a/src/server/services/wmts/qgswmtsutils.cpp b/src/server/services/wmts/qgswmtsutils.cpp new file mode 100644 index 00000000000..b6bdb3ed08e --- /dev/null +++ b/src/server/services/wmts/qgswmtsutils.cpp @@ -0,0 +1,719 @@ +/*************************************************************************** + qgswmtsutils.cpp + ------------------------- + begin : July 23, 2018 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 "qgswmtsutils.h" +#include "qgswmtsparameters.h" +#include "qgsconfigcache.h" +#include "qgsserverprojectutils.h" + +#include "qgsproject.h" +#include "qgsexception.h" +#include "qgsmapserviceexception.h" +#include "qgscoordinatereferencesystem.h" +#include "qgslayertree.h" +#include "qgslayertreemodel.h" +#include "qgslayertreemodellegendnode.h" +#include "qgssettings.h" + + +namespace QgsWmts +{ + namespace + { + QMap< QgsUnitTypes::DistanceUnit, double> populateInchesPerUnit(); + QMap< QString, tileMatrixInfo> populateTileMatrixInfoMap(); + + QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( GEO_EPSG_CRS_AUTHID ); + + int DOTS_PER_INCH = 72; + double METERS_PER_INCH = 0.02540005080010160020; + QMap< QgsUnitTypes::DistanceUnit, double> INCHES_PER_UNIT = populateInchesPerUnit(); + int tileWidth = 256; + int tileHeight = 256; + + QMap< QString, tileMatrixInfo> tileMatrixInfoMap = populateTileMatrixInfoMap(); + } + + QString implementationVersion() + { + return QStringLiteral( "1.0.0" ); + } + + + QString serviceUrl( const QgsServerRequest &request, const QgsProject *project ) + { + QString href; + if ( project ) + { + href = QgsServerProjectUtils::wmtsServiceUrl( *project ); + } + + // Build default url + if ( href.isEmpty() ) + { + QUrl url = request.url(); + + QgsWmtsParameters params; + params.load( QUrlQuery( url ) ); + params.remove( QgsServerParameter::REQUEST ); + params.remove( QgsServerParameter::VERSION_SERVICE ); + params.remove( QgsServerParameter::SERVICE ); + + url.setQuery( params.urlQuery() ); + href = url.toString(); + } + + return href; + } + + tileMatrixInfo getTileMatrixInfo( const QString &crsStr, const QgsProject *project ) + { + if ( tileMatrixInfoMap.contains( crsStr ) ) + return tileMatrixInfoMap[crsStr]; + + tileMatrixInfo tmi; + tmi.ref = crsStr; + + QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crsStr ); + QgsCoordinateTransform crsTransform( wgs84, crs, project ); + try + { + tmi.extent = crsTransform.transformBoundingBox( crs.bounds() ); + } + catch ( QgsCsException &cse ) + { + Q_UNUSED( cse ); + } + + tmi.unit = crs.mapUnits(); + + // calculate tile matrix scale denominator + double scaleDenominator = 0.0; + int colRes = ( tmi.extent.xMaximum() - tmi.extent.xMinimum() ) / tileWidth; + int rowRes = ( tmi.extent.yMaximum() - tmi.extent.yMinimum() ) / tileHeight; + if ( colRes > rowRes ) + scaleDenominator = std::ceil( colRes * INCHES_PER_UNIT[ tmi.unit ] * METERS_PER_INCH / 0.00028 ); + else + scaleDenominator = std::ceil( rowRes * INCHES_PER_UNIT[ tmi.unit ] * METERS_PER_INCH / 0.00028 ); + + // Update extent to get a square one + QgsRectangle extent = tmi.extent; + double res = 0.00028 * scaleDenominator / METERS_PER_INCH / INCHES_PER_UNIT[ tmi.unit ]; + int col = std::ceil( ( extent.xMaximum() - extent.xMinimum() ) / ( tileWidth * res ) ); + int row = std::ceil( ( extent.yMaximum() - extent.yMinimum() ) / ( tileHeight * res ) ); + if ( col > 1 || row > 1 ) + { + // Update scale + if ( col > row ) + { + res = col * res; + scaleDenominator = col * scaleDenominator; + } + else + { + res = row * res; + scaleDenominator = row * scaleDenominator; + } + // set col and row to 1 for the square + col = 1; + row = 1; + } + // Calculate extent + double left = ( extent.xMinimum() + ( extent.xMaximum() - extent.xMinimum() ) / 2.0 ) - ( col / 2.0 ) * ( tileWidth * res ); + double bottom = ( extent.yMinimum() + ( extent.yMaximum() - extent.yMinimum() ) / 2.0 ) - ( row / 2.0 ) * ( tileHeight * res ); + double right = ( extent.xMinimum() + ( extent.xMaximum() - extent.xMinimum() ) / 2.0 ) + ( col / 2.0 ) * ( tileWidth * res ); + double top = ( extent.yMinimum() + ( extent.yMaximum() - extent.yMinimum() ) / 2.0 ) + ( row / 2.0 ) * ( tileHeight * res ); + tmi.extent = QgsRectangle( left, bottom, right, top ); + + tmi.scaleDenominator = scaleDenominator; + + tileMatrixInfoMap[crsStr] = tmi; + + return tmi; + } + + tileMatrixSetDef getTileMatrixSet( tileMatrixInfo tmi, double minScale ) + { + QList< tileMatrixDef > tileMatrixList; + double scaleDenominator = tmi.scaleDenominator; + QgsRectangle extent = tmi.extent; + QgsUnitTypes::DistanceUnit unit = tmi.unit; + + while ( scaleDenominator >= minScale ) + { + double scale = scaleDenominator; + double res = 0.00028 * scale / METERS_PER_INCH / INCHES_PER_UNIT[ unit ]; + int col = std::ceil( ( extent.xMaximum() - extent.xMinimum() ) / ( tileWidth * res ) ); + int row = std::ceil( ( extent.yMaximum() - extent.yMinimum() ) / ( tileHeight * res ) ); + double left = ( extent.xMinimum() + ( extent.xMaximum() - extent.xMinimum() ) / 2.0 ) - ( col / 2.0 ) * ( tileWidth * res ); + double top = ( extent.yMinimum() + ( extent.yMaximum() - extent.yMinimum() ) / 2.0 ) + ( row / 2.0 ) * ( tileHeight * res ); + + tileMatrixDef tm; + tm.resolution = res; + tm.scaleDenominator = scale; + tm.col = col; + tm.row = row; + tm.left = std::max( left, extent.xMinimum() ); + tm.top = std::min( top, extent.yMaximum() ); + tileMatrixList.append( tm ); + + scaleDenominator = scale / 2; + } + + tileMatrixSetDef tms; + tms.ref = tmi.ref; + tms.extent = extent; + tms.unit = unit; + tms.tileMatrixList = tileMatrixList; + + return tms; + } + + double getProjectMinScale( const QgsProject *project ) + { + double scale = -1.0; + + // default scales + QgsSettings settings; + QStringList scaleList = settings.value( QStringLiteral( "Map/scales" ), PROJECT_SCALES ).toString().split( ',' ); + //load project scales + bool projectScales = project->readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ); + if ( projectScales ) + { + scaleList = project->readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) ); + } + // get min and max scales + if ( !scaleList.isEmpty() ) + { + for ( const QString &scaleText : scaleList ) + { + double scaleValue = scaleText.toDouble(); + if ( scale == -1.0 ) + { + scale = scaleValue; + } + else if ( scaleValue < scale ) + { + scale = scaleValue; + } + } + } + if ( scale < 500.0 ) + { + return 500.0; + } + return scale; + } + + QList< tileMatrixSetDef > getTileMatrixSetList( const QgsProject *project ) + { + QList< tileMatrixSetDef > tmsList; + + double minScale = project->readNumEntry( QStringLiteral( "WMTSMinScale" ), QStringLiteral( "/" ), -1.0 ); + if ( minScale == -1.0 ) + { + minScale = getProjectMinScale( project ); + } + + QStringList crsList = QgsServerProjectUtils::wmsOutputCrsList( *project ); + for ( const QString &crsStr : crsList ) + { + tileMatrixInfo tmi = getTileMatrixInfo( crsStr, project ); + if ( tmi.scaleDenominator > 0.0 ) + { + tmsList.append( getTileMatrixSet( tmi, minScale ) ); + } + } + + return tmsList; + } + + QList< layerDef > getWmtsLayerList( QgsServerInterface *serverIface, const QgsProject *project ) + { + QList< layerDef > wmtsLayers; +#ifdef HAVE_SERVER_PYTHON_PLUGINS + QgsAccessControl *accessControl = serverIface->accessControls(); +#endif + QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( GEO_EPSG_CRS_AUTHID ); + + QStringList nonIdentifiableLayers = project->nonIdentifiableLayers(); + + // WMTS Project configuration + bool wmtsProject = project->readBoolEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Project" ) ); + + // Root Layer name + QString rootLayerName = QgsServerProjectUtils::wmsRootName( *project ); + if ( rootLayerName.isEmpty() && !project->title().isEmpty() ) + { + rootLayerName = project->title(); + } + + if ( wmtsProject && !rootLayerName.isEmpty() ) + { + layerDef pLayer; + pLayer.id = rootLayerName; + + if ( !project->title().isEmpty() ) + { + pLayer.title = project->title(); + pLayer.abstract = project->title(); + } + + //transform the project native CRS into WGS84 + QgsRectangle projRect = QgsServerProjectUtils::wmsExtent( *project ); + QgsCoordinateReferenceSystem projCrs = project->crs(); + QgsCoordinateTransform exGeoTransform( projCrs, wgs84, project ); + try + { + pLayer.wgs84BoundingRect = exGeoTransform.transformBoundingBox( projRect ); + } + catch ( const QgsCsException & ) + { + pLayer.wgs84BoundingRect = QgsRectangle( -180, -90, 180, 90 ); + } + + // Formats + bool wmtsPngProject = project->readBoolEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Project" ) ); + if ( wmtsPngProject ) + pLayer.formats << QStringLiteral( "image/png" ); + bool wmtsJpegProject = project->readBoolEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Project" ) ); + if ( wmtsJpegProject ) + pLayer.formats << QStringLiteral( "image/jpeg" ); + + // Project is not queryable in WMS + //pLayer.queryable = ( nonIdentifiableLayers.count() != project->count() ); + pLayer.queryable = false; + + wmtsLayers.append( pLayer ); + } + + QStringList wmtsGroupNameList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Group" ) ); + if ( !wmtsGroupNameList.isEmpty() ) + { + QgsLayerTreeGroup *treeRoot = project->layerTreeRoot(); + + QStringList wmtsPngGroupNameList = project->readListEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Group" ) ); + QStringList wmtsJpegGroupNameList = project->readListEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Group" ) ); + + for ( const QString &gName : wmtsGroupNameList ) + { + QgsLayerTreeGroup *treeGroup = treeRoot->findGroup( gName ); + if ( !treeGroup ) + { + continue; + } + + layerDef pLayer; + pLayer.id = treeGroup->customProperty( QStringLiteral( "wmsShortName" ) ).toString(); + if ( pLayer.id.isEmpty() ) + pLayer.id = gName; + + pLayer.title = treeGroup->customProperty( QStringLiteral( "wmsTitle" ) ).toString(); + if ( pLayer.title.isEmpty() ) + pLayer.title = gName; + + pLayer.abstract = treeGroup->customProperty( QStringLiteral( "wmsAbstract" ) ).toString(); + + QgsRectangle wgs84BoundingRect; + bool queryable = false; + double maxScale = 0.0; + double minScale = 0.0; + for ( QgsLayerTreeLayer *layer : treeGroup->findLayers() ) + { + QgsMapLayer *l = layer->layer(); + if ( !l ) + { + continue; + } + //transform the layer native CRS into WGS84 + QgsCoordinateReferenceSystem layerCrs = l->crs(); + QgsCoordinateTransform exGeoTransform( layerCrs, wgs84, project ); + try + { + wgs84BoundingRect.combineExtentWith( exGeoTransform.transformBoundingBox( l->extent() ) ); + } + catch ( const QgsCsException & ) + { + wgs84BoundingRect.combineExtentWith( QgsRectangle( -180, -90, 180, 90 ) ); + } + if ( !queryable && !nonIdentifiableLayers.contains( l->id() ) ) + { + queryable = true; + } + + double lMaxScale = l->maximumScale(); + if ( lMaxScale > 0.0 && lMaxScale > maxScale ) + { + maxScale = lMaxScale; + } + double lMinScale = l->minimumScale(); + if ( lMinScale > 0.0 && ( minScale == 0.0 || lMinScale < minScale ) ) + { + minScale = lMinScale; + } + } + pLayer.wgs84BoundingRect = wgs84BoundingRect; + pLayer.queryable = queryable; + pLayer.maxScale = maxScale; + pLayer.minScale = minScale; + + // Formats + if ( wmtsPngGroupNameList.contains( gName ) ) + pLayer.formats << QStringLiteral( "image/png" ); + if ( wmtsJpegGroupNameList.contains( gName ) ) + pLayer.formats << QStringLiteral( "image/jpeg" ); + + wmtsLayers.append( pLayer ); + } + } + + QStringList wmtsLayerIdList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Layer" ) ); + QStringList wmtsPngLayerIdList = project->readListEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Layer" ) ); + QStringList wmtsJpegLayerIdList = project->readListEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Layer" ) ); + + for ( const QString &lId : wmtsLayerIdList ) + { + QgsMapLayer *l = project->mapLayer( lId ); + if ( !l ) + { + continue; + } +#ifdef HAVE_SERVER_PYTHON_PLUGINS + if ( !accessControl->layerReadPermission( l ) ) + { + continue; + } +#endif + + layerDef pLayer; + pLayer.id = l->name(); + if ( !l->shortName().isEmpty() ) + pLayer.id = l->shortName(); + pLayer.id = pLayer.id.replace( ' ', '_' ); + + pLayer.title = l->title(); + pLayer.abstract = l->abstract(); + + //transform the layer native CRS into WGS84 + QgsCoordinateReferenceSystem layerCrs = l->crs(); + QgsCoordinateTransform exGeoTransform( layerCrs, wgs84, project ); + try + { + pLayer.wgs84BoundingRect = exGeoTransform.transformBoundingBox( l->extent() ); + } + catch ( const QgsCsException & ) + { + pLayer.wgs84BoundingRect = QgsRectangle( -180, -90, 180, 90 ); + } + + // Formats + if ( wmtsPngLayerIdList.contains( lId ) ) + pLayer.formats << QStringLiteral( "image/png" ); + if ( wmtsJpegLayerIdList.contains( lId ) ) + pLayer.formats << QStringLiteral( "image/jpeg" ); + + pLayer.queryable = ( !nonIdentifiableLayers.contains( l->id() ) ); + + pLayer.maxScale = l->maximumScale(); + pLayer.minScale = l->minimumScale(); + + wmtsLayers.append( pLayer ); + } + return wmtsLayers; + } + + tileMatrixSetLinkDef getLayerTileMatrixSetLink( const layerDef layer, const tileMatrixSetDef tms, const QgsProject *project ) + { + tileMatrixSetLinkDef tmsl; + + QMap< int, tileMatrixLimitDef > tileMatrixLimits; + + QgsRectangle rect( layer.wgs84BoundingRect ); + if ( tms.ref != QLatin1String( "EPSG:4326" ) ) + { + QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( tms.ref ); + QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( GEO_EPSG_CRS_AUTHID ); + QgsCoordinateTransform exGeoTransform( wgs84, crs, project ); + try + { + rect = exGeoTransform.transformBoundingBox( layer.wgs84BoundingRect ); + } + catch ( const QgsCsException & ) + { + return tmsl; + } + } + tmsl.ref = tms.ref; + + rect = rect.intersect( tms.extent ); + + int tmIdx = -1; + for ( const tileMatrixDef &tm : tms.tileMatrixList ) + { + ++tmIdx; + + if ( layer.maxScale > 0.0 && tm.scaleDenominator > layer.maxScale ) + { + continue; + } + if ( layer.minScale > 0.0 && tm.scaleDenominator < layer.minScale ) + { + continue; + } + + double res = tm.resolution; + + tileMatrixLimitDef tml; + tml.minCol = std::floor( ( rect.xMinimum() - tm.left ) / ( tileWidth * res ) ); + tml.maxCol = std::ceil( ( rect.xMaximum() - tm.left ) / ( tileWidth * res ) ) - 1; + tml.minRow = std::floor( ( tm.top - rect.yMaximum() ) / ( tileHeight * res ) ); + tml.maxRow = std::ceil( ( tm.top - rect.yMinimum() ) / ( tileHeight * res ) ) - 1; + + tileMatrixLimits[tmIdx] = tml; + } + + tmsl.tileMatrixLimits = tileMatrixLimits; + return tmsl; + } + + QUrlQuery translateWmtsParamToWmsQueryItem( const QString &request, const QgsWmtsParameters ¶ms, + const QgsProject *project, QgsServerInterface *serverIface ) + { + //defining Layer + QString layer = params.layer(); + //read Layer + if ( layer.isEmpty() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Layer is mandatory" ) ); + } + //check layer value + bool wmtsProject = project->readBoolEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Project" ) ); + QStringList wmtsGroupNameList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Group" ) ); + QStringList wmtsLayerIdList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Layer" ) ); + QStringList wmtsLayerIds; + if ( wmtsProject ) + { + // Root Layer name + QString rootLayerId = QgsServerProjectUtils::wmsRootName( *project ); + if ( rootLayerId.isEmpty() ) + { + rootLayerId = project->title(); + } + if ( !rootLayerId.isEmpty() ) + { + wmtsLayerIds << rootLayerId; + } + } + if ( !wmtsGroupNameList.isEmpty() ) + { + QgsLayerTreeGroup *treeRoot = project->layerTreeRoot(); + for ( const QString &gName : wmtsGroupNameList ) + { + QgsLayerTreeGroup *treeGroup = treeRoot->findGroup( gName ); + if ( !treeGroup ) + { + continue; + } + QString groupLayerId = treeGroup->customProperty( QStringLiteral( "wmsShortName" ) ).toString(); + if ( groupLayerId.isEmpty() ) + { + groupLayerId = gName; + } + wmtsLayerIds << groupLayerId; + } + } + if ( !wmtsLayerIdList.isEmpty() ) + { +#ifdef HAVE_SERVER_PYTHON_PLUGINS + QgsAccessControl *accessControl = serverIface->accessControls(); +#endif + for ( const QString &lId : wmtsLayerIdList ) + { + QgsMapLayer *l = project->mapLayer( lId ); + if ( !l ) + { + continue; + } +#ifdef HAVE_SERVER_PYTHON_PLUGINS + if ( !accessControl->layerReadPermission( l ) ) + { + continue; + } +#endif + QString layerLayerId = l->shortName(); + if ( layerLayerId.isEmpty() ) + { + layerLayerId = l->name(); + } + wmtsLayerIds << layerLayerId; + } + } + if ( !wmtsLayerIds.contains( layer ) ) + { + QString msg = QObject::tr( "Layer '%1' not found" ).arg( layer ); + throw QgsBadRequestException( QStringLiteral( "LayerNotDefined" ), msg ); + } + + //defining Format + QString format = params.formatAsString(); + //read Format + if ( format.isEmpty() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "Format is mandatory" ) ); + } + + //defining TileMatrixSet ref + QString tms_ref = params.tileMatrixSet(); + //read TileMatrixSet + if ( tms_ref.isEmpty() ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is mandatory" ) ); + } + + // verifying TileMatricSet value + QStringList crsList = QgsServerProjectUtils::wmsOutputCrsList( *project ); + if ( !crsList.contains( tms_ref ) ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is unknown" ) ); + } + + tileMatrixInfo tmi = getTileMatrixInfo( tms_ref, project ); + if ( tmi.scaleDenominator == 0.0 ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is unknown" ) ); + } + tileMatrixSetDef tms = getTileMatrixSet( tmi, getProjectMinScale( project ) ); + + //difining TileMatrix idx + int tm_idx = params.tileMatrixAsInt(); + //read TileMatrix + if ( tm_idx < 0 || tms.tileMatrixList.count() < tm_idx ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrix is unknown" ) ); + } + tileMatrixDef tm = tms.tileMatrixList.at( tm_idx ); + + //defining TileRow + int tr = params.tileRowAsInt(); + //read TileRow + if ( tr < 0 || tm.row <= tr ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TileRow is unknown" ) ); + } + + //defining TileCol + int tc = params.tileColAsInt(); + //read TileCol + if ( tc < 0 || tm.col <= tc ) + { + throw QgsRequestNotWellFormedException( QStringLiteral( "TileCol is unknown" ) ); + } + + int tileWidth = 256; + int tileHeight = 256; + double res = tm.resolution; + double minx = tm.left + tc * ( tileWidth * res ); + double miny = tm.top - ( tr + 1 ) * ( tileHeight * res ); + double maxx = tm.left + ( tc + 1 ) * ( tileWidth * res ); + double maxy = tm.top - tr * ( tileHeight * res ); + QString bbox; + if ( tms.ref == "EPSG:4326" ) + { + bbox = qgsDoubleToString( miny, 6 ) + ',' + + qgsDoubleToString( minx, 6 ) + ',' + + qgsDoubleToString( maxy, 6 ) + ',' + + qgsDoubleToString( maxx, 6 ); + } + else + { + bbox = qgsDoubleToString( minx, 6 ) + ',' + + qgsDoubleToString( miny, 6 ) + ',' + + qgsDoubleToString( maxx, 6 ) + ',' + + qgsDoubleToString( maxy, 6 ); + } + + QUrlQuery query; + if ( !params.value( QStringLiteral( "MAP" ) ).isEmpty() ) + { + query.addQueryItem( QgsServerParameter::name( QgsServerParameter::MAP ), params.value( QStringLiteral( "MAP" ) ) ); + } + query.addQueryItem( QgsServerParameter::name( QgsServerParameter::SERVICE ), QStringLiteral( "WMS" ) ); + query.addQueryItem( QgsServerParameter::name( QgsServerParameter::VERSION_SERVICE ), QStringLiteral( "1.3.0" ) ); + query.addQueryItem( QgsServerParameter::name( QgsServerParameter::REQUEST ), request ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::LAYERS ), layer ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::STYLES ), QString() ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::CRS ), tms.ref ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::BBOX ), bbox ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::WIDTH ), QStringLiteral( "256" ) ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::HEIGHT ), QStringLiteral( "256" ) ); + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::FORMAT ), format ); + if ( params.format() == QgsWmtsParameters::Format::PNG ) + { + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::TRANSPARENT ), QStringLiteral( "true" ) ); + } + query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::DPI ), QStringLiteral( "96" ) ); + + return query; + } + + namespace + { + + QMap< QgsUnitTypes::DistanceUnit, double> populateInchesPerUnit() + { + QMap< QgsUnitTypes::DistanceUnit, double> m; + m[ QgsUnitTypes::DistanceMeters ] = 39.37; + m[ QgsUnitTypes::DistanceFeet ] = 12.0; + m[ QgsUnitTypes::DistanceYards ] = 36.0; + m[ QgsUnitTypes::DistanceMiles ] = 63360.0; + m[ QgsUnitTypes::DistanceDegrees ] = 4374754.0; + m[ QgsUnitTypes::DistanceKilometers ] = m[ QgsUnitTypes::DistanceMeters ] * 1000.0; + m[ QgsUnitTypes::DistanceNauticalMiles ] = m[ QgsUnitTypes::DistanceMeters ] * 1852.0; + m[ QgsUnitTypes::DistanceCentimeters ] = m[ QgsUnitTypes::DistanceMeters ] / 100.0; + m[ QgsUnitTypes::DistanceMillimeters ] = m[ QgsUnitTypes::DistanceMeters ] / 1000.0; + return m; + } + + QMap< QString, tileMatrixInfo> populateTileMatrixInfoMap() + { + QMap< QString, tileMatrixInfo> m; + + // Tile matrix information + // to build tile matrix set like Google Mercator or TMS + tileMatrixInfo tmi3857; + tmi3857.ref = QStringLiteral( "EPSG:3857" ); + tmi3857.extent = QgsRectangle( -20037508.3427892480, -20037508.3427892480, 20037508.3427892480, 20037508.3427892480 ); + tmi3857.scaleDenominator = 559082264.0287179; + tmi3857.unit = QgsUnitTypes::DistanceMeters; + m[tmi3857.ref] = tmi3857; + + + tileMatrixInfo tmi4326; + tmi4326.ref = QStringLiteral( "EPSG:4326" ); + tmi4326.extent = QgsRectangle( -180, -90, 180, 90 ); + tmi4326.scaleDenominator = 279541132.0143588675418869; + tmi4326.unit = QgsUnitTypes::DistanceDegrees; + m[tmi4326.ref] = tmi4326; + + return m; + } + + } + +} // namespace QgsWmts + + diff --git a/src/server/services/wmts/qgswmtsutils.h b/src/server/services/wmts/qgswmtsutils.h new file mode 100644 index 00000000000..146c03195d4 --- /dev/null +++ b/src/server/services/wmts/qgswmtsutils.h @@ -0,0 +1,145 @@ +/*************************************************************************** + qgswmtsutils.h + + Define WMTS service utility functions + ------------------------------------ + begin : July 23 , 2017 + copyright : (C) 2018 by René-Luc D'Hont + email : rldhont at 3liz dot 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 QGSWMTSUTILS_H +#define QGSWMTSUTILS_H + +#include "qgsmodule.h" +#include "qgswmtsparameters.h" +#include "qgswmtsserviceexception.h" + +#include + +/** + * \ingroup server + * WMTS implementation + * \since QGIS 3.4 + */ + +//! WMTS implementation +namespace QgsWmts +{ + + struct tileMatrixInfo + { + QString ref; + + QgsRectangle extent; + + double scaleDenominator = 0.0; + + QgsUnitTypes::DistanceUnit unit; + }; + + struct tileMatrixDef + { + double resolution = 0.0; + + double scaleDenominator = 0.0; + + int col = 0; + + int row = 0; + + double left = 0.0; + + double top = 0.0; + }; + + struct tileMatrixSetDef + { + QString ref; + + QgsRectangle extent; + + QgsUnitTypes::DistanceUnit unit; + + QList< tileMatrixDef > tileMatrixList; + }; + + struct tileMatrixLimitDef + { + int minCol; + + int maxCol; + + int minRow; + + int maxRow; + }; + + struct tileMatrixSetLinkDef + { + QString ref; + + QMap< int, tileMatrixLimitDef > tileMatrixLimits; + }; + + struct layerDef + { + QString id; + + QString title; + + QString abstract; + + QgsRectangle wgs84BoundingRect; + + QStringList formats; + + bool queryable = false; + + double maxScale = 0.0; + + double minScale = 0.0; + }; + + /** + * Returns the highest version supported by this implementation + */ + QString implementationVersion(); + + /** + * Service URL string + */ + QString serviceUrl( const QgsServerRequest &request, const QgsProject *project ); + + // Define namespaces used in WMTS documents + const QString WMTS_NAMESPACE = QStringLiteral( "http://www.opengis.net/wmts/1.0" ); + const QString GML_NAMESPACE = QStringLiteral( "http://www.opengis.net/gml" ); + const QString OWS_NAMESPACE = QStringLiteral( "http://www.opengis.net/ows/1.1" ); + + tileMatrixInfo getTileMatrixInfo( const QString &crsStr, const QgsProject *project ); + tileMatrixSetDef getTileMatrixSet( tileMatrixInfo tmi, double minScale ); + double getProjectMinScale( const QgsProject *project ); + QList< tileMatrixSetDef > getTileMatrixSetList( const QgsProject *project ); + + QList< layerDef > getWmtsLayerList( QgsServerInterface *serverIface, const QgsProject *project ); + tileMatrixSetLinkDef getLayerTileMatrixSetLink( const layerDef layer, const tileMatrixSetDef tms, const QgsProject *project ); + + /** + * Translate WMTS parameters to WMS query item + */ + QUrlQuery translateWmtsParamToWmsQueryItem( const QString &request, const QgsWmtsParameters ¶ms, + const QgsProject *project, QgsServerInterface *serverIface ); + +} // namespace QgsWmts + +#endif + + diff --git a/src/ui/qgsprojectpropertiesbase.ui b/src/ui/qgsprojectpropertiesbase.ui index 30b936c4b16..6be811a1e92 100644 --- a/src/ui/qgsprojectpropertiesbase.ui +++ b/src/ui/qgsprojectpropertiesbase.ui @@ -2467,6 +2467,89 @@ + + + + + 0 + 3 + + + + WMTS capabilities + + + + + + + 0 + 0 + + + + + Layer + + + + + Published + + + + + PNG + + + + + JPEG + + + + + + + + + + Minimum scale + + + + + + + 1 + + + 1000000000 + + + 5000 + + + + + + + + + + + Advertised URL + + + + + + + + + + + @@ -2916,6 +2999,9 @@ mMaxWidthLineEdit mMaxHeightLineEdit mWMSImageQualitySpinBox + twWmtsLayers + mWMTSMinScaleLineEdit + mWMTSUrlLineEdit twWFSLayers pbnWFSLayersSelectAll pbnWFSLayersDeselectAll diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index b1dddabad50..f26cf487d58 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -269,6 +269,8 @@ IF (WITH_SERVER) ADD_PYTHON_TEST(PyQgsServerAccessControlWFS test_qgsserver_accesscontrol_wfs.py) ADD_PYTHON_TEST(PyQgsServerAccessControlWCS test_qgsserver_accesscontrol_wcs.py) ADD_PYTHON_TEST(PyQgsServerAccessControlWFSTransactional test_qgsserver_accesscontrol_wfs_transactional.py) + ADD_PYTHON_TEST(PyQgsServerCacheManager test_qgsserver_cachemanager.py) + ADD_PYTHON_TEST(PyQgsServerWMTS test_qgsserver_wmts.py) ADD_PYTHON_TEST(PyQgsServerWFS test_qgsserver_wfs.py) ADD_PYTHON_TEST(PyQgsServerWFST test_qgsserver_wfst.py) ADD_PYTHON_TEST(PyQgsOfflineEditingWFS test_offline_editing_wfs.py) diff --git a/tests/src/python/test_qgsserver_cachemanager.py b/tests/src/python/test_qgsserver_cachemanager.py new file mode 100644 index 00000000000..478bee8da84 --- /dev/null +++ b/tests/src/python/test_qgsserver_cachemanager.py @@ -0,0 +1,420 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsServer. + +.. note:: 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. +""" +__author__ = 'René-Luc DHONT' +__date__ = '19/07/2018' +__copyright__ = 'Copyright 2015, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +print('CTEST_FULL_OUTPUT') + +import qgis # NOQA + +import os +import urllib.request +import urllib.parse +import urllib.error +import tempfile +import hashlib + +from qgis.testing import unittest +from utilities import unitTestDataPath +from qgis.server import QgsServer, QgsServerCacheFilter, QgsServerRequest, QgsBufferServerRequest, QgsBufferServerResponse +from qgis.core import QgsApplication, QgsFontUtils, QgsProject +from qgis.PyQt.QtCore import QIODevice, QFile, QByteArray, QBuffer +from qgis.PyQt.QtGui import QImage +from qgis.PyQt.QtXml import QDomDocument + + +class PyServerCache(QgsServerCacheFilter): + + """ Used to have restriction access """ + + # Be able to deactivate the access control to have a reference point + _active = False + + def __init__(self, server_iface): + super(QgsServerCacheFilter, self).__init__(server_iface) + + self._cache_dir = os.path.join(tempfile.gettempdir(), "qgs_server_cache") + if not os.path.exists(self._cache_dir): + os.mkdir(self._cache_dir) + + self._tile_cache_dir = os.path.join(self._cache_dir, 'tiles') + if not os.path.exists(self._tile_cache_dir): + os.mkdir(self._tile_cache_dir) + + def getCachedDocument(self, project, request, key): + m = hashlib.md5() + paramMap = request.parameters() + urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) + m.update(urlParam.encode('utf8')) + + if not os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")): + return QByteArray() + + doc = QDomDocument(m.hexdigest() + ".xml") + with open(os.path.join(self._cache_dir, m.hexdigest() + ".xml"), "r") as f: + statusOK, errorStr, errorLine, errorColumn = doc.setContent(f.read(), True) + if not statusOK: + print("Could not read or find the contents document. Error at line %d, column %d:\n%s" % (errorLine, errorColumn, errorStr)) + return QByteArray() + + return doc.toByteArray() + + def setCachedDocument(self, doc, project, request, key): + if not doc: + print("Could not cache None document") + return False + m = hashlib.md5() + paramMap = request.parameters() + urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) + m.update(urlParam.encode('utf8')) + with open(os.path.join(self._cache_dir, m.hexdigest() + ".xml"), "w") as f: + f.write(doc.toString()) + return os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")) + + def deleteCachedDocument(self, project, request, key): + m = hashlib.md5() + paramMap = request.parameters() + urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) + m.update(urlParam.encode('utf8')) + if os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")): + os.remove(os.path.join(self._cache_dir, m.hexdigest() + ".xml")) + return not os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")) + + def deleteCachedDocuments(self, project): + filelist = [f for f in os.listdir(self._cache_dir) if f.endswith(".xml")] + for f in filelist: + os.remove(os.path.join(self._cache_dir, f)) + filelist = [f for f in os.listdir(self._cache_dir) if f.endswith(".xml")] + return len(filelist) == 0 + + def getCachedImage(self, project, request, key): + m = hashlib.md5() + paramMap = request.parameters() + urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) + m.update(urlParam.encode('utf8')) + + if not os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")): + return QByteArray() + + img = QImage(m.hexdigest() + ".png") + with open(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png"), "rb") as f: + statusOK = img.loadFromData(f.read()) + if not statusOK: + print("Could not read or find the contents document. Error at line %d, column %d:\n%s" % (errorLine, errorColumn, errorStr)) + return QByteArray() + + ba = QByteArray() + buff = QBuffer(ba) + buff.open(QIODevice.WriteOnly) + img.save(buff, 'PNG') + return ba + + def setCachedImage(self, img, project, request, key): + m = hashlib.md5() + paramMap = request.parameters() + urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) + m.update(urlParam.encode('utf8')) + with open(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png"), "wb") as f: + f.write(img) + return os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")) + + def deleteCachedImage(self, project, request, key): + m = hashlib.md5() + paramMap = request.parameters() + urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) + m.update(urlParam.encode('utf8')) + if os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")): + os.remove(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")) + return not os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")) + + def deleteCachedImages(self, project): + filelist = [f for f in os.listdir(self._tile_cache_dir) if f.endswith(".png")] + for f in filelist: + os.remove(os.path.join(self._tile_cache_dir, f)) + filelist = [f for f in os.listdir(self._tile_cache_dir) if f.endswith(".png")] + return len(filelist) == 0 + + +class TestQgsServerCacheManager(unittest.TestCase): + + @classmethod + def _handle_request(cls, qs, requestMethod=QgsServerRequest.GetMethod, data=None): + if data is not None: + data = data.encode('utf-8') + request = QgsBufferServerRequest(qs, requestMethod, {}, data) + response = QgsBufferServerResponse() + cls._server.handleRequest(request, response) + headers = [] + rh = response.headers() + rk = sorted(rh.keys()) + for k in rk: + headers.append(("%s: %s" % (k, rh[k])).encode('utf-8')) + return b"\n".join(headers) + b"\n\n", bytes(response.body()) + + @classmethod + def setUpClass(cls): + """Run before all tests""" + cls._app = QgsApplication([], False) + cls._server = QgsServer() + cls._handle_request("") + cls._server_iface = cls._server.serverInterface() + cls._servercache = PyServerCache(cls._server_iface) + cls._server_iface.registerServerCache(cls._servercache, 100) + + @classmethod + def tearDownClass(cls): + """Run after all tests""" + #cls._servercache.deleteCachedDocuments(None) + del cls._server + cls._app.exitQgis + + def _result(self, data): + headers = {} + for line in data[0].decode('UTF-8').split("\n"): + if line != "": + header = line.split(":") + self.assertEqual(len(header), 2, line) + headers[str(header[0])] = str(header[1]).strip() + + return data[1], headers + + def _execute_request(self, qs, requestMethod=QgsServerRequest.GetMethod, data=None): + request = QgsBufferServerRequest(qs, requestMethod, {}, data) + response = QgsBufferServerResponse() + self._server.handleRequest(request, response) + headers = [] + rh = response.headers() + rk = sorted(rh.keys()) + for k in rk: + headers.append(("%s: %s" % (k, rh[k])).encode('utf-8')) + return b"\n".join(headers) + b"\n\n", bytes(response.body()) + + def setUp(self): + """Create the server instance""" + self.fontFamily = QgsFontUtils.standardTestFontFamily() + QgsFontUtils.loadStandardTestFonts(['All']) + + d = unitTestDataPath('qgis_server_accesscontrol') + '/' + self._project_path = os.path.join(d, "project.qgs") + + def test_getcapabilities(self): + project = self._project_path + assert os.path.exists(project), "Project file not found: " + project + + # without cache + query_string = '?MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.parse.quote(project), 'GetCapabilities') + header, body = self._execute_request(query_string) + doc = QDomDocument("wms_getcapabilities_130.xml") + doc.setContent(body) + # with cache + header, body = self._execute_request(query_string) + + # without cache + query_string = '?MAP=%s&SERVICE=WMS&VERSION=1.1.1&REQUEST=%s' % (urllib.parse.quote(project), 'GetCapabilities') + header, body = self._execute_request(query_string) + # with cache + header, body = self._execute_request(query_string) + + # without cache + query_string = '?MAP=%s&SERVICE=WFS&VERSION=1.1.0&REQUEST=%s' % (urllib.parse.quote(project), 'GetCapabilities') + header, body = self._execute_request(query_string) + # with cache + header, body = self._execute_request(query_string) + + # without cache + query_string = '?MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), 'GetCapabilities') + header, body = self._execute_request(query_string) + # with cache + header, body = self._execute_request(query_string) + + # without cache + query_string = '?MAP=%s&SERVICE=WCS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), 'GetCapabilities') + header, body = self._execute_request(query_string) + # with cache + header, body = self._execute_request(query_string) + + # without cache + query_string = '?MAP=%s&SERVICE=WMTS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), 'GetCapabilities') + header, body = self._execute_request(query_string) + # with cache + header, body = self._execute_request(query_string) + + filelist = [f for f in os.listdir(self._servercache._cache_dir) if f.endswith(".xml")] + self.assertEqual(len(filelist), 6, 'Not enough file in cache') + + cacheManager = self._server_iface.cacheManager() + + self.assertTrue(cacheManager.deleteCachedDocuments(None), 'deleteCachedDocuments does not return True') + + filelist = [f for f in os.listdir(self._servercache._cache_dir) if f.endswith(".xml")] + self.assertEqual(len(filelist), 0, 'All files in cache are not deleted ') + + prj = QgsProject() + prj.read(project) + + query_string = '?MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.parse.quote(project), 'GetCapabilities') + request = QgsBufferServerRequest(query_string, QgsServerRequest.GetMethod, {}, None) + + accessControls = self._server_iface.accessControls() + + cDoc = QDomDocument("wms_getcapabilities_130.xml") + self.assertFalse(cacheManager.getCachedDocument(cDoc, prj, request, accessControls), 'getCachedDocument is not None') + + self.assertTrue(cacheManager.setCachedDocument(doc, prj, request, accessControls), 'setCachedDocument false') + + self.assertTrue(cacheManager.getCachedDocument(cDoc, prj, request, accessControls), 'getCachedDocument is None') + self.assertEqual(doc.documentElement().tagName(), cDoc.documentElement().tagName(), 'cachedDocument not equal to provide document') + + self.assertTrue(cacheManager.deleteCachedDocuments(None), 'deleteCachedDocuments does not return True') + + def test_gettile(self): + project = self._project_path + assert os.path.exists(project), "Project file not found: " + project + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(project), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Country", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + # without cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + # with cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(project), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Country", + "STYLE": "", + "TILEMATRIXSET": "EPSG:4326", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + # without cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + # with cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(project), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "QGIS Server Hello World", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + # without cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + # with cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(project), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "QGIS Server Hello World", + "STYLE": "", + "TILEMATRIXSET": "EPSG:4326", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + # without cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + # with cache + r, h = self._result(self._execute_request(qs)) + self.assertEqual( + h.get("Content-Type"), "image/png", + "Content type is wrong: %s\n%s" % (h.get("Content-Type"), r)) + + filelist = [f for f in os.listdir(self._servercache._tile_cache_dir) if f.endswith(".png")] + self.assertEqual(len(filelist), 4, 'Not enough image in cache') + + cacheManager = self._server_iface.cacheManager() + + self.assertTrue(cacheManager.deleteCachedImages(None), 'deleteCachedImages does not return True') + + filelist = [f for f in os.listdir(self._servercache._tile_cache_dir) if f.endswith(".png")] + self.assertEqual(len(filelist), 0, 'All images in cache are not deleted ') + + def test_gettile_invalid_parameters(self): + project = self._project_path + assert os.path.exists(project), "Project file not found: " + project + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(project), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Country", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "FOO", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + err = b"TILECOL (\'FOO\') cannot be converted into int" in r + self.assertTrue(err) + + filelist = [f for f in os.listdir(self._servercache._tile_cache_dir) if f.endswith(".png")] + self.assertEqual(len(filelist), 0, 'Exception has been cached ') + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/src/python/test_qgsserver_wms.py b/tests/src/python/test_qgsserver_wms.py index 0360039a756..0681eaf3f6a 100644 --- a/tests/src/python/test_qgsserver_wms.py +++ b/tests/src/python/test_qgsserver_wms.py @@ -123,7 +123,9 @@ class TestQgsServerWMS(TestQgsServerWMSTestBase): self.assertXMLEqual(response, expected, msg="request %s failed.\nQuery: %s\nExpected file: %s\nResponse:\n%s" % (query_string, request, reference_path, response.decode('utf-8'))) def test_wms_getcapabilities_project(self): + """WMS GetCapabilities without map parameter""" self.wms_request_compare_project('GetCapabilities') + # reference_file='getcapabilities_without_map_param' could be the right response def wms_inspire_request_compare(self, request): """WMS INSPIRE tests""" diff --git a/tests/src/python/test_qgsserver_wmts.py b/tests/src/python/test_qgsserver_wmts.py new file mode 100644 index 00000000000..648960809b6 --- /dev/null +++ b/tests/src/python/test_qgsserver_wmts.py @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsServer WFS. + +From build dir, run: ctest -R PyQgsServerWFS -V + + +.. note:: 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. + +""" +__author__ = 'René-Luc Dhont' +__date__ = '19/09/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import os + +# Needed on Qt 5 so that the serialization of XML is consistent among all executions +os.environ['QT_HASH_SEED'] = '1' + +import re +import urllib.request +import urllib.parse +import urllib.error + +from qgis.server import QgsServerRequest + +from qgis.testing import unittest +from qgis.PyQt.QtCore import QSize + +import osgeo.gdal # NOQA + +from test_qgsserver import QgsServerTestBase + +# Strip path and content length because path may vary +RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \d+|timeStamp="[^"]+"' +RE_ATTRIBUTES = b'[^>\s]+=[^>\s]+' + + +class TestQgsServerWMTS(QgsServerTestBase): + + """QGIS Server WMTS Tests""" + + def wmts_request_compare(self, request, version='', extra_query_string='', reference_base_name=None): + #project = self.testdata_path + "test_project_wfs.qgs" + project = self.projectGroupsPath + assert os.path.exists(project), "Project file not found: " + project + + query_string = '?MAP=%s&SERVICE=WMTS&REQUEST=%s' % (urllib.parse.quote(project), request) + if version: + query_string += '&VERSION=%s' % version + + if extra_query_string: + query_string += '&%s' % extra_query_string + + header, body = self._execute_request(query_string) + self.assert_headers(header, body) + response = header + body + + if reference_base_name is not None: + reference_name = reference_base_name + else: + reference_name = 'wmts_' + request.lower() + + reference_name += '.txt' + + reference_path = self.testdata_path + reference_name + + self.store_reference(reference_path, response) + f = open(reference_path, 'rb') + expected = f.read() + f.close() + response = re.sub(RE_STRIP_UNCHECKABLE, b'', response) + expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected) + + self.assertXMLEqual(response, expected, msg="request %s failed.\n Query: %s" % (query_string, request)) + + def test_project_wmts(self): + """Test some WMTS request""" + for request in ('GetCapabilities',): + self.wmts_request_compare(request) + #self.wmts_request_compare(request, '1.0.0') + + def test_wmts_gettile(self): + # Testing project WMTS layer + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "QGIS Server Hello World", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._img_diff_error(r, h, "WMTS_GetTile_Project_3857_0", 20000) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "QGIS Server Hello World", + "STYLE": "", + "TILEMATRIXSET": "EPSG:4326", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._img_diff_error(r, h, "WMTS_GetTile_Project_4326_0", 20000) + + # Testing group WMTS layer + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "CountryGroup", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._img_diff_error(r, h, "WMTS_GetTile_CountryGroup_3857_0", 20000) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "CountryGroup", + "STYLE": "", + "TILEMATRIXSET": "EPSG:4326", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._img_diff_error(r, h, "WMTS_GetTile_CountryGroup_4326_0", 20000) + + # Testing QgsMapLayer WMTS layer + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Hello", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._img_diff_error(r, h, "WMTS_GetTile_Hello_3857_0", 20000) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Hello", + "STYLE": "", + "TILEMATRIXSET": "EPSG:4326", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + self._img_diff_error(r, h, "WMTS_GetTile_Hello_4326_0", 20000) + + def test_wmts_gettile_invalid_parameters(self): + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Hello", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "FOO", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + err = b"TILECOL (\'FOO\') cannot be converted into int" in r + self.assertTrue(err) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Hello", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "1", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + err = b"TileCol is unknown" in r + self.assertTrue(err) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Hello", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "-1", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + err = b"TileCol is unknown" in r + self.assertTrue(err) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "dem", + "STYLE": "", + "TILEMATRIXSET": "EPSG:3857", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + err = b"Layer \'dem\' not found" in r + self.assertTrue(err) + + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(self.projectGroupsPath), + "SERVICE": "WMTS", + "VERSION": "1.0.0", + "REQUEST": "GetTile", + "LAYER": "Hello", + "STYLE": "", + "TILEMATRIXSET": "EPSG:2154", + "TILEMATRIX": "0", + "TILEROW": "0", + "TILECOL": "0", + "FORMAT": "image/png" + }.items())]) + + r, h = self._result(self._execute_request(qs)) + err = b"TileMatrixSet is unknown" in r + self.assertTrue(err) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testdata/control_images/qgis_server/WMS_GetMap_ContextRendering/WMS_GetMap_ContextRendering_mask.png b/tests/testdata/control_images/qgis_server/WMS_GetMap_ContextRendering/WMS_GetMap_ContextRendering_mask.png new file mode 100644 index 00000000000..53f44ed121b Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMS_GetMap_ContextRendering/WMS_GetMap_ContextRendering_mask.png differ diff --git a/tests/testdata/control_images/qgis_server/WMS_GetMap_LabelingSettings/WMS_GetMap_LabelingSettings_mask.png b/tests/testdata/control_images/qgis_server/WMS_GetMap_LabelingSettings/WMS_GetMap_LabelingSettings_mask.png new file mode 100644 index 00000000000..93141ecf02f Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMS_GetMap_LabelingSettings/WMS_GetMap_LabelingSettings_mask.png differ diff --git a/tests/testdata/control_images/qgis_server/WMS_GetPrint_Highlight/WMS_GetPrint_Highlight_mask.png b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Highlight/WMS_GetPrint_Highlight_mask.png index e783f2d8200..925b2516b7f 100644 Binary files a/tests/testdata/control_images/qgis_server/WMS_GetPrint_Highlight/WMS_GetPrint_Highlight_mask.png and b/tests/testdata/control_images/qgis_server/WMS_GetPrint_Highlight/WMS_GetPrint_Highlight_mask.png differ diff --git a/tests/testdata/control_images/qgis_server/WMTS_GetTile_CountryGroup_3857_0/WMTS_GetTile_CountryGroup_3857_0.png b/tests/testdata/control_images/qgis_server/WMTS_GetTile_CountryGroup_3857_0/WMTS_GetTile_CountryGroup_3857_0.png new file mode 100644 index 00000000000..276c41e0400 Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMTS_GetTile_CountryGroup_3857_0/WMTS_GetTile_CountryGroup_3857_0.png differ diff --git a/tests/testdata/control_images/qgis_server/WMTS_GetTile_CountryGroup_4326_0/WMTS_GetTile_CountryGroup_4326_0.png b/tests/testdata/control_images/qgis_server/WMTS_GetTile_CountryGroup_4326_0/WMTS_GetTile_CountryGroup_4326_0.png new file mode 100644 index 00000000000..8c6486b88f6 Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMTS_GetTile_CountryGroup_4326_0/WMTS_GetTile_CountryGroup_4326_0.png differ diff --git a/tests/testdata/control_images/qgis_server/WMTS_GetTile_Hello_3857_0/WMTS_GetTile_Hello_3857_0.png b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Hello_3857_0/WMTS_GetTile_Hello_3857_0.png new file mode 100644 index 00000000000..b9a737f0537 Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Hello_3857_0/WMTS_GetTile_Hello_3857_0.png differ diff --git a/tests/testdata/control_images/qgis_server/WMTS_GetTile_Hello_4326_0/WMTS_GetTile_Hello_4326_0.png b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Hello_4326_0/WMTS_GetTile_Hello_4326_0.png new file mode 100644 index 00000000000..2b54a1bf8bc Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Hello_4326_0/WMTS_GetTile_Hello_4326_0.png differ diff --git a/tests/testdata/control_images/qgis_server/WMTS_GetTile_Project_3857_0/WMTS_GetTile_Project_3857_0.png b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Project_3857_0/WMTS_GetTile_Project_3857_0.png new file mode 100644 index 00000000000..eae982b9ece Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Project_3857_0/WMTS_GetTile_Project_3857_0.png differ diff --git a/tests/testdata/control_images/qgis_server/WMTS_GetTile_Project_4326_0/WMTS_GetTile_Project_4326_0.png b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Project_4326_0/WMTS_GetTile_Project_4326_0.png new file mode 100644 index 00000000000..843453c5c13 Binary files /dev/null and b/tests/testdata/control_images/qgis_server/WMTS_GetTile_Project_4326_0/WMTS_GetTile_Project_4326_0.png differ diff --git a/tests/testdata/qgis_server/getcapabilities_without_map_param.txt b/tests/testdata/qgis_server/getcapabilities_without_map_param.txt new file mode 100644 index 00000000000..205683d1a6a --- /dev/null +++ b/tests/testdata/qgis_server/getcapabilities_without_map_param.txt @@ -0,0 +1,246 @@ +Content-Length: 6575 +Content-Type: text/xml; charset=utf-8 + + + + + WMS + QGIS TestProject + + + infoMapAccessService + + + + + Alessandro Pasotti + QGIS dev team + + elpaso@itopen.it + + conditions unknown + None + + + + + text/xml + + + + + + + + + + image/jpeg + image/png + image/png; mode=16bit + image/png; mode=8bit + image/png; mode=1bit + application/dxf + + + + + + + + + + text/plain + text/html + text/xml + application/vnd.ogc.gml + application/vnd.ogc.gml/3.1.1 + + + + + + + + + + image/jpeg + image/png + + + + + + + + + + text/xml + + + + + + + + + + text/xml + + + + + + + + + + + XML + + + + QGIS Test Project + QGIS Test Project + CRS:84 + EPSG:4326 + EPSG:3857 + + 8.20315 + 8.20416 + 44.9012 + 44.9016 + + + + QGIS Test Project + + infoMapAccessService + + + layer_with_short_name + A Layer with a short name + A Layer with an abstract + CRS:84 + EPSG:4326 + EPSG:3857 + + 8.20346 + 8.20355 + 44.9014 + 44.9015 + + + + + + + testlayer èé + A test vector layer + A test vector layer with unicode òà + CRS:84 + EPSG:4326 + EPSG:3857 + + 8.20346 + 8.20355 + 44.9014 + 44.9015 + + + + + + + group_name + Group title + Group abstract + CRS:84 + EPSG:4326 + EPSG:3857 + + 8.20346 + 8.20355 + 44.9014 + 44.9015 + + + + + testlayer2 + testlayer2 + CRS:84 + EPSG:4326 + EPSG:3857 + + 8.20346 + 8.20355 + 44.9014 + 44.9015 + + + + + + + + groupwithoutshortname + groupwithoutshortname + CRS:84 + EPSG:4326 + EPSG:3857 + + 8.20346 + 8.20355 + 44.9014 + 44.9015 + + + + + testlayer3 + testlayer3 + CRS:84 + EPSG:4326 + EPSG:3857 + + 8.20346 + 8.20355 + 44.9014 + 44.9015 + + + + + + + + + diff --git a/tests/testdata/qgis_server/wmts_getcapabilities.txt b/tests/testdata/qgis_server/wmts_getcapabilities.txt new file mode 100644 index 00000000000..90c1e18898c --- /dev/null +++ b/tests/testdata/qgis_server/wmts_getcapabilities.txt @@ -0,0 +1,869 @@ + +Content-Type: text/xml; charset=utf-8 + + + + OGC WMTS + 1.0.0 + QGIS Server test + + conditions unknown + None + + + QGIS + + Stéphane Brunner + + + + + + + + + + + + + + + + + + + + + + + + + + + + QGIS Server Hello World + QGIS Server Hello World + QGIS Server Hello World + + -174.766573 -69.957838 + 177.930819 84.307876 + + + -19454925.898459 -11055006.822989 + 19807168.136881 19143772.793601 + + + image/png + + EPSG:3857 + + + 0 + 0 + 0 + 0 + 0 + + + 1 + 0 + 1 + 0 + 1 + + + 2 + 0 + 3 + 0 + 3 + + + 3 + 0 + 7 + 0 + 6 + + + 4 + 0 + 15 + 0 + 12 + + + 5 + 0 + 31 + 0 + 24 + + + 6 + 0 + 63 + 1 + 49 + + + 7 + 1 + 127 + 2 + 99 + + + 8 + 3 + 254 + 5 + 198 + + + 9 + 7 + 509 + 11 + 397 + + + 10 + 14 + 1018 + 22 + 794 + + + 11 + 29 + 2036 + 45 + 1588 + + + 12 + 59 + 4072 + 91 + 3177 + + + 13 + 119 + 8144 + 182 + 6355 + + + 14 + 238 + 16289 + 365 + 12711 + + + 15 + 476 + 32579 + 730 + 25423 + + + 16 + 952 + 65159 + 1461 + 50846 + + + 17 + 1905 + 130318 + 2923 + 101693 + + + 18 + 3810 + 260637 + 5846 + 203386 + + + 19 + 7621 + 521274 + 11692 + 406772 + + + 20 + 15243 + 1042549 + 23384 + 813545 + + + + + EPSG:4326 + + + 0 + 0 + 1 + 0 + 0 + + + 1 + 0 + 3 + 0 + 1 + + + 2 + 0 + 7 + 0 + 3 + + + 3 + 0 + 15 + 0 + 7 + + + 4 + 0 + 31 + 0 + 14 + + + 5 + 0 + 63 + 1 + 28 + + + 6 + 1 + 127 + 2 + 56 + + + 7 + 3 + 254 + 4 + 113 + + + 8 + 7 + 508 + 8 + 227 + + + 9 + 14 + 1016 + 16 + 454 + + + 10 + 29 + 2032 + 32 + 908 + + + 11 + 59 + 4065 + 64 + 1816 + + + 12 + 118 + 8130 + 129 + 3633 + + + 13 + 237 + 16260 + 258 + 7266 + + + 14 + 475 + 32520 + 517 + 14533 + + + 15 + 951 + 65041 + 1034 + 29066 + + + 16 + 1902 + 130083 + 2068 + 58133 + + + 17 + 3804 + 260167 + 4137 + 116267 + + + 18 + 7608 + 520335 + 8274 + 232535 + + + 19 + 15216 + 1040671 + 16549 + 465071 + + + + + + CountryGroup + CountryGroup + + -176.248495 -67.592996 + 179.412741 83.621086 + + + -19619892.68012 -10327100.342322 + 19972134.918542 18415866.312934 + + + image/png + text/plain + text/html + text/xml + application/vnd.ogc.gml + application/vnd.ogc.gml/3.1.1 + + EPSG:3857 + + + 0 + 0 + 0 + 0 + 0 + + + 1 + 0 + 1 + 0 + 1 + + + 2 + 0 + 3 + 0 + 3 + + + + + EPSG:4326 + + + 0 + 0 + 1 + 0 + 0 + + + 1 + 0 + 3 + 0 + 1 + + + + + + Hello + + -132.467818 -1.006739 + 101.888717 69.520496 + + + -14746250.075131 -112075.428077 + 11342200.077197 10914413.714128 + + + image/png + text/plain + text/html + text/xml + application/vnd.ogc.gml + application/vnd.ogc.gml/3.1.1 + + EPSG:3857 + + + 0 + 0 + 0 + 0 + 0 + + + 1 + 0 + 1 + 0 + 1 + + + 2 + 0 + 3 + 0 + 2 + + + + + EPSG:4326 + + + 0 + 0 + 1 + 0 + 0 + + + 1 + 0 + 3 + 0 + 1 + + + + + + EPSG:3857 + EPSG:3857 + + 0 + 559082264.028718 + -20037508.342789 20037508.342789 + 256 + 256 + 1 + 1 + + + 1 + 279541132.014359 + -20037508.342789 20037508.342789 + 256 + 256 + 2 + 2 + + + 2 + 139770566.007179 + -20037508.342789 20037508.342789 + 256 + 256 + 4 + 4 + + + 3 + 69885283.00359 + -20037508.342789 20037508.342789 + 256 + 256 + 8 + 8 + + + 4 + 34942641.501795 + -20037508.342789 20037508.342789 + 256 + 256 + 16 + 16 + + + 5 + 17471320.750897 + -20037508.342789 20037508.342789 + 256 + 256 + 32 + 32 + + + 6 + 8735660.375449 + -20037508.342789 20037508.342789 + 256 + 256 + 64 + 64 + + + 7 + 4367830.187724 + -20037508.342789 20037508.342789 + 256 + 256 + 128 + 128 + + + 8 + 2183915.093862 + -20037508.342789 20037508.342789 + 256 + 256 + 256 + 256 + + + 9 + 1091957.546931 + -20037508.342789 20037508.342789 + 256 + 256 + 512 + 512 + + + 10 + 545978.773466 + -20037508.342789 20037508.342789 + 256 + 256 + 1024 + 1024 + + + 11 + 272989.386733 + -20037508.342789 20037508.342789 + 256 + 256 + 2048 + 2048 + + + 12 + 136494.693366 + -20037508.342789 20037508.342789 + 256 + 256 + 4096 + 4096 + + + 13 + 68247.346683 + -20037508.342789 20037508.342789 + 256 + 256 + 8192 + 8192 + + + 14 + 34123.673342 + -20037508.342789 20037508.342789 + 256 + 256 + 16384 + 16384 + + + 15 + 17061.836671 + -20037508.342789 20037508.342789 + 256 + 256 + 32768 + 32768 + + + 16 + 8530.918335 + -20037508.342789 20037508.342789 + 256 + 256 + 65536 + 65536 + + + 17 + 4265.459168 + -20037508.342789 20037508.342789 + 256 + 256 + 131072 + 131072 + + + 18 + 2132.729584 + -20037508.342789 20037508.342789 + 256 + 256 + 262144 + 262144 + + + 19 + 1066.364792 + -20037508.342789 20037508.342789 + 256 + 256 + 524288 + 524288 + + + 20 + 533.182396 + -20037508.342789 20037508.342789 + 256 + 256 + 1048576 + 1048576 + + + + EPSG:4326 + EPSG:4326 + + 0 + 279541132.014359 + -180 90 + 256 + 256 + 2 + 1 + + + 1 + 139770566.007179 + -180 90 + 256 + 256 + 4 + 2 + + + 2 + 69885283.00359 + -180 90 + 256 + 256 + 8 + 4 + + + 3 + 34942641.501795 + -180 90 + 256 + 256 + 16 + 8 + + + 4 + 17471320.750897 + -180 90 + 256 + 256 + 32 + 16 + + + 5 + 8735660.375449 + -180 90 + 256 + 256 + 64 + 32 + + + 6 + 4367830.187724 + -180 90 + 256 + 256 + 128 + 64 + + + 7 + 2183915.093862 + -180 90 + 256 + 256 + 256 + 128 + + + 8 + 1091957.546931 + -180 90 + 256 + 256 + 512 + 256 + + + 9 + 545978.773466 + -180 90 + 256 + 256 + 1023 + 512 + + + 10 + 272989.386733 + -180 90 + 256 + 256 + 2045 + 1023 + + + 11 + 136494.693366 + -180 90 + 256 + 256 + 4089 + 2045 + + + 12 + 68247.346683 + -180 90 + 256 + 256 + 8178 + 4089 + + + 13 + 34123.673342 + -180 90 + 256 + 256 + 16355 + 8178 + + + 14 + 17061.836671 + -180 90 + 256 + 256 + 32709 + 16355 + + + 15 + 8530.918335 + -180 90 + 256 + 256 + 65418 + 32709 + + + 16 + 4265.459168 + -180 90 + 256 + 256 + 130836 + 65418 + + + 17 + 2132.729584 + -180 90 + 256 + 256 + 261672 + 130836 + + + 18 + 1066.364792 + -180 90 + 256 + 256 + 523344 + 261672 + + + 19 + 533.182396 + -180 90 + 256 + 256 + 1046688 + 523344 + + + + diff --git a/tests/testdata/qgis_server_accesscontrol/project.qgs b/tests/testdata/qgis_server_accesscontrol/project.qgs index 90863eb46bd..2096c4afb26 100644 --- a/tests/testdata/qgis_server_accesscontrol/project.qgs +++ b/tests/testdata/qgis_server_accesscontrol/project.qgs @@ -1,16 +1,17 @@ - + + QGIS Server Hello World - - - + + + +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -18,31 +19,31 @@ - + - + - + - + - + - + - + - + - + @@ -57,20 +58,20 @@ country20170328164317226 - + - - - - - - - - + + + + + + + + - + meters 11863620.20301065221428871 @@ -85,7 +86,7 @@ 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -94,56 +95,55 @@ 0 - - - - + -19619892.68012013286352158 -10327100.34232237376272678 @@ -162,7 +162,7 @@ 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -175,6 +175,7 @@ + @@ -186,32 +187,32 @@ - false + true - spatialite + - + - - + + - - + + - + - - + + @@ -224,7 +225,7 @@ - + @@ -244,180 +245,180 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -425,12 +426,12 @@ 0 0 - - - + + + - - + + @@ -452,22 +453,22 @@ - + . - - + + - + - + - . @@ -494,7 +495,7 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - + @@ -510,46 +511,46 @@ def my_form_open(dialog, layer, feature): - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -560,26 +561,26 @@ def my_form_open(dialog, layer, feature): - - - - + + + + - - - + + + - + @@ -587,17 +588,17 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + @@ -618,31 +619,31 @@ def my_form_open(dialog, layer, feature): - - + + - - + + - - + + - - + + - + - + - ../../../../../.. @@ -669,8 +670,10 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - + + + @@ -680,7 +683,7 @@ def my_form_open(dialog, layer, feature): name - + -14746250.07513097859919071 -112075.42807669920148328 @@ -699,7 +702,7 @@ def my_form_open(dialog, layer, feature): 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -712,6 +715,7 @@ def my_form_open(dialog, layer, feature): + @@ -727,57 +731,57 @@ def my_form_open(dialog, layer, feature): - spatialite + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -787,24 +791,24 @@ def my_form_open(dialog, layer, feature): - - + + 0 0 1 - - - + + + - + @@ -832,36 +836,36 @@ def my_form_open(dialog, layer, feature): - - - + + + - - - + + + - - - + + + - - - + + + - + - + - ../../../../../.. @@ -888,15 +892,17 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - - + + + - - + + + + @@ -906,12 +912,12 @@ def my_form_open(dialog, layer, feature): "pkuid" - + - -14746250.07513097859919071 - -112075.42807669920148328 - 11342200.07719692215323448 - 10914413.7141284141689539 + -2465695.66895584994927049 + 80258.53580146089370828 + 5037064.00943838991224766 + 3762589.19456820981577039 Hello_SubsetString_copy20160222085231770 dbname='./helloworld.db' table="hello" (geom) sql="pkuid" in ( 7, 8 ) @@ -925,7 +931,7 @@ def my_form_open(dialog, layer, feature): 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -938,6 +944,7 @@ def my_form_open(dialog, layer, feature): + @@ -953,57 +960,57 @@ def my_form_open(dialog, layer, feature): - spatialite + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -1013,24 +1020,24 @@ def my_form_open(dialog, layer, feature): - - + + 0 0 1 - - - + + + - + @@ -1058,31 +1065,31 @@ def my_form_open(dialog, layer, feature): - - - + + + - - - + + + - - - + + + - - - + + + - + - + ../../../../../.. @@ -1109,15 +1116,17 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - - + + + - - + + + + @@ -1127,7 +1136,7 @@ def my_form_open(dialog, layer, feature): "pkuid" - + -14746250.07513097859919071 -112075.42807669920148328 @@ -1146,7 +1155,7 @@ def my_form_open(dialog, layer, feature): 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -1159,6 +1168,7 @@ def my_form_open(dialog, layer, feature): + @@ -1174,57 +1184,57 @@ def my_form_open(dialog, layer, feature): - spatialite + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -1234,24 +1244,24 @@ def my_form_open(dialog, layer, feature): - - + + 0 0 1 - - - + + + - + @@ -1279,31 +1289,31 @@ def my_form_open(dialog, layer, feature): - - - + + + - - - + + + - - - + + + - - - + + + - + - + ../../../../../.. @@ -1330,15 +1340,17 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - - + + + - - + + + + @@ -1348,7 +1360,7 @@ def my_form_open(dialog, layer, feature): "pkuid" - + -19619892.68012013286352158 -10327100.34232237376272678 @@ -1367,7 +1379,7 @@ def my_form_open(dialog, layer, feature): 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -1380,6 +1392,7 @@ def my_form_open(dialog, layer, feature): + @@ -1395,29 +1408,29 @@ def my_form_open(dialog, layer, feature): - spatialite + - + - - + + - - + + - + - - + + @@ -1452,147 +1465,147 @@ def my_form_open(dialog, layer, feature): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 0 @@ -1600,31 +1613,31 @@ def my_form_open(dialog, layer, feature): name - - - + + - + 0 tablayout @@ -1637,19 +1650,19 @@ def my_form_open(dialog, layer, feature): - + - - + + - - + + - + - - + + @@ -1670,7 +1683,7 @@ def my_form_open(dialog, layer, feature): - + @@ -1705,147 +1718,147 @@ def my_form_open(dialog, layer, feature): - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 0 @@ -1853,31 +1866,31 @@ def my_form_open(dialog, layer, feature): name - - - + + - + 0 tablayout @@ -1891,46 +1904,46 @@ def my_form_open(dialog, layer, feature): - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -1941,20 +1954,20 @@ def my_form_open(dialog, layer, feature): - - - - + + + + - - - + + + @@ -1964,17 +1977,17 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + @@ -1995,27 +2008,27 @@ def my_form_open(dialog, layer, feature): - - + + - - + + - - + + - - + + - + - + ../../../../../.. @@ -2026,8 +2039,10 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - + + + @@ -2037,7 +2052,7 @@ def my_form_open(dialog, layer, feature): "name" - + -19619892.68012013286352158 -10327100.34232237376272678 @@ -2056,7 +2071,7 @@ def my_form_open(dialog, layer, feature): 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -2069,6 +2084,7 @@ def my_form_open(dialog, layer, feature): + @@ -2084,37 +2100,37 @@ def my_form_open(dialog, layer, feature): - spatialite + - + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -2124,7 +2140,7 @@ def my_form_open(dialog, layer, feature): - + @@ -2132,18 +2148,18 @@ def my_form_open(dialog, layer, feature): 0 1 - - - - + + + + - + @@ -2164,31 +2180,31 @@ def my_form_open(dialog, layer, feature): - - + + - - + + - - + + - - + + - + - + - @@ -2214,6 +2230,8 @@ def my_form_open(dialog, layer, feature): ]]> 0 generatedlayout + + @@ -2223,7 +2241,7 @@ def my_form_open(dialog, layer, feature): name - + -29.99999999999666755 29.99999999999666755 @@ -2255,6 +2273,7 @@ def my_form_open(dialog, layer, feature): + @@ -2270,10 +2289,9 @@ def my_form_open(dialog, layer, feature): - - + gdal @@ -2283,7 +2301,7 @@ def my_form_open(dialog, layer, feature): - + None @@ -2300,12 +2318,12 @@ def my_form_open(dialog, layer, feature): - + 0 - + -14746250.07513097859919071 -112075.42807669920148328 @@ -2324,7 +2342,7 @@ def my_form_open(dialog, layer, feature): 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -2337,6 +2355,7 @@ def my_form_open(dialog, layer, feature): + @@ -2352,57 +2371,57 @@ def my_form_open(dialog, layer, feature): - spatialite + - + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -2416,17 +2435,17 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + @@ -2454,31 +2473,31 @@ def my_form_open(dialog, layer, feature): - - - + + + - - - + + + - - - + + + - - - + + + - + - + ../../../../../.. @@ -2489,15 +2508,17 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout - - - + + + - - + + + + @@ -2507,7 +2528,7 @@ def my_form_open(dialog, layer, feature): COALESCE( "pkuid", '<NULL>' ) - + 1000 2000 @@ -2526,7 +2547,7 @@ def my_form_open(dialog, layer, feature): 3857 3857 EPSG:3857 - WGS 84 / Pseudo Mercator + WGS 84 / Pseudo-Mercator merc WGS84 false @@ -2539,6 +2560,7 @@ def my_form_open(dialog, layer, feature): + @@ -2554,44 +2576,44 @@ def my_form_open(dialog, layer, feature): - spatialite + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -2605,17 +2627,17 @@ def my_form_open(dialog, layer, feature): 0 1 - - - + + + - + @@ -2643,31 +2665,31 @@ def my_form_open(dialog, layer, feature): - - - + + + - - - + + + - - - + + + - - - + + + - + - + ../../../../../.. @@ -2677,6 +2699,8 @@ def my_form_open(dialog, layer, feature): 0 generatedlayout + + @@ -2699,117 +2723,30 @@ def my_form_open(dialog, layer, feature): - - - country20131022151106556 - hello20131022151106574 - - - enabled - enabled - - advanced - - 40.000000 - 40.000000 - - - 1 - 1 - - to vertex - - 40 - 1 - - to_vertex - to_vertex - - - - + true 90 - - Hello_SubsetString_copy20160222085231770 - Hello_copy20150804164427541 - country20131022151106556 - hello20131022151106574 - points20150803121107046 - + + + false - - - - 2 - true - D - - true - - - - - false - QGIS - 5000 - - - - - EPSG:3857 - EPSG:4326 - - - WGS84 - meters m2 + + + 255 + true + + 1 + + + QGIS Server test - - true - 0 - true - false - 16 - 30 - 50 - false - false - - - false - - 1 - 1 - 1 - 1 - 0 - - - false - true - - - - - - - - - dem20150730091219559 - - None - 5000 - - false - - + QGIS Stéphane Brunner - conditions unknown points20150803121107046 @@ -2821,91 +2758,220 @@ def my_form_open(dialog, layer, feature): points20150803121107046 + + 5000 + + + + Simple test app. + + true + false + + false + + false + + + + + + 2000 + days + + 0 + 1 + 1 + + 0 + 0 + 1 + + + + + + + + country20131022151106556 + + true + + + 0 + 1 + 1 + 1 + 1 + + + None + 5 + + + + + WGS84 + + conditions unknown + + true + 2 + D + + + + + 40 + + country20131022151106556 + hello20131022151106574 + + 1 + advanced + + 40.000000 + 40.000000 + + + enabled + enabled + + to vertex + + + 1 + 1 + + + to_vertex + to_vertex + + + false + + EPSG:3857 + EPSG:4326 + - EPSG:3857 + +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs 1 - +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs + EPSG:3857 3857 + + + + country20131022151106556 + + true + + + + true + false + 50 + 0 + 30 + false + 16 + false + true + + + dem20150730091219559 + + -20609693.37008669599890709 -11055006.82298868149518967 20961935.60850896313786507 19143772.79360072687268257 - Simple test app. - - 5 - - 1 - 2000 - 1 - 0 - 0 - - 0 - days - - 1 - - 128 255 - 255 - 246 - 108 - 255 + 128 255 + 255 + 255 + 108 + 246 - - - - 1 - - 255 - true - - + + 5000 + + + + + false + + + Hello_SubsetString_copy20160222085231770 + Hello_copy20150804164427541 + country20131022151106556 + hello20131022151106574 + points20150803121107046 + + + + + + + QGIS Server Hello World + + + + + + + + + + + + + 2000-01-01T00:00:00 + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + @@ -2913,181 +2979,181 @@ def my_form_open(dialog, layer, feature): - - - + + + - - + + - - - + + + - + - points20150803121107046 - hello20131022151106574 - Hello_copy20150804164427541 - Hello_SubsetString_copy20160222085231770 - Hello_Project_SubsetString_copy20160223113949592 - dem20150730091219559 - country20131022151106556 - Country_copy20161127151800736 - country20170328164317226 + points20150803121107046 + hello20131022151106574 + Hello_copy20150804164427541 + Hello_SubsetString_copy20160222085231770 + Hello_Project_SubsetString_copy20160223113949592 + dem20150730091219559 + country20131022151106556 + Country_copy20161127151800736 + country20170328164317226 - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - + - - - + + + - - + + - - - + + + - - + + - + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + @@ -3095,212 +3161,212 @@ def my_form_open(dialog, layer, feature): - - - + + + - - - - + + + - - + + - - - + + + - + - points20150803121107046 - hello20131022151106574 - Hello_copy20150804164427541 - Hello_SubsetString_copy20160222085231770 - Hello_Project_SubsetString_copy20160223113949592 - dem20150730091219559 - country20131022151106556 - Country_copy20161127151800736 - country20170328164317226 + points20150803121107046 + hello20131022151106574 + Hello_copy20150804164427541 + Hello_SubsetString_copy20160222085231770 + Hello_Project_SubsetString_copy20160223113949592 + dem20150730091219559 + country20131022151106556 + Country_copy20161127151800736 + country20170328164317226 - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + - + - - - + + + - - + + - - - + + + - - + + - + - - - + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + @@ -3308,44 +3374,44 @@ def my_form_open(dialog, layer, feature): - - - + + + - + - + - - - + + + - + - + - + - + diff --git a/tests/testdata/qgis_server_accesscontrol/project_groups.qgs b/tests/testdata/qgis_server_accesscontrol/project_groups.qgs index bd1f6ee625d..00a255d0d18 100644 --- a/tests/testdata/qgis_server_accesscontrol/project_groups.qgs +++ b/tests/testdata/qgis_server_accesscontrol/project_groups.qgs @@ -1,5 +1,5 @@ - + QGIS Server Hello World @@ -4362,6 +4362,29 @@ def my_form_open(dialog, layer, feature): 1 +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs + + true + + CountryGroup + + + hello20131022151106574 + + + + true + + CountryGroup + + + hello20131022151106574 + + + + false + + +