read_h2_excel_sheets()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 24
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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