Important
Traducerea este un efort al comunității, la care puteți să vă alăturați. În prezent, această pagină este tradusă 48.21%.
19. Biblioteca de analiză a rețelelor
Sugestie
Fragmentele de cod de pe această pagină necesită următoarele importuri, dacă vă aflați în afara consolei pyqgis:
from qgis.core import (
QgsVectorLayer,
QgsPointXY,
)
The network analysis library can be used to:
create mathematical graph from geographical data (polyline vector layers)
implement basic methods from graph theory (currently only Dijkstra’s algorithm)
Pe scurt, un caz tipic de utilizare poate fi descris astfel:
crearea grafului din geodate (de obicei un strat vectorial de tip polilinie)
rularea analizei grafului
folosirea rezultatelor analizei (de exemplu, vizualizarea lor)
19.1. Construirea unui graf
Primul lucru pe care trebuie să-l faceți — este de a pregăti datele de intrare, ceea ce înseamnă conversia stratului vectorial într-un graf. Toate acțiunile viitoare vor folosi acest graf, și nu stratul.
Ca și sursă putem folosi orice strat vectorial de tip polilinie. Nodurile poliliniilor devin noduri ale grafului, segmentele poliliniilor reprezentând marginile grafului. În cazul în care mai multe noduri au aceleași coordonate, atunci ele sunt în același nod al grafului. Astfel, două linii care au un nod comun devin conectate între ele.
În plus, în timpul creării grafului este posibilă „fixarea” («legarea”) de stratul vectorial de intrare a oricărui număr de puncte suplimentare. Pentru fiecare punct suplimentar va fi găsită o potrivire — cel mai apropiat nod sau cea mai apropiată muchie a grafului. În ultimul caz muchia va fi divizată iar noul nod va fi adăugat.
Atributele stratului vectorial și lungimea unei muchii pot fi folosite ca proprietăți ale marginii.
Converting from a vector layer to the graph is done using the
Builder programming pattern.
A graph is constructed using a so-called Director.
There is only one Director for now: QgsVectorLayerDirector.
The director sets the basic settings that will be used to construct a graph
from a line vector layer, used by the builder to create the graph.
Currently, as in the case with the director, only one builder exists:
QgsGraphBuilder,
that creates QgsGraph objects.
You may want to implement your own builders that will build a graph compatible
with such libraries as BGL
or 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.
Este timpul de a aprofunda acest proces.
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)
Este necesară, apoi, crearea unei strategii pentru calcularea proprietăților marginii
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)
Apoi spuneți directorului despre această strategie
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. Analiza grafului
Analiza de rețea este utilizată pentru a găsi răspunsuri la două întrebări: care noduri sunt conectate și identificarea celei mai scurte căi. Pentru a rezolva această problemă, biblioteca de analiză de rețea oferă algoritmul lui Dijkstra.
Algoritmul lui Dijkstra găsește cea mai bună cale între unul dintre vârfurile grafului și toate celelalte, precum și valorile parametrilor de optimizare. Rezultatele pot fi reprezentate ca cel mai scurt arbore.
The shortest path tree is a directed weighted graph (or more precisely a tree) with the following properties:
doar un singur nod nu are muchii de intrare — rădăcina arborelui
toate celelalte noduri au numai o margine de intrare
dacă nodul B este accesibil din nodul A, apoi calea de la A la B este singura disponibilă și este optimă (cea mai scurtă) în acest graf
To get the shortest path tree use the methods
shortestTree()
and dijkstra()
of the QgsGraphAnalyzer class.
It is recommended to use the dijkstra() method
because it works faster and uses memory more efficiently.
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— input graphstartVertexIdx— index of the point on the tree (the root of the tree)criterionNum— number of edge property to use (started from 0).
tree = QgsGraphAnalyzer.shortestTree(graph, startId, 0)
The dijkstra() method has the
same arguments, but returns a tuple of arrays:
In the first array, element n contains index of the incoming edge or -1 if there are no incoming edges.
In the second array, element n contains the distance from the root of the tree to vertex n or DOUBLE_MAX if vertex n is unreachable from the root.
(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).
Atenționare
Use this code only as an example, it creates a lot of
QgsRubberBand objects and may be slow on large datasets.
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. Găsirea celor mai scurte căi
To find the optimal path between two points the following approach is used.
Both points (start A and end B) are „tied” to the graph when it is built.
Then using the shortestTree()
or dijkstra() method
we build the shortest path tree with root in the start point A.
In the same tree we also find the end point B and start to walk through the tree
from point B to point A.
The whole algorithm can be written as:
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
În acest moment avem calea, sub formă de listă inversată de noduri (nodurile sunt listate în ordine inversă, de la punctul de final către cel de start), ele fiind vizitate în timpul parcurgerii căii.
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. Ariile de disponibilitate
Aria de disponibilitate a nodului A este un subset de noduri ale graf-ului, care sunt accesibile din nodul A iar costurile căii de la A la aceste noduri nu sunt mai mari decât o anumită valoare.
Mai clar, acest lucru poate fi dovedit cu următorul exemplu: „Există o echipă de intervenție în caz de incendiu. Ce zone ale orașului acoperă această echipă în 5 minute? Dar în 10 minute? Dar în 15 minute?”. Răspunsul la aceste întrebări îl reprezintă zonele de disponibilitate ale echipei de intervenție.
To find the areas of availability we can use
the dijkstra() method
of the QgsGraphAnalyzer class.
It is enough to compare the elements of the cost array with a predefined value.
If cost[i] is less than or equal to a predefined value,
then vertex i is inside the area of availability, otherwise it is outside.
Mai dificilă este obținerea granițelor zonei de disponibilitate. Marginea de jos reprezintă un set de noduri care încă sunt accesibile, iar marginea de sus un set de noduri inaccesibile. De fapt, acest lucru este simplu: marginea disponibilă a atins aceste margini parcurgând arborele cel mai scurt, pentru care nodul de start este accesibil, spre deosebire de celelalt capăt, care nu este accesibil.
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))