Merge pull request #4313 from pblottiere/cleansingleton

[Server] WMS getmap refactoring
This commit is contained in:
rldhont 2017-06-01 17:57:11 +02:00 committed by GitHub
commit df9ee6f705
60 changed files with 17167 additions and 1004 deletions

View File

@ -460,6 +460,7 @@ Convenience function to query topological editing status
:rtype: QgsAnnotationManager
%End
void setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers );
%Docstring
Set a list of layers which should not be taken into account on map identification

View File

@ -84,11 +84,18 @@ class QgsServerProjectParser
/** Returns the text of the <layername> element for a layer element
@return name or a null string in case of error*/
QString layerName( const QDomElement &layerElem ) const;
// QString layerName( const QDomElement &layerElem ) const;
QStringList wfsLayers() const;
QStringList wcsLayers() const;
/** Gets a list containing names of layers. If a layer has a short name,
* then it's used instead of it's name.
* \returns A list of layers' names or short name if defined
* \since QGIS 3.0
*/
QStringList layersNames() const;
//! Add layers for vector joins
void addJoinLayersForElement( const QDomElement &layerElem ) const;
@ -101,10 +108,6 @@ class QgsServerProjectParser
@return id or a null string in case of error*/
QString layerId( const QDomElement &layerElem ) const;
/** Returns the text of the <id> element for a layer element
@return id or a null string in case of error*/
QString layerShortName( const QDomElement &layerElem ) const;
QgsRectangle projectExtent() const;
int numberOfLayers() const;

View File

@ -1980,6 +1980,11 @@ QgsAnnotationManager *QgsProject::annotationManager()
return mAnnotationManager.get();
}
const QgsAnnotationManager *QgsProject::annotationManager() const
{
return mAnnotationManager.get();
}
void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
{
QStringList currentLayers = nonIdentifiableLayers();

View File

@ -419,6 +419,12 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
*/
QgsAnnotationManager *annotationManager();
/**
* Returns a const pointer to the project's annotation manager.
* \since QGIS 3.0
*/
const QgsAnnotationManager *annotationManager() const SIP_SKIP;
/**
* Set a list of layers which should not be taken into account on map identification
*/

View File

@ -40,6 +40,20 @@ QgsConfigCache::QgsConfigCache()
QObject::connect( &mFileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &QgsConfigCache::removeChangedEntry );
}
const QgsProject *QgsConfigCache::project( const QString &path )
{
if ( ! mProjectCache[ path ] )
{
std::unique_ptr<QgsProject> prj( new QgsProject() );
if ( prj->read( path ) )
{
mProjectCache.insert( path, prj.release() );
}
}
return mProjectCache[ path ];
}
QgsServerProjectParser *QgsConfigCache::serverConfiguration( const QString &filePath )
{
QgsMessageLog::logMessage(

View File

@ -31,6 +31,7 @@
class QgsServerProjectParser;
class QgsAccessControl;
class QgsProject;
class SERVER_EXPORT QgsConfigCache : public QObject
{
@ -47,6 +48,14 @@ class SERVER_EXPORT QgsConfigCache : public QObject
void removeEntry( const QString &path );
/** If the project is not cached yet, then the project is read thank to the
* path. If the project is not available, then a nullptr is returned.
* \param path the filename of the QGIS project
* \returns the project or nullptr if an error happened
* \since QGIS 3.0
*/
const QgsProject *project( const QString &path );
private:
QgsConfigCache();
@ -58,6 +67,7 @@ class SERVER_EXPORT QgsConfigCache : public QObject
QCache<QString, QDomDocument> mXmlDocumentCache;
QCache<QString, QgsWmsConfigParser> mWMSConfigCache;
QCache<QString, QgsProject> mProjectCache;
private slots:
//! Removes changed entry from this cache

View File

@ -40,8 +40,32 @@ void QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( const QgsAccess
}
if ( !layer->subsetString().isEmpty() )
{
sql.prepend( " AND " );
sql.prepend( ") AND (" );
sql.append( ")" );
sql.prepend( layer->subsetString() );
sql.prepend( "(" );
}
if ( !layer->setSubsetString( sql ) )
{
QgsMessageLog::logMessage( QStringLiteral( "Layer does not support Subset String" ) );
}
}
}
}
void QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( const QgsAccessControl *accessControl, QgsMapLayer *mapLayer )
{
if ( QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( mapLayer ) )
{
QString sql = accessControl->extraSubsetString( layer );
if ( !sql.isEmpty() )
{
if ( !layer->subsetString().isEmpty() )
{
sql.prepend( ") AND (" );
sql.append( ")" );
sql.prepend( layer->subsetString() );
sql.prepend( "(" );
}
if ( !layer->setSubsetString( sql ) )
{

View File

@ -53,6 +53,13 @@ class SERVER_EXPORT QgsOWSServerFilterRestorer
static void applyAccessControlLayerFilters( const QgsAccessControl *accessControl, QgsMapLayer *mapLayer,
QHash<QgsMapLayer *, QString> &originalLayerFilters );
/** Applies filters from access control on layer.
* \param accessControl The access control instance
* \param mapLayer The layer on which the filter has to be applied
* \since QGIS 3.0
*/
static void applyAccessControlLayerFilters( const QgsAccessControl *accessControl, QgsMapLayer *mapLayer );
private:
const QgsAccessControl *mAccessControl = nullptr;
QHash<QgsMapLayer *, QString> mOriginalLayerFilters;

View File

@ -76,6 +76,7 @@ QgsServer::QgsServer( )
abort();
}
init();
mConfigCache = QgsConfigCache::instance();
}
QString &QgsServer::serverName()
@ -353,20 +354,10 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
QString configFilePath = configPath( *sConfigFilePath, parameterMap );
// load the project if needed and not empty
auto projectIt = mProjectRegistry.find( configFilePath );
if ( projectIt == mProjectRegistry.constEnd() )
const QgsProject *project = mConfigCache->project( configFilePath );
if ( ! project )
{
// load the project
QgsProject *project = new QgsProject();
project->setFileName( configFilePath );
if ( project->read() )
{
projectIt = mProjectRegistry.insert( configFilePath, project );
}
else
{
throw QgsServerException( QStringLiteral( "Project file error" ) );
}
throw QgsServerException( QStringLiteral( "Project file error" ) );
}
sServerInterface->setConfigFilePath( configFilePath );
@ -397,7 +388,7 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
QgsService *service = sServiceRegistry.getService( serviceString, versionString );
if ( service )
{
service->executeRequest( request, responseDecorator, projectIt.value() );
service->executeRequest( request, responseDecorator, project );
}
else
{

View File

@ -124,8 +124,8 @@ class SERVER_EXPORT QgsServer
static QgsServerSettings sSettings;
// map of QgsProject
QMap<QString, const QgsProject *> mProjectRegistry;
//! cache
QgsConfigCache *mConfigCache;
};
#endif // QGSSERVER_H

View File

@ -29,10 +29,13 @@
#include "qgsvectorlayerjoinbuffer.h"
#include "qgseditorwidgetregistry.h"
#include "qgslayertreegroup.h"
#include "qgslayertreelayer.h"
#include "qgslayertree.h"
#include "qgslogger.h"
#include "qgseditorwidgetsetup.h"
#include "qgsgui.h"
#include "qgsexpressionnodeimpl.h"
#include "qgsserverprojectutils.h"
#include <QDomDocument>
#include <QFileInfo>
@ -42,32 +45,35 @@
QgsServerProjectParser::QgsServerProjectParser( QDomDocument *xmlDoc, const QString &filePath )
: mXMLDoc( xmlDoc )
, mProject( QgsConfigCache::instance()->project( filePath ) )
, mProjectPath( filePath )
, mUseLayerIDs( false )
{
QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
mProjectLayerElements.reserve( layers.size() );
Q_FOREACH ( QgsMapLayer *layer, layers )
{
QDomDocument doc;
QDomElement el = doc.createElement( "maplayer" );
layer->writeLayerXml( el, doc, QgsReadWriteContext() );
mProjectLayerElements.push_back( el );
QString name = layer->shortName();
if ( name.isEmpty() )
{
name = layer->name();
}
mProjectLayerElementsByName.insert( name, el );
mProjectLayerElementsById.insert( layer->id(), el );
}
mRestrictedLayers = findRestrictedLayers();
//accelerate the search for layers, groups and the creation of annotation items
if ( mXMLDoc )
{
QDomNodeList layerNodeList = mXMLDoc->elementsByTagName( QStringLiteral( "maplayer" ) );
QDomElement currentElement;
int nNodes = layerNodeList.size();
mProjectLayerElements.reserve( nNodes );
for ( int i = 0; i < nNodes; ++i )
{
currentElement = layerNodeList.at( i ).toElement();
mProjectLayerElements.push_back( currentElement );
QString lName = layerShortName( currentElement );
if ( lName.isEmpty() )
lName = layerName( currentElement );
mProjectLayerElementsByName.insert( lName, currentElement );
mProjectLayerElementsById.insert( layerId( currentElement ), currentElement );
}
mLegendGroupElements = findLegendGroupElements();
mUseLayerIDs = findUseLayerIds();
mRestrictedLayers = findRestrictedLayers();
mCustomLayerOrder.clear();
QDomElement customOrder = mXMLDoc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) ).firstChildElement( QStringLiteral( "custom-order" ) );
@ -88,9 +94,31 @@ QgsServerProjectParser::QgsServerProjectParser( QDomDocument *xmlDoc, const QStr
}
}
bool QgsServerProjectParser::useLayerIds() const
{
return QgsServerProjectUtils::wmsUseLayerIds( *mProject );
}
QStringList QgsServerProjectParser::layersNames() const
{
QStringList names;
Q_FOREACH ( QgsMapLayer *layer, mProject->mapLayers() )
{
if ( ! layer->shortName().isEmpty() )
{
names.append( layer->shortName() );
}
else
{
names.append( layer->name() );
}
}
return names;
}
QgsServerProjectParser::QgsServerProjectParser()
: mXMLDoc( nullptr )
, mUseLayerIDs( false )
{
}
@ -349,21 +377,6 @@ QString QgsServerProjectParser::layerId( const QDomElement &layerElem ) const
return idElem.text();
}
QString QgsServerProjectParser::layerShortName( const QDomElement &layerElem ) const
{
if ( layerElem.isNull() )
{
return QString();
}
QDomElement nameElem = layerElem.firstChildElement( QStringLiteral( "shortname" ) );
if ( nameElem.isNull() )
{
return QString();
}
return nameElem.text().replace( QLatin1String( "," ), QLatin1String( "%60" ) );
}
QgsRectangle QgsServerProjectParser::projectExtent() const
{
QgsRectangle extent;
@ -637,21 +650,6 @@ void QgsServerProjectParser::serviceCapabilities( QDomElement &parentElement, QD
parentElement.appendChild( serviceElem );
}
QString QgsServerProjectParser::layerName( const QDomElement &layerElem ) const
{
if ( layerElem.isNull() )
{
return QString();
}
QDomElement nameElem = layerElem.firstChildElement( QStringLiteral( "layername" ) );
if ( nameElem.isNull() )
{
return QString();
}
return nameElem.text().replace( QLatin1String( "," ), QLatin1String( "%60" ) ); //commas are not allowed in layer names
}
void QgsServerProjectParser::combineExtentAndCrsOfGroupChildren( QDomElement &groupElem, QDomDocument &doc, bool considerMapExtent ) const
{
QgsRectangle combinedBBox;
@ -1048,130 +1046,54 @@ QDomElement QgsServerProjectParser::propertiesElem() const
QSet<QString> QgsServerProjectParser::findRestrictedLayers() const
{
QSet<QString> restrictedLayerSet;
// get name of restricted layers/groups in project
QStringList restricted = QgsServerProjectUtils::wmsRestrictedLayers( *mProject );
if ( !mXMLDoc )
// extract restricted layers from excluded groups
QStringList restrictedLayersNames;
QgsLayerTree *root = mProject->layerTreeRoot();
Q_FOREACH ( QString l, restricted )
{
return restrictedLayerSet;
QgsLayerTreeGroup *group = root->findGroup( l );
if ( group )
{
QList<QgsLayerTreeLayer *> groupLayers = group->findLayers();
Q_FOREACH ( QgsLayerTreeLayer *treeLayer, groupLayers )
{
restrictedLayersNames.append( treeLayer->name() );
}
}
else
{
restrictedLayersNames.append( l );
}
}
//names of unpublished layers / groups
QDomElement propertiesElem = mXMLDoc->documentElement().firstChildElement( QStringLiteral( "properties" ) );
if ( !propertiesElem.isNull() )
// build output with names, ids or short name according to the configuration
QSet<QString> restrictedLayers;
QList<QgsLayerTreeLayer *> layers = root->findLayers();
Q_FOREACH ( QgsLayerTreeLayer *layer, layers )
{
QDomElement wmsLayerRestrictionElem = propertiesElem.firstChildElement( QStringLiteral( "WMSRestrictedLayers" ) );
if ( !wmsLayerRestrictionElem.isNull() )
if ( restrictedLayersNames.contains( layer->name() ) )
{
QStringList restrictedLayersAndGroups;
QDomNodeList wmsLayerRestrictionValues = wmsLayerRestrictionElem.elementsByTagName( QStringLiteral( "value" ) );
for ( int i = 0; i < wmsLayerRestrictionValues.size(); ++i )
QString shortName = layer->layer()->shortName();
if ( QgsServerProjectUtils::wmsUseLayerIds( *mProject ) )
{
restrictedLayerSet.insert( wmsLayerRestrictionValues.at( i ).toElement().text() );
restrictedLayers.insert( layer->layerId() );
}
else if ( ! shortName.isEmpty() )
{
restrictedLayers.insert( shortName );
}
else
{
restrictedLayers.insert( layer->name() );
}
}
}
//get legend dom element
if ( restrictedLayerSet.size() < 1 || !mXMLDoc )
{
return restrictedLayerSet;
}
QDomElement legendElem = mXMLDoc->documentElement().firstChildElement( QStringLiteral( "legend" ) );
if ( legendElem.isNull() )
{
return restrictedLayerSet;
}
//go through all legend groups and insert names of subgroups / sublayers if there is a match
QDomNodeList legendGroupList = legendElem.elementsByTagName( QStringLiteral( "legendgroup" ) );
for ( int i = 0; i < legendGroupList.size(); ++i )
{
//get name
QDomElement groupElem = legendGroupList.at( i ).toElement();
QString groupName = groupElem.attribute( QStringLiteral( "name" ) );
if ( restrictedLayerSet.contains( groupName ) ) //match: add names of subgroups and sublayers to set
{
//embedded group? -> also get names of subgroups and sublayers from embedded projects
if ( groupElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
{
sublayersOfEmbeddedGroup( convertToAbsolutePath( groupElem.attribute( QStringLiteral( "project" ) ) ), groupName, restrictedLayerSet );
}
else //local group
{
QDomNodeList subgroupList = groupElem.elementsByTagName( QStringLiteral( "legendgroup" ) );
for ( int j = 0; j < subgroupList.size(); ++j )
{
restrictedLayerSet.insert( subgroupList.at( j ).toElement().attribute( QStringLiteral( "name" ) ) );
}
QDomNodeList sublayerList = groupElem.elementsByTagName( QStringLiteral( "legendlayer" ) );
for ( int k = 0; k < sublayerList.size(); ++k )
{
restrictedLayerSet.insert( sublayerList.at( k ).toElement().attribute( QStringLiteral( "name" ) ) );
}
}
}
}
// wmsLayerRestrictionValues contains LayerIDs
if ( mUseLayerIDs )
{
QDomNodeList legendLayerList = legendElem.elementsByTagName( QStringLiteral( "legendlayer" ) );
for ( int i = 0; i < legendLayerList.size(); ++i )
{
//get name
QDomElement layerElem = legendLayerList.at( i ).toElement();
QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
if ( restrictedLayerSet.contains( layerName ) ) //match: add layer id
{
// get legend layer file element
QDomNodeList layerfileList = layerElem.elementsByTagName( QStringLiteral( "legendlayerfile" ) );
if ( !layerfileList.isEmpty() )
{
// add layer id
restrictedLayerSet.insert( layerfileList.at( 0 ).toElement().attribute( QStringLiteral( "layerid" ) ) );
}
}
}
}
// Add short name in restricted layers
else
{
QDomNodeList layerNodeList = mXMLDoc->elementsByTagName( "maplayer" );
for ( int i = 0; i < layerNodeList.size(); ++i )
{
QDomElement layerElem = layerNodeList.at( i ).toElement();
// get name
QString lName = layerName( layerElem );
if ( restrictedLayerSet.contains( lName ) )
{
// get short name
lName = layerShortName( layerElem );
if ( !lName.isEmpty() )
{
// add short name
restrictedLayerSet.insert( lName );
}
}
}
}
return restrictedLayerSet;
}
bool QgsServerProjectParser::findUseLayerIds() const
{
if ( !mXMLDoc )
return false;
QDomElement propertiesElem = mXMLDoc->documentElement().firstChildElement( QStringLiteral( "properties" ) );
if ( propertiesElem.isNull() )
return false;
QDomElement wktElem = propertiesElem.firstChildElement( QStringLiteral( "WMSUseLayerIDs" ) );
if ( wktElem.isNull() )
return false;
return wktElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
return restrictedLayers;
}
void QgsServerProjectParser::layerFromLegendLayer( const QDomElement &legendLayerElem, QMap< int, QgsMapLayer *> &layers, bool useCache ) const
@ -1304,6 +1226,11 @@ void QgsServerProjectParser::sublayersOfEmbeddedGroup( const QString &projectFil
}
}
QStringList QgsServerProjectParser::wfsLayers() const
{
return QgsServerProjectUtils::wfsLayerIds( *mProject );
}
QStringList QgsServerProjectParser::wfsLayerNames() const
{
QStringList layerNameList;
@ -1312,7 +1239,7 @@ QStringList QgsServerProjectParser::wfsLayerNames() const
projectLayerMap( layerMap );
QgsMapLayer *currentLayer = nullptr;
QStringList wfsIdList = wfsLayers();
QStringList wfsIdList = QgsServerProjectUtils::wfsLayerIds( *mProject );
QStringList::const_iterator wfsIdIt = wfsIdList.constBegin();
for ( ; wfsIdIt != wfsIdList.constEnd(); ++wfsIdIt )
{
@ -1322,7 +1249,8 @@ QStringList QgsServerProjectParser::wfsLayerNames() const
currentLayer = layerMapIt.value();
if ( currentLayer )
{
layerNameList.append( mUseLayerIDs ? currentLayer->id() : currentLayer->name() );
bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *mProject );
layerNameList.append( useLayerIds ? currentLayer->id() : currentLayer->name() );
}
}
}
@ -1348,7 +1276,8 @@ QStringList QgsServerProjectParser::wcsLayerNames() const
currentLayer = layerMapIt.value();
if ( currentLayer )
{
layerNameList.append( mUseLayerIDs ? currentLayer->id() : currentLayer->name() );
bool useLayerIds = QgsServerProjectUtils::wmsUseLayerIds( *mProject );
layerNameList.append( useLayerIds ? currentLayer->id() : currentLayer->name() );
}
}
}
@ -1454,66 +1383,9 @@ QList< QPair< QString, QgsDatumTransformStore::Entry > > QgsServerProjectParser:
return layerTransformList;
}
QStringList QgsServerProjectParser::wfsLayers() const
{
QStringList wfsList;
if ( !mXMLDoc )
{
return wfsList;
}
QDomElement qgisElem = mXMLDoc->documentElement();
if ( qgisElem.isNull() )
{
return wfsList;
}
QDomElement propertiesElem = qgisElem.firstChildElement( QStringLiteral( "properties" ) );
if ( propertiesElem.isNull() )
{
return wfsList;
}
QDomElement wfsLayersElem = propertiesElem.firstChildElement( QStringLiteral( "WFSLayers" ) );
if ( wfsLayersElem.isNull() )
{
return wfsList;
}
QDomNodeList valueList = wfsLayersElem.elementsByTagName( QStringLiteral( "value" ) );
for ( int i = 0; i < valueList.size(); ++i )
{
wfsList << valueList.at( i ).toElement().text();
}
return wfsList;
}
QStringList QgsServerProjectParser::wcsLayers() const
{
QStringList wcsList;
if ( !mXMLDoc )
{
return wcsList;
}
QDomElement qgisElem = mXMLDoc->documentElement();
if ( qgisElem.isNull() )
{
return wcsList;
}
QDomElement propertiesElem = qgisElem.firstChildElement( QStringLiteral( "properties" ) );
if ( propertiesElem.isNull() )
{
return wcsList;
}
QDomElement wcsLayersElem = propertiesElem.firstChildElement( QStringLiteral( "WCSLayers" ) );
if ( wcsLayersElem.isNull() )
{
return wcsList;
}
QDomNodeList valueList = wcsLayersElem.elementsByTagName( QStringLiteral( "value" ) );
for ( int i = 0; i < valueList.size(); ++i )
{
wcsList << valueList.at( i ).toElement().text();
}
return wcsList;
return QgsServerProjectUtils::wcsLayerIds( *mProject );
}
void QgsServerProjectParser::addJoinLayersForElement( const QDomElement &layerElem ) const

View File

@ -85,7 +85,7 @@ class SERVER_EXPORT QgsServerProjectParser
QDomElement propertiesElem() const;
QSet<QString> restrictedLayers() const { return mRestrictedLayers; }
bool useLayerIds() const { return mUseLayerIDs; }
bool useLayerIds() const;
QHash< QString, QDomElement > projectLayerElementsByName() const { return mProjectLayerElementsByName; }
QHash< QString, QDomElement > projectLayerElementsById() const { return mProjectLayerElementsById; }
@ -95,16 +95,19 @@ class SERVER_EXPORT QgsServerProjectParser
QStringList wfsLayerNames() const;
QStringList wcsLayerNames() const;
/** Gets a list containing names of layers. If a layer has a short name,
* then it's used instead of it's name.
* \returns A list of layers' names or short name if defined
* \since QGIS 3.0
*/
QStringList layersNames() const;
QDomElement firstComposerLegendElement() const;
QList<QDomElement> publishedComposerElements() const;
QList< QPair< QString, QgsDatumTransformStore::Entry > > layerCoordinateTransforms() const;
/** Returns the text of the <layername> element for a layer element
\returns name or a null string in case of error*/
QString layerName( const QDomElement &layerElem ) const;
QStringList wfsLayers() const;
QStringList wcsLayers() const;
@ -119,10 +122,6 @@ class SERVER_EXPORT QgsServerProjectParser
\returns id or a null string in case of error*/
QString layerId( const QDomElement &layerElem ) const;
/** Returns the text of the <id> element for a layer element
\returns id or a null string in case of error*/
QString layerShortName( const QDomElement &layerElem ) const;
QgsRectangle projectExtent() const;
int numberOfLayers() const;
@ -138,6 +137,9 @@ class SERVER_EXPORT QgsServerProjectParser
//! Content of project file
QDomDocument *mXMLDoc = nullptr;
//! Project
const QgsProject *mProject = nullptr;
//! Absolute project file path (including file name)
QString mProjectPath;
@ -156,8 +158,6 @@ class SERVER_EXPORT QgsServerProjectParser
//! Names of layers and groups which should not be published
QSet<QString> mRestrictedLayers;
bool mUseLayerIDs;
QgsServerProjectParser(); //forbidden
//! Returns a complete string set with all the restricted layer names (layers/groups that are not to be published)
@ -165,8 +165,6 @@ class SERVER_EXPORT QgsServerProjectParser
QStringList mCustomLayerOrder;
bool findUseLayerIds() const;
QList<QDomElement> findLegendGroupElements() const;
QList<QDomElement> setLegendGroupElementsWithLayerTree( QgsLayerTreeGroup *layerTreeGroup, const QDomElement &legendElement ) const;

View File

@ -102,7 +102,7 @@ int QgsServerProjectUtils::wmsMaxHeight( const QgsProject &project )
bool QgsServerProjectUtils::wmsUseLayerIds( const QgsProject &project )
{
return project.readBoolEntry( QStringLiteral( "WMSUseLayerIDs" ), QStringLiteral( "/" ) );
return project.readBoolEntry( QStringLiteral( "WMSUseLayerIDs" ), QStringLiteral( "/" ), false );
}
bool QgsServerProjectUtils::wmsInfoFormatSia2045( const QgsProject &project )

View File

@ -1746,25 +1746,9 @@ void QgsWmsProjectParser::addOWSLayers( QDomDocument &doc,
int QgsWmsProjectParser::layersAndStyles( QStringList &layers, QStringList &styles ) const
{
layers.clear();
layers = mProjectParser->layersNames();
styles.clear();
const QList<QDomElement> &projectLayerElements = mProjectParser->projectLayerElements();
QList<QDomElement>::const_iterator elemIt = projectLayerElements.constBegin();
QString currentLayerName;
for ( ; elemIt != projectLayerElements.constEnd(); ++elemIt )
{
currentLayerName = mProjectParser->layerShortName( *elemIt );
if ( currentLayerName.isEmpty() )
currentLayerName = mProjectParser->layerName( *elemIt );
if ( !currentLayerName.isEmpty() )
{
layers << currentLayerName;
styles << QString();
}
}
styles.reserve( layers.size() );
return 0;
}

View File

@ -18,12 +18,20 @@ SET (wms_SRCS
qgsmaprendererjobproxy.cpp
qgsmediancut.cpp
qgswmsrenderer.cpp
qgswmsparameters.cpp
qgslayerrestorer.cpp
)
SET (wms_MOC_HDRS
qgswmsparameters.h
)
########################################################
# Build
ADD_LIBRARY (wms MODULE ${wms_SRCS})
QT5_WRAP_CPP(wms_MOC_SRCS ${wms_MOC_HDRS})
ADD_LIBRARY (wms MODULE ${wms_SRCS} ${wms_MOC_SRCS} ${wms_MOC_HDRS})
INCLUDE_DIRECTORIES(SYSTEM
@ -35,6 +43,7 @@ INCLUDE_DIRECTORIES(SYSTEM
INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core
${CMAKE_SOURCE_DIR}/src/core/annotations
${CMAKE_SOURCE_DIR}/src/core/expression
${CMAKE_SOURCE_DIR}/src/core/dxf
${CMAKE_SOURCE_DIR}/src/core/expression

View File

@ -0,0 +1,115 @@
/***************************************************************************
qgslayerrestorer.cpp
--------------------
begin : April 24, 2017
copyright : (C) 2017 by Paul Blottiere
email : paul.blottiere@oslandia.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 "qgslayerrestorer.h"
#include "qgsvectorlayer.h"
#include "qgsrasterlayer.h"
#include "qgsrasterrenderer.h"
#include "qgsmaplayerstylemanager.h"
const QString DEFAULT_NAMED_STYLE = "server_default_style";
QgsLayerRestorer::QgsLayerRestorer( const QList<QgsMapLayer *> &layers )
{
Q_FOREACH ( QgsMapLayer *layer, layers )
{
QgsLayerSettings settings;
QString style = layer->styleManager()->currentStyle();
if ( style.isEmpty() )
{
layer->styleManager()->addStyleFromLayer( DEFAULT_NAMED_STYLE );
settings.mNamedStyle = DEFAULT_NAMED_STYLE;
}
else
{
settings.mNamedStyle = style;
}
// set a custom property allowing to keep in memory if a SLD file has
// been loaded for rendering
layer->setCustomProperty( "readSLD", false );
QString errMsg;
QDomDocument sldDoc;
layer->exportSldStyle( sldDoc, errMsg );
settings.mSldStyle.setContent( sldDoc.toString(), true ); // for namespace processing
if ( layer->type() == QgsMapLayer::LayerType::VectorLayer )
{
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vLayer )
{
settings.mOpacity = vLayer->opacity();
settings.mSelectedFeatureIds = vLayer->selectedFeatureIds();
settings.mFilter = vLayer->subsetString();
}
}
else if ( layer->type() == QgsMapLayer::LayerType::RasterLayer )
{
QgsRasterLayer *rLayer = qobject_cast<QgsRasterLayer *>( layer );
if ( rLayer )
{
settings.mOpacity = rLayer->renderer()->opacity();
}
}
mLayerSettings[layer] = settings;
}
}
QgsLayerRestorer::~QgsLayerRestorer()
{
for ( QgsMapLayer *layer : mLayerSettings.keys() )
{
QgsLayerSettings settings = mLayerSettings[layer];
layer->styleManager()->setCurrentStyle( settings.mNamedStyle );
// if a SLD file has been loaded for rendering, we restore the previous one
QString errMsg;
QDomElement root = settings.mSldStyle.firstChildElement( "StyledLayerDescriptor" );
QDomElement el = root.firstChildElement( "NamedLayer" );
if ( layer->customProperty( "readSLD", false ).toBool() )
{
layer->readSld( el, errMsg );
}
layer->removeCustomProperty( "readSLD" );
if ( layer->type() == QgsMapLayer::LayerType::VectorLayer )
{
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vLayer )
{
vLayer->setOpacity( settings.mOpacity );
vLayer->selectByIds( settings.mSelectedFeatureIds );
vLayer->setSubsetString( settings.mFilter );
}
}
else if ( layer->type() == QgsMapLayer::LayerType::RasterLayer )
{
QgsRasterLayer *rLayer = qobject_cast<QgsRasterLayer *>( layer );
if ( rLayer )
{
rLayer->renderer()->setOpacity( settings.mOpacity );
}
}
}
}

View File

@ -0,0 +1,48 @@
/***************************************************************************
qgslayerrestorer.h
-------------------
begin : April 24, 2017
copyright : (C) 2017 by Paul Blottiere
email : paul.blottiere@oslandia.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 QGSLAYERRESTORER_H
#define QGSLAYERRESTORER_H
#include <QList>
#include "qgsmaplayer.h"
/** RAII class to restore layer configuration on destruction (opacity,
* filters, ...)
* \since QGIS 3.0
*/
class QgsLayerRestorer
{
struct QgsLayerSettings
{
double mOpacity;
QString mNamedStyle;
QDomDocument mSldStyle;
QString mFilter;
QgsFeatureIds mSelectedFeatureIds;
};
public:
QgsLayerRestorer( const QList<QgsMapLayer *> &layers );
~QgsLayerRestorer();
private:
QMap<QgsMapLayer *, QgsLayerSettings> mLayerSettings;
};
#endif

View File

@ -0,0 +1,660 @@
/***************************************************************************
qgswmsparameters.cpp
--------------------
begin : March 17, 2017
copyright : (C) 2017 by Paul Blottiere
email : paul dot blottiere at oslandia 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 "qgswmsparameters.h"
#include "qgsmessagelog.h"
#include <iostream>
namespace QgsWms
{
QgsWmsParameters::QgsWmsParameters()
{
const Parameter pHighlightGeom = { ParameterName::HIGHLIGHT_GEOM,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightGeom );
const Parameter pHighlightSymbol = { ParameterName::HIGHLIGHT_SYMBOL,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightSymbol );
const Parameter pHighlightLabel = { ParameterName::HIGHLIGHT_LABELSTRING,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightLabel );
const Parameter pHighlightColor = { ParameterName::HIGHLIGHT_LABELCOLOR,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightColor );
const Parameter pHighlightFontSize = { ParameterName::HIGHLIGHT_LABELSIZE,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightFontSize );
const Parameter pHighlightFontWeight = { ParameterName::HIGHLIGHT_LABELWEIGHT,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightFontWeight );
const Parameter pHighlightFont = { ParameterName::HIGHLIGHT_LABELFONT,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightFont );
const Parameter pHighlightBufferColor = { ParameterName::HIGHLIGHT_LABELBUFFERCOLOR,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightBufferColor );
const Parameter pHighlightBufferSize = { ParameterName::HIGHLIGHT_LABELBUFFERSIZE,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pHighlightBufferSize );
const Parameter pCRS = { ParameterName::CRS,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pCRS );
const Parameter pHeight = { ParameterName::HEIGHT,
QVariant::Int,
QVariant( 0 ),
QVariant()
};
save( pHeight );
const Parameter pWidth = { ParameterName::WIDTH,
QVariant::Int,
QVariant( 0 ),
QVariant()
};
save( pWidth );
const Parameter pBbox = { ParameterName::BBOX,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pBbox );
const Parameter pSld = { ParameterName::SLD,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pSld );
const Parameter pLayer = { ParameterName::LAYER,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pLayer );
const Parameter pLayers = { ParameterName::LAYERS,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pLayers );
const Parameter pStyle = { ParameterName::STYLE,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pLayers );
const Parameter pStyles = { ParameterName::STYLES,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pStyles );
const Parameter pOpacities = { ParameterName::OPACITIES,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pOpacities );
const Parameter pFilter = { ParameterName::FILTER,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pFilter );
const Parameter pSelection = { ParameterName::SELECTION,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pSelection );
}
QgsWmsParameters::QgsWmsParameters( const QgsServerRequest::Parameters &parameters )
{
load( parameters );
}
void QgsWmsParameters::load( const QgsServerRequest::Parameters &parameters )
{
mRequestParameters = parameters;
const QMetaEnum metaEnum( QMetaEnum::fromType<ParameterName>() );
foreach ( QString key, parameters.keys() )
{
const ParameterName name = ( ParameterName ) metaEnum.keyToValue( key.toStdString().c_str() );
if ( name >= 0 )
{
QVariant value( parameters[key] );
if ( value.canConvert( mParameters[name].mType ) )
{
mParameters[name].mValue = value;
}
else
{
raiseError( name );
}
}
}
}
void QgsWmsParameters::dump() const
{
const QMetaEnum metaEnum( QMetaEnum::fromType<ParameterName>() );
log( "WMS Request parameters:" );
for ( auto parameter : mParameters.toStdMap() )
{
const QString value = parameter.second.mValue.toString();
if ( ! value.isEmpty() )
{
const QString name = metaEnum.valueToKey( parameter.first );
log( " - " + name + " : " + value );
}
}
}
void QgsWmsParameters::save( const Parameter &parameter )
{
mParameters[ parameter.mName ] = parameter;
}
QVariant QgsWmsParameters::value( ParameterName name ) const
{
return mParameters[name].mValue;
}
QStringList QgsWmsParameters::highlightGeom() const
{
return toStringList( ParameterName::HIGHLIGHT_GEOM, ';' );
}
QList<QgsGeometry> QgsWmsParameters::highlightGeomAsGeom() const
{
QList<QgsGeometry> geometries;
Q_FOREACH ( QString wkt, highlightGeom() )
{
QgsGeometry g( QgsGeometry::fromWkt( wkt ) );
if ( g.isGeosValid() )
{
geometries.append( g );
}
else
{
QString val = value( ParameterName::HIGHLIGHT_GEOM ).toString();
QString msg = "HIGHLIGHT_GEOM ('" + val + "') cannot be converted into a list of geometries";
raiseError( msg );
}
}
return geometries;
}
QStringList QgsWmsParameters::highlightSymbol() const
{
return toStringList( ParameterName::HIGHLIGHT_SYMBOL, ';' );
}
QString QgsWmsParameters::crs() const
{
return value( ParameterName::CRS ).toString();
}
QString QgsWmsParameters::bbox() const
{
return value( ParameterName::BBOX ).toString();
}
QgsRectangle QgsWmsParameters::bboxAsRectangle() const
{
QgsRectangle extent;
if ( !bbox().isEmpty() )
{
QStringList corners = bbox().split( "," );
if ( corners.size() == 4 )
{
double d[4];
bool ok;
for ( int i = 0; i < 4; i++ )
{
corners[i].replace( QLatin1String( " " ), QLatin1String( "+" ) );
d[i] = corners[i].toDouble( &ok );
if ( !ok )
{
raiseError( "BBOX ('" + bbox() + "') cannot be converted into a rectangle" );
}
}
extent = QgsRectangle( d[0], d[1], d[2], d[3] );
}
else
{
raiseError( "BBOX ('" + bbox() + "') cannot be converted into a rectangle" );
}
}
return extent;
}
int QgsWmsParameters::height() const
{
bool ok = false;
int height = value( ParameterName::HEIGHT ).toInt( &ok );
if ( ! ok )
raiseError( ParameterName::HEIGHT );
return height;
}
int QgsWmsParameters::width() const
{
bool ok = false;
int width = value( ParameterName::WIDTH ).toInt( &ok );
if ( ! ok )
raiseError( ParameterName::WIDTH );
return width;
}
QStringList QgsWmsParameters::toStringList( ParameterName name, char delimiter ) const
{
return value( name ).toString().split( delimiter, QString::SkipEmptyParts );
}
QList<int> QgsWmsParameters::toIntList( QStringList l, ParameterName p ) const
{
QList<int> elements;
Q_FOREACH ( QString element, l )
{
bool ok;
int e = element.toInt( &ok );
if ( ok )
{
elements.append( e );
}
else
{
QString val = value( p ).toString();
QString n = name( p );
QString msg = n + " ('" + val + "') cannot be converted into a list of int";
raiseError( msg );
}
}
return elements;
}
QList<float> QgsWmsParameters::toFloatList( QStringList l, ParameterName p ) const
{
QList<float> elements;
Q_FOREACH ( QString element, l )
{
bool ok;
float e = element.toFloat( &ok );
if ( ok )
{
elements.append( e );
}
else
{
QString val = value( p ).toString();
QString n = name( p );
QString msg = n + " ('" + val + "') cannot be converted into a list of float";
raiseError( msg );
}
}
return elements;
}
QList<QColor> QgsWmsParameters::toColorList( QStringList l, ParameterName p ) const
{
QList<QColor> elements;
Q_FOREACH ( QString element, l )
{
QColor c = QColor( element );
if ( c.isValid() )
{
elements.append( c );
}
else
{
QString val = value( p ).toString();
QString n = name( p );
QString msg = n + " ('" + val + "') cannot be converted into a list of colors";
raiseError( msg );
}
}
return elements;
}
QStringList QgsWmsParameters::highlightLabelString() const
{
return toStringList( ParameterName::HIGHLIGHT_LABELSTRING, ';' );
}
QStringList QgsWmsParameters::highlightLabelSize() const
{
return toStringList( ParameterName::HIGHLIGHT_LABELSIZE, ';' );
}
QList<int> QgsWmsParameters::highlightLabelSizeAsInt() const
{
return toIntList( highlightLabelSize(), ParameterName::HIGHLIGHT_LABELSIZE );
}
QStringList QgsWmsParameters::highlightLabelColor() const
{
return toStringList( ParameterName::HIGHLIGHT_LABELCOLOR, ';' );
}
QList<QColor> QgsWmsParameters::highlightLabelColorAsColor() const
{
return toColorList( highlightLabelColor(), ParameterName::HIGHLIGHT_LABELCOLOR );
}
QStringList QgsWmsParameters::highlightLabelWeight() const
{
return toStringList( ParameterName::HIGHLIGHT_LABELWEIGHT, ';' );
}
QList<int> QgsWmsParameters::highlightLabelWeightAsInt() const
{
return toIntList( highlightLabelWeight(), ParameterName::HIGHLIGHT_LABELWEIGHT );
}
QStringList QgsWmsParameters::highlightLabelFont() const
{
return toStringList( ParameterName::HIGHLIGHT_LABELFONT, ';' );
}
QStringList QgsWmsParameters::highlightLabelBufferColor() const
{
return toStringList( ParameterName::HIGHLIGHT_LABELBUFFERCOLOR, ';' );
}
QList<QColor> QgsWmsParameters::highlightLabelBufferColorAsColor() const
{
return toColorList( highlightLabelBufferColor(), ParameterName::HIGHLIGHT_LABELBUFFERCOLOR );
}
QStringList QgsWmsParameters::highlightLabelBufferSize() const
{
return toStringList( ParameterName::HIGHLIGHT_LABELBUFFERSIZE, ';' );
}
QList<float> QgsWmsParameters::highlightLabelBufferSizeAsFloat() const
{
return toFloatList( highlightLabelBufferSize(), ParameterName::HIGHLIGHT_LABELBUFFERSIZE );
}
QString QgsWmsParameters::sld() const
{
return value( ParameterName::SLD ).toString();
}
QStringList QgsWmsParameters::filters() const
{
return toStringList( ParameterName::FILTER, ';' );
}
QStringList QgsWmsParameters::selections() const
{
return toStringList( ParameterName::SELECTION );
}
QStringList QgsWmsParameters::opacities() const
{
return toStringList( ParameterName::OPACITIES );
}
QList<int> QgsWmsParameters::opacitiesAsInt() const
{
return toIntList( opacities(), ParameterName::OPACITIES );
}
QStringList QgsWmsParameters::allLayersNickname() const
{
QStringList layer = toStringList( ParameterName::LAYER );
QStringList layers = toStringList( ParameterName::LAYERS );
return layer << layers;
}
QStringList QgsWmsParameters::allStyles() const
{
QStringList style = value( ParameterName::STYLE ).toString().split( ",", QString::SkipEmptyParts );
QStringList styles = value( ParameterName::STYLES ).toString().split( "," );
return style << styles;
}
QList<QgsWmsParametersLayer> QgsWmsParameters::layersParameters() const
{
QList<QgsWmsParametersLayer> parameters;
QStringList layers = allLayersNickname();
QStringList styles = allStyles();
QStringList filter = filters();
QStringList selection = selections();
QList<int> opacities = opacitiesAsInt();
// filter format: "LayerName:filterString;LayerName2:filterString2;..."
// several filters can be defined for one layer
QMultiMap<QString, QString> layerFilters;
Q_FOREACH ( QString f, filter )
{
QStringList splits = f.split( ":" );
if ( splits.size() == 2 )
{
layerFilters.insert( splits[0], splits[1] );
}
else
{
QString filterStr = value( ParameterName::FILTER ).toString();
raiseError( "FILTER ('" + filterStr + "') is not properly formatted" );
}
}
// selection format: "LayerName:id0,id1;LayerName2:id0,id1;..."
// several filters can be defined for one layer
QMultiMap<QString, QString> layerSelections;
Q_FOREACH ( QString s, selection )
{
QStringList splits = s.split( ":" );
if ( splits.size() == 2 )
{
layerSelections.insert( splits[0], splits[1] );
}
else
{
QString selStr = value( ParameterName::SELECTION ).toString();
raiseError( "SELECTION ('" + selStr + "') is not properly formatted" );
}
}
for ( int i = 0; i < layers.size(); i++ )
{
QString layer = layers[i];
QgsWmsParametersLayer param;
param.mNickname = layer;
if ( i < styles.count() )
param.mStyle = styles[i];
if ( i < opacities.count() )
param.mOpacity = opacities[i];
if ( layerFilters.contains( layer ) )
{
QMultiMap<QString, QString>::const_iterator it;
it = layerFilters.find( layer );
while ( it != layerFilters.end() && it.key() == layer )
{
param.mFilter.append( it.value() );
++it;
}
}
if ( layerSelections.contains( layer ) )
{
QMultiMap<QString, QString>::const_iterator it;
it = layerSelections.find( layer );
while ( it != layerSelections.end() && it.key() == layer )
{
param.mSelection << it.value().split( "," );
++it;
}
}
parameters.append( param );
}
return parameters;
}
QList<QgsWmsParametersHighlightLayer> QgsWmsParameters::highlightLayersParameters() const
{
QList<QgsWmsParametersHighlightLayer> params;
QList<QgsGeometry> geoms = highlightGeomAsGeom();
QStringList slds = highlightSymbol();
QStringList labels = highlightLabelString();
QList<QColor> colors = highlightLabelColorAsColor();
QList<int> sizes = highlightLabelSizeAsInt();
QList<int> weights = highlightLabelWeightAsInt();
QStringList fonts = highlightLabelFont();
QList<QColor> bufferColors = highlightLabelBufferColorAsColor();
QList<float> bufferSizes = highlightLabelBufferSizeAsFloat();
int nLayers = qMin( geoms.size(), slds.size() );
for ( int i = 0; i < nLayers; i++ )
{
QgsWmsParametersHighlightLayer param;
param.mName = "highlight_" + QString::number( i );
param.mGeom = geoms[i];
param.mSld = slds[i];
if ( i < labels.count() )
param.mLabel = labels[i];
if ( i < colors.count() )
param.mColor = colors[i];
if ( i < sizes.count() )
param.mSize = sizes[i];
if ( i < weights.count() )
param.mWeight = weights[i];
if ( i < fonts.count() )
param.mFont = fonts[ i ];
if ( i < bufferColors.count() )
param.mBufferColor = bufferColors[i];
if ( i < bufferSizes.count() )
param.mBufferSize = bufferSizes[i];
params.append( param );
}
return params;
}
QString QgsWmsParameters::name( ParameterName name ) const
{
const QMetaEnum metaEnum( QMetaEnum::fromType<ParameterName>() );
return metaEnum.valueToKey( name );
}
void QgsWmsParameters::log( const QString &msg ) const
{
QgsMessageLog::logMessage( msg, "Server", QgsMessageLog::INFO );
}
void QgsWmsParameters::raiseError( ParameterName paramName ) const
{
const QString value = mParameters[paramName].mValue.toString();
const QString param = name( paramName );
const QString type = QVariant::typeToName( mParameters[paramName].mType );
raiseError( param + " ('" + value + "') cannot be converted into " + type );
}
void QgsWmsParameters::raiseError( const QString &msg ) const
{
throw QgsBadRequestException( QStringLiteral( "Invalid WMS Parameter" ), msg );
}
}

View File

@ -0,0 +1,301 @@
/***************************************************************************
qgswmsparameters.h
------------------
begin : March 17, 2017
copyright : (C) 2017 by Paul Blottiere
email : paul dot blottiere at oslandia 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 QGSWMSPARAMETERS_H
#define QGSWMSPARAMETERS_H
#include <QMap>
#include <QObject>
#include <QMetaEnum>
#include <QColor>
#include "qgsrectangle.h"
#include "qgswmsserviceexception.h"
#include "qgsserverrequest.h"
#include "qgsgeometry.h"
/** QgsWmsParameters provides an interface to retrieve and manipulate WMS
* parameters received from the client.
* \since QGIS 3.0
*/
namespace QgsWms
{
struct QgsWmsParametersLayer
{
QString mNickname; // name, id or short name
int mOpacity = -1;
QStringList mFilter; // list of filter
QStringList mSelection; // list of string fid
QString mStyle;
};
struct QgsWmsParametersHighlightLayer
{
QString mName;
QgsGeometry mGeom;
QString mSld;
QString mLabel;
QColor mColor;
int mSize = 0;
int mWeight = 0;
QString mFont;
float mBufferSize = 0;
QColor mBufferColor;
};
class QgsWmsParameters
{
Q_GADGET
public:
enum ParameterName
{
CRS, // instead of SRS for WMS 1.3.0
// SRS, // for WMS 1.1.1
WIDTH,
HEIGHT,
BBOX,
LAYER,
LAYERS,
STYLE,
STYLES,
OPACITIES,
SLD,
FILTER,
SELECTION,
HIGHLIGHT_GEOM,
HIGHLIGHT_SYMBOL,
HIGHLIGHT_LABELSTRING,
HIGHLIGHT_LABELFONT,
HIGHLIGHT_LABELSIZE,
HIGHLIGHT_LABELWEIGHT,
HIGHLIGHT_LABELCOLOR,
HIGHLIGHT_LABELBUFFERCOLOR,
HIGHLIGHT_LABELBUFFERSIZE
};
Q_ENUM( ParameterName )
struct Parameter
{
ParameterName mName;
QVariant::Type mType;
QVariant mDefaultValue;
QVariant mValue;
};
/** Constructor.
* \param map of parameters where keys are parameters' names.
*/
QgsWmsParameters( const QgsServerRequest::Parameters &parameters );
/** Constructor.
*/
QgsWmsParameters();
/** Loads new parameters.
* \param map of parameters
*/
void load( const QgsServerRequest::Parameters &parameters );
/** Dumps parameters.
*/
void dump() const;
/** Returns CRS or an empty string if none is defined.
* \returns crs parameter as string
*/
QString crs() const;
/** Returns WIDTH parameter as an int (0 if not defined). An exception is
* raised if it cannot be converted.
* \returns width parameter
* \throws QgsBadRequestException
*/
int width() const;
/** Returns HEIGHT parameter as an int (0 if not defined). An exception
* is raised if it cannot be converted.
* \returns height parameter
* \throws QgsBadRequestException
*/
int height() const;
/** Returns BBOX if defined or an empty string.
* \returns bbox parameter
*/
QString bbox() const;
/** Returns BBOX as a rectangle if defined and valid. An exception is
* raised if the BBOX string cannot be converted into a rectangle.
* \returns bbox as rectangle
* \throws QgsBadRequestException
*/
QgsRectangle bboxAsRectangle() const;
/** Returns SLD if defined or an empty string.
* \returns sld
*/
QString sld() const;
/** Returns the list of feature selection found in SELECTION parameter.
* \returns the list of selection
*/
QStringList selections() const;
/** Returns the list of filters found in FILTER parameter.
* \returns the list of filter
*/
QStringList filters() const;
/** Returns the list of opacities found in OPACITIES parameter.
* \returns the list of opacities in string
*/
QStringList opacities() const;
/** Returns the list of opacities found in OPACITIES parameter as
* integers. If an opacity cannot be converted into an integer, then an
* exception is raised
* \returns a list of opacities as integers
* \throws QgsBadRequestException
*/
QList<int> opacitiesAsInt() const;
/** Returns nickname of layers found in LAYER and LAYERS parameters.
* \returns nickname of layers
*/
QStringList allLayersNickname() const;
/** Returns styles found in STYLE and STYLES parameters.
* \returns name of styles
*/
QStringList allStyles() const;
/** Returns parameters for each layer found in LAYER/LAYERS.
* \returns layer parameters
*/
QList<QgsWmsParametersLayer> layersParameters() const;
/** Returns parameters for each highlight layer.
* \returns parameters for each highlight layer
*/
QList<QgsWmsParametersHighlightLayer> highlightLayersParameters() const;
/** Returns HIGHLIGHT_GEOM as a list of string in WKT.
* \returns highlight geometries
*/
QStringList highlightGeom() const;
/** Returns HIGHLIGHT_GEOM as a list of geometries. An exception is
* raised if an invalid geometry is found.
* \returns highlight geometries
* \throws QgsBadRequestException
*/
QList<QgsGeometry> highlightGeomAsGeom() const;
/** Returns HIGHLIGHT_SYMBOL as a list of string.
* \returns highlight sld symbols
*/
QStringList highlightSymbol() const;
/** Returns HIGHLIGHT_LABELSTRING as a list of string.
* \returns highlight label string
*/
QStringList highlightLabelString() const;
/** Returns HIGHLIGHT_LABELCOLOR as a list of string.
* \returns highlight label color
*/
QStringList highlightLabelColor() const;
/** Returns HIGHLIGHT_LABELCOLOR as a list of color. An exception is
* raised if an invalid color is found.
* \returns highlight label color
* \throws QgsBadRequestException
*/
QList<QColor> highlightLabelColorAsColor() const;
/** Returns HIGHLIGHT_LABELSIZE as a list of string.
* \returns highlight label size
*/
QStringList highlightLabelSize() const;
/** Returns HIGHLIGHT_LABELSIZE as a list of int An exception is raised
* if an invalid size is found.
* \returns highlight label size
* \throws QgsBadRequestException
*/
QList<int> highlightLabelSizeAsInt() const;
/** Returns HIGHLIGHT_LABELWEIGHT as a list of string.
* \returns highlight label weight
*/
QStringList highlightLabelWeight() const;
/** Returns HIGHLIGHT_LABELWEIGHT as a list of int. An exception is
* raised if an invalid weight is found.
* \returns highlight label weight
* \throws QgsBadRequestException
*/
QList<int> highlightLabelWeightAsInt() const;
/** Returns HIGHLIGHT_LABELFONT
* \returns highlight label font
*/
QStringList highlightLabelFont() const;
/** Returns HIGHLIGHT_LABELBUFFERSIZE
* \returns highlight label buffer size
*/
QStringList highlightLabelBufferSize() const;
/** Returns HIGHLIGHT_LABELBUFFERSIZE as a list of float. An exception is
* raised if an invalid size is found.
* \returns highlight label buffer size
* \throws QgsBadRequestException
*/
QList<float> highlightLabelBufferSizeAsFloat() const;
/** Returns HIGHLIGHT_LABELBUFFERCOLOR as a list of string.
* \returns highlight label buffer color
*/
QStringList highlightLabelBufferColor() const;
/** Returns HIGHLIGHT_LABELBUFFERCOLOR as a list of colors. An axception
* is raised if an invalid color is found.
* \returns highlight label buffer color
* \throws QgsBadRequestException
*/
QList<QColor> highlightLabelBufferColorAsColor() const;
private:
QString name( ParameterName name ) const;
void raiseError( ParameterName name ) const;
void raiseError( const QString &msg ) const;
void initParameters();
QVariant value( ParameterName name ) const;
void log( const QString &msg ) const;
void save( const Parameter &parameter );
QStringList toStringList( ParameterName name, char delimiter = ',' ) const;
QList<int> toIntList( QStringList l, ParameterName name ) const;
QList<float> toFloatList( QStringList l, ParameterName name ) const;
QList<QColor> toColorList( QStringList l, ParameterName name ) const;
QgsServerRequest::Parameters mRequestParameters;
QMap<ParameterName, Parameter> mParameters;
};
}
#endif

View File

@ -28,6 +28,7 @@
#include "qgsfieldformatterregistry.h"
#include "qgsfeatureiterator.h"
#include "qgsgeometry.h"
#include "qgsmapserviceexception.h"
#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgslayertreemodellegendnode.h"
@ -58,6 +59,13 @@
#include "qgswmsserviceexception.h"
#include "qgsserverprojectutils.h"
#include "qgsgui.h"
#include "qgsmaplayerstylemanager.h"
#include "qgswkbtypes.h"
#include "qgsannotationmanager.h"
#include "qgsannotation.h"
#include "qgsvectorlayerlabeling.h"
#include "qgspallabeling.h"
#include "qgslayerrestorer.h"
#include <QImage>
#include <QPainter>
@ -109,6 +117,11 @@ namespace QgsWms
, mSettings( *serverIface->serverSettings() )
, mProject( project )
{
mWmsParameters.load( parameters );
mWmsParameters.dump();
initRestrictedLayers();
initNicknameLayers();
}
@ -256,7 +269,7 @@ namespace QgsWms
if ( contentBasedLegend )
{
HitTest hitTest;
getMap( contentBasedMapSettings, &hitTest );
getMapOld( contentBasedMapSettings, &hitTest );
Q_FOREACH ( QgsLayerTreeNode *node, rootGroup.children() )
{
@ -401,7 +414,7 @@ namespace QgsWms
}
void QgsRenderer::runHitTest( const QgsMapSettings &mapSettings, HitTest &hitTest )
void QgsRenderer::runHitTest( const QgsMapSettings &mapSettings, HitTest &hitTest ) const
{
QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
@ -426,7 +439,7 @@ namespace QgsWms
}
}
void QgsRenderer::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, QgsRenderContext &context )
void QgsRenderer::runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, QgsRenderContext &context ) const
{
QgsFeatureRenderer *r = vl->renderer();
bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRenderer::MoreSymbolsPerFeature;
@ -693,13 +706,13 @@ namespace QgsWms
}
#endif
QImage *QgsRenderer::getMap( HitTest *hitTest )
QImage *QgsRenderer::getMapOld( HitTest *hitTest )
{
QgsMapSettings mapSettings;
return getMap( mapSettings, hitTest );
return getMapOld( mapSettings, hitTest );
}
QImage *QgsRenderer::getMap( QgsMapSettings &mapSettings, HitTest *hitTest )
QImage *QgsRenderer::getMapOld( QgsMapSettings &mapSettings, HitTest *hitTest )
{
if ( !checkMaximumWidthHeight() )
{
@ -792,6 +805,101 @@ namespace QgsWms
return image;
}
QImage *QgsRenderer::getMap( HitTest *hitTest )
{
QgsMapSettings mapSettings;
return getMap( mapSettings, hitTest );
}
QImage *QgsRenderer::getMap( QgsMapSettings &mapSettings, HitTest *hitTest )
{
// check size
if ( !checkMaximumWidthHeight() )
{
throw QgsBadRequestException( QStringLiteral( "Size error" ),
QStringLiteral( "The requested map size is too large" ) );
}
// get layers parameters
QList<QgsMapLayer *> layers;
QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
// init layer restorer before doing anything
std::unique_ptr<QgsLayerRestorer> restorer;
restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
// init stylized layers according to LAYERS/STYLES or SLD
QString sld = mWmsParameters.sld();
if ( !sld.isEmpty() )
{
layers = sldStylizedLayers( sld );
}
else
{
layers = stylizedLayers( params );
}
// remove unwanted layers (restricted layers, ...)
removeUnwantedLayers( layers );
// configure each layer with opacity, selection filter, ...
bool updateMapExtent = mWmsParameters.bbox().isEmpty() ? true : false;
Q_FOREACH ( QgsMapLayer *layer, layers )
{
Q_FOREACH ( QgsWmsParametersLayer param, params )
{
if ( param.mNickname == layerNickname( *layer ) )
{
checkLayerReadPermissions( layer );
setLayerOpacity( layer, param.mOpacity );
setLayerFilter( layer, param.mFilter );
setLayerAccessControlFilter( layer );
setLayerSelection( layer, param.mSelection );
if ( updateMapExtent )
updateExtent( layer, mapSettings );
break;
}
}
}
// add highlight layers above others
layers = layers << highlightLayers();
// create the output image and the painter
std::unique_ptr<QPainter> painter;
std::unique_ptr<QImage> image( createImage() );
// configure map settings (background, DPI, ...)
configureMapSettings( image.get(), mapSettings );
// add layers to map settings (revert order for the rendering)
std::reverse( layers.begin(), layers.end() );
mapSettings.setLayers( layers );
// rendering step for layers
painter.reset( layersRendering( mapSettings, *image.get(), hitTest ) );
// rendering step for annotations
annotationsRendering( painter.get() );
// painting is terminated
painter->end();
// scale output image if necessary (required by WMS spec)
QImage *scaledImage = scaleImage( image.get() );
if ( scaledImage )
image.reset( scaledImage );
// return
return image.release();
}
static void infoPointToMapCoordinates( int i, int j, QgsPoint *infoPoint, const QgsMapSettings &mapSettings )
{
//check if i, j are in the pixel range of the image
@ -1201,9 +1309,8 @@ namespace QgsWms
QString version = mParameters.value( QStringLiteral( "VERSION" ), QStringLiteral( "1.3.0" ) );
if ( useBbox && version != QLatin1String( "1.1.1" ) )
{
QString bboxStr = mParameters.value( "BBOX" );
QgsRectangle mapExtent = parseBbox( bboxStr );
if ( !bboxStr.isEmpty() && mapExtent.isEmpty() )
QgsRectangle mapExtent = mWmsParameters.bboxAsRectangle();
if ( !mWmsParameters.bbox().isEmpty() && mapExtent.isEmpty() )
{
throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ),
QStringLiteral( "Invalid BBOX parameter" ) );
@ -1305,13 +1412,10 @@ namespace QgsWms
mapSettings.setOutputDpi( paintDevice->logicalDpiX() );
//map extent
QgsRectangle mapExtent;
QString bboxStr = mParameters.value( "BBOX" );
if ( !bboxStr.isEmpty() )
QgsRectangle mapExtent = mWmsParameters.bboxAsRectangle();
if ( !mWmsParameters.bbox().isEmpty() && mapExtent.isEmpty() )
{
mapExtent = parseBbox( bboxStr );
if ( mapExtent.isEmpty() )
throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ), QStringLiteral( "Invalid BBOX parameter" ) );
throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ), QStringLiteral( "Invalid BBOX parameter" ) );
}
QString crs = mParameters.value( QStringLiteral( "CRS" ), mParameters.value( QStringLiteral( "SRS" ) ) );
@ -1807,7 +1911,6 @@ namespace QgsWms
return layerKeys;
}
void QgsRenderer::applyRequestedLayerFilters( const QStringList &layerList, QgsMapSettings &mapSettings, QHash<QgsMapLayer *, QString> &originalFilters ) const
{
if ( layerList.isEmpty() )
@ -2246,30 +2349,19 @@ namespace QgsWms
{
//test if maxWidth / maxHeight set and WIDTH / HEIGHT parameter is in the range
int wmsMaxWidth = QgsServerProjectUtils::wmsMaxWidth( *mProject );
if ( wmsMaxWidth != -1 )
int width = mWmsParameters.width();
if ( wmsMaxWidth != -1 && width != -1 && width > wmsMaxWidth )
{
QMap<QString, QString>::const_iterator widthIt = mParameters.find( QStringLiteral( "WIDTH" ) );
if ( widthIt != mParameters.constEnd() )
{
if ( widthIt->toInt() > wmsMaxWidth )
{
return false;
}
}
return false;
}
int wmsMaxHeight = QgsServerProjectUtils::wmsMaxHeight( *mProject );
if ( wmsMaxHeight != -1 )
int height = mWmsParameters.height();
if ( wmsMaxHeight != -1 && height != -1 && height > wmsMaxHeight )
{
QMap<QString, QString>::const_iterator heightIt = mParameters.find( QStringLiteral( "HEIGHT" ) );
if ( heightIt != mParameters.constEnd() )
{
if ( heightIt->toInt() > wmsMaxHeight )
{
return false;
}
}
return false;
}
return true;
}
@ -2639,5 +2731,460 @@ namespace QgsWms
}
} // namespace QgsWms
void QgsRenderer::initRestrictedLayers()
{
mRestrictedLayers.clear();
// get name of restricted layers/groups in project
QStringList restricted = QgsServerProjectUtils::wmsRestrictedLayers( *mProject );
// extract restricted layers from excluded groups
QStringList restrictedLayersNames;
QgsLayerTreeGroup *root = mProject->layerTreeRoot();
Q_FOREACH ( QString l, restricted )
{
QgsLayerTreeGroup *group = root->findGroup( l );
if ( group )
{
QList<QgsLayerTreeLayer *> groupLayers = group->findLayers();
Q_FOREACH ( QgsLayerTreeLayer *treeLayer, groupLayers )
{
restrictedLayersNames.append( treeLayer->name() );
}
}
else
{
restrictedLayersNames.append( l );
}
}
// build output with names, ids or short name according to the configuration
QList<QgsLayerTreeLayer *> layers = root->findLayers();
Q_FOREACH ( QgsLayerTreeLayer *layer, layers )
{
if ( restrictedLayersNames.contains( layer->name() ) )
{
mRestrictedLayers.append( layerNickname( *layer->layer() ) );
}
}
}
void QgsRenderer::initNicknameLayers()
{
Q_FOREACH ( QgsMapLayer *ml, mProject->mapLayers() )
{
mNicknameLayers[ layerNickname( *ml ) ] = ml;
}
}
QString QgsRenderer::layerNickname( const QgsMapLayer &layer ) const
{
QString name = layer.shortName();
if ( QgsServerProjectUtils::wmsUseLayerIds( *mProject ) )
{
name = layer.id();
}
else if ( name.isEmpty() )
{
name = layer.name();
}
return name;
}
bool QgsRenderer::layerScaleVisibility( const QgsMapLayer &layer, double scaleDenominator ) const
{
bool visible = false;
bool scaleBasedVisibility = layer.hasScaleBasedVisibility();
bool useScaleConstraint = ( scaleDenominator > 0 && scaleBasedVisibility );
if ( !useScaleConstraint || layer.isInScaleRange( scaleDenominator ) )
{
visible = true;
}
return visible;
}
QList<QgsMapLayer *> QgsRenderer::highlightLayers()
{
QList<QgsMapLayer *> highlightLayers;
// try to create highlight layer for each geometry
QList<QgsWmsParametersHighlightLayer> params = mWmsParameters.highlightLayersParameters();
QString crs = mWmsParameters.crs();
Q_FOREACH ( QgsWmsParametersHighlightLayer param, params )
{
// create sld document from symbology
QDomDocument sldDoc;
if ( !sldDoc.setContent( param.mSld, true ) )
{
continue;
}
// create renderer from sld document
QString errorMsg;
std::unique_ptr<QgsFeatureRenderer> renderer;
QDomElement el = sldDoc.documentElement();
renderer.reset( QgsFeatureRenderer::loadSld( el, param.mGeom.type(), errorMsg ) );
if ( !renderer )
{
QgsMessageLog::logMessage( errorMsg, "Server", QgsMessageLog::INFO );
continue;
}
// build url for vector layer
QString typeName = QgsWkbTypes::geometryDisplayString( param.mGeom.type() );
QString url = typeName + "?crs=" + crs;
if ( ! param.mLabel.isEmpty() )
{
url += "&field=label:string";
}
// create vector layer
std::unique_ptr<QgsVectorLayer> layer;
layer.reset( new QgsVectorLayer( url, param.mName, "memory" ) );
if ( !layer->isValid() )
{
continue;
}
// create feature with label if necessary
QgsFeature fet( layer->pendingFields() );
if ( ! param.mLabel.isEmpty() )
{
fet.setAttribute( 0, param.mLabel );
// init labeling engine
QgsPalLayerSettings palSettings;
palSettings.fieldName = "label"; // defined in url
palSettings.priority = 10; // always drawn
palSettings.displayAll = true;
QgsPalLayerSettings::Placement placement = QgsPalLayerSettings::AroundPoint;
switch ( param.mGeom.type() )
{
case QgsWkbTypes::PointGeometry:
{
placement = QgsPalLayerSettings::AroundPoint;
palSettings.dist = 2; // in mm
palSettings.placementFlags = 0;
break;
}
case QgsWkbTypes::PolygonGeometry:
{
QgsGeometry point = param.mGeom.pointOnSurface();
QgsPoint pt = point.asPoint();
placement = QgsPalLayerSettings::AroundPoint;
QgsPalLayerSettings::Property pX = QgsPalLayerSettings::PositionX;
QVariant x( pt.x() );
palSettings.dataDefinedProperties().setProperty( pX, x );
QgsPalLayerSettings::Property pY = QgsPalLayerSettings::PositionY;
QVariant y( pt.y() );
palSettings.dataDefinedProperties().setProperty( pY, y );
QgsPalLayerSettings::Property pHali = QgsPalLayerSettings::Hali;
QVariant hali( "Center" );
palSettings.dataDefinedProperties().setProperty( pHali, hali );
QgsPalLayerSettings::Property pVali = QgsPalLayerSettings::Vali;
QVariant vali( "Half" );
palSettings.dataDefinedProperties().setProperty( pVali, vali );
break;
}
default:
{
placement = QgsPalLayerSettings::Line;
palSettings.dist = 2;
palSettings.placementFlags = 10;
break;
}
}
palSettings.placement = placement;
QgsTextFormat textFormat;
QgsTextBufferSettings bufferSettings;
if ( param.mColor.isValid() )
{
textFormat.setColor( param.mColor );
}
if ( param.mSize > 0 )
{
textFormat.setSize( param.mSize );
}
// no weight property in PAL settings or QgsTextFormat
/* if ( param.fontWeight > 0 )
{
} */
if ( ! param.mFont.isEmpty() )
{
textFormat.setFont( param.mFont );
}
if ( param.mBufferColor.isValid() )
{
bufferSettings.setColor( param.mBufferColor );
}
if ( param.mBufferSize > 0 )
{
bufferSettings.setEnabled( true );
bufferSettings.setSize( param.mBufferSize );
}
textFormat.setBuffer( bufferSettings );
palSettings.setFormat( textFormat );
QgsVectorLayerSimpleLabeling *simpleLabeling = new QgsVectorLayerSimpleLabeling( palSettings );
layer->setLabeling( simpleLabeling );
}
fet.setGeometry( param.mGeom );
// add feature to layer and set the SLD renderer
layer->dataProvider()->addFeatures( QgsFeatureList() << fet );
layer->setRenderer( renderer.release() );
// keep the vector as an highlight layer
if ( layer->isValid() )
{
highlightLayers.append( layer.release() );
}
}
return highlightLayers;
}
QList<QgsMapLayer *> QgsRenderer::sldStylizedLayers( const QString &sld ) const
{
QList<QgsMapLayer *> layers;
if ( !sld.isEmpty() )
{
QDomDocument doc;
doc.setContent( sld, true );
QDomElement docEl = doc.documentElement();
QDomElement root = doc.firstChildElement( "StyledLayerDescriptor" );
QDomElement namedElem = root.firstChildElement( "NamedLayer" );
if ( !docEl.isNull() )
{
QDomNodeList named = docEl.elementsByTagName( "NamedLayer" );
for ( int i = 0; i < named.size(); ++i )
{
QDomNodeList names = named.item( i ).toElement().elementsByTagName( "Name" );
if ( !names.isEmpty() )
{
QString lname = names.item( 0 ).toElement().text();
QString err;
if ( mNicknameLayers.contains( lname ) && !mRestrictedLayers.contains( lname ) )
{
mNicknameLayers[lname]->readSld( namedElem, err );
mNicknameLayers[lname]->setCustomProperty( "readSLD", true );
layers.append( mNicknameLayers[lname] );
}
}
}
}
}
return layers;
}
QList<QgsMapLayer *> QgsRenderer::stylizedLayers( const QList<QgsWmsParametersLayer> &params ) const
{
QList<QgsMapLayer *> layers;
Q_FOREACH ( QgsWmsParametersLayer param, params )
{
QString nickname = param.mNickname;
QString style = param.mStyle;
if ( mNicknameLayers.contains( nickname ) )
{
if ( !style.isEmpty() )
{
bool rc = mNicknameLayers[nickname]->styleManager()->setCurrentStyle( style );
if ( ! rc )
{
throw QgsMapServiceException( QStringLiteral( "StyleNotDefined" ), QStringLiteral( "Style \"%1\" does not exist for layer \"%2\"" ).arg( style, nickname ) );
}
}
layers.append( mNicknameLayers[nickname] );
}
}
return layers;
}
QPainter *QgsRenderer::layersRendering( const QgsMapSettings &mapSettings, QImage &image, HitTest *hitTest ) const
{
QPainter *painter;
if ( hitTest )
{
runHitTest( mapSettings, *hitTest );
painter = new QPainter();
}
else
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
mAccessControl->resolveFilterFeatures( mapSettings.layers() );
#endif
QgsMapRendererJobProxy renderJob( mSettings.parallelRendering(), mSettings.maxThreads(), mAccessControl );
renderJob.render( mapSettings, &image );
painter = renderJob.takePainter();
}
return painter;
}
void QgsRenderer::setLayerOpacity( QgsMapLayer *layer, int opacity ) const
{
if ( opacity >= 0 && opacity <= 255 )
{
if ( layer->type() == QgsMapLayer::LayerType::VectorLayer )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
vl->setOpacity( opacity / 255. );
}
else if ( layer->type() == QgsMapLayer::LayerType::RasterLayer )
{
QgsRasterLayer *rl = qobject_cast<QgsRasterLayer *>( layer );
QgsRasterRenderer *rasterRenderer = rl->renderer();
rasterRenderer->setOpacity( opacity / 255. );
}
}
}
void QgsRenderer::setLayerFilter( QgsMapLayer *layer, const QStringList &filters ) const
{
if ( layer->type() == QgsMapLayer::VectorLayer )
{
QgsVectorLayer *filteredLayer = qobject_cast<QgsVectorLayer *>( layer );
Q_FOREACH ( QString filter, filters )
{
if ( !testFilterStringSafety( filter ) )
{
throw QgsBadRequestException( QStringLiteral( "Filter string rejected" ),
QStringLiteral( "The filter string %1"
" has been rejected because of security reasons."
" Note: Text strings have to be enclosed in single or double quotes."
" A space between each word / special character is mandatory."
" Allowed Keywords and special characters are "
" AND,OR,IN,<,>=,>,>=,!=,',',(,),DMETAPHONE,SOUNDEX."
" Not allowed are semicolons in the filter expression." ).arg( filter ) );
}
QString newSubsetString = filter;
if ( !filteredLayer->subsetString().isEmpty() )
{
newSubsetString.prepend( ") AND (" );
newSubsetString.append( ")" );
newSubsetString.prepend( filteredLayer->subsetString() );
newSubsetString.prepend( "(" );
}
filteredLayer->setSubsetString( newSubsetString );
}
}
}
void QgsRenderer::setLayerSelection( QgsMapLayer *layer, const QStringList &fids ) const
{
if ( layer->type() == QgsMapLayer::VectorLayer )
{
QgsFeatureIds selectedIds;
Q_FOREACH ( const QString &id, fids )
{
selectedIds.insert( STRING_TO_FID( id ) );
}
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
vl->selectByIds( selectedIds );
}
}
void QgsRenderer::setLayerAccessControlFilter( QgsMapLayer *layer ) const
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
QgsOWSServerFilterRestorer::applyAccessControlLayerFilters( mAccessControl, layer );
#else
Q_UNUSED( layer );
#endif
}
void QgsRenderer::updateExtent( const QgsMapLayer *layer, QgsMapSettings &mapSettings ) const
{
QgsRectangle layerExtent = mapSettings.layerToMapCoordinates( layer, layer->extent() );
QgsRectangle mapExtent = mapSettings.extent();
if ( !layerExtent.isEmpty() )
{
mapExtent.combineExtentWith( layerExtent );
mapSettings.setExtent( mapExtent );
}
}
void QgsRenderer::annotationsRendering( QPainter *painter ) const
{
const QgsAnnotationManager *annotationManager = mProject->annotationManager();
QList< QgsAnnotation * > annotations = annotationManager->annotations();
QgsRenderContext renderContext = QgsRenderContext::fromQPainter( painter );
Q_FOREACH ( QgsAnnotation *annotation, annotations )
{
annotation->render( renderContext );
}
}
QImage *QgsRenderer::scaleImage( const QImage *image ) const
{
//test if width / height ratio of image is the same as the ratio of
// WIDTH / HEIGHT parameters. If not, the image has to be scaled (required
// by WMS spec)
QImage *scaledImage = nullptr;
int width = mWmsParameters.width();
int height = mWmsParameters.height();
if ( width != image->width() || height != image->height() )
{
scaledImage = new QImage( image->scaled( width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
}
return scaledImage;
}
void QgsRenderer::checkLayerReadPermissions( QgsMapLayer *layer ) const
{
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( layer ) )
{
throw QgsSecurityException( QStringLiteral( "You are not allowed to access to the layer: %1" ).arg( layer->name() ) );
}
#else
Q_UNUSED( layer );
#endif
}
void QgsRenderer::removeUnwantedLayers( QList<QgsMapLayer *> &layers, double scaleDenominator ) const
{
QList<QgsMapLayer *> wantedLayers;
Q_FOREACH ( QgsMapLayer *layer, layers )
{
if ( !layerScaleVisibility( *layer, scaleDenominator ) )
continue;
if ( mRestrictedLayers.contains( layerNickname( *layer ) ) )
continue;
wantedLayers.append( layer );
}
layers = wantedLayers;
}
} // namespace QgsWms

View File

@ -22,6 +22,7 @@
#include "qgswmsconfigparser.h"
#include "qgsserversettings.h"
#include "qgswmsparameters.h"
#include <QDomDocument>
#include <QMap>
#include <QPair>
@ -93,10 +94,12 @@ namespace QgsWms
of the image object). If an instance to existing hit test structure is passed, instead of rendering
it will fill the structure with symbols that would be used for rendering */
QImage *getMap( HitTest *hitTest = nullptr );
QImage *getMapOld( HitTest *hitTest = nullptr );
/** Identical to getMap( HitTest* hitTest ) and updates the map settings actually used.
\since QGIS 3.0 */
QImage *getMap( QgsMapSettings &mapSettings, HitTest *hitTest = nullptr );
QImage *getMapOld( QgsMapSettings &mapSettings, HitTest *hitTest = nullptr );
/** Returns printed page as binary
@ -111,6 +114,59 @@ namespace QgsWms
private:
// Init the restricted layers with nicknames
void initRestrictedLayers();
// Build and returns highlight layers
QList<QgsMapLayer *> highlightLayers();
// Init a map with nickname for layers' project
void initNicknameLayers();
// Return the nickname of the layer (short name, id or name according to
// the project configuration)
QString layerNickname( const QgsMapLayer &layer ) const;
// Return true if the layer has to be displayed according to he current
// scale
bool layerScaleVisibility( const QgsMapLayer &layer, double scaleDenominator ) const;
// Remove unwanted layers (restricted, not visible, etc)
void removeUnwantedLayers( QList<QgsMapLayer *> &layers, double scaleDenominator = -1 ) const;
// Rendering step for layers
QPainter *layersRendering( const QgsMapSettings &mapSettings, QImage &image, HitTest *hitTest = nullptr ) const;
// Rendering step for annotations
void annotationsRendering( QPainter *painter ) const;
// Return a list of layers stylized with LAYERS/STYLES parameters
QList<QgsMapLayer *> stylizedLayers( const QList<QgsWmsParametersLayer> &params ) const;
// Return a list of layers stylized with SLD parameter
QList<QgsMapLayer *> sldStylizedLayers( const QString &sld ) const;
// Set layer opacity
void setLayerOpacity( QgsMapLayer *layer, int opacity ) const;
// Set layer filter
void setLayerFilter( QgsMapLayer *layer, const QStringList &filter ) const;
// Set layer python filter
void setLayerAccessControlFilter( QgsMapLayer *layer ) const;
// Set layer selection
void setLayerSelection( QgsMapLayer *layer, const QStringList &fids ) const;
// Combine map extent with layer extent
void updateExtent( const QgsMapLayer *layer, QgsMapSettings &mapSettings ) const;
// Scale image with WIDTH/HEIGHT if necessary
QImage *scaleImage( const QImage *image ) const;
// Check layer read permissions
void checkLayerReadPermissions( QgsMapLayer *layer ) const;
/** Initializes WMS layers and configures rendering.
* \param layersList out: list with WMS layer names
* \param stylesList out: list with WMS style names
@ -170,9 +226,9 @@ namespace QgsWms
QStringList layerSet( const QStringList &layersList, const QStringList &stylesList, const QgsCoordinateReferenceSystem &destCRS, double scaleDenominator = -1 ) const;
//! Record which symbols would be used if the map was in the current configuration of renderer. This is useful for content-based legend
void runHitTest( const QgsMapSettings &mapSettings, HitTest &hitTest );
void runHitTest( const QgsMapSettings &mapSettings, HitTest &hitTest ) const;
//! Record which symbols within one layer would be rendered with the given renderer context
void runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, QgsRenderContext &context );
void runHitTestLayer( QgsVectorLayer *vl, SymbolSet &usedSymbols, QgsRenderContext &context ) const;
//! Read legend parameter from the request or from the first print composer in the project
void legendParameters( double &boxSpace, double &layerSpace, double &layerTitleSpace,
@ -263,6 +319,9 @@ namespace QgsWms
const QgsServerSettings &mSettings;
const QgsProject *mProject = nullptr;
QgsWmsParameters mWmsParameters;
QStringList mRestrictedLayers;
QMap<QString, QgsMapLayer *> mNicknameLayers;
public:

View File

@ -55,6 +55,7 @@ RE_ATTRIBUTES = b'[^>\s]+=[^>\s]+'
class QgsServerTestBase(unittest.TestCase):
"""Base class for QGIS server tests"""
# Set to True in child classes to re-generate reference files for this class
@ -97,6 +98,9 @@ class QgsServerTestBase(unittest.TestCase):
d = unitTestDataPath('qgis_server_accesscontrol') + '/'
self.projectPath = os.path.join(d, "project.qgs")
self.projectAnnotationPath = os.path.join(d, "project_with_annotations.qgs")
self.projectStatePath = os.path.join(d, "project_state.qgs")
self.projectUseLayerIdsPath = os.path.join(d, "project_use_layerids.qgs")
# Clean env just to be sure
env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
@ -187,14 +191,14 @@ class QgsServerTestBase(unittest.TestCase):
self.server.handleRequest(request, response)
headers = []
rh = response.headers()
rk = list(rh.keys())
rk.sort()
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())
class TestQgsServer(QgsServerTestBase):
"""Tests container"""
# Set to True to re-generate reference files for this class

View File

@ -142,8 +142,8 @@ class RestrictedAccessControl(QgsAccessControlFilter):
if not self._active:
return super(RestrictedAccessControl, self).authorizedLayerAttributes(layer, attributes)
if "colour" in attributes: # spellok
attributes.remove("colour") # spellok
if "color" in attributes: # spellok
attributes.remove("color") # spellok
return attributes
def allowToEdit(self, layer, feature):
@ -169,8 +169,7 @@ class TestQgsServerAccessControl(unittest.TestCase):
cls._server.handleRequest(request, response)
headers = []
rh = response.headers()
rk = list(rh.keys())
rk.sort()
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())
@ -250,7 +249,7 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<TreeName>Country</TreeName>") != -1,
"No Country layer in GetProjectSettings\n%s" % response)
self.assertTrue(
str(response).find("<LayerDrawingOrder>Country_Labels,Country,dem,Hello_Filter_SubsetString,Hello_Project_SubsetString,Hello_SubsetString,Hello,db_point</LayerDrawingOrder>") != -1,
str(response).find("<LayerDrawingOrder>Country_Diagrams,Country_Labels,Country,dem,Hello_Filter_SubsetString,Hello_Project_SubsetString,Hello_SubsetString,Hello,db_point</LayerDrawingOrder>") != -1,
"LayerDrawingOrder in GetProjectSettings\n%s" % response)
response, headers = self._get_restricted(query_string)
@ -261,7 +260,7 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<TreeName>Country</TreeName>") != -1,
"Country layer in GetProjectSettings\n%s" % response)
self.assertTrue(
str(response).find("<LayerDrawingOrder>Country_Labels,dem,Hello_Filter_SubsetString,Hello_Project_SubsetString,Hello_SubsetString,Hello,db_point</LayerDrawingOrder>") != -1,
str(response).find("<LayerDrawingOrder>Country_Diagrams,Country_Labels,dem,Hello_Filter_SubsetString,Hello_Project_SubsetString,Hello_SubsetString,Hello,db_point</LayerDrawingOrder>") != -1,
"LayerDrawingOrder in GetProjectSettings\n%s" % response)
def test_wms_getprojectsettings(self):
@ -451,7 +450,7 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<qgs:pk>1</qgs:pk>") != -1,
"No result in GetFeatureInfo\n%s" % response)
self.assertTrue(
str(response).find("<qgs:colour>red</qgs:colour>") != -1, # spellok
str(response).find("<qgs:color>red</qgs:color>") != -1, # spellok
"No color in result of GetFeatureInfo\n%s" % response)
response, headers = self._get_restricted(query_string)
@ -459,10 +458,10 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<qgs:pk>1</qgs:pk>") != -1,
"No result in GetFeatureInfo\n%s" % response)
self.assertFalse(
str(response).find("<qgs:colour>red</qgs:colour>") != -1, # spellok
str(response).find("<qgs:color>red</qgs:color>") != -1, # spellok
"Unexpected color in result of GetFeatureInfo\n%s" % response)
self.assertFalse(
str(response).find("<qgs:colour>NULL</qgs:colour>") != -1, # spellok
str(response).find("<qgs:color>NULL</qgs:color>") != -1, # spellok
"Unexpected color NULL in result of GetFeatureInfo\n%s" % response)
def test_wms_getfeatureinfo_hello2(self):
@ -603,7 +602,7 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<qgs:pk>1</qgs:pk>") != -1,
"No result in GetFeature\n%s" % response)
self.assertTrue(
str(response).find("<qgs:colour>red</qgs:colour>") != -1, # spellok
str(response).find("<qgs:color>red</qgs:color>") != -1, # spellok
"No color in result of GetFeature\n%s" % response)
response, headers = self._post_restricted(data)
@ -611,10 +610,10 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<qgs:pk>1</qgs:pk>") != -1,
"No result in GetFeature\n%s" % response)
self.assertFalse(
str(response).find("<qgs:colour>red</qgs:colour>") != -1, # spellok
str(response).find("<qgs:color>red</qgs:color>") != -1, # spellok
"Unexpected color in result of GetFeature\n%s" % response)
self.assertFalse(
str(response).find("<qgs:colour>NULL</qgs:colour>") != -1, # spellok
str(response).find("<qgs:color>NULL</qgs:color>") != -1, # spellok
"Unexpected color NULL in result of GetFeature\n%s" % response)
def test_wfs_getfeature_hello2(self):

View File

@ -27,9 +27,12 @@ class TestQgsServerProjectUtils(unittest.TestCase):
self.testdata_path = unitTestDataPath('qgis_server_project') + '/'
self.prj = QgsProject()
prjPath = os.path.join(self.testdata_path, "project.qgs")
self.prj.setFileName(prjPath)
self.prj.read()
self.prjPath = os.path.join(self.testdata_path, "project.qgs")
self.prj.read(self.prjPath)
self.prj2 = QgsProject()
self.prj2Path = os.path.join(self.testdata_path, "project2.qgs")
self.prj2.read(self.prj2Path)
def tearDown(self):
pass
@ -43,6 +46,40 @@ class TestQgsServerProjectUtils(unittest.TestCase):
self.assertEqual(QgsServerProjectUtils.wcsServiceUrl(self.prj), "my_wcs_advertised_url")
self.assertEqual(QgsServerProjectUtils.wfsServiceUrl(self.prj), "my_wfs_advertised_url")
def test_wmsuselayerids(self):
self.assertEqual(QgsServerProjectUtils.wmsUseLayerIds(self.prj), False)
self.assertEqual(QgsServerProjectUtils.wmsUseLayerIds(self.prj2), True)
def test_wmsrestrictedlayers(self):
# retrieve entry from project
result = QgsServerProjectUtils.wmsRestrictedLayers(self.prj)
expected = []
expected.append('points') # layer
expected.append('group1') # local group
expected.append('groupEmbedded') # embedded group
self.assertListEqual(sorted(expected), sorted(result))
def test_wfslayersids(self):
# retrieve entry from project
result = QgsServerProjectUtils.wfsLayerIds(self.prj)
expected = []
expected.append('multipoint20170309173637804') # from embedded group
expected.append('points20170309173738552') # local layer
expected.append('polys20170309173913723') # from local group
self.assertEqual(expected, result)
def test_wcslayersids(self):
# retrieve entry from project
result = QgsServerProjectUtils.wcsLayerIds(self.prj)
expected = []
expected.append('landsat20170313142548073')
self.assertEqual(expected, result)
if __name__ == '__main__':
unittest.main()

View File

@ -39,6 +39,7 @@ RE_ATTRIBUTES = b'[^>\s]+=[^>\s]+'
class TestQgsServerWMS(QgsServerTestBase):
"""QGIS Server WMS Tests"""
# Set to True to re-generate reference files for this class
@ -175,6 +176,293 @@ class TestQgsServerWMS(QgsServerTestBase):
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Basic")
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,dem",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Basic2")
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectUseLayerIdsPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "country20131022151106556",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Basic3")
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,db_point",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Basic4")
def test_wms_getmap_invalid_parameters(self):
# height should be an int
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "FOO",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"HEIGHT (\'FOO\') cannot be converted into int" in r
self.assertTrue(err)
# width should be an int
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "FOO",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"WIDTH (\'FOO\') cannot be converted into int" in r
self.assertTrue(err)
# bbox should be formatted like "double,double,double,double"
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"BBOX (\'-16817707,-4710778,5696513\') cannot be converted into a rectangle" in r
self.assertTrue(err)
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,FOO",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"BBOX (\'-16817707,-4710778,5696513,FOO\') cannot be converted into a rectangle" in r
self.assertTrue(err)
# opacities should be a list of int
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"OPACITIES": "253,FOO",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"OPACITIES (\'253,FOO\') cannot be converted into a list of int" in r
self.assertTrue(err)
# filters should be formatted like "layer0:filter0;layer1:filter1"
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857",
"FILTER": "Country \"name\" = 'eurasia'"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"FILTER (\'Country \"name\" = \'eurasia\'\') is not properly formatted" in r
self.assertTrue(err)
# selections should be formatted like "layer0:id0,id1;layer1:id0"
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"SRS": "EPSG:3857",
"SELECTION": "Country=4"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"SELECTION (\'Country=4\') is not properly formatted" in r
self.assertTrue(err)
# invalid highlight geometries
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country_Labels",
"HIGHLIGHT_GEOM": "POLYGONN((-15000000 10000000, -15000000 6110620, 2500000 6110620, 2500000 10000000, -15000000 10000000))",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"HIGHLIGHT_GEOM (\'POLYGONN((-15000000 10000000, -15000000 6110620, 2500000 6110620, 2500000 10000000, -15000000 10000000))\') cannot be converted into a list of geometries" in r
self.assertTrue(err)
# invalid highlight label colors
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country_Labels",
"HIGHLIGHT_LABELCOLOR": "%2300230000;%230023000",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"HIGHLIGHT_LABELCOLOR (\'#00230000;#0023000\') cannot be converted into a list of colors" in r
self.assertTrue(err)
# invalid list of label sizes
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country_Labels",
"HIGHLIGHT_LABELSIZE": "16;17;FOO",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"HIGHLIGHT_LABELSIZE (\'16;17;FOO\') cannot be converted into a list of int" in r
self.assertTrue(err)
# invalid list of label buffer size
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country_Labels",
"HIGHLIGHT_LABELBUFFERSIZE": "1.5;2;FF",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"HIGHLIGHT_LABELBUFFERSIZE (\'1.5;2;FF\') cannot be converted into a list of float" in r
self.assertTrue(err)
# invalid buffer color
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country_Labels",
"HIGHLIGHT_LABELBUFFERCOLOR": "%232300FF00;%232300FF0",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
err = b"HIGHLIGHT_LABELBUFFERCOLOR (\'#2300FF00;#2300FF0\') cannot be converted into a list of colors" in r
self.assertTrue(err)
def test_wms_getmap_transparent(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
@ -400,6 +688,43 @@ class TestQgsServerWMS(QgsServerTestBase):
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Filter")
# try to display a feature yet filtered by the project
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectStatePath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857",
"FILTER": "Country:\"name\" = 'africa'"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Filter2")
# display all features to check that initial filter is restored
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectStatePath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857",
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Filter3")
def test_wms_getmap_selection(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
@ -419,6 +744,24 @@ class TestQgsServerWMS(QgsServerTestBase):
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Selection")
def test_wms_getmap_diagrams(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country_Diagrams,Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Diagrams")
def test_wms_getmap_opacities(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
@ -438,6 +781,121 @@ class TestQgsServerWMS(QgsServerTestBase):
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Opacities")
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,Hello,dem",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857",
"OPACITIES": "125,50,150"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Opacities2")
def test_wms_getmap_highlight(self):
# highlight layer with color separated from sld
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country_Labels",
"HIGHLIGHT_GEOM": "POLYGON((-15000000 10000000, -15000000 6110620, 2500000 6110620, 2500000 10000000, -15000000 10000000))",
"HIGHLIGHT_SYMBOL": "<StyledLayerDescriptor><UserStyle><Name>Highlight</Name><FeatureTypeStyle><Rule><Name>Symbol</Name><LineSymbolizer><Stroke><SvgParameter name=\"stroke\">%23ea1173</SvgParameter><SvgParameter name=\"stroke-opacity\">1</SvgParameter><SvgParameter name=\"stroke-width\">1.6</SvgParameter></Stroke></LineSymbolizer></Rule></FeatureTypeStyle></UserStyle></StyledLayerDescriptor>",
"HIGHLIGHT_LABELSTRING": "Highlight Layer!",
"HIGHLIGHT_LABELSIZE": "16",
"HIGHLIGHT_LABELCOLOR": "%2300FF0000",
"HIGHLIGHT_LABELBUFFERCOLOR": "%232300FF00",
"HIGHLIGHT_LABELBUFFERSIZE": "1.5",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Highlight")
def test_wms_getmap_annotations(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectAnnotationPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Annotations")
def test_wms_getmap_sld(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,db_point",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_SLDRestored")
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"REQUEST": "GetMap",
"VERSION": "1.1.1",
"SERVICE": "WMS",
"SLD": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>db_point</se:Name> <UserStyle> <se:Name>db_point_style</se:Name> <se:FeatureTypeStyle> <se:Rule> <se:Name>Single symbol</se:Name> <ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\"> <ogc:PropertyIsEqualTo> <ogc:PropertyName>gid</ogc:PropertyName> <ogc:Literal>1</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <se:PointSymbolizer uom=\"http://www.opengeospatial.org/se/units/metre\"> <se:Graphic> <se:Mark> <se:WellKnownName>square</se:WellKnownName> <se:Fill> <se:SvgParameter name=\"fill\">5e86a1</se:SvgParameter> </se:Fill> <se:Stroke> <se:SvgParameter name=\"stroke\">000000</se:SvgParameter> </se:Stroke> </se:Mark> <se:Size>0.007</se:Size> </se:Graphic> </se:PointSymbolizer> </se:Rule> </se:FeatureTypeStyle> </UserStyle> </NamedLayer> </StyledLayerDescriptor>",
"BBOX": "-16817707,-4710778,5696513,14587125",
"WIDTH": "500",
"HEIGHT": "500",
"LAYERS": "db_point",
"STYLES": "",
"FORMAT": "image/png",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_SLD")
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"LAYERS": "Country,db_point",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",
"HEIGHT": "500",
"WIDTH": "500",
"CRS": "EPSG:3857"
}.items())])
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_SLDRestored")
def test_wms_getprint_basic(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,269 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="2.99.0-Master" projectname="">
<title></title>
<autotransaction active="0"/>
<evaluateDefaultValues active="0"/>
<layer-tree-group checked="Qt::Checked" expanded="1" name="">
<customproperties/>
<layer-tree-group checked="Qt::Checked" expanded="1" name="groupEmbedded">
<customproperties/>
<layer-tree-layer checked="Qt::Checked" expanded="1" id="multipoint20170309173637804" name="multipoint">
<customproperties/>
</layer-tree-layer>
</layer-tree-group>
</layer-tree-group>
<snapping-settings enabled="0" type="1" unit="2" tolerance="0" mode="2" intersection-snapping="0">
<individual-layer-settings>
<layer-setting enabled="0" type="1" units="2" tolerance="0" id="multipoint20170309173637804"/>
</individual-layer-settings>
</snapping-settings>
<relations/>
<mapcanvas>
<units>degrees</units>
<extent>
<xmin>-118.30669239248942404</xmin>
<ymin>22.77273630404421567</ymin>
<xmax>-81.58695972535105057</xmax>
<ymax>46.72277089440163422</ymax>
</extent>
<rotation>0</rotation>
<destinationsrs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</destinationsrs>
<rendermaptile>0</rendermaptile>
<layer_coordinate_transform_info>
<layer_coordinate_transform layerid="multipoint20170309173637804" srcDatumTransform="-1" destDatumTransform="-1" srcAuthId="EPSG:4326" destAuthId="EPSG:4326"/>
<layer_coordinate_transform layerid="polys20170309163723865" srcDatumTransform="-1" destDatumTransform="-1" srcAuthId="EPSG:4326" destAuthId="EPSG:4326"/>
<layer_coordinate_transform layerid="points20170309163100401" srcDatumTransform="-1" destDatumTransform="-1" srcAuthId="EPSG:4326" destAuthId="EPSG:4326"/>
<layer_coordinate_transform layerid="lines20170309163100396" srcDatumTransform="-1" destDatumTransform="-1" srcAuthId="EPSG:4326" destAuthId="EPSG:4326"/>
</layer_coordinate_transform_info>
</mapcanvas>
<layer-tree-canvas>
<custom-order enabled="0">
<item>multipoint20170309173637804</item>
</custom-order>
</layer-tree-canvas>
<legend updateDrawingOrder="true">
<legendgroup open="true" checked="Qt::Checked" name="groupEmbedded">
<legendlayer open="true" checked="Qt::Checked" showFeatureCount="0" drawingOrder="-1" name="multipoint">
<filegroup open="true" hidden="false">
<legendlayerfile layerid="multipoint20170309173637804" visible="1" isInOverview="0"/>
</filegroup>
</legendlayer>
</legendgroup>
</legend>
<projectlayers>
<maplayer autoRefreshTime="0" geometry="Point" simplifyMaxScale="1" simplifyDrawingTol="1" autoRefreshEnabled="0" simplifyDrawingHints="1" minimumScale="0" readOnly="0" type="vector" maximumScale="1e+8" simplifyAlgorithm="0" simplifyLocal="1" hasScaleBasedVisibilityFlag="0">
<extent>
<xmin>-117.43241304327183627</xmin>
<ymin>23.34297522286225401</ymin>
<xmax>-82.46123907456862412</xmax>
<ymax>46.15253197558359943</ymax>
</extent>
<id>multipoint20170309173637804</id>
<datasource>../multipoint.shp</datasource>
<keywordList>
<value></value>
</keywordList>
<layername>multipoint</layername>
<srs>
<spatialrefsys>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>WGS84</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</srs>
<provider encoding="UTF-8">ogr</provider>
<vectorjoins/>
<layerDependencies/>
<dataDependencies/>
<expressionfields/>
<map-layer-style-manager current="">
<map-layer-style name=""/>
</map-layer-style-manager>
<renderer-v2 enableorderby="0" forceraster="0" symbollevels="0" type="singleSymbol">
<symbols>
<symbol clip_to_extent="1" type="marker" alpha="1" name="0">
<layer enabled="1" pass="0" class="SimpleMarker" locked="0">
<prop v="0" k="angle"/>
<prop v="124,179,68,255" k="color"/>
<prop v="1" k="horizontal_anchor_point"/>
<prop v="bevel" k="joinstyle"/>
<prop v="circle" k="name"/>
<prop v="0,0" k="offset"/>
<prop v="0,0,0,0,0,0" k="offset_map_unit_scale"/>
<prop v="MM" k="offset_unit"/>
<prop v="0,0,0,255" k="outline_color"/>
<prop v="solid" k="outline_style"/>
<prop v="0" k="outline_width"/>
<prop v="0,0,0,0,0,0" k="outline_width_map_unit_scale"/>
<prop v="MM" k="outline_width_unit"/>
<prop v="diameter" k="scale_method"/>
<prop v="2" k="size"/>
<prop v="0,0,0,0,0,0" k="size_map_unit_scale"/>
<prop v="MM" k="size_unit"/>
<prop v="1" k="vertical_anchor_point"/>
<data_defined_properties>
<Option type="Map">
<Option value="" type="QString" name="name"/>
<Option name="properties"/>
<Option value="collection" type="QString" name="type"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<labeling type="simple"/>
<customproperties/>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerTransparency>0</layerTransparency>
<fieldConfiguration>
<field name="Id">
<editWidget type="">
<config>
<Option/>
</config>
</editWidget>
</field>
</fieldConfiguration>
<annotationform></annotationform>
<aliases>
<alias index="0" field="Id" name=""/>
</aliases>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults>
<default expression="" field="Id"/>
</defaults>
<constraints>
<constraint exp_strength="0" notnull_strength="0" constraints="0" unique_strength="0" field="Id"/>
</constraints>
<constraintExpressions>
<constraint exp="" desc="" field="Id"/>
</constraintExpressions>
<attributeactions>
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
</attributeactions>
<attributetableconfig actionWidgetStyle="dropDown" sortOrder="0" sortExpression="">
<columns/>
</attributetableconfig>
<editform></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<widgets/>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<expressionfields/>
<previewExpression></previewExpression>
<mapTip></mapTip>
</maplayer>
</projectlayers>
<properties>
<WMSContactPhone type="QString"></WMSContactPhone>
<WCSLayers type="QStringList"/>
<WMSContactPerson type="QString"></WMSContactPerson>
<WFSTLayers>
<Delete type="QStringList"/>
<Insert type="QStringList"/>
<Update type="QStringList"/>
</WFSTLayers>
<WMSRequestDefinedDataSources type="bool">false</WMSRequestDefinedDataSources>
<WMSServiceCapabilities type="bool">false</WMSServiceCapabilities>
<Measurement>
<DistanceUnits type="QString">meters</DistanceUnits>
<AreaUnits type="QString">m2</AreaUnits>
</Measurement>
<WMSContactPosition type="QString"></WMSContactPosition>
<WMSServiceAbstract type="QString"></WMSServiceAbstract>
<WCSUrl type="QString"></WCSUrl>
<WMSFees type="QString">conditions unknown</WMSFees>
<Gui>
<SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart>
<SelectionColorRedPart type="int">255</SelectionColorRedPart>
<SelectionColorBluePart type="int">0</SelectionColorBluePart>
<CanvasColorBluePart type="int">255</CanvasColorBluePart>
<CanvasColorGreenPart type="int">255</CanvasColorGreenPart>
<CanvasColorRedPart type="int">255</CanvasColorRedPart>
<SelectionColorGreenPart type="int">255</SelectionColorGreenPart>
</Gui>
<WMSOnlineResource type="QString"></WMSOnlineResource>
<WMSPrecision type="QString">8</WMSPrecision>
<WMSAddWktGeometry type="bool">false</WMSAddWktGeometry>
<Legend>
<filterByMap type="bool">false</filterByMap>
</Legend>
<WMSAccessConstraints type="QString">None</WMSAccessConstraints>
<WMSRestrictedComposers type="QStringList"/>
<Identify>
<disabledLayers type="QStringList"/>
</Identify>
<WMSContactMail type="QString"></WMSContactMail>
<WMSImageQuality type="int">90</WMSImageQuality>
<WMSContactOrganization type="QString"></WMSContactOrganization>
<WMSUrl type="QString"></WMSUrl>
<WMSUseLayerIDs type="bool">true</WMSUseLayerIDs>
<WMSServiceTitle type="QString"></WMSServiceTitle>
<WFSLayers type="QStringList"/>
<PositionPrecision>
<Automatic type="bool">true</Automatic>
<DegreeFormat type="QString">MU</DegreeFormat>
<DecimalPlaces type="int">2</DecimalPlaces>
</PositionPrecision>
<WFSUrl type="QString"></WFSUrl>
<Paths>
<Absolute type="bool">false</Absolute>
</Paths>
<WMSKeywordList type="QStringList">
<value></value>
</WMSKeywordList>
<SpatialRefSys>
<ProjectCRSID type="int">3452</ProjectCRSID>
<ProjectCrs type="QString">EPSG:4326</ProjectCrs>
<ProjectCRSProj4String type="QString">+proj=longlat +datum=WGS84 +no_defs</ProjectCRSProj4String>
</SpatialRefSys>
<WMSSegmentizeFeatureInfoGeometry type="bool">false</WMSSegmentizeFeatureInfoGeometry>
<Macros>
<pythonCode type="QString"></pythonCode>
</Macros>
<DefaultStyles>
<RandomColors type="bool">true</RandomColors>
<Fill type="QString"></Fill>
<ColorRamp type="QString"></ColorRamp>
<Line type="QString"></Line>
<Marker type="QString"></Marker>
<AlphaInt type="int">255</AlphaInt>
</DefaultStyles>
<Measure>
<Ellipsoid type="QString">NONE</Ellipsoid>
</Measure>
<WMSRestrictedLayers type="QStringList">
<value>multipoint</value>
</WMSRestrictedLayers>
</properties>
<visibility-presets/>
<Annotations/>
</qgis>