Importante
La traduzione è uno sforzo comunitario you can join. Questa pagina è attualmente tradotta al 75.00%.
19. Libreria analisi di rete
Suggerimento
I frammenti di codice in questa pagina hanno bisogno delle seguenti importazioni se sei al di fuori della console di pyqgis:
from qgis.core import (
QgsVectorLayer,
QgsPointXY,
)
La libreria di analisi di rete può essere utilizzata per:
creare un grafico matematico a partire da dati geografici (layer vettoriali polilineari)
implementare i metodi di base della teoria dei grafi (attualmente solo l’algoritmo di Dijkstra)
In breve, un caso d’uso tipico può essere descritto come segue:
creare un grafo da geodati (solitamente layer vettoriali polilineari)
eseguire l’analisi del grafo
utilizzare i risultati dell’analisi (ad esempio, visualizzarli)
19.1. Costruire un grafo
La prima cosa da fare è preparare i dati di input, cioè convertire un layer vettoriale in un grafo. Tutte le azioni successive utilizzeranno questo grafo, non il layer.
Come sorgente possiamo utilizzare qualsiasi layer vettoriale di polilinee. I nodi delle polilinee diventano vertici del grafo e i segmenti delle polilinee sono bordi del grafo. Se più nodi hanno le stesse coordinate, allora sono lo stesso vertice del grafo. Quindi due linee che hanno un nodo in comune diventano collegate tra loro.
Inoltre, durante la creazione del grafo è possibile «fissare» («legare») al layer del vettore di input un numero qualsiasi di punti aggiuntivi. Per ogni punto aggiuntivo verrà trovata una corrispondenza — con il vertice del grafo più vicino o con il bordo del grafo più vicino. In quest’ultimo caso, il bordo verrà diviso e verrà aggiunto un nuovo vertice.
Gli attributi del layer vettoriale e la lunghezza di un bordo possono essere utilizzati come proprietà di un bordo.
La conversione da un layer vettoriale a grafo viene effettuata utilizzando il modello di programmazione Builder. Un grafo viene costruito usando un cosiddetto Director. Per ora esiste un solo Director: QgsVectorLayerDirector. Il Director imposta le impostazioni di base che verranno utilizzate per costruire un grafico da un layer vettoriale di linee, utilizzato dal costruttore per creare il grafico. Attualmente, come nel caso del director, esiste un solo costruttore: QgsGraphBuilder, che crea oggetti QgsGraph. Puoi implementare i propri costruttori per creare un grafo compatibile con librerie come BGL o 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.
È il momento di addentrarsi nel processo.
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)
È quindi necessario creare una strategia per il calcolo delle proprietà dei bordi
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)
E informare il director di questa strategia
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. Analisi grafo
L’analisi di rete viene utilizzata per trovare le risposte a due domande: quali vertici sono connessi e come trovare il percorso più breve. Per risolvere questi problemi, la libreria di analisi di rete fornisce l’algoritmo di Dijkstra.
L’algoritmo di Dijkstra trova il percorso più breve da un vertice del grafo a tutti gli altri e i valori dei parametri di ottimizzazione. I risultati possono essere rappresentati come un albero del percorso più breve.
L’albero del percorso più breve è un grafo diretto pesato (o più precisamente un albero) con le seguenti proprietà:
solo un vertice non ha bordi in entrata: la radice dell’albero.
tutti gli altri vertici hanno un solo bordo in entrata
se il vertice B è raggiungibile dal vertice A, allora il percorso da A a B è il solo percorso disponibile ed è ottimale (il più breve) su questo grafo
Per ottenere l’albero del percorso più breve, utilizzare i metodi shortestTree() e dijkstra() della classe QgsGraphAnalyzer. Si consiglia di utilizzare il metodo dijkstra() perché funziona più velocemente e utilizza la memoria in modo più efficiente.
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— grafo in ingressostartVertexIdx— indice del punto sull” albero (la radice dell’albero)criterionNum— numero di proprietà del bordo da usare (a partire da 0).
tree = QgsGraphAnalyzer.shortestTree(graph, startId, 0)
Il metodo dijkstra() ha gli stessi argomenti, ma restituisce una tupla di array:
Nel primo array, l’elemento n contiene l’indice del bordo in entrata o -1 se non ci sono bordi in entrata.
Nel secondo array, l’elemento n contiene la distanza dalla radice dell’albero al vertice n o DOUBLE_MAX se il vertice n non è raggiungibile dalla radice.
(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).
Avvertimento
Usa questo codice solo come esempio; crea molti oggetti QgsRubberBand e può essere lento su grandi insiemi di dati.
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. Trovare i percorsi più brevi
Per trovare il percorso ottimale tra due punti si utilizza il seguente approccio. Entrambi i punti (inizio A e fine B) sono «legati» al grafo quando viene costruito. Quindi, utilizzando il metodo shortestTree() o dijkstra() si costruisce l’albero del percorso più breve con radice nel punto iniziale A. Nello stesso albero si trova anche il punto finale B e si inizia a percorrere l’albero dal punto B al punto A. L’intero algoritmo può essere scritto come:
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
A questo punto abbiamo il percorso, sotto forma di elenco invertito di vertici (i vertici sono elencati in ordine inverso, dal punto finale al punto iniziale) che saranno visitati durante il percorso.
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. Zone di disponibilità
La zona di disponibilità per il vertice A è il sottoinsieme dei vertici del grafo che sono accessibili dal vertice A e il costo dei percorsi da A a questi vertici non è superiore a un certo valore.
Questo può essere mostrato più chiaramente con il seguente esempio: «C’è una stazione dei vigili del fuoco. Quali zone della città può raggiungere un camion dei pompieri in 5 minuti? 10 minuti? 15 minuti?». Le risposte a queste domande sono le zone di disponibilità della caserma dei pompieri.
Per trovare le zone di disponibilità possiamo usare il metodo dijkstra() della classe QgsGraphAnalyzer. È sufficiente confrontare gli elementi dell’array cost con un valore predefinito. Se cost[i] è minore o uguale a un valore predefinito, allora il vertice i è all’interno della zona di disponibilità, altrimenti è fuori.
Un problema più difficile è quello di ottenere i confini della zona di disponibilità. Il confine inferiore è l’insieme dei vertici ancora accessibili, mentre il confine superiore è l’insieme dei vertici non accessibili. In realtà è semplice: si tratta del confine di disponibilità basato sui bordi dell’albero del cammino più breve per i quali il vertice di origine del bordo è accessibile e il vertice di destinazione del bordo no.
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))