1
|
|
|
""" |
2
|
|
|
The central module containing all code dealing with the H2 grid in eGon100RE |
3
|
|
|
|
4
|
|
|
""" |
5
|
|
|
from geoalchemy2.types import Geometry |
6
|
|
|
from shapely.geometry import LineString, MultiLineString, Point |
7
|
|
|
import geopandas as gpd |
8
|
|
|
import pandas as pd |
9
|
|
|
import os |
10
|
|
|
from urllib.request import urlretrieve |
11
|
|
|
from pathlib import Path |
12
|
|
|
from fuzzywuzzy import process |
13
|
|
|
from shapely import wkb |
14
|
|
|
import math |
15
|
|
|
import re |
16
|
|
|
from itertools import count |
17
|
|
|
import numpy as np |
18
|
|
|
from scipy.spatial import cKDTree |
19
|
|
|
|
20
|
|
|
from egon.data import config, db |
21
|
|
|
from egon.data.datasets.scenario_parameters import get_sector_parameters |
22
|
|
|
from egon.data.datasets.scenario_parameters.parameters import annualize_capital_costs |
23
|
|
|
|
24
|
|
|
|
25
|
|
|
def insert_h2_pipelines(scn_name): |
26
|
|
|
"Insert H2_grid based on Input Data from FNB-Gas" |
27
|
|
|
|
28
|
|
|
download_h2_grid_data() |
29
|
|
|
H2_grid_Neubau, H2_grid_Umstellung, H2_grid_Erweiterung = read_h2_excel_sheets() |
30
|
|
|
h2_bus_location = pd.read_csv(Path(".")/"h2_grid_nodes.csv") |
31
|
|
|
con=db.engine() |
32
|
|
|
|
33
|
|
|
sources = config.datasets()["etrago_hydrogen"]["sources"] |
34
|
|
|
target = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_links"] |
35
|
|
|
|
36
|
|
|
h2_buses_df = pd.read_sql( |
37
|
|
|
f""" |
38
|
|
|
SELECT bus_id, x, y FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} |
39
|
|
|
WHERE carrier in ('H2_grid') |
40
|
|
|
AND scn_name = '{scn_name}' |
41
|
|
|
""" |
42
|
|
|
, con) |
43
|
|
|
|
44
|
|
|
# Delete old entries |
45
|
|
|
db.execute_sql( |
46
|
|
|
f""" |
47
|
|
|
DELETE FROM {target["schema"]}.{target["table"]} |
48
|
|
|
WHERE "carrier" = 'H2_grid' |
49
|
|
|
AND scn_name = '{scn_name}' AND bus0 IN ( |
50
|
|
|
SELECT bus_id |
51
|
|
|
FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} |
52
|
|
|
WHERE country = 'DE' |
53
|
|
|
) |
54
|
|
|
""" |
55
|
|
|
) |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
target = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_links"] |
59
|
|
|
|
60
|
|
|
for df in [H2_grid_Neubau, H2_grid_Umstellung, H2_grid_Erweiterung]: |
61
|
|
|
|
62
|
|
|
if df is H2_grid_Neubau: |
63
|
|
|
df.rename(columns={'Planerische \nInbetriebnahme': 'Planerische Inbetriebnahme'}, inplace=True) |
64
|
|
|
df.loc[df['Endpunkt\n(Ort)'] == 'AQD Anlandung', 'Endpunkt\n(Ort)'] = 'Schillig' |
65
|
|
|
df.loc[df['Endpunkt\n(Ort)'] == 'Hallendorf', 'Endpunkt\n(Ort)'] = 'Salzgitter' |
66
|
|
|
|
67
|
|
|
if df is H2_grid_Erweiterung: |
68
|
|
|
df.rename(columns={'Umstellungsdatum/ Planerische Inbetriebnahme': 'Planerische Inbetriebnahme', |
69
|
|
|
'Nenndurchmesser (DN)': 'Nenndurchmesser \n(DN)', |
70
|
|
|
'Investitionskosten\n(Mio. Euro),\nKostenschätzung': 'Investitionskosten*\n(Mio. Euro)'}, |
71
|
|
|
inplace=True) |
72
|
|
|
df = df[df['Berücksichtigung im Kernnetz \n[ja/nein/zurückgezogen]'].str.strip().str.lower() == 'ja'] |
73
|
|
|
df.loc[df['Endpunkt\n(Ort)'] == 'Osdorfer Straße', 'Endpunkt\n(Ort)'] = 'Berlin- Lichterfelde' |
74
|
|
|
|
75
|
|
|
h2_bus_location['Ort'] = h2_bus_location['Ort'].astype(str).str.strip() |
76
|
|
|
df['Anfangspunkt\n(Ort)'] = df['Anfangspunkt\n(Ort)'].astype(str).str.strip() |
77
|
|
|
df['Endpunkt\n(Ort)'] = df['Endpunkt\n(Ort)'].astype(str).str.strip() |
78
|
|
|
|
79
|
|
|
df = df[['Anfangspunkt\n(Ort)', 'Endpunkt\n(Ort)', 'Nenndurchmesser \n(DN)', 'Druckstufe (DP)\n[mind. 30 barg]', |
80
|
|
|
'Investitionskosten*\n(Mio. Euro)', 'Planerische Inbetriebnahme', 'Länge \n(km)']] |
81
|
|
|
|
82
|
|
|
# matching start- and endpoint of each pipeline with georeferenced data |
83
|
|
|
df['Anfangspunkt_matched'] = fuzzy_match(df, h2_bus_location, 'Anfangspunkt\n(Ort)') |
84
|
|
|
df['Endpunkt_matched'] = fuzzy_match(df, h2_bus_location, 'Endpunkt\n(Ort)') |
85
|
|
|
|
86
|
|
|
# manuell adjustments based on Detailmaßnahmenkarte der FNB-Gas [https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/] |
87
|
|
|
df= fix_h2_grid_infrastructure(df) |
88
|
|
|
|
89
|
|
|
df_merged = pd.merge(df, h2_bus_location[['Ort', 'geom', 'x', 'y']], |
90
|
|
|
how='left', left_on='Anfangspunkt_matched', right_on='Ort').rename( |
91
|
|
|
columns={'geom': 'geom_start', 'x': 'x_start', 'y': 'y_start'}) |
92
|
|
|
df_merged = pd.merge(df_merged, h2_bus_location[['Ort', 'geom', 'x', 'y']], |
93
|
|
|
how='left', left_on='Endpunkt_matched', right_on='Ort').rename( |
94
|
|
|
columns={'geom': 'geom_end', 'x': 'x_end', 'y': 'y_end'}) |
95
|
|
|
|
96
|
|
|
H2_grid_df = df_merged.dropna(subset=['geom_start', 'geom_end']) |
97
|
|
|
H2_grid_df = H2_grid_df[H2_grid_df['geom_start'] != H2_grid_df['geom_end']] |
98
|
|
|
H2_grid_df = pd.merge(H2_grid_df, h2_buses_df, how='left', left_on=['x_start', 'y_start'], right_on=['x','y']).rename( |
99
|
|
|
columns={'bus_id': 'bus0'}) |
100
|
|
|
H2_grid_df = pd.merge(H2_grid_df, h2_buses_df, how='left', left_on=['x_end', 'y_end'], right_on=['x','y']).rename( |
101
|
|
|
columns={'bus_id': 'bus1'}) |
102
|
|
|
H2_grid_df[['bus0', 'bus1']] = H2_grid_df[['bus0', 'bus1']].astype('Int64') |
103
|
|
|
|
104
|
|
|
H2_grid_df['geom_start'] = H2_grid_df['geom_start'].apply(lambda x: wkb.loads(bytes.fromhex(x))) |
105
|
|
|
H2_grid_df['geom_end'] = H2_grid_df['geom_end'].apply(lambda x: wkb.loads(bytes.fromhex(x))) |
106
|
|
|
H2_grid_df['topo'] = H2_grid_df.apply( |
107
|
|
|
lambda row: LineString([row['geom_start'], row['geom_end']]), axis=1) |
108
|
|
|
H2_grid_df['geom'] = H2_grid_df.apply( |
109
|
|
|
lambda row: MultiLineString([LineString([row['geom_start'], row['geom_end']])]), axis=1) |
110
|
|
|
H2_grid_gdf = gpd.GeoDataFrame(H2_grid_df, geometry='geom', crs=4326) |
111
|
|
|
|
112
|
|
|
scn_params = get_sector_parameters("gas", scn_name) |
113
|
|
|
next_link_id = db.next_etrago_id('link') |
114
|
|
|
|
115
|
|
|
H2_grid_gdf['link_id'] = range(next_link_id, next_link_id + len(H2_grid_gdf)) |
116
|
|
|
H2_grid_gdf['scn_name'] = scn_name |
117
|
|
|
H2_grid_gdf['carrier'] = 'H2_grid' |
118
|
|
|
H2_grid_gdf['Planerische Inbetriebnahme'] = H2_grid_gdf['Planerische Inbetriebnahme'].astype(str).apply( |
119
|
|
|
lambda x: int(re.findall(r'\d{4}', x)[-1]) if re.findall(r'\d{4}', x) else |
120
|
|
|
int(re.findall(r'\d{2}\.\d{2}\.(\d{4})', x)[-1]) if re.findall(r'\d{2}\.\d{2}\.(\d{4})', x) else None) |
121
|
|
|
H2_grid_gdf['build_year'] = H2_grid_gdf['Planerische Inbetriebnahme'].astype('Int64') |
122
|
|
|
H2_grid_gdf['p_nom'] = H2_grid_gdf.apply(lambda row:calculate_H2_capacity(row['Druckstufe (DP)\n[mind. 30 barg]'], row['Nenndurchmesser \n(DN)']), axis=1 ) |
123
|
|
|
H2_grid_gdf['p_nom_min'] = H2_grid_gdf['p_nom'] |
124
|
|
|
H2_grid_gdf['p_nom_max'] = float("Inf") |
125
|
|
|
H2_grid_gdf['p_nom_extendable'] = False |
126
|
|
|
H2_grid_gdf['lifetime'] = scn_params["lifetime"]["H2_pipeline"] |
127
|
|
|
H2_grid_gdf['capital_cost'] = H2_grid_gdf.apply(lambda row: annualize_capital_costs( |
128
|
|
|
(float(row["Investitionskosten*\n(Mio. Euro)"]) * 10**6 / row['p_nom']) |
129
|
|
|
if pd.notna(row["Investitionskosten*\n(Mio. Euro)"]) |
130
|
|
|
and str(row["Investitionskosten*\n(Mio. Euro)"]).replace(",", "").replace(".", "").isdigit() |
131
|
|
|
and float(row["Investitionskosten*\n(Mio. Euro)"]) != 0 |
132
|
|
|
else scn_params["overnight_cost"]["H2_pipeline"] * row['Länge \n(km)'], |
|
|
|
|
133
|
|
|
row['lifetime'], 0.05), axis=1) |
134
|
|
|
H2_grid_gdf['p_min_pu'] = -1 |
135
|
|
|
|
136
|
|
|
selected_columns = [ |
137
|
|
|
'scn_name', 'link_id', 'bus0', 'bus1', 'build_year', 'p_nom', 'p_nom_min', |
138
|
|
|
'p_nom_extendable', 'capital_cost', 'geom', 'topo', 'carrier', 'p_nom_max', 'p_min_pu', |
139
|
|
|
] |
140
|
|
|
|
141
|
|
|
H2_grid_final=H2_grid_gdf[selected_columns] |
142
|
|
|
|
143
|
|
|
# Insert data to db |
144
|
|
|
H2_grid_final.to_postgis( |
145
|
|
|
target["table"], |
146
|
|
|
con, |
147
|
|
|
schema= target["schema"], |
148
|
|
|
if_exists="append", |
149
|
|
|
dtype={"geom": Geometry()}, |
150
|
|
|
) |
151
|
|
|
|
152
|
|
|
#connect saltcaverns to H2_grid |
153
|
|
|
connect_saltcavern_to_h2_grid(scn_name) |
154
|
|
|
|
155
|
|
|
#connect neighbour countries to H2_grid |
156
|
|
|
connect_h2_grid_to_neighbour_countries(scn_name) |
157
|
|
|
|
158
|
|
|
|
159
|
|
|
def replace_pipeline(df, start, end, intermediate): |
160
|
|
|
""" |
161
|
|
|
Method for adjusting pipelines manually by splittiing pipeline with an intermediate point. |
162
|
|
|
|
163
|
|
|
Parameters |
164
|
|
|
---------- |
165
|
|
|
df : pandas.core.frame.DataFrame |
166
|
|
|
dataframe to be adjusted |
167
|
|
|
start: str |
168
|
|
|
startpoint of pipeline |
169
|
|
|
end: str |
170
|
|
|
endpoint of pipeline |
171
|
|
|
intermediate: str |
172
|
|
|
new intermediate point for splitting given pipeline |
173
|
|
|
|
174
|
|
|
Returns |
175
|
|
|
--------- |
176
|
|
|
df : <class 'pandas.core.frame.DataFrame'> |
177
|
|
|
adjusted dataframe |
178
|
|
|
|
179
|
|
|
|
180
|
|
|
""" |
181
|
|
|
# Find rows where the start and end points match |
182
|
|
|
mask = ((df['Anfangspunkt_matched'] == start) & (df['Endpunkt_matched'] == end)) | \ |
183
|
|
|
((df['Anfangspunkt_matched'] == end) & (df['Endpunkt_matched'] == start)) |
184
|
|
|
|
185
|
|
|
# Separate the rows to replace |
186
|
|
|
if mask.any(): |
187
|
|
|
df_replacement = df[~mask].copy() |
188
|
|
|
row_replaced = df[mask].iloc[0] |
189
|
|
|
|
190
|
|
|
# Add new rows for the split pipelines |
191
|
|
|
new_rows = pd.DataFrame({ |
192
|
|
|
'Anfangspunkt_matched': [start, intermediate], |
193
|
|
|
'Endpunkt_matched': [intermediate, end], |
194
|
|
|
'Nenndurchmesser \n(DN)': [row_replaced['Nenndurchmesser \n(DN)'], row_replaced['Nenndurchmesser \n(DN)']], |
195
|
|
|
'Druckstufe (DP)\n[mind. 30 barg]': [row_replaced['Druckstufe (DP)\n[mind. 30 barg]'], row_replaced['Druckstufe (DP)\n[mind. 30 barg]']], |
196
|
|
|
'Investitionskosten*\n(Mio. Euro)': [row_replaced['Investitionskosten*\n(Mio. Euro)'], row_replaced['Investitionskosten*\n(Mio. Euro)']], |
197
|
|
|
'Planerische Inbetriebnahme': [row_replaced['Planerische Inbetriebnahme'],row_replaced['Planerische Inbetriebnahme']], |
198
|
|
|
'Länge \n(km)': [row_replaced['Länge \n(km)'], row_replaced['Länge \n(km)']] |
199
|
|
|
}) |
200
|
|
|
|
201
|
|
|
df_replacement = pd.concat([df_replacement, new_rows], ignore_index=True) |
202
|
|
|
return df_replacement |
203
|
|
|
else: |
204
|
|
|
return df |
205
|
|
|
|
206
|
|
|
|
207
|
|
|
|
208
|
|
|
def fuzzy_match(df1, df2, column_to_match, threshold=80): |
209
|
|
|
''' |
210
|
|
|
Method for matching input data of H2_grid with georeferenced data (even if the strings are not exact the same) |
211
|
|
|
|
212
|
|
|
Parameters |
213
|
|
|
---------- |
214
|
|
|
df1 : pandas.core.frame.DataFrame |
215
|
|
|
Input dataframe |
216
|
|
|
df2 : pandas.core.frame.DataFrame |
217
|
|
|
georeferenced dataframe with h2_buses |
218
|
|
|
column_to_match: str |
219
|
|
|
matching column |
220
|
|
|
treshhold: float |
221
|
|
|
matching percentage for succesfull comparison |
222
|
|
|
|
223
|
|
|
Returns |
224
|
|
|
--------- |
225
|
|
|
matched : list |
226
|
|
|
list with all matched location names |
227
|
|
|
|
228
|
|
|
''' |
229
|
|
|
options = df2['Ort'].unique() |
230
|
|
|
matched = [] |
231
|
|
|
|
232
|
|
|
# Compare every locationname in df1 with locationnames in df2 |
233
|
|
|
for value in df1[column_to_match]: |
234
|
|
|
match, score = process.extractOne(value, options) |
235
|
|
|
if score >= threshold: |
236
|
|
|
matched.append(match) |
237
|
|
|
else: |
238
|
|
|
matched.append(None) |
239
|
|
|
|
240
|
|
|
return matched |
241
|
|
|
|
242
|
|
|
|
243
|
|
|
def calculate_H2_capacity(pressure, diameter): |
244
|
|
|
''' |
245
|
|
|
Method for calculagting capacity of pipelines based on data input from FNB Gas |
246
|
|
|
|
247
|
|
|
Parameters |
248
|
|
|
---------- |
249
|
|
|
pressure : float |
250
|
|
|
input for pressure of pipeline |
251
|
|
|
diameter: float |
252
|
|
|
input for diameter of pipeline |
253
|
|
|
column_to_match: str |
254
|
|
|
matching column |
255
|
|
|
treshhold: float |
256
|
|
|
matching percentage for succesfull comparison |
257
|
|
|
|
258
|
|
|
Returns |
259
|
|
|
--------- |
260
|
|
|
energy_flow: float |
261
|
|
|
transmission capacity of pipeline |
262
|
|
|
|
263
|
|
|
''' |
264
|
|
|
|
265
|
|
|
pressure = str(pressure).replace(",", ".") |
266
|
|
|
diameter =str(diameter) |
267
|
|
|
|
268
|
|
|
def convert_to_float(value): |
269
|
|
|
try: |
270
|
|
|
return float(value) |
271
|
|
|
except ValueError: |
272
|
|
|
return 400 #average value from data-source cause capacities of some lines are not fixed yet |
273
|
|
|
|
274
|
|
|
# in case of given range for pipeline-capacity calculate average value |
275
|
|
|
if '-' in diameter: |
276
|
|
|
diameters = diameter.split('-') |
277
|
|
|
diameter = (convert_to_float(diameters[0]) + convert_to_float(diameters[1])) / 2 |
278
|
|
|
elif '/' in diameter: |
279
|
|
|
diameters = diameter.split('/') |
280
|
|
|
diameter = (convert_to_float(diameters[0]) + convert_to_float(diameters[1])) / 2 |
281
|
|
|
else: |
282
|
|
|
try: |
283
|
|
|
diameter = float(diameter) |
284
|
|
|
except ValueError: |
285
|
|
|
diameter = 400 #average value from data-source |
286
|
|
|
|
287
|
|
|
if '-' in pressure: |
288
|
|
|
pressures = pressure.split('-') |
289
|
|
|
pressure = (float(pressures[0]) + float(pressures[1])) / 2 |
290
|
|
|
elif '/' in pressure: |
291
|
|
|
pressures = pressure.split('/') |
292
|
|
|
pressure = (float(pressures[0]) + float(pressures[1])) / 2 |
293
|
|
|
else: |
294
|
|
|
try: |
295
|
|
|
pressure = float(diameter) |
296
|
|
|
except ValueError: |
297
|
|
|
pressure = 70 #averaqge value from data-source |
298
|
|
|
|
299
|
|
|
|
300
|
|
|
velocity = 40 #source: L.Koops (2023): GAS PIPELINE VERSUS LIQUID HYDROGEN TRANSPORT – PERSPECTIVES FOR TECHNOLOGIES, ENERGY DEMAND ANDv TRANSPORT CAPACITY, AND IMPLICATIONS FOR AVIATION |
301
|
|
|
temperature = 10+273.15 #source: L.Koops (2023): GAS PIPELINE VERSUS LIQUID HYDROGEN TRANSPORT – PERSPECTIVES FOR TECHNOLOGIES, ENERGY DEMAND ANDv TRANSPORT CAPACITY, AND IMPLICATIONS FOR AVIATION |
302
|
|
|
density = pressure*10**5/(4.1243*10**3*temperature) #gasconstant H2 = 4.1243 [kJ/kgK] |
303
|
|
|
mass_flow = density*math.pi*((diameter/10**3)/2)**2*velocity |
304
|
|
|
energy_flow = mass_flow * 119.988 #low_heating_value H2 = 119.988 [MJ/kg] |
305
|
|
|
|
306
|
|
|
return energy_flow |
307
|
|
|
|
308
|
|
|
|
309
|
|
|
def download_h2_grid_data(): |
310
|
|
|
""" |
311
|
|
|
Download Input data for H2_grid from FNB-Gas (https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/) |
312
|
|
|
|
313
|
|
|
The following data for H2 are downloaded into the folder |
314
|
|
|
./datasets/h2_data: |
315
|
|
|
* Links (file Anlage_3_Wasserstoffkernnetz_Neubau.xlsx, |
316
|
|
|
Anlage_4_Wasserstoffkernnetz_Umstellung.xlsx, |
317
|
|
|
Anlage_2_Wasserstoffkernetz_weitere_Leitungen.xlsx) |
318
|
|
|
|
319
|
|
|
Returns |
320
|
|
|
------- |
321
|
|
|
None |
322
|
|
|
|
323
|
|
|
""" |
324
|
|
|
path = Path("datasets/h2_data") |
325
|
|
|
os.makedirs(path, exist_ok=True) |
326
|
|
|
|
327
|
|
|
download_config = config.datasets()["etrago_hydrogen"]["sources"]["H2_grid"] |
328
|
|
|
target_file_Um = path / download_config["converted_ch4_pipes"]["path"] |
329
|
|
|
target_file_Neu = path / download_config["new_constructed_pipes"]["path"] |
330
|
|
|
target_file_Erw = path / download_config["pipes_of_further_h2_grid_operators"]["path"] |
331
|
|
|
|
332
|
|
|
for target_file in [target_file_Neu, target_file_Um, target_file_Erw]: |
333
|
|
|
if target_file is target_file_Um: |
334
|
|
|
url = download_config["converted_ch4_pipes"]["url"] |
335
|
|
|
elif target_file is target_file_Neu: |
336
|
|
|
url = download_config["new_constructed_pipes"]['url'] |
337
|
|
|
else: |
338
|
|
|
url = download_config["pipes_of_further_h2_grid_operators"]['url'] |
339
|
|
|
|
340
|
|
|
if not os.path.isfile(target_file): |
341
|
|
|
urlretrieve(url, target_file) |
342
|
|
|
|
343
|
|
|
|
344
|
|
|
def read_h2_excel_sheets(): |
345
|
|
|
""" |
346
|
|
|
Read downloaded excel files with location names for future h2-pipelines |
347
|
|
|
|
348
|
|
|
Returns |
349
|
|
|
------- |
350
|
|
|
df_Neu : <class 'pandas.core.frame.DataFrame'> |
351
|
|
|
df_Um : <class 'pandas.core.frame.DataFrame'> |
352
|
|
|
df_Erw : <class 'pandas.core.frame.DataFrame'> |
353
|
|
|
|
354
|
|
|
|
355
|
|
|
""" |
356
|
|
|
|
357
|
|
|
path = Path(".") / "datasets" / "h2_data" |
358
|
|
|
download_config = config.datasets()["etrago_hydrogen"]["sources"]["H2_grid"] |
359
|
|
|
excel_file_Um = pd.ExcelFile(f'{path}/{download_config["converted_ch4_pipes"]["path"]}') |
360
|
|
|
excel_file_Neu = pd.ExcelFile(f'{path}/{download_config["new_constructed_pipes"]["path"]}') |
361
|
|
|
excel_file_Erw = pd.ExcelFile(f'{path}/{download_config["pipes_of_further_h2_grid_operators"]["path"]}') |
362
|
|
|
|
363
|
|
|
df_Um= pd.read_excel(excel_file_Um, header=3) |
364
|
|
|
df_Neu = pd.read_excel(excel_file_Neu, header=3) |
365
|
|
|
df_Erw = pd.read_excel(excel_file_Erw, header=2) |
366
|
|
|
|
367
|
|
|
return df_Neu, df_Um, df_Erw |
368
|
|
|
|
369
|
|
|
def fix_h2_grid_infrastructure(df): |
370
|
|
|
""" |
371
|
|
|
Manuell adjustments for more accurate grid topology based on Detailmaßnahmenkarte der |
372
|
|
|
FNB-Gas [https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/] |
373
|
|
|
|
374
|
|
|
Returns |
375
|
|
|
------- |
376
|
|
|
df : <class 'pandas.core.frame.DataFrame'> |
377
|
|
|
|
378
|
|
|
""" |
379
|
|
|
|
380
|
|
|
df = replace_pipeline(df, 'Lubmin', 'Uckermark', 'Wrangelsburg') |
381
|
|
|
df = replace_pipeline(df, 'Wrangelsburg', 'Uckermark', 'Schönermark') |
382
|
|
|
df = replace_pipeline(df, 'Hemmingstedt', 'Ascheberg (Holstein)', 'Remmels Nord') |
383
|
|
|
df = replace_pipeline(df, 'Heidenau', 'Elbe-Süd', 'Weißenfelde') |
384
|
|
|
df = replace_pipeline(df, 'Weißenfelde', 'Elbe-Süd', 'Stade') |
385
|
|
|
df = replace_pipeline(df, 'Stade AOS', 'KW Schilling', 'Abzweig Stade') |
386
|
|
|
df = replace_pipeline(df, 'Rosengarten (Sottorf)', 'Moorburg', 'Leversen') |
387
|
|
|
df = replace_pipeline(df, 'Leversen', 'Moorburg', 'Hamburg Süd') |
388
|
|
|
df = replace_pipeline(df, 'Achim', 'Folmhusen', 'Wardenburg') |
389
|
|
|
df = replace_pipeline(df, 'Achim', 'Wardenburg', 'Sandkrug') |
390
|
|
|
df = replace_pipeline(df, 'Dykhausen', 'Bunde', 'Emden') |
391
|
|
|
df = replace_pipeline(df, 'Emden', 'Nüttermoor', 'Jemgum') |
392
|
|
|
df = replace_pipeline(df, 'Rostock', 'Glasewitz', 'Fliegerhorst Laage') |
393
|
|
|
df = replace_pipeline(df, 'Wilhelmshaven', 'Dykhausen', 'Sande') |
394
|
|
|
df = replace_pipeline(df, 'Wilhelmshaven Süd', 'Wilhelmshaven Nord', 'Wilhelmshaven') |
395
|
|
|
df = replace_pipeline(df, 'Sande', 'Jemgum', 'Westerstede') |
396
|
|
|
df = replace_pipeline(df, 'Kalle', 'Ochtrup', 'Frensdorfer Bruchgraben') |
397
|
|
|
df = replace_pipeline(df, 'Frensdorfer Bruchgraben', 'Ochtrup', 'Bad Bentheim') |
398
|
|
|
df = replace_pipeline(df, 'Bunde', 'Wettringen', 'Emsbüren') |
399
|
|
|
df = replace_pipeline(df, 'Emsbüren', 'Dorsten', 'Ochtrup') |
400
|
|
|
df = replace_pipeline(df, 'Ochtrup', 'Dorsten', 'Heek') |
401
|
|
|
df = replace_pipeline(df, 'Lemförde', 'Drohne', 'Reiningen') |
402
|
|
|
df = replace_pipeline(df, 'Edesbüttel', 'Bobbau', 'Uhrsleben') |
403
|
|
|
df = replace_pipeline(df, 'Sixdorf', 'Wiederitzsch', 'Cörmigk') |
404
|
|
|
df = replace_pipeline(df, 'Schkeuditz', 'Plaußig', 'Wiederitzsch') |
405
|
|
|
df = replace_pipeline(df, 'Wiederitzsch', 'Plaußig', 'Mockau Nord') |
406
|
|
|
df = replace_pipeline(df, 'Bobbau', 'Rückersdorf', 'Nempitz') |
407
|
|
|
df = replace_pipeline(df, 'Räpitz', 'Böhlen', 'Kleindalzig') |
408
|
|
|
df = replace_pipeline(df, 'Buchholz', 'Friedersdorf', 'Werben') |
409
|
|
|
df = replace_pipeline(df, 'Radeland', 'Uckermark', 'Friedersdorf') |
410
|
|
|
df = replace_pipeline(df, 'Friedersdorf', 'Uckermark', 'Herzfelde') |
411
|
|
|
df = replace_pipeline(df, 'Blumberg', 'Berlin-Mitte', 'Berlin-Marzahn') |
412
|
|
|
df = replace_pipeline(df, 'Radeland', 'Zethau', 'Coswig') |
413
|
|
|
df = replace_pipeline(df, 'Leuna', 'Böhlen', 'Räpitz') |
414
|
|
|
df = replace_pipeline(df, 'Dürrengleina', 'Stadtroda', 'Zöllnitz') |
415
|
|
|
df = replace_pipeline(df, 'Mailing', 'Kötz', 'Wertingen') |
416
|
|
|
df = replace_pipeline(df, 'Lampertheim', 'Rüsselsheim', 'Gernsheim-Nord') |
417
|
|
|
df = replace_pipeline(df, 'Birlinghoven', 'Rüsselsheim', 'Wiesbaden') |
418
|
|
|
df = replace_pipeline(df, 'Medelsheim', 'Mittelbrunn', 'Seyweiler') |
419
|
|
|
df = replace_pipeline(df, 'Seyweiler', 'Dillingen', 'Fürstenhausen') |
420
|
|
|
df = replace_pipeline(df, 'Reckrod', 'Wolfsbehringen', 'Eisenach') |
421
|
|
|
df = replace_pipeline(df, 'Elten', 'St. Hubert', 'Hüthum') |
422
|
|
|
df = replace_pipeline(df, 'St. Hubert', 'Hüthum', 'Uedener Bruch') |
423
|
|
|
df = replace_pipeline(df, 'Wallach', 'Möllen', 'Spellen') |
424
|
|
|
df = replace_pipeline(df, 'St. Hubert', 'Glehn', 'Krefeld') |
425
|
|
|
df = replace_pipeline(df, 'Neumühl', 'Werne', 'Bottrop') |
426
|
|
|
df = replace_pipeline(df, 'Bottrop', 'Werne', 'Recklinghausen') |
427
|
|
|
df = replace_pipeline(df, 'Werne', 'Eisenach', 'Arnsberg-Bruchhausen') |
428
|
|
|
df = replace_pipeline(df, 'Dorsten', 'Gescher', 'Gescher Süd') |
429
|
|
|
df = replace_pipeline(df, 'Dorsten', 'Hamborn', 'Averbruch') |
430
|
|
|
df = replace_pipeline(df, 'Neumühl', 'Bruckhausen', 'Hamborn') |
431
|
|
|
df = replace_pipeline(df, 'Werne', 'Paffrath', 'Westhofen') |
432
|
|
|
df = replace_pipeline(df, 'Glehn', 'Voigtslach', 'Dormagen') |
433
|
|
|
df = replace_pipeline(df, 'Voigtslach', 'Paffrath','Leverkusen') |
434
|
|
|
df = replace_pipeline(df, 'Glehn', 'Ludwigshafen','Wesseling') |
435
|
|
|
df = replace_pipeline(df, 'Rothenstadt', 'Rimpar','Reutles') |
436
|
|
|
|
437
|
|
|
return df |
438
|
|
|
|
439
|
|
|
def connect_saltcavern_to_h2_grid(scn_name): |
440
|
|
|
""" |
441
|
|
|
Connect each saltcavern with nearest H2-Bus of the H2-Grid and insert the links into the database |
442
|
|
|
|
443
|
|
|
Returns |
444
|
|
|
------- |
445
|
|
|
None |
446
|
|
|
|
447
|
|
|
""" |
448
|
|
|
|
449
|
|
|
targets = config.datasets()["etrago_hydrogen"]["targets"] |
450
|
|
|
sources = config.datasets()["etrago_hydrogen"]["sources"] |
451
|
|
|
engine = db.engine() |
452
|
|
|
|
453
|
|
|
db.execute_sql(f""" |
454
|
|
|
DELETE FROM {targets["hydrogen_links"]["schema"]}.{targets["hydrogen_links"]["table"]} |
455
|
|
|
WHERE "carrier" in ('H2_saltcavern') |
456
|
|
|
AND scn_name = '{scn_name}'; |
457
|
|
|
""") |
458
|
|
|
h2_buses_query = f"""SELECT bus_id, x, y,ST_Transform(geom, 32632) as geom |
459
|
|
|
FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} |
460
|
|
|
WHERE carrier = 'H2_grid' AND scn_name = '{scn_name}' |
461
|
|
|
""" |
462
|
|
|
h2_buses = gpd.read_postgis(h2_buses_query, engine) |
463
|
|
|
|
464
|
|
|
salt_caverns_query = f"""SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom |
465
|
|
|
FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} |
466
|
|
|
WHERE carrier = 'H2_saltcavern' AND scn_name = '{scn_name}' |
467
|
|
|
""" |
468
|
|
|
salt_caverns = gpd.read_postgis(salt_caverns_query, engine) |
469
|
|
|
|
470
|
|
|
max_link_id = db.next_etrago_id('link') |
471
|
|
|
next_link_id = count(start=max_link_id, step=1) |
472
|
|
|
scn_params = get_sector_parameters("gas", scn_name) |
473
|
|
|
|
474
|
|
|
H2_coords = np.array([(point.x, point.y) for point in h2_buses.geometry]) |
475
|
|
|
H2_tree = cKDTree(H2_coords) |
476
|
|
|
links=[] |
477
|
|
|
for idx, bus_saltcavern in salt_caverns.iterrows(): |
478
|
|
|
saltcavern_coords = [bus_saltcavern['geom'].x, bus_saltcavern['geom'].y] |
479
|
|
|
|
480
|
|
|
dist, nearest_idx = H2_tree.query(saltcavern_coords, k=1) |
481
|
|
|
nearest_h2_bus = h2_buses.iloc[nearest_idx] |
482
|
|
|
|
483
|
|
|
link = { |
484
|
|
|
'scn_name': scn_name, |
485
|
|
|
'bus0': nearest_h2_bus['bus_id'], |
486
|
|
|
'bus1': bus_saltcavern['bus_id'], |
487
|
|
|
'link_id': next(next_link_id), |
488
|
|
|
'carrier': 'H2_saltcavern', |
489
|
|
|
'lifetime':25, |
490
|
|
|
'p_nom_extendable': True, |
491
|
|
|
'p_min_pu': -1, |
492
|
|
|
'capital_cost': scn_params["overnight_cost"]["H2_pipeline"]*dist/1000, |
493
|
|
|
'geom': MultiLineString([LineString([(nearest_h2_bus['x'], nearest_h2_bus['y']), (bus_saltcavern['x'], bus_saltcavern['y'])])]) |
494
|
|
|
} |
495
|
|
|
links.append(link) |
496
|
|
|
|
497
|
|
|
links_df = gpd.GeoDataFrame(links, geometry='geom', crs=4326) |
498
|
|
|
|
499
|
|
|
links_df.to_postgis( |
500
|
|
|
targets["hydrogen_links"]["table"], |
501
|
|
|
engine, |
502
|
|
|
schema=targets["hydrogen_links"]["schema"], |
503
|
|
|
index=False, |
504
|
|
|
if_exists="append", |
505
|
|
|
dtype={"geom": Geometry()}, |
506
|
|
|
) |
507
|
|
|
|
508
|
|
|
|
509
|
|
|
def connect_h2_grid_to_neighbour_countries(scn_name): |
510
|
|
|
""" |
511
|
|
|
Connect germand H2_grid with neighbour countries. All german H2-Buses wich were planned as connection |
512
|
|
|
points for Import/Export of Hydrogen to corresponding neighbours country, are based on Publication |
513
|
|
|
of FNB-GAS (https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/). |
514
|
|
|
|
515
|
|
|
Returns |
516
|
|
|
------- |
517
|
|
|
None |
518
|
|
|
|
519
|
|
|
""" |
520
|
|
|
engine = db.engine() |
521
|
|
|
targets = config.datasets()["etrago_hydrogen"]["targets"] |
522
|
|
|
sources = config.datasets()["etrago_hydrogen"]["sources"] |
523
|
|
|
|
524
|
|
|
h2_buses_df = gpd.read_postgis( |
525
|
|
|
f""" |
526
|
|
|
SELECT bus_id, x, y, geom |
527
|
|
|
FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} |
528
|
|
|
WHERE carrier in ('H2_grid') |
529
|
|
|
AND scn_name = '{scn_name}' |
530
|
|
|
|
531
|
|
|
""" |
532
|
|
|
, engine) |
533
|
|
|
|
534
|
|
|
h2_links_df = pd.read_sql( |
535
|
|
|
f""" |
536
|
|
|
SELECT link_id, bus0, bus1, p_nom |
537
|
|
|
FROM {sources["links"]["schema"]}.{sources["links"]["table"]} |
538
|
|
|
WHERE carrier in ('H2_grid') |
539
|
|
|
AND scn_name = '{scn_name}' |
540
|
|
|
|
541
|
|
|
""" |
542
|
|
|
, engine) |
543
|
|
|
|
544
|
|
|
abroad_buses_df = gpd.read_postgis( |
545
|
|
|
f""" |
546
|
|
|
SELECT bus_id, x, y, geom, country |
547
|
|
|
FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} |
548
|
|
|
WHERE carrier = 'H2' AND scn_name = '{scn_name}' AND country != 'DE' |
549
|
|
|
""", |
550
|
|
|
engine |
551
|
|
|
) |
552
|
|
|
|
553
|
|
|
abroad_con_buses = [ |
554
|
|
|
('Greifenhagen', 'PL'), |
555
|
|
|
('Fürstenberg (PL)', 'PL'), |
556
|
|
|
('Eynatten', 'BE'), |
557
|
|
|
('Überackern', 'AT'), |
558
|
|
|
('Vlieghuis', 'NL'), |
559
|
|
|
('Oude', 'NL'), |
560
|
|
|
('Oude Statenzijl', 'NL'), |
561
|
|
|
('Vreden', 'NL'), |
562
|
|
|
('Elten', 'NL'), |
563
|
|
|
('Leidingen', 'FR'), |
564
|
|
|
('Carling', 'FR'), |
565
|
|
|
('Medelsheim', 'FR'), |
566
|
|
|
('Waidhaus', 'CZ'), |
567
|
|
|
('Deutschneudorf', 'CZ'), |
568
|
|
|
('Grenzach', 'CH'), |
569
|
|
|
('AWZ', 'DK'), |
570
|
|
|
('AWZ', 'SE'), |
571
|
|
|
('AQD Offshore SEN 1', 'GB'), |
572
|
|
|
('AQD Offshore SEN 1', 'NO'), |
573
|
|
|
('AQD Offshore SEN 1', 'DK'), |
574
|
|
|
('AQD Offshore SEN 1', 'NL'), |
575
|
|
|
('Fessenheim', 'FR'), |
576
|
|
|
('Ellund', 'DK') |
577
|
|
|
] |
578
|
|
|
|
579
|
|
|
h2_bus_location = pd.read_csv(Path(".")/"h2_grid_nodes.csv") |
580
|
|
|
|
581
|
|
|
### prepare data for connecting abroad_buses |
582
|
|
|
matched_locations = h2_bus_location[h2_bus_location['Ort'].isin([name for name, _ in abroad_con_buses])] |
583
|
|
|
matched_buses = matched_locations.merge( |
584
|
|
|
h2_buses_df, |
585
|
|
|
left_on=['x', 'y'], |
586
|
|
|
right_on=['x', 'y'], |
587
|
|
|
how='inner' |
588
|
|
|
) |
589
|
|
|
|
590
|
|
|
final_matched_buses = matched_buses[['bus_id', 'Ort', 'x', 'y', 'geom_y']].rename(columns={'geom_y': 'geom'}) |
591
|
|
|
|
592
|
|
|
abroad_links = h2_links_df[(h2_links_df['bus0'].isin(final_matched_buses['bus_id'])) | (h2_links_df['bus1'].isin(final_matched_buses['bus_id']))] |
593
|
|
|
abroad_links_bus0 = abroad_links.merge(final_matched_buses, left_on= 'bus0', right_on= 'bus_id', how='inner') |
594
|
|
|
abroad_links_bus1 = abroad_links.merge(final_matched_buses, left_on= 'bus1', right_on= 'bus_id', how='inner') |
595
|
|
|
abroad_con_df = pd.concat([abroad_links_bus1, abroad_links_bus0]) |
596
|
|
|
|
597
|
|
|
|
598
|
|
|
connection_links = [] |
599
|
|
|
max_link_id = db.next_etrago_id('link') |
600
|
|
|
next_max_link_id = count(start=max_link_id, step=1) |
601
|
|
|
|
602
|
|
|
for inland_name, country_code in abroad_con_buses: |
603
|
|
|
# filter out germand h2_buses for connecting neighbour-countries |
604
|
|
|
inland_bus = abroad_con_df[abroad_con_df['Ort'] == inland_name] |
605
|
|
|
if inland_bus.empty: |
606
|
|
|
print(f"Warning: No Inland-Bus found for {inland_name}.") |
607
|
|
|
continue |
608
|
|
|
|
609
|
|
|
# filter out corresponding abroad_bus for connecting neighbour countries |
610
|
|
|
abroad_bus = abroad_buses_df[abroad_buses_df['country'] == country_code] |
611
|
|
|
if abroad_bus.empty: |
612
|
|
|
print(f"Warning: No Abroad-Bus found for {country_code}.") |
613
|
|
|
continue |
614
|
|
|
|
615
|
|
|
for _, i_bus in inland_bus.iterrows(): |
616
|
|
|
abroad_bus['distance'] = abroad_bus['geom'].apply( |
617
|
|
|
lambda g: i_bus['geom'].distance(g) |
|
|
|
|
618
|
|
|
) |
619
|
|
|
|
620
|
|
|
nearest_abroad_bus = abroad_bus.loc[abroad_bus['distance'].idxmin()] |
621
|
|
|
relevant_buses = inland_bus[inland_bus['bus_id'] == i_bus['bus_id']] |
622
|
|
|
p_nom_value = relevant_buses['p_nom'].sum() |
623
|
|
|
|
624
|
|
|
connection_links.append({ |
625
|
|
|
'scn_name': scn_name, |
626
|
|
|
'carrier': 'H2_grid', |
627
|
|
|
'link_id': next(next_max_link_id), |
628
|
|
|
'bus0': i_bus['bus_id'], |
|
|
|
|
629
|
|
|
'bus1': nearest_abroad_bus['bus_id'], |
|
|
|
|
630
|
|
|
'p_nom': p_nom_value, |
|
|
|
|
631
|
|
|
'p_min_pu': -1, |
632
|
|
|
'geom': MultiLineString( |
633
|
|
|
[LineString([ |
634
|
|
|
(i_bus['geom'].x, i_bus['geom'].y), |
635
|
|
|
(nearest_abroad_bus['geom'].x, nearest_abroad_bus['geom'].y) |
636
|
|
|
])] |
637
|
|
|
) |
638
|
|
|
}) |
639
|
|
|
connection_links_df = gpd.GeoDataFrame(connection_links, geometry='geom', crs="EPSG:4326") |
640
|
|
|
|
641
|
|
|
connection_links_df.to_postgis( |
642
|
|
|
name=targets["hydrogen_links"]["table"], |
643
|
|
|
con=engine, |
644
|
|
|
schema=targets["hydrogen_links"]["schema"], |
645
|
|
|
if_exists="append", |
646
|
|
|
index=False, |
647
|
|
|
) |
648
|
|
|
print("Neighbour countries are succesfully connected to H2-grid") |
649
|
|
|
|
650
|
|
|
|