Always export GeoJSON features in WGS84 (match specifications)

This commit is contained in:
Nyall Dawson 2016-05-07 23:36:55 +10:00
parent 55793a4534
commit ca2c6290b1
4 changed files with 140 additions and 8 deletions

View File

@ -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& );
};

View File

@ -29,12 +29,22 @@ 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
@ -42,6 +52,17 @@ 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,9 +74,18 @@ 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 ),
@ -63,8 +93,11 @@ QString QgsJSONExporter::exportFeature( const QgsFeature& feature, const QVarian
qgsDoubleToString( box.yMaximum(), mPrecision ) );
}
s += " \"geometry\":\n ";
s += geom->exportToGeoJSON( mPrecision );
s += exportGeom->exportToGeoJSON( mPrecision );
s += ",\n";
if ( exportGeom != geom )
delete exportGeom;
}
else
{

View File

@ -17,6 +17,8 @@
#define QGSJSONUTILS_H
#include "qgsfeature.h"
#include "qgscoordinatereferencesystem.h"
#include "qgscoordinatetransform.h"
class QTextCodec;
class QgsVectorLayer;
@ -24,6 +26,9 @@ 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

View File

@ -15,7 +15,22 @@ __revision__ = '$Format:%H$'
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 @@ class TestQgsJSONUtils(unittest.TestCase):
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 """