mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-17 00:04:02 -04:00
Now the widgets factories can give a score on how good they could handle a widget. Additionaly, plugins can be added to choose a widget factory in function of an external information. One of them uses a table in PostgresQL to allow specification of the widget type and configuration. I took the opportunity to remove a few deprecated method in relation to this.
3522 lines
119 KiB
C++
3522 lines
119 KiB
C++
/***************************************************************************
|
|
qgswmsserver.cpp
|
|
-------------------
|
|
begin : May 14, 2006
|
|
copyright : (C) 2006 by Marco Hugentobler & Ionut Iosifescu Enescu
|
|
email : marco dot hugentobler at karto dot baug dot ethz dot ch
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "qgswmsserver.h"
|
|
#include "qgscapabilitiescache.h"
|
|
#include "qgscsexception.h"
|
|
#include "qgsdxfexport.h"
|
|
#include "qgsfield.h"
|
|
#include "qgsfeatureiterator.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgslayertree.h"
|
|
#include "qgslayertreemodel.h"
|
|
#include "qgslayertreemodellegendnode.h"
|
|
#include "qgslegendrenderer.h"
|
|
#include "qgsmaplayer.h"
|
|
#include "qgsmaplayerlegend.h"
|
|
#include "qgsmaplayerregistry.h"
|
|
#include "qgsmaprenderer.h"
|
|
#include "qgsmaptopixel.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsrasteridentifyresult.h"
|
|
#include "qgsrasterlayer.h"
|
|
#include "qgsrasterrenderer.h"
|
|
#include "qgsscalecalculator.h"
|
|
#include "qgscoordinatereferencesystem.h"
|
|
#include "qgsvectordataprovider.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgsmapserviceexception.h"
|
|
#include "qgssldconfigparser.h"
|
|
#include "qgssymbol.h"
|
|
#include "qgsrenderer.h"
|
|
#include "qgspaintenginehack.h"
|
|
#include "qgsogcutils.h"
|
|
#include "qgsfeature.h"
|
|
#include "qgseditorwidgetregistry.h"
|
|
#include "qgsserverstreamingdevice.h"
|
|
#include "qgsaccesscontrol.h"
|
|
#include "qgsfeaturerequest.h"
|
|
|
|
#include <QImage>
|
|
#include <QPainter>
|
|
#include <QStringList>
|
|
#include <QTemporaryFile>
|
|
#include <QTextStream>
|
|
#include <QDir>
|
|
|
|
//for printing
|
|
#include "qgscomposition.h"
|
|
#include <QBuffer>
|
|
#include <QPrinter>
|
|
#include <QSvgGenerator>
|
|
#include <QUrl>
|
|
#include <QPaintEngine>
|
|
|
|
QgsWmsServer::QgsWmsServer(
|
|
const QString& configFilePath
|
|
, QMap<QString, QString> ¶meters
|
|
, QgsWmsConfigParser* cp
|
|
, QgsRequestHandler* rh
|
|
, QgsMapRenderer* renderer
|
|
, QgsCapabilitiesCache* capCache
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
, const QgsAccessControl* accessControl
|
|
#endif
|
|
)
|
|
: QgsOWSServer(
|
|
configFilePath
|
|
, parameters
|
|
, rh
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
, accessControl
|
|
#endif
|
|
)
|
|
, mMapRenderer( renderer )
|
|
, mCapabilitiesCache( capCache )
|
|
, mConfigParser( cp )
|
|
, mOwnsConfigParser( false )
|
|
, mDrawLegendLayerLabel( true )
|
|
, mDrawLegendItemLabel( true )
|
|
{
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
mMapRenderer->setFeatureFilterProvider( mAccessControl );
|
|
#endif
|
|
}
|
|
|
|
QgsWmsServer::QgsWmsServer()
|
|
: QgsOWSServer(
|
|
QString()
|
|
, QMap<QString, QString>()
|
|
, nullptr
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
, nullptr
|
|
#endif
|
|
)
|
|
, mMapRenderer( nullptr )
|
|
, mCapabilitiesCache()
|
|
, mConfigParser( nullptr )
|
|
, mOwnsConfigParser( false )
|
|
, mDrawLegendLayerLabel( true )
|
|
, mDrawLegendItemLabel( true )
|
|
{
|
|
}
|
|
|
|
QgsWmsServer::~QgsWmsServer()
|
|
{
|
|
}
|
|
|
|
void QgsWmsServer::cleanupAfterRequest()
|
|
{
|
|
if ( mOwnsConfigParser )
|
|
{
|
|
delete mConfigParser;
|
|
mConfigParser = nullptr;
|
|
mOwnsConfigParser = false;
|
|
}
|
|
}
|
|
|
|
void QgsWmsServer::executeRequest()
|
|
{
|
|
if ( !mMapRenderer || !mConfigParser || !mRequestHandler || !mCapabilitiesCache )
|
|
{
|
|
return; //TODO: error handling
|
|
}
|
|
|
|
//request type
|
|
QString request = mParameters.value( "REQUEST" );
|
|
if ( request.isEmpty() )
|
|
{
|
|
QgsMessageLog::logMessage( "unable to find 'REQUEST' parameter, exiting..." );
|
|
mRequestHandler->setServiceException( QgsMapServiceException( "OperationNotSupported", "Please check the value of the REQUEST parameter" ) );
|
|
cleanupAfterRequest();
|
|
return;
|
|
}
|
|
|
|
//version
|
|
QString version = mParameters.value( "VERSION", "1.3.0" );
|
|
bool getProjectSettings = ( request.compare( "GetProjectSettings", Qt::CaseInsensitive ) == 0 );
|
|
if ( getProjectSettings )
|
|
{
|
|
version = "1.3.0"; //getProjectSettings extends WMS 1.3.0 capabilities
|
|
}
|
|
|
|
//GetCapabilities
|
|
if ( request.compare( "GetCapabilities", Qt::CaseInsensitive ) == 0 || getProjectSettings )
|
|
{
|
|
QStringList cacheKeyList;
|
|
cacheKeyList << ( getProjectSettings ? "projectSettings" : version );
|
|
cacheKeyList << getenv( "SERVER_NAME" );
|
|
bool cache = true;
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
cache = mAccessControl->fillCacheKey( cacheKeyList );
|
|
#endif
|
|
QString cacheKey = cacheKeyList.join( "-" );
|
|
const QDomDocument* capabilitiesDocument = mCapabilitiesCache->searchCapabilitiesDocument( mConfigFilePath, cacheKey );
|
|
if ( !capabilitiesDocument ) //capabilities xml not in cache. Create a new one
|
|
{
|
|
QgsMessageLog::logMessage( "Capabilities document not found in cache" );
|
|
QDomDocument doc;
|
|
try
|
|
{
|
|
doc = getCapabilities( version, getProjectSettings );
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
cleanupAfterRequest();
|
|
return;
|
|
}
|
|
if ( cache )
|
|
{
|
|
mCapabilitiesCache->insertCapabilitiesDocument( mConfigFilePath, cacheKey, &doc );
|
|
capabilitiesDocument = mCapabilitiesCache->searchCapabilitiesDocument( mConfigFilePath, cacheKey );
|
|
}
|
|
else
|
|
{
|
|
doc = doc.cloneNode().toDocument();
|
|
capabilitiesDocument = &doc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( "Found capabilities document in cache" );
|
|
}
|
|
|
|
if ( capabilitiesDocument )
|
|
{
|
|
mRequestHandler->setGetCapabilitiesResponse( *capabilitiesDocument );
|
|
}
|
|
}
|
|
//GetMap
|
|
else if ( request.compare( "GetMap", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
//export as dxf
|
|
QString format = mParameters.value( "FORMAT" );
|
|
if ( format.compare( "application/dxf", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
try
|
|
{
|
|
getMapAsDxf();
|
|
cleanupAfterRequest();
|
|
return;
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
QgsMessageLog::logMessage( "Caught exception during GetMap request" );
|
|
mRequestHandler->setServiceException( ex );
|
|
cleanupAfterRequest();
|
|
return;
|
|
}
|
|
}
|
|
|
|
QImage* result = nullptr;
|
|
try
|
|
{
|
|
result = getMap();
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
QgsMessageLog::logMessage( "Caught exception during GetMap request" );
|
|
mRequestHandler->setServiceException( ex );
|
|
cleanupAfterRequest();
|
|
return;
|
|
}
|
|
|
|
if ( result )
|
|
{
|
|
QgsMessageLog::logMessage( "Setting GetMap response" );
|
|
mRequestHandler->setGetMapResponse( "WMS", result, getImageQuality() );
|
|
QgsMessageLog::logMessage( "Response sent" );
|
|
}
|
|
else
|
|
{
|
|
//do some error handling
|
|
QgsMessageLog::logMessage( "result image is 0" );
|
|
}
|
|
delete result;
|
|
}
|
|
//GetFeatureInfo
|
|
else if ( request.compare( "GetFeatureInfo", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
QDomDocument featureInfoDoc;
|
|
try
|
|
{
|
|
if ( getFeatureInfo( featureInfoDoc, version ) != 0 )
|
|
{
|
|
cleanupAfterRequest();
|
|
return;
|
|
}
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
cleanupAfterRequest();
|
|
return;
|
|
}
|
|
|
|
QString infoFormat = mParameters.value( "INFO_FORMAT", "text/plain" );
|
|
mRequestHandler->setGetFeatureInfoResponse( featureInfoDoc, infoFormat );
|
|
}
|
|
//GetContext
|
|
else if ( request.compare( "GetContext", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
try
|
|
{
|
|
QDomDocument doc = getContext();
|
|
mRequestHandler->setXmlResponse( doc );
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
}
|
|
}
|
|
//GetSchemaExtension
|
|
else if ( request.compare( "GetSchemaExtension", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
try
|
|
{
|
|
QDomDocument doc = getSchemaExtension();
|
|
mRequestHandler->setXmlResponse( doc );
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
}
|
|
}
|
|
//GetStyle for compatibility with earlier QGIS versions
|
|
else if ( request.compare( "GetStyle", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
try
|
|
{
|
|
QDomDocument doc = getStyle();
|
|
mRequestHandler->setXmlResponse( doc );
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
}
|
|
}
|
|
//GetStyles
|
|
else if ( request.compare( "GetStyles", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
// GetStyles is defined for WMS1.1.1/SLD1.0
|
|
// and in qgis-server WMS1.3.0 extension
|
|
try
|
|
{
|
|
QDomDocument doc = getStyles();
|
|
mRequestHandler->setXmlResponse( doc );
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
}
|
|
}
|
|
//GetStyles
|
|
else if ( request.compare( "DescribeLayer", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
// DescribeLayer is defined for WMS1.1.1/SLD1.0
|
|
// and in qgis-server WMS1.3.0 extension
|
|
try
|
|
{
|
|
QDomDocument doc = describeLayer();
|
|
mRequestHandler->setXmlResponse( doc );
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
}
|
|
}
|
|
//GetLegendGraphic
|
|
else if ( request.compare( "GetLegendGraphic", Qt::CaseInsensitive ) == 0 ||
|
|
request.compare( "GetLegendGraphics", Qt::CaseInsensitive ) == 0 )
|
|
// GetLegendGraphics for compatibility with earlier QGIS versions
|
|
{
|
|
QImage* result = nullptr;
|
|
try
|
|
{
|
|
result = getLegendGraphics();
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
QgsMessageLog::logMessage( "Caught exception during GetLegendGraphic request" );
|
|
mRequestHandler->setServiceException( ex );
|
|
}
|
|
|
|
if ( result )
|
|
{
|
|
QgsMessageLog::logMessage( "Setting GetLegendGraphic response" );
|
|
//setting is the same for GetMap and GetLegendGraphic
|
|
mRequestHandler->setGetMapResponse( "WMS", result, getImageQuality() );
|
|
QgsMessageLog::logMessage( "Response sent" );
|
|
}
|
|
else
|
|
{
|
|
//do some error handling
|
|
QgsMessageLog::logMessage( "result image is 0" );
|
|
}
|
|
delete result;
|
|
}
|
|
//GetPrint
|
|
else if ( request.compare( "GetPrint", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
QByteArray* printOutput = nullptr;
|
|
try
|
|
{
|
|
printOutput = getPrint( mRequestHandler->format() );
|
|
}
|
|
catch ( QgsMapServiceException& ex )
|
|
{
|
|
mRequestHandler->setServiceException( ex );
|
|
}
|
|
|
|
if ( printOutput )
|
|
{
|
|
mRequestHandler->setGetPrintResponse( printOutput );
|
|
}
|
|
delete printOutput;
|
|
}
|
|
else//unknown request
|
|
{
|
|
QgsMapServiceException e( "OperationNotSupported", "Operation " + request + " not supported" );
|
|
mRequestHandler->setServiceException( e );
|
|
}
|
|
cleanupAfterRequest();
|
|
}
|
|
|
|
void QgsWmsServer::appendFormats( QDomDocument &doc, QDomElement &elem, const QStringList &formats )
|
|
{
|
|
Q_FOREACH ( const QString& format, formats )
|
|
{
|
|
QDomElement formatElem = doc.createElement( "Format"/*wms:Format*/ );
|
|
formatElem.appendChild( doc.createTextNode( format ) );
|
|
elem.appendChild( formatElem );
|
|
}
|
|
}
|
|
|
|
QDomDocument QgsWmsServer::getCapabilities( QString version, bool fullProjectInformation )
|
|
{
|
|
QgsMessageLog::logMessage( "Entering." );
|
|
QDomDocument doc;
|
|
QDomElement wmsCapabilitiesElement;
|
|
|
|
//Prepare url
|
|
QString hrefString;
|
|
if ( mConfigParser )
|
|
{
|
|
hrefString = mConfigParser->serviceUrl();
|
|
}
|
|
if ( hrefString.isEmpty() )
|
|
{
|
|
hrefString = serviceUrl();
|
|
}
|
|
|
|
if ( version == "1.1.1" )
|
|
{
|
|
doc = QDomDocument( "WMT_MS_Capabilities SYSTEM 'http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd'" ); //WMS 1.1.1 needs DOCTYPE "SYSTEM http://schemas.opengis.net/wms/1.1.1/WMS_MS_Capabilities.dtd"
|
|
addXmlDeclaration( doc );
|
|
wmsCapabilitiesElement = doc.createElement( "WMT_MS_Capabilities"/*wms:WMS_Capabilities*/ );
|
|
}
|
|
else // 1.3.0 as default
|
|
{
|
|
addXmlDeclaration( doc );
|
|
wmsCapabilitiesElement = doc.createElement( "WMS_Capabilities"/*wms:WMS_Capabilities*/ );
|
|
wmsCapabilitiesElement.setAttribute( "xmlns", "http://www.opengis.net/wms" );
|
|
wmsCapabilitiesElement.setAttribute( "xmlns:sld", "http://www.opengis.net/sld" );
|
|
wmsCapabilitiesElement.setAttribute( "xmlns:qgs", "http://www.qgis.org/wms" );
|
|
wmsCapabilitiesElement.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" );
|
|
QString schemaLocation = "http://www.opengis.net/wms";
|
|
schemaLocation += " http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd";
|
|
schemaLocation += " http://www.opengis.net/sld";
|
|
schemaLocation += " http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd";
|
|
schemaLocation += " http://www.qgis.org/wms";
|
|
if ( mConfigParser && mConfigParser->wmsInspireActivated() )
|
|
{
|
|
wmsCapabilitiesElement.setAttribute( "xmlns:inspire_common", "http://inspire.ec.europa.eu/schemas/common/1.0" );
|
|
wmsCapabilitiesElement.setAttribute( "xmlns:inspire_vs", "http://inspire.ec.europa.eu/schemas/inspire_vs/1.0" );
|
|
schemaLocation += " http://inspire.ec.europa.eu/schemas/inspire_vs/1.0";
|
|
schemaLocation += " http://inspire.ec.europa.eu/schemas/inspire_vs/1.0/inspire_vs.xsd";
|
|
}
|
|
schemaLocation += " " + hrefString + "SERVICE=WMS&REQUEST=GetSchemaExtension";
|
|
wmsCapabilitiesElement.setAttribute( "xsi:schemaLocation", schemaLocation );
|
|
}
|
|
wmsCapabilitiesElement.setAttribute( "version", version );
|
|
doc.appendChild( wmsCapabilitiesElement );
|
|
|
|
//todo: add service capabilities
|
|
if ( mConfigParser )
|
|
{
|
|
mConfigParser->serviceCapabilities( wmsCapabilitiesElement, doc );
|
|
}
|
|
|
|
//wms:Capability element
|
|
QDomElement capabilityElement = doc.createElement( "Capability"/*wms:Capability*/ );
|
|
wmsCapabilitiesElement.appendChild( capabilityElement );
|
|
//wms:Request element
|
|
QDomElement requestElement = doc.createElement( "Request"/*wms:Request*/ );
|
|
capabilityElement.appendChild( requestElement );
|
|
|
|
QDomElement dcpTypeElement = doc.createElement( "DCPType"/*wms:DCPType*/ );
|
|
QDomElement httpElement = doc.createElement( "HTTP"/*wms:HTTP*/ );
|
|
dcpTypeElement.appendChild( httpElement );
|
|
|
|
QDomElement elem;
|
|
|
|
//wms:GetCapabilities
|
|
elem = doc.createElement( "GetCapabilities"/*wms:GetCapabilities*/ );
|
|
appendFormats( doc, elem, QStringList() << ( version == "1.1.1" ? "application/vnd.ogc.wms_xml" : "text/xml" ) );
|
|
elem.appendChild( dcpTypeElement );
|
|
requestElement.appendChild( elem );
|
|
|
|
// SOAP platform
|
|
//only give this information if it is not a WMS request to be in sync with the WMS capabilities schema
|
|
QMap<QString, QString>::const_iterator service_it = mParameters.constFind( "SERVICE" );
|
|
if ( service_it != mParameters.constEnd() && service_it.value().compare( "WMS", Qt::CaseInsensitive ) != 0 )
|
|
{
|
|
QDomElement soapElement = doc.createElement( "SOAP"/*wms:SOAP*/ );
|
|
httpElement.appendChild( soapElement );
|
|
QDomElement soapResourceElement = doc.createElement( "OnlineResource"/*wms:OnlineResource*/ );
|
|
soapResourceElement.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
|
|
soapResourceElement.setAttribute( "xlink:type", "simple" );
|
|
soapResourceElement.setAttribute( "xlink:href", hrefString );
|
|
soapElement.appendChild( soapResourceElement );
|
|
}
|
|
|
|
//only Get supported for the moment
|
|
QDomElement getElement = doc.createElement( "Get"/*wms:Get*/ );
|
|
httpElement.appendChild( getElement );
|
|
QDomElement olResourceElement = doc.createElement( "OnlineResource"/*wms:OnlineResource*/ );
|
|
olResourceElement.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
|
|
olResourceElement.setAttribute( "xlink:type", "simple" );
|
|
olResourceElement.setAttribute( "xlink:href", hrefString );
|
|
getElement.appendChild( olResourceElement );
|
|
|
|
#if 0
|
|
// POST already used by SOAP
|
|
QDomElement postElement = doc.createElement( "post"/*wms:SOAP*/ );
|
|
httpElement.appendChild( postElement );
|
|
QDomElement postResourceElement = doc.createElement( "OnlineResource"/*wms:OnlineResource*/ );
|
|
postResourceElement.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
|
|
postResourceElement.setAttribute( "xlink:type", "simple" );
|
|
postResourceElement.setAttribute( "xlink:href", "http://" + QString( getenv( "SERVER_NAME" ) ) + QString( getenv( "REQUEST_URI" ) ) );
|
|
postElement.appendChild( postResourceElement );
|
|
dcpTypeElement.appendChild( postElement );
|
|
#endif
|
|
|
|
//wms:GetMap
|
|
elem = doc.createElement( "GetMap"/*wms:GetMap*/ );
|
|
appendFormats( doc, elem, QStringList() << "image/jpeg" << "image/png" << "image/png; mode=16bit" << "image/png; mode=8bit" << "image/png; mode=1bit" << "application/dxf" );
|
|
elem.appendChild( dcpTypeElement.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
|
|
requestElement.appendChild( elem );
|
|
|
|
//wms:GetFeatureInfo
|
|
elem = doc.createElement( "GetFeatureInfo" );
|
|
appendFormats( doc, elem, QStringList() << "text/plain" << "text/html" << "text/xml" << "application/vnd.ogc.gml" << "application/vnd.ogc.gml/3.1.1" );
|
|
elem.appendChild( dcpTypeElement.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
|
|
requestElement.appendChild( elem );
|
|
|
|
//wms:GetLegendGraphic
|
|
elem = doc.createElement(( version == "1.1.1" ? "GetLegendGraphic" : "sld:GetLegendGraphic" )/*wms:GetLegendGraphic*/ );
|
|
appendFormats( doc, elem, QStringList() << "image/jpeg" << "image/png" );
|
|
elem.appendChild( dcpTypeElement.cloneNode().toElement() ); // this is the same as for 'GetCapabilities'
|
|
requestElement.appendChild( elem );
|
|
|
|
//wms:DescribeLayer
|
|
elem = doc.createElement(( version == "1.1.1" ? "DescribeLayer" : "sld:DescribeLayer" )/*wms:GetLegendGraphic*/ );
|
|
appendFormats( doc, elem, QStringList() << "text/xml" );
|
|
elem.appendChild( dcpTypeElement.cloneNode().toElement() ); // this is the same as for 'GetCapabilities'
|
|
requestElement.appendChild( elem );
|
|
|
|
//wms:GetStyles
|
|
elem = doc.createElement(( version == "1.1.1" ? "GetStyles" : "qgs:GetStyles" )/*wms:GetStyles*/ );
|
|
appendFormats( doc, elem, QStringList() << "text/xml" );
|
|
elem.appendChild( dcpTypeElement.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
|
|
requestElement.appendChild( elem );
|
|
|
|
if ( fullProjectInformation ) //remove composer templates from GetCapabilities in the long term
|
|
{
|
|
//wms:GetPrint
|
|
elem = doc.createElement( "GetPrint" /*wms:GetPrint*/ );
|
|
appendFormats( doc, elem, QStringList() << "svg" << "png" << "pdf" );
|
|
elem.appendChild( dcpTypeElement.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
|
|
requestElement.appendChild( elem );
|
|
}
|
|
|
|
//Exception element is mandatory
|
|
elem = doc.createElement( "Exception" );
|
|
appendFormats( doc, elem, QStringList() << ( version == "1.1.1" ? "application/vnd.ogc.se_xml" : "text/xml" ) );
|
|
capabilityElement.appendChild( elem );
|
|
|
|
//UserDefinedSymbolization element
|
|
if ( version == "1.3.0" )
|
|
{
|
|
elem = doc.createElement( "sld:UserDefinedSymbolization" );
|
|
elem.setAttribute( "SupportSLD", "1" );
|
|
elem.setAttribute( "UserLayer", "0" );
|
|
elem.setAttribute( "UserStyle", "1" );
|
|
elem.setAttribute( "RemoteWFS", "0" );
|
|
elem.setAttribute( "InlineFeature", "0" );
|
|
elem.setAttribute( "RemoteWCS", "0" );
|
|
capabilityElement.appendChild( elem );
|
|
|
|
if ( mConfigParser && mConfigParser->wmsInspireActivated() )
|
|
{
|
|
mConfigParser->inspireCapabilities( capabilityElement, doc );
|
|
}
|
|
}
|
|
|
|
if ( mConfigParser && fullProjectInformation ) //remove composer templates from GetCapabilities in the long term
|
|
{
|
|
//Insert <ComposerTemplate> elements derived from wms:_ExtendedCapabilities
|
|
mConfigParser->printCapabilities( capabilityElement, doc );
|
|
}
|
|
|
|
if ( mConfigParser && fullProjectInformation )
|
|
{
|
|
//WFS layers
|
|
QStringList wfsLayers = mConfigParser->wfsLayerNames();
|
|
if ( !wfsLayers.isEmpty() )
|
|
{
|
|
QDomElement wfsLayersElem = doc.createElement( "WFSLayers" );
|
|
QStringList::const_iterator wfsIt = wfsLayers.constBegin();
|
|
for ( ; wfsIt != wfsLayers.constEnd(); ++wfsIt )
|
|
{
|
|
QDomElement wfsLayerElem = doc.createElement( "WFSLayer" );
|
|
wfsLayerElem.setAttribute( "name", *wfsIt );
|
|
wfsLayersElem.appendChild( wfsLayerElem );
|
|
}
|
|
capabilityElement.appendChild( wfsLayersElem );
|
|
}
|
|
}
|
|
|
|
//add the xml content for the individual layers/styles
|
|
QgsMessageLog::logMessage( "calling layersAndStylesCapabilities" );
|
|
if ( mConfigParser )
|
|
{
|
|
mConfigParser->layersAndStylesCapabilities( capabilityElement, doc, version, fullProjectInformation );
|
|
}
|
|
QgsMessageLog::logMessage( "layersAndStylesCapabilities returned" );
|
|
|
|
#if 0
|
|
//for debugging: save the document to disk
|
|
QFile capabilitiesFile( QDir::tempPath() + "/capabilities.txt" );
|
|
if ( capabilitiesFile.open( QIODevice::WriteOnly | QIODevice::Text ) )
|
|
{
|
|
QTextStream capabilitiesStream( &capabilitiesFile );
|
|
doc.save( capabilitiesStream, 4 );
|
|
}
|
|
#endif
|
|
|
|
return doc;
|
|
}
|
|
|
|
QDomDocument QgsWmsServer::getContext()
|
|
{
|
|
QDomDocument doc;
|
|
addXmlDeclaration( doc );
|
|
QDomElement owsContextElem = doc.createElement( "OWSContext" );
|
|
owsContextElem.setAttribute( "xmlns", "http://www.opengis.net/ows-context" );
|
|
owsContextElem.setAttribute( "xmlns:ows-context", "http://www.opengis.net/ows-context" );
|
|
owsContextElem.setAttribute( "xmlns:context", "http://www.opengis.net/context" );
|
|
owsContextElem.setAttribute( "xmlns:ows", "http://www.opengis.net/ows" );
|
|
owsContextElem.setAttribute( "xmlns:sld", "http://www.opengis.net/sld" );
|
|
owsContextElem.setAttribute( "xmlns:ogc", "http://www.opengis.net/ogc" );
|
|
owsContextElem.setAttribute( "xmlns:gml", "http://www.opengis.net/gml" );
|
|
owsContextElem.setAttribute( "xmlns:kml", "http://www.opengis.net/kml/2.2" );
|
|
owsContextElem.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
|
|
owsContextElem.setAttribute( "xmlns:ns9", "http://www.w3.org/2005/Atom" );
|
|
owsContextElem.setAttribute( "xmlns:xal", "urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" );
|
|
owsContextElem.setAttribute( "xmlns:ins", "http://www.inspire.org" );
|
|
owsContextElem.setAttribute( "version", "0.3.1" );
|
|
doc.appendChild( owsContextElem );
|
|
|
|
if ( mConfigParser )
|
|
{
|
|
//Prepare url
|
|
QString hrefString = mConfigParser->serviceUrl();
|
|
if ( hrefString.isEmpty() )
|
|
{
|
|
hrefString = serviceUrl();
|
|
}
|
|
mConfigParser->owsGeneralAndResourceList( owsContextElem, doc, hrefString );
|
|
}
|
|
|
|
return doc;
|
|
}
|
|
|
|
|
|
static QgsLayerTreeModelLegendNode* _findLegendNodeForRule( QgsLayerTreeModel* legendModel, const QString& rule )
|
|
{
|
|
Q_FOREACH ( QgsLayerTreeLayer* nodeLayer, legendModel->rootGroup()->findLayers() )
|
|
{
|
|
Q_FOREACH ( QgsLayerTreeModelLegendNode* legendNode, legendModel->layerLegendNodes( nodeLayer ) )
|
|
{
|
|
if ( legendNode->data( Qt::DisplayRole ).toString() == rule )
|
|
return legendNode;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
static QgsRectangle _parseBBOX( const QString &bboxStr, bool &ok )
|
|
{
|
|
ok = false;
|
|
|
|
QStringList lst = bboxStr.split( "," );
|
|
if ( lst.count() != 4 )
|
|
return QgsRectangle();
|
|
|
|
double d[4];
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
bool ok;
|
|
lst[i].replace( " ", "+" );
|
|
d[i] = lst[i].toDouble( &ok );
|
|
if ( !ok )
|
|
return QgsRectangle();
|
|
}
|
|
|
|
ok = true;
|
|
return QgsRectangle( d[0], d[1], d[2], d[3] );
|
|
}
|
|
|
|
|
|
QImage* QgsWmsServer::getLegendGraphics()
|
|
{
|
|
if ( !mConfigParser || !mMapRenderer )
|
|
{
|
|
return nullptr;
|
|
}
|
|
if ( !mParameters.contains( "LAYER" ) && !mParameters.contains( "LAYERS" ) )
|
|
{
|
|
throw QgsMapServiceException( "LayerNotSpecified", "LAYER is mandatory for GetLegendGraphic operation" );
|
|
}
|
|
if ( !mParameters.contains( "FORMAT" ) )
|
|
{
|
|
throw QgsMapServiceException( "FormatNotSpecified", "FORMAT is mandatory for GetLegendGraphic operation" );
|
|
}
|
|
|
|
bool contentBasedLegend = false;
|
|
QgsRectangle contentBasedLegendExtent;
|
|
|
|
if ( mParameters.contains( "BBOX" ) )
|
|
{
|
|
contentBasedLegend = true;
|
|
|
|
bool bboxOk;
|
|
contentBasedLegendExtent = _parseBBOX( mParameters["BBOX"], bboxOk );
|
|
if ( !bboxOk || contentBasedLegendExtent.isEmpty() )
|
|
throw QgsMapServiceException( "InvalidParameterValue", "Invalid BBOX parameter" );
|
|
|
|
if ( mParameters.contains( "RULE" ) )
|
|
throw QgsMapServiceException( "InvalidParameterValue", "BBOX parameter cannot be combined with RULE" );
|
|
}
|
|
|
|
QStringList layersList, stylesList;
|
|
|
|
if ( readLayersAndStyles( layersList, stylesList ) != 0 )
|
|
{
|
|
QgsMessageLog::logMessage( "error reading layers and styles" );
|
|
return nullptr;
|
|
}
|
|
|
|
if ( layersList.size() < 1 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
//scale
|
|
double scaleDenominator = -1;
|
|
QMap<QString, QString>::const_iterator scaleIt = mParameters.constFind( "SCALE" );
|
|
if ( scaleIt != mParameters.constEnd() )
|
|
{
|
|
bool conversionSuccess;
|
|
double scaleValue = scaleIt.value().toDouble( &conversionSuccess );
|
|
if ( conversionSuccess )
|
|
{
|
|
scaleDenominator = scaleValue;
|
|
}
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem dummyCRS;
|
|
QStringList layerIds = layerSet( layersList, stylesList, dummyCRS, scaleDenominator );
|
|
if ( layerIds.size() < 1 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
//get icon size, spaces between legend items and font from config parser
|
|
double boxSpace, layerSpace, layerTitleSpace, symbolSpace, iconLabelSpace, symbolWidth, symbolHeight;
|
|
QFont layerFont, itemFont;
|
|
QColor layerFontColor, itemFontColor;
|
|
legendParameters( boxSpace, layerSpace, layerTitleSpace, symbolSpace,
|
|
iconLabelSpace, symbolWidth, symbolHeight, layerFont, itemFont, layerFontColor, itemFontColor );
|
|
|
|
QString rule;
|
|
int ruleSymbolWidth = 0, ruleSymbolHeight = 0;
|
|
QMap<QString, QString>::const_iterator ruleIt = mParameters.constFind( "RULE" );
|
|
if ( ruleIt != mParameters.constEnd() )
|
|
{
|
|
rule = ruleIt.value();
|
|
|
|
QMap<QString, QString>::const_iterator widthIt = mParameters.constFind( "WIDTH" );
|
|
if ( widthIt != mParameters.constEnd() )
|
|
{
|
|
bool conversionSuccess;
|
|
double width = widthIt.value().toDouble( &conversionSuccess );
|
|
if ( conversionSuccess )
|
|
{
|
|
ruleSymbolWidth = width;
|
|
}
|
|
}
|
|
|
|
QMap<QString, QString>::const_iterator heightIt = mParameters.constFind( "HEIGHT" );
|
|
if ( heightIt != mParameters.constEnd() )
|
|
{
|
|
bool conversionSuccess;
|
|
double width = heightIt.value().toDouble( &conversionSuccess );
|
|
if ( conversionSuccess )
|
|
{
|
|
ruleSymbolHeight = width;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checks showFeatureCount parameter
|
|
bool showFeatureCount = false;
|
|
if ( mParameters.contains( "SHOWFEATURECOUNT" ) )
|
|
showFeatureCount = QVariant( mParameters[ "SHOWFEATURECOUNT" ] ).toBool();
|
|
|
|
// Create the layer tree root
|
|
QgsLayerTreeGroup rootGroup;
|
|
// Store layers' name to reset them
|
|
QMap<QString, QString> layerNameMap;
|
|
// Create tree layer node for each layer
|
|
Q_FOREACH ( const QString& layerId, layerIds )
|
|
{
|
|
// get layer
|
|
QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer( layerId );
|
|
// create tree layer node
|
|
QgsLayerTreeLayer *layer = rootGroup.addLayer( ml );
|
|
// store the layer's name
|
|
layerNameMap.insert( layerId, ml->name() );
|
|
// set layer name with layer's title to have it in legend
|
|
if ( !ml->title().isEmpty() )
|
|
layer->setLayerName( ml->title() );
|
|
// set show feature count
|
|
if ( showFeatureCount )
|
|
layer->setCustomProperty( "showFeatureCount", showFeatureCount );
|
|
}
|
|
QgsLayerTreeModel legendModel( &rootGroup );
|
|
|
|
QList<QgsLayerTreeNode*> rootChildren = rootGroup.children();
|
|
|
|
if ( scaleDenominator > 0 )
|
|
legendModel.setLegendFilterByScale( scaleDenominator );
|
|
|
|
if ( contentBasedLegend )
|
|
{
|
|
HitTest hitTest;
|
|
getMap( &hitTest );
|
|
|
|
Q_FOREACH ( QgsLayerTreeNode* node, rootGroup.children() )
|
|
{
|
|
Q_ASSERT( QgsLayerTree::isLayer( node ) );
|
|
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
|
|
|
|
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( nodeLayer->layer() );
|
|
if ( !vl || !vl->renderer() )
|
|
continue;
|
|
|
|
const SymbolSet& usedSymbols = hitTest[vl];
|
|
QList<int> order;
|
|
int i = 0;
|
|
Q_FOREACH ( const QgsLegendSymbolItem& legendItem, vl->renderer()->legendSymbolItemsV2() )
|
|
{
|
|
if ( usedSymbols.contains( legendItem.legacyRuleKey() ) )
|
|
order.append( i );
|
|
++i;
|
|
}
|
|
|
|
// either remove the whole layer or just filter out some items
|
|
if ( order.isEmpty() )
|
|
rootGroup.removeChildNode( nodeLayer );
|
|
else
|
|
{
|
|
QgsMapLayerLegendUtils::setLegendNodeOrder( nodeLayer, order );
|
|
legendModel.refreshLayerLegend( nodeLayer );
|
|
}
|
|
}
|
|
}
|
|
|
|
// find out DPI
|
|
QImage* tmpImage = createImage( 1, 1 );
|
|
if ( !tmpImage )
|
|
return nullptr;
|
|
qreal dpmm = tmpImage->dotsPerMeterX() / 1000.0;
|
|
delete tmpImage;
|
|
|
|
// setup legend configuration
|
|
QgsLegendSettings legendSettings;
|
|
legendSettings.setTitle( QString() );
|
|
legendSettings.setBoxSpace( boxSpace );
|
|
legendSettings.rstyle( QgsComposerLegendStyle::Subgroup ).setMargin( QgsComposerLegendStyle::Top, layerSpace );
|
|
// TODO: not available: layer title space
|
|
legendSettings.rstyle( QgsComposerLegendStyle::Symbol ).setMargin( QgsComposerLegendStyle::Top, symbolSpace );
|
|
legendSettings.rstyle( QgsComposerLegendStyle::SymbolLabel ).setMargin( QgsComposerLegendStyle::Left, iconLabelSpace );
|
|
legendSettings.setSymbolSize( QSizeF( symbolWidth, symbolHeight ) );
|
|
legendSettings.rstyle( QgsComposerLegendStyle::Subgroup ).setFont( layerFont );
|
|
legendSettings.rstyle( QgsComposerLegendStyle::SymbolLabel ).setFont( itemFont );
|
|
// TODO: not available: layer font color
|
|
legendSettings.setFontColor( itemFontColor );
|
|
|
|
if ( contentBasedLegend )
|
|
{
|
|
legendSettings.setMapScale( mMapRenderer->scale() );
|
|
double scaleFactor = mMapRenderer->outputUnits() == QgsMapRenderer::Millimeters ? mMapRenderer->outputDpi() / 25.4 : 1.0;
|
|
legendSettings.setMmPerMapUnit( 1 / ( mMapRenderer->mapUnitsPerPixel() * scaleFactor ) );
|
|
}
|
|
|
|
if ( !rule.isEmpty() )
|
|
{
|
|
//create second image with the right dimensions
|
|
QImage* paintImage = createImage( ruleSymbolWidth, ruleSymbolHeight );
|
|
|
|
//go through the items a second time for painting
|
|
QPainter p( paintImage );
|
|
p.setRenderHint( QPainter::Antialiasing, true );
|
|
p.scale( dpmm, dpmm );
|
|
|
|
QgsLayerTreeModelLegendNode* legendNode = _findLegendNodeForRule( &legendModel, rule );
|
|
if ( legendNode )
|
|
{
|
|
QgsLayerTreeModelLegendNode::ItemContext ctx;
|
|
ctx.painter = &p;
|
|
ctx.labelXOffset = 0;
|
|
ctx.point = QPointF();
|
|
double itemHeight = ruleSymbolHeight / dpmm;
|
|
legendNode->drawSymbol( legendSettings, &ctx, itemHeight );
|
|
}
|
|
|
|
QgsMapLayerRegistry::instance()->removeAllMapLayers();
|
|
return paintImage;
|
|
}
|
|
|
|
Q_FOREACH ( QgsLayerTreeNode* node, rootChildren )
|
|
{
|
|
if ( QgsLayerTree::isLayer( node ) )
|
|
{
|
|
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
if ( !mAccessControl->layerReadPermission( nodeLayer->layer() ) )
|
|
{
|
|
throw QgsMapServiceException( "Security", "You are not allowed to access to the layer: " + nodeLayer->layer()->name() );
|
|
}
|
|
#endif
|
|
|
|
// layer titles - hidden or not
|
|
QgsLegendRenderer::setNodeLegendStyle( nodeLayer, mDrawLegendLayerLabel ? QgsComposerLegendStyle::Subgroup : QgsComposerLegendStyle::Hidden );
|
|
|
|
// rule item titles
|
|
if ( !mDrawLegendItemLabel )
|
|
{
|
|
Q_FOREACH ( QgsLayerTreeModelLegendNode* legendNode, legendModel.layerLegendNodes( nodeLayer ) )
|
|
{
|
|
legendNode->setUserLabel( " " ); // empty string = no override, so let's use one space
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsLegendRenderer legendRenderer( &legendModel, legendSettings );
|
|
QSizeF minSize = legendRenderer.minimumSize();
|
|
QSize s( minSize.width() * dpmm, minSize.height() * dpmm );
|
|
|
|
QImage* paintImage = createImage( s.width(), s.height() );
|
|
|
|
QPainter p( paintImage );
|
|
p.setRenderHint( QPainter::Antialiasing, true );
|
|
p.scale( dpmm, dpmm );
|
|
|
|
legendRenderer.drawLegend( &p );
|
|
|
|
p.end();
|
|
|
|
// reset layers' name
|
|
Q_FOREACH ( const QString& layerId, layerIds )
|
|
{
|
|
QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer( layerId );
|
|
ml->setName( layerNameMap[ layerId ] );
|
|
}
|
|
// clear map layer registry
|
|
QgsMapLayerRegistry::instance()->removeAllMapLayers();
|
|
return paintImage;
|
|
}
|
|
|
|
|
|
void QgsWmsServer::runHitTest( QPainter* painter, HitTest& hitTest )
|
|
{
|
|
QPaintDevice* thePaintDevice = painter->device();
|
|
|
|
// setup QgsRenderContext in the same way as QgsMapRenderer does
|
|
QgsRenderContext context;
|
|
context.setPainter( painter ); // we are not going to draw anything, but we still need a working painter
|
|
context.setRenderingStopped( false );
|
|
context.setRasterScaleFactor(( thePaintDevice->logicalDpiX() + thePaintDevice->logicalDpiY() ) / 2.0 / mMapRenderer->outputDpi() );
|
|
context.setScaleFactor( mMapRenderer->outputUnits() == QgsMapRenderer::Millimeters ? mMapRenderer->outputDpi() / 25.4 : 1.0 );
|
|
context.setRendererScale( mMapRenderer->scale() );
|
|
context.setMapToPixel( *mMapRenderer->coordinateTransform() );
|
|
context.setExtent( mMapRenderer->extent() );
|
|
|
|
Q_FOREACH ( const QString& layerID, mMapRenderer->layerSet() )
|
|
{
|
|
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerID ) );
|
|
if ( !vl || !vl->renderer() )
|
|
continue;
|
|
|
|
if ( vl->hasScaleBasedVisibility() && ( mMapRenderer->scale() < vl->minimumScale() || mMapRenderer->scale() > vl->maximumScale() ) )
|
|
{
|
|
hitTest[vl] = SymbolSet(); // no symbols -> will not be shown
|
|
continue;
|
|
}
|
|
|
|
if ( mMapRenderer->hasCrsTransformEnabled() )
|
|
{
|
|
QgsRectangle r1 = mMapRenderer->extent(), r2;
|
|
mMapRenderer->splitLayersExtent( vl, r1, r2 );
|
|
if ( !r1.isFinite() || !r2.isFinite() ) //there was a problem transforming the extent. Skip the layer
|
|
continue;
|
|
context.setCoordinateTransform( mMapRenderer->transformation( vl ) );
|
|
context.setExtent( r1 );
|
|
}
|
|
|
|
SymbolSet& usedSymbols = hitTest[vl];
|
|
runHitTestLayer( vl, usedSymbols, context );
|
|
}
|
|
}
|
|
|
|
void QgsWmsServer::runHitTestLayer( QgsVectorLayer* vl, SymbolSet& usedSymbols, QgsRenderContext& context )
|
|
{
|
|
QgsFeatureRenderer* r = vl->renderer();
|
|
bool moreSymbolsPerFeature = r->capabilities() & QgsFeatureRenderer::MoreSymbolsPerFeature;
|
|
r->startRender( context, vl->pendingFields() );
|
|
QgsFeature f;
|
|
QgsFeatureRequest request( context.extent() );
|
|
request.setFlags( QgsFeatureRequest::ExactIntersect );
|
|
QgsFeatureIterator fi = vl->getFeatures( request );
|
|
while ( fi.nextFeature( f ) )
|
|
{
|
|
context.expressionContext().setFeature( f );
|
|
if ( moreSymbolsPerFeature )
|
|
{
|
|
Q_FOREACH ( QgsSymbol* s, r->originalSymbolsForFeature( f, context ) )
|
|
usedSymbols.insert( s );
|
|
}
|
|
else
|
|
usedSymbols.insert( r->originalSymbolForFeature( f, context ) );
|
|
}
|
|
r->stopRender( context );
|
|
}
|
|
|
|
|
|
void QgsWmsServer::legendParameters( double& boxSpace, double& layerSpace, double& layerTitleSpace,
|
|
double& symbolSpace, double& iconLabelSpace, double& symbolWidth, double& symbolHeight,
|
|
QFont& layerFont, QFont& itemFont, QColor& layerFontColor, QColor& itemFontColor )
|
|
{
|
|
//spaces between legend elements
|
|
QMap<QString, QString>::const_iterator boxSpaceIt = mParameters.constFind( "BOXSPACE" );
|
|
boxSpace = ( boxSpaceIt == mParameters.constEnd() ) ? mConfigParser->legendBoxSpace() : boxSpaceIt.value().toDouble();
|
|
QMap<QString, QString>::const_iterator layerSpaceIt = mParameters.constFind( "LAYERSPACE" );
|
|
layerSpace = ( layerSpaceIt == mParameters.constEnd() ) ? mConfigParser->legendLayerSpace() : layerSpaceIt.value().toDouble();
|
|
QMap<QString, QString>::const_iterator layerTitleSpaceIt = mParameters.constFind( "LAYERTITLESPACE" );
|
|
layerTitleSpace = ( layerTitleSpaceIt == mParameters.constEnd() ) ? mConfigParser->legendLayerTitleSpace() : layerTitleSpaceIt.value().toDouble();
|
|
QMap<QString, QString>::const_iterator symbolSpaceIt = mParameters.constFind( "SYMBOLSPACE" );
|
|
symbolSpace = ( symbolSpaceIt == mParameters.constEnd() ) ? mConfigParser->legendSymbolSpace() : symbolSpaceIt.value().toDouble();
|
|
QMap<QString, QString>::const_iterator iconLabelSpaceIt = mParameters.constFind( "ICONLABELSPACE" );
|
|
iconLabelSpace = ( iconLabelSpaceIt == mParameters.constEnd() ) ? mConfigParser->legendIconLabelSpace() : iconLabelSpaceIt.value().toDouble();
|
|
QMap<QString, QString>::const_iterator symbolWidthIt = mParameters.constFind( "SYMBOLWIDTH" );
|
|
symbolWidth = ( symbolWidthIt == mParameters.constEnd() ) ? mConfigParser->legendSymbolWidth() : symbolWidthIt.value().toDouble();
|
|
QMap<QString, QString>::const_iterator symbolHeightIt = mParameters.constFind( "SYMBOLHEIGHT" );
|
|
symbolHeight = ( symbolHeightIt == mParameters.constEnd() ) ? mConfigParser->legendSymbolHeight() : symbolHeightIt.value().toDouble();
|
|
|
|
//font properties
|
|
layerFont = mConfigParser->legendLayerFont();
|
|
QMap<QString, QString>::const_iterator layerFontFamilyIt = mParameters.constFind( "LAYERFONTFAMILY" );
|
|
if ( layerFontFamilyIt != mParameters.constEnd() )
|
|
{
|
|
layerFont.setFamily( layerFontFamilyIt.value() );
|
|
}
|
|
QMap<QString, QString>::const_iterator layerFontBoldIt = mParameters.constFind( "LAYERFONTBOLD" );
|
|
if ( layerFontBoldIt != mParameters.constEnd() )
|
|
{
|
|
layerFont.setBold( layerFontBoldIt.value().compare( "TRUE", Qt::CaseInsensitive ) == 0 );
|
|
}
|
|
QMap<QString, QString>::const_iterator layerFontItalicIt = mParameters.constFind( "LAYERFONTITALIC" );
|
|
if ( layerFontItalicIt != mParameters.constEnd() )
|
|
{
|
|
layerFont.setItalic( layerFontItalicIt.value().compare( "TRUE", Qt::CaseInsensitive ) == 0 );
|
|
}
|
|
QMap<QString, QString>::const_iterator layerFontSizeIt = mParameters.constFind( "LAYERFONTSIZE" );
|
|
layerFont.setPointSizeF( layerFontSizeIt != mParameters.constEnd() ? layerFontSizeIt.value().toDouble() : layerFont.pointSizeF() );
|
|
QMap<QString, QString>::const_iterator layerFontColorIt = mParameters.constFind( "LAYERFONTCOLOR" );
|
|
if ( layerFontColorIt != mParameters.constEnd() )
|
|
{
|
|
layerFontColor.setNamedColor( layerFontColorIt.value() );
|
|
}
|
|
else
|
|
{
|
|
layerFontColor = QColor( 0, 0, 0 );
|
|
}
|
|
QMap<QString, QString>::const_iterator layerTitleIt = mParameters.constFind( "LAYERTITLE" );
|
|
if ( layerTitleIt != mParameters.constEnd() )
|
|
{
|
|
mDrawLegendLayerLabel = ( layerTitleIt.value().compare( "TRUE", Qt::CaseInsensitive ) == 0 );
|
|
}
|
|
else
|
|
{
|
|
mDrawLegendLayerLabel = true;
|
|
}
|
|
|
|
|
|
itemFont = mConfigParser->legendItemFont();
|
|
QMap<QString, QString>::const_iterator itemFontFamilyIt = mParameters.constFind( "ITEMFONTFAMILY" );
|
|
if ( itemFontFamilyIt != mParameters.constEnd() )
|
|
{
|
|
itemFont.setFamily( itemFontFamilyIt.value() );
|
|
}
|
|
QMap<QString, QString>::const_iterator itemFontBoldIt = mParameters.constFind( "ITEMFONTBOLD" );
|
|
if ( itemFontBoldIt != mParameters.constEnd() )
|
|
{
|
|
itemFont.setBold( itemFontBoldIt.value().compare( "TRUE", Qt::CaseInsensitive ) == 0 );
|
|
}
|
|
QMap<QString, QString>::const_iterator itemFontItalicIt = mParameters.constFind( "ITEMFONTITALIC" );
|
|
if ( itemFontItalicIt != mParameters.constEnd() )
|
|
{
|
|
itemFont.setItalic( itemFontItalicIt.value().compare( "TRUE", Qt::CaseInsensitive ) == 0 );
|
|
}
|
|
QMap<QString, QString>::const_iterator itemFontSizeIt = mParameters.constFind( "ITEMFONTSIZE" );
|
|
itemFont.setPointSizeF( itemFontSizeIt != mParameters.constEnd() ? itemFontSizeIt.value().toDouble() : itemFont.pointSizeF() );
|
|
QMap<QString, QString>::const_iterator itemFontColorIt = mParameters.constFind( "ITEMFONTCOLOR" );
|
|
if ( itemFontColorIt != mParameters.constEnd() )
|
|
{
|
|
itemFontColor.setNamedColor( itemFontColorIt.value() );
|
|
}
|
|
else
|
|
{
|
|
itemFontColor = QColor( 0, 0, 0 );
|
|
}
|
|
QMap<QString, QString>::const_iterator itemLabelIt = mParameters.constFind( "RULELABEL" );
|
|
if ( itemLabelIt != mParameters.constEnd() )
|
|
{
|
|
mDrawLegendItemLabel = ( itemLabelIt.value().compare( "TRUE", Qt::CaseInsensitive ) == 0 );
|
|
}
|
|
else
|
|
{
|
|
mDrawLegendItemLabel = true;
|
|
}
|
|
}
|
|
|
|
QDomDocument QgsWmsServer::getSchemaExtension()
|
|
{
|
|
QDomDocument xsdDoc;
|
|
|
|
QFileInfo xsdFileInfo( "schemaExtension.xsd" );
|
|
if ( !xsdFileInfo.exists() )
|
|
{
|
|
QgsMessageLog::logMessage( "Error, xsd file 'schemaExtension.xsd' does not exist", "Server", QgsMessageLog::CRITICAL );
|
|
return xsdDoc;
|
|
}
|
|
|
|
QString xsdFilePath = xsdFileInfo.absoluteFilePath();
|
|
QFile xsdFile( xsdFilePath );
|
|
if ( !xsdFile.exists() )
|
|
{
|
|
QgsMessageLog::logMessage( "Error, xsd file 'schemaExtension.xsd' does not exist", "Server", QgsMessageLog::CRITICAL );
|
|
return xsdDoc;
|
|
}
|
|
|
|
if ( !xsdFile.open( QIODevice::ReadOnly ) )
|
|
{
|
|
QgsMessageLog::logMessage( "Error, cannot open xsd file 'schemaExtension.xsd' does not exist", "Server", QgsMessageLog::CRITICAL );
|
|
return xsdDoc;
|
|
}
|
|
|
|
QString errorMsg;
|
|
int line, column;
|
|
if ( !xsdDoc.setContent( &xsdFile, true, &errorMsg, &line, &column ) )
|
|
{
|
|
QgsMessageLog::logMessage( "Error parsing file 'schemaExtension.xsd" +
|
|
QString( "': parse error %1 at row %2, column %3" ).arg( errorMsg ).arg( line ).arg( column ), "Server", QgsMessageLog::CRITICAL );
|
|
return xsdDoc;
|
|
}
|
|
return xsdDoc;
|
|
}
|
|
|
|
QDomDocument QgsWmsServer::getStyle()
|
|
{
|
|
QDomDocument doc;
|
|
if ( !mParameters.contains( "STYLE" ) )
|
|
{
|
|
throw QgsMapServiceException( "StyleNotSpecified", "Style is mandatory for GetStyle operation" );
|
|
}
|
|
|
|
if ( !mParameters.contains( "LAYER" ) )
|
|
{
|
|
throw QgsMapServiceException( "LayerNotSpecified", "Layer is mandatory for GetStyle operation" );
|
|
}
|
|
|
|
QString styleName = mParameters[ "STYLE" ];
|
|
QString layerName = mParameters[ "LAYER" ];
|
|
|
|
return mConfigParser->getStyle( styleName, layerName );
|
|
}
|
|
|
|
// GetStyles is defined for WMS1.1.1/SLD1.0 and in WMS 1.3.0 SLD Extension
|
|
QDomDocument QgsWmsServer::getStyles()
|
|
{
|
|
QDomDocument doc;
|
|
if ( !mParameters.contains( "LAYERS" ) )
|
|
{
|
|
throw QgsMapServiceException( "LayerNotSpecified", "Layers is mandatory for GetStyles operation" );
|
|
}
|
|
|
|
QStringList layersList = mParameters[ "LAYERS" ].split( ",", QString::SkipEmptyParts );
|
|
if ( layersList.size() < 1 )
|
|
{
|
|
throw QgsMapServiceException( "LayerNotSpecified", "Layers is mandatory for GetStyles operation" );
|
|
}
|
|
|
|
return mConfigParser->getStyles( layersList );
|
|
}
|
|
|
|
// DescribeLayer is defined for WMS1.1.1/SLD1.0 and in WMS 1.3.0 SLD Extension
|
|
QDomDocument QgsWmsServer::describeLayer()
|
|
{
|
|
if ( !mParameters.contains( "SLD_VERSION" ) )
|
|
{
|
|
throw QgsMapServiceException( "MissingParameterValue", "SLD_VERSION is mandatory for DescribeLayer operation" );
|
|
}
|
|
if ( mParameters[ "SLD_VERSION" ] != "1.1.0" )
|
|
{
|
|
throw QgsMapServiceException( "InvalidParameterValue", QString( "SLD_VERSION = %1 is not supported" ).arg( mParameters[ "SLD_VERSION" ] ) );
|
|
}
|
|
|
|
if ( !mParameters.contains( "LAYERS" ) )
|
|
{
|
|
throw QgsMapServiceException( "MissingParameterValue", "LAYERS is mandatory for DescribeLayer operation" );
|
|
}
|
|
|
|
QStringList layersList = mParameters[ "LAYERS" ].split( ",", QString::SkipEmptyParts );
|
|
if ( layersList.size() < 1 )
|
|
{
|
|
throw QgsMapServiceException( "InvalidParameterValue", "Layers is empty" );
|
|
}
|
|
|
|
//Prepare url
|
|
QString hrefString = mConfigParser->serviceUrl();
|
|
if ( hrefString.isEmpty() )
|
|
{
|
|
hrefString = serviceUrl();
|
|
}
|
|
|
|
return mConfigParser->describeLayer( layersList, hrefString );
|
|
}
|
|
|
|
QByteArray* QgsWmsServer::getPrint( const QString& formatString )
|
|
{
|
|
QStringList layersList, stylesList, layerIdList;
|
|
QImage* theImage = initializeRendering( layersList, stylesList, layerIdList );
|
|
if ( !theImage )
|
|
{
|
|
return nullptr;
|
|
}
|
|
delete theImage;
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
Q_FOREACH ( QgsMapLayer *layer, QgsMapLayerRegistry::instance()->mapLayers() )
|
|
{
|
|
if ( !mAccessControl->layerReadPermission( layer ) )
|
|
{
|
|
throw QgsMapServiceException( "Security", "You are not allowed to access to the layer: " + layer->name() );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope
|
|
//there's LOTS of potential exit paths here, so we avoid having to restore the filters manually
|
|
QScopedPointer< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() );
|
|
|
|
applyRequestedLayerFilters( layersList, filterRestorer->originalFilters() );
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
applyAccessControlLayersFilters( layersList, filterRestorer->originalFilters() );
|
|
#endif
|
|
|
|
QStringList selectedLayerIdList = applyFeatureSelections( layersList );
|
|
|
|
//GetPrint request needs a template parameter
|
|
if ( !mParameters.contains( "TEMPLATE" ) )
|
|
{
|
|
clearFeatureSelections( selectedLayerIdList );
|
|
throw QgsMapServiceException( "ParameterMissing", "The TEMPLATE parameter is required for the GetPrint request" );
|
|
}
|
|
|
|
QList< QPair< QgsVectorLayer*, QgsFeatureRenderer*> > bkVectorRenderers;
|
|
QList< QPair< QgsRasterLayer*, QgsRasterRenderer* > > bkRasterRenderers;
|
|
QList< QPair< QgsVectorLayer*, double > > labelTransparencies;
|
|
QList< QPair< QgsVectorLayer*, double > > labelBufferTransparencies;
|
|
|
|
applyOpacities( layersList, bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
|
|
|
|
QStringList highlightLayers;
|
|
QgsComposition* c = mConfigParser->createPrintComposition( mParameters[ "TEMPLATE" ], mMapRenderer, QMap<QString, QString>( mParameters ), highlightLayers );
|
|
if ( !c )
|
|
{
|
|
restoreOpacities( bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
|
|
clearFeatureSelections( selectedLayerIdList );
|
|
QgsWmsConfigParser::removeHighlightLayers( highlightLayers );
|
|
return nullptr;
|
|
}
|
|
|
|
QByteArray* ba = nullptr;
|
|
c->setPlotStyle( QgsComposition::Print );
|
|
|
|
//SVG export without a running X-Server is a problem. See e.g. http://developer.qt.nokia.com/forums/viewthread/2038
|
|
if ( formatString.compare( "svg", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
c->setPlotStyle( QgsComposition::Print );
|
|
|
|
QSvgGenerator generator;
|
|
ba = new QByteArray();
|
|
QBuffer svgBuffer( ba );
|
|
generator.setOutputDevice( &svgBuffer );
|
|
int width = ( int )( c->paperWidth() * c->printResolution() / 25.4 ); //width in pixel
|
|
int height = ( int )( c->paperHeight() * c->printResolution() / 25.4 ); //height in pixel
|
|
generator.setSize( QSize( width, height ) );
|
|
generator.setResolution( c->printResolution() ); //because the rendering is done in mm, convert the dpi
|
|
|
|
QPainter p( &generator );
|
|
if ( c->printAsRaster() ) //embed one raster into the svg
|
|
{
|
|
QImage img = c->printPageAsRaster( 0 );
|
|
p.drawImage( QRect( 0, 0, width, height ), img, QRectF( 0, 0, img.width(), img.height() ) );
|
|
}
|
|
else
|
|
{
|
|
c->renderPage( &p, 0 );
|
|
}
|
|
p.end();
|
|
}
|
|
else if ( formatString.compare( "png", Qt::CaseInsensitive ) == 0 || formatString.compare( "jpg", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
QImage image = c->printPageAsRaster( 0 ); //can only return the first page if pixmap is requested
|
|
|
|
ba = new QByteArray();
|
|
QBuffer buffer( ba );
|
|
buffer.open( QIODevice::WriteOnly );
|
|
image.save( &buffer, formatString.toLocal8Bit().data(), -1 );
|
|
}
|
|
else if ( formatString.compare( "pdf", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
QTemporaryFile tempFile;
|
|
if ( !tempFile.open() )
|
|
{
|
|
delete c;
|
|
restoreOpacities( bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
|
|
clearFeatureSelections( selectedLayerIdList );
|
|
return nullptr;
|
|
}
|
|
|
|
c->exportAsPDF( tempFile.fileName() );
|
|
ba = new QByteArray();
|
|
*ba = tempFile.readAll();
|
|
}
|
|
else //unknown format
|
|
{
|
|
restoreOpacities( bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
|
|
clearFeatureSelections( selectedLayerIdList );
|
|
throw QgsMapServiceException( "InvalidFormat", "Output format '" + formatString + "' is not supported in the GetPrint request" );
|
|
}
|
|
|
|
restoreOpacities( bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
|
|
clearFeatureSelections( selectedLayerIdList );
|
|
QgsWmsConfigParser::removeHighlightLayers( highlightLayers );
|
|
|
|
delete c;
|
|
return ba;
|
|
}
|
|
|
|
#if 0
|
|
QImage* QgsWMSServer::printCompositionToImage( QgsComposition* c ) const
|
|
{
|
|
int width = ( int )( c->paperWidth() * c->printResolution() / 25.4 ); //width in pixel
|
|
int height = ( int )( c->paperHeight() * c->printResolution() / 25.4 ); //height in pixel
|
|
QImage* image = new QImage( QSize( width, height ), QImage::Format_ARGB32 );
|
|
image->setDotsPerMeterX( c->printResolution() / 25.4 * 1000 );
|
|
image->setDotsPerMeterY( c->printResolution() / 25.4 * 1000 );
|
|
image->fill( 0 );
|
|
QPainter p( image );
|
|
QRectF sourceArea( 0, 0, c->paperWidth(), c->paperHeight() );
|
|
QRectF targetArea( 0, 0, width, height );
|
|
c->render( &p, targetArea, sourceArea );
|
|
p.end();
|
|
return image;
|
|
}
|
|
#endif
|
|
|
|
QImage* QgsWmsServer::getMap( HitTest* hitTest )
|
|
{
|
|
if ( !checkMaximumWidthHeight() )
|
|
{
|
|
throw QgsMapServiceException( "Size error", "The requested map size is too large" );
|
|
}
|
|
QStringList layersList, stylesList, layerIdList;
|
|
QImage* theImage = initializeRendering( layersList, stylesList, layerIdList );
|
|
|
|
QPainter thePainter( theImage );
|
|
thePainter.setRenderHint( QPainter::Antialiasing ); //make it look nicer
|
|
|
|
QStringList layerSet = mMapRenderer->layerSet();
|
|
QStringList highlightLayers = QgsWmsConfigParser::addHighlightLayers( mParameters, layerSet );
|
|
mMapRenderer->setLayerSet( layerSet );
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
Q_FOREACH ( QgsMapLayer *layer, QgsMapLayerRegistry::instance()->mapLayers() )
|
|
{
|
|
if ( !mAccessControl->layerReadPermission( layer ) )
|
|
{
|
|
throw QgsMapServiceException( "Security", "You are not allowed to access to the layer: " + layer->name() );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope
|
|
//there's LOTS of potential exit paths here, so we avoid having to restore the filters manually
|
|
QScopedPointer< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() );
|
|
|
|
applyRequestedLayerFilters( layersList, filterRestorer->originalFilters() );
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
applyAccessControlLayersFilters( layersList, filterRestorer->originalFilters() );
|
|
#endif
|
|
|
|
QStringList selectedLayerIdList = applyFeatureSelections( layersList );
|
|
|
|
QList< QPair< QgsVectorLayer*, QgsFeatureRenderer*> > bkVectorRenderers;
|
|
QList< QPair< QgsRasterLayer*, QgsRasterRenderer* > > bkRasterRenderers;
|
|
QList< QPair< QgsVectorLayer*, double > > labelTransparencies;
|
|
QList< QPair< QgsVectorLayer*, double > > labelBufferTransparencies;
|
|
|
|
applyOpacities( layersList, bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
|
|
|
|
if ( hitTest )
|
|
runHitTest( &thePainter, *hitTest );
|
|
else
|
|
{
|
|
mMapRenderer->render( &thePainter );
|
|
}
|
|
|
|
if ( mConfigParser )
|
|
{
|
|
//draw configuration format specific overlay items
|
|
mConfigParser->drawOverlays( &thePainter, theImage->dotsPerMeterX() / 1000.0 * 25.4, theImage->width(), theImage->height() );
|
|
}
|
|
|
|
restoreOpacities( bkVectorRenderers, bkRasterRenderers, labelTransparencies, labelBufferTransparencies );
|
|
clearFeatureSelections( selectedLayerIdList );
|
|
QgsWmsConfigParser::removeHighlightLayers( highlightLayers );
|
|
|
|
// QgsMessageLog::logMessage( "clearing filters" );
|
|
if ( !hitTest )
|
|
QgsMapLayerRegistry::instance()->removeAllMapLayers();
|
|
|
|
//#ifdef QGISDEBUG
|
|
// theImage->save( QDir::tempPath() + QDir::separator() + "lastrender.png" );
|
|
//#endif
|
|
|
|
return theImage;
|
|
}
|
|
|
|
void QgsWmsServer::getMapAsDxf()
|
|
{
|
|
QgsServerStreamingDevice d( "application/dxf" , mRequestHandler );
|
|
if ( !d.open( QIODevice::WriteOnly ) )
|
|
{
|
|
throw QgsMapServiceException( "Internal server error", "Error opening output device for writing" );
|
|
}
|
|
|
|
QgsDxfExport dxf;
|
|
|
|
//BBOX
|
|
bool bboxOk;
|
|
QgsRectangle extent = _parseBBOX( mParameters.value( "BBOX", "0,0,0,0" ), bboxOk );
|
|
if ( !bboxOk )
|
|
{
|
|
extent = QgsRectangle();
|
|
}
|
|
dxf.setExtent( extent );
|
|
|
|
//get format options (for MODE, USE_TITLE_AS_LAYERNAME, SCALE, LAYERATTRIBUTES)
|
|
QMap<QString, QString > formatOptionsMap;
|
|
readFormatOptions( formatOptionsMap );
|
|
|
|
QList< QPair<QgsVectorLayer *, int > > layers;
|
|
readDxfLayerSettings( layers, formatOptionsMap );
|
|
dxf.addLayers( layers );
|
|
|
|
dxf.setLayerTitleAsName( formatOptionsMap.contains( "USE_TITLE_AS_LAYERNAME" ) );
|
|
|
|
//MODE
|
|
QMap<QString, QString>::const_iterator modeIt = formatOptionsMap.find( "MODE" );
|
|
|
|
QgsDxfExport::SymbologyExport se;
|
|
if ( modeIt == formatOptionsMap.constEnd() )
|
|
{
|
|
se = QgsDxfExport::NoSymbology;
|
|
}
|
|
else
|
|
{
|
|
if ( modeIt->compare( "SymbolLayerSymbology", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
se = QgsDxfExport::SymbolLayerSymbology;
|
|
}
|
|
else if ( modeIt->compare( "FeatureSymbology", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
se = QgsDxfExport::FeatureSymbology;
|
|
}
|
|
else
|
|
{
|
|
se = QgsDxfExport::NoSymbology;
|
|
}
|
|
}
|
|
dxf.setSymbologyExport( se );
|
|
|
|
//SCALE
|
|
QMap<QString, QString>::const_iterator scaleIt = formatOptionsMap.find( "SCALE" );
|
|
if ( scaleIt != formatOptionsMap.constEnd() )
|
|
{
|
|
dxf.setSymbologyScaleDenominator( scaleIt->toDouble() );
|
|
}
|
|
|
|
QString codec = "ISO-8859-1";
|
|
QMap<QString, QString>::const_iterator codecIt = formatOptionsMap.find( "CODEC" );
|
|
if ( codecIt != formatOptionsMap.constEnd() )
|
|
{
|
|
codec = formatOptionsMap.value( "CODEC" );
|
|
}
|
|
|
|
dxf.writeToFile( &d, codec );
|
|
d.close();
|
|
}
|
|
|
|
int QgsWmsServer::getFeatureInfo( QDomDocument& result, const QString& version )
|
|
{
|
|
if ( !mMapRenderer || !mConfigParser )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
result.clear();
|
|
QStringList layersList, stylesList;
|
|
bool conversionSuccess;
|
|
|
|
for ( QMap<QString, QString>::const_iterator it = mParameters.constBegin(); it != mParameters.constEnd(); ++it )
|
|
{
|
|
QgsMessageLog::logMessage( QString( "%1 // %2" ).arg( it.key(), it.value() ) );
|
|
}
|
|
|
|
if ( readLayersAndStyles( layersList, stylesList ) != 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
if ( initializeSLDParser( layersList, stylesList ) != 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
QImage* outputImage = createImage();
|
|
if ( !outputImage )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if ( configureMapRender( outputImage ) != 0 )
|
|
{
|
|
delete outputImage;
|
|
return 2;
|
|
}
|
|
|
|
QgsMessageLog::logMessage( "mMapRenderer->extent(): " + mMapRenderer->extent().toString() );
|
|
QgsMessageLog::logMessage( QString( "mMapRenderer width = %1 height = %2" ).arg( mMapRenderer->outputSize().width() ).arg( mMapRenderer->outputSize().height() ) );
|
|
QgsMessageLog::logMessage( QString( "mMapRenderer->mapUnitsPerPixel() = %1" ).arg( mMapRenderer->mapUnitsPerPixel() ) );
|
|
|
|
//find out the current scale denominator and set it to the SLD parser
|
|
QgsScaleCalculator scaleCalc(( outputImage->logicalDpiX() + outputImage->logicalDpiY() ) / 2, mMapRenderer->destinationCrs().mapUnits() );
|
|
QgsRectangle mapExtent = mMapRenderer->extent();
|
|
double scaleDenominator = scaleCalc.calculate( mapExtent, outputImage->width() );
|
|
mConfigParser->setScaleDenominator( scaleDenominator );
|
|
delete outputImage; //no longer needed for feature info
|
|
|
|
//read FEATURE_COUNT
|
|
int featureCount = 1;
|
|
if ( mParameters.contains( "FEATURE_COUNT" ) )
|
|
{
|
|
featureCount = mParameters[ "FEATURE_COUNT" ].toInt( &conversionSuccess );
|
|
if ( !conversionSuccess )
|
|
{
|
|
featureCount = 1;
|
|
}
|
|
}
|
|
|
|
//read QUERY_LAYERS
|
|
if ( !mParameters.contains( "QUERY_LAYERS" ) )
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
QStringList queryLayerList = mParameters[ "QUERY_LAYERS" ].split( ",", QString::SkipEmptyParts );
|
|
if ( queryLayerList.size() < 1 )
|
|
{
|
|
return 4;
|
|
}
|
|
|
|
//read I,J resp. X,Y
|
|
QString iString = mParameters.value( "I", mParameters.value( "X" ) );
|
|
int i = iString.toInt( &conversionSuccess );
|
|
if ( !conversionSuccess )
|
|
{
|
|
i = -1;
|
|
}
|
|
|
|
QString jString = mParameters.value( "J", mParameters.value( "Y" ) );
|
|
int j = jString.toInt( &conversionSuccess );
|
|
if ( !conversionSuccess )
|
|
{
|
|
j = -1;
|
|
}
|
|
|
|
//Normally, I/J or X/Y are mandatory parameters.
|
|
//However, in order to make attribute only queries via the FILTER parameter, it is allowed to skip them if the FILTER parameter is there
|
|
|
|
QgsRectangle* featuresRect = nullptr;
|
|
QScopedPointer<QgsPoint> infoPoint;
|
|
|
|
if ( i == -1 || j == -1 )
|
|
{
|
|
if ( mParameters.contains( "FILTER" ) )
|
|
{
|
|
featuresRect = new QgsRectangle();
|
|
}
|
|
else
|
|
{
|
|
throw QgsMapServiceException( "ParameterMissing", "I/J parameters are required for GetFeatureInfo" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
infoPoint.reset( new QgsPoint() );
|
|
if ( !infoPointToMapCoordinates( i, j, infoPoint.data(), mMapRenderer ) )
|
|
{
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
//get the layer registered in QgsMapLayerRegistry and apply possible filters
|
|
( void )layerSet( layersList, stylesList, mMapRenderer->destinationCrs() );
|
|
|
|
//scoped pointer to restore all original layer filters (subsetStrings) when pointer goes out of scope
|
|
//there's LOTS of potential exit paths here, so we avoid having to restore the filters manually
|
|
QScopedPointer< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer() );
|
|
applyRequestedLayerFilters( layersList, filterRestorer->originalFilters() );
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
applyAccessControlLayersFilters( layersList, filterRestorer->originalFilters() );
|
|
#endif
|
|
|
|
QDomElement getFeatureInfoElement;
|
|
QString infoFormat = mParameters.value( "INFO_FORMAT" );
|
|
if ( infoFormat.startsWith( "application/vnd.ogc.gml" ) )
|
|
{
|
|
getFeatureInfoElement = result.createElement( "wfs:FeatureCollection" );
|
|
getFeatureInfoElement.setAttribute( "xmlns:wfs", "http://www.opengis.net/wfs" );
|
|
getFeatureInfoElement.setAttribute( "xmlns:ogc", "http://www.opengis.net/ogc" );
|
|
getFeatureInfoElement.setAttribute( "xmlns:gml", "http://www.opengis.net/gml" );
|
|
getFeatureInfoElement.setAttribute( "xmlns:ows", "http://www.opengis.net/ows" );
|
|
getFeatureInfoElement.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
|
|
getFeatureInfoElement.setAttribute( "xmlns:qgs", "http://qgis.org/gml" );
|
|
getFeatureInfoElement.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" );
|
|
getFeatureInfoElement.setAttribute( "xsi:schemaLocation", "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://qgis.org/gml" );
|
|
}
|
|
else
|
|
{
|
|
QString featureInfoElemName = mConfigParser->featureInfoDocumentElement( "GetFeatureInfoResponse" );
|
|
QString featureInfoElemNS = mConfigParser->featureInfoDocumentElementNS();
|
|
if ( featureInfoElemNS.isEmpty() )
|
|
{
|
|
getFeatureInfoElement = result.createElement( featureInfoElemName );
|
|
}
|
|
else
|
|
{
|
|
getFeatureInfoElement = result.createElementNS( featureInfoElemNS, featureInfoElemName );
|
|
}
|
|
//feature info schema
|
|
QString featureInfoSchema = mConfigParser->featureInfoSchema();
|
|
if ( !featureInfoSchema.isEmpty() )
|
|
{
|
|
getFeatureInfoElement.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" );
|
|
getFeatureInfoElement.setAttribute( "xsi:schemaLocation", featureInfoSchema );
|
|
}
|
|
}
|
|
result.appendChild( getFeatureInfoElement );
|
|
|
|
QStringList nonIdentifiableLayers = mConfigParser->identifyDisabledLayers();
|
|
|
|
//Render context is needed to determine feature visibility for vector layers
|
|
QgsRenderContext renderContext;
|
|
if ( mMapRenderer )
|
|
{
|
|
renderContext.setExtent( mMapRenderer->extent() );
|
|
renderContext.setRasterScaleFactor( 1.0 );
|
|
renderContext.setMapToPixel( *( mMapRenderer->coordinateTransform() ) );
|
|
renderContext.setRendererScale( mMapRenderer->scale() );
|
|
renderContext.setScaleFactor( mMapRenderer->outputDpi() / 25.4 );
|
|
renderContext.setPainter( nullptr );
|
|
}
|
|
|
|
bool sia2045 = mConfigParser->featureInfoFormatSIA2045();
|
|
|
|
//layers can have assigned a different name for GetCapabilities
|
|
QHash<QString, QString> layerAliasMap = mConfigParser->featureInfoLayerAliasMap();
|
|
|
|
QList<QgsMapLayer*> layerList;
|
|
QgsMapLayer* currentLayer = nullptr;
|
|
QStringList::const_iterator layerIt;
|
|
for ( layerIt = queryLayerList.constBegin(); layerIt != queryLayerList.constEnd(); ++layerIt )
|
|
{
|
|
//create maplayers from sld parser (several layers are possible in case of feature info on a group)
|
|
layerList = mConfigParser->mapLayerFromStyle( *layerIt, "" );
|
|
QList<QgsMapLayer*>::iterator layerListIt = layerList.begin();
|
|
for ( ; layerListIt != layerList.end(); ++layerListIt )
|
|
{
|
|
currentLayer = *layerListIt;
|
|
if ( !currentLayer || nonIdentifiableLayers.contains( currentLayer->id() ) )
|
|
{
|
|
continue;
|
|
}
|
|
QgsMapLayer * registeredMapLayer = QgsMapLayerRegistry::instance()->mapLayer( currentLayer->id() );
|
|
if ( registeredMapLayer )
|
|
{
|
|
currentLayer = registeredMapLayer;
|
|
}
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
if ( !mAccessControl->layerReadPermission( currentLayer ) )
|
|
{
|
|
throw QgsMapServiceException( "Security", "You are not allowed to access to the layer: " + currentLayer->name() );
|
|
}
|
|
#endif
|
|
|
|
//skip layer if not visible at current map scale
|
|
bool useScaleConstraint = ( scaleDenominator > 0 && currentLayer->hasScaleBasedVisibility() );
|
|
if ( useScaleConstraint && ( currentLayer->minimumScale() > scaleDenominator || currentLayer->maximumScale() < scaleDenominator ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//switch depending on vector or raster
|
|
QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( currentLayer );
|
|
|
|
QDomElement layerElement;
|
|
if ( infoFormat.startsWith( "application/vnd.ogc.gml" ) )
|
|
{
|
|
layerElement = getFeatureInfoElement;
|
|
}
|
|
else
|
|
{
|
|
layerElement = result.createElement( "Layer" );
|
|
QString layerName = currentLayer->name();
|
|
if ( mConfigParser && mConfigParser->useLayerIds() )
|
|
layerName = currentLayer->id();
|
|
else if ( !currentLayer->shortName().isEmpty() )
|
|
layerName = currentLayer->shortName();
|
|
|
|
//check if the layer is given a different name for GetFeatureInfo output
|
|
QHash<QString, QString>::const_iterator layerAliasIt = layerAliasMap.find( layerName );
|
|
if ( layerAliasIt != layerAliasMap.constEnd() )
|
|
{
|
|
layerName = layerAliasIt.value();
|
|
}
|
|
layerElement.setAttribute( "name", layerName );
|
|
getFeatureInfoElement.appendChild( layerElement );
|
|
if ( sia2045 ) //the name might not be unique after alias replacement
|
|
{
|
|
layerElement.setAttribute( "id", currentLayer->id() );
|
|
}
|
|
}
|
|
|
|
if ( vectorLayer )
|
|
{
|
|
if ( featureInfoFromVectorLayer( vectorLayer, infoPoint.data(), featureCount, result, layerElement, mMapRenderer, renderContext,
|
|
version, infoFormat, featuresRect ) != 0 )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else //raster layer
|
|
{
|
|
if ( infoFormat.startsWith( "application/vnd.ogc.gml" ) )
|
|
{
|
|
layerElement = result.createElement( "gml:featureMember"/*wfs:FeatureMember*/ );
|
|
getFeatureInfoElement.appendChild( layerElement );
|
|
}
|
|
|
|
QgsRasterLayer* rasterLayer = dynamic_cast<QgsRasterLayer*>( currentLayer );
|
|
if ( rasterLayer )
|
|
{
|
|
if ( !infoPoint.data() )
|
|
{
|
|
continue;
|
|
}
|
|
QgsPoint layerInfoPoint = mMapRenderer->mapToLayerCoordinates( currentLayer, *( infoPoint.data() ) );
|
|
if ( featureInfoFromRasterLayer( rasterLayer, &layerInfoPoint, result, layerElement, version, infoFormat ) != 0 )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( featuresRect )
|
|
{
|
|
if ( infoFormat.startsWith( "application/vnd.ogc.gml" ) )
|
|
{
|
|
QDomElement bBoxElem = result.createElement( "gml:boundedBy" );
|
|
QDomElement boxElem;
|
|
int gmlVersion = infoFormat.startsWith( "application/vnd.ogc.gml/3" ) ? 3 : 2;
|
|
if ( gmlVersion < 3 )
|
|
{
|
|
boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect, result, 8 );
|
|
}
|
|
else
|
|
{
|
|
boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect, result, 8 );
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem crs = mMapRenderer->destinationCrs();
|
|
if ( crs.isValid() )
|
|
{
|
|
boxElem.setAttribute( "srsName", crs.authid() );
|
|
}
|
|
bBoxElem.appendChild( boxElem );
|
|
getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
|
|
}
|
|
else
|
|
{
|
|
QDomElement bBoxElem = result.createElement( "BoundingBox" );
|
|
bBoxElem.setAttribute( "CRS", mMapRenderer->destinationCrs().authid() );
|
|
bBoxElem.setAttribute( "minx", qgsDoubleToString( featuresRect->xMinimum(), 8 ) );
|
|
bBoxElem.setAttribute( "maxx", qgsDoubleToString( featuresRect->xMaximum(), 8 ) );
|
|
bBoxElem.setAttribute( "miny", qgsDoubleToString( featuresRect->yMinimum(), 8 ) );
|
|
bBoxElem.setAttribute( "maxy", qgsDoubleToString( featuresRect->yMaximum(), 8 ) );
|
|
getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
|
|
}
|
|
}
|
|
|
|
if ( sia2045 && infoFormat.compare( "text/xml", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
convertFeatureInfoToSIA2045( result );
|
|
}
|
|
|
|
//force restoration of original filters
|
|
filterRestorer.reset();
|
|
|
|
QgsMapLayerRegistry::instance()->removeAllMapLayers();
|
|
delete featuresRect;
|
|
return 0;
|
|
}
|
|
|
|
QImage* QgsWmsServer::initializeRendering( QStringList& layersList, QStringList& stylesList, QStringList& layerIdList )
|
|
{
|
|
if ( !mConfigParser )
|
|
{
|
|
QgsMessageLog::logMessage( "Error: mSLDParser is 0" );
|
|
return nullptr;
|
|
}
|
|
|
|
if ( !mMapRenderer )
|
|
{
|
|
QgsMessageLog::logMessage( "Error: mMapRenderer is 0" );
|
|
return nullptr;
|
|
}
|
|
|
|
if ( readLayersAndStyles( layersList, stylesList ) != 0 )
|
|
{
|
|
QgsMessageLog::logMessage( "error reading layers and styles" );
|
|
return nullptr;
|
|
}
|
|
|
|
if ( initializeSLDParser( layersList, stylesList ) != 0 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
//pass external GML to the SLD parser.
|
|
QString gml = mParameters.value( "GML" );
|
|
if ( !gml.isEmpty() )
|
|
{
|
|
QDomDocument* gmlDoc = new QDomDocument();
|
|
if ( gmlDoc->setContent( gml, true ) )
|
|
{
|
|
QString layerName = gmlDoc->documentElement().attribute( "layerName" );
|
|
QgsMessageLog::logMessage( "Adding entry with key: " + layerName + " to external GML data" );
|
|
mConfigParser->addExternalGMLData( layerName, gmlDoc );
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( "Error, could not add external GML to QgsSLDParser" );
|
|
delete gmlDoc;
|
|
}
|
|
}
|
|
|
|
QImage* theImage = createImage();
|
|
if ( !theImage )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if ( configureMapRender( theImage ) != 0 )
|
|
{
|
|
delete theImage;
|
|
return nullptr;
|
|
}
|
|
|
|
//find out the current scale denominater and set it to the SLD parser
|
|
QgsScaleCalculator scaleCalc(( theImage->logicalDpiX() + theImage->logicalDpiY() ) / 2, mMapRenderer->destinationCrs().mapUnits() );
|
|
QgsRectangle mapExtent = mMapRenderer->extent();
|
|
mConfigParser->setScaleDenominator( scaleCalc.calculate( mapExtent, theImage->width() ) );
|
|
|
|
layerIdList = layerSet( layersList, stylesList, mMapRenderer->destinationCrs() );
|
|
#ifdef QGISDEBUG
|
|
QgsMessageLog::logMessage( QString( "Number of layers to be rendered. %1" ).arg( layerIdList.count() ) );
|
|
#endif
|
|
mMapRenderer->setLayerSet( layerIdList );
|
|
|
|
// load label settings
|
|
mConfigParser->loadLabelSettings( mMapRenderer->labelingEngine() );
|
|
|
|
return theImage;
|
|
}
|
|
|
|
QImage* QgsWmsServer::createImage( int width, int height ) const
|
|
{
|
|
bool conversionSuccess;
|
|
|
|
if ( width < 0 )
|
|
{
|
|
width = mParameters.value( "WIDTH", "0" ).toInt( &conversionSuccess );
|
|
if ( !conversionSuccess )
|
|
width = 0;
|
|
}
|
|
|
|
if ( height < 0 )
|
|
{
|
|
height = mParameters.value( "HEIGHT", "0" ).toInt( &conversionSuccess );
|
|
if ( !conversionSuccess )
|
|
{
|
|
height = 0;
|
|
}
|
|
}
|
|
|
|
if ( width < 0 || height < 0 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
QImage* theImage = nullptr;
|
|
|
|
//is format jpeg?
|
|
QString format = mParameters.value( "FORMAT" );
|
|
bool jpeg = format.compare( "jpg", Qt::CaseInsensitive ) == 0
|
|
|| format.compare( "jpeg", Qt::CaseInsensitive ) == 0
|
|
|| format.compare( "image/jpeg", Qt::CaseInsensitive ) == 0;
|
|
|
|
//transparent parameter
|
|
bool transparent = mParameters.value( "TRANSPARENT" ).compare( "true", Qt::CaseInsensitive ) == 0;
|
|
|
|
//use alpha channel only if necessary because it slows down performance
|
|
if ( transparent && !jpeg )
|
|
{
|
|
theImage = new QImage( width, height, QImage::Format_ARGB32_Premultiplied );
|
|
theImage->fill( 0 );
|
|
}
|
|
else
|
|
{
|
|
theImage = new QImage( width, height, QImage::Format_RGB32 );
|
|
theImage->fill( qRgb( 255, 255, 255 ) );
|
|
}
|
|
|
|
if ( !theImage )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
//apply DPI parameter if present. This is an extension of Qgis Mapserver compared to WMS 1.3.
|
|
//Because of backwards compatibility, this parameter is optional
|
|
double OGC_PX_M = 0.00028; // OGC reference pixel size in meter, also used by qgis
|
|
int dpm = 1 / OGC_PX_M;
|
|
if ( mParameters.contains( "DPI" ) )
|
|
{
|
|
int dpi = mParameters[ "DPI" ].toInt( &conversionSuccess );
|
|
if ( conversionSuccess )
|
|
{
|
|
dpm = dpi / 0.0254;
|
|
}
|
|
}
|
|
theImage->setDotsPerMeterX( dpm );
|
|
theImage->setDotsPerMeterY( dpm );
|
|
return theImage;
|
|
}
|
|
|
|
int QgsWmsServer::configureMapRender( const QPaintDevice* paintDevice ) const
|
|
{
|
|
if ( !mMapRenderer || !paintDevice )
|
|
{
|
|
return 1; //paint device is needed for height, width, dpi
|
|
}
|
|
|
|
mMapRenderer->clearLayerCoordinateTransforms();
|
|
mMapRenderer->setOutputSize( QSize( paintDevice->width(), paintDevice->height() ), paintDevice->logicalDpiX() );
|
|
|
|
//map extent
|
|
bool bboxOk;
|
|
QgsRectangle mapExtent = _parseBBOX( mParameters.value( "BBOX", "0,0,0,0" ), bboxOk );
|
|
if ( !bboxOk )
|
|
{
|
|
//throw a service exception
|
|
throw QgsMapServiceException( "InvalidParameterValue", "Invalid BBOX parameter" );
|
|
}
|
|
|
|
QgsUnitTypes::DistanceUnit mapUnits = QgsUnitTypes::DistanceDegrees;
|
|
|
|
QString crs = mParameters.value( "CRS", mParameters.value( "SRS" ) );
|
|
|
|
QgsCoordinateReferenceSystem outputCRS;
|
|
|
|
//wms spec says that CRS parameter is mandatory.
|
|
//we don't reject the request if it is not there but disable reprojection on the fly
|
|
if ( crs.isEmpty() )
|
|
{
|
|
//disable on the fly projection
|
|
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectionsEnabled", 0 );
|
|
}
|
|
else
|
|
{
|
|
//enable on the fly projection
|
|
QgsMessageLog::logMessage( "enable on the fly projection" );
|
|
QgsProject::instance()->writeEntry( "SpatialRefSys", "/ProjectionsEnabled", 1 );
|
|
|
|
//destination SRS
|
|
outputCRS = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crs );
|
|
if ( !outputCRS.isValid() )
|
|
{
|
|
QgsMessageLog::logMessage( "Error, could not create output CRS from EPSG" );
|
|
throw QgsMapServiceException( "InvalidCRS", "Could not create output CRS" );
|
|
}
|
|
|
|
//read layer coordinate transforms from project file (e.g. ct with special datum shift)
|
|
if ( mConfigParser )
|
|
{
|
|
QList< QPair< QString, QgsLayerCoordinateTransform > > lt = mConfigParser->layerCoordinateTransforms();
|
|
QList< QPair< QString, QgsLayerCoordinateTransform > >::const_iterator ltIt = lt.constBegin();
|
|
for ( ; ltIt != lt.constEnd(); ++ltIt )
|
|
{
|
|
QgsLayerCoordinateTransform t = ltIt->second;
|
|
mMapRenderer->addLayerCoordinateTransform( ltIt->first, t.srcAuthId, t.destAuthId, t.srcDatumTransform, t.destDatumTransform );
|
|
}
|
|
}
|
|
|
|
//then set destinationCrs
|
|
mMapRenderer->setDestinationCrs( outputCRS );
|
|
mMapRenderer->setProjectionsEnabled( true );
|
|
mapUnits = outputCRS.mapUnits();
|
|
}
|
|
mMapRenderer->setMapUnits( mapUnits );
|
|
|
|
// Change x- and y- of BBOX for WMS 1.3.0 if axis inverted
|
|
QString version = mParameters.value( "VERSION", "1.3.0" );
|
|
if ( version != "1.1.1" && outputCRS.hasAxisInverted() )
|
|
{
|
|
mapExtent.invert();
|
|
}
|
|
|
|
mMapRenderer->setExtent( mapExtent );
|
|
|
|
if ( mConfigParser )
|
|
{
|
|
mMapRenderer->setOutputUnits( mConfigParser->outputUnits() );
|
|
}
|
|
else
|
|
{
|
|
mMapRenderer->setOutputUnits( QgsMapRenderer::Pixels ); //SLD units are in pixels normally
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int QgsWmsServer::readLayersAndStyles( QStringList& layersList, QStringList& stylesList ) const
|
|
{
|
|
//get layer and style lists from the parameters trying LAYERS and LAYER as well as STYLE and STYLES for GetLegendGraphic compatibility
|
|
layersList = mParameters.value( "LAYER" ).split( ",", QString::SkipEmptyParts );
|
|
layersList = layersList + mParameters.value( "LAYERS" ).split( ",", QString::SkipEmptyParts );
|
|
stylesList = mParameters.value( "STYLE" ).split( ",", QString::SkipEmptyParts );
|
|
stylesList = stylesList + mParameters.value( "STYLES" ).split( ",", QString::SkipEmptyParts );
|
|
|
|
return 0;
|
|
}
|
|
|
|
int QgsWmsServer::initializeSLDParser( QStringList& layersList, QStringList& stylesList )
|
|
{
|
|
QString xml = mParameters.value( "SLD" );
|
|
if ( !xml.isEmpty() )
|
|
{
|
|
//ignore LAYERS and STYLES and take those information from the SLD
|
|
QDomDocument* theDocument = new QDomDocument( "user.sld" );
|
|
QString errorMsg;
|
|
int errorLine, errorColumn;
|
|
|
|
if ( !theDocument->setContent( xml, true, &errorMsg, &errorLine, &errorColumn ) )
|
|
{
|
|
//std::cout << xml.toLatin1().data() << std::endl;
|
|
QgsMessageLog::logMessage( "Error, could not create DomDocument from SLD" );
|
|
QgsMessageLog::logMessage( QString( "The error message is: %1" ).arg( errorMsg ) );
|
|
delete theDocument;
|
|
return 1;
|
|
}
|
|
|
|
QgsSLDConfigParser* userSLDParser = new QgsSLDConfigParser( theDocument, mParameters );
|
|
userSLDParser->setFallbackParser( mConfigParser );
|
|
mConfigParser = userSLDParser;
|
|
mOwnsConfigParser = true;
|
|
//now replace the content of layersList and stylesList (if present)
|
|
layersList.clear();
|
|
stylesList.clear();
|
|
QStringList layersSTDList;
|
|
QStringList stylesSTDList;
|
|
if ( mConfigParser->layersAndStyles( layersSTDList, stylesSTDList ) != 0 )
|
|
{
|
|
QgsMessageLog::logMessage( "Error, no layers and styles found in SLD" );
|
|
return 2;
|
|
}
|
|
QStringList::const_iterator layersIt;
|
|
QStringList::const_iterator stylesIt;
|
|
for ( layersIt = layersSTDList.constBegin(), stylesIt = stylesSTDList.constBegin(); layersIt != layersSTDList.constEnd(); ++layersIt, ++stylesIt )
|
|
{
|
|
layersList << *layersIt;
|
|
stylesList << *stylesIt;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool QgsWmsServer::infoPointToMapCoordinates( int i, int j, QgsPoint* infoPoint, QgsMapRenderer* mapRenderer )
|
|
{
|
|
if ( !mapRenderer || !infoPoint )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
double xRes = mapRenderer->extent().width() / mapRenderer->width();
|
|
double yRes = mapRenderer->extent().height() / mapRenderer->height();
|
|
infoPoint->setX( mapRenderer->extent().xMinimum() + i * xRes + xRes / 2.0 );
|
|
infoPoint->setY( mapRenderer->extent().yMaximum() - j * yRes - yRes / 2.0 );
|
|
return true;
|
|
}
|
|
|
|
int QgsWmsServer::featureInfoFromVectorLayer( QgsVectorLayer* layer,
|
|
const QgsPoint* infoPoint,
|
|
int nFeatures,
|
|
QDomDocument& infoDocument,
|
|
QDomElement& layerElement,
|
|
QgsMapRenderer* mapRender,
|
|
QgsRenderContext& renderContext,
|
|
const QString& version,
|
|
const QString& infoFormat,
|
|
QgsRectangle* featureBBox ) const
|
|
{
|
|
if ( !layer || !mapRender )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
//we need a selection rect (0.01 of map width)
|
|
QgsRectangle mapRect = mapRender->extent();
|
|
QgsRectangle layerRect = mapRender->mapToLayerCoordinates( layer, mapRect );
|
|
|
|
|
|
QgsRectangle searchRect;
|
|
|
|
//info point could be 0 in case there is only an attribute filter
|
|
if ( infoPoint )
|
|
{
|
|
searchRect = featureInfoSearchRect( layer, mapRender, renderContext, *infoPoint );
|
|
}
|
|
else if ( mParameters.contains( "BBOX" ) )
|
|
{
|
|
searchRect = layerRect;
|
|
}
|
|
|
|
//do a select with searchRect and go through all the features
|
|
|
|
QgsFeature feature;
|
|
QgsAttributes featureAttributes;
|
|
int featureCounter = 0;
|
|
layer->updateFields();
|
|
const QgsFields& fields = layer->pendingFields();
|
|
bool addWktGeometry = mConfigParser && mConfigParser->featureInfoWithWktGeometry();
|
|
bool segmentizeWktGeometry = mConfigParser && mConfigParser->segmentizeFeatureInfoWktGeometry();
|
|
const QSet<QString>& excludedAttributes = layer->excludeAttributesWms();
|
|
|
|
QgsFeatureRequest fReq;
|
|
bool hasGeometry = addWktGeometry || featureBBox;
|
|
fReq.setFlags((( hasGeometry ) ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) | QgsFeatureRequest::ExactIntersect );
|
|
|
|
if ( ! searchRect.isEmpty() )
|
|
{
|
|
fReq.setFilterRect( searchRect );
|
|
}
|
|
else
|
|
{
|
|
fReq.setFlags( fReq.flags() & ~ QgsFeatureRequest::ExactIntersect );
|
|
}
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
mAccessControl->filterFeatures( layer, fReq );
|
|
|
|
QStringList attributes;
|
|
QgsField field;
|
|
Q_FOREACH ( field, layer->pendingFields().toList() )
|
|
{
|
|
attributes.append( field.name() );
|
|
}
|
|
attributes = mAccessControl->layerAttributes( layer, attributes );
|
|
fReq.setSubsetOfAttributes( attributes, layer->pendingFields() );
|
|
#endif
|
|
|
|
QgsFeatureIterator fit = layer->getFeatures( fReq );
|
|
|
|
bool featureBBoxInitialized = false;
|
|
while ( fit.nextFeature( feature ) )
|
|
{
|
|
++featureCounter;
|
|
if ( featureCounter > nFeatures )
|
|
{
|
|
break;
|
|
}
|
|
|
|
QgsFeatureRenderer* r2 = layer->renderer();
|
|
if ( !r2 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
renderContext.expressionContext().setFeature( feature );
|
|
|
|
//check if feature is rendered at all
|
|
r2->startRender( renderContext, layer->pendingFields() );
|
|
bool render = r2->willRenderFeature( feature, renderContext );
|
|
r2->stopRender( renderContext );
|
|
if ( !render )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QgsRectangle box;
|
|
if ( hasGeometry )
|
|
{
|
|
box = mapRender->layerExtentToOutputExtent( layer, feature.geometry().boundingBox() );
|
|
if ( featureBBox ) //extend feature info bounding box if requested
|
|
{
|
|
if ( !featureBBoxInitialized && featureBBox->isEmpty() )
|
|
{
|
|
*featureBBox = box;
|
|
featureBBoxInitialized = true;
|
|
}
|
|
else
|
|
{
|
|
featureBBox->combineExtentWith( box );
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsCoordinateReferenceSystem outputCrs = layer->crs();
|
|
if ( layer->crs() != mapRender->destinationCrs() && mapRender->hasCrsTransformEnabled() )
|
|
{
|
|
outputCrs = mapRender->destinationCrs();
|
|
}
|
|
|
|
if ( infoFormat == "application/vnd.ogc.gml" )
|
|
{
|
|
bool withGeom = layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry;
|
|
int version = infoFormat.startsWith( "application/vnd.ogc.gml/3" ) ? 3 : 2;
|
|
QString typeName = layer->name();
|
|
if ( mConfigParser && mConfigParser->useLayerIds() )
|
|
typeName = layer->id();
|
|
else if ( !layer->shortName().isEmpty() )
|
|
typeName = layer->shortName();
|
|
QDomElement elem = createFeatureGML(
|
|
&feature, layer, infoDocument, outputCrs, typeName, withGeom, version
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
, &attributes
|
|
#endif
|
|
);
|
|
QDomElement featureMemberElem = infoDocument.createElement( "gml:featureMember"/*wfs:FeatureMember*/ );
|
|
featureMemberElem.appendChild( elem );
|
|
layerElement.appendChild( featureMemberElem );
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
QDomElement featureElement = infoDocument.createElement( "Feature" );
|
|
featureElement.setAttribute( "id", FID_TO_STRING( feature.id() ) );
|
|
layerElement.appendChild( featureElement );
|
|
|
|
//read all attribute values from the feature
|
|
featureAttributes = feature.attributes();
|
|
for ( int i = 0; i < featureAttributes.count(); ++i )
|
|
{
|
|
//skip attribute if it is explicitly excluded from WMS publication
|
|
if ( excludedAttributes.contains( fields.at( i ).name() ) )
|
|
{
|
|
continue;
|
|
}
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
//skip attribute if it is excluded by access control
|
|
if ( !attributes.contains( fields.at( i ).name() ) )
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
//replace attribute name if there is an attribute alias?
|
|
QString attributeName = layer->attributeDisplayName( i );
|
|
|
|
QDomElement attributeElement = infoDocument.createElement( "Attribute" );
|
|
attributeElement.setAttribute( "name", attributeName );
|
|
attributeElement.setAttribute( "value",
|
|
replaceValueMapAndRelation(
|
|
layer, i,
|
|
featureAttributes[i].isNull() ? QString::null : QgsExpression::replaceExpressionText( featureAttributes[i].toString(), &renderContext.expressionContext() )
|
|
)
|
|
);
|
|
featureElement.appendChild( attributeElement );
|
|
}
|
|
|
|
//add maptip attribute based on html/expression (in case there is no maptip attribute)
|
|
QString mapTip = layer->mapTipTemplate();
|
|
if ( !mapTip.isEmpty() )
|
|
{
|
|
QDomElement maptipElem = infoDocument.createElement( "Attribute" );
|
|
maptipElem.setAttribute( "name", "maptip" );
|
|
maptipElem.setAttribute( "value", QgsExpression::replaceExpressionText( mapTip, &renderContext.expressionContext() ) );
|
|
featureElement.appendChild( maptipElem );
|
|
}
|
|
|
|
//append feature bounding box to feature info xml
|
|
if ( hasGeometry && mapRender && mConfigParser )
|
|
{
|
|
QDomElement bBoxElem = infoDocument.createElement( "BoundingBox" );
|
|
bBoxElem.setAttribute( version == "1.1.1" ? "SRS" : "CRS", outputCrs.authid() );
|
|
bBoxElem.setAttribute( "minx", qgsDoubleToString( box.xMinimum(), getWMSPrecision( 8 ) ) );
|
|
bBoxElem.setAttribute( "maxx", qgsDoubleToString( box.xMaximum(), getWMSPrecision( 8 ) ) );
|
|
bBoxElem.setAttribute( "miny", qgsDoubleToString( box.yMinimum(), getWMSPrecision( 8 ) ) );
|
|
bBoxElem.setAttribute( "maxy", qgsDoubleToString( box.yMaximum(), getWMSPrecision( 8 ) ) );
|
|
featureElement.appendChild( bBoxElem );
|
|
}
|
|
|
|
//also append the wkt geometry as an attribute
|
|
if ( addWktGeometry && hasGeometry )
|
|
{
|
|
QgsGeometry geom = feature.geometry();
|
|
if ( !geom.isEmpty() )
|
|
{
|
|
if ( layer->crs() != outputCrs )
|
|
{
|
|
QgsCoordinateTransform transform = mapRender->transformation( layer );
|
|
if ( transform.isValid() )
|
|
geom.transform( transform );
|
|
}
|
|
|
|
if ( segmentizeWktGeometry )
|
|
{
|
|
QgsAbstractGeometry* abstractGeom = geom.geometry();
|
|
if ( abstractGeom )
|
|
{
|
|
if ( QgsWkbTypes::isCurvedType( abstractGeom->wkbType() ) )
|
|
{
|
|
QgsAbstractGeometry* segmentizedGeom = abstractGeom-> segmentize();
|
|
geom.setGeometry( segmentizedGeom );
|
|
}
|
|
}
|
|
}
|
|
QDomElement geometryElement = infoDocument.createElement( "Attribute" );
|
|
geometryElement.setAttribute( "name", "geometry" );
|
|
geometryElement.setAttribute( "value", geom.exportToWkt( getWMSPrecision( 8 ) ) );
|
|
geometryElement.setAttribute( "type", "derived" );
|
|
featureElement.appendChild( geometryElement );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int QgsWmsServer::featureInfoFromRasterLayer( QgsRasterLayer* layer,
|
|
const QgsPoint* infoPoint,
|
|
QDomDocument& infoDocument,
|
|
QDomElement& layerElement,
|
|
const QString& version,
|
|
const QString& infoFormat ) const
|
|
{
|
|
Q_UNUSED( version );
|
|
|
|
if ( !infoPoint || !layer || !layer->dataProvider() )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
QgsMessageLog::logMessage( QString( "infoPoint: %1 %2" ).arg( infoPoint->x() ).arg( infoPoint->y() ) );
|
|
|
|
if ( !( layer->dataProvider()->capabilities() & QgsRasterDataProvider::IdentifyValue ) )
|
|
{
|
|
return 1;
|
|
}
|
|
QMap<int, QVariant> attributes;
|
|
// use context extent, width height (comes with request) to use WCS cache
|
|
// We can only use context if raster is not reprojected, otherwise it is difficult
|
|
// to guess correct source resolution
|
|
if ( mMapRenderer->hasCrsTransformEnabled() && layer->dataProvider()->crs() != mMapRenderer->destinationCrs() )
|
|
{
|
|
attributes = layer->dataProvider()->identify( *infoPoint, QgsRaster::IdentifyFormatValue ).results();
|
|
}
|
|
else
|
|
{
|
|
attributes = layer->dataProvider()->identify( *infoPoint, QgsRaster::IdentifyFormatValue, mMapRenderer->extent(), mMapRenderer->outputSize().width(), mMapRenderer->outputSize().height() ).results();
|
|
}
|
|
|
|
if ( infoFormat == "application/vnd.ogc.gml" )
|
|
{
|
|
QgsFeature feature;
|
|
QgsFields fields;
|
|
feature.initAttributes( attributes.count() );
|
|
int index = 0;
|
|
for ( QMap<int, QVariant>::const_iterator it = attributes.constBegin(); it != attributes.constEnd(); ++it )
|
|
{
|
|
fields.append( QgsField( layer->bandName( it.key() ), QVariant::Double ) );
|
|
feature.setAttribute( index++, QString::number( it.value().toDouble() ) );
|
|
}
|
|
feature.setFields( fields );
|
|
|
|
QgsCoordinateReferenceSystem layerCrs = layer->crs();
|
|
int version = infoFormat.startsWith( "application/vnd.ogc.gml/3" ) ? 3 : 2;
|
|
QString typeName = layer->name();
|
|
if ( mConfigParser && mConfigParser->useLayerIds() )
|
|
typeName = layer->id();
|
|
else if ( !layer->shortName().isEmpty() )
|
|
typeName = layer->shortName();
|
|
QDomElement elem = createFeatureGML(
|
|
&feature, nullptr, infoDocument, layerCrs, typeName, false, version, nullptr );
|
|
layerElement.appendChild( elem );
|
|
}
|
|
else
|
|
{
|
|
for ( QMap<int, QVariant>::const_iterator it = attributes.constBegin(); it != attributes.constEnd(); ++it )
|
|
{
|
|
QDomElement attributeElement = infoDocument.createElement( "Attribute" );
|
|
attributeElement.setAttribute( "name", layer->bandName( it.key() ) );
|
|
attributeElement.setAttribute( "value", QString::number( it.value().toDouble() ) );
|
|
layerElement.appendChild( attributeElement );
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
QStringList QgsWmsServer::layerSet( const QStringList &layersList,
|
|
const QStringList &stylesList,
|
|
const QgsCoordinateReferenceSystem &destCRS, double scaleDenominator ) const
|
|
{
|
|
Q_UNUSED( destCRS );
|
|
QStringList layerKeys;
|
|
QStringList::const_iterator llstIt;
|
|
QStringList::const_iterator slstIt;
|
|
QgsMapLayer* theMapLayer = nullptr;
|
|
QgsMessageLog::logMessage( QString( "Calculating layerset using %1 layers, %2 styles and CRS %3" ).arg( layersList.count() ).arg( stylesList.count() ).arg( destCRS.description() ) );
|
|
for ( llstIt = layersList.begin(), slstIt = stylesList.begin(); llstIt != layersList.end(); ++llstIt )
|
|
{
|
|
QString styleName;
|
|
if ( slstIt != stylesList.end() )
|
|
{
|
|
styleName = *slstIt;
|
|
}
|
|
QgsMessageLog::logMessage( "Trying to get layer " + *llstIt + "//" + styleName );
|
|
|
|
//does the layer name appear several times in the layer list?
|
|
//if yes, layer caching must be disabled because several named layers could have
|
|
//several user styles
|
|
bool allowCaching = true;
|
|
if ( layersList.count( *llstIt ) > 1 )
|
|
{
|
|
allowCaching = false;
|
|
}
|
|
|
|
QList<QgsMapLayer*> layerList = mConfigParser->mapLayerFromStyle( *llstIt, styleName, allowCaching );
|
|
int listIndex;
|
|
|
|
for ( listIndex = layerList.size() - 1; listIndex >= 0; listIndex-- )
|
|
{
|
|
theMapLayer = layerList.at( listIndex );
|
|
if ( theMapLayer )
|
|
{
|
|
QString lName = theMapLayer->name();
|
|
if ( mConfigParser && mConfigParser->useLayerIds() )
|
|
lName = theMapLayer->id();
|
|
else if ( !theMapLayer->shortName().isEmpty() )
|
|
lName = theMapLayer->shortName();
|
|
QgsMessageLog::logMessage( QString( "Checking layer: %1" ).arg( lName ) );
|
|
//test if layer is visible in requested scale
|
|
bool useScaleConstraint = ( scaleDenominator > 0 && theMapLayer->hasScaleBasedVisibility() );
|
|
if ( !useScaleConstraint ||
|
|
( theMapLayer->minimumScale() <= scaleDenominator && theMapLayer->maximumScale() >= scaleDenominator ) )
|
|
{
|
|
layerKeys.push_front( theMapLayer->id() );
|
|
QgsMapLayerRegistry::instance()->addMapLayers(
|
|
QList<QgsMapLayer *>() << theMapLayer, false, false );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( "Layer or style not defined, aborting" );
|
|
throw QgsMapServiceException( "LayerNotDefined", "Layer '" + *llstIt + "' and/or style '" + styleName + "' not defined" );
|
|
}
|
|
}
|
|
|
|
if ( slstIt != stylesList.end() )
|
|
{
|
|
++slstIt;
|
|
}
|
|
}
|
|
return layerKeys;
|
|
}
|
|
|
|
|
|
void QgsWmsServer::applyRequestedLayerFilters( const QStringList& layerList , QHash<QgsMapLayer*, QString>& originalFilters ) const
|
|
{
|
|
if ( layerList.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString filterParameter = mParameters.value( "FILTER" );
|
|
if ( !filterParameter.isEmpty() )
|
|
{
|
|
QStringList layerSplit = filterParameter.split( ";" );
|
|
QStringList::const_iterator layerIt = layerSplit.constBegin();
|
|
for ( ; layerIt != layerSplit.constEnd(); ++layerIt )
|
|
{
|
|
QStringList eqSplit = layerIt->split( ":" );
|
|
if ( eqSplit.size() < 2 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//filter string could be unsafe (danger of sql injection)
|
|
if ( !testFilterStringSafety( eqSplit.at( 1 ) ) )
|
|
{
|
|
throw QgsMapServiceException( "Filter string rejected", "The filter string " + eqSplit.at( 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." );
|
|
}
|
|
|
|
//we need to find the maplayer objects matching the layer name
|
|
QList<QgsMapLayer*> layersToFilter;
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, QgsMapLayerRegistry::instance()->mapLayers() )
|
|
{
|
|
if ( layer )
|
|
{
|
|
QString lName = layer->name();
|
|
if ( mConfigParser && mConfigParser->useLayerIds() )
|
|
lName = layer->id();
|
|
else if ( !layer->shortName().isEmpty() )
|
|
lName = layer->shortName();
|
|
if ( lName == eqSplit.at( 0 ) )
|
|
layersToFilter.push_back( layer );
|
|
}
|
|
}
|
|
|
|
Q_FOREACH ( QgsMapLayer *filter, layersToFilter )
|
|
{
|
|
QgsVectorLayer* filteredLayer = dynamic_cast<QgsVectorLayer*>( filter );
|
|
if ( filteredLayer )
|
|
{
|
|
originalFilters.insert( filteredLayer, filteredLayer->subsetString() );
|
|
QString newSubsetString = eqSplit.at( 1 );
|
|
if ( !filteredLayer->subsetString().isEmpty() )
|
|
{
|
|
newSubsetString.prepend( " AND " );
|
|
newSubsetString.prepend( filteredLayer->subsetString() );
|
|
}
|
|
filteredLayer->setSubsetString( newSubsetString );
|
|
}
|
|
}
|
|
}
|
|
|
|
//No BBOX parameter in request. We use the union of the filtered layer
|
|
//to provide the functionality of zooming to selected records via (enhanced) WMS.
|
|
if ( mMapRenderer && mMapRenderer->extent().isEmpty() )
|
|
{
|
|
QgsRectangle filterExtent;
|
|
QHash<QgsMapLayer*, QString>::const_iterator filterIt = originalFilters.constBegin();
|
|
for ( ; filterIt != originalFilters.constEnd(); ++filterIt )
|
|
{
|
|
QgsMapLayer* mapLayer = filterIt.key();
|
|
if ( !mapLayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QgsRectangle layerExtent = mMapRenderer->layerToMapCoordinates( mapLayer, mapLayer->extent() );
|
|
if ( filterExtent.isEmpty() )
|
|
{
|
|
filterExtent = layerExtent;
|
|
}
|
|
else
|
|
{
|
|
filterExtent.combineExtentWith( layerExtent );
|
|
}
|
|
}
|
|
mMapRenderer->setExtent( filterExtent );
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
void QgsWmsServer::applyAccessControlLayersFilters( const QStringList& layerList, QHash<QgsMapLayer*, QString>& originalLayerFilters ) const
|
|
{
|
|
Q_FOREACH ( const QString& layerName, layerList )
|
|
{
|
|
QList<QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayersByName( layerName );
|
|
Q_FOREACH ( QgsMapLayer* mapLayer, mapLayers )
|
|
{
|
|
applyAccessControlLayerFilters( mapLayer, originalLayerFilters );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool QgsWmsServer::testFilterStringSafety( const QString& filter ) const
|
|
{
|
|
//; too dangerous for sql injections
|
|
if ( filter.contains( ";" ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QStringList tokens = filter.split( " ", QString::SkipEmptyParts );
|
|
groupStringList( tokens, "'" );
|
|
groupStringList( tokens, "\"" );
|
|
|
|
QStringList::const_iterator tokenIt = tokens.constBegin();
|
|
for ( ; tokenIt != tokens.constEnd(); ++tokenIt )
|
|
{
|
|
//whitelist of allowed characters and keywords
|
|
if ( tokenIt->compare( "," ) == 0
|
|
|| tokenIt->compare( "(" ) == 0
|
|
|| tokenIt->compare( ")" ) == 0
|
|
|| tokenIt->compare( "=" ) == 0
|
|
|| tokenIt->compare( "!=" ) == 0
|
|
|| tokenIt->compare( "<" ) == 0
|
|
|| tokenIt->compare( "<=" ) == 0
|
|
|| tokenIt->compare( ">" ) == 0
|
|
|| tokenIt->compare( ">=" ) == 0
|
|
|| tokenIt->compare( "%" ) == 0
|
|
|| tokenIt->compare( "AND", Qt::CaseInsensitive ) == 0
|
|
|| tokenIt->compare( "OR", Qt::CaseInsensitive ) == 0
|
|
|| tokenIt->compare( "IN", Qt::CaseInsensitive ) == 0
|
|
|| tokenIt->compare( "LIKE", Qt::CaseInsensitive ) == 0
|
|
|| tokenIt->compare( "ILIKE", Qt::CaseInsensitive ) == 0
|
|
|| tokenIt->compare( "DMETAPHONE", Qt::CaseInsensitive ) == 0
|
|
|| tokenIt->compare( "SOUNDEX", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//numbers are ok
|
|
bool isNumeric;
|
|
tokenIt->toDouble( &isNumeric );
|
|
if ( isNumeric )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//numeric strings need to be quoted once either with single or with double quotes
|
|
|
|
//empty strings are ok
|
|
if ( *tokenIt == "''" )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//single quote
|
|
if ( tokenIt->size() > 2
|
|
&& ( *tokenIt )[0] == QChar( '\'' )
|
|
&& ( *tokenIt )[tokenIt->size() - 1] == QChar( '\'' )
|
|
&& ( *tokenIt )[1] != QChar( '\'' )
|
|
&& ( *tokenIt )[tokenIt->size() - 2] != QChar( '\'' ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//double quote
|
|
if ( tokenIt->size() > 2
|
|
&& ( *tokenIt )[0] == QChar( '"' )
|
|
&& ( *tokenIt )[tokenIt->size() - 1] == QChar( '"' )
|
|
&& ( *tokenIt )[1] != QChar( '"' )
|
|
&& ( *tokenIt )[tokenIt->size() - 2] != QChar( '"' ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void QgsWmsServer::groupStringList( QStringList& list, const QString& groupString )
|
|
{
|
|
//group contents within single quotes together
|
|
bool groupActive = false;
|
|
int startGroup = -1;
|
|
QString concatString;
|
|
|
|
for ( int i = 0; i < list.size(); ++i )
|
|
{
|
|
QString& str = list[i];
|
|
if ( str.startsWith( groupString ) )
|
|
{
|
|
startGroup = i;
|
|
groupActive = true;
|
|
concatString.clear();
|
|
}
|
|
|
|
if ( groupActive )
|
|
{
|
|
if ( i != startGroup )
|
|
{
|
|
concatString.append( " " );
|
|
}
|
|
concatString.append( str );
|
|
}
|
|
|
|
if ( str.endsWith( groupString ) )
|
|
{
|
|
int endGroup = i;
|
|
groupActive = false;
|
|
|
|
if ( startGroup != -1 )
|
|
{
|
|
list[startGroup] = concatString;
|
|
for ( int j = startGroup + 1; j <= endGroup; ++j )
|
|
{
|
|
list.removeAt( startGroup + 1 );
|
|
--i;
|
|
}
|
|
}
|
|
|
|
concatString.clear();
|
|
startGroup = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
QStringList QgsWmsServer::applyFeatureSelections( const QStringList& layerList ) const
|
|
{
|
|
QStringList layersWithSelections;
|
|
if ( layerList.isEmpty() )
|
|
{
|
|
return layersWithSelections;
|
|
}
|
|
|
|
QString selectionString = mParameters.value( "SELECTION" );
|
|
if ( selectionString.isEmpty() )
|
|
{
|
|
return layersWithSelections;
|
|
}
|
|
|
|
Q_FOREACH ( const QString& selectionLayer, selectionString.split( ";" ) )
|
|
{
|
|
//separate layer name from id list
|
|
QStringList layerIdSplit = selectionLayer.split( ":" );
|
|
if ( layerIdSplit.size() < 2 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//find layerId for layer name
|
|
QString layerName = layerIdSplit.at( 0 );
|
|
QgsVectorLayer* vLayer = nullptr;
|
|
|
|
Q_FOREACH ( QgsMapLayer *layer, QgsMapLayerRegistry::instance()->mapLayers() )
|
|
{
|
|
if ( layer )
|
|
{
|
|
QString lName = layer->name();
|
|
if ( mConfigParser && mConfigParser->useLayerIds() )
|
|
lName = layer->id();
|
|
else if ( !layer->shortName().isEmpty() )
|
|
lName = layer->shortName();
|
|
if ( lName == layerName )
|
|
{
|
|
vLayer = qobject_cast<QgsVectorLayer*>( layer );
|
|
layersWithSelections.push_back( vLayer->id() );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !vLayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QStringList idList = layerIdSplit.at( 1 ).split( "," );
|
|
QgsFeatureIds selectedIds;
|
|
|
|
Q_FOREACH ( const QString& id, idList )
|
|
{
|
|
selectedIds.insert( STRING_TO_FID( id ) );
|
|
}
|
|
|
|
vLayer->selectByIds( selectedIds );
|
|
}
|
|
|
|
|
|
return layersWithSelections;
|
|
}
|
|
|
|
void QgsWmsServer::clearFeatureSelections( const QStringList& layerIds ) const
|
|
{
|
|
const QMap<QString, QgsMapLayer*>& layerMap = QgsMapLayerRegistry::instance()->mapLayers();
|
|
|
|
Q_FOREACH ( const QString& id, layerIds )
|
|
{
|
|
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer * >( layerMap.value( id, nullptr ) );
|
|
if ( !layer )
|
|
continue;
|
|
|
|
layer->selectByIds( QgsFeatureIds() );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void QgsWmsServer::applyOpacities( const QStringList& layerList, QList< QPair< QgsVectorLayer*, QgsFeatureRenderer*> >& vectorRenderers,
|
|
QList< QPair< QgsRasterLayer*, QgsRasterRenderer* > >& rasterRenderers,
|
|
QList< QPair< QgsVectorLayer*, double > >& labelTransparencies,
|
|
QList< QPair< QgsVectorLayer*, double > >& labelBufferTransparencies )
|
|
{
|
|
//get opacity list
|
|
QMap<QString, QString>::const_iterator opIt = mParameters.constFind( "OPACITIES" );
|
|
if ( opIt == mParameters.constEnd() )
|
|
{
|
|
return;
|
|
}
|
|
QStringList opacityList = opIt.value().split( "," );
|
|
|
|
//collect leaf layers and their opacity
|
|
QVector< QPair< QgsMapLayer*, int > > layerOpacityList;
|
|
QStringList::const_iterator oIt = opacityList.constBegin();
|
|
QStringList::const_iterator lIt = layerList.constBegin();
|
|
for ( ; oIt != opacityList.constEnd() && lIt != layerList.constEnd(); ++oIt, ++lIt )
|
|
{
|
|
//get layer list for
|
|
int opacity = oIt->toInt();
|
|
if ( opacity < 0 || opacity > 255 )
|
|
{
|
|
continue;
|
|
}
|
|
QList<QgsMapLayer*> llist = mConfigParser->mapLayerFromStyle( *lIt, "" );
|
|
QList<QgsMapLayer*>::const_iterator lListIt = llist.constBegin();
|
|
for ( ; lListIt != llist.constEnd(); ++lListIt )
|
|
{
|
|
layerOpacityList.push_back( qMakePair( *lListIt, opacity ) );
|
|
}
|
|
}
|
|
|
|
QVector< QPair< QgsMapLayer*, int > >::const_iterator lOpIt = layerOpacityList.constBegin();
|
|
for ( ; lOpIt != layerOpacityList.constEnd(); ++lOpIt )
|
|
{
|
|
//vector or raster?
|
|
QgsMapLayer* ml = lOpIt->first;
|
|
int opacity = lOpIt->second;
|
|
double opacityRatio = opacity / 255.0; //opacity value between 0 and 1
|
|
|
|
if ( !ml || opacity == 255 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( ml->type() == QgsMapLayer::VectorLayer )
|
|
{
|
|
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( ml );
|
|
|
|
QgsFeatureRenderer* renderer = vl->renderer();
|
|
//backup old renderer
|
|
vectorRenderers.push_back( qMakePair( vl, renderer->clone() ) );
|
|
//modify symbols of current renderer
|
|
QgsRenderContext context;
|
|
context.expressionContext() << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope()
|
|
<< QgsExpressionContextUtils::layerScope( vl );
|
|
|
|
QgsSymbolList symbolList = renderer->symbols( context );
|
|
QgsSymbolList::iterator symbolIt = symbolList.begin();
|
|
for ( ; symbolIt != symbolList.end(); ++symbolIt )
|
|
{
|
|
( *symbolIt )->setAlpha(( *symbolIt )->alpha() * opacityRatio );
|
|
}
|
|
|
|
//labeling
|
|
if ( vl->customProperty( "labeling/enabled" ).toString() == "true" )
|
|
{
|
|
double labelTransparency = vl->customProperty( "labeling/textTransp" ).toDouble();
|
|
labelTransparencies.push_back( qMakePair( vl, labelTransparency ) );
|
|
vl->setCustomProperty( "labeling/textTransp", labelTransparency + ( 100 - labelTransparency ) * ( 1.0 - opacityRatio ) );
|
|
double bufferTransparency = vl->customProperty( "labeling/bufferTransp" ).toDouble();
|
|
labelBufferTransparencies.push_back( qMakePair( vl, bufferTransparency ) );
|
|
vl->setCustomProperty( "labeling/bufferTransp", bufferTransparency + ( 100 - bufferTransparency )* ( 1.0 - opacityRatio ) );
|
|
}
|
|
}
|
|
else if ( ml->type() == QgsMapLayer::RasterLayer )
|
|
{
|
|
QgsRasterLayer* rl = qobject_cast<QgsRasterLayer*>( ml );
|
|
if ( rl )
|
|
{
|
|
QgsRasterRenderer* rasterRenderer = rl->renderer();
|
|
if ( rasterRenderer )
|
|
{
|
|
rasterRenderers.push_back( qMakePair( rl, rasterRenderer->clone() ) );
|
|
rasterRenderer->setOpacity( rasterRenderer->opacity() * opacityRatio );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsWmsServer::restoreOpacities( QList< QPair< QgsVectorLayer*, QgsFeatureRenderer*> >& vectorRenderers,
|
|
QList < QPair< QgsRasterLayer*, QgsRasterRenderer* > >& rasterRenderers,
|
|
QList< QPair< QgsVectorLayer*, double > >& labelOpacities,
|
|
QList< QPair< QgsVectorLayer*, double > >& labelBufferOpacities )
|
|
{
|
|
if ( vectorRenderers.isEmpty() && rasterRenderers.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QList< QPair< QgsVectorLayer*, QgsFeatureRenderer*> >::iterator vIt = vectorRenderers.begin();
|
|
for ( ; vIt != vectorRenderers.end(); ++vIt )
|
|
{
|
|
( *vIt ).first->setRenderer(( *vIt ).second );
|
|
}
|
|
|
|
QList< QPair< QgsRasterLayer*, QgsRasterRenderer* > >::iterator rIt = rasterRenderers.begin();
|
|
for ( ; rIt != rasterRenderers.end(); ++rIt )
|
|
{
|
|
( *rIt ).first->setRenderer(( *rIt ).second );
|
|
}
|
|
|
|
QList< QPair< QgsVectorLayer*, double > >::iterator loIt = labelOpacities.begin();
|
|
for ( ; loIt != labelOpacities.end(); ++loIt )
|
|
{
|
|
( *loIt ).first->setCustomProperty( "labeling/textTransp", ( *loIt ).second );
|
|
}
|
|
|
|
QList< QPair< QgsVectorLayer*, double > >::iterator lboIt = labelBufferOpacities.begin();
|
|
for ( ; lboIt != labelBufferOpacities.end(); ++lboIt )
|
|
{
|
|
( *lboIt ).first->setCustomProperty( "labeling/bufferTransp", ( *lboIt ).second );
|
|
}
|
|
}
|
|
|
|
bool QgsWmsServer::checkMaximumWidthHeight() const
|
|
{
|
|
//test if maxWidth / maxHeight set and WIDTH / HEIGHT parameter is in the range
|
|
if ( mConfigParser->maxWidth() != -1 )
|
|
{
|
|
QMap<QString, QString>::const_iterator widthIt = mParameters.find( "WIDTH" );
|
|
if ( widthIt != mParameters.constEnd() )
|
|
{
|
|
if ( widthIt->toInt() > mConfigParser->maxWidth() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if ( mConfigParser->maxHeight() != -1 )
|
|
{
|
|
QMap<QString, QString>::const_iterator heightIt = mParameters.find( "HEIGHT" );
|
|
if ( heightIt != mParameters.constEnd() )
|
|
{
|
|
if ( heightIt->toInt() > mConfigParser->maxHeight() )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QString QgsWmsServer::serviceUrl() const
|
|
{
|
|
QString requestUri = getenv( "REQUEST_URI" );
|
|
if ( requestUri.isEmpty() )
|
|
{
|
|
// in some cases (e.g. when running through python's CGIHTTPServer) the REQUEST_URI is not defined
|
|
requestUri = QString( getenv( "SCRIPT_NAME" ) ) + "?" + QString( getenv( "QUERY_STRING" ) );
|
|
}
|
|
|
|
QUrl mapUrl( requestUri );
|
|
mapUrl.setHost( getenv( "SERVER_NAME" ) );
|
|
|
|
//Add non-default ports to url
|
|
QString portString = getenv( "SERVER_PORT" );
|
|
if ( !portString.isEmpty() )
|
|
{
|
|
bool portOk;
|
|
int portNumber = portString.toInt( &portOk );
|
|
if ( portOk )
|
|
{
|
|
if ( portNumber != 80 )
|
|
{
|
|
mapUrl.setPort( portNumber );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( QString( getenv( "HTTPS" ) ).compare( "on", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
mapUrl.setScheme( "https" );
|
|
}
|
|
else
|
|
{
|
|
mapUrl.setScheme( "http" );
|
|
}
|
|
|
|
QList<QPair<QString, QString> > queryItems = mapUrl.queryItems();
|
|
QList<QPair<QString, QString> >::const_iterator queryIt = queryItems.constBegin();
|
|
for ( ; queryIt != queryItems.constEnd(); ++queryIt )
|
|
{
|
|
if ( queryIt->first.compare( "REQUEST", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
mapUrl.removeQueryItem( queryIt->first );
|
|
}
|
|
else if ( queryIt->first.compare( "VERSION", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
mapUrl.removeQueryItem( queryIt->first );
|
|
}
|
|
else if ( queryIt->first.compare( "SERVICE", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
mapUrl.removeQueryItem( queryIt->first );
|
|
}
|
|
else if ( queryIt->first.compare( "LAYERS", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
mapUrl.removeQueryItem( queryIt->first );
|
|
}
|
|
else if ( queryIt->first.compare( "SLD_VERSION", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
mapUrl.removeQueryItem( queryIt->first );
|
|
}
|
|
else if ( queryIt->first.compare( "_DC", Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
mapUrl.removeQueryItem( queryIt->first );
|
|
}
|
|
}
|
|
return mapUrl.toString();
|
|
}
|
|
|
|
void QgsWmsServer::addXmlDeclaration( QDomDocument& doc ) const
|
|
{
|
|
QDomProcessingInstruction xmlDeclaration = doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"utf-8\"" );
|
|
doc.appendChild( xmlDeclaration );
|
|
}
|
|
|
|
void QgsWmsServer::convertFeatureInfoToSIA2045( QDomDocument& doc )
|
|
{
|
|
QDomDocument SIAInfoDoc;
|
|
QDomElement infoDocElement = doc.documentElement();
|
|
QDomElement SIAInfoDocElement = SIAInfoDoc.importNode( infoDocElement, false ).toElement();
|
|
SIAInfoDoc.appendChild( SIAInfoDocElement );
|
|
|
|
QString currentAttributeName;
|
|
QString currentAttributeValue;
|
|
QDomElement currentAttributeElem;
|
|
QString currentLayerName;
|
|
QDomElement currentLayerElem;
|
|
QDomNodeList layerNodeList = infoDocElement.elementsByTagName( "Layer" );
|
|
for ( int i = 0; i < layerNodeList.size(); ++i )
|
|
{
|
|
currentLayerElem = layerNodeList.at( i ).toElement();
|
|
currentLayerName = currentLayerElem.attribute( "name" );
|
|
|
|
QDomElement currentFeatureElem;
|
|
|
|
QDomNodeList featureList = currentLayerElem.elementsByTagName( "Feature" );
|
|
if ( featureList.size() < 1 )
|
|
{
|
|
//raster?
|
|
QDomNodeList attributeList = currentLayerElem.elementsByTagName( "Attribute" );
|
|
QDomElement rasterLayerElem;
|
|
if ( !attributeList.isEmpty() )
|
|
{
|
|
rasterLayerElem = SIAInfoDoc.createElement( currentLayerName );
|
|
}
|
|
for ( int j = 0; j < attributeList.size(); ++j )
|
|
{
|
|
currentAttributeElem = attributeList.at( j ).toElement();
|
|
currentAttributeName = currentAttributeElem.attribute( "name" );
|
|
currentAttributeValue = currentAttributeElem.attribute( "value" );
|
|
QDomElement outAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
|
|
QDomText outAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
|
|
outAttributeElem.appendChild( outAttributeText );
|
|
rasterLayerElem.appendChild( outAttributeElem );
|
|
}
|
|
if ( !attributeList.isEmpty() )
|
|
{
|
|
SIAInfoDocElement.appendChild( rasterLayerElem );
|
|
}
|
|
}
|
|
else //vector
|
|
{
|
|
//property attributes
|
|
QSet<QString> layerPropertyAttributes;
|
|
QString currentLayerId = currentLayerElem.attribute( "id" );
|
|
if ( !currentLayerId.isEmpty() )
|
|
{
|
|
QgsMapLayer* currentLayer = QgsMapLayerRegistry::instance()->mapLayer( currentLayerId );
|
|
if ( currentLayer )
|
|
{
|
|
QString WMSPropertyAttributesString = currentLayer->customProperty( "WMSPropertyAttributes" ).toString();
|
|
if ( !WMSPropertyAttributesString.isEmpty() )
|
|
{
|
|
QStringList propertyList = WMSPropertyAttributesString.split( "//" );
|
|
QStringList::const_iterator propertyIt = propertyList.constBegin();
|
|
for ( ; propertyIt != propertyList.constEnd(); ++propertyIt )
|
|
{
|
|
layerPropertyAttributes.insert( *propertyIt );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QDomElement propertyRefChild; //child to insert the next property after (or
|
|
for ( int j = 0; j < featureList.size(); ++j )
|
|
{
|
|
QDomElement SIAFeatureElem = SIAInfoDoc.createElement( currentLayerName );
|
|
currentFeatureElem = featureList.at( j ).toElement();
|
|
QDomNodeList attributeList = currentFeatureElem.elementsByTagName( "Attribute" );
|
|
|
|
for ( int k = 0; k < attributeList.size(); ++k )
|
|
{
|
|
currentAttributeElem = attributeList.at( k ).toElement();
|
|
currentAttributeName = currentAttributeElem.attribute( "name" );
|
|
currentAttributeValue = currentAttributeElem.attribute( "value" );
|
|
if ( layerPropertyAttributes.contains( currentAttributeName ) )
|
|
{
|
|
QDomElement propertyElem = SIAInfoDoc.createElement( "property" );
|
|
QDomElement identifierElem = SIAInfoDoc.createElement( "identifier" );
|
|
QDomText identifierText = SIAInfoDoc.createTextNode( currentAttributeName );
|
|
identifierElem.appendChild( identifierText );
|
|
QDomElement valueElem = SIAInfoDoc.createElement( "value" );
|
|
QDomText valueText = SIAInfoDoc.createTextNode( currentAttributeValue );
|
|
valueElem.appendChild( valueText );
|
|
propertyElem.appendChild( identifierElem );
|
|
propertyElem.appendChild( valueElem );
|
|
if ( propertyRefChild.isNull() )
|
|
{
|
|
SIAFeatureElem.insertBefore( propertyElem, QDomNode() );
|
|
propertyRefChild = propertyElem;
|
|
}
|
|
else
|
|
{
|
|
SIAFeatureElem.insertAfter( propertyElem, propertyRefChild );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QDomElement SIAAttributeElem = SIAInfoDoc.createElement( currentAttributeName );
|
|
QDomText SIAAttributeText = SIAInfoDoc.createTextNode( currentAttributeValue );
|
|
SIAAttributeElem.appendChild( SIAAttributeText );
|
|
SIAFeatureElem.appendChild( SIAAttributeElem );
|
|
}
|
|
}
|
|
SIAInfoDocElement.appendChild( SIAFeatureElem );
|
|
}
|
|
}
|
|
}
|
|
doc = SIAInfoDoc;
|
|
}
|
|
|
|
QDomElement QgsWmsServer::createFeatureGML(
|
|
QgsFeature* feat,
|
|
QgsVectorLayer* layer,
|
|
QDomDocument& doc,
|
|
QgsCoordinateReferenceSystem& crs,
|
|
const QString& typeName,
|
|
bool withGeom,
|
|
int version,
|
|
QStringList* attributes ) const
|
|
{
|
|
//qgs:%TYPENAME%
|
|
QDomElement typeNameElement = doc.createElement( "qgs:" + typeName /*qgs:%TYPENAME%*/ );
|
|
typeNameElement.setAttribute( "fid", typeName + "." + QString::number( feat->id() ) );
|
|
|
|
QgsCoordinateTransform transform;
|
|
if ( layer && layer->crs() != crs )
|
|
{
|
|
transform = mMapRenderer->transformation( layer );
|
|
}
|
|
|
|
QgsGeometry geom = feat->geometry();
|
|
|
|
QgsExpressionContext expressionContext;
|
|
expressionContext << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope();
|
|
if ( layer )
|
|
expressionContext << QgsExpressionContextUtils::layerScope( layer );
|
|
expressionContext.setFeature( *feat );
|
|
|
|
// always add bounding box info if feature contains geometry
|
|
if ( !geom.isEmpty() && geom.type() != QgsWkbTypes::UnknownGeometry && geom.type() != QgsWkbTypes::NullGeometry )
|
|
{
|
|
QgsRectangle box = feat->geometry().boundingBox();
|
|
if ( transform.isValid() )
|
|
{
|
|
try
|
|
{
|
|
QgsRectangle transformedBox = transform.transformBoundingBox( box );
|
|
box = transformedBox;
|
|
}
|
|
catch ( QgsCsException &e )
|
|
{
|
|
QgsMessageLog::logMessage( QString( "Transform error caught: %1" ).arg( e.what() ) );
|
|
}
|
|
}
|
|
|
|
QDomElement bbElem = doc.createElement( "gml:boundedBy" );
|
|
QDomElement boxElem;
|
|
if ( version < 3 )
|
|
{
|
|
boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, 8 );
|
|
}
|
|
else
|
|
{
|
|
boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, 8 );
|
|
}
|
|
|
|
if ( crs.isValid() )
|
|
{
|
|
boxElem.setAttribute( "srsName", crs.authid() );
|
|
}
|
|
bbElem.appendChild( boxElem );
|
|
typeNameElement.appendChild( bbElem );
|
|
}
|
|
|
|
if ( withGeom && !geom.isEmpty() )
|
|
{
|
|
//add geometry column (as gml)
|
|
|
|
if ( transform.isValid() )
|
|
{
|
|
geom.transform( transform );
|
|
}
|
|
|
|
QDomElement geomElem = doc.createElement( "qgs:geometry" );
|
|
QDomElement gmlElem;
|
|
if ( version < 3 )
|
|
{
|
|
gmlElem = QgsOgcUtils::geometryToGML( &geom, doc, 8 );
|
|
}
|
|
else
|
|
{
|
|
gmlElem = QgsOgcUtils::geometryToGML( &geom, doc, "GML3", 8 );
|
|
}
|
|
|
|
if ( !gmlElem.isNull() )
|
|
{
|
|
if ( crs.isValid() )
|
|
{
|
|
gmlElem.setAttribute( "srsName", crs.authid() );
|
|
}
|
|
geomElem.appendChild( gmlElem );
|
|
typeNameElement.appendChild( geomElem );
|
|
}
|
|
}
|
|
|
|
//read all allowed attribute values from the feature
|
|
QgsAttributes featureAttributes = feat->attributes();
|
|
QgsFields fields = feat->fields();
|
|
for ( int i = 0; i < fields.count(); ++i )
|
|
{
|
|
QString attributeName = fields.at( i ).name();
|
|
//skip attribute if it is explicitly excluded from WMS publication
|
|
if ( layer && layer->excludeAttributesWms().contains( attributeName ) )
|
|
{
|
|
continue;
|
|
}
|
|
//skip attribute if it is excluded by access control
|
|
if ( attributes && !attributes->contains( attributeName ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( QString( " " ), QString( "_" ) ) );
|
|
QString fieldTextString = featureAttributes.at( i ).toString();
|
|
if ( layer )
|
|
{
|
|
fieldTextString = replaceValueMapAndRelation( layer, i, QgsExpression::replaceExpressionText( fieldTextString, &expressionContext ) );
|
|
}
|
|
QDomText fieldText = doc.createTextNode( fieldTextString );
|
|
fieldElem.appendChild( fieldText );
|
|
typeNameElement.appendChild( fieldElem );
|
|
}
|
|
|
|
//add maptip attribute based on html/expression (in case there is no maptip attribute)
|
|
if ( layer )
|
|
{
|
|
QString mapTip = layer->mapTipTemplate();
|
|
|
|
if ( !mapTip.isEmpty() )
|
|
{
|
|
QString fieldTextString = QgsExpression::replaceExpressionText( mapTip, &expressionContext );
|
|
QDomElement fieldElem = doc.createElement( "qgs:maptip" );
|
|
QDomText maptipText = doc.createTextNode( fieldTextString );
|
|
fieldElem.appendChild( maptipText );
|
|
typeNameElement.appendChild( fieldElem );
|
|
}
|
|
}
|
|
|
|
return typeNameElement;
|
|
}
|
|
|
|
QString QgsWmsServer::replaceValueMapAndRelation( QgsVectorLayer* vl, int idx, const QString& attributeVal )
|
|
{
|
|
const QgsEditorWidgetSetup setup = QgsEditorWidgetRegistry::instance()->findBest( vl, vl->fields().field( idx ).name() );
|
|
if ( QgsEditorWidgetFactory *factory = QgsEditorWidgetRegistry::instance()->factory( setup.type() ) )
|
|
{
|
|
QString value( factory->representValue( vl, idx, setup.config(), QVariant(), attributeVal ) );
|
|
if ( setup.config().value( "AllowMulti" ).toBool() && value.startsWith( "{" ) && value.endsWith( "}" ) )
|
|
{
|
|
value = value.mid( 1, value.size() - 2 );
|
|
}
|
|
return value;
|
|
}
|
|
else
|
|
return QString( "(%1)" ).arg( attributeVal );
|
|
}
|
|
|
|
int QgsWmsServer::getImageQuality() const
|
|
{
|
|
|
|
// First taken from QGIS project
|
|
int imageQuality = mConfigParser->imageQuality();
|
|
|
|
// Then checks if a parameter is given, if so use it instead
|
|
if ( mParameters.contains( "IMAGE_QUALITY" ) )
|
|
{
|
|
bool conversionSuccess;
|
|
int imageQualityParameter;
|
|
imageQualityParameter = mParameters[ "IMAGE_QUALITY" ].toInt( &conversionSuccess );
|
|
if ( conversionSuccess )
|
|
{
|
|
imageQuality = imageQualityParameter;
|
|
}
|
|
}
|
|
return imageQuality;
|
|
}
|
|
|
|
int QgsWmsServer::getWMSPrecision( int defaultValue = 8 ) const
|
|
{
|
|
// First taken from QGIS project
|
|
int WMSPrecision = mConfigParser->wmsPrecision();
|
|
|
|
// Then checks if a parameter is given, if so use it instead
|
|
if ( mParameters.contains( "WMS_PRECISION" ) )
|
|
{
|
|
bool conversionSuccess;
|
|
int WMSPrecisionParameter;
|
|
WMSPrecisionParameter = mParameters[ "WMS_PRECISION" ].toInt( &conversionSuccess );
|
|
if ( conversionSuccess )
|
|
{
|
|
WMSPrecision = WMSPrecisionParameter;
|
|
}
|
|
}
|
|
if ( WMSPrecision == -1 )
|
|
{
|
|
WMSPrecision = defaultValue;
|
|
}
|
|
return WMSPrecision;
|
|
}
|
|
|
|
QgsRectangle QgsWmsServer::featureInfoSearchRect( QgsVectorLayer* ml, QgsMapRenderer* mr, const QgsRenderContext& rct, const QgsPoint& infoPoint ) const
|
|
{
|
|
if ( !ml || !mr )
|
|
{
|
|
return QgsRectangle();
|
|
}
|
|
|
|
double mapUnitTolerance = 0.0;
|
|
if ( ml->geometryType() == QgsWkbTypes::PolygonGeometry )
|
|
{
|
|
QMap<QString, QString>::const_iterator tolIt = mParameters.find( "FI_POLYGON_TOLERANCE" );
|
|
if ( tolIt != mParameters.constEnd() )
|
|
{
|
|
mapUnitTolerance = tolIt.value().toInt() * rct.mapToPixel().mapUnitsPerPixel();
|
|
}
|
|
else
|
|
{
|
|
mapUnitTolerance = mr->extent().width() / 400.0;
|
|
}
|
|
}
|
|
else if ( ml->geometryType() == QgsWkbTypes::LineGeometry )
|
|
{
|
|
QMap<QString, QString>::const_iterator tolIt = mParameters.find( "FI_LINE_TOLERANCE" );
|
|
if ( tolIt != mParameters.constEnd() )
|
|
{
|
|
mapUnitTolerance = tolIt.value().toInt() * rct.mapToPixel().mapUnitsPerPixel();
|
|
}
|
|
else
|
|
{
|
|
mapUnitTolerance = mr->extent().width() / 200.0;
|
|
}
|
|
}
|
|
else //points
|
|
{
|
|
QMap<QString, QString>::const_iterator tolIt = mParameters.find( "FI_POINT_TOLERANCE" );
|
|
if ( tolIt != mParameters.constEnd() )
|
|
{
|
|
mapUnitTolerance = tolIt.value().toInt() * rct.mapToPixel().mapUnitsPerPixel();
|
|
}
|
|
else
|
|
{
|
|
mapUnitTolerance = mr->extent().width() / 100.0;
|
|
}
|
|
}
|
|
|
|
QgsRectangle mapRectangle( infoPoint.x() - mapUnitTolerance, infoPoint.y() - mapUnitTolerance,
|
|
infoPoint.x() + mapUnitTolerance, infoPoint.y() + mapUnitTolerance );
|
|
return( mr->mapToLayerCoordinates( ml, mapRectangle ) );
|
|
}
|
|
|
|
void QgsWmsServer::readFormatOptions( QMap<QString, QString>& formatOptions ) const
|
|
{
|
|
formatOptions.clear();
|
|
QString fo = mParameters.value( "FORMAT_OPTIONS" );
|
|
QStringList formatOptionsList = fo.split( ";" );
|
|
QStringList::const_iterator optionsIt = formatOptionsList.constBegin();
|
|
for ( ; optionsIt != formatOptionsList.constEnd(); ++optionsIt )
|
|
{
|
|
int equalIdx = optionsIt->indexOf( ":" );
|
|
if ( equalIdx > 0 && equalIdx < ( optionsIt->length() - 1 ) )
|
|
{
|
|
formatOptions.insert( optionsIt->left( equalIdx ).toUpper(), optionsIt->right( optionsIt->length() - equalIdx - 1 ).toUpper() );
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsWmsServer::readDxfLayerSettings( QList< QPair<QgsVectorLayer *, int > >& layers, const QMap<QString, QString>& formatOptionsMap ) const
|
|
{
|
|
layers.clear();
|
|
|
|
QSet<QString> wfsLayers = QSet<QString>::fromList( mConfigParser->wfsLayerNames() );
|
|
|
|
QStringList layerAttributes;
|
|
QMap<QString, QString>::const_iterator layerAttributesIt = formatOptionsMap.find( "LAYERATTRIBUTES" );
|
|
if ( layerAttributesIt != formatOptionsMap.constEnd() )
|
|
{
|
|
layerAttributes = formatOptionsMap.value( "LAYERATTRIBUTES" ).split( "," );
|
|
}
|
|
|
|
//LAYERS and STYLES
|
|
QStringList layerList, styleList;
|
|
if ( readLayersAndStyles( layerList, styleList ) != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
for ( int i = 0; i < layerList.size(); ++i )
|
|
{
|
|
QString layerName = layerList.at( i );
|
|
QString styleName;
|
|
if ( styleList.size() > i )
|
|
{
|
|
styleName = styleList.at( i );
|
|
}
|
|
|
|
QList<QgsMapLayer*> layerList = mConfigParser->mapLayerFromStyle( layerName, styleName );
|
|
QList<QgsMapLayer*>::const_iterator layerIt = layerList.constBegin();
|
|
for ( ; layerIt != layerList.constEnd(); ++layerIt )
|
|
{
|
|
if ( !( *layerIt ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//vector layer?
|
|
if (( *layerIt )->type() != QgsMapLayer::VectorLayer )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
QgsVectorLayer* vlayer = static_cast<QgsVectorLayer*>( *layerIt );
|
|
|
|
int layerAttribute = -1;
|
|
if ( layerAttributes.size() > i )
|
|
{
|
|
layerAttribute = vlayer->pendingFields().indexFromName( layerAttributes.at( i ) );
|
|
}
|
|
|
|
//only wfs layers are allowed to be published
|
|
if ( !wfsLayers.contains( vlayer->name() ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
layers.append( qMakePair( vlayer, layerAttribute ) );
|
|
}
|
|
}
|
|
}
|