Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Allow storage of QgsRemappingSinkDefinition in variants
  • Loading branch information
nyalldawson committed Apr 7, 2020
1 parent 93f714d commit 8c73c61
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 21 deletions.
52 changes: 46 additions & 6 deletions python/core/auto_generated/qgsremappingproxyfeaturesink.sip.in
Expand Up @@ -64,18 +64,32 @@ mapping or use of QgsExpression expressions to transform values to the destinati
.. seealso:: :py:func:`fieldMap`
%End

QgsCoordinateTransform transform() const;
QgsCoordinateReferenceSystem sourceCrs() const;
%Docstring
Returns the transform used for reprojecting incoming features to the sink's destination CRS.
Returns the source CRS used for reprojecting incoming features to the sink's destination CRS.

.. seealso:: :py:func:`setTransform`
.. seealso:: :py:func:`setSourceCrs`
%End

void setTransform( const QgsCoordinateTransform &transform );
void setSourceCrs( const QgsCoordinateReferenceSystem &source );
%Docstring
Sets the ``transform`` used for reprojecting incoming features to the sink's destination CRS.
Sets the ``source`` crs used for reprojecting incoming features to the sink's destination CRS.

.. seealso:: :py:func:`transform`
.. seealso:: :py:func:`sourceCrs`
%End

QgsCoordinateReferenceSystem destinationCrs() const;
%Docstring
Returns the destination CRS used for reprojecting incoming features to the sink's destination CRS.

.. seealso:: :py:func:`setDestinationCrs`
%End

void setDestinationCrs( const QgsCoordinateReferenceSystem &destination );
%Docstring
Sets the ``destination`` crs used for reprojecting incoming features to the sink's destination CRS.

.. seealso:: :py:func:`destinationCrs`
%End

QgsWkbTypes::Type destinationWkbType() const;
Expand Down Expand Up @@ -106,9 +120,30 @@ Sets the ``fields`` for the destination sink.
.. seealso:: :py:func:`destinationFields`
%End

QVariant toVariant() const;
%Docstring
Saves this remapping definition to a QVariantMap, wrapped in a QVariant.
You can use QgsXmlUtils.writeVariant to save it to an XML document.

.. seealso:: :py:func:`loadVariant`
%End

bool loadVariant( const QVariantMap &map );
%Docstring
Loads this remapping definition from a QVariantMap, wrapped in a QVariant.
You can use QgsXmlUtils.readVariant to load it from an XML document.

.. seealso:: :py:func:`toVariant`
%End

bool operator==( const QgsRemappingSinkDefinition &other ) const;
bool operator!=( const QgsRemappingSinkDefinition &other ) const;

};




class QgsRemappingProxyFeatureSink : QgsFeatureSink
{
%Docstring
Expand Down Expand Up @@ -136,6 +171,11 @@ to manipulate features before sending them to the destination ``sink``.
void setExpressionContext( const QgsExpressionContext &context );
%Docstring
Sets the expression ``context`` to use when evaluating mapped field values.
%End

void setTransformContext( const QgsCoordinateTransformContext &context );
%Docstring
Sets the transform ``context`` to use when reprojecting features.
%End

QgsFeatureList remapFeature( const QgsFeature &feature ) const;
Expand Down
2 changes: 2 additions & 0 deletions src/core/qgsapplication.cpp
Expand Up @@ -61,6 +61,7 @@
#include "qgsbookmarkmanager.h"
#include "qgsstylemodel.h"
#include "qgsconnectionregistry.h"
#include "qgsremappingproxyfeaturesink.h"

#include "gps/qgsgpsconnectionregistry.h"
#include "processing/qgsprocessingregistry.h"
Expand Down Expand Up @@ -230,6 +231,7 @@ void QgsApplication::init( QString profileFolder )
qRegisterMetaType<QgsRectangle>( "QgsRectangle" );
qRegisterMetaType<QgsProcessingModelChildParameterSource>( "QgsProcessingModelChildParameterSource" );
qRegisterMetaTypeStreamOperators<QgsProcessingModelChildParameterSource>( "QgsProcessingModelChildParameterSource" );
qRegisterMetaType<QgsRemappingSinkDefinition>( "QgsRemappingSinkDefinition" );

( void ) resolvePkgPath();

Expand Down
70 changes: 69 additions & 1 deletion src/core/qgsremappingproxyfeaturesink.cpp
Expand Up @@ -29,6 +29,11 @@ void QgsRemappingProxyFeatureSink::setExpressionContext( const QgsExpressionCont
mContext = context;
}

void QgsRemappingProxyFeatureSink::setTransformContext( const QgsCoordinateTransformContext &context )
{
mTransform = QgsCoordinateTransform( mDefinition.sourceCrs(), mDefinition.destinationCrs(), context );
}

QgsFeatureList QgsRemappingProxyFeatureSink::remapFeature( const QgsFeature &feature ) const
{
QgsFeatureList res;
Expand Down Expand Up @@ -67,7 +72,7 @@ QgsFeatureList QgsRemappingProxyFeatureSink::remapFeature( const QgsFeature &fea
QgsGeometry reproject = geometry;
try
{
reproject.transform( mDefinition.transform() );
reproject.transform( mTransform );
featurePart.setGeometry( reproject );
}
catch ( QgsCsException & )
Expand Down Expand Up @@ -117,3 +122,66 @@ bool QgsRemappingProxyFeatureSink::addFeatures( QgsFeatureIterator &iterator, Qg
}
return res;
}

QVariant QgsRemappingSinkDefinition::toVariant() const
{
QVariantMap map;
map.insert( QStringLiteral( "wkb_type" ), mDestinationWkbType );
// we only really care about names here
QVariantList fieldNames;
for ( const QgsField &field : mDestinationFields )
fieldNames << field.name();
map.insert( QStringLiteral( "destination_field_names" ), fieldNames );
map.insert( QStringLiteral( "transform_source" ), mSourceCrs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018 ) );
map.insert( QStringLiteral( "transform_dest" ), mDestinationCrs.toWkt( QgsCoordinateReferenceSystem::WKT2_2018 ) );

QVariantMap fieldMap;
for ( auto it = mFieldMap.constBegin(); it != mFieldMap.constEnd(); ++it )
{
fieldMap.insert( it.key(), it.value().toVariant() );
}
map.insert( QStringLiteral( "field_map" ), fieldMap );

return map;
}

bool QgsRemappingSinkDefinition::loadVariant( const QVariantMap &map )
{
mDestinationWkbType = static_cast< QgsWkbTypes::Type >( map.value( QStringLiteral( "wkb_type" ), QgsWkbTypes::Unknown ).toInt() );

const QVariantList fieldNames = map.value( QStringLiteral( "destination_field_names" ) ).toList();
QgsFields fields;
for ( const QVariant &field : fieldNames )
{
fields.append( QgsField( field.toString() ) );
}
mDestinationFields = fields;

mSourceCrs = QgsCoordinateReferenceSystem::fromWkt( map.value( QStringLiteral( "transform_source" ) ).toString() );
mDestinationCrs = QgsCoordinateReferenceSystem::fromWkt( map.value( QStringLiteral( "transform_dest" ) ).toString() );

const QVariantMap fieldMap = map.value( QStringLiteral( "field_map" ) ).toMap();
mFieldMap.clear();
for ( auto it = fieldMap.constBegin(); it != fieldMap.constEnd(); ++it )
{
QgsProperty p;
p.loadVariant( it.value() );
mFieldMap.insert( it.key(), p );
}

return true;
}

bool QgsRemappingSinkDefinition::operator==( const QgsRemappingSinkDefinition &other ) const
{
return mDestinationWkbType == other.mDestinationWkbType
&& mDestinationFields == other.mDestinationFields
&& mFieldMap == other.mFieldMap
&& mSourceCrs == other.mSourceCrs
&& mDestinationCrs == other.mDestinationCrs;
}

bool QgsRemappingSinkDefinition::operator!=( const QgsRemappingSinkDefinition &other ) const
{
return !( *this == other );
}
55 changes: 48 additions & 7 deletions src/core/qgsremappingproxyfeaturesink.h
Expand Up @@ -74,18 +74,32 @@ class CORE_EXPORT QgsRemappingSinkDefinition
void addMappedField( const QString &destinationField, const QgsProperty &property ) { mFieldMap.insert( destinationField, property ); }

/**
* Returns the transform used for reprojecting incoming features to the sink's destination CRS.
* Returns the source CRS used for reprojecting incoming features to the sink's destination CRS.
*
* \see setTransform()
* \see setSourceCrs()
*/
QgsCoordinateTransform transform() const { return mTransform; }
QgsCoordinateReferenceSystem sourceCrs() const { return mSourceCrs; }

/**
* Sets the \a transform used for reprojecting incoming features to the sink's destination CRS.
* Sets the \a source crs used for reprojecting incoming features to the sink's destination CRS.
*
* \see transform()
* \see sourceCrs()
*/
void setTransform( const QgsCoordinateTransform &transform ) { mTransform = transform; }
void setSourceCrs( const QgsCoordinateReferenceSystem &source ) { mSourceCrs = source; }

/**
* Returns the destination CRS used for reprojecting incoming features to the sink's destination CRS.
*
* \see setDestinationCrs()
*/
QgsCoordinateReferenceSystem destinationCrs() const { return mDestinationCrs; }

/**
* Sets the \a destination crs used for reprojecting incoming features to the sink's destination CRS.
*
* \see destinationCrs()
*/
void setDestinationCrs( const QgsCoordinateReferenceSystem &destination ) { mDestinationCrs = destination; }

/**
* Returns the WKB geometry type for the destination.
Expand Down Expand Up @@ -115,18 +129,39 @@ class CORE_EXPORT QgsRemappingSinkDefinition
*/
void setDestinationFields( const QgsFields &fields ) { mDestinationFields = fields; }

/**
* Saves this remapping definition to a QVariantMap, wrapped in a QVariant.
* You can use QgsXmlUtils::writeVariant to save it to an XML document.
* \see loadVariant()
*/
QVariant toVariant() const;

/**
* Loads this remapping definition from a QVariantMap, wrapped in a QVariant.
* You can use QgsXmlUtils::readVariant to load it from an XML document.
* \see toVariant()
*/
bool loadVariant( const QVariantMap &map );

bool operator==( const QgsRemappingSinkDefinition &other ) const;
bool operator!=( const QgsRemappingSinkDefinition &other ) const;

private:

QMap< QString, QgsProperty > mFieldMap;

QgsCoordinateTransform mTransform;
QgsCoordinateReferenceSystem mSourceCrs;
QgsCoordinateReferenceSystem mDestinationCrs;

QgsWkbTypes::Type mDestinationWkbType = QgsWkbTypes::Unknown;

QgsFields mDestinationFields;

};

Q_DECLARE_METATYPE( QgsRemappingSinkDefinition )



/**
* \class QgsRemappingProxyFeatureSink
Expand Down Expand Up @@ -155,6 +190,11 @@ class CORE_EXPORT QgsRemappingProxyFeatureSink : public QgsFeatureSink
*/
void setExpressionContext( const QgsExpressionContext &context );

/**
* Sets the transform \a context to use when reprojecting features.
*/
void setTransformContext( const QgsCoordinateTransformContext &context );

/**
* Remaps a \a feature to a set of features compatible with the destination sink.
*/
Expand All @@ -172,6 +212,7 @@ class CORE_EXPORT QgsRemappingProxyFeatureSink : public QgsFeatureSink
private:

QgsRemappingSinkDefinition mDefinition;
QgsCoordinateTransform mTransform;
QgsFeatureSink *mSink = nullptr;
mutable QgsExpressionContext mContext;
};
Expand Down
72 changes: 65 additions & 7 deletions tests/src/python/test_qgsfeaturesink.py
Expand Up @@ -103,23 +103,22 @@ def testProxyFeatureSink(self):
self.assertEqual(store.features()[1]['fldtxt'], 'test2')
self.assertEqual(store.features()[2]['fldtxt'], 'test3')

def testRemappingSink(self):
def testRemappingSinkDefinition(self):
"""
Test remapping features
Test remapping sink definitions
"""
fields = QgsFields()
fields.append(QgsField('fldtxt', QVariant.String))
fields.append(QgsField('fldint', QVariant.Int))
fields.append(QgsField('fldtxt2', QVariant.String))

store = QgsFeatureStore(fields, QgsCoordinateReferenceSystem('EPSG:3857'))

mapping_def = QgsRemappingSinkDefinition()
mapping_def.setDestinationWkbType(QgsWkbTypes.Point)
self.assertEqual(mapping_def.destinationWkbType(), QgsWkbTypes.Point)
mapping_def.setTransform(QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:4326'), QgsCoordinateReferenceSystem('EPSG:3857'), QgsProject.instance()))
self.assertEqual(mapping_def.transform().sourceCrs().authid(), 'EPSG:4326')
self.assertEqual(mapping_def.transform().destinationCrs().authid(), 'EPSG:3857')
mapping_def.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
mapping_def.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
self.assertEqual(mapping_def.sourceCrs().authid(), 'EPSG:4326')
self.assertEqual(mapping_def.destinationCrs().authid(), 'EPSG:3857')
mapping_def.setDestinationFields(fields)
self.assertEqual(mapping_def.destinationFields(), fields)
mapping_def.addMappedField('fldtxt2', QgsProperty.fromField('fld1'))
Expand All @@ -128,6 +127,64 @@ def testRemappingSink(self):
self.assertEqual(mapping_def.fieldMap()['fldtxt2'].field(), 'fld1')
self.assertEqual(mapping_def.fieldMap()['fldint'].expressionString(), '@myval * fldint')

mapping_def2 = QgsRemappingSinkDefinition(mapping_def)
self.assertTrue(mapping_def == mapping_def2)
self.assertFalse(mapping_def != mapping_def2)
mapping_def2.setDestinationWkbType(QgsWkbTypes.Polygon)
self.assertFalse(mapping_def == mapping_def2)
self.assertTrue(mapping_def != mapping_def2)
mapping_def2.setDestinationWkbType(QgsWkbTypes.Point)
mapping_def2.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:3111'))
self.assertFalse(mapping_def == mapping_def2)
self.assertTrue(mapping_def != mapping_def2)
mapping_def2.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
mapping_def2.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3111'))
self.assertFalse(mapping_def == mapping_def2)
self.assertTrue(mapping_def != mapping_def2)
mapping_def2.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
mapping_def2.setDestinationFields(QgsFields())
self.assertFalse(mapping_def == mapping_def2)
self.assertTrue(mapping_def != mapping_def2)
mapping_def2.setDestinationFields(fields)
mapping_def2.addMappedField('fldint3', QgsProperty.fromExpression('@myval * fldint'))
self.assertFalse(mapping_def == mapping_def2)
self.assertTrue(mapping_def != mapping_def2)
mapping_def2.setFieldMap(mapping_def.fieldMap())
self.assertTrue(mapping_def == mapping_def2)
self.assertFalse(mapping_def != mapping_def2)

# to variant
var = mapping_def.toVariant()

def2 = QgsRemappingSinkDefinition()
def2.loadVariant(var)
self.assertEqual(def2.destinationWkbType(), QgsWkbTypes.Point)
self.assertEqual(def2.sourceCrs().authid(), 'EPSG:4326')
self.assertEqual(def2.destinationCrs().authid(), 'EPSG:3857')
self.assertEqual(def2.destinationFields()[0].name(), 'fldtxt')
self.assertEqual(def2.destinationFields()[1].name(), 'fldint')
self.assertEqual(def2.fieldMap()['fldtxt2'].field(), 'fld1')
self.assertEqual(def2.fieldMap()['fldint'].expressionString(), '@myval * fldint')

def testRemappingSink(self):
"""
Test remapping features
"""
fields = QgsFields()
fields.append(QgsField('fldtxt', QVariant.String))
fields.append(QgsField('fldint', QVariant.Int))
fields.append(QgsField('fldtxt2', QVariant.String))

store = QgsFeatureStore(fields, QgsCoordinateReferenceSystem('EPSG:3857'))

mapping_def = QgsRemappingSinkDefinition()
mapping_def.setDestinationWkbType(QgsWkbTypes.Point)
mapping_def.setSourceCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
mapping_def.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
mapping_def.setDestinationFields(fields)
mapping_def.addMappedField('fldtxt2', QgsProperty.fromField('fld1'))
mapping_def.addMappedField('fldint', QgsProperty.fromExpression('@myval * fldint'))

proxy = QgsRemappingProxyFeatureSink(mapping_def, store)
self.assertEqual(proxy.destinationSink(), store)

Expand All @@ -143,6 +200,7 @@ def testRemappingSink(self):
context.appendScope(scope)
context.setFields(incoming_fields)
proxy.setExpressionContext(context)
proxy.setTransformContext(QgsProject.instance().transformContext())

f = QgsFeature()
f.setFields(incoming_fields)
Expand Down

0 comments on commit 8c73c61

Please sign in to comment.