Skip to content

Commit 7222828

Browse files
committed
Use QJson for JSON encoding of features
1 parent 891ea18 commit 7222828

File tree

4 files changed

+290
-1
lines changed

4 files changed

+290
-1
lines changed

python/core/auto_generated/qgsjsonutils.sip.in

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,22 @@ Returns a GeoJSON string representation of a feature.
228228

229229
:return: GeoJSON string
230230

231+
.. seealso:: :py:func:`exportFeatures`
232+
%End
233+
234+
QJsonObject exportFeatureV2( const QgsFeature &feature,
235+
const QVariantMap &extraProperties = QVariantMap(),
236+
const QVariant &id = QVariant() ) const;
237+
%Docstring
238+
Returns a GeoJSON string representation of a feature.
239+
240+
:param feature: feature to convert
241+
:param extraProperties: map of extra attributes to include in feature's properties
242+
:param id: optional ID to use as GeoJSON feature's ID instead of input feature's ID. If omitted, feature's
243+
ID is used.
244+
245+
:return: GeoJSON string
246+
231247
.. seealso:: :py:func:`exportFeatures`
232248
%End
233249

@@ -314,6 +330,19 @@ Exports all attributes from a QgsFeature as a JSON map type.
314330
to speed up exporting the attributes for multiple features from the same layer.
315331
%End
316332

333+
static QJsonObject exportAttributesV2( const QgsFeature &feature, QgsVectorLayer *layer = 0,
334+
const QVector<QVariant> &attributeWidgetCaches = QVector<QVariant>() );
335+
%Docstring
336+
Exports all attributes from a QgsFeature as a JSON map type.
337+
338+
:param feature: feature to export
339+
:param layer: optional associated vector layer. If specified, this allows
340+
richer export utilising settings like the layer's fields widget configuration.
341+
:param attributeWidgetCaches: optional widget configuration cache. Can be used
342+
to speed up exporting the attributes for multiple features from the same layer.
343+
%End
344+
345+
317346
static QVariantList parseArray( const QString &json, QVariant::Type type );
318347
%Docstring
319348
Parse a simple array (depth=1).

src/core/qgsjsonutils.cpp

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,136 @@ QString QgsJsonExporter::exportFeature( const QgsFeature &feature, const QVarian
236236
return s;
237237
}
238238

239+
QJsonObject QgsJsonExporter::exportFeatureV2( const QgsFeature &feature, const QVariantMap &extraProperties, const QVariant &id ) const
240+
{
241+
QJsonObject featureJson
242+
{
243+
{ QStringLiteral( "type" ), QStringLiteral( "Feature" ) },
244+
{ QStringLiteral( "id" ), ( ! id.isValid() ? QJsonValue( feature.id() ) : QJsonValue::fromVariant( id ) ) },
245+
};
246+
247+
QgsGeometry geom = feature.geometry();
248+
if ( !geom.isNull() && mIncludeGeometry )
249+
{
250+
if ( mCrs.isValid() )
251+
{
252+
try
253+
{
254+
QgsGeometry transformed = geom;
255+
if ( transformed.transform( mTransform ) == 0 )
256+
geom = transformed;
257+
}
258+
catch ( QgsCsException &cse )
259+
{
260+
Q_UNUSED( cse );
261+
}
262+
}
263+
QgsRectangle box = geom.boundingBox();
264+
265+
if ( QgsWkbTypes::flatType( geom.wkbType() ) != QgsWkbTypes::Point )
266+
{
267+
featureJson[ QStringLiteral( "bbox" ) ] = QJsonArray( { qgsDoubleToString( box.xMinimum(), mPrecision ),
268+
qgsDoubleToString( box.yMinimum(), mPrecision ),
269+
qgsDoubleToString( box.xMaximum(), mPrecision ),
270+
qgsDoubleToString( box.yMaximum(), mPrecision ) } );
271+
}
272+
featureJson[ QStringLiteral( "geometry" ) ] = QJsonDocument::fromJson( geom.asJson( mPrecision ).toLocal8Bit() ).object();
273+
}
274+
else
275+
{
276+
featureJson[ QStringLiteral( "geometry" ) ] = QJsonValue();
277+
}
278+
279+
// build up properties element
280+
int attributeCounter { 0 };
281+
QJsonObject properties;
282+
if ( mIncludeAttributes || !extraProperties.isEmpty() )
283+
{
284+
//read all attribute values from the feature
285+
if ( mIncludeAttributes )
286+
{
287+
QgsFields fields = mLayer ? mLayer->fields() : feature.fields();
288+
// List of formatters through we want to pass the values
289+
QStringList formattersWhiteList;
290+
formattersWhiteList << QStringLiteral( "KeyValue" )
291+
<< QStringLiteral( "List" )
292+
<< QStringLiteral( "ValueRelation" )
293+
<< QStringLiteral( "ValueMap" );
294+
295+
for ( int i = 0; i < fields.count(); ++i )
296+
{
297+
if ( ( !mAttributeIndexes.isEmpty() && !mAttributeIndexes.contains( i ) ) || mExcludedAttributeIndexes.contains( i ) )
298+
continue;
299+
300+
QVariant val = feature.attributes().at( i );
301+
302+
if ( mLayer )
303+
{
304+
QgsEditorWidgetSetup setup = fields.at( i ).editorWidgetSetup();
305+
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
306+
if ( formattersWhiteList.contains( fieldFormatter->id() ) )
307+
val = fieldFormatter->representValue( mLayer.data(), i, setup.config(), QVariant(), val );
308+
}
309+
310+
QString name = fields.at( i ).name();
311+
if ( mAttributeDisplayName )
312+
{
313+
name = mLayer->attributeDisplayName( i );
314+
}
315+
properties[ name ] = QJsonValue::fromVariant( val );
316+
attributeCounter++;
317+
}
318+
}
319+
320+
if ( !extraProperties.isEmpty() )
321+
{
322+
QVariantMap::const_iterator it = extraProperties.constBegin();
323+
for ( ; it != extraProperties.constEnd(); ++it )
324+
{
325+
properties[ it.key() ] = QJsonValue::fromVariant( it.value() );
326+
attributeCounter++;
327+
}
328+
}
329+
330+
// related attributes
331+
if ( mLayer && mIncludeRelatedAttributes )
332+
{
333+
QList< QgsRelation > relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer.data() );
334+
for ( const auto &relation : qgis::as_const( relations ) )
335+
{
336+
QgsFeatureRequest req = relation.getRelatedFeaturesRequest( feature );
337+
req.setFlags( QgsFeatureRequest::NoGeometry );
338+
QgsVectorLayer *childLayer = relation.referencingLayer();
339+
QJsonArray relatedFeatureAttributes;
340+
if ( childLayer )
341+
{
342+
QgsFeatureIterator it = childLayer->getFeatures( req );
343+
QVector<QVariant> attributeWidgetCaches;
344+
int fieldIndex = 0;
345+
const QgsFields fields { childLayer->fields() };
346+
for ( const QgsField &field : fields )
347+
{
348+
QgsEditorWidgetSetup setup = field.editorWidgetSetup();
349+
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
350+
attributeWidgetCaches.append( fieldFormatter->createCache( childLayer, fieldIndex, setup.config() ) );
351+
fieldIndex++;
352+
}
353+
QgsFeature relatedFet;
354+
while ( it.nextFeature( relatedFet ) )
355+
{
356+
relatedFeatureAttributes += QgsJsonUtils::exportAttributes( relatedFet, childLayer, attributeWidgetCaches );
357+
}
358+
}
359+
properties[ relation.name() ] = relatedFeatureAttributes;
360+
attributeCounter++;
361+
}
362+
}
363+
}
364+
// bool hasProperties = attributeCounter > 0;
365+
featureJson[ QStringLiteral( "properties" ) ] = properties;
366+
return featureJson;
367+
}
368+
239369
QString QgsJsonExporter::exportFeatures( const QgsFeatureList &features ) const
240370
{
241371
QStringList featureJSON;
@@ -244,7 +374,6 @@ QString QgsJsonExporter::exportFeatures( const QgsFeatureList &features ) const
244374
{
245375
featureJSON << exportFeature( feature );
246376
}
247-
248377
return QStringLiteral( "{ \"type\": \"FeatureCollection\",\n \"features\":[\n%1\n]}" ).arg( featureJSON.join( QStringLiteral( ",\n" ) ) );
249378
}
250379

@@ -351,3 +480,25 @@ QVariantList QgsJsonUtils::parseArray( const QString &json, QVariant::Type type
351480
}
352481
return result;
353482
}
483+
484+
485+
QJsonObject QgsJsonUtils::exportAttributesV2( const QgsFeature &feature, QgsVectorLayer *layer, const QVector<QVariant> &attributeWidgetCaches )
486+
{
487+
QgsFields fields = feature.fields();
488+
QJsonObject attrs;
489+
for ( int i = 0; i < fields.count(); ++i )
490+
{
491+
QVariant val = feature.attributes().at( i );
492+
493+
if ( layer )
494+
{
495+
QgsEditorWidgetSetup setup = layer->fields().at( i ).editorWidgetSetup();
496+
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
497+
if ( fieldFormatter != QgsApplication::fieldFormatterRegistry()->fallbackFieldFormatter() )
498+
val = fieldFormatter->representValue( layer, i, setup.config(), attributeWidgetCaches.count() >= i ? attributeWidgetCaches.at( i ) : QVariant(), val );
499+
}
500+
501+
attrs.insert( fields.at( i ).name(), QJsonValue::fromVariant( val ) );
502+
}
503+
return attrs;
504+
}

src/core/qgsjsonutils.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "qgsfields.h"
2424

2525
#include <QPointer>
26+
#include <QJsonValue>
2627

2728
class QTextCodec;
2829

@@ -198,6 +199,19 @@ class CORE_EXPORT QgsJsonExporter
198199
const QVariantMap &extraProperties = QVariantMap(),
199200
const QVariant &id = QVariant() ) const;
200201

202+
/**
203+
* Returns a GeoJSON string representation of a feature.
204+
* \param feature feature to convert
205+
* \param extraProperties map of extra attributes to include in feature's properties
206+
* \param id optional ID to use as GeoJSON feature's ID instead of input feature's ID. If omitted, feature's
207+
* ID is used.
208+
* \returns GeoJSON string
209+
* \see exportFeatures()
210+
*/
211+
QJsonObject exportFeatureV2( const QgsFeature &feature,
212+
const QVariantMap &extraProperties = QVariantMap(),
213+
const QVariant &id = QVariant() ) const;
214+
201215

202216
/**
203217
* Returns a GeoJSON string representation of a list of features (feature collection).
@@ -291,6 +305,18 @@ class CORE_EXPORT QgsJsonUtils
291305
static QString exportAttributes( const QgsFeature &feature, QgsVectorLayer *layer = nullptr,
292306
const QVector<QVariant> &attributeWidgetCaches = QVector<QVariant>() );
293307

308+
/**
309+
* Exports all attributes from a QgsFeature as a JSON map type.
310+
* \param feature feature to export
311+
* \param layer optional associated vector layer. If specified, this allows
312+
* richer export utilising settings like the layer's fields widget configuration.
313+
* \param attributeWidgetCaches optional widget configuration cache. Can be used
314+
* to speed up exporting the attributes for multiple features from the same layer.
315+
*/
316+
static QJsonObject exportAttributesV2( const QgsFeature &feature, QgsVectorLayer *layer = nullptr,
317+
const QVector<QVariant> &attributeWidgetCaches = QVector<QVariant>() );
318+
319+
294320
/**
295321
* Parse a simple array (depth=1).
296322
* \param json the JSON to parse

tests/src/core/testqgsjsonutils.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
#include "qgstest.h"
1717
#include <qgsjsonutils.h>
18+
#include "qgsvectorlayer.h"
19+
#include "qgsfeature.h"
20+
1821

1922
class TestQgsJsonUtils : public QObject
2023
{
@@ -71,6 +74,86 @@ class TestQgsJsonUtils : public QObject
7174
QCOMPARE( back, list );
7275
QCOMPARE( back.at( 0 ).type(), QVariant::Double );
7376
}
77+
78+
void testV2ExportAttributes_data()
79+
{
80+
QTest::addColumn<bool>( "useQJson" );
81+
QTest::newRow( "Use V2 (QJson)" ) << true;
82+
QTest::newRow( "Use old string concat" ) << false;
83+
}
84+
85+
void testV2ExportAttributes()
86+
{
87+
88+
QFETCH( bool, useQJson );
89+
90+
QgsVectorLayer vl { QStringLiteral( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double" ), QStringLiteral( "mem" ), QStringLiteral( "memory" ) };
91+
QgsFeature feature { vl.fields() };
92+
feature.setAttributes( QgsAttributes() << QStringLiteral( "a value" ) << 1 << 2.0 );
93+
94+
if ( useQJson ) // average: 0.0048 msecs per iteration
95+
{
96+
QBENCHMARK
97+
{
98+
const auto json { QgsJsonUtils::exportAttributesV2( feature, &vl ) };
99+
QCOMPARE( QJsonDocument( json ).toJson( QJsonDocument::JsonFormat::Compact ), QStringLiteral( "{\"flddbl\":2,\"fldint\":1,\"fldtxt\":\"a value\"}" ) );
100+
}
101+
}
102+
else // average: 0.0070 msecs per iteration
103+
{
104+
QBENCHMARK
105+
{
106+
const auto json { QgsJsonUtils::exportAttributes( feature, &vl ) };
107+
QCOMPARE( json, QStringLiteral( "{\"fldtxt\":\"a value\",\n\"fldint\":1,\n\"flddbl\":2}" ) );
108+
}
109+
}
110+
}
111+
112+
void testV2ExportFeature_data()
113+
{
114+
QTest::addColumn<bool>( "useQJson" );
115+
QTest::newRow( "Use V2 (QJson)" ) << true;
116+
QTest::newRow( "Use old string concat" ) << false;
117+
}
118+
119+
void testV2ExportFeature()
120+
{
121+
122+
QFETCH( bool, useQJson );
123+
124+
QgsVectorLayer vl { QStringLiteral( "Polygon?field=fldtxt:string&field=fldint:integer&field=flddbl:double" ), QStringLiteral( "mem" ), QStringLiteral( "memory" ) };
125+
QgsFeature feature { vl.fields() };
126+
feature.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))" ) ) );
127+
feature.setAttributes( QgsAttributes() << QStringLiteral( "a value" ) << 1 << 2.0 );
128+
129+
QgsJsonExporter exporter { &vl };
130+
131+
if ( useQJson ) // average: 0.063 msecs per iteration
132+
{
133+
QBENCHMARK
134+
{
135+
const auto json { exporter.exportFeatureV2( feature ) };
136+
QCOMPARE( QJsonDocument( json ).toJson( QJsonDocument::JsonFormat::Compact ),
137+
QStringLiteral( "{\"bbox\":[\"1\",\"1\",\"5\",\"5\"],\"geometry\":{\"coordinates\""
138+
":[[[1,1],[5,1],[5,5],[1,5],[1,1]],[[2,2],[3,2],[3,3],[2,3],[2,2]]],"
139+
"\"type\":\"Polygon\"},\"id\":0,\"properties\":{\"flddbl\":2,\"fldint\":"
140+
"1,\"fldtxt\":\"a value\"},\"type\":\"Feature\"}"
141+
) );
142+
}
143+
}
144+
else // average: 0.047 msecs per iteration
145+
{
146+
QBENCHMARK
147+
{
148+
const auto json { exporter.exportFeature( feature ) };
149+
QCOMPARE( json, QStringLiteral( "{\n \"type\":\"Feature\",\n \"id\":0,\n \"bbox\":[1, 1, 5, 5],\n "
150+
"\"geometry\":\n {\"type\": \"Polygon\", "
151+
"\"coordinates\": [[ [1, 1], [5, 1], [5, 5], [1, 5], [1, 1]], [ [2, 2], "
152+
"[3, 2], [3, 3], [2, 3], [2, 2]]] },\n "
153+
"\"properties\":{\n \"fldtxt\":\"a value\",\n \"fldint\":1,\n \"flddbl\":2\n }\n}" ) );
154+
}
155+
}
156+
}
74157
};
75158

76159
QGSTEST_MAIN( TestQgsJsonUtils )

0 commit comments

Comments
 (0)