Skip to content

Commit

Permalink
[FEATURE] New class QgsJSONUtils with utilities for converting
Browse files Browse the repository at this point in the history
features to and from GeoJSON strings

Sponsored by Kanton of Zug, Switzerland
  • Loading branch information
nyalldawson committed May 9, 2016
1 parent 8d70a51 commit 24309df
Show file tree
Hide file tree
Showing 8 changed files with 498 additions and 47 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -57,6 +57,7 @@
%Include qgsgml.sip
%Include qgsgmlschema.sip
%Include qgshistogram.sip
%Include qgsjsonutils.sip
%Include qgsmaptopixelgeometrysimplifier.sip
%Include qgstransaction.sip
%Include qgstransactiongroup.sip
Expand Down
58 changes: 58 additions & 0 deletions python/core/qgsjsonutils.sip
@@ -0,0 +1,58 @@
/** \ingroup core
* \class QgsJSONUtils
* \brief Helper utilities for working with JSON and GeoJSON conversions.
* \note Added in version 2.16
*/

class QgsJSONUtils
{
%TypeHeaderCode
#include <qgsjsonutils.h>
%End

public:

/** Attempts to parse a GeoJSON string to a collection of features.
* @param string GeoJSON string to parse
* @param fields fields collection to use for parsed features
* @param encoding text encoding
* @returns list of parsed features, or an empty list if no features could be parsed
* @see stringToFields()
* @note this function is a wrapper around QgsOgrUtils::stringToFeatureList()
*/
static QgsFeatureList stringToFeatureList( const QString& string, const QgsFields& fields, QTextCodec* encoding );

/** Attempts to retrieve the fields from a GeoJSON string representing a collection of features.
* @param string GeoJSON string to parse
* @param encoding text encoding
* @returns retrieved fields collection, or an empty list if no fields could be determined from the string
* @see stringToFeatureList()
* @note this function is a wrapper around QgsOgrUtils::stringToFields()
*/
static QgsFields stringToFields( const QString& string, QTextCodec* encoding );

/** Returns a GeoJSON string representation of a feature.
* @param feature feature to convert
* @param precision maximum number of decimal places to use for geometry coordinates
* @param attrIndexes list of attribute indexes to include in GeoJSON, or an empty list to include
* all attributes
* @param includeGeom set to false to avoid including the geometry representation in the JSON output
* @param includeAttributes set to false to avoid including any attribute values in the JSON output
* @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
*/
static QString featureToGeoJSON( const QgsFeature& feature,
int precision = 17,
const QgsAttributeList& attrIndexes = QgsAttributeList(),
bool includeGeom = true,
bool includeAttributes = true,
const QVariant& id = QVariant() );

/** Encodes a value to a JSON string representation, adding appropriate quotations and escaping
* where required.
* @param value value to encode
* @returns encoded value
*/
static QString encodeValue( const QVariant& value );
};
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -121,6 +121,7 @@ SET(QGIS_CORE_SRCS
qgsgml.cpp
qgsgmlschema.cpp
qgshistogram.cpp
qgsjsonutils.cpp
qgslabel.cpp
qgslabelattributes.cpp
qgslabelfeature.cpp
Expand Down Expand Up @@ -636,6 +637,7 @@ SET(QGIS_CORE_HDRS
qgsgeometrycache.h
qgshistogram.h
qgsindexedfeature.h
qgsjsonutils.h
qgslayerdefinition.h
qgslabel.h
qgslabelattributes.h
Expand Down
121 changes: 121 additions & 0 deletions src/core/qgsjsonutils.cpp
@@ -0,0 +1,121 @@
/***************************************************************************
qgsjsonutils.h
-------------
Date : May 206
Copyright : (C) 2016 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#include "qgsjsonutils.h"
#include "qgsogrutils.h"
#include "qgsgeometry.h"

QgsFeatureList QgsJSONUtils::stringToFeatureList( const QString &string, const QgsFields &fields, QTextCodec *encoding )
{
return QgsOgrUtils::stringToFeatureList( string, fields, encoding );
}

QgsFields QgsJSONUtils::stringToFields( const QString &string, QTextCodec *encoding )
{
return QgsOgrUtils::stringToFields( string, encoding );
}

QString QgsJSONUtils::featureToGeoJSON( const QgsFeature& feature,
int precision,
const QgsAttributeList& attrIndexes,
bool includeGeom,
bool includeAttributes,
const QVariant& id )
{
QString s = "{\n \"type\":\"Feature\",\n";

// ID
s += QString( " \"id\":%1" ).arg( !id.isValid() ? QString::number( feature.id() ) : encodeValue( id ) );

if ( includeAttributes || includeGeom )
s += ",\n";
else
s += '\n';

const QgsGeometry* geom = feature.constGeometry();
if ( geom && !geom->isEmpty() && includeGeom )
{
QgsRectangle box = geom->boundingBox();

if ( QgsWKBTypes::flatType( geom->geometry()->wkbType() ) != QgsWKBTypes::Point )
{
s += QString( " \"bbox\":[%1, %2, %3, %4],\n" ).arg( qgsDoubleToString( box.xMinimum(), precision ),
qgsDoubleToString( box.yMinimum(), precision ),
qgsDoubleToString( box.xMaximum(), precision ),
qgsDoubleToString( box.yMaximum(), precision ) );
}
s += " \"geometry\":\n ";
s += geom->exportToGeoJSON( precision );
if ( includeAttributes )
s += ",\n";
else
s += '\n';
}

if ( includeAttributes )
{
//read all attribute values from the feature
s += " \"properties\":{\n";

const QgsFields* fields = feature.fields();
int attributeCounter = 0;

for ( int i = 0; i < fields->count(); ++i )
{
if ( !attrIndexes.isEmpty() && !attrIndexes.contains( i ) )
continue;

if ( attributeCounter > 0 )
s += ",\n";
QVariant val = feature.attributes().at( i );

s += QString( " \"%1\":%2" ).arg( fields->at( i ).name(), encodeValue( val ) );

++attributeCounter;
}

s += "\n }\n";
}

s += "}";

return s;
}

QString QgsJSONUtils::encodeValue( const QVariant &value )
{
if ( value.isNull() )
return "null";

switch ( value.type() )
{
case QVariant::Int:
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
case QVariant::Double:
return value.toString();

case QVariant::Bool:
return value.toBool() ? "true" : "false";

default:
case QVariant::String:
QString v = value.toString().replace( '"', "\\\"" )
.replace( '\r', "\\r" )
.replace( '\n', "\\n" );
return v.prepend( '"' ).append( '"' );
}
}
79 changes: 79 additions & 0 deletions src/core/qgsjsonutils.h
@@ -0,0 +1,79 @@
/***************************************************************************
qgsjsonutils.h
-------------
Date : May 206
Copyright : (C) 2016 Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#ifndef QGSJSONUTILS_H
#define QGSJSONUTILS_H

#include "qgsfeature.h"

class QTextCodec;

/** \ingroup core
* \class QgsJSONUtils
* \brief Helper utilities for working with JSON and GeoJSON conversions.
* \note Added in version 2.16
*/

class CORE_EXPORT QgsJSONUtils
{
public:

/** Attempts to parse a GeoJSON string to a collection of features.
* @param string GeoJSON string to parse
* @param fields fields collection to use for parsed features
* @param encoding text encoding
* @returns list of parsed features, or an empty list if no features could be parsed
* @see stringToFields()
* @note this function is a wrapper around QgsOgrUtils::stringToFeatureList()
*/
static QgsFeatureList stringToFeatureList( const QString& string, const QgsFields& fields, QTextCodec* encoding );

/** Attempts to retrieve the fields from a GeoJSON string representing a collection of features.
* @param string GeoJSON string to parse
* @param encoding text encoding
* @returns retrieved fields collection, or an empty list if no fields could be determined from the string
* @see stringToFeatureList()
* @note this function is a wrapper around QgsOgrUtils::stringToFields()
*/
static QgsFields stringToFields( const QString& string, QTextCodec* encoding );

/** Returns a GeoJSON string representation of a feature.
* @param feature feature to convert
* @param precision maximum number of decimal places to use for geometry coordinates
* @param attrIndexes list of attribute indexes to include in GeoJSON, or an empty list to include
* all attributes
* @param includeGeom set to false to avoid including the geometry representation in the JSON output
* @param includeAttributes set to false to avoid including any attribute values in the JSON output
* @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
*/
static QString featureToGeoJSON( const QgsFeature& feature,
int precision = 17,
const QgsAttributeList& attrIndexes = QgsAttributeList(),
bool includeGeom = true,
bool includeAttributes = true,
const QVariant& id = QVariant() );

/** Encodes a value to a JSON string representation, adding appropriate quotations and escaping
* where required.
* @param value value to encode
* @returns encoded value
*/
static QString encodeValue( const QVariant& value );

};

#endif // QGSJSONUTILS_H
60 changes: 13 additions & 47 deletions src/server/qgswfsserver.cpp
Expand Up @@ -38,6 +38,7 @@
#include "qgsrequesthandler.h"
#include "qgsogcutils.h"
#include "qgsaccesscontrol.h"
#include "qgsjsonutils.h"

#include <QImage>
#include <QPainter>
Expand Down Expand Up @@ -1865,42 +1866,30 @@ QgsFeatureIds QgsWFSServer::getFeatureIdsFromFilter( const QDomElement& filterEl

QString QgsWFSServer::createFeatureGeoJSON( QgsFeature* feat, int prec, QgsCoordinateReferenceSystem &, const QgsAttributeList& attrIndexes, const QSet<QString>& excludedAttributes ) /*const*/
{
QString fStr = "{\"type\": \"Feature\",\n";

fStr += " \"id\": ";
fStr += "\"" + mTypeName + "." + QString::number( feat->id() ) + "\"";
fStr += ",\n";
QString id = QString( "%1.%2" ).arg( mTypeName, FID_TO_STRING( feat->id() ) );

//copy feature so we can modify its geometry as required
QgsFeature f( *feat );
const QgsGeometry* geom = feat->constGeometry();
bool withGeom = false;
if ( geom && mWithGeom && mGeometryName != "NONE" )
{
QgsRectangle box = geom->boundingBox();

fStr += " \"bbox\": [ " + qgsDoubleToString( box.xMinimum(), prec ) + ", " + qgsDoubleToString( box.yMinimum(), prec ) + ", " + qgsDoubleToString( box.xMaximum(), prec ) + ", " + qgsDoubleToString( box.yMaximum(), prec ) + "],\n";

fStr += " \"geometry\": ";
withGeom = true;
if ( mGeometryName == "EXTENT" )
{
QgsRectangle box = geom->boundingBox();
QgsGeometry* bbox = QgsGeometry::fromRect( box );
fStr += bbox->exportToGeoJSON( prec );
delete bbox;
f.setGeometry( bbox );
}
else if ( mGeometryName == "CENTROID" )
{
QgsGeometry* centroid = geom->centroid();
fStr += centroid->exportToGeoJSON( prec );
delete centroid;
f.setGeometry( centroid );
}
else
fStr += geom->exportToGeoJSON( prec );
fStr += ",\n";
}

//read all attribute values from the feature
fStr += " \"properties\": {\n";
QgsAttributes featureAttributes = feat->attributes();
const QgsFields* fields = feat->fields();
int attributeCounter = 0;
QgsAttributeList attrsToExport;
for ( int i = 0; i < attrIndexes.count(); ++i )
{
int idx = attrIndexes[i];
Expand All @@ -1914,36 +1903,13 @@ QString QgsWFSServer::createFeatureGeoJSON( QgsFeature* feat, int prec, QgsCoord
{
continue;
}
QVariant val = featureAttributes[idx];

if ( attributeCounter == 0 )
fStr += " \"";
else
fStr += " ,\"";
fStr += attributeName;
fStr += "\": ";
if ( val.type() == 6 || val.type() == 2 )
{
fStr += val.toString();
}
else
{
fStr += "\"";
fStr += val.toString()
.replace( '"', "\\\"" )
.replace( '\r', "\\r" )
.replace( '\n', "\\n" );
fStr += "\"";
}
fStr += "\n";
++attributeCounter;
attrsToExport << idx;
}

fStr += " }\n";

fStr += " }";
bool withAttributes = !attrsToExport.isEmpty();

return fStr;
return QgsJSONUtils::featureToGeoJSON( f, prec, attrsToExport, withGeom, withAttributes, id );
}

QDomElement QgsWFSServer::createFeatureGML2( QgsFeature* feat, QDomDocument& doc, int prec, QgsCoordinateReferenceSystem& crs, const QgsAttributeList& attrIndexes, const QSet<QString>& excludedAttributes ) /*const*/
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -41,6 +41,7 @@ ADD_PYTHON_TEST(PyQgsGeometryAvoidIntersections test_qgsgeometry_avoid_intersect
ADD_PYTHON_TEST(PyQgsGeometryGeneratorSymbolLayerV2 test_qgsgeometrygeneratorsymbollayerv2.py)
ADD_PYTHON_TEST(PyQgsGeometryTest test_qgsgeometry.py)
ADD_PYTHON_TEST(PyQgsGraduatedSymbolRendererV2 test_qgsgraduatedsymbolrendererv2.py)
ADD_PYTHON_TEST(PyQgsJSONUtils test_qgsjsonutils.py)
ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py)
ADD_PYTHON_TEST(PyQgsMemoryProvider test_provider_memory.py)
ADD_PYTHON_TEST(PyQgsMultiEditToolButton test_qgsmultiedittoolbutton.py)
Expand Down

0 comments on commit 24309df

Please sign in to comment.