Skip to content

Commit 77ecfb9

Browse files
committed
[processing] add service area algorithm
1 parent 9319fc6 commit 77ecfb9

File tree

2 files changed

+238
-1
lines changed

2 files changed

+238
-1
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
from .Heatmap import Heatmap
186186
from .Orthogonalize import Orthogonalize
187187
from .ShortestPath import ShortestPath
188+
from .ServiceArea import ServiceArea
188189

189190
pluginPath = os.path.normpath(os.path.join(
190191
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -250,7 +251,7 @@ def __init__(self):
250251
ExtractSpecificNodes(), GeometryByExpression(), SnapGeometriesToLayer(),
251252
PoleOfInaccessibility(), CreateAttributeIndex(), DropGeometry(),
252253
BasicStatisticsForField(), RasterCalculator(), Heatmap(),
253-
Orthogonalize(), ShortestPath()
254+
Orthogonalize(), ShortestPath(), ServiceArea()
254255
]
255256

256257
if hasMatplotlib:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
ServiceArea.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
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+
ParameterPoint,
46+
ParameterNumber,
47+
ParameterString,
48+
ParameterTableField,
49+
ParameterSelection
50+
)
51+
from processing.core.outputs import (OutputNumber,
52+
OutputVector
53+
)
54+
from processing.tools import dataobjects
55+
56+
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
57+
58+
59+
class ServiceArea(GeoAlgorithm):
60+
61+
INPUT_VECTOR = 'INPUT_VECTOR'
62+
START_POINT = 'START_POINT'
63+
STRATEGY = 'STRATEGY'
64+
TRAVEL_COST = 'TRAVEL_COST'
65+
DIRECTION_FIELD = 'DIRECTION_FIELD'
66+
VALUE_FORWARD = 'VALUE_FORWARD'
67+
VALUE_BACKWARD = 'VALUE_BACKWARD'
68+
VALUE_BOTH = 'VALUE_BOTH'
69+
DEFAULT_DIRECTION = 'DEFAULT_DIRECTION'
70+
SPEED_FIELD = 'SPEED_FIELD'
71+
DEFAULT_SPEED = 'DEFAULT_SPEED'
72+
TOLERANCE = 'TOLERANCE'
73+
OUTPUT_LAYER = 'OUTPUT_LAYER'
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')
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(ParameterPoint(self.START_POINT,
95+
self.tr('Start point')))
96+
self.addParameter(ParameterSelection(self.STRATEGY,
97+
self.tr('Path type to calculate'),
98+
self.STRATEGIES,
99+
default=0))
100+
self.addParameter(ParameterNumber(self.TRAVEL_COST,
101+
self.tr('Travel cost (distance for "Shortest", time for "Fastest")'),
102+
0.0, 99999999.999999, 0.0))
103+
104+
params = []
105+
params.append(ParameterTableField(self.DIRECTION_FIELD,
106+
self.tr('Direction field'),
107+
self.INPUT_VECTOR,
108+
optional=True))
109+
params.append(ParameterString(self.VALUE_FORWARD,
110+
self.tr('Value for forward direction'),
111+
'',
112+
optional=True))
113+
params.append(ParameterString(self.VALUE_BACKWARD,
114+
self.tr('Value for backward direction'),
115+
'',
116+
optional=True))
117+
params.append(ParameterString(self.VALUE_BOTH,
118+
self.tr('Value for both directions'),
119+
'',
120+
optional=True))
121+
params.append(ParameterSelection(self.DEFAULT_DIRECTION,
122+
self.tr('Default direction'),
123+
list(self.DIRECTIONS.keys()),
124+
default=0))
125+
params.append(ParameterTableField(self.SPEED_FIELD,
126+
self.tr('Speed field'),
127+
self.INPUT_VECTOR,
128+
optional=True))
129+
params.append(ParameterNumber(self.DEFAULT_SPEED,
130+
self.tr('Default speed (km/h)'),
131+
0.0, 99999999.999999, 5.0))
132+
params.append(ParameterNumber(self.TOLERANCE,
133+
self.tr('Topology tolerance'),
134+
0.0, 99999999.999999, 0.0))
135+
136+
for p in params:
137+
p.isAdvanced = True
138+
self.addParameter(p)
139+
140+
self.addOutput(OutputVector(self.OUTPUT_LAYER,
141+
self.tr('Service area'),
142+
datatype=[dataobjects.TYPE_VECTOR_POLYGON]))
143+
144+
def processAlgorithm(self, progress):
145+
layer = dataobjects.getObjectFromUri(
146+
self.getParameterValue(self.INPUT_VECTOR))
147+
startPoint = self.getParameterValue(self.START_POINT)
148+
strategy = self.getParameterValue(self.STRATEGY)
149+
travelCost = self.getParameterValue(self.TRAVEL_COST)
150+
151+
directionFieldName = self.getParameterValue(self.DIRECTION_FIELD)
152+
forwardValue = self.getParameterValue(self.VALUE_FORWARD)
153+
backwardValue = self.getParameterValue(self.VALUE_BACKWARD)
154+
bothValue = self.getParameterValue(self.VALUE_BOTH)
155+
defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
156+
bothValue = self.getParameterValue(self.VALUE_BOTH)
157+
defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
158+
speedFieldName = self.getParameterValue(self.SPEED_FIELD)
159+
defaultSpeed = self.getParameterValue(self.DEFAULT_SPEED)
160+
tolerance = self.getParameterValue(self.TOLERANCE)
161+
162+
fields = QgsFields()
163+
fields.append(QgsField('type', QVariant.String, '', 254, 0))
164+
writer = self.getOutputFromName(
165+
self.OUTPUT_LAYER).getVectorWriter(
166+
fields,
167+
QgsWkbTypes.Polygon,
168+
layer.crs())
169+
170+
tmp = startPoint.split(',')
171+
startPoint = QgsPoint(float(tmp[0]), float(tmp[1]))
172+
173+
directionField = -1
174+
if directionFieldName is not None:
175+
directionField = layer.fields().lookupField(directionFieldName)
176+
speedField = -1
177+
if speedFieldName is not None:
178+
speedField = layer.fields().lookupField(speedFieldName)
179+
180+
director = QgsVectorLayerDirector(layer,
181+
directionField,
182+
forwardValue,
183+
backwardValue,
184+
bothValue,
185+
defaultDirection)
186+
187+
distUnit = iface.mapCanvas().mapSettings().destinationCrs().mapUnits()
188+
multiplier = QgsUnitTypes.fromUnitToUnitFactor(distUnit, QgsUnitTypes.DistanceMeters)
189+
if strategy == 0:
190+
strategy = QgsNetworkDistanceStrategy()
191+
else:
192+
strategy = QgsNetworkSpeedStrategy(speedField,
193+
defaultSpeed,
194+
multiplier * 1000.0 / 3600.0)
195+
196+
director.addStrategy(strategy)
197+
builder = QgsGraphBuilder(iface.mapCanvas().mapSettings().destinationCrs(),
198+
iface.mapCanvas().hasCrsTransformEnabled(),
199+
tolerance)
200+
progress.setInfo(self.tr('Building graph...'))
201+
snappedPoints = director.makeGraph(builder, [startPoint])
202+
203+
progress.setInfo(self.tr('Calculating service area...'))
204+
graph = builder.graph()
205+
idxStart = graph.findVertex(snappedPoints[0])
206+
207+
tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
208+
vertices = []
209+
for i, v in enumerate(cost):
210+
if v > travelCost and tree[i] != -1:
211+
vertexId = graph.edge(tree [i]).outVertex()
212+
if cost[vertexId] <= travelCost:
213+
vertices.append(i)
214+
215+
upperBoundary = []
216+
lowerBoundary = []
217+
for i in vertices:
218+
upperBoundary.append(graph.vertex(graph.edge(tree[i]).inVertex()).point())
219+
lowerBoundary.append(graph.vertex(graph.edge(tree[i]).outVertex()).point())
220+
221+
progress.setInfo(self.tr('Writting results...'))
222+
feat = QgsFeature()
223+
feat.setFields(fields)
224+
225+
geom = QgsGeometry.fromMultiPoint(upperBoundary)
226+
geom = geom.convexHull()
227+
feat.setGeometry(geom)
228+
feat['type'] = 'upper'
229+
writer.addFeature(feat)
230+
231+
geom = QgsGeometry.fromMultiPoint(lowerBoundary)
232+
geom = geom.convexHull()
233+
feat.setGeometry(geom)
234+
feat['type'] = 'lower'
235+
writer.addFeature(feat)
236+
del writer

0 commit comments

Comments
 (0)