Passed
Pull Request — dev (#568)
by
unknown
01:43
created

data.datasets.renewable_feedin.wind()   A

Complexity

Conditions 2

Size

Total Lines 52
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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