Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature][WMS] Add crs information in WMS GetFeatureInfo output when it differs from WGS84 #56600

Merged
merged 5 commits into from Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -975,6 +975,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 @@ -975,6 +975,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 @@ -1600,6 +1600,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 @@ -895,6 +895,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 @@ -1174,6 +1174,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 ( auto it : value.items() )
troopa81 marked this conversation as resolved.
Show resolved Hide resolved
{
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