Passed
Pull Request — dev (#846)
by
unknown
01:37
created

mapping_zensus_weather()   A

Complexity

Conditions 4

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

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