37
37
QgsFeatureSink ,
38
38
QgsFeatureRequest ,
39
39
QgsGeometry ,
40
+ QgsGeometryUtils ,
40
41
QgsFields ,
41
42
QgsPointXY ,
42
43
QgsField ,
43
44
QgsProcessing ,
45
+ QgsProcessingParameterBoolean ,
44
46
QgsProcessingParameterEnum ,
45
47
QgsProcessingParameterPoint ,
46
48
QgsProcessingParameterField ,
@@ -75,7 +77,9 @@ class ServiceAreaFromLayer(QgisAlgorithm):
75
77
SPEED_FIELD = 'SPEED_FIELD'
76
78
DEFAULT_SPEED = 'DEFAULT_SPEED'
77
79
TOLERANCE = 'TOLERANCE'
80
+ INCLUDE_BOUNDS = 'INCLUDE_BOUNDS'
78
81
OUTPUT = 'OUTPUT'
82
+ OUTPUT_LINES = 'OUTPUT_LINES'
79
83
80
84
def icon (self ):
81
85
return QIcon (os .path .join (pluginPath , 'images' , 'networkanalysis.svg' ))
@@ -146,14 +150,24 @@ def initAlgorithm(self, config=None):
146
150
self .tr ('Topology tolerance' ),
147
151
QgsProcessingParameterNumber .Double ,
148
152
0.0 , False , 0 , 99999999.99 ))
149
-
153
+ params .append (QgsProcessingParameterBoolean (self .INCLUDE_BOUNDS ,
154
+ self .tr ('Include upper/lower bound points' ),
155
+ defaultValue = False ))
150
156
for p in params :
151
157
p .setFlags (p .flags () | QgsProcessingParameterDefinition .FlagAdvanced )
152
158
self .addParameter (p )
153
159
154
- self .addParameter (QgsProcessingParameterFeatureSink (self .OUTPUT ,
155
- self .tr ('Service area (boundary nodes)' ),
156
- QgsProcessing .TypeVectorPoint ))
160
+ lines_output = QgsProcessingParameterFeatureSink (self .OUTPUT_LINES ,
161
+ self .tr ('Service area (lines)' ),
162
+ QgsProcessing .TypeVectorLine , optional = True )
163
+ lines_output .setCreateByDefault (True )
164
+ self .addParameter (lines_output )
165
+
166
+ nodes_output = QgsProcessingParameterFeatureSink (self .OUTPUT ,
167
+ self .tr ('Service area (boundary nodes)' ),
168
+ QgsProcessing .TypeVectorPoint , optional = True )
169
+ nodes_output .setCreateByDefault (False )
170
+ self .addParameter (nodes_output )
157
171
158
172
def name (self ):
159
173
return 'serviceareafromlayer'
@@ -176,6 +190,10 @@ def processAlgorithm(self, parameters, context, feedback):
176
190
defaultSpeed = self .parameterAsDouble (parameters , self .DEFAULT_SPEED , context )
177
191
tolerance = self .parameterAsDouble (parameters , self .TOLERANCE , context )
178
192
193
+ include_bounds = True # default to true to maintain 3.0 API
194
+ if self .INCLUDE_BOUNDS in parameters :
195
+ include_bounds = self .parameterAsBool (parameters , self .INCLUDE_BOUNDS , context )
196
+
179
197
fields = startPoints .fields ()
180
198
fields .append (QgsField ('type' , QVariant .String , '' , 254 , 0 ))
181
199
fields .append (QgsField ('start' , QVariant .String , '' , 254 , 0 ))
@@ -240,12 +258,11 @@ def processAlgorithm(self, parameters, context, feedback):
240
258
feedback .pushInfo (QCoreApplication .translate ('ServiceAreaFromLayer' , 'Calculating service areas…' ))
241
259
graph = builder .graph ()
242
260
243
- (sink , dest_id ) = self .parameterAsSink (parameters , self .OUTPUT , context ,
244
- fields , QgsWkbTypes .MultiPoint , network .sourceCrs ())
261
+ (point_sink , dest_id ) = self .parameterAsSink (parameters , self .OUTPUT , context ,
262
+ fields , QgsWkbTypes .MultiPoint , network .sourceCrs ())
263
+ (line_sink , line_dest_id ) = self .parameterAsSink (parameters , self .OUTPUT_LINES , context ,
264
+ fields , QgsWkbTypes .MultiLineString , network .sourceCrs ())
245
265
246
- vertices = []
247
- upperBoundary = []
248
- lowerBoundary = []
249
266
total = 100.0 / len (snappedPoints ) if snappedPoints else 1
250
267
for i , p in enumerate (snappedPoints ):
251
268
if feedback .isCanceled ():
@@ -255,35 +272,92 @@ def processAlgorithm(self, parameters, context, feedback):
255
272
origPoint = points [i ].toString ()
256
273
257
274
tree , cost = QgsGraphAnalyzer .dijkstra (graph , idxStart , 0 )
258
- for j , v in enumerate (cost ):
259
- if v > travelCost and tree [j ] != - 1 :
260
- vertexId = graph .edge (tree [j ]).fromVertex ()
261
- if cost [vertexId ] <= travelCost :
262
- vertices .append (j )
263
-
264
- for j in vertices :
265
- upperBoundary .append (graph .vertex (graph .edge (tree [j ]).toVertex ()).point ())
266
- lowerBoundary .append (graph .vertex (graph .edge (tree [j ]).fromVertex ()).point ())
267
-
268
- geomUpper = QgsGeometry .fromMultiPointXY (upperBoundary )
269
- geomLower = QgsGeometry .fromMultiPointXY (lowerBoundary )
270
-
271
- feat .setGeometry (geomUpper )
272
-
273
- attrs = source_attributes [i ]
274
- attrs .extend (['upper' , origPoint ])
275
- feat .setAttributes (attrs )
276
- sink .addFeature (feat , QgsFeatureSink .FastInsert )
277
-
278
- feat .setGeometry (geomLower )
279
- attrs [- 2 ] = 'lower'
280
- feat .setAttributes (attrs )
281
- sink .addFeature (feat , QgsFeatureSink .FastInsert )
282
275
283
- vertices [:] = []
284
- upperBoundary [:] = []
285
- lowerBoundary [:] = []
276
+ vertices = set ()
277
+ area_points = []
278
+ lines = []
279
+ for vertex , start_vertex_cost in enumerate (cost ):
280
+ inbound_edge_index = tree [vertex ]
281
+ if inbound_edge_index == - 1 and vertex != idxStart :
282
+ # unreachable vertex
283
+ continue
284
+
285
+ if start_vertex_cost > travelCost :
286
+ # vertex is too expensive, discard
287
+ continue
288
+
289
+ vertices .add (vertex )
290
+ start_point = graph .vertex (vertex ).point ()
291
+
292
+ # find all edges coming from this vertex
293
+ for edge_id in graph .vertex (vertex ).outgoingEdges ():
294
+ edge = graph .edge (edge_id )
295
+ end_vertex_cost = start_vertex_cost + edge .cost (0 )
296
+ end_point = graph .vertex (edge .toVertex ()).point ()
297
+ if end_vertex_cost <= travelCost :
298
+ # end vertex is cheap enough to include
299
+ vertices .add (edge .toVertex ())
300
+ lines .append ([start_point , end_point ])
301
+ else :
302
+ # travelCost sits somewhere on this edge, interpolate position
303
+ interpolated_end_point = QgsGeometryUtils .interpolatePointOnLineByValue (start_point .x (), start_point .y (), start_vertex_cost ,
304
+ end_point .x (), end_point .y (), end_vertex_cost , travelCost )
305
+ area_points .append (interpolated_end_point )
306
+ lines .append ([start_point , interpolated_end_point ])
307
+
308
+ for v in vertices :
309
+ area_points .append (graph .vertex (v ).point ())
310
+
311
+ feat = QgsFeature ()
312
+ if point_sink is not None :
313
+ geomPoints = QgsGeometry .fromMultiPointXY (area_points )
314
+ feat .setGeometry (geomPoints )
315
+ attrs = source_attributes [i ]
316
+ attrs .extend (['within' , origPoint ])
317
+ feat .setAttributes (attrs )
318
+ point_sink .addFeature (feat , QgsFeatureSink .FastInsert )
319
+
320
+ if include_bounds :
321
+ upperBoundary = []
322
+ lowerBoundary = []
323
+
324
+ vertices = []
325
+ for vertex , c in enumerate (cost ):
326
+ if c > travelCost and tree [vertex ] != - 1 :
327
+ vertexId = graph .edge (tree [vertex ]).fromVertex ()
328
+ if cost [vertexId ] <= travelCost :
329
+ vertices .append (vertex )
330
+
331
+ for v in vertices :
332
+ upperBoundary .append (graph .vertex (graph .edge (tree [v ]).toVertex ()).point ())
333
+ lowerBoundary .append (graph .vertex (graph .edge (tree [v ]).fromVertex ()).point ())
334
+
335
+ geomUpper = QgsGeometry .fromMultiPointXY (upperBoundary )
336
+ geomLower = QgsGeometry .fromMultiPointXY (lowerBoundary )
337
+
338
+ feat .setGeometry (geomUpper )
339
+ attrs [- 2 ] = 'upper'
340
+ feat .setAttributes (attrs )
341
+ point_sink .addFeature (feat , QgsFeatureSink .FastInsert )
342
+
343
+ feat .setGeometry (geomLower )
344
+ attrs [- 2 ] = 'lower'
345
+ feat .setAttributes (attrs )
346
+ point_sink .addFeature (feat , QgsFeatureSink .FastInsert )
347
+
348
+ if line_sink is not None :
349
+ geom_lines = QgsGeometry .fromMultiPolylineXY (lines )
350
+ feat .setGeometry (geom_lines )
351
+ attrs = source_attributes [i ]
352
+ attrs .extend (['lines' , origPoint ])
353
+ feat .setAttributes (attrs )
354
+ line_sink .addFeature (feat , QgsFeatureSink .FastInsert )
286
355
287
356
feedback .setProgress (int (i * total ))
288
357
289
- return {self .OUTPUT : dest_id }
358
+ results = {}
359
+ if point_sink is not None :
360
+ results [self .OUTPUT ] = dest_id
361
+ if line_sink is not None :
362
+ results [self .OUTPUT_LINES ] = line_dest_id
363
+ return results
0 commit comments