From e7e1311472a277be14c28c27d2b111a7366b257c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 29 May 2025 11:59:58 +1000 Subject: [PATCH] [processing] Allow dynamic travel cost for service area from layer alg It doesn't make sense to force a single cost for all input points, so make this parameter dynamic so that the cost can be eg taken from an input layer field Fixes #62004 --- .../expected/service_area_dynamic.gml | 50 +++++++++++++ .../expected/service_area_dynamic.xsd | 72 +++++++++++++++++++ .../tests/testdata/qgis_algorithm_tests2.yaml | 37 ++++++++++ .../qgsalgorithmnetworkanalysisbase.cpp | 10 ++- .../qgsalgorithmnetworkanalysisbase.h | 2 +- .../qgsalgorithmserviceareafromlayer.cpp | 47 ++++++++---- .../qgsalgorithmshortestpathlayertopoint.cpp | 2 +- .../qgsalgorithmshortestpathpointtolayer.cpp | 2 +- 8 files changed, 202 insertions(+), 20 deletions(-) create mode 100644 python/plugins/processing/tests/testdata/expected/service_area_dynamic.gml create mode 100644 python/plugins/processing/tests/testdata/expected/service_area_dynamic.xsd diff --git a/python/plugins/processing/tests/testdata/expected/service_area_dynamic.gml b/python/plugins/processing/tests/testdata/expected/service_area_dynamic.gml new file mode 100644 index 00000000000..6454a12e396 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/service_area_dynamic.gml @@ -0,0 +1,50 @@ + + + 1001182.63176944 6220409.289059481004055.89106713 6222884.39323789 + + + + 1001182.63176944 6220409.289059481001275.12302677 6220462.84851569 + 1001186.34740161 6220459.66433954 1001182.63176944 6220456.480004971001186.34740161 6220459.66433954 1001190.06317444 6220462.848515691001186.34740161 6220459.66433954 1001190.60339941 6220457.249295091001275.12302677 6220409.28905948 1001269.29926109 6220412.59372591001269.29926109 6220412.5937259 1001275.12302677 6220409.289059481001269.29926109 6220412.5937259 1001186.34740161 6220459.66433954 + route_points.0 + 1 + lines + 1001269.16642, 6220412.35961 + + + + + 1002015.98051499 6221232.832670621002211.65419834 6221397.05152815 + 1002103.66905047 6221324.68154126 1002015.98051499 6221397.051528151002103.66905047 6221324.68154126 1002169.82194678 6221268.413906911002103.66905047 6221324.68154126 1002194.14909744 6221393.530000761002211.65419834 6221232.83267062 1002169.82194678 6221268.413906911002169.82194678 6221268.41390691 1002103.66905047 6221324.681541261002169.82194678 6221268.41390691 1002211.65419834 6221232.83267062 + route_points.1 + 2 + lines + 1002173.3505, 6221272.56237 + + + + + 1002943.55552255 6222585.336820591003078.1256774 6222884.39323789 + 1002943.55552255 6222884.39323789 1002947.79277674 6222869.796417531003002.42639578 6222681.59030886 1002973.19915934 6222782.274549341003002.42639578 6222681.59030886 1003031.23572156 6222655.694694691003002.42639578 6222681.59030886 1002960.87144508 6222585.336820591003002.42639578 6222681.59030886 1003076.97655481 6222712.845044981003031.23572156 6222655.69469469 1003002.42639578 6222681.590308861003076.97655481 6222712.84504498 1003054.83943287 6222703.564180741003076.97655481 6222712.84504498 1003078.1256774 6222736.821422761002947.79277674 6222869.79641753 1002943.55552255 6222884.393237891002947.79277674 6222869.79641753 1003002.42639578 6222681.59030886 + route_points.2 + 3 + lines + 1002948.69578, 6222870.05855 + + + + + 1003721.40592142 6221869.38603381004055.89106713 6222127.01927093 + 1003946.78684547 6221880.1876508 1003941.35220452 6221871.199815521003946.78684547 6221880.1876508 1003956.9497919 6221877.536024081003946.78684547 6221880.1876508 1003936.62449785 6221882.841529051003775.57324976 6221922.76175725 1003759.50508048 6221869.38603381003775.57324976 6221922.76175725 1003829.923686 6221910.384831111003775.57324976 6221922.76175725 1003721.40592142 6221935.91665291003839.54015074 6221908.19492644 1003835.88711567 6222011.85497131003839.54015074 6221908.19492644 1003946.78684547 6221880.18765081003839.54015074 6221908.19492644 1003775.57324976 6221922.761757251003835.88711567 6222011.8549713 1003839.54015074 6221908.194926441003835.88711567 6222011.8549713 1003866.67429938 6222081.064540871003882.2233509 6222116.01879887 1003866.67429938 6222081.064540871003882.2233509 6222116.01879887 1003899.60754345 6222127.019270931003899.60754345 6222127.01927093 1003882.2233509 6222116.018798871003899.60754345 6222127.01927093 1004016.31337555 6221991.866569821004016.31337555 6221991.86656982 1003974.86248821 6222039.869307021004016.31337555 6221991.86656982 1004055.89106713 6221942.307982231003866.67429938 6222081.06454087 1003835.88711567 6222011.85497131003866.67429938 6222081.06454087 1003882.2233509 6222116.01879887 + route_points.2 + 3 + lines + 1003863.44041, 6222082.50311 + + + diff --git a/python/plugins/processing/tests/testdata/expected/service_area_dynamic.xsd b/python/plugins/processing/tests/testdata/expected/service_area_dynamic.xsd new file mode 100644 index 00000000000..9b38f157f1a --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/service_area_dynamic.xsd @@ -0,0 +1,72 @@ + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml index dd65231793f..ab202f71bf2 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests2.yaml @@ -2444,6 +2444,43 @@ tests: end: skip pk: - d + - algorithm: native:serviceareafromlayer + name: Service area from layer dynamic cost + ellipsoid: WGS84 + params: + DEFAULT_DIRECTION: 2 + DEFAULT_SPEED: 50.0 + INCLUDE_BOUNDS: false + INPUT: + name: roads.gml|layername=roads + type: vector + START_POINTS: + name: custom/route_points.gml|layername=route_points + type: vector + STRATEGY: 0 + TOLERANCE: 0.0 + TRAVEL_COST2: + expression: d*100 + type: property + VALUE_BACKWARD: '' + VALUE_BOTH: '' + VALUE_FORWARD: '' + results: + OUTPUT_LINES: + name: expected/service_area_dynamic.gml + type: vector + compare: + ignore_crs_check: true + geometry: + precision: 2 + fields: + cost: + precision: 2 + start: skip + end: skip + pk: + - d + - type - algorithm: native:createattributeindex name: Create attribute index diff --git a/src/analysis/processing/qgsalgorithmnetworkanalysisbase.cpp b/src/analysis/processing/qgsalgorithmnetworkanalysisbase.cpp index b1e21a44c0c..17f361cad40 100644 --- a/src/analysis/processing/qgsalgorithmnetworkanalysisbase.cpp +++ b/src/analysis/processing/qgsalgorithmnetworkanalysisbase.cpp @@ -130,7 +130,7 @@ void QgsNetworkAnalysisAlgorithmBase::loadCommonParams( const QVariantMap ¶m mBuilder = std::make_unique( mNetwork->sourceCrs(), true, tolerance, context.ellipsoid() ); } -void QgsNetworkAnalysisAlgorithmBase::loadPoints( QgsFeatureSource *source, QVector &points, QHash &attributes, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +void QgsNetworkAnalysisAlgorithmBase::loadPoints( QgsFeatureSource *source, QVector *points, QHash *attributes, QgsProcessingContext &context, QgsProcessingFeedback *feedback, QHash *featureHash ) { feedback->pushInfo( QObject::tr( "Loading points…" ) ); @@ -156,8 +156,12 @@ void QgsNetworkAnalysisAlgorithmBase::loadPoints( QgsFeatureSource *source, QVec QgsAbstractGeometry::vertex_iterator it = geom.vertices_begin(); while ( it != geom.vertices_end() ) { - points.push_back( QgsPointXY( *it ) ); - attributes.insert( pointId, feat.attributes() ); + if ( points ) + points->push_back( QgsPointXY( *it ) ); + if ( attributes ) + attributes->insert( pointId, feat.attributes() ); + if ( featureHash ) + featureHash->insert( pointId, feat ); it++; pointId++; } diff --git a/src/analysis/processing/qgsalgorithmnetworkanalysisbase.h b/src/analysis/processing/qgsalgorithmnetworkanalysisbase.h index 4ebffdd652e..e36773189a5 100644 --- a/src/analysis/processing/qgsalgorithmnetworkanalysisbase.h +++ b/src/analysis/processing/qgsalgorithmnetworkanalysisbase.h @@ -56,7 +56,7 @@ class QgsNetworkAnalysisAlgorithmBase : public QgsProcessingAlgorithm /** * Loads point from the feature source for further processing. */ - void loadPoints( QgsFeatureSource *source, QVector &points, QHash &attributes, QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + void loadPoints( QgsFeatureSource *source, QVector *points, QHash *attributes, QgsProcessingContext &context, QgsProcessingFeedback *feedback, QHash *features ); std::unique_ptr mNetwork; QgsVectorLayerDirector *mDirector = nullptr; diff --git a/src/analysis/processing/qgsalgorithmserviceareafromlayer.cpp b/src/analysis/processing/qgsalgorithmserviceareafromlayer.cpp index 673c540103b..244a8d54df2 100644 --- a/src/analysis/processing/qgsalgorithmserviceareafromlayer.cpp +++ b/src/analysis/processing/qgsalgorithmserviceareafromlayer.cpp @@ -65,9 +65,13 @@ void QgsServiceAreaFromLayerAlgorithm::initAlgorithm( const QVariantMap & ) auto travelCost = std::make_unique( QStringLiteral( "TRAVEL_COST" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), Qgis::ProcessingNumberParameterType::Double, 0, true, 0 ); travelCost->setFlags( travelCost->flags() | Qgis::ProcessingParameterFlag::Hidden ); - addParameter( travelCost.release() ); + addParameter( std::move( travelCost ) ); - addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TRAVEL_COST2" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), Qgis::ProcessingNumberParameterType::Double, 0, false, 0 ) ); + auto travelCost2 = std::make_unique( QStringLiteral( "TRAVEL_COST2" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), Qgis::ProcessingNumberParameterType::Double, 0, false, 0 ); + travelCost2->setIsDynamic( true ); + travelCost2->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Travel Cost" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), QgsPropertyDefinition::DoublePositive ) ); + travelCost2->setDynamicLayerParameterName( QStringLiteral( "START_POINTS" ) ); + addParameter( std::move( travelCost2 ) ); auto includeBounds = std::make_unique( QStringLiteral( "INCLUDE_BOUNDS" ), QObject::tr( "Include upper/lower bound points" ), false, true ); includeBounds->setFlags( includeBounds->flags() | Qgis::ProcessingParameterFlag::Advanced ); @@ -96,17 +100,24 @@ QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMa { loadCommonParams( parameters, context, feedback ); - std::unique_ptr startPoints( parameterAsSource( parameters, QStringLiteral( "START_POINTS" ), context ) ); + std::unique_ptr startPoints( parameterAsSource( parameters, QStringLiteral( "START_POINTS" ), context ) ); if ( !startPoints ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "START_POINTS" ) ) ); // use older deprecated travel cost style if specified, to maintain old api const bool useOldTravelCost = parameters.value( QStringLiteral( "TRAVEL_COST" ) ).isValid(); - double travelCost = parameterAsDouble( parameters, useOldTravelCost ? QStringLiteral( "TRAVEL_COST" ) : QStringLiteral( "TRAVEL_COST2" ), context ); + const double defaultTravelCost = parameterAsDouble( parameters, useOldTravelCost ? QStringLiteral( "TRAVEL_COST" ) : QStringLiteral( "TRAVEL_COST2" ), context ); - int strategy = parameterAsInt( parameters, QStringLiteral( "STRATEGY" ), context ); - if ( strategy && !useOldTravelCost ) - travelCost *= mMultiplier; + const bool dynamicTravelCost = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "TRAVEL_COST2" ) ); + QgsExpressionContext expressionContext = createExpressionContext( parameters, context, startPoints.get() ); + QgsProperty travelCostProperty; + if ( dynamicTravelCost ) + { + travelCostProperty = parameters.value( QStringLiteral( "TRAVEL_COST2" ) ).value(); + } + + const int strategy = parameterAsInt( parameters, QStringLiteral( "STRATEGY" ), context ); + const double multiplier = ( strategy && !useOldTravelCost ) ? mMultiplier : 1; bool includeBounds = true; // default to true to maintain 3.0 API if ( parameters.contains( QStringLiteral( "INCLUDE_BOUNDS" ) ) ) @@ -115,8 +126,8 @@ QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMa } QVector points; - QHash sourceAttributes; - loadPoints( startPoints.get(), points, sourceAttributes, context, feedback ); + QHash sourceFeatures; + loadPoints( startPoints.get(), &points, nullptr, context, feedback, &sourceFeatures ); feedback->pushInfo( QObject::tr( "Building graph…" ) ); QVector snappedPoints; @@ -161,6 +172,14 @@ QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMa break; } + double travelCost = defaultTravelCost; + if ( dynamicTravelCost ) + { + expressionContext.setFeature( sourceFeatures.value( i + 1 ) ); + travelCost = travelCostProperty.valueAsDouble( expressionContext, travelCost ); + } + travelCost *= multiplier; + const QgsPointXY snappedPoint = snappedPoints.at( i ); const QgsPointXY originalPoint = points.at( i ); @@ -182,7 +201,7 @@ QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMa if ( nonRoutableSink ) { feat.setGeometry( QgsGeometry::fromPointXY( originalPoint ) ); - attributes = sourceAttributes.value( i + 1 ); + attributes = sourceFeatures.value( i + 1 ).attributes(); feat.setAttributes( attributes ); if ( !nonRoutableSink->addFeature( feat, QgsFeatureSink::FastInsert ) ) throw QgsProcessingException( writeFeatureError( nonRoutableSink.get(), parameters, QStringLiteral( "OUTPUT_NON_ROUTABLE" ) ) ); @@ -260,7 +279,7 @@ QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMa { QgsGeometry geomPoints = QgsGeometry::fromMultiPointXY( areaPoints ); feat.setGeometry( geomPoints ); - attributes = sourceAttributes.value( i + 1 ); + attributes = sourceFeatures.value( i + 1 ).attributes(); attributes << QStringLiteral( "within" ) << originalPointString; feat.setAttributes( attributes ); if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) ) @@ -297,14 +316,14 @@ QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMa QgsGeometry geomLower = QgsGeometry::fromMultiPointXY( lowerBoundary ); feat.setGeometry( geomUpper ); - attributes = sourceAttributes.value( i + 1 ); + attributes = sourceFeatures.value( i + 1 ).attributes(); attributes << QStringLiteral( "upper" ) << originalPointString; feat.setAttributes( attributes ); if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) ) throw QgsProcessingException( writeFeatureError( pointsSink.get(), parameters, QStringLiteral( "OUTPUT" ) ) ); feat.setGeometry( geomLower ); - attributes = sourceAttributes.value( i + 1 ); + attributes = sourceFeatures.value( i + 1 ).attributes(); attributes << QStringLiteral( "lower" ) << originalPointString; feat.setAttributes( attributes ); if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) ) @@ -316,7 +335,7 @@ QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMa { QgsGeometry geomLines = QgsGeometry::fromMultiPolylineXY( lines ); feat.setGeometry( geomLines ); - attributes = sourceAttributes.value( i + 1 ); + attributes = sourceFeatures.value( i + 1 ).attributes(); attributes << QStringLiteral( "lines" ) << originalPointString; feat.setAttributes( attributes ); if ( !linesSink->addFeature( feat, QgsFeatureSink::FastInsert ) ) diff --git a/src/analysis/processing/qgsalgorithmshortestpathlayertopoint.cpp b/src/analysis/processing/qgsalgorithmshortestpathlayertopoint.cpp index aee138dc92e..f890ecee198 100644 --- a/src/analysis/processing/qgsalgorithmshortestpathlayertopoint.cpp +++ b/src/analysis/processing/qgsalgorithmshortestpathlayertopoint.cpp @@ -103,7 +103,7 @@ QVariantMap QgsShortestPathLayerToPointAlgorithm::processAlgorithm( const QVaria QVector points; points.push_front( endPoint ); QHash sourceAttributes; - loadPoints( startPoints.get(), points, sourceAttributes, context, feedback ); + loadPoints( startPoints.get(), &points, &sourceAttributes, context, feedback, nullptr ); feedback->pushInfo( QObject::tr( "Building graph…" ) ); QVector snappedPoints; diff --git a/src/analysis/processing/qgsalgorithmshortestpathpointtolayer.cpp b/src/analysis/processing/qgsalgorithmshortestpathpointtolayer.cpp index 3b69077b836..15fec25c2db 100644 --- a/src/analysis/processing/qgsalgorithmshortestpathpointtolayer.cpp +++ b/src/analysis/processing/qgsalgorithmshortestpathpointtolayer.cpp @@ -108,7 +108,7 @@ QVariantMap QgsShortestPathPointToLayerAlgorithm::processAlgorithm( const QVaria QVector points; points.push_front( startPoint ); QHash sourceAttributes; - loadPoints( endPoints.get(), points, sourceAttributes, context, feedback ); + loadPoints( endPoints.get(), &points, &sourceAttributes, context, feedback, nullptr ); feedback->pushInfo( QObject::tr( "Building graph…" ) ); QVector snappedPoints;