fix: removing road nodes without connections

This commit is contained in:
majidr 2024-06-10 22:46:36 -04:00
parent e1f0add43e
commit 166d8f2aba
4 changed files with 1467 additions and 52955 deletions

View File

@ -2,10 +2,12 @@ import json
import matplotlib.pyplot as plt
from shapely.geometry import Polygon, Point, LineString
import networkx as nx
from typing import List, Tuple
from rtree import index
class DistrictHeatingNetworkCreator:
def __init__(self, buildings_file, roads_file):
def __init__(self, buildings_file: str, roads_file: str):
"""
Initialize the class with paths to the buildings and roads data files.
@ -15,7 +17,7 @@ class DistrictHeatingNetworkCreator:
self.buildings_file = buildings_file
self.roads_file = roads_file
def run(self):
def run(self) -> nx.Graph:
"""
Main method to execute the district heating network creation process.
:return: NetworkX graph with nodes and edges representing the network.
@ -27,6 +29,7 @@ class DistrictHeatingNetworkCreator:
self._create_graph()
self._create_mst()
self._iteratively_remove_edges()
self.add_centroids_to_mst() # Add centroids to the MST
return self.final_mst
def _load_and_process_data(self):
@ -37,16 +40,15 @@ class DistrictHeatingNetworkCreator:
with open(self.buildings_file, 'r') as file:
city = json.load(file)
# Extract centroids and building IDs from building data
self.centroids = []
self.building_ids = [] # List to store building IDs
self.building_ids = []
buildings = city['features']
for building in buildings:
coordinates = building['geometry']['coordinates'][0]
building_polygon = Polygon(coordinates)
centroid = building_polygon.centroid
self.centroids.append(centroid)
self.building_ids.append(building['id']) # Extract building ID
self.building_ids.append(building['id'])
# Load road data
with open(self.roads_file, 'r') as file:
@ -54,18 +56,8 @@ class DistrictHeatingNetworkCreator:
line_features = [feature for feature in roads['features'] if feature['geometry']['type'] == 'LineString']
# Create a list of LineString objects and their properties
self.lines = []
for feature in line_features:
# Create a LineString from coordinates
linestring = LineString(feature['geometry']['coordinates'])
self.lines.append(linestring)
self.cleaned_lines = []
for line in self.lines:
coords = list(line.coords)
cleaned_line = LineString([coords[0], coords[-1]])
self.cleaned_lines.append(cleaned_line)
self.lines = [LineString(feature['geometry']['coordinates']) for feature in line_features]
self.cleaned_lines = [LineString([line.coords[0], line.coords[-1]]) for line in self.lines]
def _find_nearest_roads(self):
"""
@ -74,20 +66,21 @@ class DistrictHeatingNetworkCreator:
self.closest_roads = []
unique_roads_set = set()
# Loop through each centroid
for centroid in self.centroids:
min_distance = float('inf') # Start with a large number to ensure any real distance is smaller
closest_road = None
# Create spatial index for roads
idx = index.Index()
for pos, line in enumerate(self.cleaned_lines):
idx.insert(pos, line.bounds)
# Loop through each road and calculate the distance to the current centroid
for line in self.cleaned_lines:
distance = line.distance(centroid)
# Check if the current road is closer than the ones previously checked
for centroid in self.centroids:
min_distance = float('inf')
closest_road = None
for pos in idx.nearest(centroid.bounds, 10):
road = self.cleaned_lines[pos]
distance = road.distance(centroid)
if distance < min_distance:
min_distance = distance
closest_road = line
closest_road = road
# Add the closest road to the list if it's not already added
if closest_road and closest_road.wkt not in unique_roads_set:
unique_roads_set.add(closest_road.wkt)
self.closest_roads.append(closest_road)
@ -96,14 +89,11 @@ class DistrictHeatingNetworkCreator:
"""
Find the nearest point on each closest road for each centroid.
"""
def find_nearest_point_on_line(point, line):
def find_nearest_point_on_line(point: Point, line: LineString) -> Point:
return line.interpolate(line.project(point))
self.nearest_points = []
# Find the nearest point on each closest road for each centroid
for centroid in self.centroids:
# Find the closest road for this centroid
min_distance = float('inf')
closest_road = None
for road in self.closest_roads:
@ -112,7 +102,6 @@ class DistrictHeatingNetworkCreator:
min_distance = distance
closest_road = road
# Find the nearest point on the closest road
if closest_road:
nearest_point = find_nearest_point_on_line(centroid, closest_road)
self.nearest_points.append(nearest_point)
@ -121,27 +110,19 @@ class DistrictHeatingNetworkCreator:
"""
Break down roads into segments connecting nearest points.
"""
def break_down_roads(closest_roads, nearest_points_list):
def break_down_roads(closest_roads: List[LineString], nearest_points_list: List[Point]) -> List[LineString]:
new_segments = []
for road in closest_roads:
# Get coordinates of the road
coords = list(road.coords)
# Find all nearest points for this road
points_on_road = [point for point in nearest_points_list if road.distance(point) < 0.000000001]
# Sort nearest points along the road
sorted_points = sorted(points_on_road, key=lambda point: road.project(point))
# Add the start node to the sorted points
sorted_points.insert(0, Point(coords[0]))
# Add the end node to the sorted points
sorted_points.append(Point(coords[-1]))
# Create new segments
for i in range(len(sorted_points) - 1):
segment = LineString([sorted_points[i], sorted_points[i + 1]])
new_segments.append(segment)
return new_segments
# Create new segments
self.new_segments = break_down_roads(self.closest_roads, self.nearest_points)
self.cleaned_lines = [line for line in self.cleaned_lines if line not in self.closest_roads]
self.cleaned_lines.extend(self.new_segments)
@ -151,8 +132,6 @@ class DistrictHeatingNetworkCreator:
Create a NetworkX graph from the cleaned lines.
"""
self.G = nx.Graph()
# Add edges to the graph from the cleaned lines
for line in self.cleaned_lines:
coords = list(line.coords)
for i in range(len(coords) - 1):
@ -162,8 +141,7 @@ class DistrictHeatingNetworkCreator:
"""
Create a Minimum Spanning Tree (MST) from the graph.
"""
def find_paths_between_nearest_points(g, nearest_points):
def find_paths_between_nearest_points(g: nx.Graph, nearest_points: List[Point]) -> List[Tuple]:
edges = []
for i, start_point in enumerate(nearest_points):
start = (start_point.x, start_point.y)
@ -175,51 +153,89 @@ class DistrictHeatingNetworkCreator:
edges.extend((u, v, g[u][v]['weight']) for u, v in path_edges)
return edges
# Find the edges used to connect the nearest points
edges = find_paths_between_nearest_points(self.G, self.nearest_points)
# Create a graph from these edges
h = nx.Graph()
h.add_weighted_edges_from(edges)
# Compute the Minimum Spanning Tree (MST) using Kruskal's algorithm
mst = nx.minimum_spanning_tree(h, weight='weight')
# Perform pathfinding again on the MST to ensure shortest paths within the MST
final_edges = []
for u, v in mst.edges():
if nx.has_path(self.G, u, v):
path = nx.shortest_path(self.G, source=u, target=v, weight='weight')
path_edges = list(zip(path[:-1], path[1:]))
final_edges.extend((x, y, self.G[x][y]['weight']) for x, y in path_edges)
# Create the final MST graph with these edges
self.final_mst = nx.Graph()
self.final_mst.add_weighted_edges_from(final_edges)
def _iteratively_remove_edges(self):
"""
Iteratively remove edges that do not have any nearest points and have one end with only one connection.
Also remove nodes that don't have any connections.
Also remove nodes that don't have any connections and street nodes with only one connection.
"""
nearest_points_tuples = [(point.x, point.y) for point in self.nearest_points]
def find_edges_to_remove(graph):
def find_edges_to_remove(graph: nx.Graph) -> List[Tuple]:
edges_to_remove = []
for u, v in graph.edges():
for u, v, d in graph.edges(data=True):
if u not in nearest_points_tuples and v not in nearest_points_tuples:
if graph.degree(u) == 1 or graph.degree(v) == 1:
edges_to_remove.append((u, v))
edges_to_remove.append((u, v, d))
return edges_to_remove
edges_to_remove = find_edges_to_remove(self.final_mst)
def find_nodes_to_remove(graph: nx.Graph) -> List[Tuple]:
nodes_to_remove = []
for node in graph.nodes():
if graph.degree(node) == 0:
nodes_to_remove.append(node)
return nodes_to_remove
while edges_to_remove:
edges_to_remove = find_edges_to_remove(self.final_mst)
self.final_mst_steps = [list(self.final_mst.edges(data=True))]
while edges_to_remove or find_nodes_to_remove(self.final_mst):
self.final_mst.remove_edges_from(edges_to_remove)
# Find and remove nodes with no connections
nodes_to_remove = [node for node in self.final_mst.nodes() if self.final_mst.degree(node) == 0]
nodes_to_remove = find_nodes_to_remove(self.final_mst)
self.final_mst.remove_nodes_from(nodes_to_remove)
edges_to_remove = find_edges_to_remove(self.final_mst)
self.final_mst_steps.append(list(self.final_mst.edges(data=True)))
def find_single_connection_street_nodes(graph: nx.Graph) -> List[Tuple]:
single_connection_street_nodes = []
for node in graph.nodes():
if node not in nearest_points_tuples and graph.degree(node) == 1:
single_connection_street_nodes.append(node)
return single_connection_street_nodes
single_connection_street_nodes = find_single_connection_street_nodes(self.final_mst)
while single_connection_street_nodes:
for node in single_connection_street_nodes:
neighbors = list(self.final_mst.neighbors(node))
self.final_mst.remove_node(node)
for neighbor in neighbors:
if self.final_mst.degree(neighbor) == 0:
self.final_mst.remove_node(neighbor)
single_connection_street_nodes = find_single_connection_street_nodes(self.final_mst)
self.final_mst_steps.append(list(self.final_mst.edges(data=True)))
def add_centroids_to_mst(self):
"""
Add centroids to the final MST graph and connect them to their associated node on the graph.
"""
for centroid in self.centroids:
centroid_tuple = (centroid.x, centroid.y)
self.final_mst.add_node(centroid_tuple, type='centroid')
nearest_point = None
min_distance = float('inf')
for node in self.final_mst.nodes():
if self.final_mst.nodes[node].get('type') != 'centroid':
node_point = Point(node)
distance = centroid.distance(node_point)
if distance < min_distance:
min_distance = distance
nearest_point = node
if nearest_point:
self.final_mst.add_edge(centroid_tuple, nearest_point, weight=min_distance)
def plot_network_graph(self):
"""
@ -227,11 +243,8 @@ class DistrictHeatingNetworkCreator:
"""
plt.figure(figsize=(15, 10))
pos = {node: (node[0], node[1]) for node in self.final_mst.nodes()}
# Draw nodes and edges
nx.draw_networkx_nodes(self.final_mst, pos, node_color='blue', node_size=50)
nx.draw_networkx_edges(self.final_mst, pos, edge_color='gray')
plt.title('District Heating Network Graph')
plt.axis('off')
plt.show()

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -2,15 +2,14 @@ from DistrictHeatingNetworkCreator import DistrictHeatingNetworkCreator
from Scripts.road_processor import road_processor
from pathlib import Path
import time
location = [45.51663850312751, -73.59854314961274]
start_time = time.perf_counter()
roads_file = road_processor(-73.62785596251456, 45.4868575174825, 0.005)
roads_file = road_processor(location[1], location[0], 0.001)
buildings_file = Path('./input_files/buildings.geojson').resolve()
dhn_creator = DistrictHeatingNetworkCreator(buildings_file, roads_file)
network_graph = dhn_creator.run()
end_time = time.perf_counter()
elapsed_time = end_time - start_time