Skip to content
Permalink
Browse files

Always export GeoJSON features in WGS84 (match specifications)

  • Loading branch information
nyalldawson committed May 7, 2016
1 parent 55793a4 commit ca2c6290b127a00926ca05df718304aeba2ce0ad
Showing with 140 additions and 8 deletions.
  1. +23 −1 python/core/qgsjsonutils.sip
  2. +37 −4 src/core/qgsjsonutils.cpp
  3. +25 −2 src/core/qgsjsonutils.h
  4. +55 −1 tests/src/python/test_qgsjsonutils.py
@@ -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
*/

@@ -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()
*/
@@ -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
@@ -128,6 +146,10 @@ class QgsJSONExporter
*/
QString exportFeatures( const QgsFeatureList& features ) const;

private:

QgsJSONExporter( const QgsJSONExporter& );

};


@@ -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
{
@@ -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
{
@@ -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
*/

@@ -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()
*/
@@ -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
@@ -148,7 +168,6 @@ class CORE_EXPORT QgsJSONExporter
*/
QString exportFeatures( const QgsFeatureList& features ) const;


private:

//! Maximum number of decimal places for geometry coordinates
@@ -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
@@ -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()
@@ -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 """

0 comments on commit ca2c629

Please sign in to comment.
You can’t perform that action at this time.