Skip to content

Commit

Permalink
Use QJson for JSON encoding of features
Browse files Browse the repository at this point in the history
  • Loading branch information
elpaso committed Apr 16, 2019
1 parent 891ea18 commit 7222828
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 1 deletion.
29 changes: 29 additions & 0 deletions python/core/auto_generated/qgsjsonutils.sip.in
Expand Up @@ -228,6 +228,22 @@ Returns a GeoJSON string representation of a feature.

:return: GeoJSON string

.. seealso:: :py:func:`exportFeatures`
%End

QJsonObject exportFeatureV2( const QgsFeature &feature,
const QVariantMap &extraProperties = QVariantMap(),
const QVariant &id = QVariant() ) const;
%Docstring
Returns a GeoJSON string representation of a feature.

:param feature: feature to convert
:param extraProperties: map of extra attributes to include in feature's properties
:param id: optional ID to use as GeoJSON feature's ID instead of input feature's ID. If omitted, feature's
ID is used.

:return: GeoJSON string

.. seealso:: :py:func:`exportFeatures`
%End

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

static QJsonObject exportAttributesV2( const QgsFeature &feature, QgsVectorLayer *layer = 0,
const QVector<QVariant> &attributeWidgetCaches = QVector<QVariant>() );
%Docstring
Exports all attributes from a QgsFeature as a JSON map type.

:param feature: feature to export
:param layer: optional associated vector layer. If specified, this allows
richer export utilising settings like the layer's fields widget configuration.
:param attributeWidgetCaches: optional widget configuration cache. Can be used
to speed up exporting the attributes for multiple features from the same layer.
%End


static QVariantList parseArray( const QString &json, QVariant::Type type );
%Docstring
Parse a simple array (depth=1).
Expand Down
153 changes: 152 additions & 1 deletion src/core/qgsjsonutils.cpp
Expand Up @@ -236,6 +236,136 @@ QString QgsJsonExporter::exportFeature( const QgsFeature &feature, const QVarian
return s;
}

QJsonObject QgsJsonExporter::exportFeatureV2( const QgsFeature &feature, const QVariantMap &extraProperties, const QVariant &id ) const
{
QJsonObject featureJson
{
{ QStringLiteral( "type" ), QStringLiteral( "Feature" ) },
{ QStringLiteral( "id" ), ( ! id.isValid() ? QJsonValue( feature.id() ) : QJsonValue::fromVariant( id ) ) },
};

QgsGeometry geom = feature.geometry();
if ( !geom.isNull() && mIncludeGeometry )
{
if ( mCrs.isValid() )
{
try
{
QgsGeometry transformed = geom;
if ( transformed.transform( mTransform ) == 0 )
geom = transformed;
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
}
}
QgsRectangle box = geom.boundingBox();

if ( QgsWkbTypes::flatType( geom.wkbType() ) != QgsWkbTypes::Point )
{
featureJson[ QStringLiteral( "bbox" ) ] = QJsonArray( { qgsDoubleToString( box.xMinimum(), mPrecision ),
qgsDoubleToString( box.yMinimum(), mPrecision ),
qgsDoubleToString( box.xMaximum(), mPrecision ),
qgsDoubleToString( box.yMaximum(), mPrecision ) } );
}
featureJson[ QStringLiteral( "geometry" ) ] = QJsonDocument::fromJson( geom.asJson( mPrecision ).toLocal8Bit() ).object();
}
else
{
featureJson[ QStringLiteral( "geometry" ) ] = QJsonValue();
}

// build up properties element
int attributeCounter { 0 };
QJsonObject properties;
if ( mIncludeAttributes || !extraProperties.isEmpty() )
{
//read all attribute values from the feature
if ( mIncludeAttributes )
{
QgsFields fields = mLayer ? mLayer->fields() : feature.fields();
// List of formatters through we want to pass the values
QStringList formattersWhiteList;
formattersWhiteList << QStringLiteral( "KeyValue" )
<< QStringLiteral( "List" )
<< QStringLiteral( "ValueRelation" )
<< QStringLiteral( "ValueMap" );

for ( int i = 0; i < fields.count(); ++i )
{
if ( ( !mAttributeIndexes.isEmpty() && !mAttributeIndexes.contains( i ) ) || mExcludedAttributeIndexes.contains( i ) )
continue;

QVariant val = feature.attributes().at( i );

if ( mLayer )
{
QgsEditorWidgetSetup setup = fields.at( i ).editorWidgetSetup();
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
if ( formattersWhiteList.contains( fieldFormatter->id() ) )
val = fieldFormatter->representValue( mLayer.data(), i, setup.config(), QVariant(), val );
}

QString name = fields.at( i ).name();
if ( mAttributeDisplayName )
{
name = mLayer->attributeDisplayName( i );
}
properties[ name ] = QJsonValue::fromVariant( val );
attributeCounter++;
}
}

if ( !extraProperties.isEmpty() )
{
QVariantMap::const_iterator it = extraProperties.constBegin();
for ( ; it != extraProperties.constEnd(); ++it )
{
properties[ it.key() ] = QJsonValue::fromVariant( it.value() );
attributeCounter++;
}
}

// related attributes
if ( mLayer && mIncludeRelatedAttributes )
{
QList< QgsRelation > relations = QgsProject::instance()->relationManager()->referencedRelations( mLayer.data() );
for ( const auto &relation : qgis::as_const( relations ) )
{
QgsFeatureRequest req = relation.getRelatedFeaturesRequest( feature );
req.setFlags( QgsFeatureRequest::NoGeometry );
QgsVectorLayer *childLayer = relation.referencingLayer();
QJsonArray relatedFeatureAttributes;
if ( childLayer )
{
QgsFeatureIterator it = childLayer->getFeatures( req );
QVector<QVariant> attributeWidgetCaches;
int fieldIndex = 0;
const QgsFields fields { childLayer->fields() };
for ( const QgsField &field : fields )
{
QgsEditorWidgetSetup setup = field.editorWidgetSetup();
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
attributeWidgetCaches.append( fieldFormatter->createCache( childLayer, fieldIndex, setup.config() ) );
fieldIndex++;
}
QgsFeature relatedFet;
while ( it.nextFeature( relatedFet ) )
{
relatedFeatureAttributes += QgsJsonUtils::exportAttributes( relatedFet, childLayer, attributeWidgetCaches );
}
}
properties[ relation.name() ] = relatedFeatureAttributes;
attributeCounter++;
}
}
}
// bool hasProperties = attributeCounter > 0;
featureJson[ QStringLiteral( "properties" ) ] = properties;
return featureJson;
}

QString QgsJsonExporter::exportFeatures( const QgsFeatureList &features ) const
{
QStringList featureJSON;
Expand All @@ -244,7 +374,6 @@ QString QgsJsonExporter::exportFeatures( const QgsFeatureList &features ) const
{
featureJSON << exportFeature( feature );
}

return QStringLiteral( "{ \"type\": \"FeatureCollection\",\n \"features\":[\n%1\n]}" ).arg( featureJSON.join( QStringLiteral( ",\n" ) ) );
}

Expand Down Expand Up @@ -351,3 +480,25 @@ QVariantList QgsJsonUtils::parseArray( const QString &json, QVariant::Type type
}
return result;
}


QJsonObject QgsJsonUtils::exportAttributesV2( const QgsFeature &feature, QgsVectorLayer *layer, const QVector<QVariant> &attributeWidgetCaches )
{
QgsFields fields = feature.fields();
QJsonObject attrs;
for ( int i = 0; i < fields.count(); ++i )
{
QVariant val = feature.attributes().at( i );

if ( layer )
{
QgsEditorWidgetSetup setup = layer->fields().at( i ).editorWidgetSetup();
QgsFieldFormatter *fieldFormatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
if ( fieldFormatter != QgsApplication::fieldFormatterRegistry()->fallbackFieldFormatter() )
val = fieldFormatter->representValue( layer, i, setup.config(), attributeWidgetCaches.count() >= i ? attributeWidgetCaches.at( i ) : QVariant(), val );
}

attrs.insert( fields.at( i ).name(), QJsonValue::fromVariant( val ) );
}
return attrs;
}
26 changes: 26 additions & 0 deletions src/core/qgsjsonutils.h
Expand Up @@ -23,6 +23,7 @@
#include "qgsfields.h"

#include <QPointer>
#include <QJsonValue>

class QTextCodec;

Expand Down Expand Up @@ -198,6 +199,19 @@ class CORE_EXPORT QgsJsonExporter
const QVariantMap &extraProperties = QVariantMap(),
const QVariant &id = QVariant() ) const;

/**
* Returns a GeoJSON string representation of a feature.
* \param feature feature to convert
* \param extraProperties map of extra attributes to include in feature's properties
* \param id optional ID to use as GeoJSON feature's ID instead of input feature's ID. If omitted, feature's
* ID is used.
* \returns GeoJSON string
* \see exportFeatures()
*/
QJsonObject exportFeatureV2( const QgsFeature &feature,
const QVariantMap &extraProperties = QVariantMap(),
const QVariant &id = QVariant() ) const;


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

/**
* Exports all attributes from a QgsFeature as a JSON map type.
* \param feature feature to export
* \param layer optional associated vector layer. If specified, this allows
* richer export utilising settings like the layer's fields widget configuration.
* \param attributeWidgetCaches optional widget configuration cache. Can be used
* to speed up exporting the attributes for multiple features from the same layer.
*/
static QJsonObject exportAttributesV2( const QgsFeature &feature, QgsVectorLayer *layer = nullptr,
const QVector<QVariant> &attributeWidgetCaches = QVector<QVariant>() );


/**
* Parse a simple array (depth=1).
* \param json the JSON to parse
Expand Down
83 changes: 83 additions & 0 deletions tests/src/core/testqgsjsonutils.cpp
Expand Up @@ -15,6 +15,9 @@

#include "qgstest.h"
#include <qgsjsonutils.h>
#include "qgsvectorlayer.h"
#include "qgsfeature.h"


class TestQgsJsonUtils : public QObject
{
Expand Down Expand Up @@ -71,6 +74,86 @@ class TestQgsJsonUtils : public QObject
QCOMPARE( back, list );
QCOMPARE( back.at( 0 ).type(), QVariant::Double );
}

void testV2ExportAttributes_data()
{
QTest::addColumn<bool>( "useQJson" );
QTest::newRow( "Use V2 (QJson)" ) << true;
QTest::newRow( "Use old string concat" ) << false;
}

void testV2ExportAttributes()
{

QFETCH( bool, useQJson );

QgsVectorLayer vl { QStringLiteral( "Point?field=fldtxt:string&field=fldint:integer&field=flddbl:double" ), QStringLiteral( "mem" ), QStringLiteral( "memory" ) };
QgsFeature feature { vl.fields() };
feature.setAttributes( QgsAttributes() << QStringLiteral( "a value" ) << 1 << 2.0 );

if ( useQJson ) // average: 0.0048 msecs per iteration
{
QBENCHMARK
{
const auto json { QgsJsonUtils::exportAttributesV2( feature, &vl ) };
QCOMPARE( QJsonDocument( json ).toJson( QJsonDocument::JsonFormat::Compact ), QStringLiteral( "{\"flddbl\":2,\"fldint\":1,\"fldtxt\":\"a value\"}" ) );
}
}
else // average: 0.0070 msecs per iteration
{
QBENCHMARK
{
const auto json { QgsJsonUtils::exportAttributes( feature, &vl ) };
QCOMPARE( json, QStringLiteral( "{\"fldtxt\":\"a value\",\n\"fldint\":1,\n\"flddbl\":2}" ) );
}
}
}

void testV2ExportFeature_data()
{
QTest::addColumn<bool>( "useQJson" );
QTest::newRow( "Use V2 (QJson)" ) << true;
QTest::newRow( "Use old string concat" ) << false;
}

void testV2ExportFeature()
{

QFETCH( bool, useQJson );

QgsVectorLayer vl { QStringLiteral( "Polygon?field=fldtxt:string&field=fldint:integer&field=flddbl:double" ), QStringLiteral( "mem" ), QStringLiteral( "memory" ) };
QgsFeature feature { vl.fields() };
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))" ) ) );
feature.setAttributes( QgsAttributes() << QStringLiteral( "a value" ) << 1 << 2.0 );

QgsJsonExporter exporter { &vl };

if ( useQJson ) // average: 0.063 msecs per iteration
{
QBENCHMARK
{
const auto json { exporter.exportFeatureV2( feature ) };
QCOMPARE( QJsonDocument( json ).toJson( QJsonDocument::JsonFormat::Compact ),
QStringLiteral( "{\"bbox\":[\"1\",\"1\",\"5\",\"5\"],\"geometry\":{\"coordinates\""
":[[[1,1],[5,1],[5,5],[1,5],[1,1]],[[2,2],[3,2],[3,3],[2,3],[2,2]]],"
"\"type\":\"Polygon\"},\"id\":0,\"properties\":{\"flddbl\":2,\"fldint\":"
"1,\"fldtxt\":\"a value\"},\"type\":\"Feature\"}"
) );
}
}
else // average: 0.047 msecs per iteration
{
QBENCHMARK
{
const auto json { exporter.exportFeature( feature ) };
QCOMPARE( json, QStringLiteral( "{\n \"type\":\"Feature\",\n \"id\":0,\n \"bbox\":[1, 1, 5, 5],\n "
"\"geometry\":\n {\"type\": \"Polygon\", "
"\"coordinates\": [[ [1, 1], [5, 1], [5, 5], [1, 5], [1, 1]], [ [2, 2], "
"[3, 2], [3, 3], [2, 3], [2, 2]]] },\n "
"\"properties\":{\n \"fldtxt\":\"a value\",\n \"fldint\":1,\n \"flddbl\":2\n }\n}" ) );
}
}
}
};

QGSTEST_MAIN( TestQgsJsonUtils )
Expand Down

0 comments on commit 7222828

Please sign in to comment.