QGIS API Documentation 4.3.0-Master (2b7e6c9893e)
Loading...
Searching...
No Matches
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
23#include <QString>
24
25using namespace Qt::StringLiterals;
26
28
29QString QgsServiceAreaFromLayerAlgorithm::name() const
30{
31 return u"serviceareafromlayer"_s;
32}
33
34QString QgsServiceAreaFromLayerAlgorithm::displayName() const
35{
36 return QObject::tr( "Service area (from layer)" );
37}
38
39QStringList QgsServiceAreaFromLayerAlgorithm::tags() const
40{
41 return QObject::tr( "network,service,area,shortest,fastest" ).split( ',' );
42}
43
44QString QgsServiceAreaFromLayerAlgorithm::shortHelpString() const
45{
46 return QObject::tr(
47 "This algorithm creates a new vector layer with all the edges or parts of "
48 "edges of a network line layer that can be reached within a distance "
49 "or a time, starting from features of a point layer. The distance and "
50 "the time (both referred to as \"travel cost\") must be specified "
51 "respectively in the network layer units or in hours."
52 );
53}
54
55QString QgsServiceAreaFromLayerAlgorithm::shortDescription() const
56{
57 return QObject::tr(
58 "Creates a vector layer with all the edges or parts of "
59 "edges of a network line layer that can be reached within a distance "
60 "or a time, starting from features of a point layer."
61 );
62}
63
64QgsServiceAreaFromLayerAlgorithm *QgsServiceAreaFromLayerAlgorithm::createInstance() const
65{
66 return new QgsServiceAreaFromLayerAlgorithm();
67}
68
69void QgsServiceAreaFromLayerAlgorithm::initAlgorithm( const QVariantMap & )
70{
71 addCommonParams();
72 addParameter( new QgsProcessingParameterFeatureSource( u"START_POINTS"_s, QObject::tr( "Vector layer with start points" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::VectorPoint ) ) );
73
74 auto travelCost
75 = std::make_unique<QgsProcessingParameterNumber>( u"TRAVEL_COST"_s, QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), Qgis::ProcessingNumberParameterType::Double, 0, true, 0 );
76 travelCost->setFlags( travelCost->flags() | Qgis::ProcessingParameterFlag::Hidden );
77 addParameter( std::move( travelCost ) );
78
79 auto travelCost2 = std::make_unique<
80 QgsProcessingParameterNumber>( u"TRAVEL_COST2"_s, QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), Qgis::ProcessingNumberParameterType::Double, 0, false, 0 );
81 travelCost2->setIsDynamic( true );
82 travelCost2->setDynamicPropertyDefinition( QgsPropertyDefinition( u"Travel Cost"_s, QObject::tr( "Travel cost (distance for 'Shortest', time for 'Fastest')" ), QgsPropertyDefinition::DoublePositive ) );
83 travelCost2->setDynamicLayerParameterName( u"START_POINTS"_s );
84 addParameter( std::move( travelCost2 ) );
85
86 auto includeBounds = std::make_unique<QgsProcessingParameterBoolean>( u"INCLUDE_BOUNDS"_s, QObject::tr( "Include upper/lower bound points" ), false, true );
87 includeBounds->setFlags( includeBounds->flags() | Qgis::ProcessingParameterFlag::Advanced );
88 addParameter( includeBounds.release() );
89
90 std::unique_ptr<QgsProcessingParameterNumber> maxPointDistanceFromNetwork
91 = std::make_unique<QgsProcessingParameterDistance>( u"POINT_TOLERANCE"_s, QObject::tr( "Maximum point distance from network" ), QVariant(), u"INPUT"_s, true, 0 );
92 maxPointDistanceFromNetwork->setFlags( maxPointDistanceFromNetwork->flags() | Qgis::ProcessingParameterFlag::Advanced );
93 maxPointDistanceFromNetwork->setHelp(
94 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." )
95 );
96 addParameter( maxPointDistanceFromNetwork.release() );
97
98 auto outputLines = std::make_unique<QgsProcessingParameterFeatureSink>( u"OUTPUT_LINES"_s, QObject::tr( "Service area (lines)" ), Qgis::ProcessingSourceType::VectorLine, QVariant(), true );
99 outputLines->setCreateByDefault( true );
100 addParameter( outputLines.release() );
101
102 auto outputPoints = std::make_unique<QgsProcessingParameterFeatureSink>( u"OUTPUT"_s, QObject::tr( "Service area (boundary nodes)" ), Qgis::ProcessingSourceType::VectorPoint, QVariant(), true );
103 outputPoints->setCreateByDefault( false );
104 addParameter( outputPoints.release() );
105
106 auto outputNonRoutable = std::make_unique<QgsProcessingParameterFeatureSink>( u"OUTPUT_NON_ROUTABLE"_s, QObject::tr( "Non-routable features" ), Qgis::ProcessingSourceType::VectorPoint, QVariant(), true );
107 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)." ) );
108 outputNonRoutable->setCreateByDefault( false );
109 addParameter( outputNonRoutable.release() );
110}
111
112QVariantMap QgsServiceAreaFromLayerAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
113{
114 QGS_MARK_ALGORITHM_SOURCE
115
116 loadCommonParams( parameters, context, feedback );
117
118 std::unique_ptr<QgsProcessingFeatureSource> startPoints( parameterAsSource( parameters, u"START_POINTS"_s, context ) );
119 if ( !startPoints )
120 throw QgsProcessingException( invalidSourceError( parameters, u"START_POINTS"_s ) );
121
122 // use older deprecated travel cost style if specified, to maintain old api
123 const bool useOldTravelCost = parameters.value( u"TRAVEL_COST"_s ).isValid();
124 const double defaultTravelCost = parameterAsDouble( parameters, useOldTravelCost ? u"TRAVEL_COST"_s : u"TRAVEL_COST2"_s, context );
125
126 const bool dynamicTravelCost = QgsProcessingParameters::isDynamic( parameters, u"TRAVEL_COST2"_s );
127 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, startPoints.get() );
128 QgsProperty travelCostProperty;
129 if ( dynamicTravelCost )
130 {
131 travelCostProperty = parameters.value( u"TRAVEL_COST2"_s ).value<QgsProperty>();
132 }
133
134 const int strategy = parameterAsInt( parameters, u"STRATEGY"_s, context );
135 const double multiplier = ( strategy && !useOldTravelCost ) ? mMultiplier : 1;
136
137 bool includeBounds = true; // default to true to maintain 3.0 API
138 if ( parameters.contains( u"INCLUDE_BOUNDS"_s ) )
139 {
140 includeBounds = parameterAsBool( parameters, u"INCLUDE_BOUNDS"_s, context );
141 }
142
143 QVector<QgsPointXY> points;
144 QHash<int, QgsFeature> sourceFeatures;
145 loadPoints( startPoints.get(), &points, nullptr, context, feedback, &sourceFeatures );
146
147 feedback->pushInfo( QObject::tr( "Building graph…" ) );
148 QVector<QgsPointXY> snappedPoints;
149 mDirector->makeGraph( mBuilder.get(), points, snappedPoints, feedback );
150
151 feedback->pushInfo( QObject::tr( "Calculating service areas…" ) );
152 std::unique_ptr<QgsGraph> graph( mBuilder->takeGraph() );
153
154 QgsFields newFields;
155 newFields.append( QgsField( u"type"_s, QMetaType::Type::QString ) );
156 newFields.append( QgsField( u"start"_s, QMetaType::Type::QString ) );
157 QgsFields fields = QgsProcessingUtils::combineFields( startPoints->fields(), newFields );
158
159 QString pointsSinkId;
160 std::unique_ptr<QgsFeatureSink> pointsSink( parameterAsSink( parameters, u"OUTPUT"_s, context, pointsSinkId, fields, Qgis::WkbType::MultiPoint, mNetwork->sourceCrs() ) );
161
162 QString linesSinkId;
163 std::unique_ptr<QgsFeatureSink> linesSink( parameterAsSink( parameters, u"OUTPUT_LINES"_s, context, linesSinkId, fields, Qgis::WkbType::MultiLineString, mNetwork->sourceCrs() ) );
164
165 QString nonRoutableSinkId;
166 std::unique_ptr<QgsFeatureSink> nonRoutableSink( parameterAsSink( parameters, u"OUTPUT_NON_ROUTABLE"_s, context, nonRoutableSinkId, startPoints->fields(), Qgis::WkbType::Point, mNetwork->sourceCrs() ) );
167
168 const double pointDistanceThreshold = parameters.value( u"POINT_TOLERANCE"_s ).isValid() ? parameterAsDouble( parameters, u"POINT_TOLERANCE"_s, context ) : -1;
169
170 int idxStart;
171 QVector<int> tree;
172 QVector<double> costs;
173
174 int inboundEdgeIndex;
175 double startVertexCost, endVertexCost;
176 QgsPointXY startPoint, endPoint;
177 QgsGraphEdge edge;
178
179 QgsFeature feat;
180 QgsAttributes attributes;
181
182 const double step = snappedPoints.size() > 0 ? 100.0 / snappedPoints.size() : 1;
183 for ( int i = 0; i < snappedPoints.size(); i++ )
184 {
185 if ( feedback->isCanceled() )
186 {
187 break;
188 }
189
190 double travelCost = defaultTravelCost;
191 if ( dynamicTravelCost )
192 {
193 expressionContext.setFeature( sourceFeatures.value( i + 1 ) );
194 travelCost = travelCostProperty.valueAsDouble( expressionContext, travelCost );
195 }
196 travelCost *= multiplier;
197
198 const QgsPointXY snappedPoint = snappedPoints.at( i );
199 const QgsPointXY originalPoint = points.at( i );
200
201 if ( pointDistanceThreshold >= 0 )
202 {
203 double distancePointToNetwork = 0;
204 try
205 {
206 distancePointToNetwork = mBuilder->distanceArea()->measureLine( originalPoint, snappedPoint );
207 }
208 catch ( QgsCsException & )
209 {
210 throw QgsProcessingException( QObject::tr( "An error occurred while calculating length" ) );
211 }
212
213 if ( distancePointToNetwork > pointDistanceThreshold )
214 {
215 feedback->pushWarning( QObject::tr( "Point is too far from the network layer (%1, maximum permitted is %2)" ).arg( distancePointToNetwork ).arg( pointDistanceThreshold ) );
216 if ( nonRoutableSink )
217 {
218 feat.setGeometry( QgsGeometry::fromPointXY( originalPoint ) );
219 attributes = sourceFeatures.value( i + 1 ).attributes();
220 feat.setAttributes( attributes );
221 if ( !nonRoutableSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
222 throw QgsProcessingException( writeFeatureError( nonRoutableSink.get(), parameters, u"OUTPUT_NON_ROUTABLE"_s ) );
223 else
224 feedback->featureAddedToSink( u"OUTPUT_NON_ROUTABLE"_s );
225 }
226
227 feedback->setProgress( i * step );
228 continue;
229 }
230 }
231
232 const QString originalPointString = originalPoint.toString();
233
234 idxStart = graph->findVertex( snappedPoint );
235
236 QgsGraphAnalyzer::dijkstra( graph.get(), idxStart, 0, &tree, &costs );
237
238 QgsMultiPointXY areaPoints;
239 QgsMultiPolylineXY lines;
240 QSet<int> vertices;
241
242 for ( int j = 0; j < costs.size(); j++ )
243 {
244 inboundEdgeIndex = tree.at( j );
245
246 if ( inboundEdgeIndex == -1 && j != idxStart )
247 {
248 // unreachable vertex
249 continue;
250 }
251
252 startVertexCost = costs.at( j );
253 if ( startVertexCost > travelCost )
254 {
255 // vertex is too expensive, discard
256 continue;
257 }
258
259 vertices.insert( j );
260 startPoint = graph->vertex( j ).point();
261
262 // find all edges coming from this vertex
263 const QList<int> outgoingEdges = graph->vertex( j ).outgoingEdges();
264 for ( int edgeId : outgoingEdges )
265 {
266 edge = graph->edge( edgeId );
267 endVertexCost = startVertexCost + edge.cost( 0 ).toDouble();
268 endPoint = graph->vertex( edge.toVertex() ).point();
269 if ( endVertexCost <= travelCost )
270 {
271 // end vertex is cheap enough to include
272 vertices.insert( edge.toVertex() );
273 lines.push_back( QgsPolylineXY() << startPoint << endPoint );
274 }
275 else
276 {
277 // travelCost sits somewhere on this edge, interpolate position
278 QgsPointXY interpolatedEndPoint = QgsGeometryUtils::interpolatePointOnLineByValue( startPoint.x(), startPoint.y(), startVertexCost, endPoint.x(), endPoint.y(), endVertexCost, travelCost );
279
280 areaPoints.push_back( interpolatedEndPoint );
281 lines.push_back( QgsPolylineXY() << startPoint << interpolatedEndPoint );
282 }
283 } // edges
284 } // costs
285
286 // convert to list and sort to maintain same order of points between algorithm runs
287 QList<int> verticesList = qgis::setToList( vertices );
288 areaPoints.reserve( verticesList.size() );
289 std::sort( verticesList.begin(), verticesList.end() );
290 for ( int v : verticesList )
291 {
292 areaPoints.push_back( graph->vertex( v ).point() );
293 }
294
295 if ( pointsSink )
296 {
297 QgsGeometry geomPoints = QgsGeometry::fromMultiPointXY( areaPoints );
298 feat.setGeometry( geomPoints );
299 attributes = sourceFeatures.value( i + 1 ).attributes();
300 attributes << u"within"_s << originalPointString;
301 feat.setAttributes( attributes );
302 if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
303 throw QgsProcessingException( writeFeatureError( pointsSink.get(), parameters, u"OUTPUT"_s ) );
304 else
305 feedback->featureAddedToSink( u"OUTPUT"_s );
306
307 if ( includeBounds )
308 {
309 QgsMultiPointXY upperBoundary, lowerBoundary;
310 QVector<int> nodes;
311 nodes.reserve( costs.size() );
312
313 int vertexId;
314 for ( int v = 0; v < costs.size(); v++ )
315 {
316 if ( costs.at( v ) > travelCost && tree.at( v ) != -1 )
317 {
318 vertexId = graph->edge( tree.at( v ) ).fromVertex();
319 if ( costs.at( vertexId ) <= travelCost )
320 {
321 nodes.push_back( v );
322 }
323 }
324 } // costs
325
326 upperBoundary.reserve( nodes.size() );
327 lowerBoundary.reserve( nodes.size() );
328 for ( int n : std::as_const( nodes ) )
329 {
330 upperBoundary.push_back( graph->vertex( graph->edge( tree.at( n ) ).toVertex() ).point() );
331 lowerBoundary.push_back( graph->vertex( graph->edge( tree.at( n ) ).fromVertex() ).point() );
332 } // nodes
333
334 QgsGeometry geomUpper = QgsGeometry::fromMultiPointXY( upperBoundary );
335 QgsGeometry geomLower = QgsGeometry::fromMultiPointXY( lowerBoundary );
336
337 feat.setGeometry( geomUpper );
338 attributes = sourceFeatures.value( i + 1 ).attributes();
339 attributes << u"upper"_s << originalPointString;
340 feat.setAttributes( attributes );
341 if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
342 throw QgsProcessingException( writeFeatureError( pointsSink.get(), parameters, u"OUTPUT"_s ) );
343 else
344 feedback->featureAddedToSink( u"OUTPUT"_s );
345
346 feat.setGeometry( geomLower );
347 attributes = sourceFeatures.value( i + 1 ).attributes();
348 attributes << u"lower"_s << originalPointString;
349 feat.setAttributes( attributes );
350 if ( !pointsSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
351 throw QgsProcessingException( writeFeatureError( pointsSink.get(), parameters, u"OUTPUT"_s ) );
352 else
353 feedback->featureAddedToSink( u"OUTPUT"_s );
354 } // includeBounds
355 }
356
357 if ( linesSink )
358 {
360 feat.setGeometry( geomLines );
361 attributes = sourceFeatures.value( i + 1 ).attributes();
362 attributes << u"lines"_s << originalPointString;
363 feat.setAttributes( attributes );
364 if ( !linesSink->addFeature( feat, QgsFeatureSink::FastInsert ) )
365 throw QgsProcessingException( writeFeatureError( linesSink.get(), parameters, u"OUTPUT_LINES"_s ) );
366 else
367 feedback->featureAddedToSink( u"OUTPUT_LINES"_s );
368 }
369
370 feedback->setProgress( i * step );
371 } // snappedPoints
372
373 QVariantMap outputs;
374 if ( pointsSink )
375 {
376 pointsSink->finalize();
377 feedback->featureSinkFinalized( u"OUTPUT"_s );
378 outputs.insert( u"OUTPUT"_s, pointsSinkId );
379 }
380 if ( linesSink )
381 {
382 linesSink->finalize();
383 feedback->featureSinkFinalized( linesSinkId );
384 outputs.insert( u"OUTPUT_LINES"_s, linesSinkId );
385 }
386 if ( nonRoutableSink )
387 {
388 nonRoutableSink->finalize();
389 feedback->featureSinkFinalized( nonRoutableSinkId );
390 outputs.insert( u"OUTPUT_NON_ROUTABLE"_s, nonRoutableSinkId );
391 }
392
393 return outputs;
394}
395
@ VectorPoint
Vector point layers.
Definition qgis.h:3752
@ VectorLine
Vector line layers.
Definition qgis.h:3753
@ Point
Point.
Definition qgis.h:296
@ MultiPoint
MultiPoint.
Definition qgis.h:300
@ MultiLineString
MultiLineString.
Definition qgis.h:301
@ Hidden
Parameter is hidden and should not be shown to users.
Definition qgis.h:3985
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3984
@ Double
Double/float values.
Definition qgis.h:4025
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:60
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:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:45
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:75
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:185
QVariant cost(int strategyIndex) const
Returns edge cost calculated using specified strategy.
Definition qgsgraph.cpp:170
Represents a 2D point.
Definition qgspointxy.h:62
QString toString(int precision=-1) const
Returns a string representation of the point (x, y) with a preset precision.
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
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.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
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.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
void setIsDynamic(bool dynamic)
Sets whether the parameter is dynamic, and can support data-defined values (i.e.
An input feature source (such as vector layers) parameter for processing algorithms.
A numeric 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:47
@ DoublePositive
Positive double value (including 0).
Definition qgsproperty.h:57
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.
QVector< QgsPointXY > QgsMultiPointXY
A collection of QgsPoints that share a common collection of attributes.
Definition qgsgeometry.h:98
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition qgsgeometry.h:63