Merge pull request #4846 from rldhont/server-wms-configparser-getfeatureinfo

[Server] WMS GetFeatureInfo refactoring
This commit is contained in:
rldhont 2017-07-19 10:00:01 +02:00 committed by GitHub
commit 08c06def1b
11 changed files with 1025 additions and 478 deletions

View File

@ -145,6 +145,62 @@ namespace QgsServerProjectUtils
:rtype: bool
%End
bool wmsFeatureInfoAddWktGeometry( const QgsProject &project );
%Docstring
Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request.
\param project the QGIS project
:return: if the geometry is displayed as Well Known Text in GetFeatureInfo request.
:rtype: bool
%End
bool wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project );
%Docstring
Returns if the geometry has to be segmentize in GetFeatureInfo request.
\param project the QGIS project
:return: if the geometry has to be segmentize in GetFeatureInfo request.
:rtype: bool
%End
int wmsFeatureInfoPrecision( const QgsProject &project );
%Docstring
Returns the geometry precision for GetFeatureInfo request.
\param project the QGIS project
:return: the geometry precision for GetFeatureInfo request.
:rtype: int
%End
QString wmsFeatureInfoDocumentElement( const QgsProject &project );
%Docstring
Returns the document element name for XML GetFeatureInfo request.
\param project the QGIS project
:return: the document element name for XML GetFeatureInfo request.
:rtype: str
%End
QString wmsFeatureInfoDocumentElementNs( const QgsProject &project );
%Docstring
Returns the document element namespace for XML GetFeatureInfo request.
\param project the QGIS project
:return: the document element namespace for XML GetFeatureInfo request.
:rtype: str
%End
QString wmsFeatureInfoSchema( const QgsProject &project );
%Docstring
Returns the schema URL for XML GetFeatureInfo request.
\param project the QGIS project
:return: the schema URL for XML GetFeatureInfo request.
:rtype: str
%End
QHash<QString, QString> wmsFeatureInfoLayerAliasMap( const QgsProject &project );
%Docstring
Returns the mapping between layer name and wms layer name for GetFeatureInfo request.
\param project the QGIS project
:return: the mapping between layer name and wms layer name for GetFeatureInfo request.
:rtype: QHash<str, QString>
%End
bool wmsInspireActivate( const QgsProject &project );
%Docstring
Returns if Inspire is activated.

View File

@ -117,6 +117,77 @@ bool QgsServerProjectUtils::wmsInfoFormatSia2045( const QgsProject &project )
return false;
}
bool QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( const QgsProject &project )
{
QString wktGeom = project.readEntry( QStringLiteral( "WMSAddWktGeometry" ), QStringLiteral( "/" ), "" );
if ( wktGeom.compare( QLatin1String( "enabled" ), Qt::CaseInsensitive ) == 0
|| wktGeom.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
{
return true;
}
return false;
}
bool QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project )
{
QString segmGeom = project.readEntry( QStringLiteral( "WMSSegmentizeFeatureInfoGeometry" ), QStringLiteral( "/" ), "" );
if ( segmGeom.compare( QLatin1String( "enabled" ), Qt::CaseInsensitive ) == 0
|| segmGeom.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
{
return true;
}
return false;
}
int QgsServerProjectUtils::wmsFeatureInfoPrecision( const QgsProject &project )
{
return project.readNumEntry( QStringLiteral( "WMSPrecision" ), QStringLiteral( "/" ), 6 );
}
QString QgsServerProjectUtils::wmsFeatureInfoDocumentElement( const QgsProject &project )
{
return project.readEntry( QStringLiteral( "WMSFeatureInfoDocumentElement" ), QStringLiteral( "/" ), "" );
}
QString QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( const QgsProject &project )
{
return project.readEntry( QStringLiteral( "WMSFeatureInfoDocumentElementNS" ), QStringLiteral( "/" ), "" );
}
QString QgsServerProjectUtils::wmsFeatureInfoSchema( const QgsProject &project )
{
return project.readEntry( QStringLiteral( "WMSFeatureInfoSchema" ), QStringLiteral( "/" ), "" );
}
QHash<QString, QString> QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( const QgsProject &project )
{
QHash<QString, QString> aliasMap;
//WMSFeatureInfoAliasLayers
QStringList aliasLayerStringList = project.readListEntry( QStringLiteral( "WMSFeatureInfoAliasLayers" ), QStringLiteral( "/value" ), QStringList() );
if ( aliasLayerStringList.isEmpty() )
{
return aliasMap;
}
//WMSFeatureInfoLayerAliases
QStringList layerAliasStringList = project.readListEntry( QStringLiteral( "WMSFeatureInfoLayerAliases" ), QStringLiteral( "/value" ), QStringList() );
if ( layerAliasStringList.isEmpty() )
{
return aliasMap;
}
int nMapEntries = qMin( aliasLayerStringList.size(), layerAliasStringList.size() );
for ( int i = 0; i < nMapEntries; ++i )
{
aliasMap.insert( aliasLayerStringList.at( i ), layerAliasStringList.at( i ) );
}
return aliasMap;
}
bool QgsServerProjectUtils::wmsInspireActivate( const QgsProject &project )
{
return project.readBoolEntry( QStringLiteral( "WMSInspire" ), QStringLiteral( "/activated" ) );

View File

@ -132,6 +132,48 @@ namespace QgsServerProjectUtils
*/
SERVER_EXPORT bool wmsInfoFormatSia2045( const QgsProject &project );
/** Returns if the geometry is displayed as Well Known Text in GetFeatureInfo request.
* \param project the QGIS project
* \returns if the geometry is displayed as Well Known Text in GetFeatureInfo request.
*/
SERVER_EXPORT bool wmsFeatureInfoAddWktGeometry( const QgsProject &project );
/** Returns if the geometry has to be segmentize in GetFeatureInfo request.
* \param project the QGIS project
* \returns if the geometry has to be segmentize in GetFeatureInfo request.
*/
SERVER_EXPORT bool wmsFeatureInfoSegmentizeWktGeometry( const QgsProject &project );
/** Returns the geometry precision for GetFeatureInfo request.
* \param project the QGIS project
* \returns the geometry precision for GetFeatureInfo request.
*/
SERVER_EXPORT int wmsFeatureInfoPrecision( const QgsProject &project );
/** Returns the document element name for XML GetFeatureInfo request.
* \param project the QGIS project
* \returns the document element name for XML GetFeatureInfo request.
*/
SERVER_EXPORT QString wmsFeatureInfoDocumentElement( const QgsProject &project );
/** Returns the document element namespace for XML GetFeatureInfo request.
* \param project the QGIS project
* \returns the document element namespace for XML GetFeatureInfo request.
*/
SERVER_EXPORT QString wmsFeatureInfoDocumentElementNs( const QgsProject &project );
/** Returns the schema URL for XML GetFeatureInfo request.
* \param project the QGIS project
* \returns the schema URL for XML GetFeatureInfo request.
*/
SERVER_EXPORT QString wmsFeatureInfoSchema( const QgsProject &project );
/** Returns the mapping between layer name and wms layer name for GetFeatureInfo request.
* \param project the QGIS project
* \returns the mapping between layer name and wms layer name for GetFeatureInfo request.
*/
SERVER_EXPORT QHash<QString, QString> wmsFeatureInfoLayerAliasMap( const QgsProject &project );
/** Returns if Inspire is activated.
* \param project the QGIS project
* \returns if Inspire is activated.

View File

@ -25,136 +25,6 @@
namespace QgsWms
{
void writeInfoResponse( QDomDocument &infoDoc, QgsServerResponse &response, const QString &infoFormat )
{
QByteArray ba;
QgsMessageLog::logMessage( "Info format is:" + infoFormat );
if ( infoFormat == QLatin1String( "text/xml" ) || infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) )
{
ba = infoDoc.toByteArray();
}
else if ( infoFormat == QLatin1String( "text/plain" ) || infoFormat == QLatin1String( "text/html" ) )
{
//create string
QString featureInfoString;
if ( infoFormat == QLatin1String( "text/plain" ) )
{
featureInfoString.append( "GetFeatureInfo results\n" );
featureInfoString.append( "\n" );
}
else if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "<HEAD>\n" );
featureInfoString.append( "<TITLE> GetFeatureInfo results </TITLE>\n" );
featureInfoString.append( "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">\n" );
featureInfoString.append( "</HEAD>\n" );
featureInfoString.append( "<BODY>\n" );
}
QDomNodeList layerList = infoDoc.elementsByTagName( QStringLiteral( "Layer" ) );
//layer loop
for ( int i = 0; i < layerList.size(); ++i )
{
QDomElement layerElem = layerList.at( i ).toElement();
if ( infoFormat == QLatin1String( "text/plain" ) )
{
featureInfoString.append( "Layer '" + layerElem.attribute( QStringLiteral( "name" ) ) + "'\n" );
}
else if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "<TABLE border=1 width=100%>\n" );
featureInfoString.append( "<TR><TH width=25%>Layer</TH><TD>" + layerElem.attribute( QStringLiteral( "name" ) ) + "</TD></TR>\n" );
featureInfoString.append( "</BR>" );
}
//feature loop (for vector layers)
QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
QDomElement currentFeatureElement;
if ( featureNodeList.isEmpty() ) //raster layer?
{
QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int j = 0; j < attributeNodeList.size(); ++j )
{
QDomElement attributeElement = attributeNodeList.at( j ).toElement();
if ( infoFormat == QLatin1String( "text/plain" ) )
{
featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" );
}
else if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) + "</TH><TD>" +
attributeElement.attribute( QStringLiteral( "value" ) ) + "</TD></TR>\n" );
}
}
}
else //vector layer
{
for ( int j = 0; j < featureNodeList.size(); ++j )
{
QDomElement featureElement = featureNodeList.at( j ).toElement();
if ( infoFormat == QLatin1String( "text/plain" ) )
{
featureInfoString.append( "Feature " + featureElement.attribute( QStringLiteral( "id" ) ) + "\n" );
}
else if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "<TABLE border=1 width=100%>\n" );
featureInfoString.append( "<TR><TH>Feature</TH><TD>" + featureElement.attribute( QStringLiteral( "id" ) ) + "</TD></TR>\n" );
}
//attribute loop
QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int k = 0; k < attributeNodeList.size(); ++k )
{
QDomElement attributeElement = attributeNodeList.at( k ).toElement();
if ( infoFormat == QLatin1String( "text/plain" ) )
{
featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" );
}
else if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) + "</TH><TD>" + attributeElement.attribute( QStringLiteral( "value" ) ) + "</TD></TR>\n" );
}
}
if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "</TABLE>\n</BR>\n" );
}
}
}
if ( infoFormat == QLatin1String( "text/plain" ) )
{
featureInfoString.append( "\n" );
}
else if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "</TABLE>\n<BR></BR>\n" );
}
}
if ( infoFormat == QLatin1String( "text/html" ) )
{
featureInfoString.append( "</BODY>\n" );
}
ba = featureInfoString.toUtf8();
}
else //unsupported format, set exception
{
throw QgsServiceException( QStringLiteral( "InvalidFormat" ),
QString( "Feature info format '%1' is not supported. Possibilities are 'text/plain', 'text/html' or 'text/xml'." ).arg( infoFormat ) );
}
response.setHeader( QStringLiteral( "Content-Type" ), infoFormat + QStringLiteral( "; charset=utf-8" ) );
response.write( ba );
}
void writeGetFeatureInfo( QgsServerInterface *serverIface, const QgsProject *project,
const QString &version, const QgsServerRequest &request,
QgsServerResponse &response )
@ -163,9 +33,11 @@ namespace QgsWms
QgsServerRequest::Parameters params = request.parameters();
QgsRenderer renderer( serverIface, project, params, getConfigParser( serverIface ) );
QDomDocument doc = renderer.getFeatureInfo( version );
QString outputFormat = params.value( QStringLiteral( "INFO_FORMAT" ), QStringLiteral( "text/plain" ) );
writeInfoResponse( doc, response, outputFormat );
std::unique_ptr<QByteArray> result( renderer.getFeatureInfo( version ) );
QString infoFormat = params.value( QStringLiteral( "INFO_FORMAT" ), QStringLiteral( "text/plain" ) );
response.setHeader( QStringLiteral( "Content-Type" ), infoFormat + QStringLiteral( "; charset=utf-8" ) );
response.write( *result );
}

View File

@ -191,6 +191,41 @@ namespace QgsWms
};
save( pFormat );
const Parameter pInfoFormat = { ParameterName::INFO_FORMAT,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pInfoFormat );
const Parameter pI = { ParameterName::I,
QVariant::Int,
QVariant( -1 ),
QVariant()
};
save( pI );
const Parameter pJ = { ParameterName::J,
QVariant::Int,
QVariant( -1 ),
QVariant()
};
save( pJ );
const Parameter pX = { ParameterName::X,
QVariant::Int,
QVariant( -1 ),
QVariant()
};
save( pX );
const Parameter pY = { ParameterName::Y,
QVariant::Int,
QVariant( -1 ),
QVariant()
};
save( pY );
const Parameter pRule = { ParameterName::RULE,
QVariant::String,
QVariant( "" ),
@ -254,6 +289,20 @@ namespace QgsWms
};
save( pLayers );
const Parameter pQueryLayers = { ParameterName::QUERY_LAYERS,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pQueryLayers );
const Parameter pFeatureCount = { ParameterName::FEATURE_COUNT,
QVariant::Int,
QVariant( 1 ),
QVariant()
};
save( pFeatureCount );
const Parameter pLayerTitle = { ParameterName::LAYERTITLE,
QVariant::Bool,
QVariant( true ),
@ -324,12 +373,26 @@ namespace QgsWms
};
save( pFilter );
const Parameter pFilterGeom = { ParameterName::FILTER_GEOM,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pFilterGeom );
const Parameter pSelection = { ParameterName::SELECTION,
QVariant::String,
QVariant( "" ),
QVariant()
};
save( pSelection );
const Parameter pWmsPrecision = { ParameterName::WMS_PRECISION,
QVariant::Int,
QVariant( -1 ),
QVariant()
};
save( pWmsPrecision );
}
QgsWmsParameters::QgsWmsParameters( const QgsServerRequest::Parameters &parameters )
@ -628,9 +691,12 @@ namespace QgsWms
QgsWmsParameters::Format QgsWmsParameters::format() const
{
Format f = Format::PNG;
QString fStr = formatAsString();
if ( fStr.isEmpty() )
return Format::NONE;
Format f = Format::PNG;
if ( fStr.compare( QLatin1String( "jpg" ), Qt::CaseInsensitive ) == 0
|| fStr.compare( QLatin1String( "jpeg" ), Qt::CaseInsensitive ) == 0
|| fStr.compare( QLatin1String( "image/jpeg" ), Qt::CaseInsensitive ) == 0 )
@ -639,6 +705,81 @@ namespace QgsWms
return f;
}
QString QgsWmsParameters::infoFormatAsString() const
{
return value( ParameterName::INFO_FORMAT ).toString();
}
QgsWmsParameters::Format QgsWmsParameters::infoFormat() const
{
QString fStr = infoFormatAsString();
Format f = Format::TEXT;
if ( fStr.isEmpty() )
return f;
if ( fStr.startsWith( QLatin1String( "text/xml" ), Qt::CaseInsensitive ) )
f = Format::XML;
else if ( fStr.startsWith( QLatin1String( "text/html" ), Qt::CaseInsensitive ) )
f = Format::HTML;
else if ( fStr.startsWith( QLatin1String( "application/vnd.ogc.gml" ), Qt::CaseInsensitive ) )
f = Format::GML;
return f;
}
int QgsWmsParameters::infoFormatVersion() const
{
if ( infoFormat() != Format::GML )
return -1;
QString fStr = infoFormatAsString();
if ( fStr.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ), Qt::CaseInsensitive ) )
return 3;
else
return 2;
}
QString QgsWmsParameters::i() const
{
return value( ParameterName::I ).toString();
}
QString QgsWmsParameters::j() const
{
return value( ParameterName::J ).toString();
}
int QgsWmsParameters::iAsInt() const
{
return toInt( ParameterName::I );
}
int QgsWmsParameters::jAsInt() const
{
return toInt( ParameterName::J );
}
QString QgsWmsParameters::x() const
{
return value( ParameterName::X ).toString();
}
QString QgsWmsParameters::y() const
{
return value( ParameterName::Y ).toString();
}
int QgsWmsParameters::xAsInt() const
{
return toInt( ParameterName::X );
}
int QgsWmsParameters::yAsInt() const
{
return toInt( ParameterName::Y );
}
QString QgsWmsParameters::rule() const
{
return value( ParameterName::RULE ).toString();
@ -674,6 +815,16 @@ namespace QgsWms
return toBool( ParameterName::SHOWFEATURECOUNT );
}
QString QgsWmsParameters::featureCount() const
{
return value( ParameterName::FEATURE_COUNT ).toString();
}
int QgsWmsParameters::featureCountAsInt() const
{
return toInt( ParameterName::FEATURE_COUNT );
}
QString QgsWmsParameters::boxSpace() const
{
return value( ParameterName::BOXSPACE ).toString();
@ -960,6 +1111,16 @@ namespace QgsWms
return toFloatList( highlightLabelBufferSize(), ParameterName::HIGHLIGHT_LABELBUFFERSIZE );
}
QString QgsWmsParameters::wmsPrecision() const
{
return value( ParameterName::WMS_PRECISION ).toString();
}
int QgsWmsParameters::wmsPrecisionAsInt() const
{
return toInt( ParameterName::WMS_PRECISION );
}
QString QgsWmsParameters::sld() const
{
return value( ParameterName::SLD ).toString();
@ -970,6 +1131,11 @@ namespace QgsWms
return toStringList( ParameterName::FILTER, ';' );
}
QString QgsWmsParameters::filterGeom() const
{
return value( ParameterName::FILTER_GEOM ).toString();
}
QStringList QgsWmsParameters::selections() const
{
return toStringList( ParameterName::SELECTION );
@ -992,6 +1158,11 @@ namespace QgsWms
return layer << layers;
}
QStringList QgsWmsParameters::queryLayersNickname() const
{
return toStringList( ParameterName::QUERY_LAYERS );
}
QStringList QgsWmsParameters::allStyles() const
{
QStringList style = value( ParameterName::STYLE ).toString().split( ",", QString::SkipEmptyParts );

View File

@ -86,6 +86,8 @@ namespace QgsWms
LAYERS,
LAYERSPACE,
LAYERTITLESPACE,
QUERY_LAYERS,
FEATURE_COUNT,
SHOWFEATURECOUNT,
STYLE,
STYLES,
@ -95,7 +97,13 @@ namespace QgsWms
OPACITIES,
SLD,
FILTER,
FILTER_GEOM,
FORMAT,
INFO_FORMAT,
I,
J,
X,
Y,
RULE,
RULELABEL,
SCALE,
@ -108,7 +116,8 @@ namespace QgsWms
HIGHLIGHT_LABELWEIGHT,
HIGHLIGHT_LABELCOLOR,
HIGHLIGHT_LABELBUFFERCOLOR,
HIGHLIGHT_LABELBUFFERSIZE
HIGHLIGHT_LABELBUFFERSIZE,
WMS_PRECISION
};
Q_ENUM( ParameterName )
@ -116,7 +125,11 @@ namespace QgsWms
{
NONE,
JPG,
PNG
PNG,
TEXT,
XML,
HTML,
GML
};
struct Parameter
@ -203,6 +216,11 @@ namespace QgsWms
*/
QStringList filters() const;
/** Returns the filter geometry found in FILTER_GEOM parameter.
* \returns the filter geometry as Well Known Text.
*/
QString filterGeom() const;
/** Returns the list of opacities found in OPACITIES parameter.
* \returns the list of opacities in string
*/
@ -221,6 +239,11 @@ namespace QgsWms
*/
QStringList allLayersNickname() const;
/** Returns nickname of layers found in QUERY_LAYERS parameter.
* \returns nickname of layers
*/
QStringList queryLayersNickname() const;
/** Returns styles found in STYLE and STYLES parameters.
* \returns name of styles
*/
@ -242,6 +265,75 @@ namespace QgsWms
*/
Format format() const;
/** Returns INFO_FORMAT parameter as a string.
* \returns INFO_FORMAT parameter as string
*/
QString infoFormatAsString() const;
/** Returns infoFormat. If the INFO_FORMAT parameter is not used, then the
* default value is text/plain.
* \returns infoFormat
*/
Format infoFormat() const;
/** Returns the infoFormat version for GML. If the INFO_FORMAT is not GML,
* then the default value is -1.
* \returns infoFormat version
*/
int infoFormatVersion() const;
/** Returns I parameter or an empty string if not defined.
* \returns i parameter
*/
QString i() const;
/** Returns I parameter as an int or its default value if not
* defined. An exception is raised if I is defined and cannot be
* converted.
* \returns i parameter
* \throws QgsBadRequestException
*/
int iAsInt() const;
/** Returns J parameter or an empty string if not defined.
* \returns j parameter
*/
QString j() const;
/** Returns J parameter as an int or its default value if not
* defined. An exception is raised if J is defined and cannot be
* converted.
* \returns j parameter
* \throws QgsBadRequestException
*/
int jAsInt() const;
/** Returns X parameter or an empty string if not defined.
* \returns x parameter
*/
QString x() const;
/** Returns X parameter as an int or its default value if not
* defined. An exception is raised if X is defined and cannot be
* converted.
* \returns x parameter
* \throws QgsBadRequestException
*/
int xAsInt() const;
/** Returns Y parameter or an empty string if not defined.
* \returns y parameter
*/
QString y() const;
/** Returns Y parameter as an int or its default value if not
* defined. An exception is raised if Y is defined and cannot be
* converted.
* \returns j parameter
* \throws QgsBadRequestException
*/
int yAsInt() const;
/** Returns RULE parameter or an empty string if none is defined
* \returns RULE parameter or an empty string if none is defined
*/
@ -271,6 +363,18 @@ namespace QgsWms
*/
bool showFeatureCountAsBool() const;
/** Returns FEATURE_COUNT parameter or an empty string if none is defined
* \returns FEATURE_COUNT parameter or an empty string if none is defined
*/
QString featureCount() const;
/** Returns FEATURE_COUNT as an integer. An exception is raised if an invalid
* parameter is found.
* \returns FeatureCount
* \throws QgsBadRequestException
*/
int featureCountAsInt() const;
/** Returns SCALE parameter or an empty string if none is defined
* \returns SCALE parameter or an empty string if none is defined
*/
@ -595,6 +699,19 @@ namespace QgsWms
*/
QList<QColor> highlightLabelBufferColorAsColor() const;
/** Returns WMS_PRECISION parameter or an empty string if not defined.
* \returns wms precision parameter
*/
QString wmsPrecision() const;
/** Returns WMS_PRECISION parameter as an int or its default value if not
* defined. An exception is raised if WMS_PRECISION is defined and cannot be
* converted.
* \returns wms precision parameter
* \throws QgsBadRequestException
*/
int wmsPrecisionAsInt() const;
private:
QString name( ParameterName name ) const;
void raiseError( ParameterName name ) const;

View File

@ -642,322 +642,111 @@ namespace QgsWms
infoPoint->setY( mapSettings.extent().yMaximum() - j * yRes - yRes / 2.0 );
}
QDomDocument QgsRenderer::getFeatureInfo( const QString &version )
QByteArray *QgsRenderer::getFeatureInfo( const QString &version )
{
if ( !mConfigParser )
// Verifying Mandatory parameters
// The QUERY_LAYERS parameter is Mandatory
QStringList queryLayers = mWmsParameters.queryLayersNickname();
if ( queryLayers.isEmpty() )
{
throw QgsException( QStringLiteral( "No config parser" ) );
throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ),
QStringLiteral( "QUERY_LAYERS parameter is required for GetFeatureInfo" ) );
}
QDomDocument result;
QStringList layersList, stylesList;
bool conversionSuccess;
// The I/J parameters are Mandatory if they are not replaced by X/Y or FILTER or FILTER_GEOM
bool ijDefined = false;
if ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() )
ijDefined = true;
for ( auto it = mParameters.constBegin(); it != mParameters.constEnd(); ++it )
bool xyDefined = false;
if ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() )
xyDefined = true;
bool filtersDefined = false;
if ( !mWmsParameters.filters().isEmpty() )
filtersDefined = true;
bool filterGeomDefined = false;
if ( !mWmsParameters.filterGeom().isEmpty() )
filterGeomDefined = true;
if ( !ijDefined && !xyDefined && !filtersDefined && !filterGeomDefined )
{
QgsMessageLog::logMessage( QStringLiteral( "%1 // %2" ).arg( it.key(), it.value() ) );
throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ),
QStringLiteral( "I/J parameters are required for GetFeatureInfo" ) );
}
readLayersAndStyles( mParameters, layersList, stylesList );
initializeSLDParser( layersList, stylesList );
// get layers parameters
QList<QgsMapLayer *> layers;
QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
// init layer restorer before doing anything
std::unique_ptr<QgsLayerRestorer> restorer;
restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
// init stylized layers according to LAYERS/STYLES or SLD
QString sld = mWmsParameters.sld();
if ( !sld.isEmpty() )
layers = sldStylizedLayers( sld );
else
layers = stylizedLayers( params );
// create the mapSettings and the output image
QgsMapSettings mapSettings;
std::unique_ptr<QImage> outputImage( createImage() );
// configure map settings (background, DPI, ...)
configureMapSettings( outputImage.get(), mapSettings );
QgsMessageLog::logMessage( "mapSettings.destinationCrs(): " + mapSettings.destinationCrs().authid() );
QgsMessageLog::logMessage( "mapSettings.extent(): " + mapSettings.extent().toString() );
QgsMessageLog::logMessage( QStringLiteral( "mapSettings width = %1 height = %2" ).arg( mapSettings.outputSize().width() ).arg( mapSettings.outputSize().height() ) );
QgsMessageLog::logMessage( QStringLiteral( "mapSettings.mapUnitsPerPixel() = %1" ).arg( mapSettings.mapUnitsPerPixel() ) );
//find out the current scale denominator and set it to the SLD parser
QgsScaleCalculator scaleCalc( ( outputImage->logicalDpiX() + outputImage->logicalDpiY() ) / 2, mapSettings.destinationCrs().mapUnits() );
QgsRectangle mapExtent = mapSettings.extent();
double scaleDenominator = scaleCalc.calculate( mapExtent, outputImage->width() );
mConfigParser->setScaleDenominator( scaleDenominator );
//read FEATURE_COUNT
int featureCount = 1;
if ( mParameters.contains( QStringLiteral( "FEATURE_COUNT" ) ) )
// remove unwanted layers (restricted layers, ...)
removeUnwantedLayers( layers, scaleDenominator );
// remove non identifiable layers
removeNonIdentifiableLayers( layers );
Q_FOREACH ( QgsMapLayer *layer, layers )
{
featureCount = mParameters[ QStringLiteral( "FEATURE_COUNT" )].toInt( &conversionSuccess );
if ( !conversionSuccess )
Q_FOREACH ( QgsWmsParametersLayer param, params )
{
featureCount = 1;
if ( param.mNickname == layerNickname( *layer ) )
{
checkLayerReadPermissions( layer );
setLayerFilter( layer, param.mFilter );
setLayerAccessControlFilter( layer );
break;
}
}
}
//read QUERY_LAYERS
if ( !mParameters.contains( QStringLiteral( "QUERY_LAYERS" ) ) )
{
throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ), QStringLiteral( "No QUERY_LAYERS" ) );
}
// add layers to map settings (revert order for the rendering)
std::reverse( layers.begin(), layers.end() );
mapSettings.setLayers( layers );
QStringList queryLayerList = mParameters[ QStringLiteral( "QUERY_LAYERS" )].split( ',', QString::SkipEmptyParts );
if ( queryLayerList.isEmpty() )
{
throw QgsBadRequestException( QStringLiteral( "InvalidParameterValue" ), QStringLiteral( "Malformed QUERY_LAYERS" ) );
}
QDomDocument result = featureInfoDocument( layers, mapSettings, outputImage.get(), version );
//read I,J resp. X,Y
QString iString = mParameters.value( QStringLiteral( "I" ), mParameters.value( QStringLiteral( "X" ) ) );
int i = iString.toInt( &conversionSuccess );
if ( !conversionSuccess )
{
i = -1;
}
QByteArray *ba = nullptr;
ba = new QByteArray();
QString jString = mParameters.value( QStringLiteral( "J" ), mParameters.value( QStringLiteral( "Y" ) ) );
int j = jString.toInt( &conversionSuccess );
if ( !conversionSuccess )
{
j = -1;
}
//read FILTER_GEOM
std::unique_ptr<QgsGeometry> filterGeom;
if ( mParameters.contains( QStringLiteral( "FILTER_GEOM" ) ) )
{
filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mParameters.value( QStringLiteral( "FILTER_GEOM" ) ) ) ) );
}
//In case the output image is distorted (WIDTH/HEIGHT ratio not equal to BBOX width/height), I and J need to be adapted as well
int widthParam = mParameters.value( "WIDTH", "-1" ).toInt();
int heightParam = mParameters.value( "HEIGHT", "-1" ).toInt();
if ( ( i != -1 && j != -1 && widthParam != -1 && heightParam != -1 ) && ( widthParam != outputImage->width() || heightParam != outputImage->height() ) )
{
i *= ( outputImage->width() / ( double )widthParam );
j *= ( outputImage->height() / ( double )heightParam );
}
//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
std::unique_ptr<QgsRectangle> featuresRect;
std::unique_ptr<QgsPointXY> infoPoint;
if ( i == -1 || j == -1 )
{
if ( mParameters.contains( QStringLiteral( "FILTER" ) ) )
{
featuresRect.reset( new QgsRectangle() );
}
else if ( !filterGeom.get() )
{
throw QgsBadRequestException( QStringLiteral( "ParameterMissing" ),
QStringLiteral( "I/J parameters are required for GetFeatureInfo" ) );
}
}
QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat();
if ( infoFormat == QgsWmsParameters::Format::TEXT )
*ba = convertFeatureInfoToText( result );
else if ( infoFormat == QgsWmsParameters::Format::HTML )
*ba = convertFeatureInfoToHtml( result );
else
{
infoPoint.reset( new QgsPointXY() );
infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings );
}
*ba = result.toByteArray();
//get the layer registered in QgsMapLayerRegistry and apply possible filters
( void )layerSet( layersList, stylesList, mapSettings.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
std::unique_ptr< QgsOWSServerFilterRestorer > filterRestorer( new QgsOWSServerFilterRestorer( mAccessControl ) );
applyRequestedLayerFilters( layersList, mapSettings, filterRestorer->originalFilters() );
#ifdef HAVE_SERVER_PYTHON_PLUGINS
applyAccessControlLayersFilters( layersList, filterRestorer->originalFilters() );
#endif
QDomElement getFeatureInfoElement;
QString infoFormat = mParameters.value( QStringLiteral( "INFO_FORMAT" ) );
if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) )
{
getFeatureInfoElement = result.createElement( QStringLiteral( "wfs:FeatureCollection" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:wfs" ), QStringLiteral( "http://www.opengis.net/wfs" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ows" ), QStringLiteral( "http://www.opengis.net/ows" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:qgs" ), QStringLiteral( "http://qgis.org/gml" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://qgis.org/gml" ) );
}
else
{
QString featureInfoElemName = mConfigParser->featureInfoDocumentElement( QStringLiteral( "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( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), featureInfoSchema );
}
}
result.appendChild( getFeatureInfoElement );
QStringList nonIdentifiableLayers = mConfigParser->identifyDisabledLayers();
//Render context is needed to determine feature visibility for vector layers
QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings );
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;
for ( auto 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, QLatin1String( "" ) );
for ( auto layerListIt = layerList.begin() ; layerListIt != layerList.end(); ++layerListIt )
{
currentLayer = *layerListIt;
if ( !currentLayer || nonIdentifiableLayers.contains( currentLayer->id() ) )
{
continue;
}
QgsMapLayer *registeredMapLayer = QgsProject::instance()->mapLayer( currentLayer->id() );
if ( registeredMapLayer )
{
currentLayer = registeredMapLayer;
}
#ifdef HAVE_SERVER_PYTHON_PLUGINS
if ( !mAccessControl->layerReadPermission( currentLayer ) )
{
throw QgsSecurityException( QStringLiteral( "You are not allowed to access to the layer: %1" ).arg( currentLayer->name() ) );
}
#endif
//skip layer if not visible at current map scale
if ( scaleDenominator > 0 && !currentLayer->isInScaleRange( scaleDenominator ) )
{
continue;
}
//switch depending on vector or raster
QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( currentLayer );
QDomElement layerElement;
if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) )
{
layerElement = getFeatureInfoElement;
}
else
{
layerElement = result.createElement( QStringLiteral( "Layer" ) );
QString layerName = currentLayer->name();
if ( 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( QStringLiteral( "name" ), layerName );
getFeatureInfoElement.appendChild( layerElement );
if ( sia2045 ) //the name might not be unique after alias replacement
{
layerElement.setAttribute( QStringLiteral( "id" ), currentLayer->id() );
}
}
if ( vectorLayer )
{
if ( !featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, infoFormat, featuresRect.get(), filterGeom.get() ) )
{
continue;
}
}
else //raster layer
{
if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) )
{
layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
getFeatureInfoElement.appendChild( layerElement );
}
QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( currentLayer );
if ( rasterLayer )
{
if ( !infoPoint )
{
continue;
}
QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( currentLayer, *( infoPoint.get() ) );
if ( !featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version, infoFormat ) )
{
continue;
}
}
else
{
continue;
}
}
}
}
if ( featuresRect )
{
if ( infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml" ) ) )
{
QDomElement bBoxElem = result.createElement( QStringLiteral( "gml:boundedBy" ) );
QDomElement boxElem;
int gmlVersion = infoFormat.startsWith( QLatin1String( "application/vnd.ogc.gml/3" ) ) ? 3 : 2;
if ( gmlVersion < 3 )
{
boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 );
}
else
{
boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect.get(), result, 8 );
}
QgsCoordinateReferenceSystem crs = mapSettings.destinationCrs();
if ( crs.isValid() )
{
boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
}
bBoxElem.appendChild( boxElem );
getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
}
else
{
QDomElement bBoxElem = result.createElement( QStringLiteral( "BoundingBox" ) );
bBoxElem.setAttribute( QStringLiteral( "CRS" ), mapSettings.destinationCrs().authid() );
bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( featuresRect->xMinimum(), 8 ) );
bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( featuresRect->xMaximum(), 8 ) );
bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( featuresRect->yMinimum(), 8 ) );
bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( featuresRect->yMaximum(), 8 ) );
getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
}
}
if ( sia2045 && infoFormat.compare( QLatin1String( "text/xml" ), Qt::CaseInsensitive ) == 0 )
{
convertFeatureInfoToSIA2045( result );
}
//force restoration of original filters
filterRestorer.reset();
QgsProject::instance()->removeAllMapLayers();
return result;
return ba;
}
QImage *QgsRenderer::initializeRendering( QStringList &layersList, QStringList &stylesList, QStringList &layerIdList, QgsMapSettings &mapSettings )
@ -1243,6 +1032,224 @@ namespace QgsWms
}
}
QDomDocument QgsRenderer::featureInfoDocument( QList<QgsMapLayer *> &layers, const QgsMapSettings &mapSettings,
const QImage *outputImage, const QString &version ) const
{
QStringList queryLayers = mWmsParameters.queryLayersNickname();
bool ijDefined = ( !mWmsParameters.i().isEmpty() && !mWmsParameters.j().isEmpty() );
bool xyDefined = ( !mWmsParameters.x().isEmpty() && !mWmsParameters.y().isEmpty() );
bool filtersDefined = !mWmsParameters.filters().isEmpty();
bool filterGeomDefined = !mWmsParameters.filterGeom().isEmpty();
int featureCount = mWmsParameters.featureCountAsInt();
if ( featureCount < 1 )
{
featureCount = 1;
}
int i = mWmsParameters.iAsInt();
int j = mWmsParameters.jAsInt();
if ( xyDefined && !ijDefined )
{
i = mWmsParameters.xAsInt();
j = mWmsParameters.yAsInt();
}
int width = mWmsParameters.widthAsInt();
int height = mWmsParameters.heightAsInt();
if ( ( i != -1 && j != -1 && width != 0 && height != 0 ) && ( width != outputImage->width() || height != outputImage->height() ) )
{
i *= ( outputImage->width() / ( double )width );
j *= ( outputImage->height() / ( double )height );
}
// init search variables
std::unique_ptr<QgsRectangle> featuresRect;
std::unique_ptr<QgsGeometry> filterGeom;
std::unique_ptr<QgsPointXY> infoPoint;
if ( i != -1 && j != -1 )
{
infoPoint.reset( new QgsPointXY() );
infoPointToMapCoordinates( i, j, infoPoint.get(), mapSettings );
}
else if ( filtersDefined )
{
featuresRect.reset( new QgsRectangle() );
}
else if ( filterGeomDefined )
{
filterGeom.reset( new QgsGeometry( QgsGeometry::fromWkt( mWmsParameters.filterGeom() ) ) );
}
QDomDocument result;
QDomElement getFeatureInfoElement;
QgsWmsParameters::Format infoFormat = mWmsParameters.infoFormat();
if ( infoFormat == QgsWmsParameters::Format::GML )
{
getFeatureInfoElement = result.createElement( QStringLiteral( "wfs:FeatureCollection" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:wfs" ), QStringLiteral( "http://www.opengis.net/wfs" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:gml" ), QStringLiteral( "http://www.opengis.net/gml" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:ows" ), QStringLiteral( "http://www.opengis.net/ows" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:qgs" ), QStringLiteral( "http://qgis.org/gml" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd http://qgis.org/gml" ) );
}
else
{
QString featureInfoElemName = QgsServerProjectUtils::wmsFeatureInfoDocumentElement( *mProject );
if ( featureInfoElemName.isEmpty() )
{
featureInfoElemName = QStringLiteral( "GetFeatureInfoResponse" );
}
QString featureInfoElemNs = QgsServerProjectUtils::wmsFeatureInfoDocumentElementNs( *mProject );
if ( featureInfoElemNs.isEmpty() )
{
getFeatureInfoElement = result.createElement( featureInfoElemName );
}
else
{
getFeatureInfoElement = result.createElementNS( featureInfoElemNs, featureInfoElemName );
}
//feature info schema
QString featureInfoSchema = QgsServerProjectUtils::wmsFeatureInfoSchema( *mProject );
if ( !featureInfoSchema.isEmpty() )
{
getFeatureInfoElement.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) );
getFeatureInfoElement.setAttribute( QStringLiteral( "xsi:schemaLocation" ), featureInfoSchema );
}
}
result.appendChild( getFeatureInfoElement );
//Render context is needed to determine feature visibility for vector layers
QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings );
bool sia2045 = QgsServerProjectUtils::wmsInfoFormatSia2045( *mProject );
//layers can have assigned a different name for GetCapabilities
QHash<QString, QString> layerAliasMap = QgsServerProjectUtils::wmsFeatureInfoLayerAliasMap( *mProject );
Q_FOREACH ( QString queryLayer, queryLayers )
{
Q_FOREACH ( QgsMapLayer *layer, layers )
{
if ( queryLayer == layerNickname( *layer ) )
{
QDomElement layerElement;
if ( infoFormat == QgsWmsParameters::Format::GML )
{
layerElement = getFeatureInfoElement;
}
else
{
layerElement = result.createElement( QStringLiteral( "Layer" ) );
QString layerName = queryLayer;
//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( QStringLiteral( "name" ), layerName );
getFeatureInfoElement.appendChild( layerElement );
if ( sia2045 ) //the name might not be unique after alias replacement
{
layerElement.setAttribute( QStringLiteral( "id" ), layer->id() );
}
}
if ( layer->type() == QgsMapLayer::VectorLayer )
{
QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vectorLayer )
{
if ( !featureInfoFromVectorLayer( vectorLayer, infoPoint.get(), featureCount, result, layerElement, mapSettings, renderContext, version, featuresRect.get(), filterGeom.get() ) )
{
break;
}
break;
}
}
else
{
if ( infoFormat == QgsWmsParameters::Format::GML )
{
layerElement = result.createElement( QStringLiteral( "gml:featureMember" )/*wfs:FeatureMember*/ );
getFeatureInfoElement.appendChild( layerElement );
}
QgsRasterLayer *rasterLayer = qobject_cast<QgsRasterLayer *>( layer );
if ( rasterLayer )
{
if ( !infoPoint )
{
break;
}
QgsPointXY layerInfoPoint = mapSettings.mapToLayerCoordinates( layer, *( infoPoint.get() ) );
if ( !featureInfoFromRasterLayer( rasterLayer, mapSettings, &layerInfoPoint, result, layerElement, version ) )
{
break;
}
break;
}
}
break;
}
}
}
if ( featuresRect )
{
if ( infoFormat == QgsWmsParameters::Format::GML )
{
QDomElement bBoxElem = result.createElement( QStringLiteral( "gml:boundedBy" ) );
QDomElement boxElem;
int gmlVersion = mWmsParameters.infoFormatVersion();
if ( gmlVersion < 3 )
{
boxElem = QgsOgcUtils::rectangleToGMLBox( featuresRect.get(), result, 8 );
}
else
{
boxElem = QgsOgcUtils::rectangleToGMLEnvelope( featuresRect.get(), result, 8 );
}
QgsCoordinateReferenceSystem crs = mapSettings.destinationCrs();
if ( crs.isValid() )
{
boxElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
}
bBoxElem.appendChild( boxElem );
getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
}
else
{
QDomElement bBoxElem = result.createElement( QStringLiteral( "BoundingBox" ) );
bBoxElem.setAttribute( QStringLiteral( "CRS" ), mapSettings.destinationCrs().authid() );
bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( featuresRect->xMinimum(), 8 ) );
bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( featuresRect->xMaximum(), 8 ) );
bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( featuresRect->yMinimum(), 8 ) );
bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( featuresRect->yMaximum(), 8 ) );
getFeatureInfoElement.insertBefore( bBoxElem, QDomNode() ); //insert as first child
}
}
if ( sia2045 && infoFormat == QgsWmsParameters::Format::XML )
{
convertFeatureInfoToSia2045( result );
}
return result;
}
bool QgsRenderer::featureInfoFromVectorLayer( QgsVectorLayer *layer,
const QgsPointXY *infoPoint,
int nFeatures,
@ -1251,7 +1258,6 @@ namespace QgsWms
const QgsMapSettings &mapSettings,
QgsRenderContext &renderContext,
const QString &version,
const QString &infoFormat,
QgsRectangle *featureBBox,
QgsGeometry *filterGeom ) const
{
@ -1288,8 +1294,8 @@ namespace QgsWms
int featureCounter = 0;
layer->updateFields();
const QgsFields &fields = layer->pendingFields();
bool addWktGeometry = mConfigParser && mConfigParser->featureInfoWithWktGeometry();
bool segmentizeWktGeometry = mConfigParser && mConfigParser->segmentizeFeatureInfoWktGeometry();
bool addWktGeometry = QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject );
bool segmentizeWktGeometry = QgsServerProjectUtils::wmsFeatureInfoSegmentizeWktGeometry( *mProject );
const QSet<QString> &excludedAttributes = layer->excludeAttributesWms();
QgsFeatureRequest fReq;
@ -1385,17 +1391,13 @@ namespace QgsWms
outputCrs = mapSettings.destinationCrs();
}
if ( infoFormat == QLatin1String( "application/vnd.ogc.gml" ) )
if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
{
bool withGeom = layer->wkbType() != QgsWkbTypes::NoGeometry && addWktGeometry;
int version = infoFormat.startsWith( QLatin1String( "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();
int gmlVersion = mWmsParameters.infoFormatVersion();
QString typeName = layerNickname( *layer );
QDomElement elem = createFeatureGML(
&feature, layer, infoDocument, outputCrs, mapSettings, typeName, withGeom, version
&feature, layer, infoDocument, outputCrs, mapSettings, typeName, withGeom, gmlVersion
#ifdef HAVE_SERVER_PYTHON_PLUGINS
, &attributes
#endif
@ -1453,14 +1455,14 @@ namespace QgsWms
}
//append feature bounding box to feature info xml
if ( layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry && mConfigParser )
if ( layer->wkbType() != QgsWkbTypes::NoGeometry && hasGeometry )
{
QDomElement bBoxElem = infoDocument.createElement( QStringLiteral( "BoundingBox" ) );
bBoxElem.setAttribute( version == QLatin1String( "1.1.1" ) ? "SRS" : "CRS", outputCrs.authid() );
bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( box.xMinimum(), getWMSPrecision( 8 ) ) );
bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( box.xMaximum(), getWMSPrecision( 8 ) ) );
bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( box.yMinimum(), getWMSPrecision( 8 ) ) );
bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( box.yMaximum(), getWMSPrecision( 8 ) ) );
bBoxElem.setAttribute( QStringLiteral( "minx" ), qgsDoubleToString( box.xMinimum(), getWMSPrecision() ) );
bBoxElem.setAttribute( QStringLiteral( "maxx" ), qgsDoubleToString( box.xMaximum(), getWMSPrecision() ) );
bBoxElem.setAttribute( QStringLiteral( "miny" ), qgsDoubleToString( box.yMinimum(), getWMSPrecision() ) );
bBoxElem.setAttribute( QStringLiteral( "maxy" ), qgsDoubleToString( box.yMaximum(), getWMSPrecision() ) );
featureElement.appendChild( bBoxElem );
}
@ -1484,14 +1486,14 @@ namespace QgsWms
{
if ( QgsWkbTypes::isCurvedType( abstractGeom->wkbType() ) )
{
QgsAbstractGeometry *segmentizedGeom = abstractGeom-> segmentize();
QgsAbstractGeometry *segmentizedGeom = abstractGeom->segmentize();
geom.setGeometry( segmentizedGeom );
}
}
}
QDomElement geometryElement = infoDocument.createElement( QStringLiteral( "Attribute" ) );
geometryElement.setAttribute( QStringLiteral( "name" ), QStringLiteral( "geometry" ) );
geometryElement.setAttribute( QStringLiteral( "value" ), geom.exportToWkt( getWMSPrecision( 8 ) ) );
geometryElement.setAttribute( QStringLiteral( "value" ), geom.exportToWkt( getWMSPrecision() ) );
geometryElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "derived" ) );
featureElement.appendChild( geometryElement );
}
@ -1511,8 +1513,7 @@ namespace QgsWms
const QgsPointXY *infoPoint,
QDomDocument &infoDocument,
QDomElement &layerElement,
const QString &version,
const QString &infoFormat ) const
const QString &version ) const
{
Q_UNUSED( version );
@ -1540,7 +1541,7 @@ namespace QgsWms
attributes = layer->dataProvider()->identify( *infoPoint, QgsRaster::IdentifyFormatValue, mapSettings.extent(), mapSettings.outputSize().width(), mapSettings.outputSize().height() ).results();
}
if ( infoFormat == QLatin1String( "application/vnd.ogc.gml" ) )
if ( mWmsParameters.infoFormat() == QgsWmsParameters::Format::GML )
{
QgsFeature feature;
QgsFields fields;
@ -1554,14 +1555,10 @@ namespace QgsWms
feature.setFields( fields );
QgsCoordinateReferenceSystem layerCrs = layer->crs();
int version = infoFormat.startsWith( QLatin1String( "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();
int gmlVersion = mWmsParameters.infoFormatVersion();
QString typeName = layerNickname( *layer );
QDomElement elem = createFeatureGML(
&feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, version, nullptr );
&feature, nullptr, infoDocument, layerCrs, mapSettings, typeName, false, gmlVersion, nullptr );
layerElement.appendChild( elem );
}
else
@ -2097,7 +2094,7 @@ namespace QgsWms
return true;
}
void QgsRenderer::convertFeatureInfoToSIA2045( QDomDocument &doc )
void QgsRenderer::convertFeatureInfoToSia2045( QDomDocument &doc ) const
{
QDomDocument SIAInfoDoc;
QDomElement infoDocElement = doc.documentElement();
@ -2212,6 +2209,130 @@ namespace QgsWms
doc = SIAInfoDoc;
}
QByteArray QgsRenderer::convertFeatureInfoToHtml( const QDomDocument &doc ) const
{
QString featureInfoString;
//the HTML head
featureInfoString.append( "<HEAD>\n" );
featureInfoString.append( "<TITLE> GetFeatureInfo results </TITLE>\n" );
featureInfoString.append( "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">\n" );
featureInfoString.append( "</HEAD>\n" );
//start the html body
featureInfoString.append( "<BODY>\n" );
QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
//layer loop
for ( int i = 0; i < layerList.size(); ++i )
{
QDomElement layerElem = layerList.at( i ).toElement();
featureInfoString.append( "<TABLE border=1 width=100%>\n" );
featureInfoString.append( "<TR><TH width=25%>Layer</TH><TD>" + layerElem.attribute( QStringLiteral( "name" ) ) + "</TD></TR>\n" );
featureInfoString.append( "</BR>" );
//feature loop (for vector layers)
QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
QDomElement currentFeatureElement;
if ( !featureNodeList.isEmpty() ) //vector layer
{
for ( int j = 0; j < featureNodeList.size(); ++j )
{
QDomElement featureElement = featureNodeList.at( j ).toElement();
featureInfoString.append( "<TABLE border=1 width=100%>\n" );
featureInfoString.append( "<TR><TH>Feature</TH><TD>" + featureElement.attribute( QStringLiteral( "id" ) ) +
"</TD></TR>\n" );
//attribute loop
QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int k = 0; k < attributeNodeList.size(); ++k )
{
QDomElement attributeElement = attributeNodeList.at( k ).toElement();
featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) +
"</TH><TD>" + attributeElement.attribute( QStringLiteral( "value" ) ) + "</TD></TR>\n" );
}
featureInfoString.append( "</TABLE>\n</BR>\n" );
}
}
else //raster layer
{
QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int j = 0; j < attributeNodeList.size(); ++j )
{
QDomElement attributeElement = attributeNodeList.at( j ).toElement();
featureInfoString.append( "<TR><TH>" + attributeElement.attribute( QStringLiteral( "name" ) ) +
"</TH><TD>" + attributeElement.attribute( QStringLiteral( "value" ) ) + "</TD></TR>\n" );
}
}
featureInfoString.append( "</TABLE>\n<BR></BR>\n" );
}
//start the html body
featureInfoString.append( "</BODY>\n" );
return featureInfoString.toUtf8();
}
QByteArray QgsRenderer::convertFeatureInfoToText( const QDomDocument &doc ) const
{
QString featureInfoString;
//the Text head
featureInfoString.append( "GetFeatureInfo results\n" );
featureInfoString.append( "\n" );
QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
//layer loop
for ( int i = 0; i < layerList.size(); ++i )
{
QDomElement layerElem = layerList.at( i ).toElement();
featureInfoString.append( "Layer '" + layerElem.attribute( QStringLiteral( "name" ) ) + "'\n" );
//feature loop (for vector layers)
QDomNodeList featureNodeList = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
QDomElement currentFeatureElement;
if ( !featureNodeList.isEmpty() ) //vector layer
{
for ( int j = 0; j < featureNodeList.size(); ++j )
{
QDomElement featureElement = featureNodeList.at( j ).toElement();
featureInfoString.append( "Feature " + featureElement.attribute( QStringLiteral( "id" ) ) + "\n" );
//attribute loop
QDomNodeList attributeNodeList = featureElement.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int k = 0; k < attributeNodeList.size(); ++k )
{
QDomElement attributeElement = attributeNodeList.at( k ).toElement();
featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" );
}
}
}
else //raster layer
{
QDomNodeList attributeNodeList = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int j = 0; j < attributeNodeList.size(); ++j )
{
QDomElement attributeElement = attributeNodeList.at( j ).toElement();
featureInfoString.append( attributeElement.attribute( QStringLiteral( "name" ) ) + " = '" +
attributeElement.attribute( QStringLiteral( "value" ) ) + "'\n" );
}
}
featureInfoString.append( "\n" );
}
return featureInfoString.toUtf8();
}
QDomElement QgsRenderer::createFeatureGML(
QgsFeature *feat,
QgsVectorLayer *layer,
@ -2263,11 +2384,11 @@ namespace QgsWms
QDomElement boxElem;
if ( version < 3 )
{
boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, 8 );
boxElem = QgsOgcUtils::rectangleToGMLBox( &box, doc, getWMSPrecision() );
}
else
{
boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, 8 );
boxElem = QgsOgcUtils::rectangleToGMLEnvelope( &box, doc, getWMSPrecision() );
}
if ( crs.isValid() )
@ -2291,11 +2412,11 @@ namespace QgsWms
QDomElement gmlElem;
if ( version < 3 )
{
gmlElem = QgsOgcUtils::geometryToGML( geom, doc, 8 );
gmlElem = QgsOgcUtils::geometryToGML( geom, doc, getWMSPrecision() );
}
else
{
gmlElem = QgsOgcUtils::geometryToGML( geom, doc, QStringLiteral( "GML3" ), 8 );
gmlElem = QgsOgcUtils::geometryToGML( geom, doc, QStringLiteral( "GML3" ), getWMSPrecision() );
}
if ( !gmlElem.isNull() )
@ -2388,27 +2509,18 @@ namespace QgsWms
return imageQuality;
}
int QgsRenderer::getWMSPrecision( int defaultValue = 8 ) const
int QgsRenderer::getWMSPrecision() const
{
// First taken from QGIS project
int WMSPrecision = mConfigParser->wmsPrecision();
// First taken from QGIS project and the default value is 6
int WMSPrecision = QgsServerProjectUtils::wmsFeatureInfoPrecision( *mProject );
// Then checks if a parameter is given, if so use it instead
if ( mParameters.contains( QStringLiteral( "WMS_PRECISION" ) ) )
{
bool conversionSuccess;
int WMSPrecisionParameter;
WMSPrecisionParameter = mParameters[ QStringLiteral( "WMS_PRECISION" )].toInt( &conversionSuccess );
if ( conversionSuccess )
{
WMSPrecision = WMSPrecisionParameter;
}
}
if ( WMSPrecision == -1 )
{
WMSPrecision = defaultValue;
}
return WMSPrecision;
int WMSPrecisionParameter = mWmsParameters.wmsPrecisionAsInt();
if ( WMSPrecisionParameter > -1 )
return WMSPrecisionParameter;
else
return WMSPrecision;
}
QgsRectangle QgsRenderer::featureInfoSearchRect( QgsVectorLayer *ml, const QgsMapSettings &mapSettings, const QgsRenderContext &rct, const QgsPointXY &infoPoint ) const
@ -2929,6 +3041,25 @@ namespace QgsWms
layers = wantedLayers;
}
void QgsRenderer::removeNonIdentifiableLayers( QList<QgsMapLayer *> &layers ) const
{
QStringList nonIdentifiableLayers = mProject->nonIdentifiableLayers();
if ( !nonIdentifiableLayers.isEmpty() )
{
QList<QgsMapLayer *> wantedLayers;
Q_FOREACH ( QgsMapLayer *layer, layers )
{
if ( nonIdentifiableLayers.contains( layer->id() ) )
continue;
wantedLayers.append( layer );
}
layers = wantedLayers;
}
}
QgsLayerTreeModel *QgsRenderer::buildLegendTreeModel( const QList<QgsMapLayer *> &layers, double scaleDenominator, QgsLayerTree &rootGroup )
{
// get params

View File

@ -116,7 +116,7 @@ namespace QgsWms
/** Creates an xml document that describes the result of the getFeatureInfo request.
* May throw an exception
*/
QDomDocument getFeatureInfo( const QString &version = "1.3.0" );
QByteArray *getFeatureInfo( const QString &version = "1.3.0" );
private:
@ -140,6 +140,9 @@ namespace QgsWms
// Remove unwanted layers (restricted, not visible, etc)
void removeUnwantedLayers( QList<QgsMapLayer *> &layers, double scaleDenominator = -1 ) const;
// Remove non identifiable layers (restricted, not visible, etc)
void removeNonIdentifiableLayers( QList<QgsMapLayer *> &layers ) const;
// Rendering step for layers
QPainter *layersRendering( const QgsMapSettings &mapSettings, QImage &image, HitTest *hitTest = nullptr ) const;
@ -211,6 +214,9 @@ namespace QgsWms
*/
void initializeSLDParser( QStringList &layersList, QStringList &stylesList );
QDomDocument featureInfoDocument( QList<QgsMapLayer *> &layers, const QgsMapSettings &mapSettings,
const QImage *outputImage, const QString &version ) const;
/** Appends feature info xml for the layer to the layer element of the feature info dom document
\param featureBBox the bounding box of the selected features in output CRS
\returns true in case of success*/
@ -222,7 +228,6 @@ namespace QgsWms
const QgsMapSettings &mapSettings,
QgsRenderContext &renderContext,
const QString &version,
const QString &infoFormat,
QgsRectangle *featureBBox = nullptr,
QgsGeometry *filterGeom = nullptr ) const;
//! Appends feature info xml for the layer to the layer element of the dom document
@ -231,8 +236,7 @@ namespace QgsWms
const QgsPointXY *infoPoint,
QDomDocument &infoDocument,
QDomElement &layerElement,
const QString &version,
const QString &infoFormat ) const;
const QString &version ) const;
/** Creates a layer set and returns a stringlist with layer ids that can be passed to a renderer. Usually used in conjunction with readLayersAndStyles
\param scaleDenominator Filter out layer if scale based visibility does not match (or use -1 if no scale restriction)*/
@ -291,7 +295,13 @@ namespace QgsWms
bool checkMaximumWidthHeight() const;
//! Converts a feature info xml document to SIA2045 norm
void convertFeatureInfoToSIA2045( QDomDocument &doc );
void convertFeatureInfoToSia2045( QDomDocument &doc ) const;
//! Converts a feature info xml document to HTML
QByteArray convertFeatureInfoToHtml( const QDomDocument &doc ) const;
//! Converts a feature info xml document to Text
QByteArray convertFeatureInfoToText( const QDomDocument &doc ) const;
QDomElement createFeatureGML(
QgsFeature *feat,
@ -334,7 +344,7 @@ namespace QgsWms
int getImageQuality() const;
//! Return precision to use for GetFeatureInfo request
int getWMSPrecision( int defaultValue ) const;
int getWMSPrecision() const;
};

View File

@ -453,6 +453,33 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<qgs:color>red</qgs:color>") != -1, # spellok
"No color in result of GetFeatureInfo\n%s" % response)
response, headers = self._get_restricted(query_string)
self.assertEqual(
headers.get("Content-Type"), "text/xml; charset=utf-8",
"Content type for GetFeatureInfo is wrong: %s" % headers.get("Content-Type"))
self.assertTrue(
str(response).find('<ServiceException code="Security">') != -1,
"Not allowed do a GetFeatureInfo on Country"
)
query_string = "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetFeatureInfo",
"LAYERS": "Hello",
"QUERY_LAYERS": "Hello",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
"HEIGHT": "500",
"WIDTH": "500",
"SRS": "EPSG:3857",
"FEATURE_COUNT": "10",
"INFO_FORMAT": "application/vnd.ogc.gml",
"X": "56",
"Y": "144"
}.items())])
response, headers = self._get_restricted(query_string)
self.assertTrue(
str(response).find("<qgs:pk>1</qgs:pk>") != -1,
@ -1053,6 +1080,33 @@ class TestQgsServerAccessControl(unittest.TestCase):
str(response).find("<qgs:pk>1</qgs:pk>") != -1,
"No good result in GetFeatureInfo Hello/1\n%s" % response)
response, headers = self._get_restricted(query_string)
self.assertEqual(
headers.get("Content-Type"), "text/xml; charset=utf-8",
"Content type for GetFeatureInfo is wrong: %s" % headers.get("Content-Type"))
self.assertTrue(
str(response).find('<ServiceException code="Security">') != -1,
"Not allowed do a GetFeatureInfo on Country"
)
query_string = "&".join(["%s=%s" % i for i in list({
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetFeatureInfo",
"LAYERS": "Hello_SubsetString",
"QUERY_LAYERS": "Hello_SubsetString",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-6318936.5,5696513,16195283.5",
"HEIGHT": "500",
"WIDTH": "500",
"SRS": "EPSG:3857",
"FEATURE_COUNT": "10",
"INFO_FORMAT": "application/vnd.ogc.gml",
"X": "56",
"Y": "144",
"MAP": urllib.parse.quote(self.projectPath)
}.items())])
response, headers = self._get_restricted(query_string)
self.assertTrue(
str(response).find("<qgs:pk>") != -1,

View File

@ -69,7 +69,16 @@ class TestQgsServerWMS(QgsServerTestBase):
for request in ('GetCapabilities', 'GetProjectSettings', 'GetContext'):
self.wms_request_compare(request)
# Test getfeatureinfo response
# Test getfeatureinfo response xml
self.wms_request_compare('GetFeatureInfo',
'&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
'info_format=text%2Fxml&transparent=true&' +
'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
'5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
'wms_getfeatureinfo-text-xml')
# Test getfeatureinfo response html
self.wms_request_compare('GetFeatureInfo',
'&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
'info_format=text%2Fhtml&transparent=true&' +

View File

@ -0,0 +1,14 @@
*****
Content-Type: text/xml; charset=utf-8
<GetFeatureInfoResponse>
<Layer name="testlayer èé">
<Feature id="2">
<Attribute value="3" name="id"/>
<Attribute value="three" name="name"/>
<Attribute value="three èé↓" name="utf8nameè"/>
<BoundingBox maxy="5606011.4565" maxx="913204.9128" miny="5606011.4565" CRS="EPSG:3857" minx="913204.9128"/>
<Attribute type="derived" value="Point (913204.9128 5606011.4565)" name="geometry"/>
</Feature>
</Layer>
</GetFeatureInfoResponse>