Skip to content

Commit

Permalink
Always export GeoJSON features in WGS84 (match specifications)
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed May 9, 2016
1 parent 55793a4 commit ca2c629
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 8 deletions.
24 changes: 23 additions & 1 deletion python/core/qgsjsonutils.sip
@@ -1,6 +1,9 @@
/** \ingroup core
* \class QgsJSONExporter
* \brief Handles exporting QgsFeature features to GeoJSON features.
*
* Note that geometries will be automatically reprojected to WGS84 to match GeoJSON spec
* if either the source vector layer or source CRS is set.
* \note Added in version 2.16
*/

Expand Down Expand Up @@ -63,7 +66,8 @@ class QgsJSONExporter
*/
bool includeRelated() const;

/** Sets the associated vector layer (required for related attribute export).
/** Sets the associated vector layer (required for related attribute export). This will automatically
* update the sourceCrs() to match.
* @param vectorLayer vector layer
* @see vectorLayer()
*/
Expand All @@ -74,6 +78,20 @@ class QgsJSONExporter
*/
QgsVectorLayer* vectorLayer() const;

/** Sets the source CRS for feature geometries. The source CRS must be set if geometries are to be
* correctly automatically reprojected to WGS 84, to match GeoJSON specifications.
* @param crs source CRS for input feature geometries
* @note the source CRS will be overwritten when a vector layer is specified via setVectorLayer()
* @see sourceCrs()
*/
void setSourceCrs( const QgsCoordinateReferenceSystem& crs );

/** Returns the source CRS for feature geometries. The source CRS must be set if geometries are to be
* correctly automatically reprojected to WGS 84, to match GeoJSON specifications.
* @see setSourceCrs()
*/
const QgsCoordinateReferenceSystem& sourceCrs() 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
Expand Down Expand Up @@ -128,6 +146,10 @@ class QgsJSONExporter
*/
QString exportFeatures( const QgsFeatureList& features ) const;

private:

QgsJSONExporter( const QgsJSONExporter& );

};


Expand Down
41 changes: 37 additions & 4 deletions src/core/qgsjsonutils.cpp
Expand Up @@ -29,19 +29,40 @@ QgsJSONExporter::QgsJSONExporter( const QgsVectorLayer* vectorLayer, int precisi
, mIncludeRelatedAttributes( false )
, mLayerId( vectorLayer ? vectorLayer->id() : QString() )
{

if ( vectorLayer )
{
mCrs = vectorLayer->crs();
mTransform.setSourceCrs( mCrs );
}
mTransform.setDestCRS( QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem::EpsgCrsId ) );
}

void QgsJSONExporter::setVectorLayer( const QgsVectorLayer* vectorLayer )
{
mLayerId = vectorLayer ? vectorLayer->id() : QString();
if ( vectorLayer )
{
mCrs = vectorLayer->crs();
mTransform.setSourceCrs( mCrs );
}
}

QgsVectorLayer *QgsJSONExporter::vectorLayer() const
{
return qobject_cast< QgsVectorLayer* >( QgsMapLayerRegistry::instance()->mapLayer( mLayerId ) );
}

void QgsJSONExporter::setSourceCrs( const QgsCoordinateReferenceSystem& crs )
{
mCrs = crs;
mTransform.setSourceCrs( mCrs );
}

const QgsCoordinateReferenceSystem& QgsJSONExporter::sourceCrs() const
{
return mCrs;
}

QString QgsJSONExporter::exportFeature( const QgsFeature& feature, const QVariantMap& extraProperties,
const QVariant& id ) const
{
Expand All @@ -53,18 +74,30 @@ QString QgsJSONExporter::exportFeature( const QgsFeature& feature, const QVarian
const QgsGeometry* geom = feature.constGeometry();
if ( geom && !geom->isEmpty() && mIncludeGeometry )
{
QgsRectangle box = geom->boundingBox();
const QgsGeometry* exportGeom = geom;
if ( mCrs.isValid() )
{
QgsGeometry* clone = new QgsGeometry( *geom );
if ( clone->transform( mTransform ) == 0 )
exportGeom = clone;
else
delete clone;
}
QgsRectangle box = exportGeom->boundingBox();

if ( QgsWKBTypes::flatType( geom->geometry()->wkbType() ) != QgsWKBTypes::Point )
if ( QgsWKBTypes::flatType( exportGeom->geometry()->wkbType() ) != QgsWKBTypes::Point )
{
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( mPrecision );
s += exportGeom->exportToGeoJSON( mPrecision );
s += ",\n";

if ( exportGeom != geom )
delete exportGeom;
}
else
{
Expand Down
27 changes: 25 additions & 2 deletions src/core/qgsjsonutils.h
Expand Up @@ -17,13 +17,18 @@
#define QGSJSONUTILS_H

#include "qgsfeature.h"
#include "qgscoordinatereferencesystem.h"
#include "qgscoordinatetransform.h"

class QTextCodec;
class QgsVectorLayer;

/** \ingroup core
* \class QgsJSONExporter
* \brief Handles exporting QgsFeature features to GeoJSON features.
*
* Note that geometries will be automatically reprojected to WGS84 to match GeoJSON spec
* if either the source vector layer or source CRS is set.
* \note Added in version 2.16
*/

Expand Down Expand Up @@ -83,7 +88,8 @@ class CORE_EXPORT QgsJSONExporter
*/
bool includeRelated() const { return mIncludeRelatedAttributes; }

/** Sets the associated vector layer (required for related attribute export).
/** Sets the associated vector layer (required for related attribute export). This will automatically
* update the sourceCrs() to match.
* @param vectorLayer vector layer
* @see vectorLayer()
*/
Expand All @@ -94,6 +100,20 @@ class CORE_EXPORT QgsJSONExporter
*/
QgsVectorLayer* vectorLayer() const;

/** Sets the source CRS for feature geometries. The source CRS must be set if geometries are to be
* correctly automatically reprojected to WGS 84, to match GeoJSON specifications.
* @param crs source CRS for input feature geometries
* @note the source CRS will be overwritten when a vector layer is specified via setVectorLayer()
* @see sourceCrs()
*/
void setSourceCrs( const QgsCoordinateReferenceSystem& crs );

/** Returns the source CRS for feature geometries. The source CRS must be set if geometries are to be
* correctly automatically reprojected to WGS 84, to match GeoJSON specifications.
* @see setSourceCrs()
*/
const QgsCoordinateReferenceSystem& sourceCrs() 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
Expand Down Expand Up @@ -148,7 +168,6 @@ class CORE_EXPORT QgsJSONExporter
*/
QString exportFeatures( const QgsFeatureList& features ) const;


private:

//! Maximum number of decimal places for geometry coordinates
Expand All @@ -173,6 +192,10 @@ class CORE_EXPORT QgsJSONExporter
//! Layer ID of associated vector layer. Required for related attribute export.
QString mLayerId;

QgsCoordinateReferenceSystem mCrs;

QgsCoordinateTransform mTransform;

};

/** \ingroup core
Expand Down
56 changes: 55 additions & 1 deletion tests/src/python/test_qgsjsonutils.py
Expand Up @@ -15,7 +15,22 @@
import qgis # NOQA

from qgis.testing import unittest, start_app
from qgis.core import QgsJSONUtils, QgsJSONExporter, QgsProject, QgsMapLayerRegistry, QgsFeature, QgsField, QgsFields, QgsWKBTypes, QgsGeometry, QgsPointV2, QgsLineStringV2, NULL, QgsVectorLayer, QgsRelation
from qgis.core import (QgsJSONUtils,
QgsJSONExporter,
QgsCoordinateReferenceSystem,
QgsProject,
QgsMapLayerRegistry,
QgsFeature,
QgsField,
QgsFields,
QgsWKBTypes,
QgsGeometry,
QgsPointV2,
QgsLineStringV2,
NULL,
QgsVectorLayer,
QgsRelation
)
from qgis.PyQt.QtCore import QVariant, QTextCodec

start_app()
Expand Down Expand Up @@ -364,6 +379,45 @@ def testJSONExporter(self):
self.assertEqual(exporter.exportFeature(feature, extraProperties={"extra": "val1", "extra2": {"nested_map": 5, "nested_map2": "val"}, "extra3": [1, 2, 3]}), expected)
exporter.setIncludeGeometry(True)

def testExportFeatureCrs(self):
""" Test CRS transform when exporting features """

exporter = QgsJSONExporter()
self.assertFalse(exporter.sourceCrs().isValid())

#test layer
layer = QgsVectorLayer("Point?crs=epsg:3111&field=fldtxt:string",
"parent", "memory")
exporter = QgsJSONExporter(layer)
self.assertTrue(exporter.sourceCrs().isValid())
self.assertEqual(exporter.sourceCrs().authid(), 'EPSG:3111')

exporter.setSourceCrs(QgsCoordinateReferenceSystem(3857, QgsCoordinateReferenceSystem.EpsgCrsId))
self.assertTrue(exporter.sourceCrs().isValid())
self.assertEqual(exporter.sourceCrs().authid(), 'EPSG:3857')

# vector layer CRS should override
exporter.setVectorLayer(layer)
self.assertEqual(exporter.sourceCrs().authid(), 'EPSG:3111')

# test that exported feature is reprojected
feature = QgsFeature(layer.fields(), 5)
feature.setGeometry(QgsGeometry(QgsPointV2(2502577, 2403869)))
feature.setAttributes(['test point'])

# low precision, only need rough coordinate to check and don't want to deal with rounding errors
exporter.setPrecision(1)
expected = """{
"type":"Feature",
"id":5,
"geometry":
{"type": "Point", "coordinates": [145, -37.9]},
"properties":{
"fldtxt":"test point"
}
}"""
self.assertEqual(exporter.exportFeature(feature), expected)

def testExportFeatureRelations(self):
""" Test exporting a feature with relations """

Expand Down

0 comments on commit ca2c629

Please sign in to comment.