Skip to content

Commit aa3c7cc

Browse files
committedDec 13, 2016
[processing] add algorithm for calculating service areas from point
layer Add start point coordinates to outputs
1 parent d1349f7 commit aa3c7cc

File tree

3 files changed

+288
-2
lines changed

3 files changed

+288
-2
lines changed
 

‎python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@
186186
from .Orthogonalize import Orthogonalize
187187
from .ShortestPathPointToPoint import ShortestPathPointToPoint
188188
from .ServiceAreaFromPoint import ServiceAreaFromPoint
189+
from .ServiceAreaFromLayer import ServiceAreaFromLayer
189190

190191
pluginPath = os.path.normpath(os.path.join(
191192
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -251,7 +252,8 @@ def __init__(self):
251252
ExtractSpecificNodes(), GeometryByExpression(), SnapGeometriesToLayer(),
252253
PoleOfInaccessibility(), CreateAttributeIndex(), DropGeometry(),
253254
BasicStatisticsForField(), RasterCalculator(), Heatmap(),
254-
Orthogonalize(), ShortestPathPointToPoint(), ServiceAreaFromPoint()
255+
Orthogonalize(), ShortestPathPointToPoint(), ServiceAreaFromPoint(),
256+
ServiceAreaFromLayer()
255257
]
256258

257259
if hasMatplotlib:
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
ServiceAreaFromLayer.py
6+
---------------------
7+
Date : December 2016
8+
Copyright : (C) 2016 by Alexander Bruy
9+
Email : alexander dot bruy at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Alexander Bruy'
21+
__date__ = 'December 2016'
22+
__copyright__ = '(C) 2016, Alexander Bruy'
23+
24+
# This will get replaced with a git SHA1 when you do a git archive
25+
26+
__revision__ = '$Format:%H$'
27+
28+
import os
29+
30+
from qgis.PyQt.QtCore import QVariant
31+
from qgis.PyQt.QtGui import QIcon
32+
33+
from qgis.core import QgsWkbTypes, QgsUnitTypes, QgsFeature, QgsGeometry, QgsPoint, QgsField, QgsFields, QgsFeatureRequest
34+
from qgis.analysis import (QgsVectorLayerDirector,
35+
QgsNetworkDistanceStrategy,
36+
QgsNetworkSpeedStrategy,
37+
QgsGraphBuilder,
38+
QgsGraphAnalyzer
39+
)
40+
from qgis.utils import iface
41+
42+
from processing.core.GeoAlgorithm import GeoAlgorithm
43+
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
44+
from processing.core.parameters import (ParameterVector,
45+
ParameterNumber,
46+
ParameterString,
47+
ParameterTableField,
48+
ParameterSelection
49+
)
50+
from processing.core.outputs import (OutputNumber,
51+
OutputVector
52+
)
53+
from processing.tools import dataobjects, vector
54+
55+
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
56+
57+
58+
class ServiceAreaFromLayer(GeoAlgorithm):
59+
60+
INPUT_VECTOR = 'INPUT_VECTOR'
61+
START_POINTS = 'START_POINTS'
62+
STRATEGY = 'STRATEGY'
63+
TRAVEL_COST = 'TRAVEL_COST'
64+
DIRECTION_FIELD = 'DIRECTION_FIELD'
65+
VALUE_FORWARD = 'VALUE_FORWARD'
66+
VALUE_BACKWARD = 'VALUE_BACKWARD'
67+
VALUE_BOTH = 'VALUE_BOTH'
68+
DEFAULT_DIRECTION = 'DEFAULT_DIRECTION'
69+
SPEED_FIELD = 'SPEED_FIELD'
70+
DEFAULT_SPEED = 'DEFAULT_SPEED'
71+
TOLERANCE = 'TOLERANCE'
72+
OUTPUT_POINTS = 'OUTPUT_POINTS'
73+
OUTPUT_POLYGON = 'OUTPUT_POLYGON'
74+
75+
def getIcon(self):
76+
return QIcon(os.path.join(pluginPath, 'images', 'networkanalysis.svg'))
77+
78+
def defineCharacteristics(self):
79+
self.DIRECTIONS = {self.tr('Forward direction'): QgsVectorLayerDirector.DirectionForward,
80+
self.tr('Backward direction'): QgsVectorLayerDirector.DirectionForward,
81+
self.tr('Both directions'): QgsVectorLayerDirector.DirectionForward
82+
}
83+
84+
self.STRATEGIES = [self.tr('Shortest'),
85+
self.tr('Fastest')
86+
]
87+
88+
self.name, self.i18n_name = self.trAlgorithm('Service area (from layer)')
89+
self.group, self.i18n_group = self.trAlgorithm('Network analysis')
90+
91+
self.addParameter(ParameterVector(self.INPUT_VECTOR,
92+
self.tr('Vector layer representing network'),
93+
[dataobjects.TYPE_VECTOR_LINE]))
94+
self.addParameter(ParameterVector(self.START_POINTS,
95+
self.tr('Vector layer with start points'),
96+
[dataobjects.TYPE_VECTOR_POINT]))
97+
self.addParameter(ParameterSelection(self.STRATEGY,
98+
self.tr('Path type to calculate'),
99+
self.STRATEGIES,
100+
default=0))
101+
self.addParameter(ParameterNumber(self.TRAVEL_COST,
102+
self.tr('Travel cost (distance for "Shortest", time for "Fastest")'),
103+
0.0, 99999999.999999, 0.0))
104+
105+
params = []
106+
params.append(ParameterTableField(self.DIRECTION_FIELD,
107+
self.tr('Direction field'),
108+
self.INPUT_VECTOR,
109+
optional=True))
110+
params.append(ParameterString(self.VALUE_FORWARD,
111+
self.tr('Value for forward direction'),
112+
'',
113+
optional=True))
114+
params.append(ParameterString(self.VALUE_BACKWARD,
115+
self.tr('Value for backward direction'),
116+
'',
117+
optional=True))
118+
params.append(ParameterString(self.VALUE_BOTH,
119+
self.tr('Value for both directions'),
120+
'',
121+
optional=True))
122+
params.append(ParameterSelection(self.DEFAULT_DIRECTION,
123+
self.tr('Default direction'),
124+
list(self.DIRECTIONS.keys()),
125+
default=0))
126+
params.append(ParameterTableField(self.SPEED_FIELD,
127+
self.tr('Speed field'),
128+
self.INPUT_VECTOR,
129+
optional=True))
130+
params.append(ParameterNumber(self.DEFAULT_SPEED,
131+
self.tr('Default speed (km/h)'),
132+
0.0, 99999999.999999, 5.0))
133+
params.append(ParameterNumber(self.TOLERANCE,
134+
self.tr('Topology tolerance'),
135+
0.0, 99999999.999999, 0.0))
136+
137+
for p in params:
138+
p.isAdvanced = True
139+
self.addParameter(p)
140+
141+
self.addOutput(OutputVector(self.OUTPUT_POINTS,
142+
self.tr('Service area (boundary nodes)'),
143+
datatype=[dataobjects.TYPE_VECTOR_POINT]))
144+
self.addOutput(OutputVector(self.OUTPUT_POLYGON,
145+
self.tr('Service area (convex hull)'),
146+
datatype=[dataobjects.TYPE_VECTOR_POLYGON]))
147+
148+
def processAlgorithm(self, progress):
149+
layer = dataobjects.getObjectFromUri(
150+
self.getParameterValue(self.INPUT_VECTOR))
151+
startPoints = dataobjects.getObjectFromUri(
152+
self.getParameterValue(self.START_POINTS))
153+
strategy = self.getParameterValue(self.STRATEGY)
154+
travelCost = self.getParameterValue(self.TRAVEL_COST)
155+
156+
directionFieldName = self.getParameterValue(self.DIRECTION_FIELD)
157+
forwardValue = self.getParameterValue(self.VALUE_FORWARD)
158+
backwardValue = self.getParameterValue(self.VALUE_BACKWARD)
159+
bothValue = self.getParameterValue(self.VALUE_BOTH)
160+
defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
161+
bothValue = self.getParameterValue(self.VALUE_BOTH)
162+
defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
163+
speedFieldName = self.getParameterValue(self.SPEED_FIELD)
164+
defaultSpeed = self.getParameterValue(self.DEFAULT_SPEED)
165+
tolerance = self.getParameterValue(self.TOLERANCE)
166+
167+
fields = QgsFields()
168+
fields.append(QgsField('type', QVariant.String, '', 254, 0))
169+
fields.append(QgsField('start', QVariant.String, '', 254, 0))
170+
171+
feat = QgsFeature()
172+
feat.setFields(fields)
173+
174+
writerPoints = self.getOutputFromName(
175+
self.OUTPUT_POINTS).getVectorWriter(
176+
fields,
177+
QgsWkbTypes.Point,
178+
layer.crs())
179+
180+
writerPolygons = self.getOutputFromName(
181+
self.OUTPUT_POLYGON).getVectorWriter(
182+
fields,
183+
QgsWkbTypes.Polygon,
184+
layer.crs())
185+
186+
directionField = -1
187+
if directionFieldName is not None:
188+
directionField = layer.fields().lookupField(directionFieldName)
189+
speedField = -1
190+
if speedFieldName is not None:
191+
speedField = layer.fields().lookupField(speedFieldName)
192+
193+
director = QgsVectorLayerDirector(layer,
194+
directionField,
195+
forwardValue,
196+
backwardValue,
197+
bothValue,
198+
defaultDirection)
199+
200+
distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits()
201+
multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
202+
if strategy == 0:
203+
strategy = QgsNetworkDistanceStrategy()
204+
else:
205+
strategy = QgsNetworkSpeedStrategy(speedField,
206+
defaultSpeed,
207+
multiplier * 1000.0 / 3600.0)
208+
209+
director.addStrategy(strategy)
210+
builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(),
211+
iface.mapCanvas().hasCrsTransformEnabled(),
212+
tolerance)
213+
214+
progress.setInfo(self.tr('Loading start points...'))
215+
request = QgsFeatureRequest()
216+
request.setFlags(request.flags() ^ QgsFeatureRequest.SubsetOfAttributes)
217+
features = vector.features(startPoints, request)
218+
points = []
219+
for f in features:
220+
points.append(f.geometry().asPoint())
221+
222+
progress.setInfo(self.tr('Building graph...'))
223+
snappedPoints = director.makeGraph(builder, points)
224+
225+
progress.setInfo(self.tr('Calculating service areas...'))
226+
graph = builder.graph()
227+
228+
vertices = []
229+
upperBoundary = []
230+
lowerBoundary = []
231+
total = 100.0 / len(snappedPoints)
232+
for i, p in enumerate(snappedPoints):
233+
idxStart = graph.findVertex(snappedPoints[i])
234+
origPoint = points[i].toString()
235+
236+
tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
237+
for j, v in enumerate(cost):
238+
if v > travelCost and tree[j] != -1:
239+
vertexId = graph.edge(tree [j]).outVertex()
240+
if cost[vertexId] <= travelCost:
241+
vertices.append(j)
242+
243+
for j in vertices:
244+
upperBoundary.append(graph.vertex(graph.edge(tree[j]).inVertex()).point())
245+
lowerBoundary.append(graph.vertex(graph.edge(tree[j]).outVertex()).point())
246+
247+
geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
248+
geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)
249+
250+
feat.setGeometry(geomUpper)
251+
feat['type'] = 'upper'
252+
feat['start'] = origPoint
253+
writerPoints.addFeature(feat)
254+
255+
feat.setGeometry(geomLower)
256+
feat['type'] = 'lower'
257+
feat['start'] = origPoint
258+
writerPoints.addFeature(feat)
259+
260+
geom = geomUpper.convexHull()
261+
feat.setGeometry(geom)
262+
feat['type'] = 'upper'
263+
feat['start'] = origPoint
264+
writerPolygons.addFeature(feat)
265+
266+
geom = geomLower.convexHull()
267+
feat.setGeometry(geom)
268+
feat['type'] = 'lower'
269+
feat['start'] = origPoint
270+
writerPolygons.addFeature(feat)
271+
272+
vertices[:] = []
273+
upperBoundary[:] = []
274+
lowerBoundary[:] = []
275+
276+
progress.setPercentage(int(i * total))
277+
278+
del writerPoints
279+
del writerPolygons

‎python/plugins/processing/algs/qgis/ServiceAreaFromPoint.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def defineCharacteristics(self):
140140

141141
self.addOutput(OutputVector(self.OUTPUT_POINTS,
142142
self.tr('Service area (boundary nodes)'),
143-
datatype=[dataobjects.TYPE_VECTOR_POI]))
143+
datatype=[dataobjects.TYPE_VECTOR_POINT]))
144144
self.addOutput(OutputVector(self.OUTPUT_POLYGON,
145145
self.tr('Service area (convex hull)'),
146146
datatype=[dataobjects.TYPE_VECTOR_POLYGON]))
@@ -218,6 +218,7 @@ def processAlgorithm(self, progress):
218218

219219
fields = QgsFields()
220220
fields.append(QgsField('type', QVariant.String, '', 254, 0))
221+
fields.append(QgsField('start', QVariant.String, '', 254, 0))
221222

222223
feat = QgsFeature()
223224
feat.setFields(fields)
@@ -233,10 +234,12 @@ def processAlgorithm(self, progress):
233234

234235
feat.setGeometry(geomUpper)
235236
feat['type'] = 'upper'
237+
feat['start'] = startPoint.toString()
236238
writer.addFeature(feat)
237239

238240
feat.setGeometry(geomLower)
239241
feat['type'] = 'lower'
242+
feat['start'] = startPoint.toString()
240243
writer.addFeature(feat)
241244

242245
del writer
@@ -250,10 +253,12 @@ def processAlgorithm(self, progress):
250253
geom = geomUpper.convexHull()
251254
feat.setGeometry(geom)
252255
feat['type'] = 'upper'
256+
feat['start'] = startPoint.toString()
253257
writer.addFeature(feat)
254258

255259
geom = geomLower.convexHull()
256260
feat.setGeometry(geom)
257261
feat['type'] = 'lower'
262+
feat['start'] = startPoint.toString()
258263
writer.addFeature(feat)
259264
del writer

0 commit comments

Comments
 (0)
Please sign in to comment.