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

offshore_weather_cells()   A

Complexity

Conditions 1

Size

Total Lines 21
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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