[processing] Fixes for Service Area algorithms

- Output interpolated points when travel cost falls mid-way along
an edge
- Output all intermediate reachable points also
- Make outputting upper/lower bound points optional, and non-default.
Now by default we just output all definitely reachable points and
the interpolated points along edges which correspond to the travel cost.
This allows the output to be used to correctly generate service areas
e.g. by concave/convex polygons and all reachable nodes will be
included in the area.
- Allow algorithm to optionally output a line layer (and make the
point layer optional too, and default to just the line layer output)
containing all reachable line segments (including interpolated
segments of lines when the travel cost sits midway along that
edge). This output is more easily understandably for users.
This commit is contained in:
Nyall Dawson 2018-04-04 08:16:36 +10:00
parent 2e7455c180
commit ccb72ebce2
23 changed files with 469 additions and 65 deletions

View File

@ -30,7 +30,8 @@ Solve shortest path problem using Dijkstra algorithm
:param source: source graph
:param startVertexIdx: index of the start vertex
:param criterionNum: index of the optimization strategy
:param resultTree: array that represents shortest path tree. resultTree[ vertexIndex ] == inboundingArcIndex if vertex reachable, otherwise resultTree[ vertexIndex ] == -1
:param resultTree: array that represents shortest path tree. resultTree[ vertexIndex ] == inboundingArcIndex if vertex reachable, otherwise resultTree[ vertexIndex ] == -1.
Note that the startVertexIdx will also have a value of -1 and may need special handling by callers.
:param resultCost: array of the paths costs
%End

View File

@ -37,10 +37,12 @@ from qgis.core import (QgsWkbTypes,
QgsFeatureSink,
QgsFeatureRequest,
QgsGeometry,
QgsGeometryUtils,
QgsFields,
QgsPointXY,
QgsField,
QgsProcessing,
QgsProcessingParameterBoolean,
QgsProcessingParameterEnum,
QgsProcessingParameterPoint,
QgsProcessingParameterField,
@ -75,7 +77,9 @@ class ServiceAreaFromLayer(QgisAlgorithm):
SPEED_FIELD = 'SPEED_FIELD'
DEFAULT_SPEED = 'DEFAULT_SPEED'
TOLERANCE = 'TOLERANCE'
INCLUDE_BOUNDS = 'INCLUDE_BOUNDS'
OUTPUT = 'OUTPUT'
OUTPUT_LINES = 'OUTPUT_LINES'
def icon(self):
return QIcon(os.path.join(pluginPath, 'images', 'networkanalysis.svg'))
@ -146,14 +150,24 @@ class ServiceAreaFromLayer(QgisAlgorithm):
self.tr('Topology tolerance'),
QgsProcessingParameterNumber.Double,
0.0, False, 0, 99999999.99))
params.append(QgsProcessingParameterBoolean(self.INCLUDE_BOUNDS,
self.tr('Include upper/lower bound points'),
defaultValue=False))
for p in params:
p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(p)
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,
self.tr('Service area (boundary nodes)'),
QgsProcessing.TypeVectorPoint))
lines_output = QgsProcessingParameterFeatureSink(self.OUTPUT_LINES,
self.tr('Service area (lines)'),
QgsProcessing.TypeVectorLine, optional=True)
lines_output.setCreateByDefault(True)
self.addParameter(lines_output)
nodes_output = QgsProcessingParameterFeatureSink(self.OUTPUT,
self.tr('Service area (boundary nodes)'),
QgsProcessing.TypeVectorPoint, optional=True)
nodes_output.setCreateByDefault(False)
self.addParameter(nodes_output)
def name(self):
return 'serviceareafromlayer'
@ -176,6 +190,10 @@ class ServiceAreaFromLayer(QgisAlgorithm):
defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)
include_bounds = True # default to true to maintain 3.0 API
if self.INCLUDE_BOUNDS in parameters:
include_bounds = self.parameterAsBool(parameters, self.INCLUDE_BOUNDS, context)
fields = startPoints.fields()
fields.append(QgsField('type', QVariant.String, '', 254, 0))
fields.append(QgsField('start', QVariant.String, '', 254, 0))
@ -240,12 +258,11 @@ class ServiceAreaFromLayer(QgisAlgorithm):
feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromLayer', 'Calculating service areas…'))
graph = builder.graph()
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.MultiPoint, network.sourceCrs())
(point_sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.MultiPoint, network.sourceCrs())
(line_sink, line_dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LINES, context,
fields, QgsWkbTypes.MultiLineString, network.sourceCrs())
vertices = []
upperBoundary = []
lowerBoundary = []
total = 100.0 / len(snappedPoints) if snappedPoints else 1
for i, p in enumerate(snappedPoints):
if feedback.isCanceled():
@ -255,35 +272,92 @@ class ServiceAreaFromLayer(QgisAlgorithm):
origPoint = points[i].toString()
tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
for j, v in enumerate(cost):
if v > travelCost and tree[j] != -1:
vertexId = graph.edge(tree[j]).fromVertex()
if cost[vertexId] <= travelCost:
vertices.append(j)
for j in vertices:
upperBoundary.append(graph.vertex(graph.edge(tree[j]).toVertex()).point())
lowerBoundary.append(graph.vertex(graph.edge(tree[j]).fromVertex()).point())
vertices = set()
area_points = []
lines = []
for vertex, start_vertex_cost in enumerate(cost):
inbound_edge_index = tree[vertex]
if inbound_edge_index == -1 and vertex != idxStart:
# unreachable vertex
continue
geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)
if start_vertex_cost > travelCost:
# vertex is too expensive, discard
continue
feat.setGeometry(geomUpper)
vertices.add(vertex)
start_point = graph.vertex(vertex).point()
attrs = source_attributes[i]
attrs.extend(['upper', origPoint])
feat.setAttributes(attrs)
sink.addFeature(feat, QgsFeatureSink.FastInsert)
# find all edges coming from this vertex
for edge_id in graph.vertex(vertex).outgoingEdges():
edge = graph.edge(edge_id)
end_vertex_cost = start_vertex_cost + edge.cost(0)
end_point = graph.vertex(edge.toVertex()).point()
if end_vertex_cost <= travelCost:
# end vertex is cheap enough to include
vertices.add(edge.toVertex())
lines.append([start_point, end_point])
else:
# travelCost sits somewhere on this edge, interpolate position
interpolated_end_point = QgsGeometryUtils.interpolatePointOnLineByValue(start_point.x(), start_point.y(), start_vertex_cost,
end_point.x(), end_point.y(), end_vertex_cost, travelCost)
area_points.append(interpolated_end_point)
lines.append([start_point, interpolated_end_point])
feat.setGeometry(geomLower)
attrs[-2] = 'lower'
feat.setAttributes(attrs)
sink.addFeature(feat, QgsFeatureSink.FastInsert)
for v in vertices:
area_points.append(graph.vertex(v).point())
vertices[:] = []
upperBoundary[:] = []
lowerBoundary[:] = []
feat = QgsFeature()
if point_sink is not None:
geomPoints = QgsGeometry.fromMultiPointXY(area_points)
feat.setGeometry(geomPoints)
attrs = source_attributes[i]
attrs.extend(['within', origPoint])
feat.setAttributes(attrs)
point_sink.addFeature(feat, QgsFeatureSink.FastInsert)
if include_bounds:
upperBoundary = []
lowerBoundary = []
vertices = []
for vertex, c in enumerate(cost):
if c > travelCost and tree[vertex] != -1:
vertexId = graph.edge(tree[vertex]).fromVertex()
if cost[vertexId] <= travelCost:
vertices.append(vertex)
for v in vertices:
upperBoundary.append(graph.vertex(graph.edge(tree[v]).toVertex()).point())
lowerBoundary.append(graph.vertex(graph.edge(tree[v]).fromVertex()).point())
geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)
feat.setGeometry(geomUpper)
attrs[-2] = 'upper'
feat.setAttributes(attrs)
point_sink.addFeature(feat, QgsFeatureSink.FastInsert)
feat.setGeometry(geomLower)
attrs[-2] = 'lower'
feat.setAttributes(attrs)
point_sink.addFeature(feat, QgsFeatureSink.FastInsert)
if line_sink is not None:
geom_lines = QgsGeometry.fromMultiPolylineXY(lines)
feat.setGeometry(geom_lines)
attrs = source_attributes[i]
attrs.extend(['lines', origPoint])
feat.setAttributes(attrs)
line_sink.addFeature(feat, QgsFeatureSink.FastInsert)
feedback.setProgress(int(i * total))
return {self.OUTPUT: dest_id}
results = {}
if point_sink is not None:
results[self.OUTPUT] = dest_id
if line_sink is not None:
results[self.OUTPUT_LINES] = line_dest_id
return results

View File

@ -36,9 +36,11 @@ from qgis.core import (QgsWkbTypes,
QgsFeature,
QgsFeatureSink,
QgsGeometry,
QgsGeometryUtils,
QgsFields,
QgsField,
QgsProcessing,
QgsProcessingParameterBoolean,
QgsProcessingParameterEnum,
QgsProcessingParameterPoint,
QgsProcessingParameterField,
@ -73,7 +75,9 @@ class ServiceAreaFromPoint(QgisAlgorithm):
SPEED_FIELD = 'SPEED_FIELD'
DEFAULT_SPEED = 'DEFAULT_SPEED'
TOLERANCE = 'TOLERANCE'
INCLUDE_BOUNDS = 'INCLUDE_BOUNDS'
OUTPUT = 'OUTPUT'
OUTPUT_LINES = 'OUTPUT_LINES'
def icon(self):
return QIcon(os.path.join(pluginPath, 'images', 'networkanalysis.svg'))
@ -143,14 +147,25 @@ class ServiceAreaFromPoint(QgisAlgorithm):
self.tr('Topology tolerance'),
QgsProcessingParameterNumber.Double,
0.0, False, 0, 99999999.99))
params.append(QgsProcessingParameterBoolean(self.INCLUDE_BOUNDS,
self.tr('Include upper/lower bound points'),
defaultValue=False))
for p in params:
p.setFlags(p.flags() | QgsProcessingParameterDefinition.FlagAdvanced)
self.addParameter(p)
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,
self.tr('Service area (boundary nodes)'),
QgsProcessing.TypeVectorPoint))
lines_output = QgsProcessingParameterFeatureSink(self.OUTPUT_LINES,
self.tr('Service area (lines)'),
QgsProcessing.TypeVectorLine, optional=True)
lines_output.setCreateByDefault(True)
self.addParameter(lines_output)
nodes_output = QgsProcessingParameterFeatureSink(self.OUTPUT,
self.tr('Service area (boundary nodes)'),
QgsProcessing.TypeVectorPoint, optional=True)
nodes_output.setCreateByDefault(False)
self.addParameter(nodes_output)
def name(self):
return 'serviceareafrompoint'
@ -173,6 +188,10 @@ class ServiceAreaFromPoint(QgisAlgorithm):
defaultSpeed = self.parameterAsDouble(parameters, self.DEFAULT_SPEED, context)
tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context)
include_bounds = True # default to true to maintain 3.0 API
if self.INCLUDE_BOUNDS in parameters:
include_bounds = self.parameterAsBool(parameters, self.INCLUDE_BOUNDS, context)
directionField = -1
if directionFieldName:
directionField = network.fields().lookupField(directionFieldName)
@ -208,18 +227,41 @@ class ServiceAreaFromPoint(QgisAlgorithm):
idxStart = graph.findVertex(snappedPoints[0])
tree, cost = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
vertices = []
for i, v in enumerate(cost):
if v > travelCost and tree[i] != -1:
vertexId = graph.edge(tree[i]).fromVertex()
if cost[vertexId] <= travelCost:
vertices.append(i)
vertices = set()
points = []
lines = []
for vertex, start_vertex_cost in enumerate(cost):
inbound_edge_index = tree[vertex]
if inbound_edge_index == -1 and vertex != idxStart:
# unreachable vertex
continue
if start_vertex_cost > travelCost:
# vertex is too expensive, discard
continue
vertices.add(vertex)
start_point = graph.vertex(vertex).point()
# find all edges coming from this vertex
for edge_id in graph.vertex(vertex).outgoingEdges():
edge = graph.edge(edge_id)
end_vertex_cost = start_vertex_cost + edge.cost(0)
end_point = graph.vertex(edge.toVertex()).point()
if end_vertex_cost <= travelCost:
# end vertex is cheap enough to include
vertices.add(edge.toVertex())
lines.append([start_point, end_point])
else:
# travelCost sits somewhere on this edge, interpolate position
interpolated_end_point = QgsGeometryUtils.interpolatePointOnLineByValue(start_point.x(), start_point.y(), start_vertex_cost,
end_point.x(), end_point.y(), end_vertex_cost, travelCost)
points.append(interpolated_end_point)
lines.append([start_point, interpolated_end_point])
upperBoundary = []
lowerBoundary = []
for i in vertices:
upperBoundary.append(graph.vertex(graph.edge(tree[i]).toVertex()).point())
lowerBoundary.append(graph.vertex(graph.edge(tree[i]).fromVertex()).point())
points.append(graph.vertex(i).point())
feedback.pushInfo(QCoreApplication.translate('ServiceAreaFromPoint', 'Writing results…'))
@ -230,25 +272,55 @@ class ServiceAreaFromPoint(QgisAlgorithm):
feat = QgsFeature()
feat.setFields(fields)
geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)
(point_sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.MultiPoint, network.sourceCrs())
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, QgsWkbTypes.MultiPoint, network.sourceCrs())
results = {}
feat.setGeometry(geomUpper)
feat['type'] = 'upper'
feat['start'] = startPoint.toString()
sink.addFeature(feat, QgsFeatureSink.FastInsert)
if point_sink is not None:
results[self.OUTPUT] = dest_id
geomPoints = QgsGeometry.fromMultiPointXY(points)
feat.setGeometry(geomPoints)
feat['type'] = 'within'
feat['start'] = startPoint.toString()
point_sink.addFeature(feat, QgsFeatureSink.FastInsert)
feat.setGeometry(geomLower)
feat['type'] = 'lower'
feat['start'] = startPoint.toString()
sink.addFeature(feat, QgsFeatureSink.FastInsert)
if include_bounds:
upperBoundary = []
lowerBoundary = []
upperBoundary.append(startPoint)
lowerBoundary.append(startPoint)
geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)
vertices = []
for i, v in enumerate(cost):
if v > travelCost and tree[i] != -1:
vertexId = graph.edge(tree[i]).fromVertex()
if cost[vertexId] <= travelCost:
vertices.append(i)
return {self.OUTPUT: dest_id}
for i in vertices:
upperBoundary.append(graph.vertex(graph.edge(tree[i]).toVertex()).point())
lowerBoundary.append(graph.vertex(graph.edge(tree[i]).fromVertex()).point())
geomUpper = QgsGeometry.fromMultiPointXY(upperBoundary)
geomLower = QgsGeometry.fromMultiPointXY(lowerBoundary)
feat.setGeometry(geomUpper)
feat['type'] = 'upper'
feat['start'] = startPoint.toString()
point_sink.addFeature(feat, QgsFeatureSink.FastInsert)
feat.setGeometry(geomLower)
feat['type'] = 'lower'
feat['start'] = startPoint.toString()
point_sink.addFeature(feat, QgsFeatureSink.FastInsert)
(line_sink, line_dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LINES, context,
fields, QgsWkbTypes.MultiLineString, network.sourceCrs())
if line_sink is not None:
results[self.OUTPUT_LINES] = line_dest_id
geom_lines = QgsGeometry.fromMultiPolylineXY(lines)
feat.setGeometry(geom_lines)
feat['type'] = 'lines'
feat['start'] = startPoint.toString()
line_sink.addFeature(feat, QgsFeatureSink.FastInsert)
return results

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
PROJCS["WGS_1984_UTM_Zone_33S",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",15],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["Meter",1]]

View File

@ -0,0 +1 @@
PROJCS["WGS 84 / UTM zone 33S",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",15],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32733"]]

View File

@ -0,0 +1 @@
PROJCS["WGS_1984_UTM_Zone_33S",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",15],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["Meter",1]]

View File

@ -0,0 +1 @@
PROJCS["WGS 84 / UTM zone 33S",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",15],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",10000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32733"]]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="service_area_lines" type="ogr:service_area_lines_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="service_area_lines_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:MultiLineStringPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="type" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="254"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="start" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="254"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="service_area_no_bounds" type="ogr:service_area_no_bounds_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="service_area_no_bounds_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:MultiPointPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="type" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="254"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="start" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="254"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -3049,6 +3049,66 @@ tests:
start: skip
end: skip
- algorithm: qgis:serviceareafrompoint
name: Service area (from point, shortest, no bounds)
params:
DEFAULT_DIRECTION: 2
DEFAULT_SPEED: 5.0
INCLUDE_BOUNDS: false
INPUT:
name: roads.gml
type: vector
START_POINT: 1002465.0089601517,6221875.432489508
STRATEGY: 0
TOLERANCE: 0.0
TRAVEL_COST: 700.0
VALUE_BACKWARD: ''
VALUE_BOTH: ''
VALUE_FORWARD: ''
results:
OUTPUT:
name: expected/service_area_no_bounds.gml
type: vector
compare:
ignore_crs_check: true
geometry:
precision: 2
fields:
cost:
precision: 2
start: skip
end: skip
- algorithm: qgis:serviceareafrompoint
name: Service area from point, output lines
params:
DEFAULT_DIRECTION: 2
DEFAULT_SPEED: 5.0
INCLUDE_BOUNDS: false
INPUT:
name: roads.gml
type: vector
START_POINT: 1003654.8065293541,6222397.723338357 [EPSG:32733]
STRATEGY: 0
TOLERANCE: 0.0
TRAVEL_COST: 400.0
VALUE_BACKWARD: ''
VALUE_BOTH: ''
VALUE_FORWARD: ''
results:
OUTPUT_LINES:
name: expected/service_area_lines.gml
type: vector
compare:
ignore_crs_check: true
geometry:
precision: 2
fields:
cost:
precision: 2
start: skip
end: skip
- algorithm: qgis:serviceareafromlayer
name: Service area from layer
params:
@ -3083,6 +3143,75 @@ tests:
- d
- type
- algorithm: qgis:serviceareafromlayer
name: Service area from layer no bounds
params:
DEFAULT_DIRECTION: 2
DEFAULT_SPEED: 5.0
INCLUDE_BOUNDS: false
INPUT:
name: roads.gml
type: vector
START_POINTS:
name: custom/route_points.gml
type: vector
STRATEGY: 0
TOLERANCE: 0.0
TRAVEL_COST: 700.0
VALUE_BACKWARD: ''
VALUE_BOTH: ''
VALUE_FORWARD: ''
results:
OUTPUT:
name: expected/service_area_from_layer_no_bounds.shp
type: vector
compare:
ignore_crs_check: true
geometry:
precision: 2
fields:
cost:
precision: 2
start: skip
end: skip
pk:
- d
- type
- algorithm: qgis:serviceareafromlayer
name: Service area from layer (lines)
params:
DEFAULT_DIRECTION: 2
DEFAULT_SPEED: 5.0
INCLUDE_BOUNDS: false
INPUT:
name: roads.gml
type: vector
START_POINTS:
name: custom/route_points.gml
type: vector
STRATEGY: 0
TOLERANCE: 0.0
TRAVEL_COST: 500.0
VALUE_BACKWARD: ''
VALUE_BOTH: ''
VALUE_FORWARD: ''
results:
OUTPUT_LINES:
name: expected/service_area_from_layer_lines.shp
type: vector
compare:
ignore_crs_check: true
geometry:
precision: 2
fields:
cost:
precision: 2
start: skip
end: skip
pk:
- d
- type
- algorithm: qgis:createattributeindex
name: Create attribute index

View File

@ -38,7 +38,8 @@ class ANALYSIS_EXPORT QgsGraphAnalyzer
* \param source source graph
* \param startVertexIdx index of the start vertex
* \param criterionNum index of the optimization strategy
* \param resultTree array that represents shortest path tree. resultTree[ vertexIndex ] == inboundingArcIndex if vertex reachable, otherwise resultTree[ vertexIndex ] == -1
* \param resultTree array that represents shortest path tree. resultTree[ vertexIndex ] == inboundingArcIndex if vertex reachable, otherwise resultTree[ vertexIndex ] == -1.
* Note that the startVertexIdx will also have a value of -1 and may need special handling by callers.
* \param resultCost array of the paths costs
*/
static void SIP_PYALTERNATIVETYPE( SIP_PYLIST ) dijkstra( const QgsGraph *source, int startVertexIdx, int criterionNum, QVector<int> *resultTree = nullptr, QVector<double> *resultCost = nullptr );