Importante

La traduzione è uno sforzo comunitario you can join. Questa pagina è attualmente tradotta al 100.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:

  1. creare un grafo da geodati (solitamente layer vettoriali polilineari)

  2. eseguire l’analisi del grafo

  3. 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 polilinea. I nodi delle polilinee diventano vertici del grafo e i segmenti delle polilinee sono gli archi del grafo. Se più nodi hanno le stesse coordinate, allora costituiscono lo stesso vertice del grafo. Quindi due linee che hanno un nodo comune vengono 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.

Per calcolare le proprietà degli archi si utilizza il modello di programmazione strategy. Per ora sono disponibili solo la strategia QgsNetworkDistanceStrategy (che tiene conto della lunghezza del percorso) e QgsNetworkSpeedStrategy (che tiene conto anche della velocità). Puoi implementare una tua strategia che utilizzi tutti i parametri necessari.

È il momento di addentrarsi nel processo.

  1. Prima di tutto, per utilizzare questa libreria dobbiamo importare il modulo di analisi

    from qgis.analysis import *
    
  2. Poi alcuni esempi di creazione di un 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)
    

    Per costruire un director, dobbiamo passare un layer vettoriale che verrà utilizzato come origine per la struttura del grafo e le informazioni sul movimento consentito su ciascun segmento stradale (movimento unidirezionale o bidirezionale, direzione diretta o inversa). La chiamata ha questo aspetto (per maggiori dettagli sui parametri, vedere qgis.analysis.QgsVectorLayerDirector):

    1director = QgsVectorLayerDirector(
    2    vectorLayer,
    3    directionFieldId,
    4    directDirectionValue,
    5    reverseDirectionValue,
    6    bothDirectionValue,
    7    defaultDirection,
    8)
    
  3. È 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)
    
  4. E informare il director di questa strategia

    director = QgsVectorLayerDirector(vectorLayer, -1, "", "", "", 3)
    director.addStrategy(strategy)
    
  5. Ora possiamo usare il builder, che creerà il grafo, utilizzando il costruttore della classe QgsGraphBuilder.

    # only CRS is set, all other values are defaults
    builder = QgsGraphBuilder(vectorLayer.crs())
    
  6. Possiamo anche definire diversi punti, che verranno utilizzati nell’analisi. Ad esempio:

    startPoint = QgsPointXY(1179720.1871, 5419067.3507)
    endPoint = QgsPointXY(1180616.0205, 5419745.7839)
    
  7. Ora tutto è pronto per costruire il grafico e «collegare» questi punti ad esso:

    tiedPoints = director.makeGraph(builder, [startPoint, endPoint])
    

    La creazione del grafico può richiedere un certo tempo (che dipende dal numero di elementi nel layer e dalle dimensioni del layer. tiedPoints è un elenco con le coordinate dei punti «collegati».

  8. Al termine dell’operazione di costruzione, possiamo ottenere il grafico e utilizzarlo per l’analisi:

    graph = builder.graph()
    
  9. Con il codice seguente possiamo ottenere gli indici dei vertici dei nostri punti:

    startId = graph.findVertex(tiedPoints[0])
    endId = graph.findVertex(tiedPoints[1])
    

19.2. Analisi grafo

L’analisi delle reti viene utilizzata per trovare le risposte a due domande: quali vertici sono collegati e come trovare il percorso più breve. Per risolvere questi problemi, la libreria di analisi delle reti fornisce l’algoritmo di Dijkstra.

L’algoritmo di Dijkstra trova il percorso più breve tra 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.

Il metodo shortestTree() è utile quando vuoi percorrere l’albero del percorso più breve. Crea sempre un nuovo oggetto grafo (QgsGraph) e accetta tre variabili:

  • source — grafo in ingresso

  • startVertexIdx — 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)

Ecco un codice molto semplice per visualizzare l’albero del percorso più breve utilizzando il grafo creato con il metodo shortestTree() o dijkstra() (seleziona il layer linestring nel pannello Layer e sostituisci le coordinate con le tue).

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

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 verranno visitati durante il tragitto lungo questo percorso.

Ecco un esempio di codice per la console Python di QGIS (potrebbe essere necessario caricare e selezionare un layer di stringhe di linee nel TOC e sostituire le coordinate nel codice con le proprie) che utilizza il metodo shortestTree() o dijkstra():

 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)

19.2.2. Zone di disponibilità

L’area 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 è ottenere i confini dell’area di disponibilità. Il bordo inferiore è l’insieme dei vertici ancora accessibili, mentre il bordo superiore è l’insieme dei vertici non accessibili. In realtà è semplice: si tratta del confine di disponibilità basato sugli archi dell’albero del percorso più breve per il quale il vertice di origine dell’arco è accessibile e il vertice di destinazione dell’arco non lo è.

Ecco un esempio:

 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))