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

mapping_zensus_weather()   A

Complexity

Conditions 1

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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