Skip to content
Permalink
Browse files

Merge pull request #8903 from pblottiere/gfi_geojson

JSON format for WMS GetFeatureInfo request [server]
  • Loading branch information
pblottiere committed Jan 28, 2019
2 parents 42413ce + 8305bda commit aecdb4278dd394cf09ad76ffbb6904ff081de526
Showing with 2,152 additions and 554 deletions.
  1. +15 −0 python/core/auto_generated/qgsjsonutils.sip.in
  2. +7 −1 src/core/qgsjsonutils.cpp
  3. +15 −0 src/core/qgsjsonutils.h
  4. +10 −4 src/server/qgsserverparameters.cpp
  5. +1 −0 src/server/qgsserverparameters.h
  6. +2 −0 src/server/services/wms/qgswmsgetcapabilities.cpp
  7. +4 −1 src/server/services/wms/qgswmsparameters.cpp
  8. +2 −1 src/server/services/wms/qgswmsparameters.h
  9. +128 −0 src/server/services/wms/qgswmsrenderer.cpp
  10. +3 −0 src/server/services/wms/qgswmsrenderer.h
  11. +15 −15 tests/src/app/testqgsappbrowserproviders.cpp
  12. +32 −0 tests/src/python/test_qgsjsonutils.py
  13. +75 −0 tests/src/python/test_qgsserver_wms_getfeatureinfo.py
  14. +119 −46 tests/testdata/qgis_server/getcapabilities.txt
  15. +2 −0 tests/testdata/qgis_server/getcapabilities_inspire.txt
  16. +42 −1 tests/testdata/qgis_server/getcontext.txt
  17. +87 −2 tests/testdata/qgis_server/getprojectsettings.txt
  18. +1,244 −437 tests/testdata/qgis_server/test_project.qgs
  19. +58 −0 tests/testdata/qgis_server/wms_getcapabilities_1_1_1.txt
  20. +119 −46 tests/testdata/qgis_server/wms_getcapabilities_1_3_0.txt
  21. +2 −0 tests/testdata/qgis_server/wms_getcapabilities_empty_spatial_layer.txt
  22. +2 −0 tests/testdata/qgis_server/wms_getcapabilities_without_title.txt
  23. +16 −0 tests/testdata/qgis_server/wms_getfeatureinfo-raster-text-xml.txt
  24. +15 −0 tests/testdata/qgis_server/wms_getfeatureinfo_alias_json.txt
  25. +14 −0 tests/testdata/qgis_server/wms_getfeatureinfo_exclude_attribute_json.txt
  26. +15 −0 tests/testdata/qgis_server/wms_getfeatureinfo_geojson.txt
  27. +16 −0 tests/testdata/qgis_server/wms_getfeatureinfo_geometry_json.txt
  28. +15 −0 tests/testdata/qgis_server/wms_getfeatureinfo_json.txt
  29. +58 −0 tests/testdata/qgis_server/wms_getfeatureinfo_multiple_json.txt
  30. +19 −0 tests/testdata/qgis_server/wms_getfeatureinfo_raster_json.txt
@@ -104,6 +104,21 @@ Sets whether to include attributes of features linked via references in the JSON
Returns whether attributes of related (child) features will be included in the JSON exports.

.. seealso:: :py:func:`setIncludeRelated`
%End

void setAttributeDisplayName( bool displayName );
%Docstring
Sets whether to print original names of attributes or aliases if
defined.

.. versionadded:: 3.6
%End

bool attributeDisplayName() const;
%Docstring
Returns whether original names of attributes or aliases are printed.

.. versionadded:: 3.6
%End

void setVectorLayer( QgsVectorLayer *vectorLayer );
@@ -145,7 +145,13 @@ QString QgsJsonExporter::exportFeature( const QgsFeature &feature, const QVarian
val = fieldFormatter->representValue( mLayer.data(), i, setup.config(), QVariant(), val );
}

properties += QStringLiteral( " \"%1\":%2" ).arg( fields.at( i ).name(), QgsJsonUtils::encodeValue( val ) );
QString name = fields.at( i ).name();
if ( mAttributeDisplayName )
{
name = mLayer->attributeDisplayName( i );
}

properties += QStringLiteral( " \"%1\":%2" ).arg( name, QgsJsonUtils::encodeValue( val ) );

++attributeCounter;
}
@@ -103,6 +103,20 @@ class CORE_EXPORT QgsJsonExporter
*/
bool includeRelated() const { return mIncludeRelatedAttributes; }

/**
* Sets whether to print original names of attributes or aliases if
* defined.
* \since QGIS 3.6
*/
void setAttributeDisplayName( bool displayName ) { mAttributeDisplayName = displayName; };

/**
* Returns whether original names of attributes or aliases are printed.
* \since QGIS 3.6
*/

bool attributeDisplayName() const { return mAttributeDisplayName; }

/**
* Sets the associated vector layer (required for related attribute export). This will automatically
* update the sourceCrs() to match.
@@ -223,6 +237,7 @@ class CORE_EXPORT QgsJsonExporter

QgsCoordinateTransform mTransform;

bool mAttributeDisplayName = false;
};

/**
@@ -398,6 +398,7 @@ QgsServerParameters::QgsServerParameters()
QgsServerParameters::QgsServerParameters( const QUrlQuery &query )
: QgsServerParameters()
{
mUrlQuery = query;
load( query );
}

@@ -415,11 +416,16 @@ void QgsServerParameters::add( const QString &key, const QString &value )

QUrlQuery QgsServerParameters::urlQuery() const
{
QUrlQuery query;
QUrlQuery query = mUrlQuery;

for ( auto param : toMap().toStdMap() )
if ( query.isEmpty() )
{
query.addQueryItem( param.first, param.second );
query.clear();

for ( auto param : toMap().toStdMap() )
{
query.addQueryItem( param.first, param.second );
}
}

return query;
@@ -534,7 +540,7 @@ void QgsServerParameters::load( const QUrlQuery &query )
mParameters[name].raiseError();
}
}
else if ( item.first.compare( QLatin1String( "VERSION" ) ) == 0 )
else if ( item.first.compare( QLatin1String( "VERSION" ), Qt::CaseInsensitive ) == 0 )
{
const QgsServerParameter::Name name = QgsServerParameter::VERSION_SERVICE;
mParameters[name].mValue = item.second;
@@ -338,6 +338,7 @@ class SERVER_EXPORT QgsServerParameters
QVariant value( QgsServerParameter::Name name ) const;

QMap<QgsServerParameter::Name, QgsServerParameter> mParameters;
QUrlQuery mUrlQuery;
};

#endif
@@ -479,6 +479,8 @@ namespace QgsWms
appendFormat( elem, QStringLiteral( "text/xml" ) );
appendFormat( elem, QStringLiteral( "application/vnd.ogc.gml" ) );
appendFormat( elem, QStringLiteral( "application/vnd.ogc.gml/3.1.1" ) );
appendFormat( elem, QStringLiteral( "application/json" ) );
appendFormat( elem, QStringLiteral( "application/geo+json" ) );
elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
requestElem.appendChild( elem );

@@ -522,7 +522,7 @@ namespace QgsWms
{
bool loaded = false;

const QRegExp composerParamRegExp( QStringLiteral( "^MAP\\d+:" ) );
const QRegExp composerParamRegExp( QStringLiteral( "^MAP\\d+:" ), Qt::CaseInsensitive );
if ( key.contains( composerParamRegExp ) )
{
const int mapId = key.midRef( 3, key.indexOf( ':' ) - 3 ).toInt();
@@ -758,6 +758,9 @@ namespace QgsWms
f = Format::TEXT;
else if ( fStr.startsWith( QLatin1String( "application/vnd.ogc.gml" ), Qt::CaseInsensitive ) )
f = Format::GML;
else if ( fStr.startsWith( QLatin1String( "application/json" ), Qt::CaseInsensitive )
|| fStr.startsWith( QLatin1String( "application/geo+json" ), Qt::CaseInsensitive ) )
f = Format::JSON;
else
f = Format::NONE;

@@ -309,7 +309,8 @@ namespace QgsWms
TEXT,
XML,
HTML,
GML
GML,
JSON
};

/**
@@ -19,6 +19,7 @@


#include "qgswmsutils.h"
#include "qgsjsonutils.h"
#include "qgswmsrenderer.h"
#include "qgsfilterrestorer.h"
#include "qgsexception.h"
@@ -1119,6 +1120,8 @@ namespace QgsWms
ba = convertFeatureInfoToText( result );
else if ( infoFormat == QgsWmsParameters::Format::HTML )
ba = convertFeatureInfoToHtml( result );
else if ( infoFormat == QgsWmsParameters::Format::JSON )
ba = convertFeatureInfoToJson( layers, result );
else
ba = result.toByteArray();

@@ -2275,6 +2278,131 @@ namespace QgsWms
return featureInfoString.toUtf8();
}

QByteArray QgsRenderer::convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc ) const
{
QString json;
json.append( QStringLiteral( "{\"type\": \"FeatureCollection\",\n" ) );
json.append( QStringLiteral( " \"features\":[\n" ) );

const bool withGeometry = ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && mWmsParameters.withGeometry() );

const QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
for ( int i = 0; i < layerList.size(); ++i )
{
const QDomElement layerElem = layerList.at( i ).toElement();
const QString layerName = layerElem.attribute( QStringLiteral( "name" ) );

QgsMapLayer *layer;
for ( QgsMapLayer *l : layers )
{
if ( layerNickname( *l ).compare( layerName ) == 0 )
{
layer = l;
}
}

if ( ! layer )
continue;

if ( layer->type() == QgsMapLayer::VectorLayer )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );

// search features to export
QgsFeatureList features;
QgsAttributeList attributes;
const QDomNodeList featuresNode = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
if ( featuresNode.isEmpty() )
continue;

for ( int j = 0; j < featuresNode.size(); ++j )
{
const QDomElement featureNode = featuresNode.at( j ).toElement();
const QgsFeatureId fid = featureNode.attribute( QStringLiteral( "id" ) ).toLongLong();
QgsFeature feature = QgsFeature( vl->getFeature( fid ) );

QString wkt;
if ( withGeometry )
{
const QDomNodeList attrs = featureNode.elementsByTagName( "Attribute" );
for ( int k = 0; k < attrs.count(); k++ )
{
const QDomElement elm = attrs.at( k ).toElement();
if ( elm.attribute( QStringLiteral( "name" ) ).compare( "geometry" ) == 0 )
{
wkt = elm.attribute( "value" );
break;
}
}

if ( ! wkt.isEmpty() )
{
// CRS in WMS parameters may be different from the layer
feature.setGeometry( QgsGeometry::fromWkt( wkt ) );
}
}
features << feature;

// search attributes to export (one time only)
if ( not attributes.isEmpty() )
continue;

const QDomNodeList attributesNode = featureNode.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int k = 0; k < attributesNode.size(); ++k )
{
const QDomElement attributeElement = attributesNode.at( k ).toElement();
const QString fieldName = attributeElement.attribute( QStringLiteral( "name" ) );

attributes << feature.fieldNameIndex( fieldName );
}
}

// export
QgsJsonExporter exporter( vl );
exporter.setAttributeDisplayName( true );
exporter.setAttributes( attributes );
exporter.setIncludeGeometry( withGeometry );

for ( const auto feature : features )
{
if ( json.right( 1 ).compare( QStringLiteral( "}" ) ) == 0 )
{
json.append( QStringLiteral( "," ) );
}

const QString id = QStringLiteral( "%1.%2" ).arg( layer->name(), QgsJsonUtils::encodeValue( feature.id() ) );
json.append( exporter.exportFeature( feature, QVariantMap(), id ) );
}
}
else // raster layer
{
json.append( QStringLiteral( "{" ) );
json.append( QStringLiteral( "\"type\":\"Feature\",\n" ) );
json.append( QStringLiteral( "\"id\":\"%1\",\n" ).arg( layer->name() ) );
json.append( QStringLiteral( "\"properties\":{\n" ) );

const QDomNodeList attributesNode = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
for ( int j = 0; j < attributesNode.size(); ++j )
{
const QDomElement attrElmt = attributesNode.at( j ).toElement();
const QString name = attrElmt.attribute( QStringLiteral( "name" ) );
const QString value = attrElmt.attribute( QStringLiteral( "value" ) );

if ( j > 0 )
json.append( QStringLiteral( ",\n" ) );

json.append( QStringLiteral( " \"%1\": \"%2\"" ).arg( name, value ) );
}

json.append( QStringLiteral( "\n}\n}" ) );
}
}

json.append( QStringLiteral( "]}" ) );

return json.toUtf8();
}

QDomElement QgsRenderer::createFeatureGML(
QgsFeature *feat,
QgsVectorLayer *layer,
@@ -255,6 +255,9 @@ namespace QgsWms
//! Converts a feature info xml document to Text
QByteArray convertFeatureInfoToText( const QDomDocument &doc ) const;

//! Converts a feature info xml document to json
QByteArray convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc ) const;

QDomElement createFeatureGML(
QgsFeature *feat,
QgsVectorLayer *layer,
@@ -104,26 +104,26 @@ void TestQgsAppBrowserProviders::testProjectItemCreation()
{
child->populate( true );

QCOMPARE( child->children().count(), 4 );
QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 0 ) ) );
QCOMPARE( child->children().at( 0 )->name(), QStringLiteral( "groupwithoutshortname" ) );
QCOMPARE( child->children().count(), 7 );
QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 2 ) ) );
QCOMPARE( child->children().at( 2 )->name(), QStringLiteral( "groupwithoutshortname" ) );

QCOMPARE( child->children().at( 0 )->children().count(), 1 );
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 0 )->children().at( 0 ) ) );
QCOMPARE( child->children().at( 0 )->children().at( 0 )->name(), QStringLiteral( "testlayer3" ) );
QCOMPARE( child->children().at( 2 )->children().count(), 1 );
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 2 )->children().at( 0 ) ) );
QCOMPARE( child->children().at( 2 )->children().at( 0 )->name(), QStringLiteral( "testlayer3" ) );

QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 1 ) ) );
QCOMPARE( child->children().at( 1 )->name(), QStringLiteral( "groupwithshortname" ) );
QVERIFY( dynamic_cast< QgsProjectLayerTreeGroupItem * >( child->children().at( 3 ) ) );
QCOMPARE( child->children().at( 3 )->name(), QStringLiteral( "groupwithshortname" ) );

QCOMPARE( child->children().at( 1 )->children().count(), 1 );
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 1 )->children().at( 0 ) ) );
QCOMPARE( child->children().at( 1 )->children().at( 0 )->name(), QStringLiteral( "testlayer2" ) );
QCOMPARE( child->children().at( 3 )->children().count(), 1 );
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 3 )->children().at( 0 ) ) );
QCOMPARE( child->children().at( 3 )->children().at( 0 )->name(), QStringLiteral( "testlayer2" ) );

QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 2 ) ) );
QCOMPARE( child->children().at( 2 )->name(), QStringLiteral( "testlayer" ) );
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 5 ) ) );
QCOMPARE( child->children().at( 5 )->name(), QStringLiteral( "testlayer" ) );

QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 3 ) ) );
QCOMPARE( child->children().at( 3 )->name(), QStringLiteral( "testlayer \u00E8\u00E9" ) );
QVERIFY( dynamic_cast< QgsLayerItem * >( child->children().at( 6 ) ) );
QCOMPARE( child->children().at( 6 )->name(), QStringLiteral( "testlayer \u00E8\u00E9" ) );

delete dirItem;
return;
@@ -726,6 +726,38 @@ def testExportFeaturesWithLocale_regression20053(self):
exporter.setVectorLayer(source)
self.assertEqual(exporter.exportFeatures([feature]), expected)

def testExportFieldAlias(self):
""" Test exporting a feature with fields' alias """

# source layer
source = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
"parent", "memory")
pr = source.dataProvider()
pf1 = QgsFeature()
pf1.setFields(source.fields())
pf1.setAttributes(["test1", 1])
pf2 = QgsFeature()
pf2.setFields(source.fields())
pf2.setAttributes(["test2", 2])
assert pr.addFeatures([pf1, pf2])

source.setFieldAlias(0, "alias_fldtxt")
source.setFieldAlias(1, "alias_fldint")

exporter = QgsJsonExporter()
exporter.setVectorLayer(source)

expected = """{
"type":"Feature",
"id":0,
"geometry":null,
"properties":{
"alias_fldtxt":"test1",
"alias_fldint":1
}
}"""
self.assertEqual(exporter.exportFeature(pf1), expected)


if __name__ == "__main__":
unittest.main()

0 comments on commit aecdb42

Please sign in to comment.
You can’t perform that action at this time.