add regionalised hydrogen salt cavern storage potentials (#191)
* add regionalised hydrogen salt cavern storage potentials * fix reading in salt-cavern potentials and typo in imports * by default disable nearshore and offshore salt cavern potentials Co-authored-by: lisazeyen <lisa.zeyen@web.de>
This commit is contained in:
parent
7760c30d3c
commit
3ecb761b57
15
Snakefile
15
Snakefile
@ -209,6 +209,19 @@ else:
|
||||
build_biomass_transport_costs_output = {}
|
||||
|
||||
|
||||
rule build_salt_cavern_potentials:
|
||||
input:
|
||||
salt_caverns="data/h2_salt_caverns_GWh_per_sqkm.geojson",
|
||||
regions_onshore=pypsaeur("resources/regions_onshore_elec_s{simpl}_{clusters}.geojson"),
|
||||
regions_offshore=pypsaeur("resources/regions_offshore_elec_s{simpl}_{clusters}.geojson"),
|
||||
output:
|
||||
h2_cavern_potential="resources/salt_cavern_potentials_s{simpl}_{clusters}.csv"
|
||||
threads: 1
|
||||
resources: mem_mb=2000
|
||||
benchmark: "benchmarks/build_salt_cavern_potentials_s{simpl}_{clusters}"
|
||||
script: "scripts/build_salt_cavern_potentials.py"
|
||||
|
||||
|
||||
rule build_ammonia_production:
|
||||
input:
|
||||
usgs="data/myb1-2017-nitro.xls"
|
||||
@ -357,7 +370,7 @@ rule prepare_sector_network:
|
||||
costs=CDIR + "costs_{planning_horizons}.csv",
|
||||
profile_offwind_ac=pypsaeur("resources/profile_offwind-ac.nc"),
|
||||
profile_offwind_dc=pypsaeur("resources/profile_offwind-dc.nc"),
|
||||
h2_cavern="data/hydrogen_salt_cavern_potentials.csv",
|
||||
h2_cavern="resources/salt_cavern_potentials_s{simpl}_{clusters}.csv",
|
||||
busmap_s=pypsaeur("resources/busmap_elec_s{simpl}.csv"),
|
||||
busmap=pypsaeur("resources/busmap_elec_s{simpl}_{clusters}.csv"),
|
||||
clustered_pop_layout="resources/pop_layout_elec_s{simpl}_{clusters}.csv",
|
||||
|
@ -238,6 +238,10 @@ sector:
|
||||
co2_network: false
|
||||
cc_fraction: 0.9 # default fraction of CO2 captured with post-combustion capture
|
||||
hydrogen_underground_storage: true
|
||||
hydrogen_underground_storage_locations:
|
||||
- onshore # more than 50 km from sea
|
||||
# - nearshore # within 50 km of sea
|
||||
# - offshore
|
||||
use_fischer_tropsch_waste_heat: true
|
||||
use_fuel_cell_waste_heat: true
|
||||
electricity_distribution_grid: false
|
||||
|
@ -15,7 +15,7 @@ co2 budgets,co2_budget.csv,CC BY 4.0,https://arxiv.org/abs/2004.11009
|
||||
existing heating potentials,existing_infrastructure/existing_heating_raw.csv,unknown,https://ec.europa.eu/energy/studies/mapping-and-analyses-current-and-future-2020-2030-heatingcooling-fuel-deployment_en?redir=1
|
||||
IRENA existing VRE capacities,existing_infrastructure/{solar|onwind|offwind}_capcity_IRENA.csv,unknown,https://www.irena.org/Statistics/Download-Data
|
||||
USGS ammonia production,myb1-2017-nitro.xls,unknown,https://www.usgs.gov/centers/nmic/nitrogen-statistics-and-information
|
||||
hydrogen salt cavern potentials,hydrogen_salt_cavern_potentials.csv,CC BY 4.0,https://doi.org/10.1016/j.ijhydene.2019.12.161
|
||||
hydrogen salt cavern potentials,h2_salt_caverns_GWh_per_sqkm.geojson,CC BY 4.0,https://doi.org/10.1016/j.ijhydene.2019.12.161 https://doi.org/10.20944/preprints201910.0187.v1
|
||||
hotmaps industrial site database,Industrial_Database.csv,CC BY 4.0,https://gitlab.com/hotmaps/industrial_sites/industrial_sites_Industrial_Database
|
||||
Hotmaps building stock data,data_building_stock.csv,CC BY 4.0,https://gitlab.com/hotmaps/building-stock
|
||||
U-values Poland,u_values_poland.csv,unknown,https://data.europa.eu/euodp/de/data/dataset/building-stock-observatory
|
||||
|
|
@ -8,6 +8,9 @@ Future release
|
||||
.. note::
|
||||
This unreleased version currently may require the master branches of PyPSA, PyPSA-Eur, and the technology-data repository.
|
||||
|
||||
* Add regionalised hydrogen salt cavern storage potentials from `Technical Potential of Salt Caverns for Hydrogen Storage in Europe <https://doi.org/10.20944/preprints201910.0187.v1>`_.
|
||||
|
||||
|
||||
PyPSA-Eur-Sec 0.6.0 (4 October 2021)
|
||||
====================================
|
||||
|
||||
|
78
scripts/build_salt_cavern_potentials.py
Normal file
78
scripts/build_salt_cavern_potentials.py
Normal file
@ -0,0 +1,78 @@
|
||||
"""
|
||||
Build salt cavern potentials for hydrogen storage.
|
||||
|
||||
Technical Potential of Salt Caverns for Hydrogen Storage in Europe
|
||||
CC-BY 4.0
|
||||
https://doi.org/10.20944/preprints201910.0187.v1
|
||||
https://doi.org/10.1016/j.ijhydene.2019.12.161
|
||||
|
||||
Figure 6. Distribution of potential salt cavern sites across Europe with their corresponding
|
||||
energy densities (cavern storage potential divided by the volume).
|
||||
|
||||
Figure 7. Total cavern storage potential in European countries
|
||||
classified as onshore, offshore and within 50 km of shore.
|
||||
|
||||
The regional distribution is taken from the map (Figure 6) and scaled to the
|
||||
capacities from the bar chart split by nearshore (<50km from sea),
|
||||
onshore (>50km from sea), offshore (Figure 7).
|
||||
"""
|
||||
|
||||
|
||||
import geopandas as gpd
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def concat_gdf(gdf_list, crs='EPSG:4326'):
|
||||
"""Concatenate multiple geopandas dataframes with common coordinate reference system (crs)."""
|
||||
return gpd.GeoDataFrame(pd.concat(gdf_list), crs=crs)
|
||||
|
||||
|
||||
def load_bus_regions(onshore_path, offshore_path):
|
||||
"""Load pypsa-eur on- and offshore regions and concat."""
|
||||
|
||||
bus_regions_offshore = gpd.read_file(offshore_path)
|
||||
bus_regions_onshore = gpd.read_file(onshore_path)
|
||||
bus_regions = concat_gdf([bus_regions_offshore, bus_regions_onshore])
|
||||
bus_regions = bus_regions.dissolve(by='name', aggfunc='sum')
|
||||
|
||||
return bus_regions
|
||||
|
||||
|
||||
def area(gdf):
|
||||
"""Returns area of GeoDataFrame geometries in square kilometers."""
|
||||
return gdf.to_crs(epsg=3035).area.div(1e6)
|
||||
|
||||
|
||||
def salt_cavern_potential_by_region(caverns, regions):
|
||||
|
||||
# calculate area of caverns shapes
|
||||
caverns["area_caverns"] = area(caverns)
|
||||
|
||||
overlay = gpd.overlay(regions.reset_index(), caverns, keep_geom_type=True)
|
||||
|
||||
# calculate share of cavern area inside region
|
||||
overlay["share"] = area(overlay) / overlay["area_caverns"]
|
||||
|
||||
overlay["e_nom"] = overlay.eval("capacity_per_area * share * area_caverns / 1000") # TWh
|
||||
|
||||
caverns_regions = overlay.groupby(['name', "storage_type"]).e_nom.sum().unstack("storage_type")
|
||||
|
||||
return caverns_regions
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 'snakemake' not in globals():
|
||||
from helper import mock_snakemake
|
||||
snakemake = mock_snakemake('build_salt_cavern_potentials', simpl='', clusters='37')
|
||||
|
||||
|
||||
fn_onshore = snakemake.input.regions_onshore
|
||||
fn_offshore = snakemake.input.regions_offshore
|
||||
|
||||
regions = load_bus_regions(fn_onshore, fn_offshore)
|
||||
|
||||
caverns = gpd.read_file(snakemake.input.salt_caverns) # GWh/sqkm
|
||||
|
||||
caverns_regions = salt_cavern_potential_by_region(caverns, regions)
|
||||
|
||||
caverns_regions.to_csv(snakemake.output.h2_cavern_potential)
|
@ -1040,26 +1040,27 @@ def add_storage(n, costs):
|
||||
lifetime=costs.at['fuel cell', 'lifetime']
|
||||
)
|
||||
|
||||
cavern_nodes = pd.DataFrame()
|
||||
cavern_types = snakemake.config["sector"]["hydrogen_underground_storage_locations"]
|
||||
h2_caverns = pd.read_csv(snakemake.input.h2_cavern, index_col=0)[cavern_types].sum(axis=1)
|
||||
|
||||
# only use sites with at least 2 TWh potential
|
||||
h2_caverns = h2_caverns[h2_caverns > 2]
|
||||
|
||||
# convert TWh to MWh
|
||||
h2_caverns = h2_caverns * 1e6
|
||||
|
||||
# clip at 1000 TWh for one location
|
||||
h2_caverns.clip(upper=1e9, inplace=True)
|
||||
|
||||
if options['hydrogen_underground_storage']:
|
||||
h2_salt_cavern_potential = pd.read_csv(snakemake.input.h2_cavern, index_col=0, squeeze=True)
|
||||
h2_cavern_ct = h2_salt_cavern_potential[~h2_salt_cavern_potential.isna()]
|
||||
cavern_nodes = pop_layout[pop_layout.ct.isin(h2_cavern_ct.index)]
|
||||
|
||||
h2_capital_cost = costs.at["hydrogen storage underground", "fixed"]
|
||||
h2_capital_cost = costs.at["hydrogen storage underground", "fixed"]
|
||||
|
||||
# assumptions: weight storage potential in a country by population
|
||||
# TODO: fix with real geographic potentials
|
||||
# convert TWh to MWh with 1e6
|
||||
h2_pot = h2_cavern_ct.loc[cavern_nodes.ct]
|
||||
h2_pot.index = cavern_nodes.index
|
||||
h2_pot = h2_pot * cavern_nodes.fraction * 1e6
|
||||
|
||||
n.madd("Store",
|
||||
cavern_nodes.index + " H2 Store",
|
||||
bus=cavern_nodes.index + " H2",
|
||||
n.madd("Store",
|
||||
h2_caverns.index + " H2 Store",
|
||||
bus=h2_caverns.index + " H2",
|
||||
e_nom_extendable=True,
|
||||
e_nom_max=h2_pot.values,
|
||||
e_nom_max=h2_caverns.values,
|
||||
e_cyclic=True,
|
||||
carrier="H2 Store",
|
||||
capital_cost=h2_capital_cost
|
||||
@ -1067,7 +1068,7 @@ def add_storage(n, costs):
|
||||
|
||||
# hydrogen stored overground (where not already underground)
|
||||
h2_capital_cost = costs.at["hydrogen storage tank incl. compressor", "fixed"]
|
||||
nodes_overground = cavern_nodes.index.symmetric_difference(nodes)
|
||||
nodes_overground = h2_caverns.index.symmetric_difference(nodes)
|
||||
|
||||
n.madd("Store",
|
||||
nodes_overground + " H2 Store",
|
||||
|
Loading…
Reference in New Issue
Block a user