From a9855c8828e9930115fa7d886d958756341163f7 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Mon, 9 Sep 2024 16:18:02 +0200 Subject: [PATCH 01/13] Insert resulting data for CTS and industry demands for scenario eGon2035 The data will be part of the powerd-data-bundle. --- src/egon/data/datasets/demandregio/__init__.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/egon/data/datasets/demandregio/__init__.py b/src/egon/data/datasets/demandregio/__init__.py index 7879a268f..c8dd93b74 100644 --- a/src/egon/data/datasets/demandregio/__init__.py +++ b/src/egon/data/datasets/demandregio/__init__.py @@ -631,9 +631,25 @@ def insert_cts_ind(scenario, year, engine, target_values): "targets" ] + # Workaround: Since the disaggregator does not work anymore, data from + # previous runs is used for eGon2035 and eGon100RE + if scenario == "eGon2035": + ec_cts_ind2 = pd.read_csv( + "data_bundle_powerd_data/egon_demandregio_cts_ind_egon2035.csv" + ) + ec_cts_ind2.to_sql( + targets["cts_ind_demand"]["table"], + engine, + targets["cts_ind_demand"]["schema"], + if_exists="append", + index=False, + ) + return + if scenario == "eGon100RE": ec_cts_ind2 = pd.read_csv( - "data_bundle_powerd_data/egon_demandregio_cts_ind.csv") + "data_bundle_powerd_data/egon_demandregio_cts_ind.csv" + ) ec_cts_ind2.to_sql( targets["cts_ind_demand"]["table"], engine, From 5f7ba298b471858a5f130e99c07de30df70ae389 Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Mon, 23 Sep 2024 13:48:30 +0100 Subject: [PATCH 02/13] Insertion of H2-grid based on the Wasserstoffkernetz published by FNB-Gas --- setup.py | 1 + src/egon/data/datasets.yml | 10 + .../data/datasets/hydrogen_etrago/__init__.py | 29 +- src/egon/data/datasets/hydrogen_etrago/bus.py | 91 ++- .../data/datasets/hydrogen_etrago/h2_grid.py | 645 +++++++++++++----- .../datasets/hydrogen_etrago/h2_to_ch4.py | 197 +++--- 6 files changed, 690 insertions(+), 283 deletions(-) diff --git a/setup.py b/setup.py index fe2be13b4..b85864409 100755 --- a/setup.py +++ b/setup.py @@ -122,6 +122,7 @@ def read(*names, **kwargs): "wtforms", "xarray", "xlrd", + "fuzzywuzzy", ], extras_require={ "dev": ["black", "flake8", "isort>=5", "pre-commit", "pytest", "tox"] diff --git a/src/egon/data/datasets.yml b/src/egon/data/datasets.yml index f0410f9de..eb8843093 100644 --- a/src/egon/data/datasets.yml +++ b/src/egon/data/datasets.yml @@ -685,6 +685,16 @@ etrago_hydrogen: H2_AC_map: schema: 'grid' table: 'egon_etrago_ac_h2' + H2_grid: + new_constructed_pipes: + url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage3_FNB_Massnahmenliste_Neubau.xlsx' + path: "Anlage_3_Wasserstoffkernnetz_Neubau.xlsx" + converted_ch4_pipes: + url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage4_FNB_Massnahmenliste_Umstellung.xlsx" + path: "Anlage_4_Wasserstoffkernnetz_Umstellung.xlsx" + pipes_of_further_h2_grid_operators: + url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage2_Leitungsmeldungen_weiterer_potenzieller_Wasserstoffnetzbetreiber.xlsx" + path: "Anlage_2_Wasserstoffkernetz_weitere_Leitungen.xlsx" targets: hydrogen_buses: schema: 'grid' diff --git a/src/egon/data/datasets/hydrogen_etrago/__init__.py b/src/egon/data/datasets/hydrogen_etrago/__init__.py index d4f3e5d68..1cb694cec 100755 --- a/src/egon/data/datasets/hydrogen_etrago/__init__.py +++ b/src/egon/data/datasets/hydrogen_etrago/__init__.py @@ -28,6 +28,7 @@ insert_H2_overground_storage, insert_H2_saltcavern_storage, ) +from egon.data import config class HydrogenBusEtrago(Dataset): @@ -62,11 +63,12 @@ def __init__(self, dependencies): dependencies=dependencies, tasks=( calculate_and_map_saltcavern_storage_potential, - insert_hydrogen_buses, + insert_h2_buses_for_scn, ), ) + class HydrogenStoreEtrago(Dataset): """ Insert the H2 stores into the database for Germany @@ -185,10 +187,10 @@ def __init__(self, dependencies): class HydrogenGridEtrago(Dataset): """ - Insert the H2 grid in Germany into the database for eGon100RE + Insert the H2 grid in Germany into the database for eGon2035 and eGon100RE Insert the H2 links (pipelines) into Germany in the database for the - scenario eGon100RE by executing the function + scenario eGon2035/eGon100RE by executing the function :py:func:`insert_h2_pipelines `. *Dependencies* @@ -210,10 +212,29 @@ class HydrogenGridEtrago(Dataset): #: version: str = "0.0.2" + def __init__(self, dependencies): super().__init__( name=self.name, version=self.version, dependencies=dependencies, - tasks=(insert_h2_pipelines,), + tasks = insert_h2_pipelines_for_scn, ) + +def insert_h2_pipelines_for_scn(): + scenarios = config.settings()["egon-data"]["--scenarios"] + + if "eGon2035" in scenarios: + insert_h2_pipelines("eGon2035") + + if "eGon100RE" in scenarios: + insert_h2_pipelines("eGon100RE") + +def insert_h2_buses_for_scn(): + scenarios = config.settings()["egon-data"]["--scenarios"] + + if "eGon2035" in scenarios: + insert_hydrogen_buses("eGon2035") + + if "eGon100RE" in scenarios: + insert_hydrogen_buses("eGon100RE") \ No newline at end of file diff --git a/src/egon/data/datasets/hydrogen_etrago/bus.py b/src/egon/data/datasets/hydrogen_etrago/bus.py index da92c6939..3d3e0224c 100755 --- a/src/egon/data/datasets/hydrogen_etrago/bus.py +++ b/src/egon/data/datasets/hydrogen_etrago/bus.py @@ -15,29 +15,100 @@ """ from geoalchemy2 import Geometry +from pathlib import Path +import pandas as pd +import geopandas as gpd +from shapely.wkb import loads +import numpy as np +from scipy.spatial import cKDTree from egon.data import config, db from egon.data.datasets.etrago_helpers import ( - copy_and_modify_buses, finalize_bus_insertion, initialise_bus_insertion, ) -def insert_hydrogen_buses(): +def insert_hydrogen_buses(scn_name): """ Insert hydrogen buses into the database (in etrago table) - Hydrogen buses are inserted into the database using the functions: - * :py:func:`insert_H2_buses_from_CH4_grid` for H2 buses - * :py:func:`insert_H2_buses_from_saltcavern` for the H2_saltcavern - buses Parameters ---------- No parameter is required. """ + + h2_input= pd.read_csv(Path(".")/"h2_grid_nodes.csv") # (.) verweist immer auf das repository in dem egon-dat serve ausgeführt wird. + #in meinem Fall csv file in airflow_exe + h2_input.geom = h2_input.geom.apply(lambda wkb_hex: loads(bytes.fromhex(wkb_hex))) + + target_buses = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_buses"] + h2_buses = initialise_bus_insertion('H2', target_buses, scenario=scn_name) + + h2_buses.x = h2_input.x + h2_buses.y = h2_input.y + h2_buses.geom = h2_input.geom + h2_buses.carrier = 'H2_grid' + next_bus_id = db.next_etrago_id('bus') + h2_buses.bus_id= range(next_bus_id, next_bus_id + len(h2_input)) + + # Insert data to db + h2_buses.to_postgis( + target_buses["table"], + schema= target_buses["schema"], + if_exists="append", + con=db.engine(), + dtype={"geom": Geometry()} + ) + + #insert additional_buses for potential Methanisation to CH4_buses nearby: + h2_buses = h2_buses.to_crs(epsg=32632) + + sql_CH4_buses = f""" + SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom + FROM {target_buses["schema"]}.{target_buses["table"]} + WHERE carrier = 'CH4' + AND scn_name = {scn_name} AND country = 'DE' + """ + CH4_buses = gpd.read_postgis(sql_CH4_buses, con=db.engine) + + additional_H2_buses = [] + H2_coords = np.array([(point.x, point.y) for point in h2_buses.geometry]) + H2_tree = cKDTree(H2_coords) + for idx, ch4_bus in CH4_buses.iterrows(): + ch4_coords = [ch4_bus['geom'].x, ch4_bus['geom'].y] + + # filter nearest h2_bus + dist, nearest_idx = H2_tree.query(ch4_coords, k=1) + # critcical distance assumed with 10km based on former ammount of h2_buses + if dist > 10000: + # Neuen H2-Bus hinzufügen + additional_H2_buses.append({ + 'scn_name': 'eGon2025', + 'bus_id': None, + 'x': ch4_bus['x'], + 'y': ch4_bus['y'], + 'carrier': 'H2_grid', + 'geom': ch4_bus['geom'] + }) + + if additional_H2_buses: + additional_H2_buses = gpd.GeoDataFrame(additional_H2_buses, geometry='geom', crs=CH4_buses.crs) + additional_H2_buses =additional_H2_buses.to_crs(epsg=4326) + + next_bus_id = db.next_etrago_id('bus') + additional_H2_buses['bus_id'] = range(next_bus_id, next_bus_id + len(additional_H2_buses)) + # Insert data to db + additional_H2_buses.to_postgis( + target_buses["buses"]["table"], + schema= target_buses["buses"]["schema"], + con=db.engine(), + if_exists="append", + dtype={"geom": Geometry()} + ) + s = config.settings()["egon-data"]["--scenarios"] scn = [] if "eGon2035" in s: @@ -50,19 +121,13 @@ def insert_hydrogen_buses(): target = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_buses"] # initalize dataframe for hydrogen buses carrier = "H2_saltcavern" - hydrogen_buses = initialise_bus_insertion( + hydrogen_buses = initialise_bus_insertion( carrier, target, scenario=scenario ) insert_H2_buses_from_saltcavern( hydrogen_buses, carrier, sources, target, scenario ) - carrier = "H2" - hydrogen_buses = initialise_bus_insertion( - carrier, target, scenario=scenario - ) - insert_H2_buses_from_CH4_grid(hydrogen_buses, carrier, target, scenario) - def insert_H2_buses_from_saltcavern(gdf, carrier, sources, target, scn_name): """ diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index eac0133b3..3cad81ee3 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -12,177 +12,494 @@ """ from geoalchemy2.types import Geometry -from shapely.geometry import MultiLineString +from shapely.geometry import LineString, MultiLineString, Point import geopandas as gpd +import pandas as pd +import os +from urllib.request import urlretrieve +from pathlib import Path +from fuzzywuzzy import process +from shapely import wkb +import math -from egon.data import db -from egon.data.datasets.etrago_setup import link_geom_from_buses +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 -def insert_h2_pipelines(): +def insert_h2_pipelines(scn_name): + "Insert H2_grid based on Input Data from FNB-Gas" + + H2_grid_Neubau, H2_grid_Umstellung, H2_grid_Erweiterung = read_h2_excel_sheets() + h2_bus_location = pd.read_csv(Path(".")/"h2_grid_nodes.csv") + con=db.engine() + + h2_buses_df = pd.read_sql( + f""" + SELECT bus_id, x, y FROM grid.egon_etrago_bus + WHERE carrier in ('H2_grid') + AND scn_name = {scn_name} + """ - Insert hydrogen grid (H2 links) into the database for eGon100RE. - - Insert the H2 grid by executing the following steps: - * Copy the CH4 links in Germany from eGon100RE - * Overwrite the followings columns: - * bus0 and bus1 using the grid.egon_etrago_ch4_h2 table - * carrier, scn_name - * p_nom: the value attributed there corresponds to the share - of p_nom of the specific pipe that could be retrofited into - H2 pipe. This share is the same for every pipeline and is - calculated in the PyPSA-eur-sec run. - * Create new extendable pipelines to link the existing grid to the - H2_saltcavern buses - * Clean database - * Attribute link_id to the links - * Insert the into the database - - This function inserts data into the database and has no return. + , con) + + + 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' + + 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)']] + + # manuell adjustments based in Detailmaßnahmenkarte der FNB-Gas [https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/] + df= fix_h2_grid_infrastructure(df) + + # matching start- and endpoint of each pipeline with georeferenced data + df['Anfangspunkt\n(Ort)_matched'] = fuzzy_match(df, h2_bus_location, 'Anfangspunkt\n(Ort)') + df['Endpunkt\n(Ort)_matched'] = fuzzy_match(df, h2_bus_location, 'Endpunkt\n(Ort)') + + df_merged = pd.merge(df, h2_bus_location[['Ort', 'geom', 'x', 'y']], + how='left', left_on='Anfangspunkt\n(Ort)_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\n(Ort)_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) + H2_grid_gdf['build_year'] = H2_grid_gdf['Planerische Inbetriebnahme'].apply(lambda x: x.split('/')[1] if '/' in x else None) + 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'] = True + 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() + 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', 'p_nom', 'p_nom_min', + 'p_nom_extendable', 'capital_cost', 'geom', 'topo', 'carrier', 'p_nom_max', 'p_min_pu', + ] + + # Delete old entries + db.execute_sql( + f""" + DELETE FROM grid.egon_etrago_link + WHERE "carrier" = 'H2_grid' + AND scn_name = {scn_name} + """ + ) + + 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()}, + ) + + H2_grid_gdf = H2_grid_gdf.to_crs(epsg=32632) + + H2_grid_gdf['topo'] = gpd.GeoSeries(H2_grid_gdf['topo'], crs='EPSG:4326').to_crs(epsg=32632) + + #toDo: evtl. löschen der link_ids welche ersetzt werden und gefunden werden, methodik muss noch angepasst werden + if df is H2_grid_Umstellung: + deleting_ids=[] + for index, row in H2_grid_gdf.iterrows(): + filtered_link_id = replace_transformed_CH4_pipelines(row['topo'], scn_name) + matching_link = row['link_id'] + if not pd.isna(filtered_link_id): + deleting_ids.append([filtered_link_id, matching_link]) + + +def replace_transformed_CH4_pipelines(pipeline, scn_name): + """ + Delete CH4_pipelines of datamodel, which will be converted to H2_pipelines + + + Parameters + ---------- + df : geopandas dataframe + + Returns + ------- + None. + + """ + con=db.engine() + + sql_CH4_links = f""" + SELECT link_id, topo::geometry AS geom + FROM grid.egon_etrago_link + WHERE carrier = 'CH4' + AND scn_name = '{scn_name}' + """ + CH4_links = gpd.read_postgis(sql_CH4_links, con, geom_col='geom') + CH4_links = CH4_links.to_crs(epsg=32632) + + max_distance = 10000 # in Meter + + pipeline_start = Point(pipeline.coords[0]) + pipeline_end = Point(pipeline.coords[-1]) + + def calculate_distances(geom, max_distance): + # Extrahiere Start- und Endpunkte der Leitung + link_start = Point(geom.coords[0]) + link_end = Point(geom.coords[-1]) + + # calculate all distances in case of different definitions of start- and endpoint + dist_start_to_link_start = pipeline_start.distance(link_start) + dist_start_to_link_end = pipeline_start.distance(link_end) + dist_end_to_link_start = pipeline_end.distance(link_start) + dist_end_to_link_end = pipeline_end.distance(link_end) + + start_matches = [dist_start_to_link_start, dist_end_to_link_end] + end_matches = [dist_start_to_link_end, dist_end_to_link_start] + + if all(d <= max_distance for d in start_matches): + bolean_start_match = True + else: + bolean_start_match = False + + if all(d <= max_distance for d in end_matches): + bolean_start_end = True + else: + bolean_start_end = False + + if bolean_start_end or bolean_start_match: + start_match_dist = sum(start_matches) + end_match_dist = sum(end_matches) + return min(start_match_dist, end_match_dist) + + CH4_links['total_distance'] = CH4_links['geom'].apply(lambda geom: calculate_distances(geom, max_distance)) + filtered_CH4_links = CH4_links.dropna(subset=['total_distance']) + # Finde den Link mit der geringsten addierten Distanz + if not filtered_CH4_links.empty: + closest_link = filtered_CH4_links.loc[filtered_CH4_links['total_distance'].idxmin()] + closest_link_id = closest_link['link_id'] + return closest_link_id, + + + +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 : + adjusted dataframe + + + """ + # Find rows where the start and end points match + mask = ((df['Anfangspunkt\n(Ort)'] == start) & (df['Endpunkt\n(Ort)'] == end)) | \ + ((df['Anfangspunkt\n(Ort)'] == end) & (df['Endpunkt\n(Ort)'] == start)) + + # Separate the rows to replace + if mask.any(): + df_replacement = df[~mask].copy() + row_replaced = df[mask].iloc[0] + print('replacment:', df_replacement) + + # Add new rows for the split pipelines + new_rows = pd.DataFrame({ + 'Anfangspunkt\n(Ort)': [start, intermediate], + 'Endpunkt\n(Ort)': [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 + + + +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 + + +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 = 20 + temperature = 20+273.15 + density = pressure*10**5/(4.1243*10**3*temperature) #gaskonstant 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 + + +def download_h2_grid_data(): """ - H2_buses = db.select_geodataframe( - f""" - SELECT * FROM grid.egon_etrago_bus WHERE scn_name = 'eGon100RE' AND - carrier IN ('H2', 'H2_saltcavern') and country = 'DE' - """, - epsg=4326, - ) - - pipelines = db.select_geodataframe( - f""" - SELECT * FROM grid.egon_etrago_link - WHERE scn_name = 'eGon100RE' AND carrier = 'CH4' - AND bus0 IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = 'eGon100RE' AND country = 'DE' - ) AND bus1 IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = 'eGon100RE' AND country = 'DE' - ); - """ - ) - - CH4_H2_busmap = db.select_dataframe( - f""" - SELECT * FROM grid.egon_etrago_ch4_h2 WHERE scn_name = 'eGon100RE' - """, - index_col="bus_CH4", - ) - - scn_params = get_sector_parameters("gas", "eGon100RE") - - pipelines["carrier"] = "H2_retrofit" - pipelines["p_nom"] *= ( - scn_params["retrofitted_CH4pipeline-to-H2pipeline_share"] - ) - # map pipeline buses - pipelines["bus0"] = CH4_H2_busmap.loc[pipelines["bus0"], "bus_H2"].values - pipelines["bus1"] = CH4_H2_busmap.loc[pipelines["bus1"], "bus_H2"].values - pipelines["scn_name"] = "eGon100RE" - pipelines["p_min_pu"] = -1.0 - pipelines["capital_cost"] = ( - scn_params["capital_cost"]["H2_pipeline_retrofit"] - * pipelines["length"] - / 1e3 - ) - - # create new pipelines between grid and saltcaverns - new_pipelines = gpd.GeoDataFrame() - new_pipelines["bus0"] = H2_buses.loc[ - H2_buses["carrier"] == "H2_saltcavern", "bus_id" - ].values - new_pipelines["geometry"] = H2_buses.loc[ - H2_buses["carrier"] == "H2_saltcavern", "geom" - ].values - new_pipelines.set_crs(epsg=4326, inplace=True) - - # find bus in H2_grid voronoi - new_pipelines = db.assign_gas_bus_id(new_pipelines, "eGon100RE", "H2") - new_pipelines = new_pipelines.rename(columns={"bus_id": "bus1"}).drop( - columns=["bus"] - ) - - # create link geometries - new_pipelines = link_geom_from_buses( - new_pipelines[["bus0", "bus1"]], "eGon100RE" - ) - new_pipelines["geom"] = new_pipelines.apply( - lambda row: MultiLineString([row["topo"]]), axis=1 - ) - new_pipelines = new_pipelines.set_geometry("geom", crs=4326) - new_pipelines["carrier"] = "H2_gridextension" - new_pipelines["scn_name"] = "eGon100RE" - new_pipelines["p_min_pu"] = -1.0 - new_pipelines["p_nom_extendable"] = True - new_pipelines["length"] = new_pipelines.to_crs(epsg=3035).geometry.length - - # Insert capital cost data - new_pipelines["capital_cost"] = ( - scn_params["capital_cost"]["H2_pipeline"] - * new_pipelines["length"] - / 1e3 - ) - - # Delete old entries - db.execute_sql( - f""" - DELETE FROM grid.egon_etrago_link WHERE "carrier" IN - ('H2_retrofit', 'H2_gridextension') AND scn_name = 'eGon100RE' - AND bus0 NOT IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = 'eGon100RE' AND country != 'DE' - ) AND bus1 NOT IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = 'eGon100RE' AND country != 'DE' - ); - """ - ) - - engine = db.engine() - - new_id = db.next_etrago_id("link") - pipelines["link_id"] = range(new_id, new_id + len(pipelines)) - - pipelines.to_crs(epsg=4326).to_postgis( - "egon_etrago_link", - engine, - schema="grid", - index=False, - if_exists="append", - dtype={"topo": Geometry()}, - ) - - new_id = db.next_etrago_id("link") - new_pipelines["link_id"] = range(new_id, new_id + len(new_pipelines)) - - new_pipelines.to_postgis( - "egon_etrago_h2_link", - engine, - schema="grid", - index=False, - if_exists="replace", - dtype={"geom": Geometry(), "topo": Geometry()}, - ) - - db.execute_sql( + 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 + """ - select UpdateGeometrySRID('grid', 'egon_etrago_h2_link', 'topo', 4326) ; - - INSERT INTO grid.egon_etrago_link (scn_name, capital_cost, - link_id, carrier, - bus0, bus1, p_min_pu, - p_nom_extendable, length, - geom, topo) - SELECT scn_name, capital_cost, - link_id, carrier, - bus0, bus1, p_min_pu, - p_nom_extendable, length, - geom, topo - - FROM grid.egon_etrago_h2_link; - - DROP TABLE grid.egon_etrago_h2_link; + path = Path(".") / "datasets" / "h2_data" + os.makedirs(path, exist_ok=True) + + download_config = config.datasets()["etrago_hydrogen"]["sources"]["H2_grid"] + target_file_Um = download_config["converted_ch4_pipes"]["path"] + target_file_Neu = download_config["new_constructed_pipes"]["path"] + target_file_Erw = 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) + + +def read_h2_excel_sheets(): + """ + Read downloaded excel files with location names for future h2-pipelines + + Returns + ------- + df_Neu : + df_Um : + df_Erw : + + + """ + + 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 + +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 : + """ - ) + + 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') \ No newline at end of file diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py index 4e8591e14..034989275 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py @@ -14,134 +14,127 @@ """ from geoalchemy2.types import Geometry +import geopandas as gpd +import numpy as np +from scipy.spatial import cKDTree +from shapely.geometry import LineString, MultiLineString from egon.data import db, config -from egon.data.datasets.etrago_setup import link_geom_from_buses from egon.data.datasets.scenario_parameters import get_sector_parameters -def insert_h2_to_ch4_to_h2(): +def insert_h2_to_ch4_to_h2(scn_name): """ - Inserts methanisation, feed in and SMR links into the database - + Method for implementing Methanisation as optional usage of H2-Production; + For H2_Buses and CH4_Buses with distance < 10 km Methanisation/SMR Link will be implemented + Define the potentials for methanisation and Steam Methane Reaction - (SMR) modelled as extendable links as well as the H2 feedin - capacities modelled as non extendable links and insert all of them - into the database. - These tree technologies are connecting CH4 and H2 buses only. - - The capacity of the H2_feedin links is considerated as constant and - calculated as the sum of the capacities of the CH4 links connected - to the CH4 bus multiplied by the H2 energy share allowed to be fed. - This share is calculated in the function :py:func:`H2_CH4_mix_energy_fractions`. - - This function inserts data into the database and has no return. - + (SMR) modelled as extendable links + + Returns + ------- + None + """ + scenarios = config.settings()["egon-data"]["--scenarios"] + con=db.engine() + target_links = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_links"] + target_buses = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_buses"] + for scn_name in scenarios: - # Connect to local database - engine = db.engine() - - # Select CH4 and corresponding H2 buses - # No geometry required in this case! - buses = db.select_dataframe( - f""" - SELECT * FROM grid.egon_etrago_ch4_h2 WHERE scn_name = '{scn_name}' - """ - ) - - methanation = buses.copy().rename( - columns={"bus_H2": "bus0", "bus_CH4": "bus1"} - ) - SMR = buses.copy().rename( - columns={"bus_H2": "bus1", "bus_CH4": "bus0"} - ) - - # Delete old entries - db.execute_sql( - f""" - DELETE FROM grid.egon_etrago_link WHERE "carrier" IN - ('H2_to_CH4', 'H2_feedin', 'CH4_to_H2') AND scn_name = '{scn_name}' - AND bus0 NOT IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = '{scn_name}' AND country != 'DE' - ) AND bus1 NOT IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = '{scn_name}' AND country != 'DE' - ); - """ - ) - + + db.execute_sql(f""" + DELETE FROM {target_links["schema"]}.{target_links["table"]} WHERE "carrier" in ('H2_to_CH4', 'CH4_to_H2') + AND scn_name = 'eGon2035'; + """) + + sql_CH4_buses = f""" + SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom + FROM {target_buses["schema"]}.{target_buses["table"]} + WHERE carrier = 'CH4' + AND scn_name = '{scn_name}' AND country = 'DE' + """ + CH4_buses = gpd.read_postgis(sql_CH4_buses, con) + + sql_H2_buses = f""" + SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom + FROM {target_buses["schema"]}.{target_buses["table"]} + WHERE carrier = 'H2_grid' + AND scn_name = '{scn_name}' AND country = 'DE' + """ + H2_buses = gpd.read_postgis(sql_H2_buses, con) + + CH4_to_H2_links = [] + H2_to_CH4_links = [] + + CH4_coords = np.array([(point.x, point.y) for point in CH4_buses.geometry]) + CH4_tree = cKDTree(CH4_coords) + + for idx, h2_bus in H2_buses.iterrows(): + h2_coords = [h2_bus['geom'].x, h2_bus['geom'].y] + + #Filter nearest CH4_bus + dist, nearest_idx = CH4_tree.query(h2_coords, k=1) + nearest_ch4_bus = CH4_buses.iloc[nearest_idx] + + if dist < 10000: + CH4_to_H2_links.append({ + 'scn_name': scn_name, + 'link_id': None, + 'bus0': nearest_ch4_bus['bus_id'], + 'bus1': h2_bus['bus_id'], + 'carrier': 'CH4_to_H2', + 'geom': MultiLineString([LineString([(h2_bus['x'], h2_bus['y']), (nearest_ch4_bus['x'], nearest_ch4_bus['y'])])]) + }) + + H2_to_CH4_links = [ + { + 'scn_name': link['scn_name'], + 'link_id': link['link_id'], + 'bus0': link['bus1'], # Swap bus0 and bus1 + 'bus1': link['bus0'], + 'carrier': 'H2_to_CH4', + 'geom': link['geom'] + } + for link in CH4_to_H2_links + ] + + #set crs for geoDataFrame + CH4_to_H2_links = gpd.GeoDataFrame(CH4_to_H2_links, geometry='geom', crs=4326) + H2_to_CH4_links = gpd.GeoDataFrame(H2_to_CH4_links, geometry='geom', crs=4326) + + next_link_id = db.next_etrago_id('link') + CH4_to_H2_links['link_id'] = range(next_link_id, next_link_id + len(CH4_to_H2_links)) + H2_to_CH4_links['link_id'] = range(next_link_id + len(CH4_to_H2_links), next_link_id + len(CH4_to_H2_links) + len(H2_to_CH4_links)) + scn_params = get_sector_parameters("gas", scn_name) - - technology = [methanation, SMR] + technology = [CH4_to_H2_links, H2_to_CH4_links] links_names = ["H2_to_CH4", "CH4_to_H2"] - - if scn_name == "eGon2035": - feed_in = methanation.copy() - pipeline_capacities = db.select_dataframe( - f""" - SELECT bus0, bus1, p_nom FROM grid.egon_etrago_link - WHERE scn_name = '{scn_name}' AND carrier = 'CH4' - AND ( - bus0 IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = '{scn_name}' AND country = 'DE' - ) OR bus1 IN ( - SELECT bus_id FROM grid.egon_etrago_bus - WHERE scn_name = '{scn_name}' AND country = 'DE' - ) - ); - """ - ) - - feed_in["p_nom"] = 0 - feed_in["p_nom_extendable"] = False - # calculation of H2 energy share via volumetric share outsourced - # in a mixture of H2 and CH4 with 15 %vol share - H2_share = scn_params["H2_feedin_volumetric_fraction"] - H2_energy_share = H2_CH4_mix_energy_fractions(H2_share) - - for bus in feed_in["bus1"].values: - # calculate the total pipeline capacity connected to a specific bus - nodal_capacity = pipeline_capacities.loc[ - (pipeline_capacities["bus0"] == bus) - | (pipeline_capacities["bus1"] == bus), - "p_nom", - ].sum() - # multiply total pipeline capacity with H2 energy share corresponding - # to volumetric share - feed_in.loc[feed_in["bus1"] == bus, "p_nom"] = ( - nodal_capacity * H2_energy_share - ) - technology.append(feed_in) - links_names.append("H2_feedin") - + # Write new entries for table, carrier in zip(technology, links_names): # set parameters according to carrier name table["carrier"] = carrier - table["efficiency"] = scn_params["efficiency"][carrier] - if carrier != "H2_feedin": - table["p_nom_extendable"] = True - table["capital_cost"] = scn_params["capital_cost"][carrier] - table["lifetime"] = scn_params["lifetime"][carrier] + table["efficiency"] = scn_params["efficiency"][carrier] + table["p_nom_extendable"] = True + table["capital_cost"] = scn_params["capital_cost"][carrier] + table["lifetime"] = scn_params["lifetime"][carrier] new_id = db.next_etrago_id("link") table["link_id"] = range(new_id, new_id + len(table)) - table = link_geom_from_buses(table, scn_name) table.to_postgis( - "egon_etrago_link", - engine, - schema="grid", + target_links["table"], + con, + schema=target_links["schema"], index=False, if_exists="append", - dtype={"topo": Geometry()}, + dtype={"geom": Geometry()}, ) + def H2_CH4_mix_energy_fractions(x, T=25, p=50): """ From 19541293e7d35b6b7b440b25553b91c7c9847b91 Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Wed, 9 Oct 2024 10:47:29 +0100 Subject: [PATCH 03/13] Final adjustments after testing in powerd-data-pipeline --- src/egon/data/datasets.yml | 4 +- src/egon/data/datasets/hydrogen_etrago/bus.py | 121 +++--------- .../data/datasets/hydrogen_etrago/h2_grid.py | 183 ++++++------------ .../datasets/hydrogen_etrago/h2_to_ch4.py | 12 +- 4 files changed, 94 insertions(+), 226 deletions(-) diff --git a/src/egon/data/datasets.yml b/src/egon/data/datasets.yml index eb8843093..474e3869c 100644 --- a/src/egon/data/datasets.yml +++ b/src/egon/data/datasets.yml @@ -690,10 +690,10 @@ etrago_hydrogen: url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage3_FNB_Massnahmenliste_Neubau.xlsx' path: "Anlage_3_Wasserstoffkernnetz_Neubau.xlsx" converted_ch4_pipes: - url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage4_FNB_Massnahmenliste_Umstellung.xlsx" + url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage4_FNB_Massnahmenliste_Umstellung.xlsx' path: "Anlage_4_Wasserstoffkernnetz_Umstellung.xlsx" pipes_of_further_h2_grid_operators: - url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage2_Leitungsmeldungen_weiterer_potenzieller_Wasserstoffnetzbetreiber.xlsx" + url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage2_Leitungsmeldungen_weiterer_potenzieller_Wasserstoffnetzbetreiber.xlsx' path: "Anlage_2_Wasserstoffkernetz_weitere_Leitungen.xlsx" targets: hydrogen_buses: diff --git a/src/egon/data/datasets/hydrogen_etrago/bus.py b/src/egon/data/datasets/hydrogen_etrago/bus.py index 3d3e0224c..89a18619e 100755 --- a/src/egon/data/datasets/hydrogen_etrago/bus.py +++ b/src/egon/data/datasets/hydrogen_etrago/bus.py @@ -31,38 +31,40 @@ def insert_hydrogen_buses(scn_name): """ - Insert hydrogen buses into the database (in etrago table) - + Insert hydrogen buses into the database. + Location of H2_buses are based on grid nodes of Wasserstoffkernnetz published by FNB-GAS + and the location of saltcaverns in germany. Parameters ---------- - No parameter is required. + scn_name: str + Name of scenario """ - h2_input= pd.read_csv(Path(".")/"h2_grid_nodes.csv") # (.) verweist immer auf das repository in dem egon-dat serve ausgeführt wird. - #in meinem Fall csv file in airflow_exe + h2_input= pd.read_csv(Path(".")/"h2_grid_nodes.csv") h2_input.geom = h2_input.geom.apply(lambda wkb_hex: loads(bytes.fromhex(wkb_hex))) + sources = config.datasets()["etrago_hydrogen"]["sources"] target_buses = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_buses"] - h2_buses = initialise_bus_insertion('H2', target_buses, scenario=scn_name) - + h2_buses = initialise_bus_insertion('H2', target_buses, scenario = scn_name) + h2_buses.x = h2_input.x h2_buses.y = h2_input.y h2_buses.geom = h2_input.geom - h2_buses.carrier = 'H2_grid' + h2_buses.carrier = 'H2' + h2_buses.scn_name = scn_name next_bus_id = db.next_etrago_id('bus') h2_buses.bus_id= range(next_bus_id, next_bus_id + len(h2_input)) - - # Insert data to db + h2_buses.to_postgis( - target_buses["table"], - schema= target_buses["schema"], - if_exists="append", - con=db.engine(), - dtype={"geom": Geometry()} - ) - + target_buses["table"], + schema=target_buses["schema"], + if_exists="append", + con=db.engine(), + dtype={"geom": Geometry()} + ) + #insert additional_buses for potential Methanisation to CH4_buses nearby: h2_buses = h2_buses.to_crs(epsg=32632) @@ -70,9 +72,9 @@ def insert_hydrogen_buses(scn_name): SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom FROM {target_buses["schema"]}.{target_buses["table"]} WHERE carrier = 'CH4' - AND scn_name = {scn_name} AND country = 'DE' + AND scn_name = '{scn_name}' AND country = 'DE' """ - CH4_buses = gpd.read_postgis(sql_CH4_buses, con=db.engine) + CH4_buses = gpd.read_postgis(sql_CH4_buses, con=db.engine()) additional_H2_buses = [] H2_coords = np.array([(point.x, point.y) for point in h2_buses.geometry]) @@ -83,14 +85,14 @@ def insert_hydrogen_buses(scn_name): # filter nearest h2_bus dist, nearest_idx = H2_tree.query(ch4_coords, k=1) # critcical distance assumed with 10km based on former ammount of h2_buses - if dist > 10000: + if dist > 10000: # Neuen H2-Bus hinzufügen additional_H2_buses.append({ - 'scn_name': 'eGon2025', + 'scn_name': scn_name, 'bus_id': None, 'x': ch4_bus['x'], 'y': ch4_bus['y'], - 'carrier': 'H2_grid', + 'carrier': 'H2', 'geom': ch4_bus['geom'] }) @@ -102,30 +104,19 @@ def insert_hydrogen_buses(scn_name): additional_H2_buses['bus_id'] = range(next_bus_id, next_bus_id + len(additional_H2_buses)) # Insert data to db additional_H2_buses.to_postgis( - target_buses["buses"]["table"], - schema= target_buses["buses"]["schema"], + target_buses["table"], + schema= target_buses["schema"], con=db.engine(), if_exists="append", dtype={"geom": Geometry()} ) - s = config.settings()["egon-data"]["--scenarios"] - scn = [] - if "eGon2035" in s: - scn.append("eGon2035") - if "eGon100RE" in s: - scn.append("eGon100RE") - - for scenario in scn: - sources = config.datasets()["etrago_hydrogen"]["sources"] - target = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_buses"] - # initalize dataframe for hydrogen buses - carrier = "H2_saltcavern" - hydrogen_buses = initialise_bus_insertion( - carrier, target, scenario=scenario + #insert h2_buses_from_saltcaverns + hydrogen_buses = initialise_bus_insertion( + "H2_saltcavern", target_buses, scenario=scn_name ) - insert_H2_buses_from_saltcavern( - hydrogen_buses, carrier, sources, target, scenario + insert_H2_buses_from_saltcavern( + hydrogen_buses, "H2_saltcavern", sources, target_buses, scn_name ) @@ -196,53 +187,3 @@ def insert_H2_buses_from_saltcavern(gdf, carrier, sources, target, scn_name): ) -def insert_H2_buses_from_CH4_grid(gdf, carrier, target, scn_name): - """ - Insert the H2 buses based on CH4 grid into the database. - - At each CH4 location, in other words at each intersection of the CH4 - grid, a H2 bus is created. - - Parameters - ---------- - gdf : geopandas.GeoDataFrame - GeoDataFrame containing the empty bus data. - carrier : str - Name of the carrier. - target : dict - Target schema and table information. - scn_name : str - Name of the scenario. - - """ - # Connect to local database - engine = db.engine() - - # Select the CH4 buses - sql_CH4 = f"""SELECT bus_id, scn_name, geom - FROM grid.egon_etrago_bus - WHERE carrier = 'CH4' AND scn_name = '{scn_name}' - AND country = 'DE';""" - - gdf_H2 = db.select_geodataframe(sql_CH4, epsg=4326) - # CH4 bus ids and respective hydrogen bus ids are written to db for - # later use (CH4 grid to H2 links) - CH4_bus_ids = gdf_H2[["bus_id", "scn_name"]].copy() - - H2_bus_ids = finalize_bus_insertion( - gdf_H2, carrier, target, scenario=scn_name - ) - - gdf_H2_CH4 = H2_bus_ids[["bus_id"]].rename(columns={"bus_id": "bus_H2"}) - gdf_H2_CH4["bus_CH4"] = CH4_bus_ids["bus_id"] - gdf_H2_CH4["scn_name"] = CH4_bus_ids["scn_name"] - - # Insert data to db - gdf_H2_CH4.to_sql( - "egon_etrago_ch4_h2", - engine, - schema="grid", - index=False, - if_exists="replace", - ) - diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index 3cad81ee3..48e51dc77 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -21,6 +21,7 @@ from fuzzywuzzy import process from shapely import wkb import math +import re from egon.data import config, db from egon.data.datasets.scenario_parameters import get_sector_parameters @@ -30,6 +31,7 @@ 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(".")/"h2_grid_nodes.csv") con=db.engine() @@ -37,12 +39,21 @@ def insert_h2_pipelines(scn_name): h2_buses_df = pd.read_sql( f""" SELECT bus_id, x, y FROM grid.egon_etrago_bus - WHERE carrier in ('H2_grid') - AND scn_name = {scn_name} + WHERE carrier in ('H2') + AND scn_name = '{scn_name}' """ , con) + # Delete old entries + db.execute_sql( + f""" + DELETE FROM grid.egon_etrago_link + WHERE "carrier" = 'H2_grid' + AND scn_name = '{scn_name}' + """ + ) + target = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_links"] @@ -51,7 +62,7 @@ def insert_h2_pipelines(scn_name): 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', @@ -68,21 +79,20 @@ def insert_h2_pipelines(scn_name): 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)']] - # manuell adjustments based in Detailmaßnahmenkarte der FNB-Gas [https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/] + # 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) - # matching start- and endpoint of each pipeline with georeferenced data - df['Anfangspunkt\n(Ort)_matched'] = fuzzy_match(df, h2_bus_location, 'Anfangspunkt\n(Ort)') - df['Endpunkt\n(Ort)_matched'] = fuzzy_match(df, h2_bus_location, 'Endpunkt\n(Ort)') - df_merged = pd.merge(df, h2_bus_location[['Ort', 'geom', 'x', 'y']], - how='left', left_on='Anfangspunkt\n(Ort)_matched', right_on='Ort').rename( + 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\n(Ort)_matched', right_on='Ort').rename( + 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( @@ -98,41 +108,37 @@ def insert_h2_pipelines(scn_name): 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) + 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) - H2_grid_gdf['build_year'] = H2_grid_gdf['Planerische Inbetriebnahme'].apply(lambda x: x.split('/')[1] if '/' in x else None) + 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'] = True - 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() - else scn_params["overnight_cost"]["H2_pipeline"]*row['Länge \n(km)'], row['lifetime'], 0.05), axis=1) + 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', 'p_nom', 'p_nom_min', + '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', ] - # Delete old entries - db.execute_sql( - f""" - DELETE FROM grid.egon_etrago_link - WHERE "carrier" = 'H2_grid' - AND scn_name = {scn_name} - """ - ) - H2_grid_final=H2_grid_gdf[selected_columns] # Insert data to db @@ -143,88 +149,6 @@ def insert_h2_pipelines(scn_name): if_exists="append", dtype={"geom": Geometry()}, ) - - H2_grid_gdf = H2_grid_gdf.to_crs(epsg=32632) - - H2_grid_gdf['topo'] = gpd.GeoSeries(H2_grid_gdf['topo'], crs='EPSG:4326').to_crs(epsg=32632) - - #toDo: evtl. löschen der link_ids welche ersetzt werden und gefunden werden, methodik muss noch angepasst werden - if df is H2_grid_Umstellung: - deleting_ids=[] - for index, row in H2_grid_gdf.iterrows(): - filtered_link_id = replace_transformed_CH4_pipelines(row['topo'], scn_name) - matching_link = row['link_id'] - if not pd.isna(filtered_link_id): - deleting_ids.append([filtered_link_id, matching_link]) - - -def replace_transformed_CH4_pipelines(pipeline, scn_name): - """ - Delete CH4_pipelines of datamodel, which will be converted to H2_pipelines - - - Parameters - ---------- - df : geopandas dataframe - - Returns - ------- - None. - - """ - con=db.engine() - - sql_CH4_links = f""" - SELECT link_id, topo::geometry AS geom - FROM grid.egon_etrago_link - WHERE carrier = 'CH4' - AND scn_name = '{scn_name}' - """ - CH4_links = gpd.read_postgis(sql_CH4_links, con, geom_col='geom') - CH4_links = CH4_links.to_crs(epsg=32632) - - max_distance = 10000 # in Meter - - pipeline_start = Point(pipeline.coords[0]) - pipeline_end = Point(pipeline.coords[-1]) - - def calculate_distances(geom, max_distance): - # Extrahiere Start- und Endpunkte der Leitung - link_start = Point(geom.coords[0]) - link_end = Point(geom.coords[-1]) - - # calculate all distances in case of different definitions of start- and endpoint - dist_start_to_link_start = pipeline_start.distance(link_start) - dist_start_to_link_end = pipeline_start.distance(link_end) - dist_end_to_link_start = pipeline_end.distance(link_start) - dist_end_to_link_end = pipeline_end.distance(link_end) - - start_matches = [dist_start_to_link_start, dist_end_to_link_end] - end_matches = [dist_start_to_link_end, dist_end_to_link_start] - - if all(d <= max_distance for d in start_matches): - bolean_start_match = True - else: - bolean_start_match = False - - if all(d <= max_distance for d in end_matches): - bolean_start_end = True - else: - bolean_start_end = False - - if bolean_start_end or bolean_start_match: - start_match_dist = sum(start_matches) - end_match_dist = sum(end_matches) - return min(start_match_dist, end_match_dist) - - CH4_links['total_distance'] = CH4_links['geom'].apply(lambda geom: calculate_distances(geom, max_distance)) - filtered_CH4_links = CH4_links.dropna(subset=['total_distance']) - # Finde den Link mit der geringsten addierten Distanz - if not filtered_CH4_links.empty: - closest_link = filtered_CH4_links.loc[filtered_CH4_links['total_distance'].idxmin()] - closest_link_id = closest_link['link_id'] - return closest_link_id, - def replace_pipeline(df, start, end, intermediate): @@ -250,19 +174,18 @@ def replace_pipeline(df, start, end, intermediate): """ # Find rows where the start and end points match - mask = ((df['Anfangspunkt\n(Ort)'] == start) & (df['Endpunkt\n(Ort)'] == end)) | \ - ((df['Anfangspunkt\n(Ort)'] == end) & (df['Endpunkt\n(Ort)'] == start)) + 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] - print('replacment:', df_replacement) # Add new rows for the split pipelines new_rows = pd.DataFrame({ - 'Anfangspunkt\n(Ort)': [start, intermediate], - 'Endpunkt\n(Ort)': [intermediate, end], + '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)']], @@ -369,9 +292,9 @@ def convert_to_float(value): pressure = 70 #averaqge value from data-source - velocity = 20 - temperature = 20+273.15 - density = pressure*10**5/(4.1243*10**3*temperature) #gaskonstant H2 = 4.1243 [kJ/kgK] + 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] @@ -393,13 +316,13 @@ def download_h2_grid_data(): None """ - path = Path(".") / "datasets" / "h2_data" + path = Path("datasets/h2_data") os.makedirs(path, exist_ok=True) download_config = config.datasets()["etrago_hydrogen"]["sources"]["H2_grid"] - target_file_Um = download_config["converted_ch4_pipes"]["path"] - target_file_Neu = download_config["new_constructed_pipes"]["path"] - target_file_Erw = download_config["pipes_of_further_h2_grid_operators"]["path"] + 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: @@ -440,7 +363,8 @@ def read_h2_excel_sheets(): 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/] + Manuell adjustments for more accurate grid topology based on Detailmaßnahmenkarte der + FNB-Gas [https://fnb-gas.de/wasserstoffnetz-wasserstoff-kernnetz/] Returns ------- @@ -502,4 +426,7 @@ def fix_h2_grid_infrastructure(df): 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') \ No newline at end of file + df = replace_pipeline(df, 'Glehn', 'Ludwigshafen','Wesseling') + df = replace_pipeline(df, 'Rothenstadt', 'Rimpar','Reutles') + + return df \ No newline at end of file diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py index 034989275..eb75bd123 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py @@ -23,7 +23,7 @@ from egon.data.datasets.scenario_parameters import get_sector_parameters -def insert_h2_to_ch4_to_h2(scn_name): +def insert_h2_to_ch4_to_h2(): """ Method for implementing Methanisation as optional usage of H2-Production; For H2_Buses and CH4_Buses with distance < 10 km Methanisation/SMR Link will be implemented @@ -47,7 +47,7 @@ def insert_h2_to_ch4_to_h2(scn_name): db.execute_sql(f""" DELETE FROM {target_links["schema"]}.{target_links["table"]} WHERE "carrier" in ('H2_to_CH4', 'CH4_to_H2') - AND scn_name = 'eGon2035'; + AND scn_name = '{scn_name}'; """) sql_CH4_buses = f""" @@ -55,15 +55,14 @@ def insert_h2_to_ch4_to_h2(scn_name): FROM {target_buses["schema"]}.{target_buses["table"]} WHERE carrier = 'CH4' AND scn_name = '{scn_name}' AND country = 'DE' - """ - CH4_buses = gpd.read_postgis(sql_CH4_buses, con) - + """ sql_H2_buses = f""" SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom FROM {target_buses["schema"]}.{target_buses["table"]} - WHERE carrier = 'H2_grid' + WHERE carrier in ('H2','H2_saltcavern') AND scn_name = '{scn_name}' AND country = 'DE' """ + CH4_buses = gpd.read_postgis(sql_CH4_buses, con) H2_buses = gpd.read_postgis(sql_H2_buses, con) CH4_to_H2_links = [] @@ -123,6 +122,7 @@ def insert_h2_to_ch4_to_h2(scn_name): table["lifetime"] = scn_params["lifetime"][carrier] new_id = db.next_etrago_id("link") table["link_id"] = range(new_id, new_id + len(table)) + table["scn_name"] = scn_name table.to_postgis( From c6dec7b4f11ffab4886ad1fa742b2f13f3bddd5e Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Thu, 12 Dec 2024 13:03:31 +0100 Subject: [PATCH 04/13] Connect saltcaverns/neighbour countries to H2-grid --- src/egon/data/datasets.yml | 5 +- src/egon/data/datasets/hydrogen_etrago/bus.py | 2 +- .../data/datasets/hydrogen_etrago/h2_grid.py | 230 +++++++++++++++++- 3 files changed, 234 insertions(+), 3 deletions(-) diff --git a/src/egon/data/datasets.yml b/src/egon/data/datasets.yml index 1b3b026b8..99bb8d1de 100644 --- a/src/egon/data/datasets.yml +++ b/src/egon/data/datasets.yml @@ -685,6 +685,9 @@ etrago_hydrogen: H2_AC_map: schema: 'grid' table: 'egon_etrago_ac_h2' + links: + schema: 'grid' + table: 'egon_etrago_link' H2_grid: new_constructed_pipes: url: 'https://fnb-gas.de/wp-content/uploads/2024/07/2024_07_22_Anlage3_FNB_Massnahmenliste_Neubau.xlsx' @@ -1284,4 +1287,4 @@ home_batteries: scenario_path: sources: - url_status2019: 'https://zenodo.org/records/13865306/files/PoWerD_status2019_v2.backup' \ No newline at end of file + url_status2019: 'https://zenodo.org/records/13865306/files/PoWerD_status2019_v2.backup' diff --git a/src/egon/data/datasets/hydrogen_etrago/bus.py b/src/egon/data/datasets/hydrogen_etrago/bus.py index 89a18619e..8707772d4 100755 --- a/src/egon/data/datasets/hydrogen_etrago/bus.py +++ b/src/egon/data/datasets/hydrogen_etrago/bus.py @@ -52,7 +52,7 @@ def insert_hydrogen_buses(scn_name): h2_buses.x = h2_input.x h2_buses.y = h2_input.y h2_buses.geom = h2_input.geom - h2_buses.carrier = 'H2' + h2_buses.carrier = 'H2_grid' h2_buses.scn_name = scn_name next_bus_id = db.next_etrago_id('bus') h2_buses.bus_id= range(next_bus_id, next_bus_id + len(h2_input)) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index 48e51dc77..6bc27d8f4 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -22,6 +22,9 @@ from shapely import wkb import math import re +from itertools import count +import numpy as np +from scipy.spatial import cKDTree from egon.data import config, db from egon.data.datasets.scenario_parameters import get_sector_parameters @@ -149,6 +152,12 @@ def insert_h2_pipelines(scn_name): 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) def replace_pipeline(df, start, end, intermediate): @@ -429,4 +438,223 @@ def fix_h2_grid_infrastructure(df): df = replace_pipeline(df, 'Glehn', 'Ludwigshafen','Wesseling') df = replace_pipeline(df, 'Rothenstadt', 'Rimpar','Reutles') - return df \ No newline at end of file + return df + +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"]}.{sources["hydrogen_links"]["table"]} + WHERE "carrier" in ('H2_saltcavern') + AND scn_name = '{scn_name}'; + """) + h2_buses_query = """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 = """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=32632).to_crs(4326) + + links_df.to_postgis( + targets["hydrogen_links"]["table"], + engine, + schema=targets["hydrogen_links"]["schema"], + index=False, + if_exists="append", + dtype={"geom": Geometry()}, + ) + + +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( + """ + SELECT bus_id, x, y, geom, country + FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} + WHERE carrier = 'H2_grid' AND scn_name = '{scn_name}' AND country != 'DE' + """, + engine + ) + + abroad_bus_ids=tuple(abroad_buses_df['bus_id']) + db.execute_sql(f""" + DELETE FROM {targets["hydrogen_links"]["schema"]}.{sources["hydrogen_links"]["table"]} + WHERE carrier = 'H2_grid' + AND bus1 IN {abroad_bus_ids} + """) + + 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') + ] + + h2_bus_location = pd.read_csv(Path(".")/"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'],# Spalte aus matched_locations + right_on=['x', 'y'], # Spalte aus h2_buses_df (entspricht Ort oder Bus-ID) + 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': 'eGon2035', + '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") + + + From 7ae72e7d9dc356f8b5d3a9c4110e14c95d0a33bf Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Mon, 6 Jan 2025 11:04:39 +0100 Subject: [PATCH 05/13] Adjustments after Testing code in pipeline run --- .../data/datasets/hydrogen_etrago/h2_grid.py | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index 6bc27d8f4..e781d9b2a 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -42,7 +42,7 @@ def insert_h2_pipelines(scn_name): h2_buses_df = pd.read_sql( f""" SELECT bus_id, x, y FROM grid.egon_etrago_bus - WHERE carrier in ('H2') + WHERE carrier in ('H2_grid') AND scn_name = '{scn_name}' """ @@ -103,7 +103,6 @@ def insert_h2_pipelines(scn_name): 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))) @@ -153,11 +152,11 @@ def insert_h2_pipelines(scn_name): 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) + #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) def replace_pipeline(df, start, end, intermediate): @@ -455,17 +454,17 @@ def connect_saltcavern_to_h2_grid(scn_name): engine = db.engine() db.execute_sql(f""" - DELETE FROM {targets["hydrogen_links"]["schema"]}.{sources["hydrogen_links"]["table"]} + DELETE FROM {targets["hydrogen_links"]["schema"]}.{targets["hydrogen_links"]["table"]} WHERE "carrier" in ('H2_saltcavern') AND scn_name = '{scn_name}'; """) - h2_buses_query = """SELECT bus_id, x, y,ST_Transform(geom, 32632) as geom + 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 = """SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom + 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}' """ @@ -546,17 +545,17 @@ def connect_h2_grid_to_neighbour_countries(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_grid' AND scn_name = '{scn_name}' AND country != 'DE' + WHERE carrier = 'H2' AND scn_name = '{scn_name}' AND country != 'DE' """, engine ) abroad_bus_ids=tuple(abroad_buses_df['bus_id']) db.execute_sql(f""" - DELETE FROM {targets["hydrogen_links"]["schema"]}.{sources["hydrogen_links"]["table"]} + DELETE FROM {targets["hydrogen_links"]["schema"]}.{targets["hydrogen_links"]["table"]} WHERE carrier = 'H2_grid' AND bus1 IN {abroad_bus_ids} """) @@ -592,17 +591,18 @@ def connect_h2_grid_to_neighbour_countries(scn_name): 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'],# Spalte aus matched_locations - right_on=['x', 'y'], # Spalte aus h2_buses_df (entspricht Ort oder Bus-ID) + 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') @@ -656,5 +656,3 @@ def connect_h2_grid_to_neighbour_countries(scn_name): ) print("Neighbour countries are succesfully connected to H2-grid") - - From d8ac379d739a849b50d8d888408a02859cf48c24 Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Mon, 6 Jan 2025 16:24:33 +0100 Subject: [PATCH 06/13] Add abroad H2-connection from Ellund to Denmark --- .../data/datasets/hydrogen_etrago/h2_grid.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index e781d9b2a..e02060bf0 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -582,7 +582,8 @@ def connect_h2_grid_to_neighbour_countries(scn_name): ('AQD Offshore SEN 1', 'NO'), ('AQD Offshore SEN 1', 'DK'), ('AQD Offshore SEN 1', 'NL'), - ('Fessenheim', 'FR') + ('Fessenheim', 'FR'), + ('Ellund', 'DK') ] h2_bus_location = pd.read_csv(Path(".")/"h2_grid_nodes.csv") @@ -630,21 +631,21 @@ def connect_h2_grid_to_neighbour_countries(scn_name): 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': 'eGon2035', - '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.append({ + 'scn_name': 'eGon2035', + '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( From 77ca472d1b396202825318c87540c8c48de591b3 Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Thu, 16 Jan 2025 14:41:36 +0100 Subject: [PATCH 07/13] Voronoi cells are created now for buses with carrier 'H2' or 'H2_grid' --- src/egon/data/datasets/gas_areas.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/egon/data/datasets/gas_areas.py b/src/egon/data/datasets/gas_areas.py index 8ea41993e..ef5c98e86 100755 --- a/src/egon/data/datasets/gas_areas.py +++ b/src/egon/data/datasets/gas_areas.py @@ -108,11 +108,23 @@ def create_voronoi(scn_name, carrier): """, geom_col="geometry", ).to_crs(epsg=4326) + + + if isinstance(carrier, str): + if carrier == "H2": + carriers = ["H2", "H2_grid"] + else: + carriers = [carrier] + else: + carriers = carrier + + carrier_strings = "', '".join(carriers) + db.execute_sql( f""" DELETE FROM grid.egon_gas_voronoi - WHERE "carrier" = '{carrier}' and "scn_name" = '{scn_name}'; + WHERE "carrier" IN ('{carrier_strings}') and "scn_name" = '{scn_name}'; """ ) @@ -122,13 +134,14 @@ def create_voronoi(scn_name, carrier): FROM grid.egon_etrago_bus WHERE scn_name = '{scn_name}' AND country = 'DE' - AND carrier = '{carrier}'; + AND carrier IN ('{carrier_strings}'); """, ).to_crs(epsg=4326) - + + if len(buses) == 0: return - + # generate voronois # For some scenarios it is defined that there is only 1 bus (e.g. gas). It # means that there will be just 1 voronoi covering the entire german From 1957c8c04f509a69415e118d07ac8a9b145aaee0 Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Mon, 20 Jan 2025 15:34:47 +0100 Subject: [PATCH 08/13] Add sql delete statements for buses wirh carrier H2 --- src/egon/data/datasets/hydrogen_etrago/bus.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/bus.py b/src/egon/data/datasets/hydrogen_etrago/bus.py index 8707772d4..e5db16759 100755 --- a/src/egon/data/datasets/hydrogen_etrago/bus.py +++ b/src/egon/data/datasets/hydrogen_etrago/bus.py @@ -47,7 +47,15 @@ def insert_hydrogen_buses(scn_name): sources = config.datasets()["etrago_hydrogen"]["sources"] target_buses = config.datasets()["etrago_hydrogen"]["targets"]["hydrogen_buses"] - h2_buses = initialise_bus_insertion('H2', target_buses, scenario = scn_name) + h2_buses = initialise_bus_insertion('H2_grid', target_buses, scenario = scn_name) + + db.execute_sql( + f""" + DELETE FROM {target_buses['schema']}.{target_buses['table']} + WHERE scn_name = '{scn_name}' + AND carrier = 'H2' AND country = 'DE' + """ + ) h2_buses.x = h2_input.x h2_buses.y = h2_input.y From 99d994712084d8b9e67741edfa4ec5a97b425178 Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Tue, 21 Jan 2025 14:55:08 +0100 Subject: [PATCH 09/13] Correct CRS of H2-saltcavern-links --- src/egon/data/datasets/hydrogen_etrago/h2_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index e02060bf0..39ffcd2c6 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -497,7 +497,7 @@ def connect_saltcavern_to_h2_grid(scn_name): } links.append(link) - links_df = gpd.GeoDataFrame(links, geometry='geom', crs=32632).to_crs(4326) + links_df = gpd.GeoDataFrame(links, geometry='geom', crs=4326) links_df.to_postgis( targets["hydrogen_links"]["table"], From f1abe8e5414e1c677ea985aafba955bc3134cc78 Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Wed, 29 Jan 2025 09:54:36 +0100 Subject: [PATCH 10/13] Adapt har coded scn_name for abroad-connection H2_grid links --- src/egon/data/datasets/hydrogen_etrago/h2_grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index 39ffcd2c6..997382a71 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -632,7 +632,7 @@ def connect_h2_grid_to_neighbour_countries(scn_name): p_nom_value = relevant_buses['p_nom'].sum() connection_links.append({ - 'scn_name': 'eGon2035', + 'scn_name': scn_name, 'carrier': 'H2_grid', 'link_id': next(next_max_link_id), 'bus0': i_bus['bus_id'], From ac0c2334be4bb5364d1030da9a6f6a98981591fc Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Wed, 29 Jan 2025 16:32:05 +0100 Subject: [PATCH 11/13] correct carrier name from CH4_to_H2 to H2_to_CH4 --- src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py index d2e60d118..bd2810ff8 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py @@ -113,7 +113,7 @@ def insert_h2_to_ch4_to_h2(): scn_params = get_sector_parameters("gas", scn_name) technology = [CH4_to_H2_links, H2_to_CH4_links] - links_names = ["H2_to_CH4", "CH4_to_H2"] + links_names = ["CH4_to_H2", "CH4_to_H2"] # Write new entries for table, carrier in zip(technology, links_names): From 56cbd9f4d1651dbaca9e9afce657ad453959a75c Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Thu, 30 Jan 2025 10:07:56 +0100 Subject: [PATCH 12/13] Clean up H2_to_ch4.py and adjust Delete-sql statements in case of multiple executions --- .../data/datasets/hydrogen_etrago/h2_grid.py | 25 +++++++++---------- .../datasets/hydrogen_etrago/h2_to_ch4.py | 15 +++++------ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py index 997382a71..9ead3d23e 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_grid.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_grid.py @@ -39,21 +39,27 @@ def insert_h2_pipelines(scn_name): h2_bus_location = pd.read_csv(Path(".")/"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 grid.egon_etrago_bus + SELECT bus_id, x, y FROM {sources["buses"]["schema"]}.{sources["buses"]["table"]} WHERE carrier in ('H2_grid') - AND scn_name = '{scn_name}' - + AND scn_name = '{scn_name}' """ , con) # Delete old entries db.execute_sql( f""" - DELETE FROM grid.egon_etrago_link - WHERE "carrier" = 'H2_grid' - AND scn_name = '{scn_name}' + 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' + ) """ ) @@ -552,13 +558,6 @@ def connect_h2_grid_to_neighbour_countries(scn_name): """, engine ) - - abroad_bus_ids=tuple(abroad_buses_df['bus_id']) - db.execute_sql(f""" - DELETE FROM {targets["hydrogen_links"]["schema"]}.{targets["hydrogen_links"]["table"]} - WHERE carrier = 'H2_grid' - AND bus1 IN {abroad_bus_ids} - """) abroad_con_buses = [ ('Greifenhagen', 'PL'), diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py index bd2810ff8..a91aae7e4 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py @@ -50,7 +50,10 @@ def insert_h2_to_ch4_to_h2(): db.execute_sql(f""" DELETE FROM {target_links["schema"]}.{target_links["table"]} WHERE "carrier" in ('H2_to_CH4', 'CH4_to_H2') - AND scn_name = '{scn_name}'; + AND scn_name = '{scn_name}' AND bus0 IN ( + SELECT bus_id + FROM {target_buses["schema"]}.{target_buses["table"]} + WHERE country = 'DE'; """) sql_CH4_buses = f""" @@ -87,7 +90,6 @@ def insert_h2_to_ch4_to_h2(): 'link_id': None, 'bus0': nearest_ch4_bus['bus_id'], 'bus1': h2_bus['bus_id'], - 'carrier': 'CH4_to_H2', 'geom': MultiLineString([LineString([(h2_bus['x'], h2_bus['y']), (nearest_ch4_bus['x'], nearest_ch4_bus['y'])])]) }) @@ -97,7 +99,6 @@ def insert_h2_to_ch4_to_h2(): 'link_id': link['link_id'], 'bus0': link['bus1'], # Swap bus0 and bus1 'bus1': link['bus0'], - 'carrier': 'H2_to_CH4', 'geom': link['geom'] } for link in CH4_to_H2_links @@ -107,16 +108,13 @@ def insert_h2_to_ch4_to_h2(): CH4_to_H2_links = gpd.GeoDataFrame(CH4_to_H2_links, geometry='geom', crs=4326) H2_to_CH4_links = gpd.GeoDataFrame(H2_to_CH4_links, geometry='geom', crs=4326) - next_link_id = db.next_etrago_id('link') - CH4_to_H2_links['link_id'] = range(next_link_id, next_link_id + len(CH4_to_H2_links)) - H2_to_CH4_links['link_id'] = range(next_link_id + len(CH4_to_H2_links), next_link_id + len(CH4_to_H2_links) + len(H2_to_CH4_links)) scn_params = get_sector_parameters("gas", scn_name) technology = [CH4_to_H2_links, H2_to_CH4_links] - links_names = ["CH4_to_H2", "CH4_to_H2"] + links_carriers = ["CH4_to_H2", "CH4_to_H2"] # Write new entries - for table, carrier in zip(technology, links_names): + for table, carrier in zip(technology, links_carriers): # set parameters according to carrier name table["carrier"] = carrier table["efficiency"] = scn_params["efficiency"][carrier] @@ -125,7 +123,6 @@ def insert_h2_to_ch4_to_h2(): table["lifetime"] = scn_params["lifetime"][carrier] new_id = db.next_etrago_id("link") table["link_id"] = range(new_id, new_id + len(table)) - table["scn_name"] = scn_name table.to_postgis( From 6366965b78dde12054962118514c091f054463ba Mon Sep 17 00:00:00 2001 From: Lennart Zimmermann Date: Tue, 4 Feb 2025 12:01:18 +0100 Subject: [PATCH 13/13] Correct Carrier naming of H2_to_CH4 + No Methanisation from saltcaverns + H2_overground storage for each H2-Bus --- src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py | 4 ++-- src/egon/data/datasets/hydrogen_etrago/storage.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py index a91aae7e4..72db954ff 100755 --- a/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py +++ b/src/egon/data/datasets/hydrogen_etrago/h2_to_ch4.py @@ -65,7 +65,7 @@ def insert_h2_to_ch4_to_h2(): sql_H2_buses = f""" SELECT bus_id, x, y, ST_Transform(geom, 32632) as geom FROM {target_buses["schema"]}.{target_buses["table"]} - WHERE carrier in ('H2','H2_saltcavern') + WHERE carrier in ('H2') AND scn_name = '{scn_name}' AND country = 'DE' """ CH4_buses = gpd.read_postgis(sql_CH4_buses, con) @@ -111,7 +111,7 @@ def insert_h2_to_ch4_to_h2(): scn_params = get_sector_parameters("gas", scn_name) technology = [CH4_to_H2_links, H2_to_CH4_links] - links_carriers = ["CH4_to_H2", "CH4_to_H2"] + links_carriers = ["CH4_to_H2", "H2_to_CH4"] # Write new entries for table, carrier in zip(technology, links_carriers): diff --git a/src/egon/data/datasets/hydrogen_etrago/storage.py b/src/egon/data/datasets/hydrogen_etrago/storage.py index a8de73f6f..bba4ed3aa 100755 --- a/src/egon/data/datasets/hydrogen_etrago/storage.py +++ b/src/egon/data/datasets/hydrogen_etrago/storage.py @@ -45,7 +45,7 @@ def insert_H2_overground_storage(): f""" SELECT bus_id, scn_name, geom FROM {sources['buses']['schema']}. - {sources['buses']['table']} WHERE carrier = 'H2' + {sources['buses']['table']} WHERE carrier IN ('H2', 'H2_grid') AND scn_name = '{scn_name}' AND country = 'DE'""", index_col="bus_id", )