Skip to content

Commit

Permalink
OAPIF: fix support of StringList fields
Browse files Browse the repository at this point in the history
Fixes #33758
  • Loading branch information
rouault committed Jan 22, 2020
1 parent 55ec9c8 commit af14ea9
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 9 deletions.
10 changes: 6 additions & 4 deletions src/providers/wfs/qgsbackgroundcachedfeatureiterator.cpp
Expand Up @@ -20,6 +20,7 @@
#include "qgsfeedback.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include "qgswfsutils.h" // for isCompatibleType()

#include <QDataStream>
#include <QDir>
Expand Down Expand Up @@ -816,14 +817,15 @@ void QgsBackgroundCachedFeatureIterator::copyFeature( const QgsFeature &srcFeatu
if ( idx >= 0 )
{
const QVariant &v = srcFeature.attributes().value( idx );
const QVariant::Type fieldType = fields.at( i ).type();
if ( v.isNull() )
dstFeature.setAttribute( i, QVariant( fields.at( i ).type() ) );
else if ( v.type() == fields.at( i ).type() )
dstFeature.setAttribute( i, QVariant( fieldType ) );
else if ( QgsWFSUtils::isCompatibleType( v.type(), fieldType ) )
dstFeature.setAttribute( i, v );
else if ( fields.at( i ).type() == QVariant::DateTime && !v.isNull() )
else if ( fieldType == QVariant::DateTime && !v.isNull() )
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
else
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fields.at( i ).type(), v.toString() ) );
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fieldType, v.toString() ) );
}
};

Expand Down
21 changes: 17 additions & 4 deletions src/providers/wfs/qgsbackgroundcachedshareddata.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsproviderregistry.h"
#include "qgsspatialiteutils.h"
#include "qgsvectorfilewriter.h"
#include "qgswfsutils.h" // for isCompatibleType()

#include <QCryptographicHash>
#include <QDir>
Expand Down Expand Up @@ -172,6 +173,10 @@ bool QgsBackgroundCachedSharedData::createCache()
// it to a String
type = QVariant::LongLong;
}
else if ( type == QVariant::List && field.subType() == QVariant::String )
{
type = QVariant::StringList;
}

// Make sure we don't have several field names that only differ by their case
QString sqliteFieldName( field.name() );
Expand Down Expand Up @@ -262,6 +267,8 @@ bool QgsBackgroundCachedSharedData::createCache()
type = QStringLiteral( "BIGINT" );
else if ( field.type() == QVariant::Double )
type = QStringLiteral( "REAL" );
else if ( field.type() == QVariant::StringList )
type = QStringLiteral( "JSONSTRINGLIST" );

sql += QStringLiteral( ", %1 %2" ).arg( quotedIdentifier( field.name() ), type );
}
Expand Down Expand Up @@ -590,12 +597,13 @@ void QgsBackgroundCachedSharedData::serializeFeatures( QVector<QgsFeatureUniqueI
if ( idx >= 0 )
{
const QVariant &v = srcFeature.attributes().value( i );
const QVariant::Type fieldType = dataProviderFields.at( idx ).type();
if ( v.type() == QVariant::DateTime && !v.isNull() )
cachedFeature.setAttribute( idx, QVariant( v.toDateTime().toMSecsSinceEpoch() ) );
else if ( v.type() != dataProviderFields.at( idx ).type() )
cachedFeature.setAttribute( idx, QgsVectorDataProvider::convertValue( dataProviderFields.at( idx ).type(), v.toString() ) );
else
else if ( QgsWFSUtils::isCompatibleType( v.type(), fieldType ) )
cachedFeature.setAttribute( idx, v );
else
cachedFeature.setAttribute( idx, QgsVectorDataProvider::convertValue( fieldType, v.toString() ) );
}
}

Expand Down Expand Up @@ -1120,10 +1128,15 @@ QString QgsBackgroundCachedSharedData::getMD5( const QgsFeature &f )
qint64 val = v.toLongLong();
hash.addData( QByteArray( ( const char * )&val, sizeof( val ) ) );
}
else if ( v.type() == QVariant::String )
else if ( v.type() == QVariant::String )
{
hash.addData( v.toByteArray() );
}
else if ( v.type() == QVariant::StringList )
{
for ( const QString &s : v.toStringList() )
hash.addData( s.toUtf8() );
}
}

const int attrCount = attrs.size();
Expand Down
3 changes: 2 additions & 1 deletion src/providers/wfs/qgsoapifprovider.cpp
Expand Up @@ -21,6 +21,7 @@
#include "qgsoapifapirequest.h"
#include "qgsoapifcollection.h"
#include "qgsoapifitemsrequest.h"
#include "qgswfsutils.h" // for isCompatibleType()

#include <algorithm>

Expand Down Expand Up @@ -735,7 +736,7 @@ void QgsOapifFeatureDownloaderImpl::run( bool serializeFeatures, int maxFeatures
const auto dstFieldType = dstFields.at( j ).type();
if ( v.isNull() )
dstFeat.setAttribute( j, QVariant( dstFieldType ) );
else if ( v.type() == dstFieldType )
else if ( QgsWFSUtils::isCompatibleType( v.type(), dstFieldType ) )
dstFeat.setAttribute( j, v );
else
dstFeat.setAttribute( j, QgsVectorDataProvider::convertValue( dstFieldType, v.toString() ) );
Expand Down
8 changes: 8 additions & 0 deletions src/providers/wfs/qgswfsutils.h
Expand Up @@ -16,6 +16,7 @@
#define QGSWFSUTILS_H

#include <QString>
#include <QVariant>

/**
* Utility class */
Expand All @@ -26,6 +27,13 @@ class QgsWFSUtils
static QString removeNamespacePrefix( const QString &tname );
//! Returns namespace prefix (or an empty string if there is no prefix)
static QString nameSpacePrefix( const QString &tname );

static inline bool isCompatibleType( QVariant::Type a, QVariant::Type b )
{
return a == b ||
( a == QVariant::StringList && b == QVariant::List ) ||
( a == QVariant::List && b == QVariant::StringList );
}
};

#endif // QGSWFSUTILS_H
35 changes: 35 additions & 0 deletions tests/src/python/test_provider_oapif.py
Expand Up @@ -18,6 +18,7 @@
import shutil
import tempfile

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

from qgis.core import (
Expand Down Expand Up @@ -53,6 +54,10 @@ def sanitize(endpoint, x):
return ret


def GDAL_COMPUTE_VERSION(maj, min, rev):
return ((maj) * 1000000 + (min) * 10000 + (rev) * 100)


ACCEPT_LANDING = 'Accept=application/json'
ACCEPT_API = 'Accept=application/vnd.oai.openapi+json;version=3.0, application/openapi+json;version=3.0, application/json'
ACCEPT_COLLECTION = 'Accept=application/json'
Expand Down Expand Up @@ -596,6 +601,36 @@ def testDateTimeFiltering(self):
os.unlink(filename)
self.assertEqual(values, ['feat.1'])

def testStringList(self):

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

items = {
"type": "FeatureCollection",
"features": [
{"type": "Feature", "id": "feat.1", "properties": {"my_stringlist_field": ["a", "b"]}, "geometry": {"type": "Point", "coordinates": [-70.332, 66.33]}}
]
}

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

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

filename = sanitize(endpoint, '/collections/mycollection/items?limit=1000&' + ACCEPT_ITEMS)
with open(filename, 'wb') as f:
f.write(json.dumps(items).encode('UTF-8'))
features = [f for f in vl.getFeatures()]
os.unlink(filename)
if int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 4, 0):
self.assertEqual(features[0]['my_stringlist_field'], '(2:a,b)')
else:
self.assertEqual(features[0]['my_stringlist_field'], ["a", "b"])


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

0 comments on commit af14ea9

Please sign in to comment.