Passed
Pull Request — dev (#1181)
by
unknown
05:34
created

data.datasets.power_plants.wind_offshore.insert()   F

Complexity

Conditions 11

Size

Total Lines 220
Code Lines 131

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 131
dl 0
loc 220
rs 3.78
c 0
b 0
f 0
cc 11
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like data.datasets.power_plants.wind_offshore.insert() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
from pathlib import Path
2
import logging
3
4
from shapely.geometry import Point
5
import geopandas as gpd
6
import pandas as pd
7
8
from egon.data import db
9
import egon.data.config
10
11
12
def map_id_bus(scenario):
13
    # Import manually generated list of wind offshore farms with their
14
    # connection points (OSM_id)
15
    osm_year = egon.data.config.datasets()["openstreetmap"]["original_data"][
16
        "source"
17
    ]["url"]
18
19
    if scenario in ["eGon2035", "eGon100RE"]:
20
        id_bus = {
21
            "Büttel": "136034396",
22
            "Suchraum Gemeinden Ibbenbüren/Mettingen/Westerkappeln": "114319248",
23
            "Suchraum Zensenbusch": "76185022",
24
            "Rommerskirchen": "24839976",
25
            "Oberzier": "26593929",
26
            "Garrel/Ost": "23837631",
27
            "Diele": "177829920",
28
            "Dörpen/West": "142487746",
29
            "Emden/Borßum": "34835258",
30
            "Hagermarsch": "79316833",
31
            "Hanekenfähr": "61918154",
32
            "Inhausen": "29420322",
33
            "Unterweser": "32076853",
34
            "Wehrendorf": "33411203",
35
            "Rastede": "23837631",
36
            "Bentwisch": "32063539",
37
            "Lubmin": "460134233",
38
            "Suchraum Gemeinde Papendorf": "32063539",
39
            "Suchraum Gemeinden Brünzow/Kemnitz": "460134233",
40
            "inhausen": "29420322",
41
            "Cloppenburg": "50643382",
42
        }
43
        if "200101" in osm_year:
44
            id_bus2 = {
45
                "Heide/West": "289836713",
46
                "Emden/Ost": "177829920",
47
                "Wilhelmshaven 2": "23837631",
48
            }
49
        elif "220101" in osm_year:
50
            id_bus2 = {
51
                "Heide/West": "603661085",
52
                "Emden/Ost": "34835258",
53
                "Wilhelmshaven 2": "23744346",
54
            }
55
        else:
56
            raise Exception(
57
                """The OSM year used is not yet compatible with
58
                            this function"""
59
            )
60
        id_bus = {**id_bus, **id_bus2}
61
62
    elif "status" in scenario:
63
        year = int(scenario[-4:])
64
65
        id_bus = {
66
            "UW Inhausen": "29420322",
67
            "UW Bentwisch": "32063539",
68
            "UW Emden / Borssum": "34835258",
69
            "UW Emden Ost": "34835258",
70
            "UW Cloppenburg": "50643382",
71
            "UW Hagermarsch": "79316833",
72
            "UW Büttel": "136034396",
73
            "UW Dörpen West": "142487746",
74
            "UW Diele": "177829920",
75
            "UW Lubmin": "460134233",
76
        }
77
78
        if year >= 2023:
79
            # No update needed as no new stations used for offshore wind
80
            # between 2019 and 2023
81
            pass
82
83
        # TODO: If necessary add new stations when generating status quo > 2023
84
85
    else:
86
        id_bus = {}
87
88
    return id_bus
89
90
91
def assign_ONEP_areas():
92
    return {
93
        "Büttel": "NOR-4-1",
94
        "Heide/West": "NOR-10-2",
95
        "Suchraum Gemeinden Ibbenbüren/Mettingen/Westerkappeln": "NOR-9-2",
96
        "Suchraum Zensenbusch": "NOR-7-1",
97
        "Rommerskirchen": "NOR-7-1",
98
        "Oberzier": "NOR-7-1",
99
        "Garrel/Ost": "NOR-7-1",
100
        "Diele": "NOR-6-1",
101
        "Dörpen/West": "NOR-2-2",
102
        "Emden/Borßum": "NOR-0-1",
103
        "Emden/Ost": "NOR-3-3",
104
        "Hagermarsch": "NOR-2-1",
105
        "Hanekenfähr": "NOR-6-3",
106
        "Inhausen": "NOR-0-2",
107
        "Unterweser": "NOR-9-1",
108
        "Wehrendorf": "NOR-7-1",
109
        "Wilhelmshaven 2": "NOR-11-1",
110
        "Rastede": "NOR-7-1",
111
        "Bentwisch": "OST-3-1",
112
        "Lubmin": "OST-1-1",
113
        "Suchraum Gemeinde Papendorf": "OST-7-1",
114
        "Suchraum Gemeinden Brünzow/Kemnitz": "OST-1-4",
115
        "inhausen": "NOR-0-2",
116
        "Cloppenburg": "NOR-4-1",
117
    }
118
119
120
def map_ONEP_areas():
121
    return {
122
        "NOR-0-1": Point(6.5, 53.6),
123
        "NOR-0-2": Point(8.07, 53.76),
124
        "NOR-1": Point(6.21, 54.06),
125
        "NOR-1-1": Point(6.21, 54.06),
126
        "NOR-2-1": Point(6.54, 53.99),
127
        "NOR-2-2": Point(6.54, 53.99),
128
        "NOR-2-3": Point(6.54, 53.99),
129
        "NOR-3-1": Point(6.95, 54.02),
130
        "NOR-3-3": Point(6.95, 54.02),
131
        "NOR-4-1": Point(7.70, 54.44),
132
        "NOR-4-2": Point(7.70, 54.44),
133
        "NOR-5-1": Point(7.21, 55.14),
134
        "NOR-6-1": Point(5.92, 54.30),
135
        "NOR-6-2": Point(5.92, 54.30),
136
        "NOR-6-3": Point(5.92, 54.30),
137
        "NOR-7": Point(6.22, 54.32),
138
        "NOR-7-1": Point(6.22, 54.32),
139
        "NOR-8-1": Point(6.35, 54.48),
140
        "NOR-9-1": Point(5.75, 54.5),
141
        "NOR-9-2": Point(5.75, 54.5),
142
        "NOR-10-2": Point(6, 54.75),
143
        "NOR-11-1": Point(6.5, 54.75),
144
        "OST-1-1": Point(14.09, 54.82),
145
        "OST-1-2": Point(14.09, 54.82),
146
        "OST-1-3": Point(14.09, 54.82),
147
        "OST-1-4": Point(14.09, 54.82),
148
        "OST-2": Point(13.86, 54.83),
149
        "OST-3-1": Point(13.16, 54.98),
150
        "OST-3-2": Point(13.16, 54.98),
151
        "OST-7-1": Point(12.25, 54.5),
152
    }
153
154
155
def insert():
156
    """
157
    Include the offshore wind parks in egon-data.
158
159
    Parameters
160
    ----------
161
    *No parameters required
162
    """
163
    # Read file with all required input/output tables' names
164
    cfg = egon.data.config.datasets()["power_plants"]
165
166
    scenarios = egon.data.config.settings()["egon-data"]["--scenarios"]
167
168
    for scenario in scenarios:
169
        # Delete previous generators
170
        db.execute_sql(
171
            f"""
172
            DELETE FROM {cfg['target']['schema']}.{cfg['target']['table']}
173
            WHERE carrier = 'wind_offshore'
174
            AND scenario = '{scenario}'
175
            """
176
        )
177
178
        # load file
179
        if scenario == "eGon2035":
180
            offshore_path = (
181
                Path(".")
182
                / "data_bundle_egon_data"
183
                / "nep2035_version2021"
184
                / cfg["sources"]["nep_2035"]
185
            )
186
187
            offshore = pd.read_excel(
188
                offshore_path,
189
                sheet_name="WInd_Offshore_NEP",
190
                usecols=[
191
                    "Netzverknuepfungspunkt",
192
                    "Spannungsebene in kV",
193
                    "C 2035",
194
                ],
195
            )
196
            offshore.dropna(subset=["Netzverknuepfungspunkt"], inplace=True)
197
            offshore.rename(columns={"C 2035": "el_capacity"}, inplace=True)
198
199
        elif scenario == "eGon100RE":
200
            offshore_path = (
201
                Path(".")
202
                / "data_bundle_egon_data"
203
                / "nep2035_version2021"
204
                / cfg["sources"]["nep_2035"]
205
            )
206
207
            offshore = pd.read_excel(
208
                offshore_path,
209
                sheet_name="WInd_Offshore_NEP",
210
                usecols=[
211
                    "Netzverknuepfungspunkt",
212
                    "Spannungsebene in kV",
213
                    "B 2040 ",
214
                ],
215
            )
216
            offshore.dropna(subset=["Netzverknuepfungspunkt"], inplace=True)
217
            offshore.rename(columns={"B 2040 ": "el_capacity"}, inplace=True)
218
219
        elif "status" in scenario:
220
            year = int(scenario[-4:])
221
222
            offshore_path = (
223
                Path(".")
224
                / "data_bundle_egon_data"
225
                / "wind_offshore_status2019"
226
                / cfg["sources"]["wind_offshore_status2019"]
227
            )
228
            offshore = pd.read_excel(
229
                offshore_path,
230
                sheet_name="wind_offshore",
231
                usecols=[
232
                    "Name ONEP/NEP",
233
                    "NVP",
234
                    "Spannung [kV]",
235
                    "Inbetriebnahme",
236
                    "Kapazität Gesamtsystem [MW]",
237
                ],
238
            )
239
            offshore.dropna(subset=["Name ONEP/NEP"], inplace=True)
240
            offshore.rename(
241
                columns={
242
                    "NVP": "Netzverknuepfungspunkt",
243
                    "Spannung [kV]": "Spannungsebene in kV",
244
                    "Kapazität Gesamtsystem [MW]": "el_capacity",
245
                },
246
                inplace=True,
247
            )
248
            offshore = offshore[offshore["Inbetriebnahme"] <= year]
249
250
        else:
251
            raise ValueError(f"{scenario=} is not valid.")
252
253
        id_bus = map_id_bus(scenario)
254
255
        # Match wind offshore table with the corresponding OSM_id
256
        offshore["osm_id"] = offshore["Netzverknuepfungspunkt"].map(id_bus)
257
258
        buses = db.select_geodataframe(
259
            f"""
260
                SELECT bus_i as bus_id, base_kv, geom as point, CAST(osm_substation_id AS text)
261
                as osm_id FROM {cfg["sources"]["buses_data"]}
262
                """,
263
            epsg=4326,
264
            geom_col="point",
265
        )
266
267
        # Drop NANs in column osm_id
268
        buses.dropna(subset=["osm_id"], inplace=True)
269
270
        # Create columns for bus_id and geometry in the offshore df
271
        offshore["bus_id"] = pd.NA
272
        offshore["geom"] = Point(0, 0)
273
274
        # Match bus_id
275
        for index, wind_park in offshore.iterrows():
276
            if not buses[
277
                (buses["osm_id"] == wind_park["osm_id"])
278
                & (buses["base_kv"] == wind_park["Spannungsebene in kV"])
279
            ].empty:
280
                bus_ind = buses[buses["osm_id"] == wind_park["osm_id"]].index[
281
                    0
282
                ]
283
                offshore.at[index, "bus_id"] = buses.at[bus_ind, "bus_id"]
284
            else:
285
                print(f'Wind offshore farm not found: {wind_park["osm_id"]}')
286
287
        offshore.dropna(subset=["bus_id"], inplace=True)
288
289
        # Overwrite geom for status2019 parks
290
        if scenario in ["eGon2035", "eGon100RE"]:
291
            offshore["Name ONEP/NEP"] = offshore["Netzverknuepfungspunkt"].map(
292
                assign_ONEP_areas()
293
            )
294
295
        offshore["geom"] = offshore["Name ONEP/NEP"].map(map_ONEP_areas())
296
        offshore["weather_cell_id"] = pd.NA
297
298
        offshore.drop(["Name ONEP/NEP"], axis=1, inplace=True)
299
300
        if "status" in scenario:
301
            offshore.drop(["Inbetriebnahme"], axis=1, inplace=True)
302
303
        # Scale capacities for eGon100RE
304
        if scenario == "eGon100RE":
305
            # Import capacity targets for wind_offshore per scenario
306
            cap_100RE = db.select_dataframe(
307
                f"""
308
                    SELECT SUM(capacity)
309
                    FROM {cfg["sources"]["capacities"]}
310
                    WHERE scenario_name = 'eGon100RE' AND
311
                    carrier = 'wind_offshore'
312
                    """
313
            ).iloc[0, 0]
314
315
            # Scale capacities to match  target
316
            scale_factor = cap_100RE / offshore.el_capacity.sum()
317
            offshore["el_capacity"] *= scale_factor
318
319
        # Assign voltage levels to wind offshore parks
320
        offshore["voltage_level"] = 0
321
        offshore.loc[
322
            offshore[offshore["Spannungsebene in kV"] == 110].index,
323
            "voltage_level",
324
        ] = 3
325
        offshore.loc[
326
            offshore[offshore["Spannungsebene in kV"] > 110].index,
327
            "voltage_level",
328
        ] = 1
329
330
        # Delete unnecessary columns
331
        offshore.drop(
332
            [
333
                "Netzverknuepfungspunkt",
334
                "Spannungsebene in kV",
335
                "osm_id",
336
            ],
337
            axis=1,
338
            inplace=True,
339
        )
340
341
        # Set static columns
342
        offshore["carrier"] = "wind_offshore"
343
        offshore["scenario"] = scenario
344
345
        offshore = gpd.GeoDataFrame(offshore, geometry="geom", crs=4326)
346
347
        # Look for the maximum id in the table egon_power_plants
348
        next_id = db.select_dataframe(
349
            "SELECT MAX(id) FROM "
350
            + cfg["target"]["schema"]
351
            + "."
352
            + cfg["target"]["table"]
353
        ).iloc[0, 0]
354
355
        if next_id:
356
            next_id += 1
357
        else:
358
            next_id = 1
359
360
        # Reset index
361
        offshore.index = pd.RangeIndex(
362
            start=next_id, stop=next_id + len(offshore), name="id"
363
        )
364
365
        # Insert into database
366
        offshore.reset_index().to_postgis(
367
            cfg["target"]["table"],
368
            schema=cfg["target"]["schema"],
369
            con=db.engine(),
370
            if_exists="append",
371
        )
372
373
        logging.info(
374
            f"""
375
              {len(offshore)} wind_offshore generators with a total installed capacity of
376
              {offshore['el_capacity'].sum()}MW were inserted into the db
377
              """
378
        )
379