dhn_generator - class, example files and an example main file

This commit is contained in:
majidr 2024-01-24 17:39:05 -05:00
commit cfb51929e6
10 changed files with 792 additions and 0 deletions

0
.gitignore vendored Normal file
View File

View File

@ -0,0 +1,186 @@
import json
import geopandas as gpd
import matplotlib.pyplot as plt
from shapely.geometry import Polygon, Point, LineString, MultiPoint
import networkx as nx
class DistrictHeatingNetworkCreator:
def __init__(self, buildings_file, roads_file):
"""
Initialize the class with paths to the buildings and roads data files.
:param buildings_file: Path to the GeoJSON file containing building data.
:param roads_file: Path to the Shapefile containing road data.
"""
self.buildings_file = buildings_file
self.roads_file = roads_file
def run(self):
"""
Main method to execute the district heating network creation process.
:return: NetworkX graph with nodes and edges representing the network.
"""
self._load_and_process_data()
self._find_nearest_roads()
self._process_intersections()
network_graph = self._create_network_graph()
return network_graph
def _load_and_process_data(self):
"""
Load and process the building and road data.
"""
# Load building data
with open(self.buildings_file, 'r') as file:
city = json.load(file)
# Extract centroids from building data
centroids = []
buildings = city['features']
for building in buildings:
coordinates = building['geometry']['coordinates'][0]
building_polygon = Polygon(coordinates)
centroid = building_polygon.centroid
centroids.append(centroid)
# Convert centroids to a GeoDataFrame
self.centroids_gdf = gpd.GeoDataFrame(geometry=[Point(centroid.x, centroid.y) for centroid in centroids],
crs='EPSG:4326')
# Load road data
self.gdf_road = gpd.read_file(self.roads_file)
# Ensure centroids are in the same CRS as roads
self.centroids_gdf = self.centroids_gdf.to_crs(self.gdf_road.crs)
def _find_nearest_roads(self):
"""
Find the nearest road for each building centroid.
"""
# Process road geometries
self.gdf_clean = gpd.GeoDataFrame(
{'geometry': [LineString([coord for coord in line.coords]) for line in self.gdf_road.geometry]})
# Find the nearest road line and point for each centroid
self.closest_linestrings = []
self.nearest_points = []
for centroid in self.centroids_gdf.geometry:
closest_road = min(self.gdf_clean.geometry, key=lambda x: x.distance(centroid))
self.closest_linestrings.append(closest_road)
nearest_point = closest_road.interpolate(closest_road.project(centroid))
self.nearest_points.append(nearest_point)
def _process_intersections(self):
"""
Process intersections and create final geometries.
"""
# Create additional GeoDataFrames for points and nearest points
self.gdf_pts = gpd.GeoDataFrame(
{'geometry': [Point(coord) for line in self.gdf_clean.geometry for coord in line.coords]})
self.gdf_pts2 = gpd.GeoDataFrame({'geometry': self.nearest_points})
# Combine nearest points and road points into one GeoDataFrame
self.gdf_pts3 = gpd.GeoDataFrame({'geometry': self.nearest_points + list(self.gdf_pts.geometry)})
# Identify intersections and create LineStrings based on intersections
self.gdf_clean["intersect"] = [
[y for y in range(len(self.gdf_pts2)) if self.gdf_pts2.geometry[y].distance(geom) <= 1.0] for geom in
self.gdf_clean.geometry]
self.gdf_cleaner = self.gdf_clean[self.gdf_clean["intersect"].apply(len).gt(0)].reset_index(drop=True)
self.test_list = []
for idx, row in self.gdf_cleaner.iterrows():
for i in range(len(row["intersect"]) + 1):
if i == 0:
self.test_list.append(
LineString([row['geometry'].coords[0], self.gdf_pts3.geometry[row['intersect'][i]]]))
elif i < len(row['intersect']):
self.test_list.append(LineString(
[self.gdf_pts3.geometry[row['intersect'][i - 1]], self.gdf_pts3.geometry[row['intersect'][i]]]))
else:
self.test_list.append(
LineString([self.gdf_pts3.geometry[row['intersect'][i - 1]], row['geometry'].coords[-1]]))
self.gdf_cleanest = gpd.GeoDataFrame({'geometry': self.test_list})
points = [coord for geom in self.gdf_cleanest.geometry for coord in geom.coords]
gdf_pts_cnt = self.gdf_pts.copy()
gdf_pts_cnt["count"] = gdf_pts_cnt.geometry.apply(lambda x: points.count(x.coords[0]))
gdf_pts_reset = gdf_pts_cnt[gdf_pts_cnt["count"] > 1].reset_index(drop=True)
gdf_pts_drop = gdf_pts_cnt[gdf_pts_cnt["count"] <= 1].reset_index(drop=True)
# Remove unnecessary geometries from gdf_cleanest
for idx, geom in self.gdf_cleanest.iterrows():
for coord in geom.geometry.coords:
if coord in [pt.coords[0] for pt in gdf_pts_drop.geometry]:
self.gdf_cleanest = self.gdf_cleanest.drop(idx)
self.gdf_cleanest.reset_index(drop=True, inplace=True)
def _create_network_graph(self):
"""
Create a NetworkX graph from the processed geospatial data.
:return: A NetworkX graph representing the district heating network.
"""
G = nx.Graph()
# Add centroids and nearest points as nodes
for centroid in self.centroids_gdf.geometry:
G.add_node((centroid.x, centroid.y), type='centroid')
for point in self.nearest_points:
G.add_node((point.x, point.y), type='nearest_point')
# Add edges with lengths as weights for the road network
for line in self.gdf_cleanest.geometry:
length = line.length
if isinstance(line.boundary, MultiPoint):
# Handle MultiPoint boundary
points = list(line.boundary.geoms)
for i in range(len(points) - 1):
start_point = points[i]
end_point = points[i + 1]
G.add_edge((start_point.x, start_point.y), (end_point.x, end_point.y), weight=length)
else:
# Handle typical case with two endpoints
start_point, end_point = line.boundary
G.add_edge((start_point.x, start_point.y), (end_point.x, end_point.y), weight=length)
# Add edges connecting nearest points to their centroids
for point, centroid in zip(self.nearest_points, self.centroids_gdf.geometry):
distance = point.distance(centroid)
G.add_edge((point.x, point.y), (centroid.x, centroid.y), weight=distance)
return G
def plot_network_graph(self, network_graph):
"""
Plot the network graph using matplotlib and networkx.
:param network_graph: The NetworkX graph to be plotted.
"""
plt.figure(figsize=(12, 12))
pos = {node: (node[0], node[1]) for node in network_graph.nodes()}
# Draw nodes with type 'centroid'
centroids = [node for node, attr in network_graph.nodes(data=True) if attr.get('type') == 'centroid']
nx.draw_networkx_nodes(network_graph, pos, nodelist=centroids, node_color='blue', node_size=50,
label='Centroids')
# Draw nodes with type 'nearest_point'
nearest_points = [node for node, attr in network_graph.nodes(data=True) if attr.get('type') == 'nearest_point']
nx.draw_networkx_nodes(network_graph, pos, nodelist=nearest_points, node_color='green', node_size=30,
label='Nearest Points')
# Draw other nodes
other_nodes = [node for node, attr in network_graph.nodes(data=True) if 'type' not in attr]
nx.draw_networkx_nodes(network_graph, pos, nodelist=other_nodes, node_color='red', node_size=20,
label='Other Nodes')
# Draw edges
nx.draw_networkx_edges(network_graph, pos, edge_color='gray')
plt.legend()
plt.title('District Heating Network Graph')
plt.axis('off')
plt.show()

76
README.md Normal file
View File

@ -0,0 +1,76 @@
# District Heating Network Creator
## Introduction
The `DistrictHeatingNetworkCreator` class is designed to process geospatial data for district heating networks. It takes building and road data as input, identifies the nearest road to each building centroid, processes intersections, and creates a network graph representing the district heating system.
## Installation
Before using `DistrictHeatingNetworkCreator`, ensure that the required libraries are installed. You can install them using pip:
```bash
pip install geopandas networkx matplotlib shapely
```
## Usage
To use the `DistrictHeatingNetworkCreator`, follow these steps:
1. Initialize the class with paths to your GeoJSON file containing building data and Shapefile containing road data.
Example files:
- `buildings.geojson`: A GeoJSON file containing building data.
- `montreal_roads.shp`: A Shapefile containing road data for Montreal.
2. Call the `run` method to process the data and create the network graph.
3. Optionally, call the `plot_network_graph` method to visualize the network.
Example:
```python
from DistrictHeatingNetworkCreator import DistrictHeatingNetworkCreator
# Initialize the class
network_creator = DistrictHeatingNetworkCreator('path/to/buildings.geojson', 'path/to/montreal_roads.shp')
# Create the network graph
network_graph = network_creator.run()
# Plot the network graph (optional)
network_creator.plot_network_graph(network_graph)
```
## Class Methods
### `__init__(self, buildings_file, roads_file)`
Initializes the class with paths to the building and road data files.
- `buildings_file`: Path to the GeoJSON file containing building data.
- `roads_file`: Path to the Shapefile containing road data.
### `run(self)`
Executes the district heating network creation process. Returns a NetworkX graph representing the network.
### `_load_and_process_data(self)`
Loads and processes the building and road data from the specified files.
### `_find_nearest_roads(self)`
Identifies the nearest road for each building centroid and calculates the nearest point on these roads.
### `_process_intersections(self)`
Processes intersections and creates final geometries for the network graph.
### `_create_network_graph(self)`
Creates a NetworkX graph from the processed geospatial data, representing the district heating network.
### `plot_network_graph(self, network_graph)`
Plots the network graph using matplotlib and networkx.
- `network_graph`: The NetworkX graph to be plotted.

View File

@ -0,0 +1,515 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56769087843276,
45.49251875903776
],
[
-73.56765050367694,
45.492560280202284
],
[
-73.5677794213865,
45.49262188364245
],
[
-73.56781916241786,
45.49258006136105
],
[
-73.56769087843276,
45.49251875903776
]
]
]
},
"id": 173347,
"properties": {
"name": "01044617",
"address": "rue Victor-Hugo (MTL) 1666",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56765050367694,
45.492560280202284
],
[
-73.56761436875776,
45.49259744179384
],
[
-73.5676075694645,
45.49260454199484
],
[
-73.56773226889548,
45.49266394156485
],
[
-73.56773726906921,
45.49266624130272
],
[
-73.5677794213865,
45.49262188364245
],
[
-73.56765050367694,
45.492560280202284
]
]
]
},
"id": 173348,
"properties": {
"name": "01044619",
"address": "rue Victor-Hugo (MTL) 1670",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56799466831802,
45.493147041917155
],
[
-73.56810846857894,
45.49303024265641
],
[
-73.56813466880521,
45.493043042620485
],
[
-73.56815586915589,
45.49302094238279
],
[
-73.56817616900103,
45.493030842058744
],
[
-73.56831686889599,
45.492887841945894
],
[
-73.56832066931348,
45.4928840423278
],
[
-73.56829786920603,
45.49287284209684
],
[
-73.56831856934527,
45.4928519416205
],
[
-73.56828776941403,
45.49283684174529
],
[
-73.56840066984188,
45.49272284214226
],
[
-73.56806456957591,
45.4925582417287
],
[
-73.56805966895236,
45.492555742180244
],
[
-73.56805276992448,
45.49256284156738
],
[
-73.56800896923197,
45.49254174199677
],
[
-73.56786836833588,
45.49268614183882
],
[
-73.56813876862645,
45.49281644215918
],
[
-73.56801926843859,
45.49293914153178
],
[
-73.56775236910005,
45.49281054226104
],
[
-73.56760666818799,
45.49296014230256
],
[
-73.56799466831802,
45.493147041917155
]
]
]
},
"id": 175012,
"properties": {
"name": "01041483",
"address": "rue Saint-Jacques (MTL) 1390",
"function": "1000",
"height": 15,
"year_of_construction": 1985
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56772876855176,
45.49247194194522
],
[
-73.56773406949068,
45.492474341387755
],
[
-73.56773125185198,
45.492477239659124
],
[
-73.56785890467093,
45.492538239964624
],
[
-73.56789966910456,
45.49249534173201
],
[
-73.56776616865103,
45.49243264153464
],
[
-73.56772876855176,
45.49247194194522
]
]
]
},
"id": 176261,
"properties": {
"name": "01044613",
"address": "rue Victor-Hugo (MTL) 1656",
"function": "1000",
"height": 10,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56790222258577,
45.49229712328457
],
[
-73.56785996900595,
45.49234104192853
],
[
-73.56799446861396,
45.49240484193282
],
[
-73.56803643080562,
45.49236123475947
],
[
-73.56790222258577,
45.49229712328457
]
]
]
},
"id": 176296,
"properties": {
"name": "01044611",
"address": "rue Victor-Hugo (MTL) 1650",
"function": "1000",
"height": 10,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56737696960933,
45.492846041826596
],
[
-73.56737386954057,
45.49284924170359
],
[
-73.56749716846514,
45.49290664199878
],
[
-73.56750256819052,
45.49290924205888
],
[
-73.5675461362352,
45.49286408008855
],
[
-73.56741768676183,
45.492802699272865
],
[
-73.56737696960933,
45.492846041826596
]
]
]
},
"id": 177698,
"properties": {
"name": "01044629",
"address": "rue Victor-Hugo (MTL) 1696",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56773125185198,
45.492477239659124
],
[
-73.56769087843276,
45.49251875903776
],
[
-73.56781916241786,
45.49258006136105
],
[
-73.56785890467093,
45.492538239964624
],
[
-73.56773125185198,
45.492477239659124
]
]
]
},
"id": 178164,
"properties": {
"name": "01044615",
"address": "rue Victor-Hugo (MTL) 1660",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56749654484017,
45.49271875567071
],
[
-73.5674571158308,
45.49276072792864
],
[
-73.56758629109996,
45.49282245561109
],
[
-73.5676264459045,
45.49278083021942
],
[
-73.56749654484017,
45.49271875567071
]
]
]
},
"id": 181359,
"properties": {
"name": "01044625",
"address": "rue Victor-Hugo (MTL) 1686",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56757856905631,
45.49263144202049
],
[
-73.56753597507041,
45.4926767833981
],
[
-73.56766660064991,
45.492739204813354
],
[
-73.56771016843139,
45.49269404187941
],
[
-73.56758306872914,
45.492633542295685
],
[
-73.56757856905631,
45.49263144202049
]
]
]
},
"id": 181706,
"properties": {
"name": "01044621",
"address": "rue Victor-Hugo (MTL) 1676",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.56753597507041,
45.4926767833981
],
[
-73.56749654484017,
45.49271875567071
],
[
-73.5676264459045,
45.49278083021942
],
[
-73.56766660064991,
45.492739204813354
],
[
-73.56753597507041,
45.4926767833981
]
]
]
},
"id": 183144,
"properties": {
"name": "01044623",
"address": "rue Victor-Hugo (MTL) 1680",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-73.5674571158308,
45.49276072792864
],
[
-73.56741768676183,
45.492802699272865
],
[
-73.5675461362352,
45.49286408008855
],
[
-73.56758629109996,
45.49282245561109
],
[
-73.5674571158308,
45.49276072792864
]
]
]
},
"id": 183243,
"properties": {
"name": "01044627",
"address": "rue Victor-Hugo (MTL) 1690",
"function": "1000",
"height": 9,
"year_of_construction": 1986
}
}
]
}

View File

@ -0,0 +1 @@
UTF-8

Binary file not shown.

View File

@ -0,0 +1 @@
PROJCS["NAD_1983_MTM_8",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",304800.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-73.5],PARAMETER["Scale_Factor",0.9999],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]

Binary file not shown.

Binary file not shown.

13
main.py Normal file
View File

@ -0,0 +1,13 @@
from DistrictHeatingNetworkCreator import DistrictHeatingNetworkCreator
# Initialize the class
network_creator = DistrictHeatingNetworkCreator(
'./input_files/buildings.geojson',
'./input_files/roads/geobase_mtl.shp'
)
# Create the network graph
network_graph = network_creator.run()
# Plot the network graph (optional)
network_creator.plot_network_graph(network_graph)