Completed
Push — dev ( 8582b4...82307e )
by
unknown
30s queued 19s
created

replace_pipeline()   A

Complexity

Conditions 2

Size

Total Lines 46
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 46
rs 9.55
c 0
b 0
f 0
cc 2
nop 4
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)'], 
0 ignored issues
show
introduced by
The variable scn_params does not seem to be defined in case the for loop on line 60 is not entered. Are you sure this can never be the case?
Loading history...
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)
0 ignored issues
show
introduced by
The variable i_bus does not seem to be defined for all execution paths.
Loading history...
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'],
0 ignored issues
show
introduced by
The variable i_bus does not seem to be defined for all execution paths.
Loading history...
629
            '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...
630
            '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...
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