Passed
Pull Request — dev (#1068)
by
unknown
01:49
created

data.datasets.hydrogen_etrago.storage   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 361
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 12
eloc 184
dl 0
loc 361
rs 10
c 0
b 0
f 0

5 Functions

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