(WIP) feature: add pipe sizing to dhn analysis

This commit is contained in:
Majid Rezaei 2024-08-01 11:39:05 -04:00
parent 2ca52c157a
commit f79638fb72
8 changed files with 946 additions and 513 deletions

View File

@ -25,6 +25,7 @@ from scripts.pv_feasibility import pv_feasibility
import matplotlib.pyplot as plt
from scripts.district_heating_network.district_heating_network_creator import DistrictHeatingNetworkCreator
from scripts.district_heating_network.road_processor import road_processor
from scripts.district_heating_network.district_heating_factory import DistrictHeatingFactory
base_path = Path(__file__).parent
dir_manager = DirectoryManager(base_path)

103
network_edges.csv Normal file
View File

@ -0,0 +1,103 @@
Start Node,End Node,Flow Rate,Diameter
1,2,7.036628334723999,100.31636393633916
1,3,4.21120645112289,77.60554307494739
1,55,3.5317773545013225,71.06997042924019
2,4,7.480662304076292,103.43309043327245
2,69,0.5550424616902951,28.174264035849028
3,29,3.996190684370673,75.5983953887572
3,88,0.2687697084402423,19.605577350674327
4,5,12.739252570195731,134.97752174009295
4,70,6.57323783264924,96.95699548330485
5,6,1.440813373451662,45.3934656880935
5,7,14.180065943647435,142.40610275642487
6,25,0.6885299581772856,31.379852409201234
6,66,0.9403542690929205,36.67205322096126
7,8,18.919213781899757,164.4905748238601
7,94,5.923934797815284,92.04381499868917
8,9,20.2831963688024,170.31687247962364
8,62,1.704978233628294,49.379750978468536
9,10,23.795992013358696,184.47664139533458
9,74,4.390994555695343,79.24482766451129
10,11,24.08473660300742,185.59250196750875
10,85,0.36093073706075773,22.71963814788559
11,12,29.818111567083733,206.5045112717333
11,97,7.166718705095327,101.23942160182479
12,13,30.27814518525654,208.09138919592667
12,87,0.5750420227159534,28.677366552586644
13,14,42.1891802453921,245.63487040066076
13,60,14.888793825169447,145.92148273668778
14,15,42.46951106929258,246.44959372826426
14,84,0.3504135298756097,22.386175758632653
15,16,42.71334684180593,247.15606782002874
15,100,0.30479471564177585,20.878206682814064
16,17,43.164835926660224,248.45888000248186
16,82,0.5643613560678524,28.40979566604211
17,18,43.40956096248419,249.1622090841966
17,67,0.3059062947800095,20.91624319844782
18,19,44.36915698128853,251.90110032375995
18,71,1.1994950235054551,41.417959700534844
19,20,44.628206053508535,252.63539166978182
19,90,0.3238113402749617,21.5196648389043
20,21,46.27014639132988,257.24083624469847
20,75,2.052425422276719,54.178024923945806
21,22,73.0360691675874,323.1901912619741
21,23,26.455032668422902,194.5107583389826
21,24,0.31089010783469323,21.08593812173577
22,49,84.50840788728556,347.6477603374658
22,81,14.340423399622829,143.2090497302142
23,31,25.527872344225216,191.07188392806023
23,68,1.158950405247153,40.71194974740254
24,56,0.3886126347933523,23.574795504777274
25,26,0.48524655942646255,26.34333218820642
25,95,0.2541042484385019,19.063183969533693
26,27,0.2982895780206631,20.654206550796754
26,65,0.23369622675732482,18.28164730458197
27,28,0.17282514505654928,15.721462386442168
27,59,0.15683054120527573,14.976309152909318
28,57,0.2160314313208167,17.577129300900673
29,30,3.8183263880744693,73.89686263587296
29,58,0.22233037037019066,17.831540803086607
30,33,3.699868945604976,72.74156600819312
30,77,0.1480718030867907,14.552099595104522
31,32,20.88880856250903,172.8408171354717
31,61,5.79882972714514,91.06671153446011
32,46,19.8454823967694,168.46911947690657
32,83,1.304157707174466,43.18714870672264
33,34,3.4711065659703353,70.45688678723134
33,102,0.2859529745433158,20.222590387225335
34,35,3.4711065659703735,70.45688678723174
35,36,2.259511486129423,56.84558663220748
35,86,1.5144938498012546,46.539662871966804
36,37,1.7967791166489435,50.691695985759985
36,76,0.5784154618507291,28.76136031308409
37,38,1.3079368277973644,43.24967618306041
37,98,0.6110528610646417,29.56166334911917
38,39,1.161455220287694,40.75592094041735
38,93,0.18310200938718202,16.182142867916955
39,40,1.0509571713728947,38.76876664450215
39,101,0.1381225611435788,14.054706710110416
40,41,0.9248009014795987,36.367512616181635
40,73,0.15769533736659458,15.017543626027152
41,42,0.7870341012651642,33.54951537030909
41,79,0.1722085002680287,15.693390021976926
42,43,0.6096172477277307,29.526916703688457
42,72,0.22177106692165793,17.809097799856982
43,44,0.5017777614037179,26.788301656702675
43,63,0.13479935790496445,13.884600479307904
44,45,0.257385665027338,19.185876901497235
44,80,0.3054901204704009,20.902010464636216
45,64,0.32173208128418995,21.45046247984601
46,47,18.899025568880468,164.4027895397298
46,99,1.1830710348610967,41.133426323360354
47,48,17.204865763811963,156.86105088056533
47,78,2.1176997563356554,55.032807545697466
48,50,15.889475871949896,150.74546712455864
48,89,1.644237364827739,48.492182678681466
49,53,4.137682980635671,76.92510229741136
49,54,88.64609086792123,356.0567884918405
50,52,15.88947587194994,150.74546712455884
51,52,15.107521674531634,146.98942430449688
51,96,18.884402093164404,164.33917235920677
52,91,0.977442746772752,37.38825018026857
53,92,5.172103725794528,86.004878956568
54,103,88.64609086792132,356.0567884918407
1 Start Node End Node Flow Rate Diameter
2 1 2 7.036628334723999 100.31636393633916
3 1 3 4.21120645112289 77.60554307494739
4 1 55 3.5317773545013225 71.06997042924019
5 2 4 7.480662304076292 103.43309043327245
6 2 69 0.5550424616902951 28.174264035849028
7 3 29 3.996190684370673 75.5983953887572
8 3 88 0.2687697084402423 19.605577350674327
9 4 5 12.739252570195731 134.97752174009295
10 4 70 6.57323783264924 96.95699548330485
11 5 6 1.440813373451662 45.3934656880935
12 5 7 14.180065943647435 142.40610275642487
13 6 25 0.6885299581772856 31.379852409201234
14 6 66 0.9403542690929205 36.67205322096126
15 7 8 18.919213781899757 164.4905748238601
16 7 94 5.923934797815284 92.04381499868917
17 8 9 20.2831963688024 170.31687247962364
18 8 62 1.704978233628294 49.379750978468536
19 9 10 23.795992013358696 184.47664139533458
20 9 74 4.390994555695343 79.24482766451129
21 10 11 24.08473660300742 185.59250196750875
22 10 85 0.36093073706075773 22.71963814788559
23 11 12 29.818111567083733 206.5045112717333
24 11 97 7.166718705095327 101.23942160182479
25 12 13 30.27814518525654 208.09138919592667
26 12 87 0.5750420227159534 28.677366552586644
27 13 14 42.1891802453921 245.63487040066076
28 13 60 14.888793825169447 145.92148273668778
29 14 15 42.46951106929258 246.44959372826426
30 14 84 0.3504135298756097 22.386175758632653
31 15 16 42.71334684180593 247.15606782002874
32 15 100 0.30479471564177585 20.878206682814064
33 16 17 43.164835926660224 248.45888000248186
34 16 82 0.5643613560678524 28.40979566604211
35 17 18 43.40956096248419 249.1622090841966
36 17 67 0.3059062947800095 20.91624319844782
37 18 19 44.36915698128853 251.90110032375995
38 18 71 1.1994950235054551 41.417959700534844
39 19 20 44.628206053508535 252.63539166978182
40 19 90 0.3238113402749617 21.5196648389043
41 20 21 46.27014639132988 257.24083624469847
42 20 75 2.052425422276719 54.178024923945806
43 21 22 73.0360691675874 323.1901912619741
44 21 23 26.455032668422902 194.5107583389826
45 21 24 0.31089010783469323 21.08593812173577
46 22 49 84.50840788728556 347.6477603374658
47 22 81 14.340423399622829 143.2090497302142
48 23 31 25.527872344225216 191.07188392806023
49 23 68 1.158950405247153 40.71194974740254
50 24 56 0.3886126347933523 23.574795504777274
51 25 26 0.48524655942646255 26.34333218820642
52 25 95 0.2541042484385019 19.063183969533693
53 26 27 0.2982895780206631 20.654206550796754
54 26 65 0.23369622675732482 18.28164730458197
55 27 28 0.17282514505654928 15.721462386442168
56 27 59 0.15683054120527573 14.976309152909318
57 28 57 0.2160314313208167 17.577129300900673
58 29 30 3.8183263880744693 73.89686263587296
59 29 58 0.22233037037019066 17.831540803086607
60 30 33 3.699868945604976 72.74156600819312
61 30 77 0.1480718030867907 14.552099595104522
62 31 32 20.88880856250903 172.8408171354717
63 31 61 5.79882972714514 91.06671153446011
64 32 46 19.8454823967694 168.46911947690657
65 32 83 1.304157707174466 43.18714870672264
66 33 34 3.4711065659703353 70.45688678723134
67 33 102 0.2859529745433158 20.222590387225335
68 34 35 3.4711065659703735 70.45688678723174
69 35 36 2.259511486129423 56.84558663220748
70 35 86 1.5144938498012546 46.539662871966804
71 36 37 1.7967791166489435 50.691695985759985
72 36 76 0.5784154618507291 28.76136031308409
73 37 38 1.3079368277973644 43.24967618306041
74 37 98 0.6110528610646417 29.56166334911917
75 38 39 1.161455220287694 40.75592094041735
76 38 93 0.18310200938718202 16.182142867916955
77 39 40 1.0509571713728947 38.76876664450215
78 39 101 0.1381225611435788 14.054706710110416
79 40 41 0.9248009014795987 36.367512616181635
80 40 73 0.15769533736659458 15.017543626027152
81 41 42 0.7870341012651642 33.54951537030909
82 41 79 0.1722085002680287 15.693390021976926
83 42 43 0.6096172477277307 29.526916703688457
84 42 72 0.22177106692165793 17.809097799856982
85 43 44 0.5017777614037179 26.788301656702675
86 43 63 0.13479935790496445 13.884600479307904
87 44 45 0.257385665027338 19.185876901497235
88 44 80 0.3054901204704009 20.902010464636216
89 45 64 0.32173208128418995 21.45046247984601
90 46 47 18.899025568880468 164.4027895397298
91 46 99 1.1830710348610967 41.133426323360354
92 47 48 17.204865763811963 156.86105088056533
93 47 78 2.1176997563356554 55.032807545697466
94 48 50 15.889475871949896 150.74546712455864
95 48 89 1.644237364827739 48.492182678681466
96 49 53 4.137682980635671 76.92510229741136
97 49 54 88.64609086792123 356.0567884918405
98 50 52 15.88947587194994 150.74546712455884
99 51 52 15.107521674531634 146.98942430449688
100 51 96 18.884402093164404 164.33917235920677
101 52 91 0.977442746772752 37.38825018026857
102 53 92 5.172103725794528 86.004878956568
103 54 103 88.64609086792132 356.0567884918407

View File

@ -0,0 +1,86 @@
import json
import csv
import logging
class NetworkGraphExporter:
"""
A class to export a network graph to various formats like CSV and GeoJSON.
"""
def __init__(self, graph):
"""
Initialize the exporter with a network graph.
:param graph: A NetworkX graph object.
"""
self.graph = graph
def to_csv(self, file_path):
"""
Save the graph nodes with their type, names, and positions to a CSV file.
:param file_path: The path to the output CSV file.
"""
try:
with open(file_path, mode='w', newline='') as file:
writer = csv.writer(file)
writer.writerow(['Node ID', 'Name', 'Type', 'Position'])
for node, data in self.graph.nodes(data=True):
writer.writerow([node, data['name'], data['type'], data['pos']])
logging.info(f"Graph successfully saved to CSV file: {file_path}")
except Exception as e:
logging.error(f"Error saving graph to CSV file: {e}")
def to_geojson(self, file_path):
"""
Save the graph to a GeoJSON file.
:param file_path: The path to the output GeoJSON file.
"""
try:
features = []
for node, data in self.graph.nodes(data=True):
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": data['pos']
},
"properties": {
"id": node,
"name": data['name'],
"type": data['type']
}
}
features.append(feature)
for u, v, data in self.graph.edges(data=True):
feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [self.graph.nodes[u]['pos'], self.graph.nodes[v]['pos']]
},
"properties": {
"length": data['length']
}
}
features.append(feature)
geojson = {
"type": "FeatureCollection",
"features": features
}
with open(file_path, 'w') as f:
json.dump(geojson, f, indent=2)
logging.info(f"Graph successfully saved to GeoJSON file: {file_path}")
except Exception as e:
logging.error(f"Error saving graph to GeoJSON file: {e}")
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logging.getLogger("numexpr").setLevel(logging.ERROR)

View File

@ -1,20 +1,28 @@
import networkx as nx
import logging
import CoolProp.CoolProp as CP # Ensure correct import
import math
import numpy as np
import matplotlib.pyplot as plt
import csv
class DistrictHeatingFactory:
"""
DistrictHeatingFactory class
"""
def __init__(self, city, graph):
def __init__(self, city, graph, supply_temperature, return_temperature, simultaneity_factor):
self._city = city
self._network_graph = graph
self._supply_temperature = supply_temperature
self._return_temperature = return_temperature
self.simultaneity_factor = simultaneity_factor
self.fluid = "Water"
def enrich(self):
"""
Enrich the network graph nodes with attributes from the city buildings.
Enrich the network graph nodes with the whole building object from the city buildings.
"""
for node in self._network_graph.nodes(data=True):
node_id, node_attrs = node
if node_attrs.get('type') == 'building':
@ -22,11 +30,145 @@ class DistrictHeatingFactory:
building_found = False
for building in self._city.buildings:
if building.name == building_name:
building_attrs = vars(building)
for attr, value in building_attrs.items():
if attr not in self._network_graph.nodes[node_id]:
self._network_graph.nodes[node_id][attr] = value
self._network_graph.nodes[node_id]['building_obj'] = building
building_found = True
break
if not building_found:
logging.error(msg=f"Building with name '{building_name}' not found in city.")
logging.error(msg=f"Building with name '{building_name}' not found in city.")
def calculate_flow_rates(self, A, Gext):
"""
Solve the linear system to find the flow rates in each branch.
"""
try:
G = np.linalg.lstsq(A, Gext, rcond=None)[0]
return G
except np.linalg.LinAlgError as e:
logging.error(f"Error solving the linear system: {e}")
return None
def switch_nodes(self, A, edge_index, node_index, edge):
"""
Switch the in and out nodes for the given edge.
"""
u, v = edge
i = node_index[u]
j = node_index[v]
k = edge_index[edge]
A[i, k], A[j, k] = -A[i, k], -A[j, k]
def sizing(self):
"""
Calculate the diameter of the pipes in the district heating network.
"""
# Calculate the peak flow rates for buildings
total_peak_load = 0
for node in self._network_graph.nodes(data=True):
node_id, node_attrs = node
if node_attrs.get('type') == 'building':
building = node_attrs.get('building_obj')
if building and "year" in building.heating_peak_load:
peak_load = building.heating_peak_load["year"][0]
if peak_load:
try:
specific_heat_capacity = CP.PropsSI('C', 'T', (self._supply_temperature + self._return_temperature) / 2, 'P', 101325, self.fluid)
peak_mass_flow_rate = peak_load / (specific_heat_capacity * (self._supply_temperature - self._return_temperature))
self._network_graph.nodes[node_id]['peak_flow'] = peak_mass_flow_rate
total_peak_load += peak_mass_flow_rate
except Exception as e:
logging.error(f"Error calculating peak mass flow rate for building {building.name}: {e}")
# Assign the total peak load to the central plant node
for node in self._network_graph.nodes(data=True):
node_id, node_attrs = node
if node_attrs.get('type') == 'generation':
self._network_graph.nodes[node_id]['peak_flow'] = -total_peak_load
# Create the incidence matrix A and Gext vector
num_nodes = self._network_graph.number_of_nodes()
num_edges = self._network_graph.number_of_edges()
A = np.zeros((num_nodes, num_edges))
Gext = np.zeros(num_nodes)
node_index = {node: i for i, node in enumerate(self._network_graph.nodes())}
edge_index = {edge: i for i, edge in enumerate(self._network_graph.edges())}
for node, data in self._network_graph.nodes(data=True):
i = node_index[node]
Gext[i] = data.get('peak_flow', 0)
for edge in self._network_graph.edges():
u, v = edge
i = node_index[u]
j = node_index[v]
k = edge_index[edge]
A[i, k] = 1
A[j, k] = -1
# Calculate initial flow rates
G = self.calculate_flow_rates(A, Gext)
if G is None:
return
# Check for negative flow rates and switch nodes if necessary
iterations = 0
max_iterations = num_edges * 2 # Arbitrary limit to avoid infinite loop
while any(flow_rate < 0 for flow_rate in G) and iterations < max_iterations:
for idx, flow_rate in enumerate(G):
if flow_rate < 0:
edge = list(self._network_graph.edges())[idx]
self.switch_nodes(A, edge_index, node_index, edge)
G = self.calculate_flow_rates(A, Gext)
if G is None:
return
iterations += 1
# Update the edge diameters based on the calculated flow rates
for edge, flow_rate in zip(self._network_graph.edges(), G):
u, v = edge
if not (self._network_graph.nodes[u].get('type') == 'building' or self._network_graph.nodes[v].get('type') == 'building'):
flow_rate *= self.simultaneity_factor
try:
density = CP.PropsSI('D', 'T', (self._supply_temperature + self._return_temperature) / 2, 'P', 101325, self.fluid)
velocity = 0.9 # m/s
diameter = math.sqrt((4 * abs(flow_rate)) / (density * velocity * math.pi)) * 1000 # mm
self._network_graph.edges[u, v]['diameter'] = diameter
self._network_graph.edges[u, v]['flow_rate'] = flow_rate
except Exception as e:
logging.error(f"Error calculating diameter for edge ({u}, {v}): {e}")
def plot_network(self):
"""
Plot the network graph with flow rates shown as colors.
"""
pos = {node: (data['pos'][0], data['pos'][1]) for node, data in self._network_graph.nodes(data=True) if 'pos' in data}
edge_colors = [self._network_graph[u][v]['flow_rate'] for u, v in self._network_graph.edges()]
plt.figure(figsize=(18, 12))
nodes = nx.draw_networkx_nodes(self._network_graph, pos, node_color='skyblue', node_size=700)
edges = nx.draw_networkx_edges(self._network_graph, pos, edge_color=edge_colors, edge_cmap=plt.cm.viridis, width=2)
labels = nx.draw_networkx_labels(self._network_graph, pos, font_size=10, font_color='black')
sm = plt.cm.ScalarMappable(cmap=plt.cm.viridis, norm=plt.Normalize(vmin=min(edge_colors), vmax=max(edge_colors)))
sm.set_array([])
cbar = plt.colorbar(sm, label='Flow rate')
cbar.ax.tick_params(labelsize=10)
plt.title('District Heating Network with Flow Rates', fontsize=16)
plt.axis('off')
plt.legend(handles=[nodes], labels=['Nodes'], loc='upper left')
plt.show()
def save_edges_to_csv(self, file_path):
"""
Save the edges and their attributes to a CSV file.
"""
with open(file_path, mode='w', newline='') as file:
writer = csv.writer(file)
writer.writerow(['Start Node', 'End Node', 'Flow Rate', 'Diameter'])
for u, v, data in self._network_graph.edges(data=True):
writer.writerow([u, v, data.get('flow_rate', ''), data.get('diameter', '')])
logging.info(f"Edges successfully saved to CSV file: {file_path}")

View File

@ -8,9 +8,8 @@ from typing import List, Tuple
from rtree import index
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logging.getLogger('numexpr').setLevel(logging.ERROR)
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logging.getLogger("numexpr").setLevel(logging.ERROR)
def haversine(lon1, lat1, lon2, lat2):
"""
@ -30,17 +29,21 @@ def haversine(lon1, lat1, lon2, lat2):
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c # Output distance in meters
class DistrictHeatingNetworkCreator:
def __init__(self, buildings_file: str, roads_file: str):
def __init__(self, buildings_file: str, roads_file: str, central_plant_locations: List[Tuple[float, float]]):
"""
Initialize the class with paths to the buildings and roads data files.
Initialize the class with paths to the buildings and roads data files, and central plant locations.
:param buildings_file: Path to the GeoJSON file containing building data.
:param roads_file: Path to the GeoJSON file containing roads data.
:param central_plant_locations: List of tuples containing the coordinates of central plant locations.
"""
if len(central_plant_locations) < 1:
raise ValueError("The list of central plant locations must have at least one member.")
self.buildings_file = buildings_file
self.roads_file = roads_file
self.central_plant_locations = central_plant_locations
def run(self) -> nx.Graph:
"""
@ -57,7 +60,8 @@ class DistrictHeatingNetworkCreator:
self._iteratively_remove_edges()
self._add_centroids_to_mst()
self._convert_edge_weights_to_meters()
return self.final_mst
self._create_final_network_graph()
return self.network_graph
except Exception as e:
logging.error(f"Error during network creation: {e}")
raise
@ -73,6 +77,7 @@ class DistrictHeatingNetworkCreator:
self.centroids = []
self.building_names = []
self.building_positions = []
buildings = city['features']
for building in buildings:
coordinates = building['geometry']['coordinates'][0]
@ -80,6 +85,14 @@ class DistrictHeatingNetworkCreator:
centroid = building_polygon.centroid
self.centroids.append(centroid)
self.building_names.append(str(building['id']))
self.building_positions.append((centroid.x, centroid.y))
# Add central plant locations as centroids
for i, loc in enumerate(self.central_plant_locations, start=1):
centroid = Point(loc)
self.centroids.append(centroid)
self.building_names.append(f'central_plant_{i}')
self.building_positions.append((centroid.x, centroid.y))
# Load road data
with open(self.roads_file, 'r') as file:
@ -184,7 +197,9 @@ class DistrictHeatingNetworkCreator:
for line in self.cleaned_lines:
coords = list(line.coords)
for i in range(len(coords) - 1):
self.G.add_edge(coords[i], coords[i + 1], weight=Point(coords[i]).distance(Point(coords[i + 1])))
u = coords[i]
v = coords[i + 1]
self.G.add_edge(u, v, weight=Point(coords[i]).distance(Point(coords[i + 1])))
except Exception as e:
logging.error(f"Error creating graph: {e}")
raise
@ -284,24 +299,22 @@ class DistrictHeatingNetworkCreator:
"""
try:
for i, centroid in enumerate(self.centroids):
centroid_tuple = (centroid.x, centroid.y)
building_name = self.building_names[i]
# Add the centroid node with its attributes
self.final_mst.add_node(centroid_tuple, type='building', name=building_name)
pos = (centroid.x, centroid.y)
node_type = 'building' if 'central_plant' not in building_name else 'generation'
self.final_mst.add_node(pos, type=node_type, name=building_name, pos=pos)
nearest_point = None
min_distance = float('inf')
for node in self.final_mst.nodes():
if self.final_mst.nodes[node].get('type') != 'building':
node_point = Point(node)
distance = centroid.distance(node_point)
if self.final_mst.nodes[node].get('type') != 'building' and self.final_mst.nodes[node].get('type') != 'generation':
distance = centroid.distance(Point(node))
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)
self.final_mst.add_edge(pos, nearest_point, weight=min_distance)
except Exception as e:
logging.error(f"Error adding centroids to MST: {e}")
raise
@ -312,22 +325,48 @@ class DistrictHeatingNetworkCreator:
"""
try:
for u, v, data in self.final_mst.edges(data=True):
lon1, lat1 = u
lon2, lat2 = v
distance = haversine(lon1, lat1, lon2, lat2)
distance = haversine(u[0], u[1], v[0], v[1])
self.final_mst[u][v]['weight'] = distance
except Exception as e:
logging.error(f"Error converting edge weights to meters: {e}")
raise
def _create_final_network_graph(self):
"""
Create the final network graph with the required attributes from the final MST.
"""
self.network_graph = nx.Graph()
node_id = 1
node_mapping = {}
for node in self.final_mst.nodes:
pos = node
if 'type' in self.final_mst.nodes[node]:
if self.final_mst.nodes[node]['type'] == 'building':
name = self.final_mst.nodes[node]['name']
node_type = 'building'
elif self.final_mst.nodes[node]['type'] == 'generation':
name = self.final_mst.nodes[node]['name']
node_type = 'generation'
else:
name = f'junction_{node_id}'
node_type = 'junction'
self.network_graph.add_node(node_id, name=name, type=node_type, pos=pos)
node_mapping[node] = node_id
node_id += 1
for u, v, data in self.final_mst.edges(data=True):
u_new = node_mapping[u]
v_new = node_mapping[v]
length = data['weight']
self.network_graph.add_edge(u_new, v_new, length=length)
def plot_network_graph(self):
"""
Plot the network graph using matplotlib and networkx.
"""
plt.figure(figsize=(15, 10))
pos = {node: (node[0], node[1]) for node in self.final_mst.nodes()}
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')
pos = {node: data['pos'] for node, data in self.network_graph.nodes(data=True)}
nx.draw_networkx_nodes(self.network_graph, pos, node_color='blue', node_size=50)
nx.draw_networkx_edges(self.network_graph, pos, edge_color='gray')
plt.title('District Heating Network Graph')
plt.axis('off')
plt.show()

View File

@ -1,54 +0,0 @@
import json
from shapely import LineString, Point
import networkx as nx
from pathlib import Path
def networkx_to_geojson(graph: nx.Graph) -> Path:
"""
Convert a NetworkX graph to GeoJSON format.
:param graph: A NetworkX graph.
:return: GeoJSON formatted dictionary.
"""
features = []
for u, v, data in graph.edges(data=True):
line = LineString([u, v])
feature = {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": list(line.coords)
},
"properties": {
"weight": data.get("weight", 1.0)
}
}
features.append(feature)
for node, data in graph.nodes(data=True):
point = Point(node)
feature = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": list(point.coords)[0]
},
"properties": {
"type": data.get("type", "unknown"),
"id": data.get("id", "N/A")
}
}
features.append(feature)
geojson = {
"type": "FeatureCollection",
"features": features
}
output_geojson_file = Path('./out_files/network_graph.geojson').resolve()
with open(output_geojson_file, 'w') as file:
json.dump(geojson, file, indent=4)
return output_geojson_file

View File

@ -0,0 +1,191 @@
[
{
"DN": 16,
"inner_diameter": 16.1,
"outer_diameter": 21.3,
"thickness": 2.6,
"cost_per_meter": 320
},
{
"DN": 20,
"inner_diameter": 21.7,
"outer_diameter": 26.9,
"thickness": 2.6,
"cost_per_meter": 320
},
{
"DN": 25,
"inner_diameter": 27.3,
"outer_diameter": 33.7,
"thickness": 3.2,
"cost_per_meter": 320
},
{
"DN": 32,
"inner_diameter": 37.2,
"outer_diameter": 42.4,
"thickness": 2.6,
"cost_per_meter": 350
},
{
"DN": 40,
"inner_diameter": 43.1,
"outer_diameter": 48.3,
"thickness": 2.6,
"cost_per_meter": 375
},
{
"DN": 50,
"inner_diameter": 54.5,
"outer_diameter": 60.3,
"thickness": 2.9,
"cost_per_meter": 400
},
{
"DN": 65,
"inner_diameter": 70.3,
"outer_diameter": 76.1,
"thickness": 2.9,
"cost_per_meter": 450
},
{
"DN": 80,
"inner_diameter": 82.5,
"outer_diameter": 88.9,
"thickness": 3.2,
"cost_per_meter": 480
},
{
"DN": 90,
"inner_diameter": 100.8,
"outer_diameter": 108,
"thickness": 3.6,
"cost_per_meter": 480
},
{
"DN": 100,
"inner_diameter": 107.1,
"outer_diameter": 114.3,
"thickness": 3.6,
"cost_per_meter": 550
},
{
"DN": 110,
"inner_diameter": 125.8,
"outer_diameter": 133,
"thickness": 3.6,
"cost_per_meter": 550
},
{
"DN": 125,
"inner_diameter": 132.5,
"outer_diameter": 139.7,
"thickness": 3.6,
"cost_per_meter": 630
},
{
"DN": 140,
"inner_diameter": 151,
"outer_diameter": 159,
"thickness": 4,
"cost_per_meter": 700
},
{
"DN": 150,
"inner_diameter": 160.3,
"outer_diameter": 168.3,
"thickness": 4,
"cost_per_meter": 700
},
{
"DN": 180,
"inner_diameter": 184.7,
"outer_diameter": 193.7,
"thickness": 4.5,
"cost_per_meter": 700
},
{
"DN": 200,
"inner_diameter": 210.1,
"outer_diameter": 219.1,
"thickness": 4.5,
"cost_per_meter": 860
},
{
"DN": 250,
"inner_diameter": 263,
"outer_diameter": 273,
"thickness": 5,
"cost_per_meter": 860
},
{
"DN": 300,
"inner_diameter": 312.7,
"outer_diameter": 323.9,
"thickness": 5.6,
"cost_per_meter": 860
},
{
"DN": 350,
"inner_diameter": 344.4,
"outer_diameter": 355.6,
"thickness": 5.6,
"cost_per_meter": 860
},
{
"DN": 400,
"inner_diameter": 393.8,
"outer_diameter": 406.4,
"thickness": 6.3,
"cost_per_meter": 860
},
{
"DN": 450,
"inner_diameter": 444.4,
"outer_diameter": 457,
"thickness": 6.3,
"cost_per_meter": 860
},
{
"DN": 500,
"inner_diameter": 495.4,
"outer_diameter": 508,
"thickness": 6.3,
"cost_per_meter": 860
},
{
"DN": 600,
"inner_diameter": 595.8,
"outer_diameter": 610,
"thickness": 7.1,
"cost_per_meter": 860
},
{
"DN": 700,
"inner_diameter": 696.8,
"outer_diameter": 711,
"thickness": 7.1,
"cost_per_meter": 860
},
{
"DN": 800,
"inner_diameter": 797,
"outer_diameter": 813,
"thickness": 8,
"cost_per_meter": 860
},
{
"DN": 900,
"inner_diameter": 894,
"outer_diameter": 914,
"thickness": 10,
"cost_per_meter": 860
},
{
"DN": 1000,
"inner_diameter": 996,
"outer_diameter": 1016,
"thickness": 10,
"cost_per_meter": 860
}
]

File diff suppressed because one or more lines are too long