data.datasets.hydrogen_etrago.h2_grid   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 866
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 42
eloc 518
dl 0
loc 866
rs 9.0399
c 0
b 0
f 0

9 Functions

Rating   Name   Duplication   Size   Complexity  
C connect_h2_grid_to_neighbour_countries() 0 169 6
A read_h2_excel_sheets() 0 32 1
A download_h2_grid_data() 0 37 5
F insert_h2_pipelines() 0 254 14
B connect_saltcavern_to_h2_grid() 0 81 2
B fix_h2_grid_infrastructure() 0 75 1
A fuzzy_match() 0 33 3
B calculate_H2_capacity() 0 71 8
A replace_pipeline() 0 68 2

How to fix   Complexity   

Complexity

Complex classes like data.datasets.hydrogen_etrago.h2_grid often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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