Passed
Pull Request — dev (#1122)
by
unknown
04:34
created

data.datasets.renewable_feedin.add_metadata()   B

Complexity

Conditions 1

Size

Total Lines 97
Code Lines 60

Duplication

Lines 97
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 60
nop 0
dl 97
loc 97
rs 8.309
c 0
b 0
f 0

How to fix   Long Method   

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:

1
"""
2
Central module containing all code dealing with processing era5 weather data.
3
"""
4
5
import datetime
6
import json
7
import time
8
from sqlalchemy import Column, ForeignKey, Integer
9
from sqlalchemy.ext.declarative import declarative_base
10
import geopandas as gpd
11
import numpy as np
12
import pandas as pd
13
14
from egon.data import db
15
from egon.data.datasets import Dataset
16
from egon.data.datasets.era5 import EgonEra5Cells, EgonRenewableFeedIn, import_cutout
17
from egon.data.datasets.scenario_parameters import get_sector_parameters
18
from egon.data.metadata import (
19
    context,
20
    generate_resource_fields_from_sqla_model,
21
    license_ccby,
22
    meta_metadata,
23
    sources,
24
)
25
from egon.data.datasets.zensus_vg250 import DestatisZensusPopulationPerHa
26
import egon.data.config
27
28
29
class RenewableFeedin(Dataset):
30
    def __init__(self, dependencies):
31
        super().__init__(
32
            name="RenewableFeedin",
33
            version="0.0.8",
34
            dependencies=dependencies,
35
            tasks={
36
                wind,
37
                pv,
38
                solar_thermal,
39
                heat_pump_cop,
40
                wind_offshore,
41
                mapping_zensus_weather,
42
            },
43
        )
44
45
46
Base = declarative_base()
47
engine = db.engine()
48
49
50
class MapZensusWeatherCell(Base):
51
    __tablename__ = "egon_map_zensus_weather_cell"
52
    __table_args__ = {"schema": "boundaries"}
53
54
    zensus_population_id = Column(
55
        Integer,
56
        ForeignKey(DestatisZensusPopulationPerHa.id),
57
        primary_key=True,
58
        index=True,
59
    )
60
    w_id = Column(Integer, ForeignKey(EgonEra5Cells.w_id), index=True)
61
62
63
def weather_cells_in_germany(geom_column="geom"):
64
    """Get weather cells which intersect with Germany
65
66
    Returns
67
    -------
68
    GeoPandas.GeoDataFrame
69
        Index and points of weather cells inside Germany
70
71
    """
72
73
    cfg = egon.data.config.datasets()["renewable_feedin"]["sources"]
74
75
    return db.select_geodataframe(
76
        f"""SELECT w_id, geom_point, geom
77
        FROM {cfg['weather_cells']['schema']}.
78
        {cfg['weather_cells']['table']}
79
        WHERE ST_Intersects('SRID=4326;
80
        POLYGON((5 56, 15.5 56, 15.5 47, 5 47, 5 56))', geom)""",
81
        geom_col=geom_column,
82
        index_col="w_id",
83
    )
84
85
86
def offshore_weather_cells(geom_column="geom"):
87
    """Get weather cells which intersect with Germany
88
89
    Returns
90
    -------
91
    GeoPandas.GeoDataFrame
92
        Index and points of weather cells inside Germany
93
94
    """
95
96
    cfg = egon.data.config.datasets()["renewable_feedin"]["sources"]
97
98
    return db.select_geodataframe(
99
        f"""SELECT w_id, geom_point, geom
100
        FROM {cfg['weather_cells']['schema']}.
101
        {cfg['weather_cells']['table']}
102
        WHERE ST_Intersects('SRID=4326;
103
        POLYGON((5.5 55.5, 14.5 55.5, 14.5 53.5, 5.5 53.5, 5.5 55.5))',
104
         geom)""",
105
        geom_col=geom_column,
106
        index_col="w_id",
107
    )
108
109
110
def federal_states_per_weather_cell():
111
    """Assings a federal state to each weather cell in Germany.
112
113
    Sets the federal state to the weather celss using the centroid.
114
    Weather cells at the borders whoes centroid is not inside Germany
115
    are assinged to the closest federal state.
116
117
    Returns
118
    -------
119
    GeoPandas.GeoDataFrame
120
        Index, points and federal state of weather cells inside Germany
121
122
    """
123
124
    cfg = egon.data.config.datasets()["renewable_feedin"]["sources"]
125
126
    # Select weather cells and ferear states from database
127
    weather_cells = weather_cells_in_germany(geom_column="geom_point")
128
129
    federal_states = db.select_geodataframe(
130
        f"""SELECT gen, geometry
131
        FROM {cfg['vg250_lan_union']['schema']}.
132
        {cfg['vg250_lan_union']['table']}""",
133
        geom_col="geometry",
134
        index_col="gen",
135
    )
136
137
    # Map federal state and onshore wind turbine to weather cells
138
    weather_cells["federal_state"] = gpd.sjoin(
139
        weather_cells, federal_states
140
    ).index_right
141
142
    # Assign a federal state to each cell inside Germany
143
    buffer = 1000
144
145
    while (buffer < 30000) & (
146
        len(weather_cells[weather_cells["federal_state"].isnull()]) > 0
147
    ):
148
149
        cells = weather_cells[weather_cells["federal_state"].isnull()]
150
151
        cells.loc[:, "geom_point"] = cells.geom_point.buffer(buffer)
152
153
        weather_cells.loc[cells.index, "federal_state"] = gpd.sjoin(
154
            cells, federal_states
155
        ).index_right
156
157
        buffer += 200
158
159
        weather_cells = (
160
            weather_cells.reset_index()
161
            .drop_duplicates(subset="w_id", keep="first")
162
            .set_index("w_id")
163
        )
164
165
    weather_cells = weather_cells.dropna(axis=0, subset=["federal_state"])
166
167
    return weather_cells.to_crs(4326)
168
169
170
def turbine_per_weather_cell():
171
    """Assign wind onshore turbine types to weather cells
172
173
    Returns
174
    -------
175
    weather_cells : GeoPandas.GeoDataFrame
176
        Weather cells in Germany including turbine type
177
178
    """
179
180
    # Select representative onshore wind turbines per federal state
181
    map_federal_states_turbines = {
182
        "Schleswig-Holstein": "E-126",
183
        "Bremen": "E-126",
184
        "Hamburg": "E-126",
185
        "Mecklenburg-Vorpommern": "E-126",
186
        "Niedersachsen": "E-126",
187
        "Berlin": "E-141",
188
        "Brandenburg": "E-141",
189
        "Hessen": "E-141",
190
        "Nordrhein-Westfalen": "E-141",
191
        "Sachsen": "E-141",
192
        "Sachsen-Anhalt": "E-141",
193
        "Thüringen": "E-141",
194
        "Baden-Württemberg": "E-141",
195
        "Bayern": "E-141",
196
        "Rheinland-Pfalz": "E-141",
197
        "Saarland": "E-141",
198
    }
199
200
    # Select weather cells and federal states
201
    weather_cells = federal_states_per_weather_cell()
202
203
    # Assign turbine type per federal state
204
    weather_cells["wind_turbine"] = weather_cells["federal_state"].map(
205
        map_federal_states_turbines
206
    )
207
208
    return weather_cells
209
210
211
def feedin_per_turbine():
212
    """Calculate feedin timeseries per turbine type and weather cell
213
214
    Returns
215
    -------
216
    gdf : GeoPandas.GeoDataFrame
217
        Feed-in timeseries per turbine type and weather cell
218
219
    """
220
221
    # Select weather data for Germany
222
    cutout = import_cutout(boundary="Germany")
223
224
    gdf = gpd.GeoDataFrame(geometry=cutout.grid_cells(), crs=4326)
225
226
    # Calculate feedin-timeseries for E-141
227
    # source:
228
    # https://openenergy-platform.org/dataedit/view/supply/wind_turbine_library
229
    turbine_e141 = {
230
        "name": "E141 4200 kW",
231
        "hub_height": 129,
232
        "P": 4.200,
233
        "V": np.arange(1, 26, dtype=float),
234
        "POW": np.array(
235
            [
236
                0.0,
237
                0.022,
238
                0.104,
239
                0.26,
240
                0.523,
241
                0.92,
242
                1.471,
243
                2.151,
244
                2.867,
245
                3.481,
246
                3.903,
247
                4.119,
248
                4.196,
249
                4.2,
250
                4.2,
251
                4.2,
252
                4.2,
253
                4.2,
254
                4.2,
255
                4.2,
256
                4.2,
257
                4.2,
258
                4.2,
259
                4.2,
260
                4.2,
261
            ]
262
        ),
263
    }
264
    ts_e141 = cutout.wind(
265
        turbine_e141, per_unit=True, shapes=cutout.grid_cells()
266
    )
267
268
    gdf["E-141"] = ts_e141.to_pandas().transpose().values.tolist()
269
270
    # Calculate feedin-timeseries for E-126
271
    # source:
272
    # https://openenergy-platform.org/dataedit/view/supply/wind_turbine_library
273
    turbine_e126 = {
274
        "name": "E126 4200 kW",
275
        "hub_height": 159,
276
        "P": 4.200,
277
        "V": np.arange(1, 26, dtype=float),
278
        "POW": np.array(
279
            [
280
                0.0,
281
                0.0,
282
                0.058,
283
                0.185,
284
                0.4,
285
                0.745,
286
                1.2,
287
                1.79,
288
                2.45,
289
                3.12,
290
                3.66,
291
                4.0,
292
                4.15,
293
                4.2,
294
                4.2,
295
                4.2,
296
                4.2,
297
                4.2,
298
                4.2,
299
                4.2,
300
                4.2,
301
                4.2,
302
                4.2,
303
                4.2,
304
                4.2,
305
            ]
306
        ),
307
    }
308
    ts_e126 = cutout.wind(
309
        turbine_e126, per_unit=True, shapes=cutout.grid_cells()
310
    )
311
312
    gdf["E-126"] = ts_e126.to_pandas().transpose().values.tolist()
313
314
    return gdf
315
316
317
def wind():
318
    """Insert feed-in timeseries for wind onshore turbines to database
319
320
    Returns
321
    -------
322
    None.
323
324
    """
325
326
    cfg = egon.data.config.datasets()["renewable_feedin"]["targets"]
327
328
    # Get weather cells with turbine type
329
    weather_cells = turbine_per_weather_cell()
330
    weather_cells = weather_cells[weather_cells.wind_turbine.notnull()]
331
332
    # Calculate feedin timeseries per turbine and weather cell
333
    timeseries_per_turbine = feedin_per_turbine()
334
335
    # Join weather cells and feedin-timeseries
336
    timeseries = gpd.sjoin(weather_cells, timeseries_per_turbine)[
337
        ["E-141", "E-126"]
338
    ]
339
340
    weather_year = get_sector_parameters("global", "eGon2035")["weather_year"]
341
342
    df = pd.DataFrame(
343
        index=weather_cells.index,
344
        columns=["weather_year", "carrier", "feedin"],
345
        data={"weather_year": weather_year, "carrier": "wind_onshore"},
346
    )
347
348
    # Insert feedin for selected turbine per weather cell
349
    for turbine in ["E-126", "E-141"]:
350
        idx = weather_cells.index[
351
            (weather_cells.wind_turbine == turbine)
352
            & (weather_cells.index.isin(timeseries.index))
353
        ]
354
        df.loc[idx, "feedin"] = timeseries.loc[idx, turbine].values
355
356
    db.execute_sql(
357
        f"""
358
                   DELETE FROM {cfg['feedin_table']['schema']}.
359
                   {cfg['feedin_table']['table']}
360
                   WHERE carrier = 'wind_onshore'"""
361
    )
362
363
    # Insert values into database
364
    df.to_sql(
365
        cfg["feedin_table"]["table"],
366
        schema=cfg["feedin_table"]["schema"],
367
        con=db.engine(),
368
        if_exists="append",
369
    )
370
371
372
def wind_offshore():
373
    """Insert feed-in timeseries for wind offshore turbines to database
374
375
    Returns
376
    -------
377
    None.
378
379
    """
380
381
    # Get offshore weather cells arround Germany
382
    weather_cells = offshore_weather_cells()
383
384
    # Select weather data for German coast
385
    cutout = import_cutout(boundary="Germany-offshore")
386
387
    # Select weather year from cutout
388
    weather_year = cutout.name.split("-")[2]
389
390
    # Calculate feedin timeseries
391
    ts_wind_offshore = cutout.wind(
392
        "Vestas_V164_7MW_offshore",
393
        per_unit=True,
394
        shapes=weather_cells.to_crs(4326).geom,
395
    )
396
397
    # Create dataframe and insert to database
398
    insert_feedin(ts_wind_offshore, "wind_offshore", weather_year)
399
400
401
def pv():
402
    """Insert feed-in timeseries for pv plants to database
403
404
    Returns
405
    -------
406
    None.
407
408
    """
409
410
    # Get weather cells in Germany
411
    weather_cells = weather_cells_in_germany()
412
413
    # Select weather data for Germany
414
    cutout = import_cutout(boundary="Germany")
415
416
    # Select weather year from cutout
417
    weather_year = cutout.name.split("-")[1]
418
419
    # Calculate feedin timeseries
420
    ts_pv = cutout.pv(
421
        "CSi",
422
        orientation={"slope": 35.0, "azimuth": 180.0},
423
        per_unit=True,
424
        shapes=weather_cells.to_crs(4326).geom,
425
    )
426
427
    # Create dataframe and insert to database
428
    insert_feedin(ts_pv, "pv", weather_year)
429
430
431
def solar_thermal():
432
    """Insert feed-in timeseries for pv plants to database
433
434
    Returns
435
    -------
436
    None.
437
438
    """
439
440
    # Get weather cells in Germany
441
    weather_cells = weather_cells_in_germany()
442
443
    # Select weather data for Germany
444
    cutout = import_cutout(boundary="Germany")
445
446
    # Select weather year from cutout
447
    weather_year = cutout.name.split("-")[1]
448
449
    # Calculate feedin timeseries
450
    ts_solar_thermal = cutout.solar_thermal(
451
        clearsky_model="simple",
452
        orientation={"slope": 45.0, "azimuth": 180.0},
453
        per_unit=True,
454
        shapes=weather_cells.to_crs(4326).geom,
455
        capacity_factor=False,
456
    )
457
458
    # Create dataframe and insert to database
459
    insert_feedin(ts_solar_thermal, "solar_thermal", weather_year)
460
461
462
def heat_pump_cop():
463
    """
464
    Calculate coefficient of performance for heat pumps according to
465
    T. Brown et al: "Synergies of sector coupling and transmission
466
    reinforcement in a cost-optimised, highlyrenewable European energy system",
467
    2018, p. 8
468
469
    Returns
470
    -------
471
    None.
472
473
    """
474
    # Assume temperature of heating system to 55°C according to Brown et. al
475
    t_sink = 55
476
477
    carrier = "heat_pump_cop"
478
479
    # Load configuration
480
    cfg = egon.data.config.datasets()["renewable_feedin"]
481
482
    # Get weather cells in Germany
483
    weather_cells = weather_cells_in_germany()
484
485
    # Select weather data for Germany
486
    cutout = import_cutout(boundary="Germany")
487
488
    # Select weather year from cutout
489
    weather_year = cutout.name.split("-")[1]
490
491
    # Calculate feedin timeseries
492
    temperature = cutout.temperature(
493
        shapes=weather_cells.to_crs(4326).geom
494
    ).transpose()
495
496
    t_source = temperature.to_pandas()
497
498
    delta_t = t_sink - t_source
499
500
    # Calculate coefficient of performance for air sourced heat pumps
501
    # according to Brown et. al
502
    cop = 6.81 - 0.121 * delta_t + 0.00063 * delta_t**2
503
504
    df = pd.DataFrame(
505
        index=temperature.to_pandas().index,
506
        columns=["weather_year", "carrier", "feedin"],
507
        data={"weather_year": weather_year, "carrier": carrier},
508
    )
509
510
    df.feedin = cop.values.tolist()
511
512
    # Delete existing rows for carrier
513
    db.execute_sql(
514
        f"""
515
                   DELETE FROM {cfg['targets']['feedin_table']['schema']}.
516
                   {cfg['targets']['feedin_table']['table']}
517
                   WHERE carrier = '{carrier}'"""
518
    )
519
520
    # Insert values into database
521
    df.to_sql(
522
        cfg["targets"]["feedin_table"]["table"],
523
        schema=cfg["targets"]["feedin_table"]["schema"],
524
        con=db.engine(),
525
        if_exists="append",
526
    )
527
528
529
def insert_feedin(data, carrier, weather_year):
530
    """Insert feedin data into database
531
532
    Parameters
533
    ----------
534
    data : xarray.core.dataarray.DataArray
535
        Feedin timeseries data
536
    carrier : str
537
        Name of energy carrier
538
    weather_year : int
539
        Selected weather year
540
541
    Returns
542
    -------
543
    None.
544
545
    """
546
    # Transpose DataFrame
547
    data = data.transpose().to_pandas()
548
549
    # Load configuration
550
    cfg = egon.data.config.datasets()["renewable_feedin"]
551
552
    # Initialize DataFrame
553
    df = pd.DataFrame(
554
        index=data.index,
555
        columns=["weather_year", "carrier", "feedin"],
556
        data={"weather_year": weather_year, "carrier": carrier},
557
    )
558
559
    # Convert solar thermal data from W/m^2 to MW/(1000m^2) = kW/m^2
560
    if carrier == "solar_thermal":
561
        data *= 1e-3
562
563
    # Insert feedin into DataFrame
564
    df.feedin = data.values.tolist()
565
566
    # Delete existing rows for carrier
567
    db.execute_sql(
568
        f"""
569
                   DELETE FROM {cfg['targets']['feedin_table']['schema']}.
570
                   {cfg['targets']['feedin_table']['table']}
571
                   WHERE carrier = '{carrier}'"""
572
    )
573
574
    # Insert values into database
575
    df.to_sql(
576
        cfg["targets"]["feedin_table"]["table"],
577
        schema=cfg["targets"]["feedin_table"]["schema"],
578
        con=db.engine(),
579
        if_exists="append",
580
    )
581
582
583
def mapping_zensus_weather():
584
    """Perform mapping between era5 weather cell and zensus grid"""
585
586
    with db.session_scope() as session:
587
        cells_query = session.query(
588
            DestatisZensusPopulationPerHa.id.label("zensus_population_id"),
589
            DestatisZensusPopulationPerHa.geom_point,
590
        )
591
592
    gdf_zensus_population = gpd.read_postgis(
593
        cells_query.statement,
594
        cells_query.session.bind,
595
        index_col=None,
596
        geom_col="geom_point",
597
    )
598
599
    with db.session_scope() as session:
600
        cells_query = session.query(EgonEra5Cells.w_id, EgonEra5Cells.geom)
601
602
    gdf_weather_cell = gpd.read_postgis(
603
        cells_query.statement,
604
        cells_query.session.bind,
605
        index_col=None,
606
        geom_col="geom",
607
    )
608
    # CRS is 4326
609
    gdf_weather_cell = gdf_weather_cell.to_crs(epsg=3035)
610
611
    gdf_zensus_weather = gdf_zensus_population.sjoin(
612
        gdf_weather_cell, how="left", predicate="within"
613
    )
614
615
    MapZensusWeatherCell.__table__.drop(bind=engine, checkfirst=True)
616
    MapZensusWeatherCell.__table__.create(bind=engine, checkfirst=True)
617
618
    # Write mapping into db
619
    with db.session_scope() as session:
620
        session.bulk_insert_mappings(
621
            MapZensusWeatherCell,
622
            gdf_zensus_weather[["zensus_population_id", "w_id"]].to_dict(
623
                orient="records"
624
            ),
625
        )
626
627
628 View Code Duplication
def add_metadata():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
629
    """Add metdata to supply.egon_era5_renewable_feedin
630
631
    Returns
632
    -------
633
    None.
634
635
    """
636
637
    # Import column names and datatypes
638
    fields = [
639
        {
640
            "description": "Weather cell index",
641
            "name": "w_id",
642
            "type": "integer",
643
            "unit": "none",
644
        },
645
        {
646
            "description": "Weather year",
647
            "name": "weather_year",
648
            "type": "integer",
649
            "unit": "none",
650
        },
651
        {
652
            "description": "Energy carrier",
653
            "name": "carrier",
654
            "type": "string",
655
            "unit": "none",
656
        },
657
        {
658
            "description": "Weather-dependent feedin timeseries",
659
            "name": "feedin",
660
            "type": "array",
661
            "unit": "p.u.",
662
        },
663
    ]
664
665
    meta = {
666
        "name": "supply.egon_era5_renewable_feedin",
667
        "title": "eGon feedin timeseries for RES",
668
        "id": "WILL_BE_SET_AT_PUBLICATION",
669
        "description": "Weather-dependent feedin timeseries for RES",
670
        "language": ["EN"],
671
        "publicationDate": datetime.date.today().isoformat(),
672
        "context": context(),
673
        "spatial": {
674
            "location": None,
675
            "extent": "Germany",
676
            "resolution": None,
677
        },
678
        "sources": [
679
            sources()["era5"],
680
            sources()["vg250"],
681
            sources()["egon-data"],
682
        ],
683
        "licenses": [
684
            license_ccby(
685
                "© Bundesamt für Kartographie und Geodäsie 2020 (Daten verändert); "
686
                "© Copernicus Climate Change Service (C3S) Climate Data Store "
687
                "© Jonathan Amme, Clara Büttner, Ilka Cußmann, Julian Endres, Carlos Epia, Stephan Günther, Ulf Müller, Amélia Nadal, Guido Pleßmann, Francesco Witte",
688
            )
689
        ],
690
        "contributors": [
691
            {
692
                "title": "Clara Büttner",
693
                "email": "http://github.com/ClaraBuettner",
694
                "date": time.strftime("%Y-%m-%d"),
695
                "object": None,
696
                "comment": "Imported data",
697
            },
698
        ],
699
        "resources": [
700
            {
701
                "profile": "tabular-data-resource",
702
                "name": "supply.egon_scenario_capacities",
703
                "path": None,
704
                "format": "PostgreSQL",
705
                "encoding": "UTF-8",
706
                "schema": {
707
                    "fields": fields,
708
                    "primaryKey": ["index"],
709
                    "foreignKeys": [],
710
                },
711
                "dialect": {"delimiter": None, "decimalSeparator": "."},
712
            }
713
        ],
714
        "metaMetadata": meta_metadata(),
715
    }
716
717
    # Create json dump
718
    meta_json = "'" + json.dumps(meta) + "'"
719
720
    # Add metadata as a comment to the table
721
    db.submit_comment(
722
        meta_json,
723
        EgonRenewableFeedIn.__table__.schema,
724
        EgonRenewableFeedIn.__table__.name,
725
    )
726