
Introduction:
Routing and pathfinding are essential aspects of geographical analysis, and visualizing network routes can offer valuable insights. In this tutorial, we will explore how to use PyVista and OSMnx to create an interactive visualization of road networks and routes. By the end of this guide, you’ll be able to generate dynamic visualizations that showcase the road network, visited nodes, and the shortest path between two random locations.
Prerequisites
Before proceeding, ensure you have Python installed along with the required libraries – pyvista, geopandas, osmnx, networkx and numpy.
|
1 2 3 |
(base) geoknight@pop-os:~$conda create -n spatial-dev.guru python=3.11 (base) geoknight@pop-os:~$conda activate spatial-dev.guru (spatial-dev.guru) geoknight@pop-os:~$conda install -c conda-forge pyvista geopandas osmnx networkx numpy |
To get full source code for this tutorial, click here.
Table of Contents:
- Setting Up the Environment:
- Install the required libraries (
osmnx,pyvista,numpy,random,heapq,functools). - Import the necessary modules.
- Install the required libraries (
- Creating a Network Graph with OSMnx:
- Use OSMnx to fetch road network data for a specific location (e.g., Chandigarh, India).
- Extract nodes and edges from the graph.
- Defining Visualization Functions with PyVista:
- Create a function to convert graph data into PyVista graphics.
- Implement functions for selecting random start and goal nodes.
- Implementing Dijkstra’s Algorithm:
- Define a function for Dijkstra’s algorithm to find the shortest path between two nodes. Networkx provides shortest path algorithms already in the package. The reason we defined our own Dijkstra’s algorithm is to capture the visited nodes and render them using pyvista to check how they explored while finding shortest path.
- Use caching to optimize the algorithm using
functools.
- Visualizing the Results:
- Generate PyVista graphics for the road network, visited nodes, and Dijkstra path.
- Display start and goal nodes as points on the plot.
- Executing the Route Visualization:
- Call the functions in a step-by-step manner to execute the visualization.
- Print the randomly selected start and goal nodes.
- Interpreting the Dijkstra Results:
- Check if a solution is available for the given start and goal nodes.
- Display the Dijkstra path and its associated cost.
- Creating an Interactive Plot:
- Utilize PyVista’s features for interactive 3D visualization.
- Enhance the plot by adding legends and adjusting colors.
- Conclusion:
- Summarize the key steps covered in the tutorial.
- Encourage readers to explore further and customize the visualization for their specific needs.
Conclusion: By following this step-by-step guide, you have learned how to leverage PyVista and OSMnx for network route visualization. Whether you are interested in urban planning, transportation analysis, or geographical exploration, this tutorial equips you with the tools to create visually compelling representations of road networks and optimal routes. Feel free to adapt and extend the provided code to suit your specific use cases and explore additional features offered by PyVista and OSMnx.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# Import necessary libraries import osmnx as ox import pyvista as pv import numpy as np import random import heapq from functools import lru_cache # Create a networkx graph from a place place = "Chandigarh, India" G = ox.graph_from_place(place, network_type="drive") # Convert the graph to a dataframe nodes, edges = ox.graph_to_gdfs(G) # Add an index column to the nodes dataframe nodes['osmid'] = nodes.index # Define a function to convert networkx graph edges to pyvista lines def vistaLines(nodes, edges): # Convert the edges to a pyvista PolyData object with lines pts_list = edges['geometry'].apply(lambda g: np.column_stack( (g.xy[0], g.xy[1], np.zeros(len(g.xy[0]))))).tolist() vertices = np.concatenate(pts_list) lines = [] # Create an empty array with 3 columns j = 0 for i in range(len(pts_list)): pts = pts_list[i] vertex_length = len(pts) vertext_start = j vertex_end = j + vertex_length - 1 vertex_arr = [vertex_length] + \ list(range(vertext_start, vertex_end + 1)) lines.append(vertex_arr) j += vertex_length return pv.PolyData(vertices, lines=np.hstack(lines)) # Define a function to get start and goal nodes def get_start_goal_nodes(): global G nodes = list(G.nodes) # Use two random nodes as start and goal (you can replace them with specific nodes if you want) start = random.choice(nodes) goal = random.choice(nodes) # Ensure start and goal nodes are not the same while start == goal: goal = random.choice(nodes) return start, goal # Define a successors function with caching @lru_cache(maxsize=None) def successors(node): global G # Find the neighbors of the current node neighbors = list(G.neighbors(node)) # Calculate the edge weight and add it as a tuple (neighbor, weight) successors = [(neighbor, G[node][neighbor][0]['length']) for neighbor in neighbors] return successors # Define a great_circle_distance function with caching @lru_cache(maxsize=None) def great_circle_distance(p1, p2): global G coord1 = G.nodes[p1]['y'], G.nodes[p1]['x'] coord2 = G.nodes[p2]['y'], G.nodes[p2]['x'] return ox.distance.great_circle(*coord1, *coord2) # Define a function to perform Dijkstra's algorithm def dijkstra(start, goal): # Initialize variables # Distances from start to each node distances = {node: float('inf') for node in G.nodes} distances[start] = 0 # Distance to start is 0 visited = set() # Set of visited nodes queue = [(0, start)] # Priority queue (distance, node) predecessors = {node: None for node in G.nodes} while queue: current_distance, current_node = heapq.heappop(queue) if current_node == goal: return distances[goal], reconstruct_path(start, goal, predecessors), visited if current_node in visited: continue visited.add(current_node) for neighbor, weight in successors(current_node): new_distance = current_distance + weight if new_distance < distances[neighbor]: distances[neighbor] = new_distance predecessors[neighbor] = current_node heapq.heappush(queue, (new_distance, neighbor)) return float('inf'), [] # No path found # Define a function to reconstruct the path from start to goal def reconstruct_path(start, goal, predecessors): path = [goal] while path[-1] != start: path.append(predecessors[path[-1]]) path.reverse() return path # Get start and goal nodes start, goal = get_start_goal_nodes() # Print the start and goal nodes print(f"{start}, {goal}") # Run Dijkstra's algorithm dijkstra_cost, dijkstra_path, visited_nodes = dijkstra(start, goal) # Print the results of Dijkstra's algorithm print("Dijkstra Result:") if dijkstra_cost != float('inf'): print(f"Path: {dijkstra_path}") print(f"Cost: {dijkstra_cost}\n") else: print("Dijkstra: No solution available for given start and goal\n") # Convert lines and point coordinates to pyvista graphics road_network = vistaLines(*ox.graph_to_gdfs(G)) visited_nodes = vistaLines(*ox.graph_to_gdfs(G.subgraph(visited_nodes))) route_path = vistaLines( *ox.graph_to_gdfs(G.subgraph(dijkstra_path))) start_coords = pv.PolyData( list(list(nodes[nodes['osmid'] == start]['geometry'][start].coords)[0]) + [0]) goal_coords = pv.PolyData( list(list(nodes[nodes['osmid'] == goal]['geometry'][goal].coords)[0]) + [0]) # Plot the network, visited nodes and path plotter = pv.Plotter() plotter.add_mesh(road_network, line_width=1, color='blue', label='Road Network') plotter.add_mesh(visited_nodes, line_width=2, color='green', label='Visited Nodes') plotter.add_mesh(route_path, line_width=3, color='red', label='Dijkstra Path') plotter.add_mesh(start_coords, point_size=20, color='black', label='Start') plotter.add_mesh(goal_coords, point_size=20, color='purple', label='Goal') plotter.add_legend(bcolor='w', face=None) plotter.show(cpos='xy') |
I hope this tutorial will create a good foundation for you. If you want tutorials on another GIS topic or you have any queries, please send an email at contact@spatial-dev.guru.
