Skip to content

Commit

Permalink
Merge pull request #4869 from alexbruy/network-analysis
Browse files Browse the repository at this point in the history
[processing] restore network analysis algorithms
  • Loading branch information
alexbruy committed Jul 17, 2017
2 parents 6c0cb2f + f3f74a9 commit e61daed
Show file tree
Hide file tree
Showing 6 changed files with 552 additions and 527 deletions.
15 changes: 10 additions & 5 deletions python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
Expand Up @@ -87,6 +87,11 @@
from .SaveSelectedFeatures import SaveSelectedFeatures
from .SelectByAttribute import SelectByAttribute
from .SelectByExpression import SelectByExpression
from .ServiceAreaFromLayer import ServiceAreaFromLayer
from .ServiceAreaFromPoint import ServiceAreaFromPoint
from .ShortestPathLayerToPoint import ShortestPathLayerToPoint
from .ShortestPathPointToLayer import ShortestPathPointToLayer
from .ShortestPathPointToPoint import ShortestPathPointToPoint
from .SimplifyGeometries import SimplifyGeometries
from .Slope import Slope
from .Smooth import Smooth
Expand Down Expand Up @@ -158,11 +163,6 @@
# from .ExtractSpecificNodes import ExtractSpecificNodes
# from .GeometryByExpression import GeometryByExpression
# from .RasterCalculator import RasterCalculator
# from .ShortestPathPointToPoint import ShortestPathPointToPoint
# from .ShortestPathPointToLayer import ShortestPathPointToLayer
# from .ShortestPathLayerToPoint import ShortestPathLayerToPoint
# from .ServiceAreaFromPoint import ServiceAreaFromPoint
# from .ServiceAreaFromLayer import ServiceAreaFromLayer
# from .TruncateTable import TruncateTable
# from .Polygonize import Polygonize
# from .ExecuteSQL import ExecuteSQL
Expand Down Expand Up @@ -274,6 +274,11 @@ def getAlgs(self):
SaveSelectedFeatures(),
SelectByAttribute(),
SelectByExpression(),
ServiceAreaFromLayer(),
ServiceAreaFromPoint(),
ShortestPathLayerToPoint(),
ShortestPathPointToLayer(),
ShortestPathPointToPoint(),
SimplifyGeometries(),
Slope(),
Smooth(),
Expand Down
259 changes: 138 additions & 121 deletions python/plugins/processing/algs/qgis/ServiceAreaFromLayer.py
Expand Up @@ -36,10 +36,16 @@
QgsFeature,
QgsFeatureSink,
QgsGeometry,
QgsField,
QgsFields,
QgsFeatureRequest,
QgsProcessingUtils,
QgsField,
QgsProcessing,
QgsProcessingParameterEnum,
QgsProcessingParameterPoint,
QgsProcessingParameterField,
QgsProcessingParameterNumber,
QgsProcessingParameterString,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterDefinition)
from qgis.analysis import (QgsVectorLayerDirector,
QgsNetworkDistanceStrategy,
Expand All @@ -50,21 +56,13 @@
from qgis.utils import iface

from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.parameters import (ParameterVector,
ParameterNumber,
ParameterString,
ParameterTableField,
ParameterSelection
)
from processing.core.outputs import OutputVector
from processing.tools import dataobjects

pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]


class ServiceAreaFromLayer(QgisAlgorithm):

INPUT_VECTOR = 'INPUT_VECTOR'
INPUT = 'INPUT'
START_POINTS = 'START_POINTS'
STRATEGY = 'STRATEGY'
TRAVEL_COST = 'TRAVEL_COST'
Expand Down Expand Up @@ -98,62 +96,66 @@ def initAlgorithm(self, config=None):
self.tr('Fastest')
]

self.addParameter(ParameterVector(self.INPUT_VECTOR,
self.tr('Vector layer representing network'),
[dataobjects.TYPE_VECTOR_LINE]))
self.addParameter(ParameterVector(self.START_POINTS,
self.tr('Vector layer with start points'),
[dataobjects.TYPE_VECTOR_POINT]))
self.addParameter(ParameterSelection(self.STRATEGY,
self.tr('Path type to calculate'),
self.STRATEGIES,
default=0))
self.addParameter(ParameterNumber(self.TRAVEL_COST,
self.tr('Travel cost (distance for "Shortest", time for "Fastest")'),
0.0, 99999999.999999, 0.0))
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Vector layer representing network'),
[QgsProcessing.TypeVectorLine]))
self.addParameter(QgsProcessingParameterFeatureSource(self.START_POINTS,
self.tr('Vector layer with start points'),
[QgsProcessing.TypeVectorPoint]))
self.addParameter(QgsProcessingParameterEnum(self.STRATEGY,
self.tr('Path type to calculate'),
self.STRATEGIES,
defaultValue=0))
self.addParameter(QgsProcessingParameterNumber(self.TRAVEL_COST,
self.tr('Travel cost (distance for "Shortest", time for "Fastest")'),
QgsProcessingParameterNumber.Double,
0.0, False, 0, 99999999.99))

params = []
params.append(ParameterTableField(self.DIRECTION_FIELD,
self.tr('Direction field'),
self.INPUT_VECTOR,
optional=True))
params.append(ParameterString(self.VALUE_FORWARD,
self.tr('Value for forward direction'),
'',
optional=True))
params.append(ParameterString(self.VALUE_BACKWARD,
self.tr('Value for backward direction'),
'',
optional=True))
params.append(ParameterString(self.VALUE_BOTH,
self.tr('Value for both directions'),
'',
optional=True))
params.append(ParameterSelection(self.DEFAULT_DIRECTION,
self.tr('Default direction'),
list(self.DIRECTIONS.keys()),
default=2))
params.append(ParameterTableField(self.SPEED_FIELD,
self.tr('Speed field'),
self.INPUT_VECTOR,
optional=True))
params.append(ParameterNumber(self.DEFAULT_SPEED,
self.tr('Default speed (km/h)'),
0.0, 99999999.999999, 5.0))
params.append(ParameterNumber(self.TOLERANCE,
self.tr('Topology tolerance'),
0.0, 99999999.999999, 0.0))
params.append(QgsProcessingParameterField(self.DIRECTION_FIELD,
self.tr('Direction field'),
None,
self.INPUT,
optional=True))
params.append(QgsProcessingParameterString(self.VALUE_FORWARD,
self.tr('Value for forward direction'),
optional=True))
params.append(QgsProcessingParameterString(self.VALUE_BACKWARD,
self.tr('Value for backward direction'),
optional=True))
params.append(QgsProcessingParameterString(self.VALUE_BOTH,
self.tr('Value for both directions'),
optional=True))
params.append(QgsProcessingParameterEnum(self.DEFAULT_DIRECTION,
self.tr('Default direction'),
list(self.DIRECTIONS.keys()),
defaultValue=2))
params.append(QgsProcessingParameterField(self.SPEED_FIELD,
self.tr('Speed field'),
None,
self.INPUT,
optional=True))
params.append(QgsProcessingParameterNumber(self.DEFAULT_SPEED,
self.tr('Default speed (km/h)'),
QgsProcessingParameterNumber.Double,
5.0, False, 0, 99999999.99))
params.append(QgsProcessingParameterNumber(self.TOLERANCE,
self.tr('Topology tolerance'),
QgsProcessingParameterNumber.Double,
0.0, False, 0, 99999999.99))

for p in params:
p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(p)

self.addOutput(OutputVector(self.OUTPUT_POINTS,
self.tr('Service area (boundary nodes)'),
datatype=[dataobjects.TYPE_VECTOR_POINT]))
self.addOutput(OutputVector(self.OUTPUT_POLYGON,
self.tr('Service area (convex hull)'),
datatype=[dataobjects.TYPE_VECTOR_POLYGON]))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_POINTS,
self.tr('Service area (boundary nodes)'),
QgsProcessing.TypeVectorPoint,
optional=True))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_POLYGON,
self.tr('Service area (convex hull)'),
QgsProcessing.TypeVectorPolygon,
optional=True))

def name(self):
return 'serviceareafromlayer'
Expand All @@ -162,21 +164,19 @@ def displayName(self):
return self.tr('Service area (from layer)')

def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_VECTOR), context)
startPoints = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.START_POINTS), context)
strategy = self.getParameterValue(self.STRATEGY)
travelCost = self.getParameterValue(self.TRAVEL_COST)

directionFieldName = self.getParameterValue(self.DIRECTION_FIELD)
forwardValue = self.getParameterValue(self.VALUE_FORWARD)
backwardValue = self.getParameterValue(self.VALUE_BACKWARD)
bothValue = self.getParameterValue(self.VALUE_BOTH)
defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
bothValue = self.getParameterValue(self.VALUE_BOTH)
defaultDirection = self.getParameterValue(self.DEFAULT_DIRECTION)
speedFieldName = self.getParameterValue(self.SPEED_FIELD)
defaultSpeed = self.getParameterValue(self.DEFAULT_SPEED)
tolerance = self.getParameterValue(self.TOLERANCE)
network = self.parameterAsSource(parameters, self.INPUT, context)
startPoints = self.parameterAsSource(parameters, self.START_POINTS, context)
strategy = self.parameterAsEnum(parameters, self.STRATEGY, context)
travelCost = self.parameterAsDouble(parameters, self.TRAVEL_COST, context)

directionFieldName = self.parameterAsString(parameters, self.DIRECTION_FIELD, context)
forwardValue = self.parameterAsString(parameters, self.VALUE_FORWARD, context)
backwardValue = self.parameterAsString(parameters, self.VALUE_BACKWARD, context)
bothValue = self.parameterAsString(parameters, self.VALUE_BOTH, context)
defaultDirection = self.parameterAsEnum(parameters, self.DEFAULT_DIRECTION, context)
speedFieldName = self.parameterAsString(parameters, self.SPEED_FIELD, context)
defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)

fields = QgsFields()
fields.append(QgsField('type', QVariant.String, '', 254, 0))
Expand All @@ -185,20 +185,14 @@ def processAlgorithm(self, parameters, context, feedback):
feat = QgsFeature()
feat.setFields(fields)

writerPoints = self.getOutputFromName(
self.OUTPUT_POINTS).getVectorWriter(fields, QgsWkbTypes.MultiPoint, layer.crs(), context)

writerPolygons = self.getOutputFromName(
self.OUTPUT_POLYGON).getVectorWriter(fields, QgsWkbTypes.Polygon, layer.crs(), context)

directionField = -1
if directionFieldName is not None:
directionField = layer.fields().lookupField(directionFieldName)
if directionFieldName:
directionField = network.fields().lookupField(directionFieldName)
speedField = -1
if speedFieldName is not None:
speedField = layer.fields().lookupField(speedFieldName)
if speedFieldName:
speedField = network.fields().lookupField(speedFieldName)

director = QgsVectorLayerDirector(layer,
director = QgsVectorLayerDirector(network,
directionField,
forwardValue,
backwardValue,
Expand All @@ -222,22 +216,44 @@ def processAlgorithm(self, parameters, context, feedback):
feedback.pushInfo(self.tr('Loading start points...'))
request = QgsFeatureRequest()
request.setFlags(request.flags() ^ QgsFeatureRequest.SubsetOfAttributes)
features = QgsProcessingUtils.getFeatures(startPoints, context, request)
request.setDestinationCrs(network.sourceCrs())
features = startPoints.getFeatures(request)
total = 100.0 / startPoints.featureCount() if startPoints.featureCount() else 0

points = []
for f in features:
for current, f in enumerate(features):
if feedback.isCanceled():
break

points.append(f.geometry().asPoint())
feedback.setProgress(int(current * total))

feedback.pushInfo(self.tr('Building graph...'))
snappedPoints = director.makeGraph(builder, points)
snappedPoints = director.makeGraph(builder, points, feedback)

feedback.pushInfo(self.tr('Calculating service areas...'))
graph = builder.graph()

results = {}
(sinkPoints, pointsId) = self.parameterAsSink(parameters, self.OUTPUT_POINTS, context,
fields, QgsWkbTypes.MultiPoint, network.sourceCrs())

(sinkPolygon, polygonId) = self.parameterAsSink(parameters, self.OUTPUT_POLYGON, context,
fields, QgsWkbTypes.Polygon, network.sourceCrs())

if sinkPoints:
results[self.OUTPUT_POINTS] = pointsId
if sinkPolygon:
results[self.OUTPUT_POLYGON] = polygonId

vertices = []
upperBoundary = []
lowerBoundary = []
total = 100.0 / len(snappedPoints) if snappedPoints else 1
for i, p in enumerate(snappedPoints):
if feedback.isCanceled():
break

idxStart = graph.findVertex(snappedPoints[i])
origPoint = points[i].toString()

Expand All @@ -252,41 +268,42 @@ def processAlgorithm(self, parameters, context, feedback):
upperBoundary.append(graph.vertex(graph.edge(tree[j]).inVertex()).point())
lowerBoundary.append(graph.vertex(graph.edge(tree[j]).outVertex()).point())

geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

feat.setGeometry(geomUpper)
feat['type'] = 'upper'
feat['start'] = origPoint
writerPoints.addFeature(feat, QgsFeatureSink.FastInsert)

feat.setGeometry(geomLower)
feat['type'] = 'lower'
feat['start'] = origPoint
writerPoints.addFeature(feat, QgsFeatureSink.FastInsert)

upperBoundary.append(origPoint)
lowerBoundary.append(origPoint)
geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

geom = geomUpper.convexHull()
feat.setGeometry(geom)
feat['type'] = 'upper'
feat['start'] = origPoint
writerPolygons.addFeature(feat, QgsFeatureSink.FastInsert)

geom = geomLower.convexHull()
feat.setGeometry(geom)
feat['type'] = 'lower'
feat['start'] = origPoint
writerPolygons.addFeature(feat, QgsFeatureSink.FastInsert)
if sinkPoints:
geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

feat.setGeometry(geomUpper)
feat['type'] = 'upper'
feat['start'] = origPoint
sinkPoints.addFeature(feat, QgsFeatureSink.FastInsert)

feat.setGeometry(geomLower)
feat['type'] = 'lower'
feat['start'] = origPoint
sinkPoints.addFeature(feat, QgsFeatureSink.FastInsert)

if sinkPolygon:
upperBoundary.append(origPoint)
lowerBoundary.append(origPoint)
geomUpper = QgsGeometry.fromMultiPoint(upperBoundary)
geomLower = QgsGeometry.fromMultiPoint(lowerBoundary)

geom = geomUpper.convexHull()
feat.setGeometry(geom)
feat['type'] = 'upper'
feat['start'] = origPoint
sinkPolygon.addFeature(feat, QgsFeatureSink.FastInsert)

geom = geomLower.convexHull()
feat.setGeometry(geom)
feat['type'] = 'lower'
feat['start'] = origPoint
sinkPolygon.addFeature(feat, QgsFeatureSink.FastInsert)

vertices[:] = []
upperBoundary[:] = []
lowerBoundary[:] = []

feedback.setProgress(int(i * total))

del writerPoints
del writerPolygons
return results

0 comments on commit e61daed

Please sign in to comment.