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

misc qgis server wfs getFeature fixes when crs is defined as a ogc urn #58355

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 38 additions & 27 deletions src/server/services/wfs/qgswfsgetfeature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,30 @@ namespace QgsWfs
geometryName = QLatin1String( "NONE" );
}
// outputCrs
QgsCoordinateReferenceSystem outputCrs = vlayer->crs();
// if the crs is defined in the parameters, use it
// otherwise fallback:
// - geojson uses 'EPSG:4326' by default
// - other formats use the default CRS (the layer's CRS)
const QString requestSrsName = request.serverParameters().value( QStringLiteral( "SRSNAME" ) );
QString outputSrsName;
if ( !query.srsName.isEmpty() )
{
outputCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( query.srsName );
outputSrsName = query.srsName;
}
else if ( !requestSrsName.isEmpty() )
{
outputSrsName = requestSrsName;
}
else
{
// fallback to a default value
// geojson uses 'EPSG:4326' by default
outputSrsName = ( aRequest.outputFormat == QgsWfsParameters::Format::GeoJSON ) ? QStringLiteral( "EPSG:4326" ) : vlayer->crs().authid();
}

QgsCoordinateReferenceSystem outputCrs;
outputCrs.createFromUserInput( outputSrsName );

bool forceGeomToMulti = QgsWkbTypes::isMultiType( vlayer->wkbType() );

if ( !featureRequest.filterRect().isEmpty() )
Expand Down Expand Up @@ -465,19 +483,9 @@ namespace QgsWfs
// It needs to be an EPSG urn, e.g. urn:ogc:def:crs:EPSG::4326
// This follows geoserver convention
// See: https://docs.geoserver.org/stable/en/user/services/wfs/axis_order.html
// if the crs is defined in the parameters, use it
// otherwise:
// - geojson uses 'EPSG:4326' by default
// - other formats use the default CRS (DefaultSRS, which is the layer's CRS)
const QString requestSrsName = request.serverParameters().value( QStringLiteral( "SRSNAME" ) );
const QString srsName
{
!requestSrsName.isEmpty() ? requestSrsName :
( aRequest.outputFormat == QgsWfsParameters::Format::GeoJSON ? QStringLiteral( "EPSG:4326" ) : outputCrs.authid() )
};
const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
outputCrs.hasAxisInverted() &&
! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
! outputSrsName.startsWith( QLatin1String( "EPSG:" ) ) };

const createFeatureParams cfp = { layerPrecision,
layerCrs,
Expand All @@ -487,7 +495,7 @@ namespace QgsWfs
geometryName,
outputCrs,
forceGeomToMulti,
srsName,
outputSrsName,
invertAxis
};
while ( fit.nextFeature( feature ) && ( aRequest.maxFeatures == -1 || sentFeatures < aRequest.maxFeatures ) )
Expand Down Expand Up @@ -1275,21 +1283,15 @@ namespace QgsWfs
QDomElement bbElem = doc.createElement( QStringLiteral( "gml:boundedBy" ) );
if ( format == QgsWfsParameters::Format::GML3 )
{
// For WFS 1.1 we honor requested CRS and axis order
// Axis is not inverted if srsName starts with EPSG
// It needs to be an EPSG urn, e.g. urn:ogc:def:crs:EPSG::4326
// This follows geoserver convention
// See: https://docs.geoserver.org/stable/en/user/services/wfs/axis_order.html
// If requested SRS (outputSrsName) is different from rect CRS (crs) we need to transform the envelope
const QString requestSrsName = request.serverParameters().value( QStringLiteral( "SRSNAME" ) );
const QString srsName = !requestSrsName.isEmpty() ? requestSrsName : crs.authid();
const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
crs.hasAxisInverted() &&
! srsName.startsWith( QLatin1String( "EPSG:" ) ) };
const QString outputSrsName = !requestSrsName.isEmpty() ? requestSrsName : crs.authid();
QgsCoordinateReferenceSystem outputCrs;
outputCrs.createFromUserInput( outputSrsName );

// If requested SRS (srsName) is different from rect CRS (crs) we need to transform the envelope
QgsCoordinateTransform transform;
transform.setSourceCrs( crs );
transform.setDestinationCrs( QgsCoordinateReferenceSystem( srsName ) );
transform.setDestinationCrs( outputCrs );
QgsRectangle crsCorrectedRect { rect ? *rect : QgsRectangle() };

try
Expand All @@ -1301,10 +1303,19 @@ namespace QgsWfs
Q_UNUSED( cse )
}

QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( &crsCorrectedRect, doc, srsName, invertAxis, prec );
// For WFS 1.1 we honor requested CRS and axis order
// Axis is not inverted if srsName starts with EPSG
// It needs to be an EPSG urn, e.g. urn:ogc:def:crs:EPSG::4326
// This follows geoserver convention
// See: https://docs.geoserver.org/stable/en/user/services/wfs/axis_order.html
const bool invertAxis { mWfsParameters.versionAsNumber() >= QgsProjectVersion( 1, 1, 0 ) &&
outputCrs.hasAxisInverted() &&
!outputSrsName.startsWith( QLatin1String( "EPSG:" ) ) };

QDomElement envElem = QgsOgcUtils::rectangleToGMLEnvelope( &crsCorrectedRect, doc, outputSrsName, invertAxis, prec );
if ( !envElem.isNull() )
{
if ( crs.isValid() && srsName.isEmpty() )
if ( crs.isValid() && outputSrsName.isEmpty() )
{
envElem.setAttribute( QStringLiteral( "srsName" ), crs.authid() );
}
Expand Down
43 changes: 29 additions & 14 deletions tests/src/python/test_qgsserver_wfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def test_getfeature_post(self):
tests = []

template = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:GetFeature service="WFS" version="{}" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:Query typeName="testlayer" xmlns:feature="http://www.qgis.org/gml">
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:BBOX>
Expand All @@ -301,15 +301,20 @@ def test_getfeature_post(self):
</wfs:Query>
</wfs:GetFeature>
"""
tests.append(('nobbox_post', template.format("")))
tests.append(('startindex2_post', template.format('startIndex="2"')))
tests.append(('limit2_post', template.format('maxFeatures="2"')))
tests.append(('start1_limit1_post', template.format(
'startIndex="1" maxFeatures="1"')))

srsTemplate = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:Query typeName="testlayer" srsName="EPSG:3857" xmlns:feature="http://www.qgis.org/gml">
for version in ['1.0.0', '1.1.0']:
version_underscore = '_' + version.replace(".", "_")
tests.append((f'nobbox_post{version_underscore}', template.format(
version, "")))
tests.append((f'startindex2_post{version_underscore}', template.format(
version, 'startIndex="2"')))
tests.append((f'limit2_post{version_underscore}', template.format(
version, 'maxFeatures="2"')))
tests.append((f'start1_limit1_post{version_underscore}', template.format(
version, 'startIndex="1" maxFeatures="1"')))

srsTemplate = """<?xml version="" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="{}" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:Query typeName="testlayer" {} xmlns:feature="http://www.qgis.org/gml">
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:BBOX>
<ogc:PropertyName>geometry</ogc:PropertyName>
Expand All @@ -322,7 +327,12 @@ def test_getfeature_post(self):
</wfs:Query>
</wfs:GetFeature>
"""
tests.append(('srsname_post', srsTemplate.format("")))
tests.append(('srsname_post_1_0_0', srsTemplate.format(
'1.0.0', '', 'srsName="EPSG:3857"')))
tests.append(('srsname_post_1_1_0', srsTemplate.format(
'1.1.0', '', 'srsName="EPSG:3857"')))
tests.append(('srsname_post_1_1_0_urn', srsTemplate.format(
'1.1.0', '', 'srsName="urn:ogc:def:crs:EPSG::3857"')))

# Issue https://github.com/qgis/QGIS/issues/36398
# Check get feature within polygon having srsName=EPSG:4326 (same as the project/layer)
Expand Down Expand Up @@ -494,7 +504,7 @@ def test_getfeature_post(self):
</wfs:Query>
</wfs:GetFeature>
"""
tests.append(('nobbox_post', template.format("")))
tests.append(('nobbox_post_1_0_0', template.format("")))

template = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
Expand All @@ -511,7 +521,7 @@ def test_getfeature_post(self):
</wfs:Query>
</wfs:GetFeature>
"""
tests.append(('nobbox_post', template.format("")))
tests.append(('nobbox_post_1_0_0', template.format("")))

for id, req in tests:
self.wfs_getfeature_post_compare(id, req)
Expand Down Expand Up @@ -572,11 +582,16 @@ def test_getFeatureFeatureId(self):
"GetFeature", '1.0.0', "SRSNAME=EPSG:4326&TYPENAME=testlayer&FEATUREID=testlayer.0&PROPERTYNAME=*", 'wfs_getFeature_1_0_0_featureid_0')

def test_getFeatureFeature11urn(self):
"""Test GetFeature with SRSNAME urn:ogc:def:crs:EPSG::4326"""
"""Test GetFeature with SRSNAME as urn:ogc:def:crs:EPSG::X"""

# urn:ogc:def:crs:EPSG::4326
self.wfs_request_compare(
"GetFeature", '1.1.0', "SRSNAME=urn:ogc:def:crs:EPSG::4326&TYPENAME=testlayer&FEATUREID=testlayer.0", 'wfs_getFeature_1_1_0_featureid_0_1_1_0')

# urn:ogc:def:crs:EPSG::3857
self.wfs_request_compare(
"GetFeature", '1.1.0', "SRSNAME=urn:ogc:def:crs:EPSG::3857&TYPENAME=testlayer&FEATUREID=testlayer.0", 'wfs_getFeature_1_1_0_featureid_0_1_1_0_epsg3857')

def test_get_feature_srsname_empty(self):
"""Test GetFeature with an empty SRSNAME."""
self.wfs_request_compare(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Content-Type: text/xml; subtype=gml/3.1.1; charset=utf-8

<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" xmlns:ows="http://www.opengis.net/ows" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:qgs="http://www.qgis.org/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd http://www.qgis.org/gml ?MAP=/home/ale/dev/QGIS/tests/testdata/qgis_server/test_project_wfs.qgs&amp;SERVICE=WFS&amp;VERSION=1.1.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=testlayer&amp;OUTPUTFORMAT=text/xml; subtype%3Dgml/3.1.1">
<gml:boundedBy>
<gml:Envelope srsName="urn:ogc:def:crs:EPSG::3857">
<gml:lowerCorner>913204.91280263 5606011.45647302</gml:lowerCorner>
<gml:upperCorner>913214.67407005 5606025.23730414</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<gml:featureMember>
<qgs:testlayer gml:id="testlayer.0">
<gml:boundedBy>
<gml:Envelope srsName="urn:ogc:def:crs:EPSG::3857">
<gml:lowerCorner>913209.03579284 5606025.23730414</gml:lowerCorner>
<gml:upperCorner>913209.03579284 5606025.23730414</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<qgs:geometry>
<Point xmlns="http://www.opengis.net/gml" srsName="urn:ogc:def:crs:EPSG::3857">
<pos xmlns="http://www.opengis.net/gml" srsDimension="2">913209.03579284 5606025.23730414</pos>
</Point>
</qgs:geometry>
<qgs:id>1</qgs:id>
<qgs:name>one</qgs:name>
<qgs:utf8nameè>one èé</qgs:utf8nameè>
</qgs:testlayer>
</gml:featureMember>
</wfs:FeatureCollection>
46 changes: 46 additions & 0 deletions tests/testdata/qgis_server/wfs_getfeature_limit2_post_1_1_0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Content-Type: text/xml; subtype=gml/3.1.1; charset=utf-8

<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" xmlns:ows="http://www.opengis.net/ows" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:qgs="http://www.qgis.org/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd http://www.qgis.org/gml ?">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>8 44</gml:lowerCorner>
<gml:upperCorner>9 45</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<gml:featureMember>
<qgs:testlayer gml:id="testlayer.0">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>8.20349634 44.90148253</gml:lowerCorner>
<gml:upperCorner>8.20349634 44.90148253</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<qgs:geometry>
<Point xmlns="http://www.opengis.net/gml" srsName="EPSG:4326">
<pos xmlns="http://www.opengis.net/gml" srsDimension="2">8.20349634 44.90148253</pos>
</Point>
</qgs:geometry>
<qgs:id>1</qgs:id>
<qgs:name>one</qgs:name>
<qgs:utf8nameè>one èé</qgs:utf8nameè>
</qgs:testlayer>
</gml:featureMember>
<gml:featureMember>
<qgs:testlayer gml:id="testlayer.1">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>8.20354699 44.90143568</gml:lowerCorner>
<gml:upperCorner>8.20354699 44.90143568</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<qgs:geometry>
<Point xmlns="http://www.opengis.net/gml" srsName="EPSG:4326">
<pos xmlns="http://www.opengis.net/gml" srsDimension="2">8.20354699 44.90143568</pos>
</Point>
</qgs:geometry>
<qgs:id>2</qgs:id>
<qgs:name>two</qgs:name>
<qgs:utf8nameè>two àò</qgs:utf8nameè>
</qgs:testlayer>
</gml:featureMember>
</wfs:FeatureCollection>
64 changes: 64 additions & 0 deletions tests/testdata/qgis_server/wfs_getfeature_nobbox_post_1_1_0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Content-Type: text/xml; subtype=gml/3.1.1; charset=utf-8

<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" xmlns:ows="http://www.opengis.net/ows" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:qgs="http://www.qgis.org/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd http://www.qgis.org/gml ?">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>8 44</gml:lowerCorner>
<gml:upperCorner>9 45</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<gml:featureMember>
<qgs:testlayer gml:id="testlayer.0">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>8.20349634 44.90148253</gml:lowerCorner>
<gml:upperCorner>8.20349634 44.90148253</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<qgs:geometry>
<Point xmlns="http://www.opengis.net/gml" srsName="EPSG:4326">
<pos xmlns="http://www.opengis.net/gml" srsDimension="2">8.20349634 44.90148253</pos>
</Point>
</qgs:geometry>
<qgs:id>1</qgs:id>
<qgs:name>one</qgs:name>
<qgs:utf8nameè>one èé</qgs:utf8nameè>
</qgs:testlayer>
</gml:featureMember>
<gml:featureMember>
<qgs:testlayer gml:id="testlayer.1">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>8.20354699 44.90143568</gml:lowerCorner>
<gml:upperCorner>8.20354699 44.90143568</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<qgs:geometry>
<Point xmlns="http://www.opengis.net/gml" srsName="EPSG:4326">
<pos xmlns="http://www.opengis.net/gml" srsDimension="2">8.20354699 44.90143568</pos>
</Point>
</qgs:geometry>
<qgs:id>2</qgs:id>
<qgs:name>two</qgs:name>
<qgs:utf8nameè>two àò</qgs:utf8nameè>
</qgs:testlayer>
</gml:featureMember>
<gml:featureMember>
<qgs:testlayer gml:id="testlayer.2">
<gml:boundedBy>
<gml:Envelope srsName="EPSG:4326">
<gml:lowerCorner>8.20345931 44.90139484</gml:lowerCorner>
<gml:upperCorner>8.20345931 44.90139484</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
<qgs:geometry>
<Point xmlns="http://www.opengis.net/gml" srsName="EPSG:4326">
<pos xmlns="http://www.opengis.net/gml" srsDimension="2">8.20345931 44.90139484</pos>
</Point>
</qgs:geometry>
<qgs:id>3</qgs:id>
<qgs:name>three</qgs:name>
<qgs:utf8nameè>three èé↓</qgs:utf8nameè>
</qgs:testlayer>
</gml:featureMember>
</wfs:FeatureCollection>
Loading
Loading