Skip to content

Commit

Permalink
[OAPIF] Handle features with complex attribute of QMap type (fixes #5…
Browse files Browse the repository at this point in the history
  • Loading branch information
rouault authored and nyalldawson committed Sep 29, 2023
1 parent c9b50a7 commit 05469fd
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/providers/wfs/qgsbackgroundcachedfeatureiterator.cpp
Expand Up @@ -26,6 +26,7 @@
#include <QDataStream>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QMutex>
#include <QPushButton>
#include <QStyle>
Expand Down Expand Up @@ -944,7 +945,9 @@ void QgsBackgroundCachedFeatureIterator::copyFeature( const QgsFeature &srcFeatu
else if ( QgsWFSUtils::isCompatibleType( v.type(), fieldType ) )
dstFeature.setAttribute( i, v );
else if ( fieldType == QVariant::DateTime && !QgsVariantUtils::isNull( v ) )
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
dstFeature.setAttribute( i, QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) );
else if ( fieldType == QVariant::Map && !QgsVariantUtils::isNull( v ) )
dstFeature.setAttribute( i, QJsonDocument::fromJson( v.toString().toUtf8() ).toVariant() );
else
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fieldType, v.toString() ) );
}
Expand Down
11 changes: 11 additions & 0 deletions src/providers/wfs/qgsbackgroundcachedshareddata.cpp
Expand Up @@ -25,6 +25,7 @@

#include <QCryptographicHash>
#include <QDir>
#include <QJsonDocument>
#include <QMutex>

#include <set>
Expand Down Expand Up @@ -629,6 +630,16 @@ void QgsBackgroundCachedSharedData::serializeFeatures( QVector<QgsFeatureUniqueI
const QVariant::Type fieldType = dataProviderFields.at( idx ).type();
if ( v.type() == QVariant::DateTime && !QgsVariantUtils::isNull( v ) )
cachedFeature.setAttribute( idx, QVariant( v.toDateTime().toMSecsSinceEpoch() ) );
else if ( v.type() == QVariant::Map && !QgsVariantUtils::isNull( v ) )
{
QString stringValue = QString::fromUtf8( QJsonDocument::fromVariant( v ).toJson().constData() );
if ( stringValue.isEmpty() )
{
//store as string, because it's no valid QJson value
stringValue = v.toString();
}
cachedFeature.setAttribute( idx, stringValue );
}
else if ( QgsWFSUtils::isCompatibleType( v.type(), fieldType ) )
cachedFeature.setAttribute( idx, v );
else
Expand Down
49 changes: 48 additions & 1 deletion tests/src/python/test_provider_oapif.py
Expand Up @@ -16,7 +16,9 @@
import shutil
import tempfile

from qgis.PyQt.QtCore import QCoreApplication, Qt, QDateTime
from osgeo import gdal

from qgis.PyQt.QtCore import QCoreApplication, QDateTime, Qt, QVariant
from qgis.PyQt.QtTest import QSignalSpy
from qgis.core import (
QgsVectorLayer,
Expand Down Expand Up @@ -872,6 +874,51 @@ def testCRS2056(self):

self.assertEqual(source.sourceCrs().authid(), 'OGC:CRS84')

# GDAL 3.5.0 is required since it is the first version that tags "complex"
# fields as OFSTJSON

@unittest.skipIf(int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(3, 5, 0), "GDAL 3.5.0 required")
def testFeatureComplexAttribute(self):

endpoint = self.__class__.basetestpath + '/fake_qgis_http_endpoint_testFeatureComplexAttribute'
create_landing_page_api_collection(endpoint)

# first items
first_items = {
"type": "FeatureCollection",
"features": [
{"type": "Feature", "id": "feat.1", "properties": {"center": {
"type": "Point",
"coordinates": [
6.50,
51.80
]
}},
"geometry": {"type": "Point", "coordinates": [66.33, -70.332]}}
]
}
with open(sanitize(endpoint, '/collections/mycollection/items?limit=10&' + ACCEPT_ITEMS), 'wb') as f:
f.write(json.dumps(first_items).encode('UTF-8'))

# real page
with open(sanitize(endpoint, '/collections/mycollection/items?limit=1000&' + ACCEPT_ITEMS), 'wb') as f:
f.write(json.dumps(first_items).encode('UTF-8'))

vl = QgsVectorLayer("url='http://" + endpoint + "' typename='mycollection' restrictToRequestBBOX=1", 'test',
'OAPIF')
self.assertTrue(vl.isValid())

self.assertEqual(vl.fields().field("center").type(), QVariant.Map)

# First time we getFeatures(): comes directly from the GeoJSON layer
values = [f["center"] for f in vl.getFeatures()]
self.assertEqual(values, [{'coordinates': [6.5, 51.8], 'type': 'Point'}])

# Now, that comes from the Spatialite cache, through
# serialization and deserialization
values = [f["center"] for f in vl.getFeatures()]
self.assertEqual(values, [{'coordinates': [6.5, 51.8], 'type': 'Point'}])


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

0 comments on commit 05469fd

Please sign in to comment.