Merge pull request #7514 from rldhont/server-cache-manager

[Server][Feature][needs-docs] Server cache manager and WMTS service
This commit is contained in:
rldhont 2018-08-28 18:05:42 +02:00 committed by GitHub
commit 65f4bf1956
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 7516 additions and 1703 deletions

View File

@ -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<int, QgsServerCacheFilter *> QgsServerCacheFilterMap;
/************************************************************************
* This file has been generated automatically from *
* *
* src/server/qgsservercachefilter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -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 &copy );
%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 *
************************************************************************/

View File

@ -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;

View File

@ -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
};

View File

@ -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

View File

@ -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<QCheckBox *>( 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<QgsLayerTreeGroup *>( 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<QgsLayerTreeLayer *>( 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();

View File

@ -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<EllipsoidDefs> mEllipsoidList;
int mEllipsoidIndex;
//! populate WMTS tree
void populateWmtsTree( const QgsLayerTreeGroup *treeGroup, QgsTreeWidgetItem *treeItem );
//! Check OWS configuration
void checkOWS( QgsLayerTreeGroup *treeGroup, QStringList &owsNames, QStringList &encodingMessages );

View File

@ -73,6 +73,8 @@ IF (WITH_SERVER_PLUGINS)
qgsserverfilter.cpp
qgsaccesscontrolfilter.cpp
qgsaccesscontrol.cpp
qgsservercachefilter.cpp
qgsservercachemanager.cpp
)
ENDIF (WITH_SERVER_PLUGINS)

View File

@ -24,7 +24,7 @@
void QgsAccessControl::resolveFilterFeatures( const QList<QgsMapLayer *> &layers )
{
Q_FOREACH ( QgsMapLayer *l, layers )
for ( QgsMapLayer *l : layers )
{
if ( l->type() == QgsMapLayer::LayerType::VectorLayer )
{

View File

@ -228,7 +228,7 @@ void QgsRequestHandler::parseInput()
typedef QPair<QString, QString> pair_t;
QUrlQuery query( inputString );
QList<pair_t> 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() );

View File

@ -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 <QDomDocument>
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;
}

View File

@ -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 <QMultiMap>
#include <QDomDocument>
#include <QObject>
#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<int, QgsServerCacheFilter *> QgsServerCacheFilterMap;
#endif // QGSSERVERCACHEFILTER_H

View File

@ -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 &copy )
{
if ( copy.mPluginsServerCaches )
{
mPluginsServerCaches.reset( new QgsServerCacheFilterMap( *copy.mPluginsServerCaches ) );
}
else
{
mPluginsServerCaches.reset( nullptr );
}
}
QgsServerCacheManager &QgsServerCacheManager::operator=( const QgsServerCacheManager &copy )
{
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( '-' );
}

View File

@ -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 <QMultiMap>
#include <QDomDocument>
#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 &copy );
//! Assignment operator
QgsServerCacheManager &operator=( const QgsServerCacheManager &copy );
//! 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<QgsServerCacheFilterMap> mPluginsServerCaches = nullptr;
};
#endif

View File

@ -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;

View File

@ -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 )
{

View File

@ -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<QgsServerCacheManager> mCacheManager = nullptr;
QgsCapabilitiesCache *mCapabilitiesCache = nullptr;
QgsRequestHandler *mRequestHandler = nullptr;
QgsServiceRegistry *mServiceRegistry = nullptr;

View File

@ -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" ) )

View File

@ -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( "/" ), "" );
}

View File

@ -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<QString, QString> 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

View File

@ -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 )

View File

@ -10,4 +10,5 @@ ADD_SUBDIRECTORY(DummyService)
ADD_SUBDIRECTORY(wms)
ADD_SUBDIRECTORY(wfs)
ADD_SUBDIRECTORY(wcs)
ADD_SUBDIRECTORY(wmts)

View File

@ -102,7 +102,7 @@ namespace QgsWcs
};
} // namespace QgsWfs
} // namespace QgsWcs
/**
* \ingroup server

View File

@ -120,7 +120,7 @@ namespace QgsWcs
if ( coveNameList.size() == 0 || coveNameList.contains( name ) )
{
QgsRasterLayer *rLayer = qobject_cast<QgsRasterLayer *>( layer );
coveDescElement.appendChild( getCoverageOffering( doc, const_cast<QgsRasterLayer *>( rLayer ) ) );
coveDescElement.appendChild( getCoverageOffering( doc, const_cast<QgsRasterLayer *>( rLayer ), project ) );
}
}
return doc;

View File

@ -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<QgsRasterLayer *>( layer );
QDomElement layerElem = getCoverageOffering( doc, const_cast<QgsRasterLayer *>( rLayer ), true );
QDomElement layerElem = getCoverageOffering( doc, const_cast<QgsRasterLayer *>( rLayer ), project, true );
contentMetadataElement.appendChild( layerElem );
}

View File

@ -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 );
}

View File

@ -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();
}

View File

@ -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" );

View File

@ -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 );

View File

@ -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() );
}

View File

@ -62,9 +62,9 @@ namespace QgsWfs
QString createFeatureGeoJSON( QgsFeature *feat, const createFeatureParams &params );
QDomElement createFeatureGML2( QgsFeature *feat, QDomDocument &doc, const createFeatureParams &params );
QDomElement createFeatureGML2( QgsFeature *feat, QDomDocument &doc, const createFeatureParams &params, const QgsProject *project );
QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams &params );
QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams &params, 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 &params );
const createFeatureParams &params, 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 &params )
const createFeatureParams &params, 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 &params )
QDomElement createFeatureGML2( QgsFeature *feat, QDomDocument &doc, const createFeatureParams &params, 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 &params )
QDomElement createFeatureGML3( QgsFeature *feat, QDomDocument &doc, const createFeatureParams &params, 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;

View File

@ -53,7 +53,7 @@ namespace QgsWfs
params.remove( QgsServerParameter::SERVICE );
url.setQuery( params.urlQuery() );
href = url.toString( QUrl::FullyDecoded );
href = url.toString();
}
return href;

View File

@ -23,7 +23,7 @@
QgsLayerRestorer::QgsLayerRestorer( const QList<QgsMapLayer *> &layers )
{
Q_FOREACH ( QgsMapLayer *layer, layers )
for ( QgsMapLayer *layer : layers )
{
QgsLayerSettings settings;
settings.name = layer->name();

View File

@ -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 )

View File

@ -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 );
}

View File

@ -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

View File

@ -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 );

View File

@ -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<QgsVectorLayer *>( 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 &param : 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 &param : 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 &param : 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 &param : params )
{
if ( param.mNickname == layerNickname( *layer ) )
{
@ -1244,11 +1244,11 @@ namespace QgsWms
//layers can have assigned a different name for GetCapabilities
QHash<QString, QString> 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<QgsLayerTreeLayer *> 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<QgsLayerTreeLayer *> 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 &param : params )
{
// create sld document from symbology
QDomDocument sldDoc;
@ -2675,7 +2674,7 @@ namespace QgsWms
{
QList<QgsMapLayer *> layers;
Q_FOREACH ( QgsWmsParametersLayer param, params )
for ( const QgsWmsParametersLayer &param : params )
{
QString nickname = param.mNickname;
QString style = param.mStyle;
@ -2798,7 +2797,7 @@ namespace QgsWms
if ( layer->type() == QgsMapLayer::VectorLayer )
{
QgsVectorLayer *filteredLayer = qobject_cast<QgsVectorLayer *>( 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<QgsMapLayer *> wantedLayers;
Q_FOREACH ( QgsMapLayer *layer, layers )
for ( QgsMapLayer *layer : layers )
{
if ( !layerScaleVisibility( *layer, scaleDenominator ) )
continue;
@ -2948,7 +2947,7 @@ namespace QgsWms
{
QList<QgsMapLayer *> 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<QgsVectorLayerFeatureCounter *> 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<int> 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<QgsLayerTreeNode *> 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 );

View File

@ -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}
)

View File

@ -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 &registry, 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
}

View File

@ -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 <QStringList>
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

View File

@ -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 <QDomDocument>
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

View File

@ -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 <QImage>
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

View File

@ -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

View File

@ -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 <QImage>
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<QImage> image;
if ( f == QgsWmtsParameters::Format::JPG )
{
contentType = QStringLiteral( "image/jpeg" );
saveFormat = QStringLiteral( "JPEG" );
image = qgis::make_unique<QImage>( 256, 256, QImage::Format_RGB32 );
}
else
{
contentType = QStringLiteral( "image/png" );
saveFormat = QStringLiteral( "PNG" );
image = qgis::make_unique<QImage>( 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

View File

@ -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

View File

@ -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 <iostream>
namespace QgsWmts
{
//
// QgsWmsParameterForWmts
//
QString QgsWmsParameterForWmts::name( const QgsWmsParameterForWmts::Name name )
{
const QMetaEnum metaEnum( QMetaEnum::fromType<QgsWmsParameterForWmts::Name>() );
return metaEnum.valueToKey( name );
}
QgsWmsParameterForWmts::Name QgsWmsParameterForWmts::name( const QString &name )
{
const QMetaEnum metaEnum( QMetaEnum::fromType<QgsWmsParameterForWmts::Name>() );
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<QgsWmtsParameter::Name>() );
return metaEnum.valueToKey( name );
}
QgsWmtsParameter::Name QgsWmtsParameter::name( const QString &name )
{
const QMetaEnum metaEnum( QMetaEnum::fromType<QgsWmtsParameter::Name>() );
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 &parameters )
: 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 &parameter )
{
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 );
}
}

View File

@ -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 <QMap>
#include <QObject>
#include <QMetaEnum>
#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 &parameters );
/**
* 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 &parameter );
void log( const QString &msg ) const;
QList<QgsProjectVersion> mVersions;
QMap<QgsWmtsParameter::Name, QgsWmtsParameter> mWmtsParameters;
};
}
#endif

View File

@ -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 <QString>
#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

View File

@ -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 &params,
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

View File

@ -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 <QDomDocument>
/**
* \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 &params,
const QgsProject *project, QgsServerInterface *serverIface );
} // namespace QgsWmts
#endif

View File

@ -2467,6 +2467,89 @@
</layout>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBox" name="grpWmtsCapabilities">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>3</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>WMTS capabilities</string>
</property>
<layout class="QGridLayout" name="gridLayout_20">
<item row="0" column="0">
<widget class="QTreeWidget" name="twWmtsLayers">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<column>
<property name="text">
<string>Layer</string>
</property>
</column>
<column>
<property name="text">
<string>Published</string>
</property>
</column>
<column>
<property name="text">
<string>PNG</string>
</property>
</column>
<column>
<property name="text">
<string>JPEG</string>
</property>
</column>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QLabel" name="mWMTSMinScaleLabel">
<property name="text">
<string>Minimum scale</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="mWMTSMinScaleLineEdit">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1000000000</number>
</property>
<property name="value">
<number>5000</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_15">
<item>
<widget class="QLabel" name="mWMTSUrlLabel">
<property name="text">
<string>Advertised URL</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="mWMTSUrlLineEdit"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QgsCollapsibleGroupBox" name="grpWFSCapabilities">
<property name="sizePolicy">
@ -2916,6 +2999,9 @@
<tabstop>mMaxWidthLineEdit</tabstop>
<tabstop>mMaxHeightLineEdit</tabstop>
<tabstop>mWMSImageQualitySpinBox</tabstop>
<tabstop>twWmtsLayers</tabstop>
<tabstop>mWMTSMinScaleLineEdit</tabstop>
<tabstop>mWMTSUrlLineEdit</tabstop>
<tabstop>twWFSLayers</tabstop>
<tabstop>pbnWFSLayersSelectAll</tabstop>
<tabstop>pbnWFSLayersDeselectAll</tabstop>

View File

@ -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)

View File

@ -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()

View File

@ -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"""

View File

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,246 @@
Content-Length: 6575
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="utf-8"?>
<WMS_Capabilities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:qgs="http://www.qgis.org/wms" xmlns="http://www.opengis.net/wms" xsi:schemaLocation="http://www.opengis.net/wms http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd http://www.qgis.org/wms https://www.qgis.org/?SERVICE=WMS&amp;REQUEST=GetSchemaExtension" version="1.3.0" xmlns:sld="http://www.opengis.net/sld">
<Service>
<Name>WMS</Name>
<Title>QGIS TestProject</Title>
<Abstract><![CDATA[Some UTF8 text èòù]]></Abstract>
<KeywordList>
<Keyword vocabulary="ISO">infoMapAccessService</Keyword>
</KeywordList>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/" xmlns:xlink="http://www.w3.org/1999/xlink"/>
<ContactInformation>
<ContactPersonPrimary>
<ContactPerson>Alessandro Pasotti</ContactPerson>
<ContactOrganization>QGIS dev team</ContactOrganization>
</ContactPersonPrimary>
<ContactElectronicMailAddress>elpaso@itopen.it</ContactElectronicMailAddress>
</ContactInformation>
<Fees>conditions unknown</Fees>
<AccessConstraints>None</AccessConstraints>
</Service>
<Capability>
<Request>
<GetCapabilities>
<Format>text/xml</Format>
<DCPType>
<HTTP>
<Get>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Get>
</HTTP>
</DCPType>
</GetCapabilities>
<GetMap>
<Format>image/jpeg</Format>
<Format>image/png</Format>
<Format>image/png; mode=16bit</Format>
<Format>image/png; mode=8bit</Format>
<Format>image/png; mode=1bit</Format>
<Format>application/dxf</Format>
<DCPType>
<HTTP>
<Get>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Get>
</HTTP>
</DCPType>
</GetMap>
<GetFeatureInfo>
<Format>text/plain</Format>
<Format>text/html</Format>
<Format>text/xml</Format>
<Format>application/vnd.ogc.gml</Format>
<Format>application/vnd.ogc.gml/3.1.1</Format>
<DCPType>
<HTTP>
<Get>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Get>
</HTTP>
</DCPType>
</GetFeatureInfo>
<sld:GetLegendGraphic>
<Format>image/jpeg</Format>
<Format>image/png</Format>
<DCPType>
<HTTP>
<Get>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Get>
</HTTP>
</DCPType>
</sld:GetLegendGraphic>
<sld:DescribeLayer>
<Format>text/xml</Format>
<DCPType>
<HTTP>
<Get>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Get>
</HTTP>
</DCPType>
</sld:DescribeLayer>
<qgs:GetStyles>
<Format>text/xml</Format>
<DCPType>
<HTTP>
<Get>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</Get>
</HTTP>
</DCPType>
</qgs:GetStyles>
</Request>
<Exception>
<Format>XML</Format>
</Exception>
<sld:UserDefinedSymbolization SupportSLD="1" RemoteWCS="0" UserLayer="0" InlineFeature="0" RemoteWFS="0" UserStyle="1"/>
<Layer>
<Title>QGIS Test Project</Title>
<Abstract>QGIS Test Project</Abstract>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20315</westBoundLongitude>
<eastBoundLongitude>8.20416</eastBoundLongitude>
<southBoundLatitude>44.9012</southBoundLatitude>
<northBoundLatitude>44.9016</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60604e+06" maxx="913283" miny="5.60599e+06" CRS="EPSG:3857" minx="913171"/>
<BoundingBox maxy="8.20416" maxx="44.9016" miny="8.20315" CRS="EPSG:4326" minx="44.9012"/>
<Name>QGIS Test Project</Name>
<KeywordList>
<Keyword vocabulary="ISO">infoMapAccessService</Keyword>
</KeywordList>
<Layer queryable="1">
<Name>layer_with_short_name</Name>
<Title>A Layer with a short name</Title>
<Abstract>A Layer with an abstract</Abstract>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Style>
<Name>default</Name>
<Title>default</Title>
<LegendURL>
<Format>image/png</Format>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?&amp;SERVICE=WMS&amp;VERSION=1.3.0&amp;REQUEST=GetLegendGraphic&amp;LAYER=layer_with_short_name&amp;FORMAT=image/png&amp;STYLE=default&amp;SLD_VERSION=1.1.0" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</LegendURL>
</Style>
</Layer>
<Layer queryable="1">
<Name>testlayer èé</Name>
<Title>A test vector layer</Title>
<Abstract>A test vector layer with unicode òà</Abstract>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Style>
<Name>default</Name>
<Title>default</Title>
<LegendURL>
<Format>image/png</Format>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?&amp;SERVICE=WMS&amp;VERSION=1.3.0&amp;REQUEST=GetLegendGraphic&amp;LAYER=testlayer èé&amp;FORMAT=image/png&amp;STYLE=default&amp;SLD_VERSION=1.1.0" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</LegendURL>
</Style>
</Layer>
<Layer>
<Name>group_name</Name>
<Title>Group title</Title>
<Abstract>Group abstract</Abstract>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Layer queryable="1">
<Name>testlayer2</Name>
<Title>testlayer2</Title>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Style>
<Name>default</Name>
<Title>default</Title>
<LegendURL>
<Format>image/png</Format>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?&amp;SERVICE=WMS&amp;VERSION=1.3.0&amp;REQUEST=GetLegendGraphic&amp;LAYER=testlayer2&amp;FORMAT=image/png&amp;STYLE=default&amp;SLD_VERSION=1.1.0" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</LegendURL>
</Style>
</Layer>
</Layer>
<Layer>
<Name>groupwithoutshortname</Name>
<Title>groupwithoutshortname</Title>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Layer queryable="0">
<Name>testlayer3</Name>
<Title>testlayer3</Title>
<CRS>CRS:84</CRS>
<CRS>EPSG:4326</CRS>
<CRS>EPSG:3857</CRS>
<EX_GeographicBoundingBox>
<westBoundLongitude>8.20346</westBoundLongitude>
<eastBoundLongitude>8.20355</eastBoundLongitude>
<southBoundLatitude>44.9014</southBoundLatitude>
<northBoundLatitude>44.9015</northBoundLatitude>
</EX_GeographicBoundingBox>
<BoundingBox maxy="5.60603e+06" maxx="913215" miny="5.60601e+06" CRS="EPSG:3857" minx="913205"/>
<BoundingBox maxy="8.20355" maxx="44.9015" miny="8.20346" CRS="EPSG:4326" minx="44.9014"/>
<Style>
<Name>default</Name>
<Title>default</Title>
<LegendURL>
<Format>image/png</Format>
<OnlineResource xlink:type="simple" xlink:href="https://www.qgis.org/?&amp;SERVICE=WMS&amp;VERSION=1.3.0&amp;REQUEST=GetLegendGraphic&amp;LAYER=testlayer3&amp;FORMAT=image/png&amp;STYLE=default&amp;SLD_VERSION=1.1.0" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</LegendURL>
</Style>
</Layer>
</Layer>
</Layer>
</Capability>
</WMS_Capabilities>

View File

@ -0,0 +1,869 @@
Content-Type: text/xml; charset=utf-8
<Capabilities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" xmlns="http://www.opengis.net/wmts/1.0" version="1.0.0" xmlns:gml="http://www.opengis.net/gml" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<ows:ServiceIdentification>
<ows:ServiceType>OGC WMTS</ows:ServiceType>
<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
<ows:Title>QGIS Server test</ows:Title>
<ows:Abstract><![CDATA[Simple test app.]]></ows:Abstract>
<ows:Fees>conditions unknown</ows:Fees>
<ows:AccessConstraints>None</ows:AccessConstraints>
</ows:ServiceIdentification>
<ows:ServiceProvider>
<ows:ProviderName>QGIS</ows:ProviderName>
<ows:ServiceContact>
<ows:IndividualName>Stéphane Brunner</ows:IndividualName>
</ows:ServiceContact>
</ows:ServiceProvider>
<ows:OperationsMetadata>
<ows:Operation name="GetCapabilities">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="?"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="GetTile">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="?"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
<ows:Operation name="GetFeatureInfo">
<ows:DCP>
<ows:HTTP>
<ows:Get xlink:href="?"/>
</ows:HTTP>
</ows:DCP>
</ows:Operation>
</ows:OperationsMetadata>
<Contents>
<Layer>
<ows:Identifier>QGIS Server Hello World</ows:Identifier>
<ows:Title>QGIS Server Hello World</ows:Title>
<ows:Abstract>QGIS Server Hello World</ows:Abstract>
<ows:WGS84BoundingBox>
<LowerCorner>-174.766573 -69.957838</LowerCorner>
<UpperCorner>177.930819 84.307876</UpperCorner>
</ows:WGS84BoundingBox>
<ows:BoundingBox crs="EPSG:3857">
<LowerCorner>-19454925.898459 -11055006.822989</LowerCorner>
<UpperCorner>19807168.136881 19143772.793601</UpperCorner>
</ows:BoundingBox>
<Style isDefault="true">
<ows:Identifier>default</ows:Identifier>
<ows:Title>default</ows:Title>
</Style>
<Format>image/png</Format>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:3857</TileMatrixSet>
<TileMatrixSetLimits>
<TileMatrixLimits>
<TileMatrix>0</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>0</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>1</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>1</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>1</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>2</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>3</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>3</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>3</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>7</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>6</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>4</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>15</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>12</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>5</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>31</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>24</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>6</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>63</MaxTileCol>
<MinTileRow>1</MinTileRow>
<MaxTileRow>49</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>7</TileMatrix>
<MinTileCol>1</MinTileCol>
<MaxTileCol>127</MaxTileCol>
<MinTileRow>2</MinTileRow>
<MaxTileRow>99</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>8</TileMatrix>
<MinTileCol>3</MinTileCol>
<MaxTileCol>254</MaxTileCol>
<MinTileRow>5</MinTileRow>
<MaxTileRow>198</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>9</TileMatrix>
<MinTileCol>7</MinTileCol>
<MaxTileCol>509</MaxTileCol>
<MinTileRow>11</MinTileRow>
<MaxTileRow>397</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>10</TileMatrix>
<MinTileCol>14</MinTileCol>
<MaxTileCol>1018</MaxTileCol>
<MinTileRow>22</MinTileRow>
<MaxTileRow>794</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>11</TileMatrix>
<MinTileCol>29</MinTileCol>
<MaxTileCol>2036</MaxTileCol>
<MinTileRow>45</MinTileRow>
<MaxTileRow>1588</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>12</TileMatrix>
<MinTileCol>59</MinTileCol>
<MaxTileCol>4072</MaxTileCol>
<MinTileRow>91</MinTileRow>
<MaxTileRow>3177</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>13</TileMatrix>
<MinTileCol>119</MinTileCol>
<MaxTileCol>8144</MaxTileCol>
<MinTileRow>182</MinTileRow>
<MaxTileRow>6355</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>14</TileMatrix>
<MinTileCol>238</MinTileCol>
<MaxTileCol>16289</MaxTileCol>
<MinTileRow>365</MinTileRow>
<MaxTileRow>12711</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>15</TileMatrix>
<MinTileCol>476</MinTileCol>
<MaxTileCol>32579</MaxTileCol>
<MinTileRow>730</MinTileRow>
<MaxTileRow>25423</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>16</TileMatrix>
<MinTileCol>952</MinTileCol>
<MaxTileCol>65159</MaxTileCol>
<MinTileRow>1461</MinTileRow>
<MaxTileRow>50846</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>17</TileMatrix>
<MinTileCol>1905</MinTileCol>
<MaxTileCol>130318</MaxTileCol>
<MinTileRow>2923</MinTileRow>
<MaxTileRow>101693</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>18</TileMatrix>
<MinTileCol>3810</MinTileCol>
<MaxTileCol>260637</MaxTileCol>
<MinTileRow>5846</MinTileRow>
<MaxTileRow>203386</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>19</TileMatrix>
<MinTileCol>7621</MinTileCol>
<MaxTileCol>521274</MaxTileCol>
<MinTileRow>11692</MinTileRow>
<MaxTileRow>406772</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>20</TileMatrix>
<MinTileCol>15243</MinTileCol>
<MaxTileCol>1042549</MaxTileCol>
<MinTileRow>23384</MinTileRow>
<MaxTileRow>813545</MaxTileRow>
</TileMatrixLimits>
</TileMatrixSetLimits>
</TileMatrixSetLink>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:4326</TileMatrixSet>
<TileMatrixSetLimits>
<TileMatrixLimits>
<TileMatrix>0</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>1</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>1</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>3</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>1</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>2</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>7</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>3</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>3</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>15</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>7</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>4</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>31</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>14</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>5</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>63</MaxTileCol>
<MinTileRow>1</MinTileRow>
<MaxTileRow>28</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>6</TileMatrix>
<MinTileCol>1</MinTileCol>
<MaxTileCol>127</MaxTileCol>
<MinTileRow>2</MinTileRow>
<MaxTileRow>56</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>7</TileMatrix>
<MinTileCol>3</MinTileCol>
<MaxTileCol>254</MaxTileCol>
<MinTileRow>4</MinTileRow>
<MaxTileRow>113</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>8</TileMatrix>
<MinTileCol>7</MinTileCol>
<MaxTileCol>508</MaxTileCol>
<MinTileRow>8</MinTileRow>
<MaxTileRow>227</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>9</TileMatrix>
<MinTileCol>14</MinTileCol>
<MaxTileCol>1016</MaxTileCol>
<MinTileRow>16</MinTileRow>
<MaxTileRow>454</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>10</TileMatrix>
<MinTileCol>29</MinTileCol>
<MaxTileCol>2032</MaxTileCol>
<MinTileRow>32</MinTileRow>
<MaxTileRow>908</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>11</TileMatrix>
<MinTileCol>59</MinTileCol>
<MaxTileCol>4065</MaxTileCol>
<MinTileRow>64</MinTileRow>
<MaxTileRow>1816</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>12</TileMatrix>
<MinTileCol>118</MinTileCol>
<MaxTileCol>8130</MaxTileCol>
<MinTileRow>129</MinTileRow>
<MaxTileRow>3633</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>13</TileMatrix>
<MinTileCol>237</MinTileCol>
<MaxTileCol>16260</MaxTileCol>
<MinTileRow>258</MinTileRow>
<MaxTileRow>7266</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>14</TileMatrix>
<MinTileCol>475</MinTileCol>
<MaxTileCol>32520</MaxTileCol>
<MinTileRow>517</MinTileRow>
<MaxTileRow>14533</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>15</TileMatrix>
<MinTileCol>951</MinTileCol>
<MaxTileCol>65041</MaxTileCol>
<MinTileRow>1034</MinTileRow>
<MaxTileRow>29066</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>16</TileMatrix>
<MinTileCol>1902</MinTileCol>
<MaxTileCol>130083</MaxTileCol>
<MinTileRow>2068</MinTileRow>
<MaxTileRow>58133</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>17</TileMatrix>
<MinTileCol>3804</MinTileCol>
<MaxTileCol>260167</MaxTileCol>
<MinTileRow>4137</MinTileRow>
<MaxTileRow>116267</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>18</TileMatrix>
<MinTileCol>7608</MinTileCol>
<MaxTileCol>520335</MaxTileCol>
<MinTileRow>8274</MinTileRow>
<MaxTileRow>232535</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>19</TileMatrix>
<MinTileCol>15216</MinTileCol>
<MaxTileCol>1040671</MaxTileCol>
<MinTileRow>16549</MinTileRow>
<MaxTileRow>465071</MaxTileRow>
</TileMatrixLimits>
</TileMatrixSetLimits>
</TileMatrixSetLink>
</Layer>
<Layer>
<ows:Identifier>CountryGroup</ows:Identifier>
<ows:Title>CountryGroup</ows:Title>
<ows:WGS84BoundingBox>
<LowerCorner>-176.248495 -67.592996</LowerCorner>
<UpperCorner>179.412741 83.621086</UpperCorner>
</ows:WGS84BoundingBox>
<ows:BoundingBox crs="EPSG:3857">
<LowerCorner>-19619892.68012 -10327100.342322</LowerCorner>
<UpperCorner>19972134.918542 18415866.312934</UpperCorner>
</ows:BoundingBox>
<Style isDefault="true">
<ows:Identifier>default</ows:Identifier>
<ows:Title>default</ows:Title>
</Style>
<Format>image/png</Format>
<InfoFormat>text/plain</InfoFormat>
<InfoFormat>text/html</InfoFormat>
<InfoFormat>text/xml</InfoFormat>
<InfoFormat>application/vnd.ogc.gml</InfoFormat>
<InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:3857</TileMatrixSet>
<TileMatrixSetLimits>
<TileMatrixLimits>
<TileMatrix>0</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>0</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>1</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>1</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>1</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>2</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>3</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>3</MaxTileRow>
</TileMatrixLimits>
</TileMatrixSetLimits>
</TileMatrixSetLink>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:4326</TileMatrixSet>
<TileMatrixSetLimits>
<TileMatrixLimits>
<TileMatrix>0</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>1</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>1</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>3</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>1</MaxTileRow>
</TileMatrixLimits>
</TileMatrixSetLimits>
</TileMatrixSetLink>
</Layer>
<Layer>
<ows:Identifier>Hello</ows:Identifier>
<ows:WGS84BoundingBox>
<LowerCorner>-132.467818 -1.006739</LowerCorner>
<UpperCorner>101.888717 69.520496</UpperCorner>
</ows:WGS84BoundingBox>
<ows:BoundingBox crs="EPSG:3857">
<LowerCorner>-14746250.075131 -112075.428077</LowerCorner>
<UpperCorner>11342200.077197 10914413.714128</UpperCorner>
</ows:BoundingBox>
<Style isDefault="true">
<ows:Identifier>default</ows:Identifier>
<ows:Title>default</ows:Title>
</Style>
<Format>image/png</Format>
<InfoFormat>text/plain</InfoFormat>
<InfoFormat>text/html</InfoFormat>
<InfoFormat>text/xml</InfoFormat>
<InfoFormat>application/vnd.ogc.gml</InfoFormat>
<InfoFormat>application/vnd.ogc.gml/3.1.1</InfoFormat>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:3857</TileMatrixSet>
<TileMatrixSetLimits>
<TileMatrixLimits>
<TileMatrix>0</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>0</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>1</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>1</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>1</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>2</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>3</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>2</MaxTileRow>
</TileMatrixLimits>
</TileMatrixSetLimits>
</TileMatrixSetLink>
<TileMatrixSetLink>
<TileMatrixSet>EPSG:4326</TileMatrixSet>
<TileMatrixSetLimits>
<TileMatrixLimits>
<TileMatrix>0</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>1</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>1</TileMatrix>
<MinTileCol>0</MinTileCol>
<MaxTileCol>3</MaxTileCol>
<MinTileRow>0</MinTileRow>
<MaxTileRow>1</MaxTileRow>
</TileMatrixLimits>
</TileMatrixSetLimits>
</TileMatrixSetLink>
</Layer>
<TileMatrixSet>
<ows:Identifier>EPSG:3857</ows:Identifier>
<ows:SupportedCRS>EPSG:3857</ows:SupportedCRS>
<TileMatrix>
<ows:Identifier>0</ows:Identifier>
<ScaleDenominator>559082264.028718</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>1</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>1</ows:Identifier>
<ScaleDenominator>279541132.014359</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>2</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>2</ows:Identifier>
<ScaleDenominator>139770566.007179</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>4</MatrixWidth>
<MatrixHeight>4</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>3</ows:Identifier>
<ScaleDenominator>69885283.00359</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>8</MatrixWidth>
<MatrixHeight>8</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>4</ows:Identifier>
<ScaleDenominator>34942641.501795</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>16</MatrixWidth>
<MatrixHeight>16</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>5</ows:Identifier>
<ScaleDenominator>17471320.750897</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>32</MatrixWidth>
<MatrixHeight>32</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>6</ows:Identifier>
<ScaleDenominator>8735660.375449</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>64</MatrixWidth>
<MatrixHeight>64</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>7</ows:Identifier>
<ScaleDenominator>4367830.187724</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>128</MatrixWidth>
<MatrixHeight>128</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>8</ows:Identifier>
<ScaleDenominator>2183915.093862</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>256</MatrixWidth>
<MatrixHeight>256</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>9</ows:Identifier>
<ScaleDenominator>1091957.546931</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>512</MatrixWidth>
<MatrixHeight>512</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>10</ows:Identifier>
<ScaleDenominator>545978.773466</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>1024</MatrixWidth>
<MatrixHeight>1024</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>11</ows:Identifier>
<ScaleDenominator>272989.386733</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2048</MatrixWidth>
<MatrixHeight>2048</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>12</ows:Identifier>
<ScaleDenominator>136494.693366</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>4096</MatrixWidth>
<MatrixHeight>4096</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>13</ows:Identifier>
<ScaleDenominator>68247.346683</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>8192</MatrixWidth>
<MatrixHeight>8192</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>14</ows:Identifier>
<ScaleDenominator>34123.673342</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>16384</MatrixWidth>
<MatrixHeight>16384</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>15</ows:Identifier>
<ScaleDenominator>17061.836671</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>32768</MatrixWidth>
<MatrixHeight>32768</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>16</ows:Identifier>
<ScaleDenominator>8530.918335</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>65536</MatrixWidth>
<MatrixHeight>65536</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>17</ows:Identifier>
<ScaleDenominator>4265.459168</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>131072</MatrixWidth>
<MatrixHeight>131072</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>18</ows:Identifier>
<ScaleDenominator>2132.729584</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>262144</MatrixWidth>
<MatrixHeight>262144</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>19</ows:Identifier>
<ScaleDenominator>1066.364792</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>524288</MatrixWidth>
<MatrixHeight>524288</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>20</ows:Identifier>
<ScaleDenominator>533.182396</ScaleDenominator>
<TopLeftCorner>-20037508.342789 20037508.342789</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>1048576</MatrixWidth>
<MatrixHeight>1048576</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
<TileMatrixSet>
<ows:Identifier>EPSG:4326</ows:Identifier>
<ows:SupportedCRS>EPSG:4326</ows:SupportedCRS>
<TileMatrix>
<ows:Identifier>0</ows:Identifier>
<ScaleDenominator>279541132.014359</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>1</ows:Identifier>
<ScaleDenominator>139770566.007179</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>4</MatrixWidth>
<MatrixHeight>2</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>2</ows:Identifier>
<ScaleDenominator>69885283.00359</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>8</MatrixWidth>
<MatrixHeight>4</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>3</ows:Identifier>
<ScaleDenominator>34942641.501795</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>16</MatrixWidth>
<MatrixHeight>8</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>4</ows:Identifier>
<ScaleDenominator>17471320.750897</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>32</MatrixWidth>
<MatrixHeight>16</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>5</ows:Identifier>
<ScaleDenominator>8735660.375449</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>64</MatrixWidth>
<MatrixHeight>32</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>6</ows:Identifier>
<ScaleDenominator>4367830.187724</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>128</MatrixWidth>
<MatrixHeight>64</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>7</ows:Identifier>
<ScaleDenominator>2183915.093862</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>256</MatrixWidth>
<MatrixHeight>128</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>8</ows:Identifier>
<ScaleDenominator>1091957.546931</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>512</MatrixWidth>
<MatrixHeight>256</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>9</ows:Identifier>
<ScaleDenominator>545978.773466</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>1023</MatrixWidth>
<MatrixHeight>512</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>10</ows:Identifier>
<ScaleDenominator>272989.386733</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2045</MatrixWidth>
<MatrixHeight>1023</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>11</ows:Identifier>
<ScaleDenominator>136494.693366</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>4089</MatrixWidth>
<MatrixHeight>2045</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>12</ows:Identifier>
<ScaleDenominator>68247.346683</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>8178</MatrixWidth>
<MatrixHeight>4089</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>13</ows:Identifier>
<ScaleDenominator>34123.673342</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>16355</MatrixWidth>
<MatrixHeight>8178</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>14</ows:Identifier>
<ScaleDenominator>17061.836671</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>32709</MatrixWidth>
<MatrixHeight>16355</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>15</ows:Identifier>
<ScaleDenominator>8530.918335</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>65418</MatrixWidth>
<MatrixHeight>32709</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>16</ows:Identifier>
<ScaleDenominator>4265.459168</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>130836</MatrixWidth>
<MatrixHeight>65418</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>17</ows:Identifier>
<ScaleDenominator>2132.729584</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>261672</MatrixWidth>
<MatrixHeight>130836</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>18</ows:Identifier>
<ScaleDenominator>1066.364792</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>523344</MatrixWidth>
<MatrixHeight>261672</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>19</ows:Identifier>
<ScaleDenominator>533.182396</ScaleDenominator>
<TopLeftCorner>-180 90</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>1046688</MatrixWidth>
<MatrixHeight>523344</MatrixHeight>
</TileMatrix>
</TileMatrixSet>
</Contents>
</Capabilities>

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis projectname="QGIS Server Hello World" version="3.2.0-Bonn">
<qgis projectname="QGIS Server Hello World" version="3.3.0-Master">
<homePath path=""/>
<title>QGIS Server Hello World</title>
<autotransaction active="0"/>
@ -4362,6 +4362,29 @@ def my_form_open(dialog, layer, feature):
<ProjectionsEnabled type="int">1</ProjectionsEnabled>
<ProjectCRSProj4String type="QString">+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</ProjectCRSProj4String>
</SpatialRefSys>
<WMTSLayers>
<Project type="bool">true</Project>
<Group type="QStringList">
<value>CountryGroup</value>
</Group>
<Layer type="QStringList">
<value>hello20131022151106574</value>
</Layer>
</WMTSLayers>
<WMTSPngLayers>
<Project type="bool">true</Project>
<Group type="QStringList">
<value>CountryGroup</value>
</Group>
<Layer type="QStringList">
<value>hello20131022151106574</value>
</Layer>
</WMTSPngLayers>
<WMTSJpegLayers>
<Project type="bool">false</Project>
<Group type="QStringList"/>
<Layer type="QStringList"/>
</WMTSJpegLayers>
</properties>
<visibility-presets/>
<transformContext/>