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

"""
The central module containing all code dealing with H2 stores in Germany

This module contains the functions used to insert the two types of H2
store potentials in Germany:
  * H2 overground stores (carrier: 'H2_overground'): steel tanks at
    every H2_grid bus
  * H2 underground stores (carrier: 'H2_underground'): saltcavern store
    at every H2_saltcavern bus.
    NB: the saltcavern locations define the H2_saltcavern buses locations.
All these stores are modelled as extendable PyPSA stores.

"""
from geoalchemy2 import Geometry
import geopandas as gpd
import pandas as pd

from egon.data import config, db
from egon.data.datasets.etrago_helpers import copy_and_modify_stores
from egon.data.datasets.scenario_parameters import get_sector_parameters


[docs]def insert_H2_overground_storage(scn_name="eGon2035"): """ Insert H2_overground stores into the database. Insert extendable H2_overground stores (steel tanks) at each H2_grid bus. Returns ------- None """ # The targets of etrago_hydrogen also serve as source here ಠ_ಠ sources = config.datasets()["etrago_hydrogen"]["sources"] targets = config.datasets()["etrago_hydrogen"]["targets"] # Place storage at every H2 bus storages = db.select_geodataframe( f""" SELECT bus_id, scn_name, geom FROM {sources['buses']['schema']}. {sources['buses']['table']} WHERE carrier = 'H2_grid' AND scn_name = '{scn_name}' AND country = 'DE'""", index_col="bus_id", ) carrier = "H2_overground" # Add missing column storages["bus"] = storages.index storages["carrier"] = carrier # Does e_nom_extenable = True render e_nom useless? storages["e_nom"] = 0 storages["e_nom_extendable"] = True # read carrier information from scnario parameter data scn_params = get_sector_parameters("gas", scn_name) storages["capital_cost"] = scn_params["capital_cost"][carrier] storages["lifetime"] = scn_params["lifetime"][carrier] # Remove useless columns storages.drop(columns=["geom"], inplace=True) # Clean table db.execute_sql( f""" DELETE FROM grid.egon_etrago_store WHERE carrier = '{carrier}' AND scn_name = '{scn_name}' AND bus not IN ( SELECT bus_id FROM grid.egon_etrago_bus WHERE scn_name = '{scn_name}' AND country != 'DE' ); """ ) # Select next id value new_id = db.next_etrago_id("store") storages["store_id"] = range(new_id, new_id + len(storages)) storages = storages.reset_index(drop=True) # Insert data to db storages.to_sql( targets["hydrogen_stores"]["table"], db.engine(), schema=targets["hydrogen_stores"]["schema"], index=False, if_exists="append", )
[docs]def insert_H2_saltcavern_storage(scn_name="eGon2035"): """ Insert H2_underground stores into the database. Insert extendable H2_underground stores (saltcavern potentials) at every H2_saltcavern bus. Returns ------- None """ # Datatables sources and targets sources = config.datasets()["etrago_hydrogen"]["sources"] targets = config.datasets()["etrago_hydrogen"]["targets"] storage_potentials = db.select_geodataframe( f""" SELECT * FROM {sources['saltcavern_data']['schema']}. {sources['saltcavern_data']['table']}""", geom_col="geometry", ) # Place storage at every H2 bus from the H2 AC saltcavern map H2_AC_bus_map = db.select_dataframe( f""" SELECT * FROM {sources['H2_AC_map']['schema']}. {sources['H2_AC_map']['table']}""" ) storage_potentials["storage_potential"] = ( storage_potentials["area_fraction"] * storage_potentials["potential"] ) storage_potentials[ "summed_potential_per_bus" ] = storage_potentials.groupby("bus_id")["storage_potential"].transform( "sum" ) storages = storage_potentials[ ["summed_potential_per_bus", "bus_id"] ].copy() storages.drop_duplicates("bus_id", keep="last", inplace=True) # map AC buses in potetial data to respective H2 buses storages = storages.merge( H2_AC_bus_map, left_on="bus_id", right_on="bus_AC" ).reindex(columns=["bus_H2", "summed_potential_per_bus", "scn_name"]) # rename columns storages.rename( columns={"bus_H2": "bus", "summed_potential_per_bus": "e_nom_max"}, inplace=True, ) # add missing columns carrier = "H2_underground" storages["carrier"] = carrier storages["e_nom"] = 0 storages["e_nom_extendable"] = True # read carrier information from scnario parameter data scn_params = get_sector_parameters("gas", scn_name) storages["capital_cost"] = scn_params["capital_cost"][carrier] storages["lifetime"] = scn_params["lifetime"][carrier] # Clean table db.execute_sql( f""" DELETE FROM grid.egon_etrago_store WHERE carrier = '{carrier}' AND scn_name = '{scn_name}' AND bus not IN ( SELECT bus_id FROM grid.egon_etrago_bus WHERE scn_name = '{scn_name}' AND country != 'DE' ); """ ) # Select next id value new_id = db.next_etrago_id("store") storages["store_id"] = range(new_id, new_id + len(storages)) storages = storages.reset_index(drop=True) # # Insert data to db storages.to_sql( targets["hydrogen_stores"]["table"], db.engine(), schema=targets["hydrogen_stores"]["schema"], index=False, if_exists="append", )
[docs]def calculate_and_map_saltcavern_storage_potential(): """ Calculate site specific storage potential based on InSpEE-DS report. Returns ------- None """ # select onshore vg250 data sources = config.datasets()["bgr"]["sources"] vg250_data = db.select_geodataframe( f"""SELECT * FROM {sources['vg250_federal_states']['schema']}. {sources['vg250_federal_states']['table']} WHERE gf = '4'""", index_col="id", geom_col="geometry", ) # get saltcavern shapes saltcavern_data = db.select_geodataframe( f"""SELECT * FROM {sources['saltcaverns']['schema']}. {sources['saltcaverns']['table']} """, geom_col="geometry", ) # hydrogen storage potential data from InSpEE-DS report hydrogen_storage_potential = pd.DataFrame(columns=["INSPEEDS", "INSPEE"]) # values in MWh, modified to fit the saltstructure data hydrogen_storage_potential.loc["Brandenburg"] = [353e6, 159e6] hydrogen_storage_potential.loc["Mecklenburg-Vorpommern"] = [25e6, 193e6] hydrogen_storage_potential.loc["Nordrhein-Westfalen"] = [168e6, 0] hydrogen_storage_potential.loc["Sachsen-Anhalt"] = [318e6, 147e6] hydrogen_storage_potential.loc["Thüringen"] = [595e6, 0] # distribute SH/HH and NDS/HB potentials by area # overlay saltstructures with federal state, calculate respective area # map storage potential per federal state to area fraction of summed area # potential_i = area_i / area_tot * potential_tot potential_data_dict = { 0: { "federal_states": ["Schleswig-Holstein", "Hamburg"], "INSPEEDS": 0, "INSPEE": 413e6, }, 1: { "federal_states": ["Niedersachsen", "Bremen"], "INSPEEDS": 253e6, "INSPEE": 702e6, }, } # iterate over aggregated state data for SH/HH and NDS/HB for data in potential_data_dict.values(): individual_areas = {} # individual state areas for federal_state in data["federal_states"]: try: individual_areas[federal_state] = ( saltcavern_data.overlay( vg250_data[vg250_data["gen"] == federal_state], how="intersection", ) .to_crs(epsg=25832) .area.sum() ) except ValueError: individual_areas[federal_state] = 0 # derives weights from fraction of individual state area to total area total_area = sum(individual_areas.values()) weights = { f: individual_areas[f] / total_area if total_area > 0 else 0 for f in data["federal_states"] } # write data into potential dataframe for federal_state in data["federal_states"]: hydrogen_storage_potential.loc[federal_state] = [ data["INSPEEDS"] * weights[federal_state], data["INSPEE"] * weights[federal_state], ] # calculate total storage potential hydrogen_storage_potential["total"] = ( # currently only InSpEE saltstructure shapefiles are available hydrogen_storage_potential["INSPEEDS"] + hydrogen_storage_potential["INSPEE"] ) saltcaverns_in_fed_state = gpd.GeoDataFrame() # intersection of saltstructures with federal state for federal_state in hydrogen_storage_potential.index: federal_state_data = vg250_data[vg250_data["gen"] == federal_state] # skip if federal state not available (e.g. local testing) if federal_state_data.size > 0: saltcaverns_in_fed_state = saltcaverns_in_fed_state.append( saltcavern_data.overlay(federal_state_data, how="intersection") ) # write total potential in column, will be overwritten by actual # value later saltcaverns_in_fed_state.loc[ saltcaverns_in_fed_state["gen"] == federal_state, "potential" ] = hydrogen_storage_potential.loc[federal_state, "total"] # drop all federal state data columns except name of the state saltcaverns_in_fed_state.drop( columns=[ col for col in federal_state_data.columns if col not in ["gen", "geometry"] ], inplace=True, ) # this is required for the first loop as no geometry has been set # prior to this, also set crs to match original saltcavern_data crs saltcaverns_in_fed_state.set_geometry("geometry") saltcaverns_in_fed_state.set_crs(saltcavern_data.crs, inplace=True) saltcaverns_in_fed_state.to_crs(epsg=4326, inplace=True) # recalculate area in case structures have been split at federal # state borders in original data epsg # mapping of potential to individual H2 storage is in # hydrogen_etrago/storage.py saltcaverns_in_fed_state["shape_star"] = saltcaverns_in_fed_state.to_crs( epsg=25832 ).area # get substation voronois substation_voronoi = ( db.select_geodataframe( f""" SELECT * FROM grid.egon_hvmv_substation_voronoi """, index_col="bus_id", ) .to_crs(4326) .sort_index() ) # get substations substations = db.select_geodataframe( f""" SELECT * FROM grid.egon_hvmv_substation""", geom_col="point", index_col="bus_id", ).to_crs(4326) # create 500 m radius around substations as storage potential area # epsg for buffer in line with original saltstructre data substations_inflation = gpd.GeoDataFrame( geometry=substations.to_crs(25832).buffer(500).to_crs(4326) ).sort_index() # !!row wise!! intersection between the substations inflation and the # respective voronoi (overlay only allows for intersection to all # voronois) voroni_buffer_intersect = substations_inflation["geometry"].intersection( substation_voronoi["geom"] ) # make intersection a dataframe to kepp bus_id column in potential area # overlay voroni_buffer_intersect = gpd.GeoDataFrame( { "bus_id": voroni_buffer_intersect.index.tolist(), "geometry": voroni_buffer_intersect.geometry.tolist(), } ).set_crs(epsg=4326) # overlay saltstructures with substation buffer potential_areas = saltcaverns_in_fed_state.overlay( voroni_buffer_intersect, how="intersection" ).set_crs(epsg=4326) # calculate area fraction of individual site over total area within # the same federal state potential_areas["area_fraction"] = potential_areas.to_crs( epsg=25832 ).area / potential_areas.groupby("gen")["shape_star"].transform("sum") return potential_areas
[docs]def write_saltcavern_potential(): """Write saltcavern potentials into the database Returns ------- None """ potential_areas = calculate_and_map_saltcavern_storage_potential() # write information to saltcavern data targets = config.datasets()["bgr"]["targets"] potential_areas.to_crs(epsg=4326).to_postgis( targets["storage_potential"]["table"], db.engine(), schema=targets["storage_potential"]["schema"], index=True, if_exists="replace", dtype={"geometry": Geometry()}, )
[docs]def insert_H2_storage_eGon100RE(): """Copy H2 storage from the eGon2035 to the eGon100RE scenario. Returns ------- None """ copy_and_modify_stores( "eGon2035", "eGon100RE", ["H2_underground", "H2_overground"], "gas" )