QGIS API Documentation 3.43.0-Master (6c62b930b02)
qgsalgorithmserviceareafromlayer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmserviceareafromlayer.cpp
3 ---------------------
4 begin : July 2018
5 copyright : (C) 2018 by Alexander Bruy
6 email : alexander dot bruy at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgsgeometryutils.h"
21#include "qgsgraphanalyzer.h"
22
24
25QString QgsServiceAreaFromLayerAlgorithm::name() const
26{
27 return QStringLiteral( "serviceareafromlayer" );
28}
29
30QString QgsServiceAreaFromLayerAlgorithm::displayName() const
31{
32 return QObject::tr( "Service area (from layer)" );
33}
34
35QStringList QgsServiceAreaFromLayerAlgorithm::tags() const
36{
37 return QObject::tr( "network,service,area,shortest,fastest" ).split( ',' );
38}
39
40QString QgsServiceAreaFromLayerAlgorithm::shortHelpString() const
41{
42 return QObject::tr( "This algorithm creates a new vector layer with all the edges or parts of "
43 "edges of a network line layer that can be reached within a distance "
44 "or a time, starting from features of a point layer. The distance and "
45 "the time (both referred to as \"travel cost\") must be specified "
46 "respectively in the network layer units or in hours." );
47}
48
49QString QgsServiceAreaFromLayerAlgorithm::shortDescription() const
50{
51 return QObject::tr( "Creates a vector layer with all the edges or parts of "
52 "edges of a network line layer that can be reached within a distance "
53 "or a time, starting from features of a point layer." );
54}
55
56QgsServiceAreaFromLayerAlgorithm *QgsServiceAreaFromLayerAlgorithm::createInstance() const
57{
58 return new QgsServiceAreaFromLayerAlgorithm();
59}
60
61void QgsServiceAreaFromLayerAlgorithm::initAlgorithm( const QVariantMap & )
62{
63 addCommonParams();
64 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "START_POINTS" ), QObject::tr( "Vector layer with start points" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorPoint ) ) );
65
66 auto travelCost = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "TRAVEL_COST" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), Qgis::ProcessingNumberParameterType::Double, 0, true, 0 );
67 travelCost->setFlags( travelCost->flags() | Qgis::ProcessingParameterFlag::Hidden );
68 addParameter( std::move( travelCost ) );
69
70 auto travelCost2 = std::make_unique<QgsProcessingParameterNumber>( QStringLiteral( "TRAVEL_COST2" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), Qgis::ProcessingNumberParameterType::Double, 0, false, 0 );
71 travelCost2->setIsDynamic( true );
72 travelCost2->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Travel Cost" ), QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), QgsPropertyDefinition::DoublePositive ) );
73 travelCost2->setDynamicLayerParameterName( QStringLiteral( "START_POINTS" ) );
74 addParameter( std::move( travelCost2 ) );
75
76 auto includeBounds = std::make_unique<QgsProcessingParameterBoolean>( QStringLiteral( "INCLUDE_BOUNDS" ), QObject::tr( "Include upper/lower bound points" ), false, true );
77 includeBounds->setFlags( includeBounds->flags() | Qgis::ProcessingParameterFlag::Advanced );
78 addParameter( includeBounds.release() );
79
80 std::unique_ptr<QgsProcessingParameterNumber> maxPointDistanceFromNetwork = std::make_unique<QgsProcessingParameterDistance>( QStringLiteral( "POINT_TOLERANCE" ), QObject::tr( "Maximum point distance from network" ), QVariant(), QStringLiteral( "INPUT" ), true, 0 );
81 maxPointDistanceFromNetwork->setFlags( maxPointDistanceFromNetwork->flags() | Qgis::ProcessingParameterFlag::Advanced );
82 maxPointDistanceFromNetwork->setHelp( QObject::tr( "Specifies an optional limit on the distance from the points to the network layer. If a point is further from the network than this distance it will be treated as non-routable." ) );
83 addParameter( maxPointDistanceFromNetwork.release() );
84
85 auto outputLines = std::make_unique<QgsProcessingParameterFeatureSink>( QStringLiteral( "OUTPUT_LINES" ), QObject::tr( "Service area (lines)" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true );
86 outputLines->setCreateByDefault( true );
87 addParameter( outputLines.release() );
88
89 auto outputPoints = std::make_unique<QgsProcessingParameterFeatureSink>( QStringLiteral( "OUTPUT" ), QObject::tr( "Service area (boundary nodes)" ), Qgis::ProcessingSourceType::VectorPoint, QVariant(), true );
90 outputPoints->setCreateByDefault( false );
91 addParameter( outputPoints.release() );
92
93 auto outputNonRoutable = std::make_unique<QgsProcessingParameterFeatureSink>( QStringLiteral( "OUTPUT_NON_ROUTABLE" ), QObject::tr( "Non-routable features" ), Qgis::ProcessingSourceType::VectorPoint, QVariant(), true );
94 outputNonRoutable->setHelp( QObject::tr( "An optional output which will be used to store any input features which could not be routed (e.g. those which are too far from the network layer)." ) );
95 outputNonRoutable->setCreateByDefault( false );
96 addParameter( outputNonRoutable.release() );
97}
98
99QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
100{
101 loadCommonParams( parameters, context, feedback );
102
103 std::unique_ptr<QgsProcessingFeatureSource> startPoints( parameterAsSource( parameters, QStringLiteral( "START_POINTS" ), context ) );
104 if ( !startPoints )
105 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "START_POINTS" ) ) );
106
107 // use older deprecated travel cost style if specified, to maintain old api
108 const bool useOldTravelCost = parameters.value( QStringLiteral( "TRAVEL_COST" ) ).isValid();
109 const double defaultTravelCost = parameterAsDouble( parameters, useOldTravelCost ? QStringLiteral( "TRAVEL_COST" ) : QStringLiteral( "TRAVEL_COST2" ), context );
110
111 const bool dynamicTravelCost = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "TRAVEL_COST2" ) );
112 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, startPoints.get() );
113 QgsProperty travelCostProperty;
114 if ( dynamicTravelCost )
115 {
116 travelCostProperty = parameters.value( QStringLiteral( "TRAVEL_COST2" ) ).value<QgsProperty>();
117 }
118
119 const int strategy = parameterAsInt( parameters, QStringLiteral( "STRATEGY" ), context );
120 const double multiplier = ( strategy && !useOldTravelCost ) ? mMultiplier : 1;
121
122 bool includeBounds = true; // default to true to maintain 3.0 API
123 if ( parameters.contains( QStringLiteral( "INCLUDE_BOUNDS" ) ) )
124 {
125 includeBounds = parameterAsBool( parameters, QStringLiteral( "INCLUDE_BOUNDS" ), context );
126 }
127
128 QVector<QgsPointXY> points;
129 QHash<int, QgsFeature> sourceFeatures;
130 loadPoints( startPoints.get(), &points, nullptr, context, feedback, &sourceFeatures );
131
132 feedback->pushInfo( QObject::tr( "Building graph…" ) );
133 QVector<QgsPointXY> snappedPoints;
134 mDirector->makeGraph( mBuilder.get(), points, snappedPoints, feedback );
135
136 feedback->pushInfo( QObject::tr( "Calculating service areas…" ) );
137 std::unique_ptr<QgsGraph> graph( mBuilder->takeGraph() );
138
139 QgsFields newFields;
140 newFields.append( QgsField( QStringLiteral( "type" ), QMetaType::Type::QString ) );
141 newFields.append( QgsField( QStringLiteral( "start" ), QMetaType::Type::QString ) );
142 QgsFields fields = QgsProcessingUtils::combineFields( startPoints->fields(), newFields );
143
144 QString pointsSinkId;
145 std::unique_ptr<QgsFeatureSink> pointsSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, pointsSinkId, fields, Qgis::WkbType::MultiPoint, mNetwork->sourceCrs() ) );
146
147 QString linesSinkId;
148 std::unique_ptr<QgsFeatureSink> linesSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LINES" ), context, linesSinkId, fields, Qgis::WkbType::MultiLineString, mNetwork->sourceCrs() ) );
149
150 QString nonRoutableSinkId;
151 std::unique_ptr<QgsFeatureSink> nonRoutableSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_NON_ROUTABLE" ), context, nonRoutableSinkId, startPoints->fields(), Qgis::WkbType::Point, mNetwork->sourceCrs() ) );
152
153 const double pointDistanceThreshold = parameters.value( QStringLiteral( "POINT_TOLERANCE" ) ).isValid() ? parameterAsDouble( parameters, QStringLiteral( "POINT_TOLERANCE" ), context ) : -1;
154
155 int idxStart;
156 QVector<int> tree;
157 QVector<double> costs;
158
159 int inboundEdgeIndex;
160 double startVertexCost, endVertexCost;
161 QgsPointXY startPoint, endPoint;
162 QgsGraphEdge edge;
163
164 QgsFeature feat;
165 QgsAttributes attributes;
166
167 const double step = snappedPoints.size() > 0 ? 100.0 / snappedPoints.size() : 1;
168 for ( int i = 0; i < snappedPoints.size(); i++ )
169 {
170 if ( feedback->isCanceled() )
171 {
172 break;
173 }
174
175 double travelCost = defaultTravelCost;
176 if ( dynamicTravelCost )
177 {
178 expressionContext.setFeature( sourceFeatures.value( i + 1 ) );
179 travelCost = travelCostProperty.valueAsDouble( expressionContext, travelCost );
180 }
181 travelCost *= multiplier;
182
183 const QgsPointXY snappedPoint = snappedPoints.at( i );
184 const QgsPointXY originalPoint = points.at( i );
185
186 if ( pointDistanceThreshold >= 0 )
187 {
188 double distancePointToNetwork = 0;
189 try
190 {
191 distancePointToNetwork = mBuilder->distanceArea()->measureLine( originalPoint, snappedPoint );
192 }
193 catch ( QgsCsException & )
194 {
195 throw QgsProcessingException( QObject::tr( "An error occurred while calculating length" ) );
196 }
197
198 if ( distancePointToNetwork > pointDistanceThreshold )
199 {
200 feedback->pushWarning( QObject::tr( "Point is too far from the network layer (%1, maximum permitted is %2)" ).arg( distancePointToNetwork ).arg( pointDistanceThreshold ) );
201 if ( nonRoutableSink )
202 {
203 feat.setGeometry( QgsGeometry::fromPointXY( originalPoint ) );
204 attributes = sourceFeatures.value( i + 1 ).attributes();
205 feat.setAttributes( attributes );
206 if ( !nonRoutableSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
207 throw QgsProcessingException( writeFeatureError( nonRoutableSink.get(), parameters, QStringLiteral( "OUTPUT_NON_ROUTABLE" ) ) );
208 }
209
210 feedback->setProgress( i * step );
211 continue;
212 }
213 }
214
215 const QString originalPointString = originalPoint.toString();
216
217 idxStart = graph->findVertex( snappedPoint );
218
219 QgsGraphAnalyzer::dijkstra( graph.get(), idxStart, 0, &tree, &costs );
220
221 QgsMultiPointXY areaPoints;
222 QgsMultiPolylineXY lines;
223 QSet<int> vertices;
224
225 for ( int j = 0; j < costs.size(); j++ )
226 {
227 inboundEdgeIndex = tree.at( j );
228
229 if ( inboundEdgeIndex == -1 && j != idxStart )
230 {
231 // unreachable vertex
232 continue;
233 }
234
235 startVertexCost = costs.at( j );
236 if ( startVertexCost > travelCost )
237 {
238 // vertex is too expensive, discard
239 continue;
240 }
241
242 vertices.insert( j );
243 startPoint = graph->vertex( j ).point();
244
245 // find all edges coming from this vertex
246 const QList<int> outgoingEdges = graph->vertex( j ).outgoingEdges();
247 for ( int edgeId : outgoingEdges )
248 {
249 edge = graph->edge( edgeId );
250 endVertexCost = startVertexCost + edge.cost( 0 ).toDouble();
251 endPoint = graph->vertex( edge.toVertex() ).point();
252 if ( endVertexCost <= travelCost )
253 {
254 // end vertex is cheap enough to include
255 vertices.insert( edge.toVertex() );
256 lines.push_back( QgsPolylineXY() << startPoint << endPoint );
257 }
258 else
259 {
260 // travelCost sits somewhere on this edge, interpolate position
261 QgsPointXY interpolatedEndPoint = QgsGeometryUtils::interpolatePointOnLineByValue( startPoint.x(), startPoint.y(), startVertexCost, endPoint.x(), endPoint.y(), endVertexCost, travelCost );
262
263 areaPoints.push_back( interpolatedEndPoint );
264 lines.push_back( QgsPolylineXY() << startPoint << interpolatedEndPoint );
265 }
266 } // edges
267 } // costs
268
269 // convert to list and sort to maintain same order of points between algorithm runs
270 QList<int> verticesList = qgis::setToList( vertices );
271 areaPoints.reserve( verticesList.size() );
272 std::sort( verticesList.begin(), verticesList.end() );
273 for ( int v : verticesList )
274 {
275 areaPoints.push_back( graph->vertex( v ).point() );
276 }
277
278 if ( pointsSink )
279 {
280 QgsGeometry geomPoints = QgsGeometry::fromMultiPointXY( areaPoints );
281 feat.setGeometry( geomPoints );
282 attributes = sourceFeatures.value( i + 1 ).attributes();
283 attributes << QStringLiteral( "within" ) << originalPointString;
284 feat.setAttributes( attributes );
285 if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
286 throw QgsProcessingException( writeFeatureError( pointsSink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
287
288 if ( includeBounds )
289 {
290 QgsMultiPointXY upperBoundary, lowerBoundary;
291 QVector<int> nodes;
292 nodes.reserve( costs.size() );
293
294 int vertexId;
295 for ( int v = 0; v < costs.size(); v++ )
296 {
297 if ( costs.at( v ) > travelCost && tree.at( v ) != -1 )
298 {
299 vertexId = graph->edge( tree.at( v ) ).fromVertex();
300 if ( costs.at( vertexId ) <= travelCost )
301 {
302 nodes.push_back( v );
303 }
304 }
305 } // costs
306
307 upperBoundary.reserve( nodes.size() );
308 lowerBoundary.reserve( nodes.size() );
309 for ( int n : std::as_const( nodes ) )
310 {
311 upperBoundary.push_back( graph->vertex( graph->edge( tree.at( n ) ).toVertex() ).point() );
312 lowerBoundary.push_back( graph->vertex( graph->edge( tree.at( n ) ).fromVertex() ).point() );
313 } // nodes
314
315 QgsGeometry geomUpper = QgsGeometry::fromMultiPointXY( upperBoundary );
316 QgsGeometry geomLower = QgsGeometry::fromMultiPointXY( lowerBoundary );
317
318 feat.setGeometry( geomUpper );
319 attributes = sourceFeatures.value( i + 1 ).attributes();
320 attributes << QStringLiteral( "upper" ) << originalPointString;
321 feat.setAttributes( attributes );
322 if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
323 throw QgsProcessingException( writeFeatureError( pointsSink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
324
325 feat.setGeometry( geomLower );
326 attributes = sourceFeatures.value( i + 1 ).attributes();
327 attributes << QStringLiteral( "lower" ) << originalPointString;
328 feat.setAttributes( attributes );
329 if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
330 throw QgsProcessingException( writeFeatureError( pointsSink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
331 } // includeBounds
332 }
333
334 if ( linesSink )
335 {
337 feat.setGeometry( geomLines );
338 attributes = sourceFeatures.value( i + 1 ).attributes();
339 attributes << QStringLiteral( "lines" ) << originalPointString;
340 feat.setAttributes( attributes );
341 if ( !linesSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
342 throw QgsProcessingException( writeFeatureError( linesSink.get(), parameters, QStringLiteral( "OUTPUT_LINES" ) ) );
343 }
344
345 feedback->setProgress( i * step );
346 } // snappedPoints
347
348 QVariantMap outputs;
349 if ( pointsSink )
350 {
351 pointsSink->finalize();
352 outputs.insert( QStringLiteral( "OUTPUT" ), pointsSinkId );
353 }
354 if ( linesSink )
355 {
356 linesSink->finalize();
357 outputs.insert( QStringLiteral( "OUTPUT_LINES" ), linesSinkId );
358 }
359 if ( nonRoutableSink )
360 {
361 nonRoutableSink->finalize();
362 outputs.insert( QStringLiteral( "OUTPUT_NON_ROUTABLE" ), nonRoutableSinkId );
363 }
364
365 return outputs;
366}
367
@ VectorPoint
Vector point layers.
@ VectorLine
Vector line layers.
@ MultiPoint
MultiPoint.
@ MultiLineString
MultiLineString.
@ Hidden
Parameter is hidden and should not be shown to users.
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
A vector of attributes.
Custom exception class for Coordinate Reference System related exceptions.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:61
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:70
static QgsPointXY interpolatePointOnLineByValue(double x1, double y1, double v1, double x2, double y2, double v2, double value)
Interpolates the position of a point along the line from (x1, y1) to (x2, y2).
A geometry is the spatial representation of a feature.
static QgsGeometry fromMultiPolylineXY(const QgsMultiPolylineXY &multiline)
Creates a new geometry from a QgsMultiPolylineXY object.
static QgsGeometry fromMultiPointXY(const QgsMultiPointXY &multipoint)
Creates a new geometry from a QgsMultiPointXY object.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
static void dijkstra(const QgsGraph *source, int startVertexIdx, int criterionNum, QVector< int > *resultTree=nullptr, QVector< double > *resultCost=nullptr)
Solve shortest path problem using Dijkstra algorithm.
Represents an edge in a graph.
Definition qgsgraph.h:44
int toVertex() const
Returns the index of the vertex at the end of this edge.
Definition qgsgraph.cpp:184
QVariant cost(int strategyIndex) const
Returns edge cost calculated using specified strategy.
Definition qgsgraph.cpp:169
Represents a 2D point.
Definition qgspointxy.h:60
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
An input feature source (such as vector layers) parameter for processing algorithms.
static bool isDynamic(const QVariantMap &parameters, const QString &name)
Returns true if the parameter with matching name is a dynamic parameter, and must be evaluated once f...
static QgsFields combineFields(const QgsFields &fieldsA, const QgsFields &fieldsB, const QString &fieldsBPrefix=QString())
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
Definition for a property.
Definition qgsproperty.h:45
@ DoublePositive
Positive double value (including 0)
Definition qgsproperty.h:56
A store for object properties.
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
double valueAsDouble(const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property and interprets it as a double.
QVector< QgsPolylineXY > QgsMultiPolylineXY
A collection of QgsPolylines that share a common collection of attributes.
Definition qgsgeometry.h:84
QVector< QgsPointXY > QgsMultiPointXY
A collection of QgsPoints that share a common collection of attributes.
Definition qgsgeometry.h:80
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition qgsgeometry.h:62