Skip to content

Commit aecdb42

Browse files
authoredJan 28, 2019
Merge pull request #8903 from pblottiere/gfi_geojson
JSON format for WMS GetFeatureInfo request [server]
2 parents 42413ce + 8305bda commit aecdb42

30 files changed

+2152
-554
lines changed
 

‎python/core/auto_generated/qgsjsonutils.sip.in

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ Sets whether to include attributes of features linked via references in the JSON
104104
Returns whether attributes of related (child) features will be included in the JSON exports.
105105

106106
.. seealso:: :py:func:`setIncludeRelated`
107+
%End
108+
109+
void setAttributeDisplayName( bool displayName );
110+
%Docstring
111+
Sets whether to print original names of attributes or aliases if
112+
defined.
113+
114+
.. versionadded:: 3.6
115+
%End
116+
117+
bool attributeDisplayName() const;
118+
%Docstring
119+
Returns whether original names of attributes or aliases are printed.
120+
121+
.. versionadded:: 3.6
107122
%End
108123

109124
void setVectorLayer( QgsVectorLayer *vectorLayer );

‎src/core/qgsjsonutils.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,13 @@ QString QgsJsonExporter::exportFeature( const QgsFeature &feature, const QVarian
145145
val = fieldFormatter->representValue( mLayer.data(), i, setup.config(), QVariant(), val );
146146
}
147147

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

150156
++attributeCounter;
151157
}

‎src/core/qgsjsonutils.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,20 @@ class CORE_EXPORT QgsJsonExporter
103103
*/
104104
bool includeRelated() const { return mIncludeRelatedAttributes; }
105105

106+
/**
107+
* Sets whether to print original names of attributes or aliases if
108+
* defined.
109+
* \since QGIS 3.6
110+
*/
111+
void setAttributeDisplayName( bool displayName ) { mAttributeDisplayName = displayName; };
112+
113+
/**
114+
* Returns whether original names of attributes or aliases are printed.
115+
* \since QGIS 3.6
116+
*/
117+
118+
bool attributeDisplayName() const { return mAttributeDisplayName; }
119+
106120
/**
107121
* Sets the associated vector layer (required for related attribute export). This will automatically
108122
* update the sourceCrs() to match.
@@ -223,6 +237,7 @@ class CORE_EXPORT QgsJsonExporter
223237

224238
QgsCoordinateTransform mTransform;
225239

240+
bool mAttributeDisplayName = false;
226241
};
227242

228243
/**

‎src/server/qgsserverparameters.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ QgsServerParameters::QgsServerParameters()
398398
QgsServerParameters::QgsServerParameters( const QUrlQuery &query )
399399
: QgsServerParameters()
400400
{
401+
mUrlQuery = query;
401402
load( query );
402403
}
403404

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

416417
QUrlQuery QgsServerParameters::urlQuery() const
417418
{
418-
QUrlQuery query;
419+
QUrlQuery query = mUrlQuery;
419420

420-
for ( auto param : toMap().toStdMap() )
421+
if ( query.isEmpty() )
421422
{
422-
query.addQueryItem( param.first, param.second );
423+
query.clear();
424+
425+
for ( auto param : toMap().toStdMap() )
426+
{
427+
query.addQueryItem( param.first, param.second );
428+
}
423429
}
424430

425431
return query;
@@ -534,7 +540,7 @@ void QgsServerParameters::load( const QUrlQuery &query )
534540
mParameters[name].raiseError();
535541
}
536542
}
537-
else if ( item.first.compare( QLatin1String( "VERSION" ) ) == 0 )
543+
else if ( item.first.compare( QLatin1String( "VERSION" ), Qt::CaseInsensitive ) == 0 )
538544
{
539545
const QgsServerParameter::Name name = QgsServerParameter::VERSION_SERVICE;
540546
mParameters[name].mValue = item.second;

‎src/server/qgsserverparameters.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ class SERVER_EXPORT QgsServerParameters
338338
QVariant value( QgsServerParameter::Name name ) const;
339339

340340
QMap<QgsServerParameter::Name, QgsServerParameter> mParameters;
341+
QUrlQuery mUrlQuery;
341342
};
342343

343344
#endif

‎src/server/services/wms/qgswmsgetcapabilities.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,8 @@ namespace QgsWms
479479
appendFormat( elem, QStringLiteral( "text/xml" ) );
480480
appendFormat( elem, QStringLiteral( "application/vnd.ogc.gml" ) );
481481
appendFormat( elem, QStringLiteral( "application/vnd.ogc.gml/3.1.1" ) );
482+
appendFormat( elem, QStringLiteral( "application/json" ) );
483+
appendFormat( elem, QStringLiteral( "application/geo+json" ) );
482484
elem.appendChild( dcpTypeElem.cloneNode().toElement() ); //this is the same as for 'GetCapabilities'
483485
requestElem.appendChild( elem );
484486

‎src/server/services/wms/qgswmsparameters.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ namespace QgsWms
522522
{
523523
bool loaded = false;
524524

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

‎src/server/services/wms/qgswmsparameters.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ namespace QgsWms
309309
TEXT,
310310
XML,
311311
HTML,
312-
GML
312+
GML,
313+
JSON
313314
};
314315

315316
/**

‎src/server/services/wms/qgswmsrenderer.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020

2121
#include "qgswmsutils.h"
22+
#include "qgsjsonutils.h"
2223
#include "qgswmsrenderer.h"
2324
#include "qgsfilterrestorer.h"
2425
#include "qgsexception.h"
@@ -1119,6 +1120,8 @@ namespace QgsWms
11191120
ba = convertFeatureInfoToText( result );
11201121
else if ( infoFormat == QgsWmsParameters::Format::HTML )
11211122
ba = convertFeatureInfoToHtml( result );
1123+
else if ( infoFormat == QgsWmsParameters::Format::JSON )
1124+
ba = convertFeatureInfoToJson( layers, result );
11221125
else
11231126
ba = result.toByteArray();
11241127

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

2281+
QByteArray QgsRenderer::convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc ) const
2282+
{
2283+
QString json;
2284+
json.append( QStringLiteral( "{\"type\": \"FeatureCollection\",\n" ) );
2285+
json.append( QStringLiteral( " \"features\":[\n" ) );
2286+
2287+
const bool withGeometry = ( QgsServerProjectUtils::wmsFeatureInfoAddWktGeometry( *mProject ) && mWmsParameters.withGeometry() );
2288+
2289+
const QDomNodeList layerList = doc.elementsByTagName( QStringLiteral( "Layer" ) );
2290+
for ( int i = 0; i < layerList.size(); ++i )
2291+
{
2292+
const QDomElement layerElem = layerList.at( i ).toElement();
2293+
const QString layerName = layerElem.attribute( QStringLiteral( "name" ) );
2294+
2295+
QgsMapLayer *layer;
2296+
for ( QgsMapLayer *l : layers )
2297+
{
2298+
if ( layerNickname( *l ).compare( layerName ) == 0 )
2299+
{
2300+
layer = l;
2301+
}
2302+
}
2303+
2304+
if ( ! layer )
2305+
continue;
2306+
2307+
if ( layer->type() == QgsMapLayer::VectorLayer )
2308+
{
2309+
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
2310+
2311+
// search features to export
2312+
QgsFeatureList features;
2313+
QgsAttributeList attributes;
2314+
const QDomNodeList featuresNode = layerElem.elementsByTagName( QStringLiteral( "Feature" ) );
2315+
if ( featuresNode.isEmpty() )
2316+
continue;
2317+
2318+
for ( int j = 0; j < featuresNode.size(); ++j )
2319+
{
2320+
const QDomElement featureNode = featuresNode.at( j ).toElement();
2321+
const QgsFeatureId fid = featureNode.attribute( QStringLiteral( "id" ) ).toLongLong();
2322+
QgsFeature feature = QgsFeature( vl->getFeature( fid ) );
2323+
2324+
QString wkt;
2325+
if ( withGeometry )
2326+
{
2327+
const QDomNodeList attrs = featureNode.elementsByTagName( "Attribute" );
2328+
for ( int k = 0; k < attrs.count(); k++ )
2329+
{
2330+
const QDomElement elm = attrs.at( k ).toElement();
2331+
if ( elm.attribute( QStringLiteral( "name" ) ).compare( "geometry" ) == 0 )
2332+
{
2333+
wkt = elm.attribute( "value" );
2334+
break;
2335+
}
2336+
}
2337+
2338+
if ( ! wkt.isEmpty() )
2339+
{
2340+
// CRS in WMS parameters may be different from the layer
2341+
feature.setGeometry( QgsGeometry::fromWkt( wkt ) );
2342+
}
2343+
}
2344+
features << feature;
2345+
2346+
// search attributes to export (one time only)
2347+
if ( not attributes.isEmpty() )
2348+
continue;
2349+
2350+
const QDomNodeList attributesNode = featureNode.elementsByTagName( QStringLiteral( "Attribute" ) );
2351+
for ( int k = 0; k < attributesNode.size(); ++k )
2352+
{
2353+
const QDomElement attributeElement = attributesNode.at( k ).toElement();
2354+
const QString fieldName = attributeElement.attribute( QStringLiteral( "name" ) );
2355+
2356+
attributes << feature.fieldNameIndex( fieldName );
2357+
}
2358+
}
2359+
2360+
// export
2361+
QgsJsonExporter exporter( vl );
2362+
exporter.setAttributeDisplayName( true );
2363+
exporter.setAttributes( attributes );
2364+
exporter.setIncludeGeometry( withGeometry );
2365+
2366+
for ( const auto feature : features )
2367+
{
2368+
if ( json.right( 1 ).compare( QStringLiteral( "}" ) ) == 0 )
2369+
{
2370+
json.append( QStringLiteral( "," ) );
2371+
}
2372+
2373+
const QString id = QStringLiteral( "%1.%2" ).arg( layer->name(), QgsJsonUtils::encodeValue( feature.id() ) );
2374+
json.append( exporter.exportFeature( feature, QVariantMap(), id ) );
2375+
}
2376+
}
2377+
else // raster layer
2378+
{
2379+
json.append( QStringLiteral( "{" ) );
2380+
json.append( QStringLiteral( "\"type\":\"Feature\",\n" ) );
2381+
json.append( QStringLiteral( "\"id\":\"%1\",\n" ).arg( layer->name() ) );
2382+
json.append( QStringLiteral( "\"properties\":{\n" ) );
2383+
2384+
const QDomNodeList attributesNode = layerElem.elementsByTagName( QStringLiteral( "Attribute" ) );
2385+
for ( int j = 0; j < attributesNode.size(); ++j )
2386+
{
2387+
const QDomElement attrElmt = attributesNode.at( j ).toElement();
2388+
const QString name = attrElmt.attribute( QStringLiteral( "name" ) );
2389+
const QString value = attrElmt.attribute( QStringLiteral( "value" ) );
2390+
2391+
if ( j > 0 )
2392+
json.append( QStringLiteral( ",\n" ) );
2393+
2394+
json.append( QStringLiteral( " \"%1\": \"%2\"" ).arg( name, value ) );
2395+
}
2396+
2397+
json.append( QStringLiteral( "\n}\n}" ) );
2398+
}
2399+
}
2400+
2401+
json.append( QStringLiteral( "]}" ) );
2402+
2403+
return json.toUtf8();
2404+
}
2405+
22782406
QDomElement QgsRenderer::createFeatureGML(
22792407
QgsFeature *feat,
22802408
QgsVectorLayer *layer,

‎src/server/services/wms/qgswmsrenderer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ namespace QgsWms
255255
//! Converts a feature info xml document to Text
256256
QByteArray convertFeatureInfoToText( const QDomDocument &doc ) const;
257257

258+
//! Converts a feature info xml document to json
259+
QByteArray convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc ) const;
260+
258261
QDomElement createFeatureGML(
259262
QgsFeature *feat,
260263
QgsVectorLayer *layer,

‎tests/src/app/testqgsappbrowserproviders.cpp

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -104,26 +104,26 @@ void TestQgsAppBrowserProviders::testProjectItemCreation()
104104
{
105105
child->populate( true );
106106

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

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

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

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

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

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

128128
delete dirItem;
129129
return;

‎tests/src/python/test_qgsjsonutils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,38 @@ def testExportFeaturesWithLocale_regression20053(self):
726726
exporter.setVectorLayer(source)
727727
self.assertEqual(exporter.exportFeatures([feature]), expected)
728728

729+
def testExportFieldAlias(self):
730+
""" Test exporting a feature with fields' alias """
731+
732+
# source layer
733+
source = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
734+
"parent", "memory")
735+
pr = source.dataProvider()
736+
pf1 = QgsFeature()
737+
pf1.setFields(source.fields())
738+
pf1.setAttributes(["test1", 1])
739+
pf2 = QgsFeature()
740+
pf2.setFields(source.fields())
741+
pf2.setAttributes(["test2", 2])
742+
assert pr.addFeatures([pf1, pf2])
743+
744+
source.setFieldAlias(0, "alias_fldtxt")
745+
source.setFieldAlias(1, "alias_fldint")
746+
747+
exporter = QgsJsonExporter()
748+
exporter.setVectorLayer(source)
749+
750+
expected = """{
751+
"type":"Feature",
752+
"id":0,
753+
"geometry":null,
754+
"properties":{
755+
"alias_fldtxt":"test1",
756+
"alias_fldint":1
757+
}
758+
}"""
759+
self.assertEqual(exporter.exportFeature(pf1), expected)
760+
729761

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

0 commit comments

Comments
 (0)
Please sign in to comment.