
Qgis Impl Network Analysis
- 8 installs
- 29 repo stars
- Updated July 8, 2026
- openaec-foundation/qgis-claude-skill-package
Helps with ai & agent building tasks.
About
qgis-impl-network-analysis is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- qgis-impl-network-analysis
- AI & Agent Building
- AI-coding skill
Qgis Impl Network Analysis by the numbers
- 8 all-time installs (skills.sh)
- Ranked #12,339 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/qgis-claude-skill-package --skill qgis-impl-network-analysisAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 29 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/qgis-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
qgis-impl-network-analysis
Quick Reference
Core Classes (qgis.analysis)
| Class | Purpose |
|---|---|
QgsVectorLayerDirector | Controls edge direction rules for a line layer |
QgsNetworkDistanceStrategy | Cost = geometric edge length |
QgsNetworkSpeedStrategy | Cost = travel time (from speed field) |
QgsGraphBuilder | Constructs a QgsGraph from a directed layer |
QgsGraphAnalyzer | Static methods: dijkstra(), shortestTree() |
QgsGraph | In-memory graph with vertices and edges |
Workflow Steps
| Step | Class/Method | Output |
|---|---|---|
| 1. Set direction rules | QgsVectorLayerDirector(layer, ...) | Director object |
| 2. Add cost strategy | director.addStrategy(strategy) | Strategy index (0, 1, ...) |
| 3. Build graph | director.makeGraph(builder, points) | Tied (snapped) points |
| 4. Get graph object | builder.graph() | QgsGraph |
| 5. Find vertex IDs | graph.findVertex(tied_point) | Integer vertex ID |
| 6. Run Dijkstra | QgsGraphAnalyzer.dijkstra(graph, start, criterion) | (tree, cost) tuple |
| 7. Reconstruct path | Walk tree[] backwards from destination | List of QgsPointXY |
Processing Algorithm IDs
| Algorithm | ID |
|---|---|
| Shortest path (point to point) | native:shortestpathpointtopoint |
| Shortest path (point to layer) | native:shortestpathpointtolayer |
| Shortest path (layer to point) | native:shortestpathlayertopoint |
| Service area (from point) | native:serviceareafrompoint |
| Service area (from layer) | native:serviceareafromlayer |
---
Critical Warnings
NEVER assume all edges are bidirectional -- road networks contain one-way streets. ALWAYS configure QgsVectorLayerDirector with the correct direction field and values.
ALWAYS check tree[destination_id] == -1 before path reconstruction -- this value means the destination is unreachable from the source vertex.
ALWAYS use graph.findVertex(tied_point) with the tied (snapped) point returned by makeGraph(), NOT the original input point -- the original point does not exist in the graph.
NEVER skip adding a strategy before building the graph -- without at least one strategy, the graph has no edge costs and dijkstra() produces meaningless results.
ALWAYS use a projected CRS (meters) for distance-based analysis -- geographic CRS (degrees) produces incorrect distance calculations.
NEVER reconstruct a path using graph.vertex(current).incomingEdges() -- use tree[current] from the Dijkstra result instead, which gives the specific edge in the shortest path tree.
---
Decision Tree
Need network analysis?
├── Simple point-to-point route?
│ ├── Need full control over graph → Use QgsGraphBuilder + QgsGraphAnalyzer
│ └── Need quick result → Use processing.run("native:shortestpathpointtopoint")
├── Route from one point to many destinations?
│ └── Use processing.run("native:shortestpathpointtolayer")
├── Route from many origins to one destination?
│ └── Use processing.run("native:shortestpathlayertopoint")
├── Service area (reachability)?
│ ├── Single origin → Use processing.run("native:serviceareafrompoint")
│ ├── Multiple origins → Use processing.run("native:serviceareafromlayer")
│ └── Need boundary interpolation → Use QgsGraphAnalyzer.dijkstra() manually
├── Cost criterion?
│ ├── Distance (length) → QgsNetworkDistanceStrategy()
│ ├── Travel time → QgsNetworkSpeedStrategy(field_index, default_speed, factor)
│ └── Multiple criteria → Add multiple strategies, use criterion index in dijkstra()
└── Direction handling?
├── All bidirectional → directionFieldId = -1
├── One-way from attribute → directionFieldId = field index
└── All one-way (forward) → directionFieldId = -1, defaultDirection = DirectionForward---
Essential Patterns
Pattern 1: Complete Shortest Path Workflow
from qgis.analysis import (
QgsGraphBuilder, QgsVectorLayerDirector,
QgsNetworkDistanceStrategy, QgsGraphAnalyzer
)
from qgis.core import QgsProject, QgsPointXY, QgsGeometry, QgsPoint
# 1. Load road network layer
roads = QgsProject.instance().mapLayersByName("roads")[0]
# 2. Configure director (bidirectional, no direction field)
director = QgsVectorLayerDirector(
roads,
-1, # No direction field
'', '', '', # Direction values (unused)
QgsVectorLayerDirector.DirectionBoth # Default direction
)
# 3. Add distance-based cost strategy
director.addStrategy(QgsNetworkDistanceStrategy())
# 4. Define origin and destination
start_point = QgsPointXY(5.1214, 52.0907) # Utrecht
end_point = QgsPointXY(4.8952, 52.3702) # Amsterdam
# 5. Build graph (points get snapped to nearest network edge)
builder = QgsGraphBuilder(roads.crs())
tied_points = director.makeGraph(builder, [start_point, end_point])
graph = builder.graph()
# 6. Get vertex IDs from tied (snapped) points
start_id = graph.findVertex(tied_points[0])
end_id = graph.findVertex(tied_points[1])
# 7. Run Dijkstra from start vertex (criterion 0 = distance)
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, start_id, 0)
# 8. Check reachability
if tree[end_id] == -1:
raise ValueError("Destination is unreachable from origin")
# 9. Reconstruct path (walk backwards from destination)
route_points = [graph.vertex(end_id).point()]
current = end_id
while current != start_id:
edge = graph.edge(tree[current])
current = edge.fromVertex()
route_points.insert(0, graph.vertex(current).point())
# 10. Build route geometry
route_geom = QgsGeometry.fromPolyline(
[QgsPoint(p.x(), p.y()) for p in route_points]
)
print(f"Route distance: {cost[end_id]:.1f} map units")Pattern 2: One-Way Street Handling
# Find the direction field index
oneway_idx = roads.fields().indexOf('oneway')
director = QgsVectorLayerDirector(
roads,
oneway_idx, # Field containing direction info
'F', # Value meaning "forward only"
'T', # Value meaning "reverse only"
'B', # Value meaning "both directions"
QgsVectorLayerDirector.DirectionBoth # Default for unmatched values
)Direction constants:
| Constant | Meaning |
|---|---|
QgsVectorLayerDirector.DirectionForward | Digitized direction only |
QgsVectorLayerDirector.DirectionBackward | Reverse of digitized direction only |
QgsVectorLayerDirector.DirectionBoth | Both directions (bidirectional) |
Pattern 3: Travel Time Cost Strategy
speed_field_index = roads.fields().indexOf('speed_kmh')
strategy = QgsNetworkSpeedStrategy(
speed_field_index,
50.0, # Default speed when field value is NULL
1000.0 / 3600.0 # Conversion factor: km/h to m/s
)
director.addStrategy(strategy)Pattern 4: Multiple Cost Criteria
# Criterion 0: distance
director.addStrategy(QgsNetworkDistanceStrategy())
# Criterion 1: travel time
speed_idx = roads.fields().indexOf('speed_kmh')
director.addStrategy(QgsNetworkSpeedStrategy(speed_idx, 50.0, 1000.0 / 3600.0))
# Build graph once
builder = QgsGraphBuilder(roads.crs())
tied = director.makeGraph(builder, [start_point, end_point])
graph = builder.graph()
sid = graph.findVertex(tied[0])
eid = graph.findVertex(tied[1])
# Shortest by distance (criterion 0)
(tree_dist, cost_dist) = QgsGraphAnalyzer.dijkstra(graph, sid, 0)
# Fastest by travel time (criterion 1)
(tree_time, cost_time) = QgsGraphAnalyzer.dijkstra(graph, sid, 1)Pattern 5: Service Area Analysis
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, start_id, 0)
threshold = 5000.0 # 5000 meters (or seconds, depending on strategy)
# Collect fully reachable vertices (interior)
reachable = []
for vid in range(graph.vertexCount()):
if cost[vid] <= threshold and tree[vid] != -1:
reachable.append(graph.vertex(vid).point())
# Interpolate boundary points (edges that cross the threshold)
boundary = []
for vid in range(graph.vertexCount()):
if cost[vid] > threshold and tree[vid] != -1:
edge = graph.edge(tree[vid])
from_v = edge.fromVertex()
if cost[from_v] < threshold:
ratio = (threshold - cost[from_v]) / (cost[vid] - cost[from_v])
p1 = graph.vertex(from_v).point()
p2 = graph.vertex(vid).point()
interpolated = QgsPointXY(
p1.x() + ratio * (p2.x() - p1.x()),
p1.y() + ratio * (p2.y() - p1.y())
)
boundary.append(interpolated)
# Combine all reachable points for convex hull / polygon
all_points = reachable + boundaryPattern 6: Shortest Path Tree
# Get the full shortest path tree from a single origin
tree_graph = QgsGraphAnalyzer.shortestTree(graph, start_id, 0)
# tree_graph is a new QgsGraph containing only edges in the shortest path tree
# Extract all edges for visualization
for eid in range(tree_graph.edgeCount()):
edge = tree_graph.edge(eid)
from_pt = tree_graph.vertex(edge.fromVertex()).point()
to_pt = tree_graph.vertex(edge.toVertex()).point()
# Create line geometry for each edge
line = QgsGeometry.fromPolylineXY([from_pt, to_pt])---
Common Operations
Using Processing Algorithms for Routing
import processing
# Shortest path: point to point
result = processing.run("native:shortestpathpointtopoint", {
'INPUT': roads,
'STRATEGY': 0, # 0=Shortest, 1=Fastest
'DIRECTION_FIELD': 'oneway',
'VALUE_FORWARD': 'F',
'VALUE_BACKWARD': 'T',
'VALUE_BOTH': 'B',
'DEFAULT_DIRECTION': 2, # 0=Forward, 1=Backward, 2=Both
'SPEED_FIELD': 'speed_kmh',
'DEFAULT_SPEED': 50.0,
'TOLERANCE': 0.0,
'START_POINT': '5.1214,52.0907 [EPSG:4326]',
'END_POINT': '4.8952,52.3702 [EPSG:4326]',
'OUTPUT': 'TEMPORARY_OUTPUT'
})
route_layer = result['OUTPUT']Service Area via Processing
result = processing.run("native:serviceareafrompoint", {
'INPUT': roads,
'STRATEGY': 0, # 0=Shortest, 1=Fastest
'DIRECTION_FIELD': '',
'VALUE_FORWARD': '',
'VALUE_BACKWARD': '',
'VALUE_BOTH': '',
'DEFAULT_DIRECTION': 2,
'SPEED_FIELD': '',
'DEFAULT_SPEED': 50.0,
'TOLERANCE': 0.0,
'START_POINT': '5.1214,52.0907 [EPSG:4326]',
'TRAVEL_COST': 5000.0, # Distance or time threshold
'OUTPUT': 'TEMPORARY_OUTPUT'
})Creating a Route Layer from Graph Results
from qgis.core import QgsVectorLayer, QgsFeature, QgsField
from qgis.PyQt.QtCore import QVariant
# Create memory layer for the route
route_layer = QgsVectorLayer(
f"LineString?crs={roads.crs().authid()}",
"Route",
"memory"
)
provider = route_layer.dataProvider()
provider.addAttributes([
QgsField("distance", QVariant.Double),
])
route_layer.updateFields()
# Add route feature
feat = QgsFeature()
feat.setGeometry(route_geom)
feat.setAttributes([cost[end_id]])
provider.addFeature(feat)
route_layer.updateExtents()
QgsProject.instance().addMapLayer(route_layer)---
Reference Links
- references/methods.md -- API signatures for QgsGraphBuilder, QgsGraphAnalyzer, QgsVectorLayerDirector, strategy classes
- references/examples.md -- Complete working examples for routing and service area workflows
- references/anti-patterns.md -- Common mistakes in network analysis with corrections
Official Sources
- https://docs.qgis.org/3.34/en/docs/pyqgis_developer_cookbook/network_analysis.html
- https://qgis.org/pyqgis/3.34/analysis/QgsGraphAnalyzer.html
- https://qgis.org/pyqgis/3.34/analysis/QgsGraphBuilder.html
- https://qgis.org/pyqgis/3.34/analysis/QgsVectorLayerDirector.html
Anti-Patterns (QGIS Network Analysis)
1. Using Original Points Instead of Tied Points
# WRONG: Original point does not exist as a vertex in the graph
start_point = QgsPointXY(5.1214, 52.0907)
builder = QgsGraphBuilder(roads.crs())
tied_points = director.makeGraph(builder, [start_point])
graph = builder.graph()
start_id = graph.findVertex(start_point) # Returns wrong vertex or -1!
# CORRECT: ALWAYS use the tied (snapped) point returned by makeGraph()
start_id = graph.findVertex(tied_points[0])WHY: makeGraph() snaps input points to the nearest edge on the network. The original point coordinates do not match any vertex in the graph. Using the original point with findVertex() returns an incorrect or nonexistent vertex ID.
---
2. Skipping Reachability Check Before Path Reconstruction
# WRONG: Crashes if destination is unreachable
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, start_id, 0)
route = [graph.vertex(end_id).point()]
current = end_id
while current != start_id:
edge = graph.edge(tree[current]) # tree[end_id] is -1 → invalid edge!
current = edge.fromVertex()
route.insert(0, graph.vertex(current).point())
# CORRECT: ALWAYS check tree[end_id] first
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, start_id, 0)
if tree[end_id] == -1:
print("Destination unreachable")
else:
route = [graph.vertex(end_id).point()]
current = end_id
while current != start_id:
edge = graph.edge(tree[current])
current = edge.fromVertex()
route.insert(0, graph.vertex(current).point())WHY: When tree[vertex_id] == -1, the vertex is unreachable from the start. Passing -1 to graph.edge() causes an index error or undefined behavior.
---
3. Assuming All Edges Are Bidirectional
# WRONG: Ignores one-way streets in road networks
director = QgsVectorLayerDirector(
roads, -1, '', '', '',
QgsVectorLayerDirector.DirectionBoth
)
# CORRECT: Use the direction field when the data has one-way information
oneway_idx = roads.fields().indexOf('oneway')
if oneway_idx >= 0:
director = QgsVectorLayerDirector(
roads, oneway_idx,
'yes', 'reverse', 'no',
QgsVectorLayerDirector.DirectionBoth
)
else:
director = QgsVectorLayerDirector(
roads, -1, '', '', '',
QgsVectorLayerDirector.DirectionBoth
)WHY: Road networks contain one-way streets. Treating all edges as bidirectional produces routes that traverse one-way streets in the wrong direction, giving physically impossible results.
---
4. Forgetting to Add a Strategy
# WRONG: No strategy added — graph edges have no cost values
director = QgsVectorLayerDirector(roads, -1, '', '', '',
QgsVectorLayerDirector.DirectionBoth)
# director.addStrategy() is missing!
builder = QgsGraphBuilder(roads.crs())
tied = director.makeGraph(builder, points)
graph = builder.graph()
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, sid, 0) # Criterion 0 has no data
# CORRECT: ALWAYS add at least one strategy before building the graph
director.addStrategy(QgsNetworkDistanceStrategy())WHY: Without a strategy, there are no cost values for edges. Dijkstra cannot compute meaningful shortest paths when edge costs are undefined.
---
5. Using Geographic CRS for Distance Analysis
# WRONG: CRS is EPSG:4326 (degrees) — distances are in degrees, not meters
roads_4326 = QgsProject.instance().mapLayersByName("roads_wgs84")[0]
builder = QgsGraphBuilder(roads_4326.crs()) # Costs will be in degrees
# CORRECT: Reproject to a projected CRS first, or use a projected layer
roads_projected = QgsProject.instance().mapLayersByName("roads_utm")[0]
builder = QgsGraphBuilder(roads_projected.crs()) # Costs in metersWHY: QgsNetworkDistanceStrategy calculates edge length in CRS units. For EPSG:4326, units are degrees, making distance values meaningless for routing. ALWAYS use a projected CRS (meters) for distance-based analysis.
---
6. Using incomingEdges() for Path Reconstruction
# WRONG: incomingEdges() returns ALL incoming edges, not just the shortest path edge
current = end_id
while current != start_id:
incoming = graph.vertex(current).incomingEdges()
edge = graph.edge(incoming[0]) # Picks arbitrary edge, not shortest path edge!
current = edge.fromVertex()
# CORRECT: Use tree[] from Dijkstra result — it contains the specific shortest path edges
current = end_id
while current != start_id:
edge = graph.edge(tree[current]) # tree[current] = edge ID in shortest path
current = edge.fromVertex()WHY: A vertex can have multiple incoming edges. incomingEdges() returns all of them. Only tree[vertex_id] from the Dijkstra result identifies which specific edge belongs to the shortest path.
---
7. Wrong Criterion Index in Multi-Strategy Graphs
# WRONG: Strategies added in wrong order, then criterion index confused
director.addStrategy(QgsNetworkSpeedStrategy(speed_idx, 50.0, 1000.0 / 3600.0)) # index 0
director.addStrategy(QgsNetworkDistanceStrategy()) # index 1
# Developer thinks criterion 0 is distance, but it is actually time
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, sid, 0) # This is TIME, not distance
# CORRECT: Document strategy order and use matching criterion indices
director.addStrategy(QgsNetworkDistanceStrategy()) # index 0 = distance
director.addStrategy(QgsNetworkSpeedStrategy(speed_idx, 50.0, 1000.0 / 3600.0)) # index 1 = time
(tree_dist, cost_dist) = QgsGraphAnalyzer.dijkstra(graph, sid, 0) # Distance
(tree_time, cost_time) = QgsGraphAnalyzer.dijkstra(graph, sid, 1) # TimeWHY: The criterion index in dijkstra() corresponds to the order strategies were added via addStrategy(). Mixing up the order produces results optimized for the wrong metric.
---
8. Not Handling Disconnected Networks
# WRONG: Assumes all points are on the same connected component
points = [pointA, pointB, pointC, pointD]
tied = director.makeGraph(builder, points)
graph = builder.graph()
# Batch routing without checking connectivity
for i in range(len(points) - 1):
sid = graph.findVertex(tied[i])
eid = graph.findVertex(tied[i + 1])
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, sid, 0)
edge = graph.edge(tree[eid]) # Crashes if eid is on disconnected component!
# CORRECT: ALWAYS check tree[eid] != -1 for every pair
for i in range(len(points) - 1):
sid = graph.findVertex(tied[i])
eid = graph.findVertex(tied[i + 1])
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, sid, 0)
if tree[eid] == -1:
print(f"No path from point {i} to point {i+1}")
continue
# ... reconstruct pathWHY: Real-world road networks are not guaranteed to be fully connected. Islands, disconnected road segments, and one-way restrictions can make certain vertex pairs unreachable.
---
9. Incorrect Speed Strategy Conversion Factor
# WRONG: Speed field is in mph but using km/h conversion factor
director.addStrategy(QgsNetworkSpeedStrategy(
speed_idx, 50.0,
1000.0 / 3600.0 # This is km/h to m/s, wrong for mph!
))
# CORRECT: Use the right conversion factor for the speed unit
# For mph: 1609.344 / 3600.0
director.addStrategy(QgsNetworkSpeedStrategy(
speed_idx, 50.0,
1609.344 / 3600.0 # mph to m/s
))WHY: The toMetricFactor converts the speed field value to meters per second. Using the wrong factor produces incorrect travel time calculations, often off by a factor of ~1.6.
Working Code Examples (QGIS Network Analysis)
Example 1: Shortest Path Between Two Points (Full Workflow)
from qgis.analysis import (
QgsGraphBuilder, QgsVectorLayerDirector,
QgsNetworkDistanceStrategy, QgsGraphAnalyzer
)
from qgis.core import (
QgsProject, QgsPointXY, QgsGeometry, QgsPoint,
QgsVectorLayer, QgsFeature, QgsField
)
from qgis.PyQt.QtCore import QVariant
# Load road network
roads = QgsProject.instance().mapLayersByName("roads")[0]
# Configure director: all edges bidirectional
director = QgsVectorLayerDirector(
roads, -1, '', '', '',
QgsVectorLayerDirector.DirectionBoth
)
# Cost = geometric distance
director.addStrategy(QgsNetworkDistanceStrategy())
# Define start and end points
start_point = QgsPointXY(5.1214, 52.0907)
end_point = QgsPointXY(4.8952, 52.3702)
# Build graph
builder = QgsGraphBuilder(roads.crs())
tied_points = director.makeGraph(builder, [start_point, end_point])
graph = builder.graph()
# Find vertex IDs (ALWAYS use tied points, not originals)
start_id = graph.findVertex(tied_points[0])
end_id = graph.findVertex(tied_points[1])
# Run Dijkstra (criterion 0 = distance)
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, start_id, 0)
# ALWAYS check reachability before reconstruction
if tree[end_id] == -1:
print("ERROR: Destination is unreachable")
else:
# Reconstruct route (backwards from destination)
route_points = [graph.vertex(end_id).point()]
current = end_id
while current != start_id:
edge = graph.edge(tree[current])
current = edge.fromVertex()
route_points.insert(0, graph.vertex(current).point())
# Create route geometry
route_geom = QgsGeometry.fromPolyline(
[QgsPoint(p.x(), p.y()) for p in route_points]
)
# Create output layer
route_layer = QgsVectorLayer(
f"LineString?crs={roads.crs().authid()}", "Route", "memory"
)
prov = route_layer.dataProvider()
prov.addAttributes([QgsField("cost", QVariant.Double)])
route_layer.updateFields()
feat = QgsFeature()
feat.setGeometry(route_geom)
feat.setAttributes([cost[end_id]])
prov.addFeature(feat)
route_layer.updateExtents()
QgsProject.instance().addMapLayer(route_layer)
print(f"Route found: cost = {cost[end_id]:.1f}")---
Example 2: One-Way Streets with Speed-Based Routing
from qgis.analysis import (
QgsGraphBuilder, QgsVectorLayerDirector,
QgsNetworkSpeedStrategy, QgsGraphAnalyzer
)
from qgis.core import QgsProject, QgsPointXY
roads = QgsProject.instance().mapLayersByName("roads")[0]
# Direction from 'oneway' field
oneway_idx = roads.fields().indexOf('oneway')
director = QgsVectorLayerDirector(
roads,
oneway_idx,
'yes', # Forward only
'reverse', # Reverse only
'no', # Both directions
QgsVectorLayerDirector.DirectionBoth # Default for unmatched
)
# Cost = travel time (speed in km/h)
speed_idx = roads.fields().indexOf('maxspeed')
director.addStrategy(QgsNetworkSpeedStrategy(
speed_idx,
50.0, # Default 50 km/h when field is NULL
1000.0 / 3600.0 # km/h to m/s
))
# Build and solve
start = QgsPointXY(5.1214, 52.0907)
end = QgsPointXY(4.8952, 52.3702)
builder = QgsGraphBuilder(roads.crs())
tied = director.makeGraph(builder, [start, end])
graph = builder.graph()
sid = graph.findVertex(tied[0])
eid = graph.findVertex(tied[1])
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, sid, 0)
if tree[eid] == -1:
print("No route found (check one-way restrictions)")
else:
print(f"Travel time: {cost[eid]:.1f} seconds")---
Example 3: Service Area with Boundary Interpolation
from qgis.analysis import (
QgsGraphBuilder, QgsVectorLayerDirector,
QgsNetworkDistanceStrategy, QgsGraphAnalyzer
)
from qgis.core import (
QgsProject, QgsPointXY, QgsGeometry, QgsPoint,
QgsVectorLayer, QgsFeature
)
roads = QgsProject.instance().mapLayersByName("roads")[0]
director = QgsVectorLayerDirector(
roads, -1, '', '', '',
QgsVectorLayerDirector.DirectionBoth
)
director.addStrategy(QgsNetworkDistanceStrategy())
origin = QgsPointXY(5.1214, 52.0907)
builder = QgsGraphBuilder(roads.crs())
tied = director.makeGraph(builder, [origin])
graph = builder.graph()
origin_id = graph.findVertex(tied[0])
(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, origin_id, 0)
threshold = 2000.0 # 2 km service area
# Interior: fully reachable vertices
interior = []
for vid in range(graph.vertexCount()):
if cost[vid] <= threshold and tree[vid] != -1:
interior.append(graph.vertex(vid).point())
# Boundary: interpolated points where edges cross the threshold
boundary = []
for vid in range(graph.vertexCount()):
if cost[vid] > threshold and tree[vid] != -1:
edge = graph.edge(tree[vid])
from_v = edge.fromVertex()
if cost[from_v] < threshold:
ratio = (threshold - cost[from_v]) / (cost[vid] - cost[from_v])
p1 = graph.vertex(from_v).point()
p2 = graph.vertex(vid).point()
bp = QgsPointXY(
p1.x() + ratio * (p2.x() - p1.x()),
p1.y() + ratio * (p2.y() - p1.y())
)
boundary.append(bp)
# Create point layer from all reachable points
all_points = interior + boundary
point_layer = QgsVectorLayer(
f"Point?crs={roads.crs().authid()}", "Service Area Points", "memory"
)
prov = point_layer.dataProvider()
for pt in all_points:
feat = QgsFeature()
feat.setGeometry(QgsGeometry.fromPointXY(pt))
prov.addFeature(feat)
point_layer.updateExtents()
QgsProject.instance().addMapLayer(point_layer)
print(f"Service area: {len(interior)} interior + {len(boundary)} boundary points")---
Example 4: Multi-Criteria Analysis (Distance vs Time)
from qgis.analysis import (
QgsGraphBuilder, QgsVectorLayerDirector,
QgsNetworkDistanceStrategy, QgsNetworkSpeedStrategy,
QgsGraphAnalyzer
)
from qgis.core import QgsProject, QgsPointXY
roads = QgsProject.instance().mapLayersByName("roads")[0]
director = QgsVectorLayerDirector(
roads, -1, '', '', '',
QgsVectorLayerDirector.DirectionBoth
)
# Criterion 0: distance
director.addStrategy(QgsNetworkDistanceStrategy())
# Criterion 1: travel time
speed_idx = roads.fields().indexOf('speed_kmh')
director.addStrategy(QgsNetworkSpeedStrategy(speed_idx, 50.0, 1000.0 / 3600.0))
start = QgsPointXY(5.1214, 52.0907)
end = QgsPointXY(4.8952, 52.3702)
builder = QgsGraphBuilder(roads.crs())
tied = director.makeGraph(builder, [start, end])
graph = builder.graph()
sid = graph.findVertex(tied[0])
eid = graph.findVertex(tied[1])
# Shortest by distance
(tree_dist, cost_dist) = QgsGraphAnalyzer.dijkstra(graph, sid, 0)
# Fastest by travel time
(tree_time, cost_time) = QgsGraphAnalyzer.dijkstra(graph, sid, 1)
if tree_dist[eid] != -1:
print(f"Shortest route: {cost_dist[eid]:.1f} meters")
if tree_time[eid] != -1:
print(f"Fastest route: {cost_time[eid]:.1f} seconds")---
Example 5: Processing Algorithm — Shortest Path
import processing
roads = QgsProject.instance().mapLayersByName("roads")[0]
result = processing.run("native:shortestpathpointtopoint", {
'INPUT': roads,
'STRATEGY': 0, # 0=Shortest, 1=Fastest
'DIRECTION_FIELD': '',
'VALUE_FORWARD': '',
'VALUE_BACKWARD': '',
'VALUE_BOTH': '',
'DEFAULT_DIRECTION': 2, # Both
'SPEED_FIELD': '',
'DEFAULT_SPEED': 50.0,
'TOLERANCE': 0.0,
'START_POINT': '5.1214,52.0907 [EPSG:4326]',
'END_POINT': '4.8952,52.3702 [EPSG:4326]',
'OUTPUT': 'TEMPORARY_OUTPUT'
})
route_layer = result['OUTPUT']
QgsProject.instance().addMapLayer(route_layer)---
Example 6: Processing Algorithm — Service Area from Layer
import processing
roads = QgsProject.instance().mapLayersByName("roads")[0]
facilities = QgsProject.instance().mapLayersByName("hospitals")[0]
result = processing.run("native:serviceareafromlayer", {
'INPUT': roads,
'STRATEGY': 0, # Shortest
'DIRECTION_FIELD': 'oneway',
'VALUE_FORWARD': 'F',
'VALUE_BACKWARD': 'T',
'VALUE_BOTH': 'B',
'DEFAULT_DIRECTION': 2,
'SPEED_FIELD': '',
'DEFAULT_SPEED': 50.0,
'TOLERANCE': 0.0,
'START_POINTS': facilities,
'TRAVEL_COST': 5000.0, # 5 km
'OUTPUT': 'TEMPORARY_OUTPUT'
})
service_layer = result['OUTPUT']
QgsProject.instance().addMapLayer(service_layer)---
Example 7: Shortest Path Tree Visualization
from qgis.analysis import (
QgsGraphBuilder, QgsVectorLayerDirector,
QgsNetworkDistanceStrategy, QgsGraphAnalyzer
)
from qgis.core import (
QgsProject, QgsPointXY, QgsGeometry,
QgsVectorLayer, QgsFeature, QgsField
)
from qgis.PyQt.QtCore import QVariant
roads = QgsProject.instance().mapLayersByName("roads")[0]
director = QgsVectorLayerDirector(
roads, -1, '', '', '',
QgsVectorLayerDirector.DirectionBoth
)
director.addStrategy(QgsNetworkDistanceStrategy())
origin = QgsPointXY(5.1214, 52.0907)
builder = QgsGraphBuilder(roads.crs())
tied = director.makeGraph(builder, [origin])
graph = builder.graph()
origin_id = graph.findVertex(tied[0])
# Get shortest path tree as a graph
tree_graph = QgsGraphAnalyzer.shortestTree(graph, origin_id, 0)
# Create line layer from tree edges
tree_layer = QgsVectorLayer(
f"LineString?crs={roads.crs().authid()}", "Shortest Path Tree", "memory"
)
prov = tree_layer.dataProvider()
prov.addAttributes([QgsField("edge_id", QVariant.Int)])
tree_layer.updateFields()
for eid in range(tree_graph.edgeCount()):
edge = tree_graph.edge(eid)
from_pt = tree_graph.vertex(edge.fromVertex()).point()
to_pt = tree_graph.vertex(edge.toVertex()).point()
line = QgsGeometry.fromPolylineXY([from_pt, to_pt])
feat = QgsFeature()
feat.setGeometry(line)
feat.setAttributes([eid])
prov.addFeature(feat)
tree_layer.updateExtents()
QgsProject.instance().addMapLayer(tree_layer)
print(f"Tree has {tree_graph.edgeCount()} edges")API Signatures Reference (QGIS Network Analysis)
QgsVectorLayerDirector
Determines edge direction rules for graph construction from a vector line layer.
QgsVectorLayerDirector(
source: QgsFeatureSource, # Line layer to build graph from
directionFieldId: int, # Field index for direction (-1 = use default for all)
directDirectionValue: str, # Attribute value meaning "forward direction"
reverseDirectionValue: str, # Attribute value meaning "reverse direction"
bothDirectionValue: str, # Attribute value meaning "both directions"
defaultDirection: int # Default when field value does not match any above
)Direction Constants
| Constant | Value | Meaning |
|---|---|---|
QgsVectorLayerDirector.DirectionForward | 1 | Edge follows digitized direction |
QgsVectorLayerDirector.DirectionBackward | 2 | Edge goes against digitized direction |
QgsVectorLayerDirector.DirectionBoth | 3 | Edge is bidirectional |
Methods
director.addStrategy(strategy: QgsNetworkStrategy) -> None
# Add a cost strategy. First added = criterion 0, second = criterion 1, etc.
director.makeGraph(
builder: QgsGraphBuilder,
additionalPoints: list[QgsPointXY]
) -> list[QgsPointXY]
# Build graph. Returns tied (snapped) points on the network.
# Snapped points correspond 1:1 with input additionalPoints.---
QgsNetworkDistanceStrategy
Cost strategy based on geometric edge length.
QgsNetworkDistanceStrategy()
# No parameters. Cost = edge length in CRS units.---
QgsNetworkSpeedStrategy
Cost strategy based on travel time derived from a speed attribute.
QgsNetworkSpeedStrategy(
fieldId: int, # Field index containing speed values
defaultValue: float, # Default speed when field value is NULL or invalid
toMetricFactor: float # Conversion factor to metric units (e.g., 1000.0/3600.0 for km/h to m/s)
)Conversion factors:
| Speed Unit | toMetricFactor | Explanation |
|---|---|---|
| km/h | 1000.0 / 3600.0 | Converts km/h to m/s |
| mph | 1609.344 / 3600.0 | Converts mph to m/s |
| m/s | 1.0 | Already in metric units |
---
QgsGraphBuilder
Constructs a QgsGraph from director-provided edges.
QgsGraphBuilder(
crs: QgsCoordinateReferenceSystem, # CRS for the graph
otfEnabled: bool = True, # On-the-fly reprojection
topologyTolerance: float = 0.0, # Snapping tolerance
ellipsoidID: str = "WGS84" # Ellipsoid for distance calculations
)Methods
builder.graph() -> QgsGraph
# Returns the built graph. Call AFTER director.makeGraph().---
QgsGraph
In-memory graph structure with vertices and edges.
Methods
graph.vertexCount() -> int
# Number of vertices in the graph.
graph.edgeCount() -> int
# Number of edges in the graph.
graph.findVertex(point: QgsPointXY) -> int
# Find vertex ID closest to point. Returns -1 if not found.
graph.vertex(id: int) -> QgsGraphVertex
# Get vertex by ID.
graph.edge(id: int) -> QgsGraphEdge
# Get edge by ID.---
QgsGraphVertex
A vertex in the graph.
Methods
vertex.point() -> QgsPointXY
# Coordinates of this vertex.
vertex.incomingEdges() -> list[int]
# List of edge IDs arriving at this vertex.
vertex.outgoingEdges() -> list[int]
# List of edge IDs leaving this vertex.---
QgsGraphEdge
An edge in the graph connecting two vertices.
Methods
edge.fromVertex() -> int
# ID of the source vertex.
edge.toVertex() -> int
# ID of the destination vertex.
edge.cost(strategyIndex: int) -> float
# Cost of traversing this edge for the given strategy criterion.---
QgsGraphAnalyzer
Static methods for graph analysis algorithms.
dijkstra()
QgsGraphAnalyzer.dijkstra(
graph: QgsGraph,
startVertexIdx: int,
criterionNum: int # Strategy index (0, 1, ...)
) -> tuple[list[int], list[float]]
# Returns (tree, cost):
# tree[i] = edge ID of incoming edge on shortest path to vertex i
# (-1 means vertex i is unreachable or is the start vertex)
# cost[i] = total cost from start to vertex i
# (inf or max float for unreachable vertices)shortestTree()
QgsGraphAnalyzer.shortestTree(
graph: QgsGraph,
startVertexIdx: int,
criterionNum: int
) -> QgsGraph
# Returns a NEW QgsGraph containing only the shortest path tree edges.
# The returned graph has the same vertex positions but only tree edges.---
Processing Algorithm Parameters
native:shortestpathpointtopoint
| Parameter | Type | Description |
|---|---|---|
INPUT | vector line layer | Road network |
STRATEGY | enum | 0=Shortest, 1=Fastest |
DIRECTION_FIELD | field name | Direction attribute (empty = all bidirectional) |
VALUE_FORWARD | string | Forward direction value |
VALUE_BACKWARD | string | Backward direction value |
VALUE_BOTH | string | Both directions value |
DEFAULT_DIRECTION | enum | 0=Forward, 1=Backward, 2=Both |
SPEED_FIELD | field name | Speed attribute (for Fastest strategy) |
DEFAULT_SPEED | float | Default speed in km/h |
TOLERANCE | float | Topology tolerance |
START_POINT | point | Origin as "x,y [CRS]" |
END_POINT | point | Destination as "x,y [CRS]" |
OUTPUT | sink | Output line layer |
native:serviceareafrompoint
| Parameter | Type | Description |
|---|---|---|
INPUT | vector line layer | Road network |
STRATEGY | enum | 0=Shortest, 1=Fastest |
DIRECTION_FIELD | field name | Direction attribute |
VALUE_FORWARD | string | Forward direction value |
VALUE_BACKWARD | string | Backward direction value |
VALUE_BOTH | string | Both directions value |
DEFAULT_DIRECTION | enum | 0=Forward, 1=Backward, 2=Both |
SPEED_FIELD | field name | Speed attribute |
DEFAULT_SPEED | float | Default speed in km/h |
TOLERANCE | float | Topology tolerance |
START_POINT | point | Origin as "x,y [CRS]" |
TRAVEL_COST | float | Maximum cost (distance in meters or time in seconds) |
OUTPUT | sink | Output line layer (reachable network segments) |
native:serviceareafromlayer
Same as native:serviceareafrompoint but replaces START_POINT with:
| Parameter | Type | Description |
|---|---|---|
START_POINTS | vector point layer | Multiple origin points |