Skip to content

Commit

Permalink
Fix Server WMS unstable feature IDs
Browse files Browse the repository at this point in the history
Fixes #41124

Fix for JSON and GML only: the other formats were already ok.
  • Loading branch information
elpaso committed Jan 22, 2021
1 parent 4dbb40e commit e40e163
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 3 deletions.
16 changes: 13 additions & 3 deletions src/server/services/wms/qgswmsrenderer.cpp
Expand Up @@ -2319,6 +2319,8 @@ namespace QgsWms
if ( featuresNode.isEmpty() )
continue;

QMap<QgsFeatureId, QString> fidMap;

for ( int j = 0; j < featuresNode.size(); ++j )
{
const QDomElement featureNode = featuresNode.at( j ).toElement();
Expand All @@ -2336,6 +2338,8 @@ namespace QgsWms
vl->getFeatures( request ).nextFeature( feature );
}

fidMap.insert( feature.id(), fid );

QString wkt;
if ( withGeometry )
{
Expand Down Expand Up @@ -2381,7 +2385,7 @@ namespace QgsWms

for ( const auto &feature : qgis::as_const( features ) )
{
const QString id = QStringLiteral( "%1.%2" ).arg( layerName ).arg( feature.id() );
const QString id = QStringLiteral( "%1.%2" ).arg( layerName ).arg( fidMap.value( feature.id() ) );
json["features"].push_back( exporter.exportFeatureToJsonObject( feature, QVariantMap(), id ) );
}
}
Expand Down Expand Up @@ -2432,7 +2436,13 @@ namespace QgsWms
{
//qgs:%TYPENAME%
QDomElement typeNameElement = doc.createElement( "qgs:" + typeName /*qgs:%TYPENAME%*/ );
typeNameElement.setAttribute( QStringLiteral( "fid" ), typeName + "." + QString::number( feat->id() ) );
QString fid;
if ( layer && layer->dataProvider() )
fid = QgsServerFeatureId::getServerFid( *feat, layer->dataProvider()->pkAttributeIndexes() );
else
fid = feat->id();

typeNameElement.setAttribute( QStringLiteral( "fid" ), QStringLiteral( "%1.%2" ).arg( typeName, fid ) );

QgsCoordinateTransform transform;
if ( layer && layer->crs() != crs )
Expand Down Expand Up @@ -2460,7 +2470,7 @@ namespace QgsWms
{
try
{
QgsRectangle transformedBox = transform.transformBoundingBox( box );
const QgsRectangle transformedBox = transform.transformBoundingBox( box );
box = transformedBox;
}
catch ( QgsCsException &e )
Expand Down
3 changes: 3 additions & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -380,6 +380,9 @@ if (ENABLE_PGTEST)
ADD_PYTHON_TEST(PyQgsDatabaseSchemaComboBox test_qgsdatabaseschemacombobox.py)
ADD_PYTHON_TEST(PyQgsDatabaseTableComboBox test_qgsdatabasetablecombobox.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionPostgres test_qgsproviderconnection_postgres.py)
if (WITH_SERVER)
ADD_PYTHON_TEST(PyQgsServerWMSGetFeatureInfoPG test_qgsserver_wms_getfeatureinfo_postgres.py)
endif()
endif()

if (ENABLE_MSSQLTEST)
Expand Down
2 changes: 2 additions & 0 deletions tests/src/python/test_qgsserver.py
Expand Up @@ -108,7 +108,9 @@ def assertXMLEqual(self, response, expected, msg='', raw=False):

@classmethod
def setUpClass(cls):

cls.app = QgsApplication([], False)
cls.app.initQgis()

@classmethod
def tearDownClass(cls):
Expand Down
123 changes: 123 additions & 0 deletions tests/src/python/test_qgsserver_wms_getfeatureinfo_postgres.py
@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsServer GetFeatureInfo WMS with PG.
From build dir, run: ctest -R PyQgsServerWMSGetFeatureInfoPG -V
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Alessandro Pasotti'
__date__ = '22/01/2021'
__copyright__ = 'Copyright 2021, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

import os
import tempfile

# Needed on Qt 5 so that the serialization of XML is consistent among all
# executions
os.environ['QT_HASH_SEED'] = '1'

os.environ['QGIS_CUSTOM_CONFIG_PATH'] = tempfile.mkdtemp('', 'QGIS-PythonTestConfigPath')

import re
import urllib.request
import urllib.parse
import urllib.error

import xml.etree.ElementTree as ET
import json

from qgis.testing import unittest, start_app
from qgis.PyQt.QtCore import QSize

import osgeo.gdal # NOQA

from test_qgsserver_wms import TestQgsServerWMSTestBase
from qgis.core import QgsProject, QgsVectorLayer, QgsFeatureRequest, QgsExpression, QgsProviderRegistry
from qgis.server import QgsBufferServerRequest, QgsBufferServerResponse


class TestQgsServerWMSGetFeatureInfoPG(TestQgsServerWMSTestBase):
"""QGIS Server WMS Tests for GetFeatureInfo request"""

@classmethod
def setUpClass(cls):

super().setUpClass()

cls.dbconn = 'service=qgis_test'
if 'QGIS_PGTEST_DB' in os.environ:
cls.dbconn = os.environ['QGIS_PGTEST_DB']

# Test layer
md = QgsProviderRegistry.instance().providerMetadata('postgres')
uri = cls.dbconn + 'dbname=qgis_test sslmode=disable '
conn = md.createConnection(uri, {})
conn.executeSql('DROP TABLE IF EXISTS "qgis_test"."someDataLong" CASCADE')
conn.executeSql('SELECT * INTO "qgis_test"."someDataLong" FROM "qgis_test"."someData"')
conn.executeSql('ALTER TABLE "qgis_test"."someDataLong" ALTER COLUMN "pk" TYPE bigint')
conn.executeSql('ALTER TABLE "qgis_test"."someDataLong" ALTER COLUMN "pk" TYPE bigint')
conn.executeSql('ALTER TABLE "qgis_test"."someDataLong" ALTER COLUMN "pk" SET NOT NULL')
conn.executeSql('CREATE UNIQUE INDEX someDataLongIdx ON "qgis_test"."someDataLong" ("pk")')

cls.vlconn = cls.dbconn + ' sslmode=disable key=\'pk\' checkPrimaryKeyUnicity=0 srid=4326 type=POINT table="qgis_test"."someDataLong" (geom) sql='

def _baseFilterTest(self, info_format):

vl = QgsVectorLayer(self.vlconn, 'someData', 'postgres')
self.assertTrue(vl.isValid())

# Pre-filtered
vl2 = QgsVectorLayer(self.vlconn, 'someData', 'postgres')
self.assertTrue(vl2.isValid())
[f for f in vl2.getFeatures(QgsFeatureRequest(QgsExpression('pk > 2')))]

base_features_url = ('http://qgis/?SERVICE=WMS&REQUEST=GetFeatureInfo&' +
'LAYERS=someData&STYLES=&' +
r'INFO_FORMAT={}&' +
'SRS=EPSG%3A4326&' +
'QUERY_LAYERS=someData&X=-1&Y=-1&' +
'FEATURE_COUNT=100&'
'FILTER=someData')

two_feature_url = base_features_url + urllib.parse.quote(':"pk" = 2')

p = QgsProject()
p.addMapLayers([vl])

url = two_feature_url.format(urllib.parse.quote(info_format))

req = QgsBufferServerRequest(url)
res = QgsBufferServerResponse()
self.server.handleRequest(req, res, p)
reference_body = bytes(res.body()).decode('utf8')

# Pre-filter
p = QgsProject()
p.addMapLayers([vl2])

req = QgsBufferServerRequest(url)
res = QgsBufferServerResponse()
self.server.handleRequest(req, res, p)
two_feature_body = bytes(res.body()).decode('utf8')

self.assertEqual(reference_body, two_feature_body, info_format)

def testGetFeatureInfoFilterPg(self):
"""Test issue GH #41124"""

self._baseFilterTest('text/plain')
self._baseFilterTest('text/html')
self._baseFilterTest('text/xml')
self._baseFilterTest('application/json')
self._baseFilterTest('application/vnd.ogc.gml')


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

0 comments on commit e40e163

Please sign in to comment.