Viktigt
Översättning är en gemenskapsinsats du kan gå med i. Den här sidan är för närvarande översatt till 62.50%.
19. Bibliotek för nätverksanalys
Råd
Kodsnuttarna på den här sidan behöver följande import om du befinner dig utanför pyqgis-konsolen:
from qgis.core import (
QgsVectorLayer,
QgsPointXY,
)
Biblioteket för nätverksanalys kan användas för att:
skapa en matematisk graf från geografiska data (polylinje-vektorlager)
implementera grundläggande metoder från grafteori (för närvarande endast Dijkstras algoritm)
Kortfattat kan ett typiskt användningsfall beskrivas som:
skapa graf från geodata (vanligtvis polylinjevektorlager)
analys av körgrafer
använda analysresultat (t.ex. visualisera dem)
19.1. Bygga upp en graf
Det första du behöver göra — är att förbereda indata, det vill säga att konvertera ett vektorlager till en graf. Alla ytterligare åtgärder kommer att använda denna graf, inte lagret.
As a source we can use any polyline vector layer. Nodes of the polylines become graph vertices, and segments of the polylines are graph edges. If several nodes have the same coordinates then they are the same graph vertex. So two lines that have a common node become connected to each other.
Under skapandet av grafen är det dessutom möjligt att ”fixera” (”knyta”) ytterligare ett antal punkter till det ingående vektorlagret. För varje ytterligare punkt hittas en matchning — den närmaste grafvertexen eller den närmaste grafkanten. I det senare fallet delas kanten och ett nytt vertex läggs till.
Vektorlagrets attribut och längden på en kant kan användas som egenskaper för en kant.
Konvertering från ett vektorlager till grafen görs med hjälp av programmeringsmönstret Builder. En graf konstrueras med hjälp av en så kallad Director. Det finns bara en Director för tillfället: QgsVectorLayerDirector. Director anger de grundläggande inställningar som ska användas för att konstruera en graf från ett linjevektorlager, som används av byggaren för att skapa grafen. För närvarande finns det, precis som i fallet med director, bara en byggare: QgsGraphBuilder, som skapar QgsGraph-objekt. Du kanske vill implementera dina egna byggare som bygger en graf som är kompatibel med sådana bibliotek som BGL eller NetworkX.
To calculate edge properties the programming pattern
strategy is used.
For now only QgsNetworkDistanceStrategy
strategy (that takes into account the length of the route)
and QgsNetworkSpeedStrategy
(that also considers the speed) are available.
You can implement your own strategy that will use all necessary parameters.
Det är dags att dyka in i processen.
First of all, to use this library we should import the analysis module:
from qgis.analysis import *
Then some examples for creating a director:
1# Don't use information about road direction from layer attributes, 2# all roads are treated as two-way 3director = QgsVectorLayerDirector( 4 vectorLayer, -1, "", "", "", QgsVectorLayerDirector.DirectionBoth 5)
1# Use field with index 5 as source of information about road direction. 2# one-way roads with direct direction have attribute value "yes", 3# one-way roads with reverse direction have the value "1", and accordingly 4# bidirectional roads have "no". By default roads are treated as two-way. 5# This scheme can be used with OpenStreetMap data 6director = QgsVectorLayerDirector( 7 vectorLayer, 5, "yes", "1", "no", QgsVectorLayerDirector.DirectionBoth 8)
To construct a director, we should pass a vector layer that will be used as the source for the graph structure and information about allowed movement on each road segment (one-way or bidirectional movement, direct or reverse direction). The call looks like this (find more details on the parameters at
qgis.analysis.QgsVectorLayerDirector):1director = QgsVectorLayerDirector( 2 vectorLayer, 3 directionFieldId, 4 directDirectionValue, 5 reverseDirectionValue, 6 bothDirectionValue, 7 defaultDirection, 8)
Det är då nödvändigt att skapa en strategi för att beräkna kantegenskaper
1# The index of the field that contains information about the edge speed 2attributeId = 1 3# Default speed value 4defaultValue = 50 5# Conversion from speed to metric units ('1' means no conversion) 6toMetricFactor = 1 7strategy = QgsNetworkSpeedStrategy(attributeId, defaultValue, toMetricFactor)
Och berätta för chefen om denna strategi
director = QgsVectorLayerDirector(vectorLayer, -1, "", "", "", 3) director.addStrategy(strategy)
Now we can use the builder, which will create the graph, using the
QgsGraphBuilderclass constructor.# only CRS is set, all other values are defaults builder = QgsGraphBuilder(vectorLayer.crs())
Also we can define several points, which will be used in the analysis. For example:
startPoint = QgsPointXY(1179720.1871, 5419067.3507) endPoint = QgsPointXY(1180616.0205, 5419745.7839)
Now all is in place so we can build the graph and ”tie” these points to it:
tiedPoints = director.makeGraph(builder, [startPoint, endPoint])
Building the graph can take some time (which depends on the number of features in a layer and layer size).
tiedPointsis a list with coordinates of ”tied” points.When the build operation is finished we can get the graph and use it for the analysis:
graph = builder.graph()
With the next code we can get the vertex indexes of our points:
startId = graph.findVertex(tiedPoints[0]) endId = graph.findVertex(tiedPoints[1])
19.2. Grafisk analys
Networks analysis is used to find answers to two questions: which vertices are connected and how to find a shortest path. To solve these problems the network analysis library provides Dijkstra’s algorithm.
Dijkstra’s algorithm finds the shortest route from one of the vertices of the graph to all the others and the values of the optimization parameters. The results can be represented as a shortest path tree.
Trädet med den kortaste vägen är en riktad viktad graf (eller mer exakt ett träd) med följande egenskaper:
endast ett toppunkt har inga inkommande kanter - trädets rot
all other vertices have only one incoming edge
om toppunkt B kan nås från toppunkt A, så är vägen från A till B den enda tillgängliga vägen och den är optimal (kortast) i denna graf
För att få trädet med den kortaste vägen används metoderna shortestTree() och dijkstra() i klassen QgsGraphAnalyzer. Det rekommenderas att använda metoden dijkstra() eftersom den är snabbare och använder minnet mer effektivt.
The shortestTree() method
is useful when you want to walk around the shortest path tree.
It always creates a new graph object (QgsGraph)
and accepts three variables:
source— ingångsgrafstartVertexIdx— index för punkten på trädet (trädets rot)criterionNum— antal edge-egenskaper som ska användas (börjar från 0).
tree = QgsGraphAnalyzer.shortestTree(graph, startId, 0)
Metoden dijkstra() har samma argument, men returnerar en tupel av arrayer:
I den första arrayen innehåller element n index för den inkommande kanten eller -1 om det inte finns några inkommande kanter.
I den andra arrayen innehåller elementet n avståndet från trädets rot till toppunkt n eller DOUBLE_MAX om toppunkt n inte kan nås från roten.
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, startId, 0)
Here is some very simple code to display the shortest path tree using the graph created
with the shortestTree()
or the dijkstra() method
(select linestring layer in Layers panel and replace coordinates with your own).
Varning
Använd denna kod endast som ett exempel, den skapar många QgsRubberBand-objekt och kan vara långsam på stora datamängder.
1from qgis.core import *
2from qgis.gui import *
3from qgis.analysis import *
4from qgis.PyQt.QtCore import *
5from qgis.PyQt.QtGui import *
6
7vectorLayer = QgsVectorLayer(
8 "testdata/network.gpkg|layername=network_lines", "lines"
9)
10director = QgsVectorLayerDirector(
11 vectorLayer, -1, "", "", "", QgsVectorLayerDirector.DirectionBoth
12)
13strategy = QgsNetworkDistanceStrategy()
14director.addStrategy(strategy)
15builder = QgsGraphBuilder(vectorLayer.crs())
16
17pStart = QgsPointXY(1179661.925139, 5419188.074362)
18tiedPoint = director.makeGraph(builder, [pStart])
19pStart = tiedPoint[0]
20
21graph = builder.graph()
22
23idStart = graph.findVertex(pStart)
24
25tree = QgsGraphAnalyzer.shortestTree(graph, idStart, 0)
26
27i = 0
28while i < tree.edgeCount():
29 rb = QgsRubberBand(iface.mapCanvas())
30 rb.setColor(Qt.red)
31 rb.addPoint(tree.vertex(tree.edge(i).fromVertex()).point())
32 rb.addPoint(tree.vertex(tree.edge(i).toVertex()).point())
33 i = i + 1
1from qgis.core import *
2from qgis.gui import *
3from qgis.analysis import *
4from qgis.PyQt.QtCore import *
5from qgis.PyQt.QtGui import *
6
7vectorLayer = QgsVectorLayer(
8 "testdata/network.gpkg|layername=network_lines", "lines"
9)
10director = QgsVectorLayerDirector(
11 vectorLayer, -1, "", "", "", QgsVectorLayerDirector.DirectionBoth
12)
13strategy = QgsNetworkDistanceStrategy()
14director.addStrategy(strategy)
15builder = QgsGraphBuilder(vectorLayer.crs())
16
17pStart = QgsPointXY(1179661.925139, 5419188.074362)
18tiedPoint = director.makeGraph(builder, [pStart])
19pStart = tiedPoint[0]
20
21graph = builder.graph()
22
23idStart = graph.findVertex(pStart)
24(tree, costs) = QgsGraphAnalyzer.dijkstra(graph, idStart, 0)
25
26for edgeId in tree:
27 if edgeId == -1:
28 continue
29 rb = QgsRubberBand(iface.mapCanvas())
30 rb.setColor(Qt.red)
31 rb.addPoint(graph.vertex(graph.edge(edgeId).fromVertex()).point())
32 rb.addPoint(graph.vertex(graph.edge(edgeId).toVertex()).point())
19.2.1. Hitta de kortaste vägarna
För att hitta den optimala vägen mellan två punkter används följande tillvägagångssätt. Båda punkterna (start A och slut B) är ”bundna” till grafen när den byggs. Sedan använder vi metoden shortestTree() eller dijkstra() för att bygga trädet med den kortaste vägen med roten i startpunkten A. I samma träd hittar vi också slutpunkten B och börjar gå genom trädet från punkt B till punkt A. Hela algoritmen kan skrivas som:
1assign T = B
2while T != B
3 add point T to path
4 get incoming edge for point T
5 look for point TT, that is start point of this edge
6 assign T = TT
7add point A to path
At this point we have the path, in the form of the inverted list of vertices (vertices are listed in reversed order from end point to start point) that will be visited during traveling by this path.
Here is the sample code for QGIS Python Console (you may need to load and
select a linestring layer in TOC and replace coordinates in the code with yours) that
uses the shortestTree()
or dijkstra() method:
1from qgis.core import *
2from qgis.gui import *
3from qgis.analysis import *
4
5from qgis.PyQt.QtCore import *
6from qgis.PyQt.QtGui import *
7
8vectorLayer = QgsVectorLayer(
9 "testdata/network.gpkg|layername=network_lines", "lines"
10)
11director = QgsVectorLayerDirector(
12 vectorLayer, -1, "", "", "", QgsVectorLayerDirector.DirectionBoth
13)
14strategy = QgsNetworkDistanceStrategy()
15director.addStrategy(strategy)
16
17builder = QgsGraphBuilder(vectorLayer.sourceCrs())
18
19startPoint = QgsPointXY(1179661.925139, 5419188.074362)
20endPoint = QgsPointXY(1180942.970617, 5420040.097560)
21
22tiedPoints = director.makeGraph(builder, [startPoint, endPoint])
23tStart, tStop = tiedPoints
24
25graph = builder.graph()
26idxStart = graph.findVertex(tStart)
27
28tree = QgsGraphAnalyzer.shortestTree(graph, idxStart, 0)
29
30idxStart = tree.findVertex(tStart)
31idxEnd = tree.findVertex(tStop)
32
33if idxEnd == -1:
34 raise Exception("No route!")
35
36# Add last point
37route = [tree.vertex(idxEnd).point()]
38
39# Iterate the graph
40while idxEnd != idxStart:
41 edgeIds = tree.vertex(idxEnd).incomingEdges()
42 if len(edgeIds) == 0:
43 break
44 edge = tree.edge(edgeIds[0])
45 route.insert(0, tree.vertex(edge.fromVertex()).point())
46 idxEnd = edge.fromVertex()
47
48# Display
49rb = QgsRubberBand(iface.mapCanvas())
50rb.setColor(Qt.green)
51
52# This may require coordinate transformation if project's CRS
53# is different from layer's CRS
54for p in route:
55 rb.addPoint(p)
1from qgis.core import *
2from qgis.gui import *
3from qgis.analysis import *
4
5from qgis.PyQt.QtCore import *
6from qgis.PyQt.QtGui import *
7
8vectorLayer = QgsVectorLayer(
9 "testdata/network.gpkg|layername=network_lines", "lines"
10)
11director = QgsVectorLayerDirector(
12 vectorLayer, -1, "", "", "", QgsVectorLayerDirector.DirectionBoth
13)
14strategy = QgsNetworkDistanceStrategy()
15director.addStrategy(strategy)
16
17builder = QgsGraphBuilder(vectorLayer.sourceCrs())
18
19startPoint = QgsPointXY(1179661.925139, 5419188.074362)
20endPoint = QgsPointXY(1180942.970617, 5420040.097560)
21
22tiedPoints = director.makeGraph(builder, [startPoint, endPoint])
23tStart, tStop = tiedPoints
24
25graph = builder.graph()
26idxStart = graph.findVertex(tStart)
27idxEnd = graph.findVertex(tStop)
28
29(tree, costs) = QgsGraphAnalyzer.dijkstra(graph, idxStart, 0)
30
31if tree[idxEnd] == -1:
32 raise Exception('No route!')
33
34# Total cost
35cost = costs[idxEnd]
36
37# Add last point
38route = [graph.vertex(idxEnd).point()]
39
40# Iterate the graph
41while idxEnd != idxStart:
42 idxEnd = graph.edge(tree[idxEnd]).fromVertex()
43 route.insert(0, graph.vertex(idxEnd).point())
44
45# Display
46rb = QgsRubberBand(iface.mapCanvas())
47rb.setColor(Qt.red)
48
49# This may require coordinate transformation if project's CRS
50# is different from layer's CRS
51for p in route:
52 rb.addPoint(p)
19.2.2. Tillgängliga områden
The area of availability for vertex A is the subset of graph vertices that are accessible from vertex A and the cost of the paths from A to these vertices are not greater that some value.
Detta kan tydliggöras med följande exempel: ”Det finns en brandstation. Vilka delar av staden kan en brandbil nå på 5 minuter? 10 minuter? 15 minuter?”. Svaren på dessa frågor är brandstationens tillgänglighetsområden.
För att hitta områden med tillgänglighet kan vi använda metoden dijkstra() i klassen QgsGraphAnalyzer. Det räcker att jämföra elementen i kostnadsarrayen med ett fördefinierat värde. Om cost[i] är mindre än eller lika med ett fördefinierat värde, ligger vertex i inom tillgänglighetsområdet, annars ligger det utanför.
A more difficult problem is to get the borders of the area of availability. The bottom border is the set of vertices that are still accessible, and the top border is the set of vertices that are not accessible. In fact this is simple: it is the availability border based on the edges of the shortest path tree for which the source vertex of the edge is accessible and the target vertex of the edge is not.
Here is an example:
1director = QgsVectorLayerDirector(
2 vectorLayer, -1, "", "", "", QgsVectorLayerDirector.DirectionBoth
3)
4strategy = QgsNetworkDistanceStrategy()
5director.addStrategy(strategy)
6builder = QgsGraphBuilder(vectorLayer.crs())
7
8
9pStart = QgsPointXY(1179661.925139, 5419188.074362)
10delta = iface.mapCanvas().getCoordinateTransform().mapUnitsPerPixel() * 1
11
12rb = QgsRubberBand(iface.mapCanvas())
13rb.setColor(Qt.green)
14rb.addPoint(QgsPointXY(pStart.x() - delta, pStart.y() - delta))
15rb.addPoint(QgsPointXY(pStart.x() + delta, pStart.y() - delta))
16rb.addPoint(QgsPointXY(pStart.x() + delta, pStart.y() + delta))
17rb.addPoint(QgsPointXY(pStart.x() - delta, pStart.y() + delta))
18
19tiedPoints = director.makeGraph(builder, [pStart])
20graph = builder.graph()
21tStart = tiedPoints[0]
22
23idStart = graph.findVertex(tStart)
24
25(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, idStart, 0)
26
27upperBound = []
28r = 1500.0
29i = 0
30tree.reverse()
31
32while i < len(cost):
33 if cost[i] > r and tree[i] != -1:
34 outVertexId = graph.edge(tree [i]).toVertex()
35 if cost[outVertexId] < r:
36 upperBound.append(i)
37 i = i + 1
38
39for i in upperBound:
40 centerPoint = graph.vertex(i).point()
41 rb = QgsRubberBand(iface.mapCanvas())
42 rb.setColor(Qt.red)
43 rb.addPoint(QgsPointXY(centerPoint.x() - delta, centerPoint.y() - delta))
44 rb.addPoint(QgsPointXY(centerPoint.x() + delta, centerPoint.y() - delta))
45 rb.addPoint(QgsPointXY(centerPoint.x() + delta, centerPoint.y() + delta))
46 rb.addPoint(QgsPointXY(centerPoint.x() - delta, centerPoint.y() + delta))