Skip to content

Commit

Permalink
Merge pull request #56600 from troopa81/feat_add_srs_getfeatureinfo_json
Browse files Browse the repository at this point in the history
[Feature][WMS] Add crs information in WMS GetFeatureInfo output when it differs from WGS84
  • Loading branch information
troopa81 committed Apr 2, 2024
2 parents 5b2c0e4 + 713b5be commit dc677f4
Show file tree
Hide file tree
Showing 20 changed files with 187 additions and 3 deletions.
Expand Up @@ -986,6 +986,14 @@ Returns an empty string on failure.
.. versionadded:: 3.30
%End

QString toOgcUrn() const;
%Docstring
Returns the crs as OGC URN (format: urn:ogc:def:crs:OGC:1.3:CRS84)
Returns an empty string on failure.

.. versionadded:: 3.38
%End


void updateDefinition();
%Docstring
Expand Down
1 change: 1 addition & 0 deletions python/PyQt6/core/auto_generated/qgsjsonutils.sip.in
Expand Up @@ -363,6 +363,7 @@ Returns a null geometry if the geometry could not be parsed.




};

/************************************************************************
Expand Down
Expand Up @@ -986,6 +986,14 @@ Returns an empty string on failure.
.. versionadded:: 3.30
%End

QString toOgcUrn() const;
%Docstring
Returns the crs as OGC URN (format: urn:ogc:def:crs:OGC:1.3:CRS84)
Returns an empty string on failure.

.. versionadded:: 3.38
%End


void updateDefinition();
%Docstring
Expand Down
1 change: 1 addition & 0 deletions python/core/auto_generated/qgsjsonutils.sip.in
Expand Up @@ -363,6 +363,7 @@ Returns a null geometry if the geometry could not be parsed.




};

/************************************************************************
Expand Down
24 changes: 24 additions & 0 deletions src/core/proj/qgscoordinatereferencesystem.cpp
Expand Up @@ -1613,6 +1613,30 @@ QString QgsCoordinateReferenceSystem::toOgcUri() const
return QString();
}

QString QgsCoordinateReferenceSystem::toOgcUrn() const
{
const auto parts { authid().split( ':' ) };
if ( parts.length() == 2 )
{
if ( parts[0] == QLatin1String( "EPSG" ) )
return QStringLiteral( "urn:ogc:def:crs:EPSG:0:%1" ).arg( parts[1] );
else if ( parts[0] == QLatin1String( "OGC" ) )
{
return QStringLiteral( "urn:ogc:def:crs:OGC:1.3:%1" ).arg( parts[1] );
}
else
{
QgsMessageLog::logMessage( QStringLiteral( "Error converting published CRS to URN %1: (not OGC or EPSG)" ).arg( authid() ), QStringLiteral( "CRS" ), Qgis::MessageLevel::Critical );
}
}
else
{
QgsMessageLog::logMessage( QStringLiteral( "Error converting published CRS to URN: %1" ).arg( authid() ), QStringLiteral( "CRS" ), Qgis::MessageLevel::Critical );
}
return QString();
}


void QgsCoordinateReferenceSystem::updateDefinition()
{
if ( !d->mIsValid )
Expand Down
8 changes: 8 additions & 0 deletions src/core/proj/qgscoordinatereferencesystem.h
Expand Up @@ -906,6 +906,14 @@ class CORE_EXPORT QgsCoordinateReferenceSystem
*/
QString toOgcUri() const;

/**
* Returns the crs as OGC URN (format: urn:ogc:def:crs:OGC:1.3:CRS84)
* Returns an empty string on failure.
*
* \since QGIS 3.38
*/
QString toOgcUrn() const;

// Mutators -----------------------------------

/**
Expand Down
14 changes: 14 additions & 0 deletions src/core/qgsjsonutils.cpp
Expand Up @@ -245,6 +245,9 @@ json QgsJsonExporter::exportFeaturesToJsonObject( const QgsFeatureList &features
{ "type", "FeatureCollection" },
{ "features", json::array() }
};

QgsJsonUtils::addCrsInfo( data, mDestinationCrs );

for ( const QgsFeature &feature : std::as_const( features ) )
{
data["features"].push_back( exportFeatureToJsonObject( feature ) );
Expand Down Expand Up @@ -912,3 +915,14 @@ json QgsJsonUtils::exportAttributesToJsonObject( const QgsFeature &feature, QgsV
}
return attrs;
}

void QgsJsonUtils::addCrsInfo( json &value, const QgsCoordinateReferenceSystem &crs )
{
// When user request EPSG:4326 we return a compliant CRS84 lon/lat GeoJSON
// so no need to add CRS information
if ( crs.authid() == "OGC:CRS84" || crs.authid() == "EPSG:4326" )
return;

value["crs"]["type"] = "name";
value["crs"]["properties"]["name"] = crs.toOgcUrn().toStdString();
}
9 changes: 9 additions & 0 deletions src/core/qgsjsonutils.h
Expand Up @@ -423,6 +423,15 @@ class CORE_EXPORT QgsJsonUtils
*/
static QVariant jsonToVariant( const json &value ) SIP_SKIP;

/**
* Add \a crs information entry in \a json object regarding old GeoJSON specification format
* if it differs from OGC:CRS84 or EPSG:4326.
* According to new specification RFC 7946, coordinate reference system for all GeoJSON coordinates
* is assumed to be OGC:CRS84 but when user specifically request a different CRS, this method
* adds this information in the JSON output
*/
static void addCrsInfo( json &value, const QgsCoordinateReferenceSystem &crs ) SIP_SKIP;

};

#endif // QGSJSONUTILS_H
15 changes: 15 additions & 0 deletions src/server/services/wfs/qgswfsgetfeature.cpp
Expand Up @@ -1187,6 +1187,21 @@ namespace QgsWfs

fcString = QStringLiteral( "{\"type\": \"FeatureCollection\",\n" );
fcString += " \"bbox\": [ " + qgsDoubleToString( rect->xMinimum(), prec ) + ", " + qgsDoubleToString( rect->yMinimum(), prec ) + ", " + qgsDoubleToString( rect->xMaximum(), prec ) + ", " + qgsDoubleToString( rect->yMaximum(), prec ) + "],\n";

const QString srsName {request.serverParameters().value( QStringLiteral( "SRSNAME" ) )};
const QgsCoordinateReferenceSystem destinationCrs { srsName.isEmpty( ) ? QStringLiteral( "EPSG:4326" ) : srsName };
if ( ! destinationCrs.isValid() )
{
throw QgsRequestNotWellFormedException( QStringLiteral( "srsName error: '%1' is not valid." ).arg( srsName ) );
}

json value;
QgsJsonUtils::addCrsInfo( value, destinationCrs );
for ( const auto &it : value.items() )
{
fcString += " \"" + QString::fromStdString( it.key() ) + "\": " + QString::fromStdString( it.value().dump() ) + ",\n";
}

fcString += QLatin1String( " \"features\": [\n" );
response.write( fcString.toUtf8() );
}
Expand Down
6 changes: 4 additions & 2 deletions src/server/services/wms/qgswmsrenderer.cpp
Expand Up @@ -1316,7 +1316,7 @@ namespace QgsWms
else if ( infoFormat == QgsWmsParameters::Format::HTML )
ba = convertFeatureInfoToHtml( result );
else if ( infoFormat == QgsWmsParameters::Format::JSON )
ba = convertFeatureInfoToJson( layers, result );
ba = convertFeatureInfoToJson( layers, result, mapSettings.destinationCrs() );
else
ba = result.toByteArray();

Expand Down Expand Up @@ -2813,7 +2813,7 @@ namespace QgsWms
return featureInfoString.toUtf8();
}

QByteArray QgsRenderer::convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc ) const
QByteArray QgsRenderer::convertFeatureInfoToJson( const QList<QgsMapLayer *> &layers, const QDomDocument &doc, const QgsCoordinateReferenceSystem &destCRS ) const
{
json json
{
Expand Down Expand Up @@ -2915,6 +2915,8 @@ namespace QgsWms
exporter.setIncludeGeometry( withGeometry );
exporter.setTransformGeometries( false );

QgsJsonUtils::addCrsInfo( json, destCRS );

for ( const auto &feature : std::as_const( features ) )
{
const QString id = QStringLiteral( "%1.%2" ).arg( layerName ).arg( fidMap.value( feature.id() ) );
Expand Down
2 changes: 1 addition & 1 deletion src/server/services/wms/qgswmsrenderer.h
Expand Up @@ -323,7 +323,7 @@ namespace QgsWms
QByteArray convertFeatureInfoToText( const QDomDocument &doc ) const;

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

QDomElement createFeatureGML(
const QgsFeature *feat,
Expand Down
4 changes: 4 additions & 0 deletions tests/src/python/test_qgsserver_wfs.py
Expand Up @@ -787,6 +787,7 @@ def test_getFeatureFeatureJsonCrs(self):
jdata['features'][0]['geometry']
jdata['features'][0]['geometry']['coordinates']
self.assertEqual(jdata['features'][0]['geometry']['coordinates'], [807305, 5592878])
self.assertEqual(jdata['crs']['properties']['name'], "urn:ogc:def:crs:EPSG:0:3857")

query_string = "?" + "&".join(["%s=%s" % i for i in list({
"SERVICE": "WFS",
Expand All @@ -803,6 +804,7 @@ def test_getFeatureFeatureJsonCrs(self):
jdata['features'][0]['geometry']
jdata['features'][0]['geometry']['coordinates']
self.assertEqual([int(i) for i in jdata['features'][0]['geometry']['coordinates']], [7, 44])
self.assertFalse('crs' in jdata)

query_string = "?" + "&".join(["%s=%s" % i for i in list({
"SERVICE": "WFS",
Expand Down Expand Up @@ -834,6 +836,7 @@ def test_getFeatureFeatureJsonCrs(self):
jdata['features'][0]['geometry']
jdata['features'][0]['geometry']['coordinates']
self.assertEqual([int(i) for i in jdata['features'][0]['geometry']['coordinates']], [361806, 4964192])
self.assertEqual(jdata['crs']['properties']['name'], "urn:ogc:def:crs:EPSG:0:32632")

query_string = "?" + "&".join(["%s=%s" % i for i in list({
"SERVICE": "WFS",
Expand All @@ -851,6 +854,7 @@ def test_getFeatureFeatureJsonCrs(self):
jdata['features'][0]['geometry']
jdata['features'][0]['geometry']['coordinates']
self.assertEqual([int(i) for i in jdata['features'][0]['geometry']['coordinates']], [812191, 5589555])
self.assertEqual(jdata['crs']['properties']['name'], "urn:ogc:def:crs:EPSG:0:3857")

def test_insert_srsName(self):
"""Test srsName is respected when insering"""
Expand Down
24 changes: 24 additions & 0 deletions tests/src/python/test_qgsserver_wms_getfeatureinfo.py
Expand Up @@ -615,6 +615,30 @@ def testGetFeatureInfoJSON(self):
'wms_getfeatureinfo_raster_json',
normalizeJson=True)

# simple test with geometry with underlying layer in 4326 and CRS is EPSG:4326
self.wms_request_compare('GetFeatureInfo',
'&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
'info_format=application%2Fjson&transparent=true&' +
'width=600&height=400&srs=EPSG:4326&' +
'bbox=44.9014173,8.2034387,44.9015094,8.2036094&' +
'query_layers=testlayer2&X=203&Y=116&' +
'with_geometry=true',
'wms_getfeatureinfo_geometry_CRS84_json',
'test_project.qgs',
normalizeJson=True)

# simple test with geometry with underlying layer in 4326 and CRS is CRS84
self.wms_request_compare('GetFeatureInfo',
'&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
'info_format=application%2Fjson&transparent=true&' +
'width=600&height=400&srs=OGC:CRS84&' +
'bbox=8.2034387,44.9014173,8.2036094,44.9015094&' +
'query_layers=testlayer2&X=203&Y=116&' +
'with_geometry=true',
'wms_getfeatureinfo_geometry_CRS84_json',
'test_project.qgs',
normalizeJson=True)

def testGetFeatureInfoGroupedLayers(self):
"""Test that we can get feature info from the top and group layers"""

Expand Down
7 changes: 7 additions & 0 deletions tests/testdata/qgis_server/wms_getfeatureinfo_alias_json.txt
Expand Up @@ -2,6 +2,13 @@
Content-Type: application/json; charset=utf-8

{
"crs":
{
"properties": {
"name": "urn:ogc:def:crs:EPSG:0:3857"
},
"type": "name"
},
"features": [
{
"geometry": null,
Expand Down
Expand Up @@ -2,6 +2,13 @@
Content-Type: application/json; charset=utf-8

{
"crs":
{
"properties": {
"name": "urn:ogc:def:crs:EPSG:0:3857"
},
"type": "name"
},
"features": [
{
"geometry": null,
Expand Down
7 changes: 7 additions & 0 deletions tests/testdata/qgis_server/wms_getfeatureinfo_geojson.txt
Expand Up @@ -2,6 +2,13 @@
Content-Type: application/geo+json; charset=utf-8

{
"crs":
{
"properties": {
"name": "urn:ogc:def:crs:EPSG:0:3857"
},
"type": "name"
},
"features": [
{
"geometry": null,
Expand Down
@@ -0,0 +1,24 @@
*****
Content-Type: application/json; charset=utf-8

{
"features": [
{
"geometry": {
"coordinates": [
8.2035,
44.9015
],
"type": "Point"
},
"id": "testlayer2.0",
"properties": {
"id": 1,
"name": "one",
"utf8nameè": "one èé"
},
"type": "Feature"
}
],
"type": "FeatureCollection"
}
Expand Up @@ -2,6 +2,13 @@
Content-Type: application/json; charset=utf-8

{
"crs":
{
"properties": {
"name": "urn:ogc:def:crs:EPSG:0:3857"
},
"type": "name"
},
"features": [
{
"geometry": {
Expand Down
7 changes: 7 additions & 0 deletions tests/testdata/qgis_server/wms_getfeatureinfo_json.txt
Expand Up @@ -2,6 +2,13 @@
Content-Type: application/json; charset=utf-8

{
"crs":
{
"properties": {
"name": "urn:ogc:def:crs:EPSG:0:3857"
},
"type": "name"
},
"features": [
{
"geometry": null,
Expand Down
Expand Up @@ -2,6 +2,13 @@
Content-Type: application/json; charset=utf-8

{
"crs":
{
"properties": {
"name": "urn:ogc:def:crs:EPSG:0:3857"
},
"type": "name"
},
"features": [
{
"geometry": null,
Expand Down

0 comments on commit dc677f4

Please sign in to comment.