Refactor to move JSON exporter to its own class

This commit is contained in:
Nyall Dawson 2016-05-06 16:54:40 +10:00
parent 935f4ad21c
commit c3f6c39784
5 changed files with 237 additions and 58 deletions

View File

@ -1,3 +1,81 @@
/** \ingroup core
* \class QgsJSONExporter
* \brief Handles exporting QgsFeature features to GeoJSON features.
* \note Added in version 2.16
*/
class QgsJSONExporter
{
%TypeHeaderCode
#include <qgsjsonutils.h>
%End
public:
/** Constructor for QgsJSONExporter.
* @param precision maximum number of decimal places to use for geometry coordinates
* @param includeGeometry 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
*/
QgsJSONExporter( int precision = 17, bool includeGeometry = true, bool includeAttributes = true );
/** Sets the maximum number of decimal places to use in geometry coordinates.
* @param precision number of decimal places
* @see precision()
*/
void setPrecision( int precision );
/** Returns the maximum number of decimal places to use in geometry coordinates.
* @see setPrecision()
*/
int precision() const;
/** Sets whether to include geometry in the JSON exports.
* @param includeGeometry set to false to prevent geometry inclusion
* @see includeGeometry()
*/
void setIncludeGeometry( bool includeGeometry );
/** Returns whether geometry will be included in the JSON exports.
* @see setIncludeGeometry()
*/
bool includeGeometry() const;
/** Sets whether to include attributes in the JSON exports.
* @param includeAttributes set to false to prevent attribute inclusion
* @see includeAttributes()
*/
void setIncludeAttributes( bool includeAttributes );
/** Returns whether attributes will be included in the JSON exports.
* @see setIncludeAttributes()
*/
bool includeAttributes() const;
/** Sets the list of attributes to include in the JSON exports.
* @param attributes list of attribute indexes, or an empty list to include all
* attributes
* @see attributes()
*/
void setAttributes( const QgsAttributeList& attributes );
/** Returns the list of attributes which will be included in the JSON exports, or
* an empty list if all attributes will be included.
* @see setAttributes()
*/
QgsAttributeList attributes() const;
/** Returns a GeoJSON string representation of a feature.
* @param feature feature to convert
* @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
*/
QString exportFeature( const QgsFeature& feature,
const QVariant& id = QVariant() ) const;
};
/** \ingroup core
* \class QgsJSONUtils
* \brief Helper utilities for working with JSON and GeoJSON conversions.
@ -31,24 +109,6 @@ class QgsJSONUtils
*/
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

View File

@ -17,54 +17,48 @@
#include "qgsogrutils.h"
#include "qgsgeometry.h"
QgsFeatureList QgsJSONUtils::stringToFeatureList( const QString &string, const QgsFields &fields, QTextCodec *encoding )
QgsJSONExporter::QgsJSONExporter( int precision, bool includeGeometry, bool includeAttributes )
: mPrecision( precision )
, mIncludeGeometry( includeGeometry )
, mIncludeAttributes( includeAttributes )
{
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 QgsJSONExporter::exportFeature( const QgsFeature& feature, const QVariant& id ) const
{
QString s = "{\n \"type\":\"Feature\",\n";
// ID
s += QString( " \"id\":%1" ).arg( !id.isValid() ? QString::number( feature.id() ) : encodeValue( id ) );
s += QString( " \"id\":%1" ).arg( !id.isValid() ? QString::number( feature.id() ) : QgsJSONUtils::encodeValue( id ) );
if ( includeAttributes || includeGeom )
if ( mIncludeAttributes || mIncludeGeometry )
s += ",\n";
else
s += '\n';
const QgsGeometry* geom = feature.constGeometry();
if ( geom && !geom->isEmpty() && includeGeom )
if ( geom && !geom->isEmpty() && mIncludeGeometry )
{
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 += QString( " \"bbox\":[%1, %2, %3, %4],\n" ).arg( qgsDoubleToString( box.xMinimum(), mPrecision ),
qgsDoubleToString( box.yMinimum(), mPrecision ),
qgsDoubleToString( box.xMaximum(), mPrecision ),
qgsDoubleToString( box.yMaximum(), mPrecision ) );
}
s += " \"geometry\":\n ";
s += geom->exportToGeoJSON( precision );
if ( includeAttributes )
s += geom->exportToGeoJSON( mPrecision );
if ( mIncludeAttributes )
s += ",\n";
else
s += '\n';
}
if ( includeAttributes )
if ( mIncludeAttributes )
{
//read all attribute values from the feature
s += " \"properties\":{\n";
@ -74,14 +68,14 @@ QString QgsJSONUtils::featureToGeoJSON( const QgsFeature& feature,
for ( int i = 0; i < fields->count(); ++i )
{
if ( !attrIndexes.isEmpty() && !attrIndexes.contains( i ) )
if ( !mAttributeIndexes.isEmpty() && !mAttributeIndexes.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 ) );
s += QString( " \"%1\":%2" ).arg( fields->at( i ).name(), QgsJSONUtils::encodeValue( val ) );
++attributeCounter;
}
@ -94,6 +88,21 @@ QString QgsJSONUtils::featureToGeoJSON( const QgsFeature& feature,
return s;
}
//
// QgsJSONUtils
//
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::encodeValue( const QVariant &value )
{
if ( value.isNull() )

View File

@ -20,6 +20,94 @@
class QTextCodec;
/** \ingroup core
* \class QgsJSONExporter
* \brief Handles exporting QgsFeature features to GeoJSON features.
* \note Added in version 2.16
*/
class CORE_EXPORT QgsJSONExporter
{
public:
/** Constructor for QgsJSONExporter.
* @param precision maximum number of decimal places to use for geometry coordinates
* @param includeGeometry 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
*/
QgsJSONExporter( int precision = 17, bool includeGeometry = true, bool includeAttributes = true );
/** Sets the maximum number of decimal places to use in geometry coordinates.
* @param precision number of decimal places
* @see precision()
*/
void setPrecision( int precision ) { mPrecision = precision; }
/** Returns the maximum number of decimal places to use in geometry coordinates.
* @see setPrecision()
*/
int precision() const { return mPrecision; }
/** Sets whether to include geometry in the JSON exports.
* @param includeGeometry set to false to prevent geometry inclusion
* @see includeGeometry()
*/
void setIncludeGeometry( bool includeGeometry ) { mIncludeGeometry = includeGeometry; }
/** Returns whether geometry will be included in the JSON exports.
* @see setIncludeGeometry()
*/
bool includeGeometry() const { return mIncludeGeometry; }
/** Sets whether to include attributes in the JSON exports.
* @param includeAttributes set to false to prevent attribute inclusion
* @see includeAttributes()
*/
void setIncludeAttributes( bool includeAttributes ) { mIncludeAttributes = includeAttributes; }
/** Returns whether attributes will be included in the JSON exports.
* @see setIncludeAttributes()
*/
bool includeAttributes() const { return mIncludeAttributes; }
/** Sets the list of attributes to include in the JSON exports.
* @param attributes list of attribute indexes, or an empty list to include all
* attributes
* @see attributes()
*/
void setAttributes( const QgsAttributeList& attributes ) { mAttributeIndexes = attributes; }
/** Returns the list of attributes which will be included in the JSON exports, or
* an empty list if all attributes will be included.
* @see setAttributes()
*/
QgsAttributeList attributes() const { return mAttributeIndexes; }
/** Returns a GeoJSON string representation of a feature.
* @param feature feature to convert
* @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
*/
QString exportFeature( const QgsFeature& feature,
const QVariant& id = QVariant() ) const;
private:
//! Maximum number of decimal places for geometry coordinates
int mPrecision;
//! List of attribute indexes to include in export, or empty list to include all attributes
QgsAttributeList mAttributeIndexes;
//! Whether to include geometry in JSON export
bool mIncludeGeometry;
//! Whether to include attributes in JSON export
bool mIncludeAttributes;
};
/** \ingroup core
* \class QgsJSONUtils
* \brief Helper utilities for working with JSON and GeoJSON conversions.

View File

@ -1868,13 +1868,16 @@ QString QgsWFSServer::createFeatureGeoJSON( QgsFeature* feat, int prec, QgsCoord
{
QString id = QString( "%1.%2" ).arg( mTypeName, FID_TO_STRING( feat->id() ) );
QgsJSONExporter exporter;
exporter.setPrecision( prec );
//copy feature so we can modify its geometry as required
QgsFeature f( *feat );
const QgsGeometry* geom = feat->constGeometry();
bool withGeom = false;
exporter.setIncludeGeometry( false );
if ( geom && mWithGeom && mGeometryName != "NONE" )
{
withGeom = true;
exporter.setIncludeGeometry( true );
if ( mGeometryName == "EXTENT" )
{
QgsRectangle box = geom->boundingBox();
@ -1907,9 +1910,10 @@ QString QgsWFSServer::createFeatureGeoJSON( QgsFeature* feat, int prec, QgsCoord
attrsToExport << idx;
}
bool withAttributes = !attrsToExport.isEmpty();
exporter.setIncludeAttributes( !attrsToExport.isEmpty() );
exporter.setAttributes( attrsToExport );
return QgsJSONUtils::featureToGeoJSON( f, prec, attrsToExport, withGeom, withAttributes, id );
return exporter.exportFeature( f, id );
}
QDomElement QgsWFSServer::createFeatureGML2( QgsFeature* feat, QDomDocument& doc, int prec, QgsCoordinateReferenceSystem& crs, const QgsAttributeList& attrIndexes, const QSet<QString>& excludedAttributes ) /*const*/

View File

@ -15,7 +15,7 @@ __revision__ = '$Format:%H$'
import qgis # NOQA
from qgis.testing import unittest, start_app
from qgis.core import QgsJSONUtils, QgsFeature, QgsField, QgsFields, QgsWKBTypes, QgsGeometry, QgsPointV2, QgsLineStringV2, NULL
from qgis.core import QgsJSONUtils, QgsJSONExporter, QgsFeature, QgsField, QgsFields, QgsWKBTypes, QgsGeometry, QgsPointV2, QgsLineStringV2, NULL
from qgis.PyQt.QtCore import QVariant, QTextCodec
start_app()
@ -105,7 +105,7 @@ class TestQgsJSONUtils(unittest.TestCase):
self.assertEqual(QgsJSONUtils.encodeValue({'key': 'value', 'key2': 5}), '{"key":"value",\n"key2":5}')
self.assertEqual(QgsJSONUtils.encodeValue({'key': [1, 2, 3], 'key2': {'nested': 'nested\\result'}}), '{"key":[1,2,3],\n"key2":{"nested":"nested\\\\result"}}')
def testFeatureToGeoJSON(self):
def testJSONExporter(self):
""" test converting features to GeoJSON """
fields = QgsFields()
fields.append(QgsField("name", QVariant.String))
@ -116,6 +116,8 @@ class TestQgsJSONUtils(unittest.TestCase):
feature.setGeometry(QgsGeometry(QgsPointV2(5, 6)))
feature.setAttributes(['Valsier Peninsula', 6.8, 198])
exporter = QgsJSONExporter()
expected = """{
"type":"Feature",
"id":5,
@ -127,7 +129,7 @@ class TestQgsJSONUtils(unittest.TestCase):
"population":198
}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
# test with linestring for bbox inclusion
l = QgsLineStringV2()
@ -146,10 +148,12 @@ class TestQgsJSONUtils(unittest.TestCase):
"population":198
}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
# test that precision is respected
feature.setGeometry(QgsGeometry(QgsPointV2(5.444444444, 6.333333333)))
exporter.setPrecision(3)
self.assertEqual(exporter.precision(), 3)
expected = """{
"type":"Feature",
"id":5,
@ -161,10 +165,13 @@ class TestQgsJSONUtils(unittest.TestCase):
"population":198
}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature, 3), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
feature.setGeometry(QgsGeometry(QgsPointV2(5, 6)))
exporter.setPrecision(17)
# test that attribute subset is respected
exporter.setAttributes([0, 2])
self.assertEqual(exporter.attributes(), [0, 2])
expected = """{
"type":"Feature",
"id":5,
@ -175,8 +182,10 @@ class TestQgsJSONUtils(unittest.TestCase):
"population":198
}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature, attrIndexes=[0, 2]), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
exporter.setAttributes([1])
self.assertEqual(exporter.attributes(), [1])
expected = """{
"type":"Feature",
"id":5,
@ -186,9 +195,12 @@ class TestQgsJSONUtils(unittest.TestCase):
"cost":6.8
}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature, attrIndexes=[1]), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
exporter.setAttributes([])
# test excluding geometry
exporter.setIncludeGeometry(False)
self.assertEqual(exporter.includeGeometry(), False)
feature.setGeometry(QgsGeometry(QgsLineStringV2(l)))
expected = """{
@ -200,23 +212,29 @@ class TestQgsJSONUtils(unittest.TestCase):
"population":198
}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature, includeGeom=False), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
exporter.setIncludeGeometry(True)
feature.setGeometry(QgsGeometry(QgsPointV2(5, 6)))
# test excluding attributes
exporter.setIncludeAttributes(False)
self.assertEqual(exporter.includeAttributes(), False)
expected = """{
"type":"Feature",
"id":5,
"geometry":
{"type": "Point", "coordinates": [5, 6]}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature, includeAttributes=False), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
exporter.setIncludeGeometry(False)
expected = """{
"type":"Feature",
"id":5
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature, includeGeom=False, includeAttributes=False), expected)
self.assertEqual(exporter.exportFeature(feature), expected)
exporter.setIncludeAttributes(True)
# test overriding ID
expected = """{
@ -228,7 +246,7 @@ class TestQgsJSONUtils(unittest.TestCase):
"population":198
}
}"""
self.assertEqual(QgsJSONUtils.featureToGeoJSON(feature, includeGeom=False, id=29), expected)
self.assertEqual(exporter.exportFeature(feature, id=29), expected)
if __name__ == "__main__":
unittest.main()