Passed
Pull Request — dev (#1129)
by
unknown
01:48
created

write_saltcavern_potential()   A

Complexity

Conditions 1

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 13
rs 9.9
c 0
b 0
f 0
cc 1
nop 0
1
"""
2
The central module containing all code dealing with H2 stores in Germany
3
4
This module contains the functions used to insert the two types of H2
5
store potentials in Germany:
6
  * H2 overground stores (carrier: 'H2_overground'): steel tanks at
7
    every H2_grid bus
8
  * H2 underground stores (carrier: 'H2_underground'): saltcavern store
9
    at every H2_saltcavern bus.
10
    NB: the saltcavern locations define the H2_saltcavern buses locations.
11
All these stores are modelled as extendable PyPSA stores.
12
13
"""
14
from geoalchemy2 import Geometry
15
import geopandas as gpd
16
import pandas as pd
17
18
from egon.data import config, db
19
from egon.data.datasets.etrago_helpers import copy_and_modify_stores
20
from egon.data.datasets.scenario_parameters import get_sector_parameters
21
22
23
def insert_H2_overground_storage(scn_name="eGon2035"):
24
    """
25
    Insert H2_overground stores into the database.
26
27
    Insert extendable H2_overground stores (steel tanks) at each H2_grid
28
    bus. This function inserts data into the database and has no return.
29
30
    """
31
    # The targets of etrago_hydrogen also serve as source here ಠ_ಠ
32
    sources = config.datasets()["etrago_hydrogen"]["sources"]
33
    targets = config.datasets()["etrago_hydrogen"]["targets"]
34
35
    # Place storage at every H2 bus
36
    storages = db.select_geodataframe(
37
        f"""
38
        SELECT bus_id, scn_name, geom
39
        FROM {sources['buses']['schema']}.
40
        {sources['buses']['table']} WHERE carrier = 'H2_grid'
41
        AND scn_name = '{scn_name}' AND country = 'DE'""",
42
        index_col="bus_id",
43
    )
44
45
    carrier = "H2_overground"
46
    # Add missing column
47
    storages["bus"] = storages.index
48
    storages["carrier"] = carrier
49
50
    # Does e_nom_extenable = True render e_nom useless?
51
    storages["e_nom"] = 0
52
    storages["e_nom_extendable"] = True
53
54
    # read carrier information from scnario parameter data
55
    scn_params = get_sector_parameters("gas", scn_name)
56
    storages["capital_cost"] = scn_params["capital_cost"][carrier]
57
    storages["lifetime"] = scn_params["lifetime"][carrier]
58
59
    # Remove useless columns
60
    storages.drop(columns=["geom"], inplace=True)
61
62
    # Clean table
63
    db.execute_sql(
64
        f"""
65
        DELETE FROM grid.egon_etrago_store WHERE carrier = '{carrier}' AND
66
        scn_name = '{scn_name}' AND bus not IN (
67
            SELECT bus_id FROM grid.egon_etrago_bus
68
            WHERE scn_name = '{scn_name}' AND country != 'DE'
69
        );
70
        """
71
    )
72
73
    # Select next id value
74
    new_id = db.next_etrago_id("store")
75
    storages["store_id"] = range(new_id, new_id + len(storages))
76
    storages = storages.reset_index(drop=True)
77
78
    # Insert data to db
79
    storages.to_sql(
80
        targets["hydrogen_stores"]["table"],
81
        db.engine(),
82
        schema=targets["hydrogen_stores"]["schema"],
83
        index=False,
84
        if_exists="append",
85
    )
86
87
88
def insert_H2_saltcavern_storage(scn_name="eGon2035"):
89
    """
90
    Insert H2_underground stores into the database.
91
92
    Insert extendable H2_underground stores (saltcavern potentials) at
93
    every H2_saltcavern bus.This function inserts data into the database
94
    and has no return.
95
96
    """
97
98
    # Datatables sources and targets
99
    sources = config.datasets()["etrago_hydrogen"]["sources"]
100
    targets = config.datasets()["etrago_hydrogen"]["targets"]
101
102
    storage_potentials = db.select_geodataframe(
103
        f"""
104
        SELECT *
105
        FROM {sources['saltcavern_data']['schema']}.
106
        {sources['saltcavern_data']['table']}""",
107
        geom_col="geometry",
108
    )
109
110
    # Place storage at every H2 bus from the H2 AC saltcavern map
111
    H2_AC_bus_map = db.select_dataframe(
112
        f"""
113
        SELECT *
114
        FROM {sources['H2_AC_map']['schema']}.
115
        {sources['H2_AC_map']['table']}""",
116
    )
117
118
    storage_potentials["storage_potential"] = (
119
        storage_potentials["area_fraction"] * storage_potentials["potential"]
120
    )
121
122
    storage_potentials[
123
        "summed_potential_per_bus"
124
    ] = storage_potentials.groupby("bus_id")["storage_potential"].transform(
125
        "sum"
126
    )
127
128
    storages = storage_potentials[
129
        ["summed_potential_per_bus", "bus_id"]
130
    ].copy()
131
    storages.drop_duplicates("bus_id", keep="last", inplace=True)
132
133
    # map AC buses in potetial data to respective H2 buses
134
    storages = storages.merge(
135
        H2_AC_bus_map, left_on="bus_id", right_on="bus_AC"
136
    ).reindex(columns=["bus_H2", "summed_potential_per_bus", "scn_name"])
137
138
    # rename columns
139
    storages.rename(
140
        columns={"bus_H2": "bus", "summed_potential_per_bus": "e_nom_max"},
141
        inplace=True,
142
    )
143
144
    # add missing columns
145
    carrier = "H2_underground"
146
    storages["carrier"] = carrier
147
    storages["e_nom"] = 0
148
    storages["e_nom_extendable"] = True
149
150
    # read carrier information from scnario parameter data
151
    scn_params = get_sector_parameters("gas", scn_name)
152
    storages["capital_cost"] = scn_params["capital_cost"][carrier]
153
    storages["lifetime"] = scn_params["lifetime"][carrier]
154
155
    # Clean table
156
    db.execute_sql(
157
        f"""
158
        DELETE FROM grid.egon_etrago_store WHERE carrier = '{carrier}' AND
159
        scn_name = '{scn_name}' AND bus not IN (
160
            SELECT bus_id FROM grid.egon_etrago_bus
161
            WHERE scn_name = '{scn_name}' AND country != 'DE'
162
        );
163
        """
164
    )
165
166
    # Select next id value
167
    new_id = db.next_etrago_id("store")
168
    storages["store_id"] = range(new_id, new_id + len(storages))
169
    storages = storages.reset_index(drop=True)
170
171
    # # Insert data to db
172
    storages.to_sql(
173
        targets["hydrogen_stores"]["table"],
174
        db.engine(),
175
        schema=targets["hydrogen_stores"]["schema"],
176
        index=False,
177
        if_exists="append",
178
    )
179
180
181
def calculate_and_map_saltcavern_storage_potential():
182
    """
183
    Calculate site specific storage potential based on InSpEE-DS report.
184
185
    This function inserts data into the database and has no return.
186
187
    """
188
189
    # select onshore vg250 data
190
    sources = config.datasets()["bgr"]["sources"]
191
    vg250_data = db.select_geodataframe(
192
        f"""SELECT * FROM
193
                {sources['vg250_federal_states']['schema']}.
194
                {sources['vg250_federal_states']['table']}
195
            WHERE gf = '4'""",
196
        index_col="id",
197
        geom_col="geometry",
198
    )
199
200
    # get saltcavern shapes
201
    saltcavern_data = db.select_geodataframe(
202
        f"""SELECT * FROM
203
                {sources['saltcaverns']['schema']}.
204
                {sources['saltcaverns']['table']}
205
            """,
206
        geom_col="geometry",
207
    )
208
209
    # hydrogen storage potential data from InSpEE-DS report
210
    hydrogen_storage_potential = pd.DataFrame(columns=["INSPEEDS", "INSPEE"])
211
212
    # values in MWh, modified to fit the saltstructure data
213
    hydrogen_storage_potential.loc["Brandenburg"] = [353e6, 159e6]
214
    hydrogen_storage_potential.loc["Mecklenburg-Vorpommern"] = [25e6, 193e6]
215
    hydrogen_storage_potential.loc["Nordrhein-Westfalen"] = [168e6, 0]
216
    hydrogen_storage_potential.loc["Sachsen-Anhalt"] = [318e6, 147e6]
217
    hydrogen_storage_potential.loc["Thüringen"] = [595e6, 0]
218
219
    # distribute SH/HH and NDS/HB potentials by area
220
    # overlay saltstructures with federal state, calculate respective area
221
    # map storage potential per federal state to area fraction of summed area
222
    # potential_i = area_i / area_tot * potential_tot
223
224
    potential_data_dict = {
225
        0: {
226
            "federal_states": ["Schleswig-Holstein", "Hamburg"],
227
            "INSPEEDS": 0,
228
            "INSPEE": 413e6,
229
        },
230
        1: {
231
            "federal_states": ["Niedersachsen", "Bremen"],
232
            "INSPEEDS": 253e6,
233
            "INSPEE": 702e6,
234
        },
235
    }
236
237
    # iterate over aggregated state data for SH/HH and NDS/HB
238
    for data in potential_data_dict.values():
239
        individual_areas = {}
240
        # individual state areas
241
        for federal_state in data["federal_states"]:
242
            try:
243
                individual_areas[federal_state] = (
244
                    saltcavern_data.overlay(
245
                        vg250_data[vg250_data["gen"] == federal_state],
246
                        how="intersection",
247
                    )
248
                    .to_crs(epsg=25832)
249
                    .area.sum()
250
                )
251
            except ValueError:
252
                individual_areas[federal_state] = 0
253
254
        # derives weights from fraction of individual state area to total area
255
        total_area = sum(individual_areas.values())
256
        weights = {
257
            f: individual_areas[f] / total_area if total_area > 0 else 0
258
            for f in data["federal_states"]
259
        }
260
        # write data into potential dataframe
261
        for federal_state in data["federal_states"]:
262
            hydrogen_storage_potential.loc[federal_state] = [
263
                data["INSPEEDS"] * weights[federal_state],
264
                data["INSPEE"] * weights[federal_state],
265
            ]
266
267
    # calculate total storage potential
268
    hydrogen_storage_potential["total"] = (
269
        # currently only InSpEE saltstructure shapefiles are available
270
        hydrogen_storage_potential["INSPEEDS"]
271
        + hydrogen_storage_potential["INSPEE"]
272
    )
273
274
    saltcaverns_in_fed_state = gpd.GeoDataFrame()
275
276
    # intersection of saltstructures with federal state
277
    for federal_state in hydrogen_storage_potential.index:
278
        federal_state_data = vg250_data[vg250_data["gen"] == federal_state]
279
280
        # skip if federal state not available (e.g. local testing)
281
        if federal_state_data.size > 0:
282
            saltcaverns_in_fed_state = saltcaverns_in_fed_state.append(
283
                saltcavern_data.overlay(federal_state_data, how="intersection")
284
            )
285
            # write total potential in column, will be overwritten by actual
286
            # value later
287
            saltcaverns_in_fed_state.loc[
288
                saltcaverns_in_fed_state["gen"] == federal_state, "potential"
289
            ] = hydrogen_storage_potential.loc[federal_state, "total"]
290
291
    # drop all federal state data columns except name of the state
292
    saltcaverns_in_fed_state.drop(
293
        columns=[
294
            col
295
            for col in federal_state_data.columns
0 ignored issues
show
introduced by
The variable federal_state_data does not seem to be defined in case the for loop on line 277 is not entered. Are you sure this can never be the case?
Loading history...
296
            if col not in ["gen", "geometry"]
297
        ],
298
        inplace=True,
299
    )
300
301
    # this is required for the first loop as no geometry has been set
302
    # prior to this, also set crs to match original saltcavern_data crs
303
    saltcaverns_in_fed_state.set_geometry("geometry")
304
    saltcaverns_in_fed_state.set_crs(saltcavern_data.crs, inplace=True)
305
    saltcaverns_in_fed_state.to_crs(epsg=4326, inplace=True)
306
307
    # recalculate area in case structures have been split at federal
308
    # state borders in original data epsg
309
    # mapping of potential to individual H2 storage is in
310
    # hydrogen_etrago/storage.py
311
    saltcaverns_in_fed_state["shape_star"] = saltcaverns_in_fed_state.to_crs(
312
        epsg=25832
313
    ).area
314
315
    # get substation voronois
316
    substation_voronoi = (
317
        db.select_geodataframe(
318
            f"""
319
        SELECT * FROM grid.egon_hvmv_substation_voronoi
320
        """,
321
            index_col="bus_id",
322
        )
323
        .to_crs(4326)
324
        .sort_index()
325
    )
326
327
    # get substations
328
    substations = db.select_geodataframe(
329
        f"""
330
        SELECT * FROM grid.egon_hvmv_substation""",
331
        geom_col="point",
332
        index_col="bus_id",
333
    ).to_crs(4326)
334
335
    # create 500 m radius around substations as storage potential area
336
    # epsg for buffer in line with original saltstructre data
337
    substations_inflation = gpd.GeoDataFrame(
338
        geometry=substations.to_crs(25832).buffer(500).to_crs(4326)
339
    ).sort_index()
340
341
    # !!row wise!! intersection between the substations inflation and the
342
    # respective voronoi (overlay only allows for intersection to all
343
    # voronois)
344
    voroni_buffer_intersect = substations_inflation["geometry"].intersection(
345
        substation_voronoi["geom"]
346
    )
347
348
    # make intersection a dataframe to kepp bus_id column in potential area
349
    # overlay
350
    voroni_buffer_intersect = gpd.GeoDataFrame(
351
        {
352
            "bus_id": voroni_buffer_intersect.index.tolist(),
353
            "geometry": voroni_buffer_intersect.geometry.tolist(),
354
        }
355
    ).set_crs(epsg=4326)
356
357
    # overlay saltstructures with substation buffer
358
    potential_areas = saltcaverns_in_fed_state.overlay(
359
        voroni_buffer_intersect, how="intersection"
360
    ).set_crs(epsg=4326)
361
362
    # calculate area fraction of individual site over total area within
363
    # the same federal state
364
    potential_areas["area_fraction"] = potential_areas.to_crs(
365
        epsg=25832
366
    ).area / potential_areas.groupby("gen")["shape_star"].transform("sum")
367
368
    return potential_areas
369
370
def write_saltcavern_potential():
371
    """Write saltcavern potentials in the database"""
372
    potential_areas = calculate_and_map_saltcavern_storage_potential()
373
374
    # write information to saltcavern data
375
    targets = config.datasets()["bgr"]["targets"]
376
    potential_areas.to_crs(epsg=4326).to_postgis(
377
        targets["storage_potential"]["table"],
378
        db.engine(),
379
        schema=targets["storage_potential"]["schema"],
380
        index=True,
381
        if_exists="replace",
382
        dtype={"geometry": Geometry()},
383
    )
384
385
386
def insert_H2_storage_eGon100RE():
387
    """Copy H2 storage from the eGon2035 to the eGon100RE scenario."""
388
    copy_and_modify_stores(
389
        "eGon2035", "eGon100RE", ["H2_underground", "H2_overground"], "gas"
390
    )
391