Source code for egon.data.datasets.hydrogen_etrago.h2_grid

"""
The central module containing all code dealing with the H2 grid in eGon100RE

"""

from itertools import count
from pathlib import Path
from urllib.request import urlretrieve
import math
import os
import re

from fuzzywuzzy import process
from geoalchemy2.types import Geometry
from scipy.spatial import cKDTree
from shapely import wkb
from shapely.geometry import LineString, MultiLineString, Point
import geopandas as gpd
import numpy as np
import pandas as pd

from egon.data import config, db
from egon.data.datasets.scenario_parameters import get_sector_parameters
from egon.data.datasets.scenario_parameters.parameters import (
    annualize_capital_costs,
)


[docs]def insert_h2_pipelines(scn_name): "Insert H2_grid based on Input Data from FNB-Gas" download_h2_grid_data() H2_grid_Neubau, H2_grid_Umstellung, H2_grid_Erweiterung = ( read_h2_excel_sheets() ) h2_bus_location = pd.read_csv( Path(".") / "data_bundle_egon_data" / "hydrogen_network" / "h2_grid_nodes.csv" ) con = db.engine() sources = config.datasets()["etrago_hydrogen"]["sources"] target = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_links"] h2_buses_df = pd.read_sql( f""" SELECT bus_id, x, y FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} WHERE carrier in ('H2_grid') AND scn_name = '{scn_name}' """, con, ) # Delete old entries db.execute_sql( f""" DELETE FROM {target["schema"]}.{target["table"]} WHERE "carrier" = 'H2_grid' AND scn_name = '{scn_name}' AND bus0 IN ( SELECT bus_id FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} WHERE country = 'DE' ) """ ) target = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_links"] for df in [H2_grid_Neubau, H2_grid_Umstellung, H2_grid_Erweiterung]: if df is H2_grid_Neubau: df.rename( columns={ "Planerische \nInbetriebnahme": "Planerische Inbetriebnahme" }, inplace=True, ) df.loc[ df["Endpunkt\n(Ort)"] == "AQD Anlandung", "Endpunkt\n(Ort)" ] = "Schillig" df.loc[ df["Endpunkt\n(Ort)"] == "Hallendorf", "Endpunkt\n(Ort)" ] = "Salzgitter" if df is H2_grid_Erweiterung: df.rename( columns={ "Umstellungsdatum/ Planerische Inbetriebnahme": "Planerische Inbetriebnahme", "Nenndurchmesser (DN)": "Nenndurchmesser \n(DN)", "Investitionskosten\n(Mio. Euro),\nKostenschätzung": "Investitionskosten*\n(Mio. Euro)", }, inplace=True, ) df = df[ df["Berücksichtigung im Kernnetz \n[ja/nein/zurückgezogen]"] .str.strip() .str.lower() == "ja" ] df.loc[ df["Endpunkt\n(Ort)"] == "Osdorfer Straße", "Endpunkt\n(Ort)" ] = "Berlin- Lichterfelde" h2_bus_location["Ort"] = h2_bus_location["Ort"].astype(str).str.strip() df["Anfangspunkt\n(Ort)"] = ( df["Anfangspunkt\n(Ort)"].astype(str).str.strip() ) df["Endpunkt\n(Ort)"] = df["Endpunkt\n(Ort)"].astype(str).str.strip() df = df[ [ "Anfangspunkt\n(Ort)", "Endpunkt\n(Ort)", "Nenndurchmesser \n(DN)", "Druckstufe (DP)\n[mind. 30 barg]", "Investitionskosten*\n(Mio. Euro)", "Planerische Inbetriebnahme", "Länge \n(km)", ] ] # matching start- and endpoint of each pipeline with georeferenced data df["Anfangspunkt_matched"] = fuzzy_match( df, h2_bus_location, "Anfangspunkt\n(Ort)" ) df["Endpunkt_matched"] = fuzzy_match( df, h2_bus_location, "Endpunkt\n(Ort)" ) # manuell adjustments based on Detailmaßnahmenkarte der FNB-Gas [https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/] df = fix_h2_grid_infrastructure(df) df_merged = pd.merge( df, h2_bus_location[["Ort", "geom", "x", "y"]], how="left", left_on="Anfangspunkt_matched", right_on="Ort", ).rename( columns={"geom": "geom_start", "x": "x_start", "y": "y_start"} ) df_merged = pd.merge( df_merged, h2_bus_location[["Ort", "geom", "x", "y"]], how="left", left_on="Endpunkt_matched", right_on="Ort", ).rename(columns={"geom": "geom_end", "x": "x_end", "y": "y_end"}) H2_grid_df = df_merged.dropna(subset=["geom_start", "geom_end"]) H2_grid_df = H2_grid_df[ H2_grid_df["geom_start"] != H2_grid_df["geom_end"] ] H2_grid_df = pd.merge( H2_grid_df, h2_buses_df, how="left", left_on=["x_start", "y_start"], right_on=["x", "y"], ).rename(columns={"bus_id": "bus0"}) H2_grid_df = pd.merge( H2_grid_df, h2_buses_df, how="left", left_on=["x_end", "y_end"], right_on=["x", "y"], ).rename(columns={"bus_id": "bus1"}) H2_grid_df[["bus0", "bus1"]] = H2_grid_df[["bus0", "bus1"]].astype( "Int64" ) H2_grid_df["geom_start"] = H2_grid_df["geom_start"].apply( lambda x: wkb.loads(bytes.fromhex(x)) ) H2_grid_df["geom_end"] = H2_grid_df["geom_end"].apply( lambda x: wkb.loads(bytes.fromhex(x)) ) H2_grid_df["topo"] = H2_grid_df.apply( lambda row: LineString([row["geom_start"], row["geom_end"]]), axis=1, ) H2_grid_df["geom"] = H2_grid_df.apply( lambda row: MultiLineString( [LineString([row["geom_start"], row["geom_end"]])] ), axis=1, ) H2_grid_gdf = gpd.GeoDataFrame(H2_grid_df, geometry="geom", crs=4326) scn_params = get_sector_parameters("gas", scn_name) next_link_id = db.next_etrago_id("link") H2_grid_gdf["link_id"] = range( next_link_id, next_link_id + len(H2_grid_gdf) ) H2_grid_gdf["scn_name"] = scn_name H2_grid_gdf["carrier"] = "H2_grid" H2_grid_gdf["Planerische Inbetriebnahme"] = ( H2_grid_gdf["Planerische Inbetriebnahme"] .astype(str) .apply( lambda x: ( int(re.findall(r"\d{4}", x)[-1]) if re.findall(r"\d{4}", x) else ( 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 ) ) ) ) H2_grid_gdf["build_year"] = H2_grid_gdf[ "Planerische Inbetriebnahme" ].astype("Int64") 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, ) H2_grid_gdf["p_nom_min"] = H2_grid_gdf["p_nom"] H2_grid_gdf["p_nom_max"] = float("Inf") H2_grid_gdf["p_nom_extendable"] = False H2_grid_gdf["lifetime"] = scn_params["lifetime"]["H2_pipeline"] H2_grid_gdf["capital_cost"] = H2_grid_gdf.apply( lambda row: annualize_capital_costs( ( ( float(row["Investitionskosten*\n(Mio. Euro)"]) * 10**6 / row["p_nom"] ) if pd.notna(row["Investitionskosten*\n(Mio. Euro)"]) and str(row["Investitionskosten*\n(Mio. Euro)"]) .replace(",", "") .replace(".", "") .isdigit() and float(row["Investitionskosten*\n(Mio. Euro)"]) != 0 else scn_params["overnight_cost"]["H2_pipeline"] * row["Länge \n(km)"] ), row["lifetime"], 0.05, ), axis=1, ) H2_grid_gdf["p_min_pu"] = -1 selected_columns = [ "scn_name", "link_id", "bus0", "bus1", "build_year", "p_nom", "p_nom_min", "p_nom_extendable", "capital_cost", "geom", "topo", "carrier", "p_nom_max", "p_min_pu", ] H2_grid_final = H2_grid_gdf[selected_columns] # Insert data to db H2_grid_final.to_postgis( target["table"], con, schema=target["schema"], if_exists="append", dtype={"geom": Geometry()}, ) # connect saltcaverns to H2_grid connect_saltcavern_to_h2_grid(scn_name) # connect neighbour countries to H2_grid connect_h2_grid_to_neighbour_countries(scn_name)
[docs]def replace_pipeline(df, start, end, intermediate): """ Method for adjusting pipelines manually by splittiing pipeline with an intermediate point. Parameters ---------- df : pandas.core.frame.DataFrame dataframe to be adjusted start: str startpoint of pipeline end: str endpoint of pipeline intermediate: str new intermediate point for splitting given pipeline Returns --------- df : <class 'pandas.core.frame.DataFrame'> adjusted dataframe """ # Find rows where the start and end points match mask = ( (df["Anfangspunkt_matched"] == start) & (df["Endpunkt_matched"] == end) ) | ( (df["Anfangspunkt_matched"] == end) & (df["Endpunkt_matched"] == start) ) # Separate the rows to replace if mask.any(): df_replacement = df[~mask].copy() row_replaced = df[mask].iloc[0] # Add new rows for the split pipelines new_rows = pd.DataFrame( { "Anfangspunkt_matched": [start, intermediate], "Endpunkt_matched": [intermediate, end], "Nenndurchmesser \n(DN)": [ row_replaced["Nenndurchmesser \n(DN)"], row_replaced["Nenndurchmesser \n(DN)"], ], "Druckstufe (DP)\n[mind. 30 barg]": [ row_replaced["Druckstufe (DP)\n[mind. 30 barg]"], row_replaced["Druckstufe (DP)\n[mind. 30 barg]"], ], "Investitionskosten*\n(Mio. Euro)": [ row_replaced["Investitionskosten*\n(Mio. Euro)"], row_replaced["Investitionskosten*\n(Mio. Euro)"], ], "Planerische Inbetriebnahme": [ row_replaced["Planerische Inbetriebnahme"], row_replaced["Planerische Inbetriebnahme"], ], "Länge \n(km)": [ row_replaced["Länge \n(km)"], row_replaced["Länge \n(km)"], ], } ) df_replacement = pd.concat( [df_replacement, new_rows], ignore_index=True ) return df_replacement else: return df
[docs]def fuzzy_match(df1, df2, column_to_match, threshold=80): """ Method for matching input data of H2_grid with georeferenced data (even if the strings are not exact the same) Parameters ---------- df1 : pandas.core.frame.DataFrame Input dataframe df2 : pandas.core.frame.DataFrame georeferenced dataframe with h2_buses column_to_match: str matching column treshhold: float matching percentage for succesfull comparison Returns --------- matched : list list with all matched location names """ options = df2["Ort"].unique() matched = [] # Compare every locationname in df1 with locationnames in df2 for value in df1[column_to_match]: match, score = process.extractOne(value, options) if score >= threshold: matched.append(match) else: matched.append(None) return matched
[docs]def calculate_H2_capacity(pressure, diameter): """ Method for calculagting capacity of pipelines based on data input from FNB Gas Parameters ---------- pressure : float input for pressure of pipeline diameter: float input for diameter of pipeline column_to_match: str matching column treshhold: float matching percentage for succesfull comparison Returns --------- energy_flow: float transmission capacity of pipeline """ pressure = str(pressure).replace(",", ".") diameter = str(diameter) def convert_to_float(value): try: return float(value) except ValueError: return 400 # average value from data-source cause capacities of some lines are not fixed yet # in case of given range for pipeline-capacity calculate average value if "-" in diameter: diameters = diameter.split("-") diameter = ( convert_to_float(diameters[0]) + convert_to_float(diameters[1]) ) / 2 elif "/" in diameter: diameters = diameter.split("/") diameter = ( convert_to_float(diameters[0]) + convert_to_float(diameters[1]) ) / 2 else: try: diameter = float(diameter) except ValueError: diameter = 400 # average value from data-source if "-" in pressure: pressures = pressure.split("-") pressure = (float(pressures[0]) + float(pressures[1])) / 2 elif "/" in pressure: pressures = pressure.split("/") pressure = (float(pressures[0]) + float(pressures[1])) / 2 else: try: pressure = float(diameter) except ValueError: pressure = 70 # averaqge value from data-source velocity = 40 # source: L.Koops (2023): GAS PIPELINE VERSUS LIQUID HYDROGEN TRANSPORT – PERSPECTIVES FOR TECHNOLOGIES, ENERGY DEMAND ANDv TRANSPORT CAPACITY, AND IMPLICATIONS FOR AVIATION 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 density = ( pressure * 10**5 / (4.1243 * 10**3 * temperature) ) # gasconstant H2 = 4.1243 [kJ/kgK] mass_flow = density * math.pi * ((diameter / 10**3) / 2) ** 2 * velocity energy_flow = mass_flow * 119.988 # low_heating_value H2 = 119.988 [MJ/kg] return energy_flow
[docs]def download_h2_grid_data(): """ Download Input data for H2_grid from FNB-Gas (https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/) The following data for H2 are downloaded into the folder ./datasets/h2_data: * Links (file Anlage_3_Wasserstoffkernnetz_Neubau.xlsx, Anlage_4_Wasserstoffkernnetz_Umstellung.xlsx, Anlage_2_Wasserstoffkernetz_weitere_Leitungen.xlsx) Returns ------- None """ path = Path("datasets/h2_data") os.makedirs(path, exist_ok=True) download_config = config.datasets()["etrago_hydrogen"]["sources"][ "H2_grid" ] target_file_Um = path / download_config["converted_ch4_pipes"]["path"] target_file_Neu = path / download_config["new_constructed_pipes"]["path"] target_file_Erw = ( path / download_config["pipes_of_further_h2_grid_operators"]["path"] ) for target_file in [target_file_Neu, target_file_Um, target_file_Erw]: if target_file is target_file_Um: url = download_config["converted_ch4_pipes"]["url"] elif target_file is target_file_Neu: url = download_config["new_constructed_pipes"]["url"] else: url = download_config["pipes_of_further_h2_grid_operators"]["url"] if not os.path.isfile(target_file): urlretrieve(url, target_file)
[docs]def read_h2_excel_sheets(): """ Read downloaded excel files with location names for future h2-pipelines Returns ------- df_Neu : <class 'pandas.core.frame.DataFrame'> df_Um : <class 'pandas.core.frame.DataFrame'> df_Erw : <class 'pandas.core.frame.DataFrame'> """ path = Path(".") / "datasets" / "h2_data" download_config = config.datasets()["etrago_hydrogen"]["sources"][ "H2_grid" ] excel_file_Um = pd.ExcelFile( f'{path}/{download_config["converted_ch4_pipes"]["path"]}' ) excel_file_Neu = pd.ExcelFile( f'{path}/{download_config["new_constructed_pipes"]["path"]}' ) excel_file_Erw = pd.ExcelFile( f'{path}/{download_config["pipes_of_further_h2_grid_operators"]["path"]}' ) df_Um = pd.read_excel(excel_file_Um, header=3) df_Neu = pd.read_excel(excel_file_Neu, header=3) df_Erw = pd.read_excel(excel_file_Erw, header=2) return df_Neu, df_Um, df_Erw
[docs]def fix_h2_grid_infrastructure(df): """ Manuell adjustments for more accurate grid topology based on Detailmaßnahmenkarte der FNB-Gas [https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/] Returns ------- df : <class 'pandas.core.frame.DataFrame'> """ df = replace_pipeline(df, "Lubmin", "Uckermark", "Wrangelsburg") df = replace_pipeline(df, "Wrangelsburg", "Uckermark", "Schönermark") df = replace_pipeline( df, "Hemmingstedt", "Ascheberg (Holstein)", "Remmels Nord" ) df = replace_pipeline(df, "Heidenau", "Elbe-Süd", "Weißenfelde") df = replace_pipeline(df, "Weißenfelde", "Elbe-Süd", "Stade") df = replace_pipeline(df, "Stade AOS", "KW Schilling", "Abzweig Stade") df = replace_pipeline(df, "Rosengarten (Sottorf)", "Moorburg", "Leversen") df = replace_pipeline(df, "Leversen", "Moorburg", "Hamburg Süd") df = replace_pipeline(df, "Achim", "Folmhusen", "Wardenburg") df = replace_pipeline(df, "Achim", "Wardenburg", "Sandkrug") df = replace_pipeline(df, "Dykhausen", "Bunde", "Emden") df = replace_pipeline(df, "Emden", "Nüttermoor", "Jemgum") df = replace_pipeline(df, "Rostock", "Glasewitz", "Fliegerhorst Laage") df = replace_pipeline(df, "Wilhelmshaven", "Dykhausen", "Sande") df = replace_pipeline( df, "Wilhelmshaven Süd", "Wilhelmshaven Nord", "Wilhelmshaven" ) df = replace_pipeline(df, "Sande", "Jemgum", "Westerstede") df = replace_pipeline(df, "Kalle", "Ochtrup", "Frensdorfer Bruchgraben") df = replace_pipeline( df, "Frensdorfer Bruchgraben", "Ochtrup", "Bad Bentheim" ) df = replace_pipeline(df, "Bunde", "Wettringen", "Emsbüren") df = replace_pipeline(df, "Emsbüren", "Dorsten", "Ochtrup") df = replace_pipeline(df, "Ochtrup", "Dorsten", "Heek") df = replace_pipeline(df, "Lemförde", "Drohne", "Reiningen") df = replace_pipeline(df, "Edesbüttel", "Bobbau", "Uhrsleben") df = replace_pipeline(df, "Sixdorf", "Wiederitzsch", "Cörmigk") df = replace_pipeline(df, "Schkeuditz", "Plaußig", "Wiederitzsch") df = replace_pipeline(df, "Wiederitzsch", "Plaußig", "Mockau Nord") df = replace_pipeline(df, "Bobbau", "Rückersdorf", "Nempitz") df = replace_pipeline(df, "Räpitz", "Böhlen", "Kleindalzig") df = replace_pipeline(df, "Buchholz", "Friedersdorf", "Werben") df = replace_pipeline(df, "Radeland", "Uckermark", "Friedersdorf") df = replace_pipeline(df, "Friedersdorf", "Uckermark", "Herzfelde") df = replace_pipeline(df, "Blumberg", "Berlin-Mitte", "Berlin-Marzahn") df = replace_pipeline(df, "Radeland", "Zethau", "Coswig") df = replace_pipeline(df, "Leuna", "Böhlen", "Räpitz") df = replace_pipeline(df, "Dürrengleina", "Stadtroda", "Zöllnitz") df = replace_pipeline(df, "Mailing", "Kötz", "Wertingen") df = replace_pipeline(df, "Lampertheim", "Rüsselsheim", "Gernsheim-Nord") df = replace_pipeline(df, "Birlinghoven", "Rüsselsheim", "Wiesbaden") df = replace_pipeline(df, "Medelsheim", "Mittelbrunn", "Seyweiler") df = replace_pipeline(df, "Seyweiler", "Dillingen", "Fürstenhausen") df = replace_pipeline(df, "Reckrod", "Wolfsbehringen", "Eisenach") df = replace_pipeline(df, "Elten", "St. Hubert", "Hüthum") df = replace_pipeline(df, "St. Hubert", "Hüthum", "Uedener Bruch") df = replace_pipeline(df, "Wallach", "Möllen", "Spellen") df = replace_pipeline(df, "St. Hubert", "Glehn", "Krefeld") df = replace_pipeline(df, "Neumühl", "Werne", "Bottrop") df = replace_pipeline(df, "Bottrop", "Werne", "Recklinghausen") df = replace_pipeline(df, "Werne", "Eisenach", "Arnsberg-Bruchhausen") df = replace_pipeline(df, "Dorsten", "Gescher", "Gescher Süd") df = replace_pipeline(df, "Dorsten", "Hamborn", "Averbruch") df = replace_pipeline(df, "Neumühl", "Bruckhausen", "Hamborn") df = replace_pipeline(df, "Werne", "Paffrath", "Westhofen") df = replace_pipeline(df, "Glehn", "Voigtslach", "Dormagen") df = replace_pipeline(df, "Voigtslach", "Paffrath", "Leverkusen") df = replace_pipeline(df, "Glehn", "Ludwigshafen", "Wesseling") df = replace_pipeline(df, "Rothenstadt", "Rimpar", "Reutles") return df
[docs]def connect_saltcavern_to_h2_grid(scn_name): """ Connect each saltcavern with nearest H2-Bus of the H2-Grid and insert the links into the database Returns ------- None """ targets = config.datasets()["etrago_hydrogen"]["targets"] sources = config.datasets()["etrago_hydrogen"]["sources"] engine = db.engine() db.execute_sql( f""" DELETE FROM {targets["hydrogen_links"]["schema"]}.{targets["hydrogen_links"]["table"]} WHERE "carrier" in ('H2_saltcavern') AND scn_name = '{scn_name}'; """ ) h2_buses_query = f"""SELECT bus_id, x, y,ST_Transform(geom, 32632) as geom FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} WHERE carrier = 'H2_grid' AND scn_name = '{scn_name}' """ h2_buses = gpd.read_postgis(h2_buses_query, engine) salt_caverns_query = f"""SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} WHERE carrier = 'H2_saltcavern' AND scn_name = '{scn_name}' """ salt_caverns = gpd.read_postgis(salt_caverns_query, engine) max_link_id = db.next_etrago_id("link") next_link_id = count(start=max_link_id, step=1) scn_params = get_sector_parameters("gas", scn_name) H2_coords = np.array([(point.x, point.y) for point in h2_buses.geometry]) H2_tree = cKDTree(H2_coords) links = [] for idx, bus_saltcavern in salt_caverns.iterrows(): saltcavern_coords = [ bus_saltcavern["geom"].x, bus_saltcavern["geom"].y, ] dist, nearest_idx = H2_tree.query(saltcavern_coords, k=1) nearest_h2_bus = h2_buses.iloc[nearest_idx] link = { "scn_name": scn_name, "bus0": nearest_h2_bus["bus_id"], "bus1": bus_saltcavern["bus_id"], "link_id": next(next_link_id), "carrier": "H2_saltcavern", "lifetime": 25, "p_nom_extendable": True, "p_min_pu": -1, "capital_cost": scn_params["overnight_cost"]["H2_pipeline"] * dist / 1000, "geom": MultiLineString( [ LineString( [ (nearest_h2_bus["x"], nearest_h2_bus["y"]), (bus_saltcavern["x"], bus_saltcavern["y"]), ] ) ] ), } links.append(link) links_df = gpd.GeoDataFrame(links, geometry="geom", crs=4326) links_df.to_postgis( targets["hydrogen_links"]["table"], engine, schema=targets["hydrogen_links"]["schema"], index=False, if_exists="append", dtype={"geom": Geometry()}, )
[docs]def connect_h2_grid_to_neighbour_countries(scn_name): """ Connect germand H2_grid with neighbour countries. All german H2-Buses wich were planned as connection points for Import/Export of Hydrogen to corresponding neighbours country, are based on Publication of FNB-GAS (https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/). Returns ------- None """ engine = db.engine() targets = config.datasets()["etrago_hydrogen"]["targets"] sources = config.datasets()["etrago_hydrogen"]["sources"] h2_buses_df = gpd.read_postgis( f""" SELECT bus_id, x, y, geom FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} WHERE carrier in ('H2_grid') AND scn_name = '{scn_name}' """, engine, ) h2_links_df = pd.read_sql( f""" SELECT link_id, bus0, bus1, p_nom FROM {sources["links"]["schema"]}.{sources["links"]["table"]} WHERE carrier in ('H2_grid') AND scn_name = '{scn_name}' """, engine, ) abroad_buses_df = gpd.read_postgis( f""" SELECT bus_id, x, y, geom, country FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} WHERE carrier = 'H2' AND scn_name = '{scn_name}' AND country != 'DE' """, engine, ) abroad_con_buses = [ ("Greifenhagen", "PL"), ("Fürstenberg (PL)", "PL"), ("Eynatten", "BE"), ("Überackern", "AT"), ("Vlieghuis", "NL"), ("Oude", "NL"), ("Oude Statenzijl", "NL"), ("Vreden", "NL"), ("Elten", "NL"), ("Leidingen", "FR"), ("Carling", "FR"), ("Medelsheim", "FR"), ("Waidhaus", "CZ"), ("Deutschneudorf", "CZ"), ("Grenzach", "CH"), ("AWZ", "DK"), ("AWZ", "SE"), ("AQD Offshore SEN 1", "GB"), ("AQD Offshore SEN 1", "NO"), ("AQD Offshore SEN 1", "DK"), ("AQD Offshore SEN 1", "NL"), ("Fessenheim", "FR"), ("Ellund", "DK"), ] h2_bus_location = pd.read_csv( Path(".") / "data_bundle_egon_data" / "hydrogen_network" / "h2_grid_nodes.csv" ) ### prepare data for connecting abroad_buses matched_locations = h2_bus_location[ h2_bus_location["Ort"].isin([name for name, _ in abroad_con_buses]) ] matched_buses = matched_locations.merge( h2_buses_df, left_on=["x", "y"], right_on=["x", "y"], how="inner" ) final_matched_buses = matched_buses[ ["bus_id", "Ort", "x", "y", "geom_y"] ].rename(columns={"geom_y": "geom"}) 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"])) ] abroad_links_bus0 = abroad_links.merge( final_matched_buses, left_on="bus0", right_on="bus_id", how="inner" ) abroad_links_bus1 = abroad_links.merge( final_matched_buses, left_on="bus1", right_on="bus_id", how="inner" ) abroad_con_df = pd.concat([abroad_links_bus1, abroad_links_bus0]) connection_links = [] max_link_id = db.next_etrago_id("link") next_max_link_id = count(start=max_link_id, step=1) for inland_name, country_code in abroad_con_buses: # filter out germand h2_buses for connecting neighbour-countries inland_bus = abroad_con_df[abroad_con_df["Ort"] == inland_name] if inland_bus.empty: print(f"Warning: No Inland-Bus found for {inland_name}.") continue # filter out corresponding abroad_bus for connecting neighbour countries abroad_bus = abroad_buses_df[ abroad_buses_df["country"] == country_code ] if abroad_bus.empty: print(f"Warning: No Abroad-Bus found for {country_code}.") continue for _, i_bus in inland_bus.iterrows(): abroad_bus["distance"] = abroad_bus["geom"].apply( lambda g: i_bus["geom"].distance(g) ) nearest_abroad_bus = abroad_bus.loc[ abroad_bus["distance"].idxmin() ] relevant_buses = inland_bus[ inland_bus["bus_id"] == i_bus["bus_id"] ] p_nom_value = relevant_buses["p_nom"].sum() connection_links.append( { "scn_name": scn_name, "carrier": "H2_grid", "link_id": next(next_max_link_id), "bus0": i_bus["bus_id"], "bus1": nearest_abroad_bus["bus_id"], "p_nom": p_nom_value, "p_min_pu": -1, "geom": MultiLineString( [ LineString( [ (i_bus["geom"].x, i_bus["geom"].y), ( nearest_abroad_bus["geom"].x, nearest_abroad_bus["geom"].y, ), ] ) ] ), } ) connection_links_df = gpd.GeoDataFrame( connection_links, geometry="geom", crs="EPSG:4326" ) connection_links_df.to_postgis( name=targets["hydrogen_links"]["table"], con=engine, schema=targets["hydrogen_links"]["schema"], if_exists="append", index=False, ) print("Neighbour countries are succesfully connected to H2-grid")