From e1eef7ed192aacf43345d7622e0cc512116a6bf0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 15 Sep 2017 16:40:39 +1000 Subject: [PATCH 01/10] Allow use of QgsPointXY/QgsReferencedPointXY for point parameter values --- .../processing/qgsprocessingalgorithm.sip | 16 +++- .../processing/qgsprocessingparameters.sip | 17 +++- .../processing/qgsprocessingalgorithm.cpp | 9 +- src/core/processing/qgsprocessingalgorithm.h | 15 +++- .../processing/qgsprocessingparameters.cpp | 84 ++++++++++++++++++- src/core/processing/qgsprocessingparameters.h | 15 +++- src/core/qgsapplication.cpp | 1 - tests/src/core/testqgsprocessing.cpp | 32 +++++++ 8 files changed, 180 insertions(+), 9 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index b35bb52c08f..4a84c2f2665 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -696,12 +696,26 @@ class QgsProcessingAlgorithm :rtype: QgsCoordinateReferenceSystem %End - QgsPointXY parameterAsPoint( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const; + QgsPointXY parameterAsPoint( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context, + const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ) const; %Docstring Evaluates the parameter with matching ``name`` to a point. + + If ``crs`` is set then the point will be automatically + reprojected so that it is in the specified ``crs``. + +.. seealso:: parameterAsPointCrs() :rtype: QgsPointXY %End + QgsCoordinateReferenceSystem parameterAsPointCrs( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ); +%Docstring + Returns the coordinate reference system associated with an point parameter value. + +.. seealso:: parameterAsPoint() + :rtype: QgsCoordinateReferenceSystem +%End + QString parameterAsFile( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const; %Docstring Evaluates the parameter with matching ``name`` to a file/folder name. diff --git a/python/core/processing/qgsprocessingparameters.sip b/python/core/processing/qgsprocessingparameters.sip index 0fc0773d7c4..da7215c7aa9 100644 --- a/python/core/processing/qgsprocessingparameters.sip +++ b/python/core/processing/qgsprocessingparameters.sip @@ -570,12 +570,25 @@ class QgsProcessingParameters :rtype: QgsCoordinateReferenceSystem %End - static QgsPointXY parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); + static QgsPointXY parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context, + const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ); %Docstring Evaluates the parameter with matching ``definition`` to a point. + + If ``crs`` is set then the point will be automatically reprojected so that it is in the specified ``crs``. + +.. seealso:: parameterAsPointCrs() :rtype: QgsPointXY %End + static QgsCoordinateReferenceSystem parameterAsPointCrs( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); +%Docstring + Returns the coordinate reference system associated with an point parameter value. + +.. seealso:: parameterAsPoint() + :rtype: QgsCoordinateReferenceSystem +%End + static QString parameterAsFile( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); %Docstring Evaluates the parameter with matching ``definition`` to a file/folder name. @@ -817,6 +830,8 @@ class QgsProcessingParameterPoint : QgsProcessingParameterDefinition virtual QString type() const; virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const; + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + static QgsProcessingParameterPoint *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/; %Docstring diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index 703c202fc6a..8ebadca65ea 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -565,9 +565,14 @@ QgsGeometry QgsProcessingAlgorithm::parameterAsExtentGeometry( const QVariantMap return QgsProcessingParameters::parameterAsExtentGeometry( parameterDefinition( name ), parameters, context, crs ); } -QgsPointXY QgsProcessingAlgorithm::parameterAsPoint( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const +QgsPointXY QgsProcessingAlgorithm::parameterAsPoint( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context, const QgsCoordinateReferenceSystem &crs ) const { - return QgsProcessingParameters::parameterAsPoint( parameterDefinition( name ), parameters, context ); + return QgsProcessingParameters::parameterAsPoint( parameterDefinition( name ), parameters, context, crs ); +} + +QgsCoordinateReferenceSystem QgsProcessingAlgorithm::parameterAsPointCrs( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) +{ + return QgsProcessingParameters::parameterAsPointCrs( parameterDefinition( name ), parameters, context ); } QString QgsProcessingAlgorithm::parameterAsFile( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index a9fbf2074c3..d13aa80b639 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -680,8 +680,21 @@ class CORE_EXPORT QgsProcessingAlgorithm /** * Evaluates the parameter with matching \a name to a point. + * + * If \a crs is set then the point will be automatically + * reprojected so that it is in the specified \a crs. + * + * \see parameterAsPointCrs() */ - QgsPointXY parameterAsPoint( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ) const; + QgsPointXY parameterAsPoint( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context, + const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ) const; + + /** + * Returns the coordinate reference system associated with an point parameter value. + * + * \see parameterAsPoint() + */ + QgsCoordinateReferenceSystem parameterAsPointCrs( const QVariantMap ¶meters, const QString &name, QgsProcessingContext &context ); /** * Evaluates the parameter with matching \a name to a file/folder name. diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index 634b8a43bb1..008b5b25dc0 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -503,7 +503,14 @@ QgsRectangle QgsProcessingParameters::parameterAsExtent( const QgsProcessingPara if ( crs.isValid() && rr.crs().isValid() && crs != rr.crs() ) { QgsCoordinateTransform ct( rr.crs(), crs ); - return ct.transformBoundingBox( rr ); + try + { + return ct.transformBoundingBox( rr ); + } + catch ( QgsCsException & ) + { + QgsMessageLog::logMessage( QObject::tr( "Error transforming extent geometry" ) ); + } } return rr; } @@ -588,11 +595,34 @@ QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsExtentCrs( cons return QgsCoordinateReferenceSystem(); } -QgsPointXY QgsProcessingParameters::parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ) +QgsPointXY QgsProcessingParameters::parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context, const QgsCoordinateReferenceSystem &crs ) { if ( !definition ) return QgsPointXY(); + QVariant val = parameters.value( definition->name() ); + if ( val.canConvert< QgsPointXY >() ) + { + return val.value(); + } + if ( val.canConvert< QgsReferencedPointXY >() ) + { + QgsReferencedPointXY rp = val.value(); + if ( crs.isValid() && rp.crs().isValid() && crs != rp.crs() ) + { + QgsCoordinateTransform ct( rp.crs(), crs ); + try + { + return ct.transform( rp ); + } + catch ( QgsCsException & ) + { + QgsMessageLog::logMessage( QObject::tr( "Error transforming point geometry" ) ); + } + } + return rp; + } + QString pointText = parameterAsString( definition, parameters, context ); if ( pointText.isEmpty() ) pointText = definition->defaultValue().toString(); @@ -614,6 +644,25 @@ QgsPointXY QgsProcessingParameters::parameterAsPoint( const QgsProcessingParamet return QgsPointXY(); } +QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsPointCrs( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ) +{ + QVariant val = parameters.value( definition->name() ); + + if ( val.canConvert< QgsReferencedPointXY >() ) + { + QgsReferencedPointXY rr = val.value(); + if ( rr.crs().isValid() ) + { + return rr.crs(); + } + } + + if ( context.project() ) + return context.project()->crs(); + else + return QgsCoordinateReferenceSystem(); +} + QString QgsProcessingParameters::parameterAsFile( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ) { if ( !definition ) @@ -1296,6 +1345,15 @@ bool QgsProcessingParameterPoint::checkValueIsAcceptable( const QVariant &input, return true; } + if ( input.canConvert< QgsPointXY >() ) + { + return true; + } + if ( input.canConvert< QgsReferencedPointXY >() ) + { + return true; + } + if ( input.type() == QVariant::String ) { if ( input.toString().isEmpty() ) @@ -1315,6 +1373,28 @@ bool QgsProcessingParameterPoint::checkValueIsAcceptable( const QVariant &input, return false; } +QString QgsProcessingParameterPoint::valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const +{ + if ( value.canConvert() ) + return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() ); + + if ( value.canConvert< QgsPointXY >() ) + { + QgsPointXY r = value.value(); + return QStringLiteral( "QgsPointXY( %1, %2 )" ).arg( qgsDoubleToString( r.x() ), + qgsDoubleToString( r.y() ) ); + } + if ( value.canConvert< QgsReferencedPointXY >() ) + { + QgsReferencedPointXY r = value.value(); + return QStringLiteral( "QgsReferencedPointXY( QgsPointXY( %1, %2 ), QgsCoordinateReferenceSystem( '%3' ) )" ).arg( qgsDoubleToString( r.x() ), + qgsDoubleToString( r.y() ), + r.crs().authid() ); + } + + return QgsProcessingParameterDefinition::valueAsPythonString( value, context ); +} + QgsProcessingParameterPoint *QgsProcessingParameterPoint::fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) { return new QgsProcessingParameterPoint( name, description, definition, isOptional ); diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index 7591ca8a513..014105f2482 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -598,8 +598,20 @@ class CORE_EXPORT QgsProcessingParameters /** * Evaluates the parameter with matching \a definition to a point. + * + * If \a crs is set then the point will be automatically reprojected so that it is in the specified \a crs. + * + * \see parameterAsPointCrs() */ - static QgsPointXY parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); + static QgsPointXY parameterAsPoint( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context, + const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() ); + + /** + * Returns the coordinate reference system associated with an point parameter value. + * + * \see parameterAsPoint() + */ + static QgsCoordinateReferenceSystem parameterAsPointCrs( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); /** * Evaluates the parameter with matching \a definition to a file/folder name. @@ -804,6 +816,7 @@ class CORE_EXPORT QgsProcessingParameterPoint : public QgsProcessingParameterDef QgsProcessingParameterDefinition *clone() const override SIP_FACTORY; QString type() const override { return typeName(); } bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; /** * Creates a new parameter using the definition from a script code. diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 8720c61632d..67e9ae83c2a 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -151,7 +151,6 @@ void QgsApplication::init( QString profileFolder ) qRegisterMetaType( "QgsFeatureIds" ); qRegisterMetaType( "QgsMessageLog::MessageLevel" ); qRegisterMetaType( "QgsReferencedRectangle" ); - qRegisterMetaType( "QgsReferencedPoint" ); QString prefixPath( getenv( "QGIS_PREFIX_PATH" ) ? getenv( "QGIS_PREFIX_PATH" ) : applicationDirPath() ); // QgsDebugMsg( QString( "prefixPath(): %1" ).arg( prefixPath ) ); diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 0279d49fa24..a3eadca3946 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -2010,6 +2010,8 @@ void TestQgsProcessing::parameterPoint() QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( def->checkValueIsAcceptable( QgsPointXY( 1, 2 ) ) ); + QVERIFY( def->checkValueIsAcceptable( QgsReferencedPointXY( QgsPointXY( 1, 2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ) ); // string representing a point QVariantMap params; @@ -2019,13 +2021,43 @@ void TestQgsProcessing::parameterPoint() QGSCOMPARENEAR( point.x(), 1.1, 0.001 ); QGSCOMPARENEAR( point.y(), 2.2, 0.001 ); + // with target CRS - should make no difference, because source CRS is unknown + point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + QGSCOMPARENEAR( point.x(), 1.1, 0.001 ); + QGSCOMPARENEAR( point.y(), 2.2, 0.001 ); + // nonsense string params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); QCOMPARE( point.x(), 0.0 ); QCOMPARE( point.y(), 0.0 ); + // QgsPointXY + params.insert( "non_optional", QgsPointXY( 11.1, 12.2 ) ); + point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); + QGSCOMPARENEAR( point.x(), 11.1, 0.001 ); + QGSCOMPARENEAR( point.y(), 12.2, 0.001 ); + + // with target CRS - should make no difference, because source CRS is unknown + point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + QGSCOMPARENEAR( point.x(), 11.1, 0.001 ); + QGSCOMPARENEAR( point.y(), 12.2, 0.001 ); + + // QgsReferencedPointXY + params.insert( "non_optional", QgsReferencedPointXY( QgsPointXY( 1.1, 2.2 ), QgsCoordinateReferenceSystem( "EPSG:4326" ) ) ); + point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); + QGSCOMPARENEAR( point.x(), 1.1, 0.001 ); + QGSCOMPARENEAR( point.y(), 2.2, 0.001 ); + QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); + + // with target CRS + point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + QGSCOMPARENEAR( point.x(), 122451, 100 ); + QGSCOMPARENEAR( point.y(), 244963, 100 ); + QCOMPARE( def->valueAsPythonString( "1,2", context ), QStringLiteral( "'1,2'" ) ); + QCOMPARE( def->valueAsPythonString( QgsPointXY( 11, 12 ), context ), QStringLiteral( "QgsPointXY( 11, 12 )" ) ); + QCOMPARE( def->valueAsPythonString( QgsReferencedPointXY( QgsPointXY( 11, 12 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "QgsReferencedPointXY( QgsPointXY( 11, 12 ), QgsCoordinateReferenceSystem( 'EPSG:4326' ) )" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=point 1,2" ) ); From 6efcc2fe8617599bda2fd6c18ba1ec120bf18275 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 15 Sep 2017 17:05:43 +1000 Subject: [PATCH 02/10] Transparently reproject source points in algs --- .../processing/algs/qgis/ServiceAreaFromLayer.py | 1 + .../processing/algs/qgis/ServiceAreaFromPoint.py | 2 +- .../algs/qgis/ShortestPathLayerToPoint.py | 3 ++- .../algs/qgis/ShortestPathPointToLayer.py | 2 +- .../algs/qgis/ShortestPathPointToPoint.py | 4 ++-- .../plugins/processing/gui/PointSelectionPanel.py | 15 ++++++++++++++- 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py b/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py index 9e65011bdc6..22480581072 100644 --- a/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py +++ b/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py @@ -35,6 +35,7 @@ from qgis.core import (QgsWkbTypes, QgsUnitTypes, QgsFeature, QgsFeatureSink, + QgsFeatureRequest, QgsGeometry, QgsFields, QgsField, diff --git a/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py b/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py index b5f43d77196..b2473b06ebe 100644 --- a/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py +++ b/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py @@ -163,7 +163,7 @@ class ServiceAreaFromPoint(QgisAlgorithm): def processAlgorithm(self, parameters, context, feedback): network = self.parameterAsSource(parameters, self.INPUT, context) - startPoint = self.parameterAsPoint(parameters, self.START_POINT, context) + startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs()) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context) diff --git a/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py b/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py index ede6cd6621e..62745deb66e 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py +++ b/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py @@ -34,6 +34,7 @@ from qgis.PyQt.QtGui import QIcon from qgis.core import (QgsWkbTypes, QgsUnitTypes, QgsFeature, + QgsFeatureRequest, QgsFeatureSink, QgsGeometry, QgsFields, @@ -158,7 +159,7 @@ class ShortestPathLayerToPoint(QgisAlgorithm): def processAlgorithm(self, parameters, context, feedback): network = self.parameterAsSource(parameters, self.INPUT, context) startPoints = self.parameterAsSource(parameters, self.START_POINTS, context) - endPoint = self.parameterAsPoint(parameters, self.END_POINT, context) + endPoint = self.parameterAsPoint(parameters, self.END_POINT, context, network.sourceCrs()) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context) diff --git a/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py b/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py index 030c51d32ca..b9d193ee094 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py +++ b/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py @@ -157,7 +157,7 @@ class ShortestPathPointToLayer(QgisAlgorithm): def processAlgorithm(self, parameters, context, feedback): network = self.parameterAsSource(parameters, self.INPUT, context) - startPoint = self.parameterAsPoint(parameters, self.START_POINT, context) + startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs()) endPoints = self.parameterAsSource(parameters, self.END_POINTS, context) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) diff --git a/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py b/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py index ab59b2baf1c..a942808d13d 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py +++ b/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py @@ -160,8 +160,8 @@ class ShortestPathPointToPoint(QgisAlgorithm): def processAlgorithm(self, parameters, context, feedback): network = self.parameterAsSource(parameters, self.INPUT, context) - startPoint = self.parameterAsPoint(parameters, self.START_POINT, context) - endPoint = self.parameterAsPoint(parameters, self.END_POINT, context) + startPoint = self.parameterAsPoint(parameters, self.START_POINT, context, network.sourceCrs()) + endPoint = self.parameterAsPoint(parameters, self.END_POINT, context, network.sourceCrs()) strategy = self.parameterAsEnum(parameters, self.STRATEGY, context) directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context) diff --git a/python/plugins/processing/gui/PointSelectionPanel.py b/python/plugins/processing/gui/PointSelectionPanel.py index 798a3ffab32..c8fb40ccba0 100644 --- a/python/plugins/processing/gui/PointSelectionPanel.py +++ b/python/plugins/processing/gui/PointSelectionPanel.py @@ -28,6 +28,9 @@ __revision__ = '$Format:%H$' import os +from qgis.core import (QgsProject, + QgsReferencedPointXY, + QgsPointXY) from qgis.PyQt import uic from qgis.utils import iface @@ -48,6 +51,7 @@ class PointSelectionPanel(BASE, WIDGET): self.btnSelect.clicked.connect(self.selectOnCanvas) self.dialog = dialog + self.crs = QgsProject.instance().crs() if iface is not None: canvas = iface.mapCanvas() @@ -76,6 +80,7 @@ class PointSelectionPanel(BASE, WIDGET): def updatePoint(self, point, button): s = '{},{}'.format(point.x(), point.y()) + self.crs = QgsProject.instance().crs() self.leText.setText(s) canvas = iface.mapCanvas() @@ -86,7 +91,15 @@ class PointSelectionPanel(BASE, WIDGET): def getValue(self): if str(self.leText.text()).strip() != '': - return str(self.leText.text()) + try: + parts = self.leText.text().split(',') + parts = [float(p) for p in parts] + r = QgsReferencedPointXY(QgsPointXY(parts[0], parts[1]), + self.crs) + return r + + except: + return str(self.leText.text()) else: return None From 506f6d40b720eab1ad09723c2f376286e58bb3d9 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 15 Sep 2017 17:19:03 +1000 Subject: [PATCH 03/10] [processing] Fix picking point from map always triggers a release event for previous map tool --- python/plugins/processing/gui/PointMapTool.py | 9 +++++---- python/plugins/processing/gui/PointSelectionPanel.py | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/python/plugins/processing/gui/PointMapTool.py b/python/plugins/processing/gui/PointMapTool.py index 3d25acda06d..b74b2962700 100644 --- a/python/plugins/processing/gui/PointMapTool.py +++ b/python/plugins/processing/gui/PointMapTool.py @@ -25,13 +25,15 @@ __copyright__ = '(C) 2016, Alexander Bruy' __revision__ = '$Format:%H$' -from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtCore import Qt, pyqtSignal from qgis.gui import QgsMapToolEmitPoint class PointMapTool(QgsMapToolEmitPoint): + complete = pyqtSignal() + def __init__(self, canvas): QgsMapToolEmitPoint.__init__(self, canvas) @@ -41,6 +43,5 @@ class PointMapTool(QgsMapToolEmitPoint): def activate(self): self.canvas.setCursor(self.cursor) - def canvasPressEvent(self, event): - pnt = self.toMapCoordinates(event.pos()) - self.canvasClicked.emit(pnt, event.button()) + def canvasReleaseEvent(self, event): + self.complete.emit() diff --git a/python/plugins/processing/gui/PointSelectionPanel.py b/python/plugins/processing/gui/PointSelectionPanel.py index c8fb40ccba0..e8795e4ecf3 100644 --- a/python/plugins/processing/gui/PointSelectionPanel.py +++ b/python/plugins/processing/gui/PointSelectionPanel.py @@ -59,6 +59,7 @@ class PointSelectionPanel(BASE, WIDGET): self.tool = PointMapTool(canvas) self.tool.canvasClicked.connect(self.updatePoint) + self.tool.complete.connect(self.pointPicked) else: self.prevMapTool = None self.tool = None @@ -81,8 +82,9 @@ class PointSelectionPanel(BASE, WIDGET): def updatePoint(self, point, button): s = '{},{}'.format(point.x(), point.y()) self.crs = QgsProject.instance().crs() - self.leText.setText(s) + + def pointPicked(self): canvas = iface.mapCanvas() canvas.setMapTool(self.prevMapTool) self.dialog.showNormal() From 6898342c087bcf6ce0dfe161390891976c8719cf Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 15 Sep 2017 17:24:00 +1000 Subject: [PATCH 04/10] Better cursor for point picker tool --- python/plugins/processing/gui/PointMapTool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/gui/PointMapTool.py b/python/plugins/processing/gui/PointMapTool.py index b74b2962700..c31843c6504 100644 --- a/python/plugins/processing/gui/PointMapTool.py +++ b/python/plugins/processing/gui/PointMapTool.py @@ -38,7 +38,7 @@ class PointMapTool(QgsMapToolEmitPoint): QgsMapToolEmitPoint.__init__(self, canvas) self.canvas = canvas - self.cursor = Qt.ArrowCursor + self.cursor = Qt.CrossCursor def activate(self): self.canvas.setCursor(self.cursor) From 1d6e30324f6e284c9bab7b9aa43cb2663ee3b5cd Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 15 Sep 2017 17:26:49 +1000 Subject: [PATCH 05/10] Use correct SRS when building graph --- python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py | 2 +- python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py | 2 +- .../plugins/processing/algs/qgis/ShortestPathLayerToPoint.py | 4 ++-- .../plugins/processing/algs/qgis/ShortestPathPointToLayer.py | 5 +++-- .../plugins/processing/algs/qgis/ShortestPathPointToPoint.py | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py b/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py index 22480581072..11721c97d18 100644 --- a/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py +++ b/python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py @@ -209,7 +209,7 @@ class ServiceAreaFromLayer(QgisAlgorithm): multiplier * 1000.0 / 3600.0) director.addStrategy(strategy) - builder = QgsGraphBuilder(context.project().crs(), + builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance) diff --git a/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py b/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py index b2473b06ebe..a9926696a1f 100644 --- a/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py +++ b/python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py @@ -200,7 +200,7 @@ class ServiceAreaFromPoint(QgisAlgorithm): multiplier * 1000.0 / 3600.0) director.addStrategy(strategy) - builder = QgsGraphBuilder(context.project().crs(), + builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance) feedback.pushInfo(self.tr('Building graph...')) diff --git a/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py b/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py index 62745deb66e..839732c12c0 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py +++ b/python/plugins/processing/algs/qgis/ShortestPathLayerToPoint.py @@ -207,7 +207,7 @@ class ShortestPathLayerToPoint(QgisAlgorithm): multiplier = 3600 director.addStrategy(strategy) - builder = QgsGraphBuilder(context.project().crs(), + builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance) @@ -237,7 +237,7 @@ class ShortestPathLayerToPoint(QgisAlgorithm): nPoints = len(snappedPoints) total = 100.0 / nPoints if nPoints else 1 - for i in range(1, count + 1): + for i in range(1, nPoints + 1): if feedback.isCanceled(): break diff --git a/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py b/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py index b9d193ee094..434d51c7826 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py +++ b/python/plugins/processing/algs/qgis/ShortestPathPointToLayer.py @@ -36,6 +36,7 @@ from qgis.core import (QgsWkbTypes, QgsFeature, QgsFeatureSink, QgsGeometry, + QgsFeatureRequest, QgsFields, QgsField, QgsMessageLog, @@ -206,7 +207,7 @@ class ShortestPathPointToLayer(QgisAlgorithm): multiplier = 3600 director.addStrategy(strategy) - builder = QgsGraphBuilder(context.project().crs(), + builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance) @@ -237,7 +238,7 @@ class ShortestPathPointToLayer(QgisAlgorithm): nPoints = len(snappedPoints) total = 100.0 / nPoints if nPoints else 1 - for i in range(1, count + 1): + for i in range(1, nPoints + 1): if feedback.isCanceled(): break diff --git a/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py b/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py index a942808d13d..16345a177d9 100644 --- a/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py +++ b/python/plugins/processing/algs/qgis/ShortestPathPointToPoint.py @@ -206,7 +206,7 @@ class ShortestPathPointToPoint(QgisAlgorithm): multiplier = 3600 director.addStrategy(strategy) - builder = QgsGraphBuilder(context.project().crs(), + builder = QgsGraphBuilder(network.sourceCrs(), True, tolerance) feedback.pushInfo(self.tr('Building graph...')) From a4ed7214995bcaaa53af7bf46a5122e40ca5dfc5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 15 Sep 2017 18:39:36 +1000 Subject: [PATCH 06/10] Fix passing of QgsReferencedPointXY from Python to c++ --- src/core/qgsapplication.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 67e9ae83c2a..8a54785163a 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -151,6 +151,7 @@ void QgsApplication::init( QString profileFolder ) qRegisterMetaType( "QgsFeatureIds" ); qRegisterMetaType( "QgsMessageLog::MessageLevel" ); qRegisterMetaType( "QgsReferencedRectangle" ); + qRegisterMetaType( "QgsReferencedPointXY" ); QString prefixPath( getenv( "QGIS_PREFIX_PATH" ) ? getenv( "QGIS_PREFIX_PATH" ) : applicationDirPath() ); // QgsDebugMsg( QString( "prefixPath(): %1" ).arg( prefixPath ) ); From ec44e603081a680975c8fe9576d13642d8ef5d83 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 24 Sep 2017 12:46:32 +1000 Subject: [PATCH 07/10] [processing] Allow encoding crs into text definitions of points --- .../processing/qgsprocessingparameters.cpp | 58 +++++++++++++++---- tests/src/core/testqgsprocessing.cpp | 24 +++++++- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index 008b5b25dc0..e425df5eae6 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -630,15 +630,36 @@ QgsPointXY QgsProcessingParameters::parameterAsPoint( const QgsProcessingParamet if ( pointText.isEmpty() ) return QgsPointXY(); - QStringList parts = pointText.split( ',' ); - if ( parts.count() == 2 ) + QRegularExpression rx( "^(.*?)\\s*,\\s*(.*?)\\s*(?:\\[(.*)\\])?\\s*$" ); + + QString valueAsString = parameterAsString( definition, parameters, context ); + QRegularExpressionMatch match = rx.match( valueAsString ); + if ( match.hasMatch() ) { bool xOk = false; - double x = parts.at( 0 ).toDouble( &xOk ); + double x = match.captured( 1 ).toDouble( &xOk ); bool yOk = false; - double y = parts.at( 1 ).toDouble( &yOk ); + double y = match.captured( 2 ).toDouble( &yOk ); + if ( xOk && yOk ) - return QgsPointXY( x, y ); + { + QgsPointXY pt( x, y ); + + QgsCoordinateReferenceSystem pointCrs( match.captured( 3 ) ); + if ( crs.isValid() && pointCrs.isValid() && crs != pointCrs ) + { + QgsCoordinateTransform ct( pointCrs, crs ); + try + { + return ct.transform( pt ); + } + catch ( QgsCsException & ) + { + QgsMessageLog::logMessage( QObject::tr( "Error transforming point geometry" ) ); + } + } + return pt; + } } return QgsPointXY(); @@ -657,6 +678,17 @@ QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsPointCrs( const } } + QRegularExpression rx( "^(.*?)\\s*,\\s*(.*?)\\s*(?:\\[(.*)\\])?\\s*$" ); + + QString valueAsString = parameterAsString( definition, parameters, context ); + QRegularExpressionMatch match = rx.match( valueAsString ); + if ( match.hasMatch() ) + { + QgsCoordinateReferenceSystem crs( match.captured( 3 ) ); + if ( crs.isValid() ) + return crs; + } + if ( context.project() ) return context.project()->crs(); else @@ -1360,13 +1392,15 @@ bool QgsProcessingParameterPoint::checkValueIsAcceptable( const QVariant &input, return mFlags & FlagOptional; } - QStringList parts = input.toString().split( ',' ); - if ( parts.count() == 2 ) + QRegularExpression rx( "^(.*?)\\s*,\\s*(.*?)\\s*(?:\\[(.*)\\])?\\s*$" ); + + QRegularExpressionMatch match = rx.match( input.toString() ); + if ( match.hasMatch() ) { bool xOk = false; - ( void )parts.at( 0 ).toDouble( &xOk ); + ( void )match.captured( 1 ).toDouble( &xOk ); bool yOk = false; - ( void )parts.at( 1 ).toDouble( &yOk ); + ( void )match.captured( 2 ).toDouble( &yOk ); return xOk && yOk; } else @@ -1381,13 +1415,13 @@ QString QgsProcessingParameterPoint::valueAsPythonString( const QVariant &value, if ( value.canConvert< QgsPointXY >() ) { QgsPointXY r = value.value(); - return QStringLiteral( "QgsPointXY( %1, %2 )" ).arg( qgsDoubleToString( r.x() ), - qgsDoubleToString( r.y() ) ); + return QStringLiteral( "'%1,%2'" ).arg( qgsDoubleToString( r.x() ), + qgsDoubleToString( r.y() ) ); } if ( value.canConvert< QgsReferencedPointXY >() ) { QgsReferencedPointXY r = value.value(); - return QStringLiteral( "QgsReferencedPointXY( QgsPointXY( %1, %2 ), QgsCoordinateReferenceSystem( '%3' ) )" ).arg( qgsDoubleToString( r.x() ), + return QStringLiteral( "'%1,%2 [%3]'" ).arg( qgsDoubleToString( r.x() ), qgsDoubleToString( r.y() ), r.crs().authid() ); } diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index a3eadca3946..e336a97610d 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -2006,6 +2006,13 @@ void TestQgsProcessing::parameterPoint() QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1.1,2" ) ); + QVERIFY( def->checkValueIsAcceptable( " 1.1, 2 " ) ); + QVERIFY( def->checkValueIsAcceptable( "-1.1,2" ) ); + QVERIFY( def->checkValueIsAcceptable( "1.1,-2" ) ); + QVERIFY( def->checkValueIsAcceptable( "-1.1,-2" ) ); + QVERIFY( def->checkValueIsAcceptable( "1.1,2[EPSG:4326]" ) ); + QVERIFY( def->checkValueIsAcceptable( "1.1,2 [EPSG:4326]" ) ); + QVERIFY( def->checkValueIsAcceptable( " -1.1, -2 [EPSG:4326] " ) ); QVERIFY( !def->checkValueIsAcceptable( "1.1,a" ) ); QVERIFY( !def->checkValueIsAcceptable( "layer12312312" ) ); QVERIFY( !def->checkValueIsAcceptable( "" ) ); @@ -2026,6 +2033,18 @@ void TestQgsProcessing::parameterPoint() QGSCOMPARENEAR( point.x(), 1.1, 0.001 ); QGSCOMPARENEAR( point.y(), 2.2, 0.001 ); + // with CRS as string + params.insert( "non_optional", QString( "1.1,2.2[EPSG:4326]" ) ); + QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); + point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + QGSCOMPARENEAR( point.x(), 122451, 100 ); + QGSCOMPARENEAR( point.y(), 244963, 100 ); + params.insert( "non_optional", QString( "1.1,2.2 [EPSG:4326]" ) ); + QCOMPARE( QgsProcessingParameters::parameterAsPointCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); + point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + QGSCOMPARENEAR( point.x(), 122451, 100 ); + QGSCOMPARENEAR( point.y(), 244963, 100 ); + // nonsense string params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) ); point = QgsProcessingParameters::parameterAsPoint( def.get(), params, context ); @@ -2056,8 +2075,9 @@ void TestQgsProcessing::parameterPoint() QGSCOMPARENEAR( point.y(), 244963, 100 ); QCOMPARE( def->valueAsPythonString( "1,2", context ), QStringLiteral( "'1,2'" ) ); - QCOMPARE( def->valueAsPythonString( QgsPointXY( 11, 12 ), context ), QStringLiteral( "QgsPointXY( 11, 12 )" ) ); - QCOMPARE( def->valueAsPythonString( QgsReferencedPointXY( QgsPointXY( 11, 12 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "QgsReferencedPointXY( QgsPointXY( 11, 12 ), QgsCoordinateReferenceSystem( 'EPSG:4326' ) )" ) ); + QCOMPARE( def->valueAsPythonString( "1,2 [EPSG:4326]", context ), QStringLiteral( "'1,2 [EPSG:4326]'" ) ); + QCOMPARE( def->valueAsPythonString( QgsPointXY( 11, 12 ), context ), QStringLiteral( "'11,12'" ) ); + QCOMPARE( def->valueAsPythonString( QgsReferencedPointXY( QgsPointXY( 11, 12 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "'11,12 [EPSG:4326]'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=point 1,2" ) ); From fb08b0a6e2b2697fa1ce6cff332524928a6f36da Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 24 Sep 2017 13:09:52 +1000 Subject: [PATCH 08/10] [processing] Allow encoding crs into text definitions of extents --- .../processing/qgsprocessingparameters.cpp | 102 +++++++++++++++--- tests/src/core/testqgsprocessing.cpp | 43 +++++++- 2 files changed, 127 insertions(+), 18 deletions(-) diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index e425df5eae6..761224f7445 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -524,19 +524,36 @@ QgsRectangle QgsProcessingParameters::parameterAsExtent( const QgsProcessingPara if ( rectText.isEmpty() ) return QgsRectangle(); - QStringList parts = rectText.split( ',' ); - if ( parts.count() == 4 ) + QRegularExpression rx( "^(.*?)\\s*,\\s*(.*?),\\s*(.*?),\\s*(.*?)\\s*(?:\\[(.*)\\])?\\s*$" ); + QRegularExpressionMatch match = rx.match( rectText ); + if ( match.hasMatch() ) { bool xMinOk = false; - double xMin = parts.at( 0 ).toDouble( &xMinOk ); + double xMin = match.captured( 1 ).toDouble( &xMinOk ); bool xMaxOk = false; - double xMax = parts.at( 1 ).toDouble( &xMaxOk ); + double xMax = match.captured( 2 ).toDouble( &xMaxOk ); bool yMinOk = false; - double yMin = parts.at( 2 ).toDouble( &yMinOk ); + double yMin = match.captured( 3 ).toDouble( &yMinOk ); bool yMaxOk = false; - double yMax = parts.at( 3 ).toDouble( &yMaxOk ); + double yMax = match.captured( 4 ).toDouble( &yMaxOk ); if ( xMinOk && xMaxOk && yMinOk && yMaxOk ) - return QgsRectangle( xMin, yMin, xMax, yMax ); + { + QgsRectangle rect( xMin, yMin, xMax, yMax ); + QgsCoordinateReferenceSystem rectCrs( match.captured( 5 ) ); + if ( crs.isValid() && rectCrs.isValid() && crs != rectCrs ) + { + QgsCoordinateTransform ct( rectCrs, crs ); + try + { + return ct.transformBoundingBox( rect ); + } + catch ( QgsCsException & ) + { + QgsMessageLog::logMessage( QObject::tr( "Error transforming extent geometry" ) ); + } + } + return rect; + } } // try as layer extent @@ -573,6 +590,49 @@ QgsGeometry QgsProcessingParameters::parameterAsExtentGeometry( const QgsProcess } } + QString rectText; + if ( val.canConvert() ) + rectText = val.value< QgsProperty >().valueAsString( context.expressionContext(), definition->defaultValue().toString() ); + else + rectText = val.toString(); + + if ( !rectText.isEmpty() ) + { + QRegularExpression rx( "^(.*?)\\s*,\\s*(.*?),\\s*(.*?),\\s*(.*?)\\s*(?:\\[(.*)\\])?\\s*$" ); + QRegularExpressionMatch match = rx.match( rectText ); + if ( match.hasMatch() ) + { + bool xMinOk = false; + double xMin = match.captured( 1 ).toDouble( &xMinOk ); + bool xMaxOk = false; + double xMax = match.captured( 2 ).toDouble( &xMaxOk ); + bool yMinOk = false; + double yMin = match.captured( 3 ).toDouble( &yMinOk ); + bool yMaxOk = false; + double yMax = match.captured( 4 ).toDouble( &yMaxOk ); + if ( xMinOk && xMaxOk && yMinOk && yMaxOk ) + { + QgsRectangle rect( xMin, yMin, xMax, yMax ); + QgsCoordinateReferenceSystem rectCrs( match.captured( 5 ) ); + QgsGeometry g = QgsGeometry::fromRect( rect ); + if ( crs.isValid() && rectCrs.isValid() && crs != rectCrs ) + { + g = g.densifyByCount( 20 ); + QgsCoordinateTransform ct( rectCrs, crs ); + try + { + g.transform( ct ); + } + catch ( QgsCsException & ) + { + QgsMessageLog::logMessage( QObject::tr( "Error transforming extent geometry" ) ); + } + return g; + } + } + } + } + return QgsGeometry::fromRect( parameterAsExtent( definition, parameters, context, crs ) ); } @@ -589,6 +649,17 @@ QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsExtentCrs( cons } } + QRegularExpression rx( "^(.*?)\\s*,\\s*(.*?),\\s*(.*?),\\s*(.*?)\\s*(?:\\[(.*)\\])?\\s*$" ); + + QString valueAsString = parameterAsString( definition, parameters, context ); + QRegularExpressionMatch match = rx.match( valueAsString ); + if ( match.hasMatch() ) + { + QgsCoordinateReferenceSystem crs( match.captured( 5 ) ); + if ( crs.isValid() ) + return crs; + } + if ( context.project() ) return context.project()->crs(); else @@ -1301,17 +1372,18 @@ bool QgsProcessingParameterExtent::checkValueIsAcceptable( const QVariant &input return true; } - QStringList parts = input.toString().split( ',' ); - if ( parts.count() == 4 ) + QRegularExpression rx( "^(.*?)\\s*,\\s*(.*?)\\s*,\\s*(.*?)\\s*,\\s*(.*?)\\s*(?:\\[(.*)\\])?\\s*$" ); + QRegularExpressionMatch match = rx.match( input.toString() ); + if ( match.hasMatch() ) { bool xMinOk = false; - ( void )parts.at( 0 ).toDouble( &xMinOk ); + ( void )match.captured( 1 ).toDouble( &xMinOk ); bool xMaxOk = false; - ( void )parts.at( 1 ).toDouble( &xMaxOk ); + ( void )match.captured( 2 ).toDouble( &xMaxOk ); bool yMinOk = false; - ( void )parts.at( 2 ).toDouble( &yMinOk ); + ( void )match.captured( 3 ).toDouble( &yMinOk ); bool yMaxOk = false; - ( void )parts.at( 3 ).toDouble( &yMaxOk ); + ( void )match.captured( 4 ).toDouble( &yMaxOk ); if ( xMinOk && xMaxOk && yMinOk && yMaxOk ) return true; } @@ -1328,7 +1400,7 @@ QString QgsProcessingParameterExtent::valueAsPythonString( const QVariant &value if ( value.canConvert< QgsRectangle >() ) { QgsRectangle r = value.value(); - return QStringLiteral( "QgsRectangle( %1, %2, %3, %4 )" ).arg( qgsDoubleToString( r.xMinimum() ), + return QStringLiteral( "'%1, %3, %2, %4'" ).arg( qgsDoubleToString( r.xMinimum() ), qgsDoubleToString( r.yMinimum() ), qgsDoubleToString( r.xMaximum() ), qgsDoubleToString( r.yMaximum() ) ); @@ -1336,7 +1408,7 @@ QString QgsProcessingParameterExtent::valueAsPythonString( const QVariant &value if ( value.canConvert< QgsReferencedRectangle >() ) { QgsReferencedRectangle r = value.value(); - return QStringLiteral( "QgsReferencedRectangle( QgsRectangle( %1, %2, %3, %4 ), QgsCoordinateReferenceSystem( '%5' ) )" ).arg( qgsDoubleToString( r.xMinimum() ), + return QStringLiteral( "'%1, %3, %2, %4 [%5]'" ).arg( qgsDoubleToString( r.xMinimum() ), qgsDoubleToString( r.yMinimum() ), qgsDoubleToString( r.xMaximum() ), qgsDoubleToString( r.yMaximum() ), r.crs().authid() ); diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index e336a97610d..2f5cdda43b3 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -1844,6 +1844,21 @@ void TestQgsProcessing::parameterExtent() QVERIFY( !def->checkValueIsAcceptable( true ) ); QVERIFY( !def->checkValueIsAcceptable( 5 ) ); QVERIFY( def->checkValueIsAcceptable( "1,2,3,4" ) ); + QVERIFY( def->checkValueIsAcceptable( " 1, 2 ,3 , 4 " ) ); + QVERIFY( def->checkValueIsAcceptable( " 1, 2 ,3 , 4 ", &context ) ); + QVERIFY( def->checkValueIsAcceptable( "-1.1,2,-3,-4" ) ); + QVERIFY( def->checkValueIsAcceptable( "-1.1,2,-3,-4", &context ) ); + QVERIFY( def->checkValueIsAcceptable( "-1.1,-2.2,-3.3,-4.4" ) ); + QVERIFY( def->checkValueIsAcceptable( "-1.1,-2.2,-3.3,-4.4", &context ) ); + QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4[EPSG:4326]" ) ); + QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4[EPSG:4326]", &context ) ); + QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4 [EPSG:4326]" ) ); + QVERIFY( def->checkValueIsAcceptable( "1.1,2,3,4.4 [EPSG:4326]", &context ) ); + QVERIFY( def->checkValueIsAcceptable( " -1.1, -2, -3, -4.4 [EPSG:4326] " ) ); + QVERIFY( def->checkValueIsAcceptable( " -1.1, -2, -3, -4.4 [EPSG:4326] ", &context ) ); + QVERIFY( def->checkValueIsAcceptable( "121774.38859446358,948723.6921024882,-264546.200347173,492749.6672022904 [EPSG:3785]" ) ); + QVERIFY( def->checkValueIsAcceptable( "121774.38859446358,948723.6921024882,-264546.200347173,492749.6672022904 [EPSG:3785]", &context ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); QVERIFY( def->checkValueIsAcceptable( QgsRectangle( 1, 2, 3, 4 ) ) ); @@ -1894,6 +1909,27 @@ void TestQgsProcessing::parameterExtent() QGSCOMPARENEAR( ext.yMinimum(), 3.3, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 ); + // with crs in string + params.insert( "non_optional", QString( "1.1,3.3,2.2,4.4 [EPSG:4326]" ) ); + QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); + ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); + QGSCOMPARENEAR( ext.xMinimum(), 1.1, 0.001 ); + QGSCOMPARENEAR( ext.xMaximum(), 3.3, 0.001 ); + QGSCOMPARENEAR( ext.yMinimum(), 2.2, 0.001 ); + QGSCOMPARENEAR( ext.yMaximum(), 4.4, 0.001 ); + ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 ); + QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 ); + QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 ); + QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 ); + QgsGeometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + QCOMPARE( gExt.geometry()->vertexCount(), 85 ); + ext = gExt.boundingBox(); + QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 ); + QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 ); + QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 ); + QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 ); + // nonsense string params.insert( "non_optional", QString( "i'm not a crs, and nothing you can do will make me one" ) ); QVERIFY( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ).isNull() ); @@ -1913,7 +1949,7 @@ void TestQgsProcessing::parameterExtent() QGSCOMPARENEAR( ext.yMinimum(), 12.2, 0.001 ); QGSCOMPARENEAR( ext.yMaximum(), 14.4, 0.001 ); - QgsGeometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QCOMPARE( gExt.exportToWkt( 1 ), QStringLiteral( "Polygon ((11.1 12.2, 13.3 12.2, 13.3 14.4, 11.1 14.4, 11.1 12.2))" ) ); p.setCrs( QgsCoordinateReferenceSystem( "EPSG:3785" ) ); @@ -1949,8 +1985,9 @@ void TestQgsProcessing::parameterExtent() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( r1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( raster2, context ), QString( "'" ) + testDataDir + QStringLiteral( "landsat.tif'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); - QCOMPARE( def->valueAsPythonString( QgsRectangle( 11, 12, 13, 14 ), context ), QStringLiteral( "QgsRectangle( 11, 12, 13, 14 )" ) ); - QCOMPARE( def->valueAsPythonString( QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( 'EPSG:4326' ) )" ) ); + QCOMPARE( def->valueAsPythonString( QgsRectangle( 11, 12, 13, 14 ), context ), QStringLiteral( "'11, 13, 12, 14'" ) ); + QCOMPARE( def->valueAsPythonString( QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "'11, 13, 12, 14 [EPSG:4326]'" ) ); + QCOMPARE( def->valueAsPythonString( "1,2,3,4 [EPSG:4326]", context ), QStringLiteral( "'1,2,3,4 [EPSG:4326]'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=extent 1,2,3,4" ) ); From 83a8a8d730c380c634070e5f7a8fa6fa99148c0c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 24 Sep 2017 13:30:59 +1000 Subject: [PATCH 09/10] Use text encoded versions of points and extents in history panel Fixes generation of unit tests with point/extent parameters --- .../processing/gui/ExtentSelectionPanel.py | 21 ++++++++----------- .../processing/gui/PointSelectionPanel.py | 12 +++-------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/python/plugins/processing/gui/ExtentSelectionPanel.py b/python/plugins/processing/gui/ExtentSelectionPanel.py index 38f373b1ee3..3ee7ab70bd4 100755 --- a/python/plugins/processing/gui/ExtentSelectionPanel.py +++ b/python/plugins/processing/gui/ExtentSelectionPanel.py @@ -79,10 +79,14 @@ class ExtentSelectionPanel(BASE, WIDGET): if param.defaultValue() is not None: context = createContext() rect = QgsProcessingParameters.parameterAsExtent(param, {param.name(): param.defaultValue()}, context) + crs = QgsProcessingParameters.parameterAsExtentCrs(param, {param.name(): param.defaultValue()}, context) if not rect.isNull(): try: s = '{},{},{},{}'.format( rect.xMinimum(), rect.xMaximum(), rect.yMinimum(), rect.yMaximum()) + if crs.isValid(): + s += ' [' + crs.authid() + ']' + self.crs = crs self.leText.setText(s) except: pass @@ -133,10 +137,6 @@ class ExtentSelectionPanel(BASE, WIDGET): self.tr('Use extent from'), extents, False) if ok: self.setValueFromRect(QgsReferencedRectangle(extentsDict[item]["extent"], QgsCoordinateReferenceSystem(extentsDict[item]["authid"]))) - if extentsDict[item]["authid"] != iface.mapCanvas().mapSettings().destinationCrs().authid(): - iface.messageBar().pushMessage(self.tr("Warning"), - self.tr("The projection of the chosen layer is not the same as canvas projection! The selected extent might not be what was intended."), - QgsMessageBar.WARNING, 8) def selectOnCanvas(self): canvas = iface.mapCanvas() @@ -151,11 +151,14 @@ class ExtentSelectionPanel(BASE, WIDGET): s = '{},{},{},{}'.format( r.xMinimum(), r.xMaximum(), r.yMinimum(), r.yMaximum()) - self.leText.setText(s) try: self.crs = r.crs() except: self.crs = QgsProject.instance().crs() + if self.crs.isValid(): + s += ' [' + self.crs.authid() + ']' + + self.leText.setText(s) self.tool.reset() canvas = iface.mapCanvas() canvas.setMapTool(self.prevMapTool) @@ -165,13 +168,7 @@ class ExtentSelectionPanel(BASE, WIDGET): def getValue(self): if str(self.leText.text()).strip() != '': - try: - parts = self.leText.text().split(',') - parts = [float(p) for p in parts] - r = QgsReferencedRectangle(QgsRectangle(parts[0], parts[2], parts[1], parts[3]), self.crs) - return r - except: - return str(self.leText.text()) + return str(self.leText.text()) else: return None diff --git a/python/plugins/processing/gui/PointSelectionPanel.py b/python/plugins/processing/gui/PointSelectionPanel.py index e8795e4ecf3..076eedfde89 100644 --- a/python/plugins/processing/gui/PointSelectionPanel.py +++ b/python/plugins/processing/gui/PointSelectionPanel.py @@ -82,6 +82,8 @@ class PointSelectionPanel(BASE, WIDGET): def updatePoint(self, point, button): s = '{},{}'.format(point.x(), point.y()) self.crs = QgsProject.instance().crs() + if self.crs.isValid(): + s += ' [' + self.crs.authid() + ']' self.leText.setText(s) def pointPicked(self): @@ -93,15 +95,7 @@ class PointSelectionPanel(BASE, WIDGET): def getValue(self): if str(self.leText.text()).strip() != '': - try: - parts = self.leText.text().split(',') - parts = [float(p) for p in parts] - r = QgsReferencedPointXY(QgsPointXY(parts[0], parts[1]), - self.crs) - return r - - except: - return str(self.leText.text()) + return str(self.leText.text()) else: return None From d72309e42b9c3358aa26ced6089f4105dc1c8a84 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 24 Sep 2017 13:45:54 +1000 Subject: [PATCH 10/10] Fix transform of extent parameter set to match layer's extent --- .../processing/qgsprocessingparameters.cpp | 45 ++++++++++++++++++- tests/src/core/testqgsprocessing.cpp | 21 ++++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index 761224f7445..356e04c1030 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -558,8 +558,22 @@ QgsRectangle QgsProcessingParameters::parameterAsExtent( const QgsProcessingPara // try as layer extent if ( QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( rectText, context ) ) - return layer->extent(); - + { + QgsRectangle rect = layer->extent(); + if ( crs.isValid() && layer->crs().isValid() && crs != layer->crs() ) + { + QgsCoordinateTransform ct( layer->crs(), crs ); + try + { + return ct.transformBoundingBox( rect ); + } + catch ( QgsCsException & ) + { + QgsMessageLog::logMessage( QObject::tr( "Error transforming extent geometry" ) ); + } + } + return rect; + } return QgsRectangle(); } @@ -633,6 +647,27 @@ QgsGeometry QgsProcessingParameters::parameterAsExtentGeometry( const QgsProcess } } + // try as layer extent + if ( QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( rectText, context ) ) + { + QgsRectangle rect = layer->extent(); + QgsGeometry g = QgsGeometry::fromRect( rect ); + if ( crs.isValid() && layer->crs().isValid() && crs != layer->crs() ) + { + g = g.densifyByCount( 20 ); + QgsCoordinateTransform ct( layer->crs(), crs ); + try + { + g.transform( ct ); + } + catch ( QgsCsException & ) + { + QgsMessageLog::logMessage( QObject::tr( "Error transforming extent geometry" ) ); + } + } + return g; + } + return QgsGeometry::fromRect( parameterAsExtent( definition, parameters, context, crs ) ); } @@ -660,6 +695,12 @@ QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsExtentCrs( cons return crs; } + // try as layer crs + if ( QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( valueAsString, context ) ) + { + return layer->crs(); + } + if ( context.project() ) return context.project()->crs(); else diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 2f5cdda43b3..148585269f3 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -1883,15 +1883,34 @@ void TestQgsProcessing::parameterExtent() params.insert( "non_optional", raster1 ); QVERIFY( def->checkValueIsAcceptable( raster1 ) ); QCOMPARE( QgsProcessingParameters::parameterAsExtent( def.get(), params, context ), r1->extent() ); + QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:4326" ) ); + ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) ); + QGSCOMPARENEAR( ext.xMinimum(), 1535375, 100 ); + QGSCOMPARENEAR( ext.xMaximum(), 1535475, 100 ); + QGSCOMPARENEAR( ext.yMinimum(), 5083255, 100 ); + QGSCOMPARENEAR( ext.yMaximum(), 5083355, 100 ); // string representing a non-project layer source params.insert( "non_optional", raster2 ); QVERIFY( def->checkValueIsAcceptable( raster2 ) ); + QCOMPARE( QgsProcessingParameters::parameterAsExtentCrs( def.get(), params, context ).authid(), QStringLiteral( "EPSG:32633" ) ); ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context ); QGSCOMPARENEAR( ext.xMinimum(), 781662.375000, 10 ); QGSCOMPARENEAR( ext.xMaximum(), 793062.375000, 10 ); QGSCOMPARENEAR( ext.yMinimum(), 3339523.125000, 10 ); QGSCOMPARENEAR( ext.yMaximum(), 3350923.125000, 10 ); + ext = QgsProcessingParameters::parameterAsExtent( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) ); + QGSCOMPARENEAR( ext.xMinimum(), 17.924273, 0.01 ); + QGSCOMPARENEAR( ext.xMaximum(), 18.045658, 0.01 ); + QGSCOMPARENEAR( ext.yMinimum(), 30.151856, 0.01 ); + QGSCOMPARENEAR( ext.yMaximum(), 30.257289, 0.01 ); + QgsGeometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:4326" ) ); + QCOMPARE( gExt.geometry()->vertexCount(), 85 ); + ext = gExt.boundingBox(); + QGSCOMPARENEAR( ext.xMinimum(), 17.924273, 0.01 ); + QGSCOMPARENEAR( ext.xMaximum(), 18.045658, 0.01 ); + QGSCOMPARENEAR( ext.yMinimum(), 30.151856, 0.01 ); + QGSCOMPARENEAR( ext.yMaximum(), 30.257289, 0.01 ); // string representation of an extent params.insert( "non_optional", QString( "1.1,2.2,3.3,4.4" ) ); @@ -1922,7 +1941,7 @@ void TestQgsProcessing::parameterExtent() QGSCOMPARENEAR( ext.xMaximum(), 367354, 100 ); QGSCOMPARENEAR( ext.yMinimum(), 244963, 100 ); QGSCOMPARENEAR( ext.yMaximum(), 490287, 100 ); - QgsGeometry gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); + gExt = QgsProcessingParameters::parameterAsExtentGeometry( def.get(), params, context, QgsCoordinateReferenceSystem( "EPSG:3785" ) ); QCOMPARE( gExt.geometry()->vertexCount(), 85 ); ext = gExt.boundingBox(); QGSCOMPARENEAR( ext.xMinimum(), 122451, 100 );