Belangrijk

Vertalen is een inspanning van de gemeenschap waaraan u deel kunt nemen. Deze pagina is momenteel voor 100.00% vertaald.

19. Bibliotheek Netwerkanalyse

Hint

De codesnippers op deze pagina hebben de volgende import nodig als u buiten de console van PyQGIS bent:

from qgis.core import (
  QgsVectorLayer,
  QgsPointXY,
)

De bibliotheek Netwerkanalyse kan worden gebruikt voor:

  • rekenkundige grafieken maken uit geografische gegevens (polylijn vectorlagen)

  • basismethoden implementeren vanuit grafiektheorie (momenteel alleen Dijkstra’s algoritme)

In het kort kan een typisch gebruik worden omschreven als:

  1. maakt rekenkundige grafiek uit geo-gegevens (gewoonlijk polylijn vectorlaag)

  2. voert grafiekanalyse uit

  3. gebruikt resultaten van analyse (door ze, bijvoorbeeld, te visualiseren)

19.1. Een grafiek bouwen

Het eerste dat u moet doen — is om invoergegevens voor te bereiden, dat is een vectorlaag converteren naar een grafiek. Alle verdere acties zullen deze grafiek gebruiken, niet de laag.

Als een bron kunnen we elke polylijn vectorlaag gebruiken. Knopen van de polylijnen worden punten in de grafiek, en segmenten van de polylijnen worden randen van de grafiek. Indien verscheidene knopen dezelfde coördinaten hebben dan zijn zij dezelfde knop in de grafiek. Dus twee lijnen die een gemeenschappelijk knoop hebben worden aan elkaar verbonden.

Aanvullend, gedurende het maken van de grafiek is het mogelijk om de een willekeurige aantal aanvullende punten “vast te zetten” (“te verbinden”) aan de invoer vectorlaag. Voor elk aanvullend punt zal een overeenkomst worden gevonden — het dichtstbijzijnde punt in de grafiek of de dichtstbijzijnde gelegen rand. In het laatste geval zal de rand worden gesplitst en een nieuw punt worden toegevoegd.

Attributen van de vectorlaag en de lengte van een rand kunnen worden gebruikt als de eigenschappen van een rand.

Converteren van een vectorlaag naar de grafiek wordt gedaan met behulp van het Builder programmeringspatroon. Een grafiek wordt geconstrueerd met behulp van een zogenaamde Director. Er is nu nog slechts één Director: QgsVectorLayerDirector. De director stelt de basisinstellingen in die zullen worden gebruikt om een grafiek uit een lijn-vectorlaag te maken, gebruikt door de builder om de grafiek te maken. Momenteel, net zoals in het geval van de Director, bestaat er slechts één builder: QgsGraphBuilder, die objecten QgsGraph maakt. U wilt misschien uw eigen builders implementeren die een grafiek bouwen die compatibel is met bibliotheken zoals BGL of NetworkX.

Voor het berekenen van de eigenschappen van de rand wordt het programmeringspatroon strategy gebruikt. Momenteel zijn alleen beschikbaar QgsNetworkDistanceStrategy strategie (die rekening houdt met de lengte van de route) en QgsNetworkSpeedStrategy (die ook rekening houdt met de snelheid). U kunt uw eigen strategie implementeren die alle noodzakelijke parameters zal gebruiken.

Het is tijd om het proces in te duiken.

  1. Als eerste, om deze bibliotheek te kunnen gebruiken, zouden we de module analysis moeten importeren:

    from qgis.analysis import *
    
  2. Dan enkele voorbeelden voor het maken van een 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)
    

    We zouden, om een director te construeren, een vectorlaag door moeten geven, die zal worden gebruikt als de bron voor de structuur van de grafiek en informatie over toegestane bewegingen over elke segment van de weg (één richting of beide, directe of tegengestelde richting). De aanroep ziet er uit zoals deze (meer details over de parameters zijn te vinden in qgis.analysis.QgsVectorLayerDirector):

    1director = QgsVectorLayerDirector(
    2    vectorLayer,
    3    directionFieldId,
    4    directDirectionValue,
    5    reverseDirectionValue,
    6    bothDirectionValue,
    7    defaultDirection,
    8)
    
  3. Het is dan nodig om een strategie te maken voor het berekenen van de eigenschappen van de rand

    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. En de director vertellen over deze strategie

    director = QgsVectorLayerDirector(vectorLayer, -1, "", "", "", 3)
    director.addStrategy(strategy)
    
  5. Nu kunnen we de builder gebruiken, wat de grafiek zal maken met de klasseconstructor QgsGraphBuilder.

    # only CRS is set, all other values are defaults
    builder = QgsGraphBuilder(vectorLayer.crs())
    
  6. Ook kunnen we verscheidene punten definiëren, die zullen worden gebruikt in de analyse. Bijvoorbeeld:

    startPoint = QgsPointXY(1179720.1871, 5419067.3507)
    endPoint = QgsPointXY(1180616.0205, 5419745.7839)
    
  7. Nu is alles op zijn plaats dus kunnen we de grafiek bouwen en deze punten daaraan “verbinden”:

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

    Bouwen van de grafiek kan enige tijd vergen (wat afhankelijk is van het aantal objecten in een laag en de grootte van de laag). tiedPoints is een lijst met coördinaten van de “verbonden” punten.

  8. Als het bouwen is voltooid kunnen we de grafiek ophalen en die gebruiken voor de analyse:

    graph = builder.graph()
    
  9. Met de volgende code kunnen we de vertex-indexen van onze punten krijgen:

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

19.2. Grafiekanalyse

Netwerkanalyse wordt gebruikt om antwoord te vinden op twee vragen: welke punten zijn verbonden en hoe het kortste pad te vinden. De bibliotheek Network analysis verschaft Dijkstra’s algoritme om deze problemen op te lossen.

Dijkstra’s algoritme zoekt de kortste route van één van de punten van de grafiek naar alle andere en de waarden van de parameters voor optimalisatie. De resultaten kunnen worden weergegeven als een kortste pad-boom.

De kortste pad-boom is een gedirigeerde gewogen grafiek (of meer precies een boom) met de volgende eigenschappen:

  • slechts één punt heeft geen inkomende randen — de wortel van de boom

  • alle andere punten hebben slechts één inkomende rand

  • als punt B bereikbaar is vanuit punt A, dan is het pad van A naar B het enige beschikbare pad en is het optimaal (kortste) op deze grafiek

Gebruik, om de boom van het kortste pad te verkrijgen, de methoden shortestTree() en dijkstra() van de klasse QgsGraphAnalyzer. Aanbevolen wordt om de methode dijkstra() te gebruiken omdat die sneller werkt en het geheugen meer efficiënt gebruikt.

De methode shortestTree() is handig wanneer u over de boom van het kortste pad wilt wandelen. Het maakt altijd een nieuw grafiekobject (QgsGraph) en accepteert drie variabelen:

  • source — grafiek voor invoer

  • startVertexIdx — index van het punt op de boom (de wortel van de boom)

  • criterionNum — nummer van te gebruiken eigenschap van de rand (beginnend vanaf 0).

tree = QgsGraphAnalyzer.shortestTree(graph, startId, 0)

De methode dijkstra() heeft dezelfde argumente, maar geeft een tuple met arrays terug:

  • In de eerste array bevat element n de index van de inkomende rand of -1 als er geen inkomende randen zijn.

  • In de tweede array bevat element n de afstand vanaf de bron van de boom tot punt n of DOUBLE_MAX als punt n onbereikbaar is vanaf de bron.

(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, startId, 0)

Hier is enige eenvoudige code om de boom van het kortste pad weer te geven met de grafiek die is gemaakt met de methode shortestTree() of de methode dijkstra() (selecteer de lijnenlaag in het paneel Lagen en vervang de coördinaten door die van uzelf).

Waarschuwing

Gebruik deze code alleen als voorbeeld, Het maakt heel veel objecten QgsRubberBand en zou zeer traag kunnen zijn voor grote gegevenssets.

 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. Kortste pad zoeken

De volgende benadering wordt gebruikt om het optimale pad tussen twee punten te zoeken. Beide punten (begin A en einde B) zijn “verbonden” met de grafiek wanneer die wordt gebouwd. Dan bouwen we met de methode shortestTree() of dijkstra() de boom voor het kortste pad met de wortel in beginpunt A. In dezelfde boom zoeken we ook naar eindpunt B en beginnen te lopen door de boom vanaf punt B naar punt A. Het gehele algoritme kan worden geschreven als:

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

Op dit punt hebben we het pad, in de vorm van de geïnverteerde lijst van punten (punten zijn vermeld in de omgekeerde volgorde van eindpunt naar beginpunt) die zullen worden bezocht gedurende het lopen over dit pad.

Hier is de voorbeeldcode voor de console van Python in QGIS (u zou misschien een lijnenlaag willen laden en selecteren in de inhoudsopgave en de coördinaten in de code te vervangen door die van uzelf) dat de methode shortestTree() of de methode dijkstra() gebruikt:

 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. Beschikbare gebieden

Het beschikbare gebied voor punt A is de subset van punten op de grafiek die toegankelijk zijn vanuit punt A en de kosten van de paden van A naar deze punten zijn niet groter dan een bepaalde waarde.

Dit kan duidelijker worden weergegeven met behulp van het volgende voorbeeld: “Er is een brandweergarage. Welke delen van de stad kan een brandweerauto bereiken in 5 minuten? 10 minuten? 15 minuten?”. De antwoorden op deze vragen zijn de beschikbare gebieden voor deze brandweergarage.

We kunnen de methode dijkstra() van de klasse QgsGraphAnalyzer gebruiken om de beschikbare gebieden te zoeken. Het is voldoende om de elementen van de array met kosten te vergelijken met een vooraf gedefinieerde waarde. Als de kosten[i] minder zijn dan of gelijk zijn aan een vooraf gedefinieerde waarde, dan ligt punt i binnen het beschikbare gebied, anders ligt het er buiten.

Een wat moeilijker probleem is om de grenzen van de beschikbare gebieden te verkrijgen. De ondergrens is de set punten die nog steeds toegankelijk zijn, en de bovengrens is de set punten die niet toegankelijk zijn. In feite is dit eenvoudig: het is de grens van beschikbaarheid, gebaseerd op de randen van de boom van het kortste pad waarvoor het bronpunt van de rand toegankelijk is en het doelpunt van de rand is dat niet.

Hier is een voorbeeld:

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