import_gas_gen_egon100()   B
last analyzed

Complexity

Conditions 3

Size

Total Lines 144
Code Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 85
dl 0
loc 144
rs 7.4909
c 0
b 0
f 0
cc 3
nop 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
"""The central module containing all code dealing with the distribution and
2
allocation of data on conventional and renewable power plants.
3
"""
4
5
from pathlib import Path
6
import logging
7
8
from geoalchemy2 import Geometry
9
from shapely.geometry import Point
10
from sqlalchemy import BigInteger, Column, Float, Integer, Sequence, String
11
from sqlalchemy.dialects.postgresql import JSONB
12
from sqlalchemy.ext.declarative import declarative_base
13
from sqlalchemy.orm import sessionmaker
14
import geopandas as gpd
15
import numpy as np
16
import pandas as pd
17
18
from egon.data import db, logger
19
from egon.data.datasets import Dataset, wrapped_partial
20
from egon.data.datasets.mastr import (
21
    WORKING_DIR_MASTR_NEW,
22
    WORKING_DIR_MASTR_OLD,
23
)
24
from egon.data.datasets.power_plants.conventional import (
25
    match_nep_no_chp,
26
    select_nep_power_plants,
27
    select_no_chp_combustion_mastr,
28
)
29
from egon.data.datasets.power_plants.mastr import (
30
    EgonPowerPlantsBiomass,
31
    EgonPowerPlantsHydro,
32
    EgonPowerPlantsPv,
33
    EgonPowerPlantsWind,
34
    import_mastr,
35
)
36
from egon.data.datasets.power_plants.pv_rooftop import pv_rooftop_per_mv_grid
37
from egon.data.datasets.power_plants.pv_rooftop_buildings import (
38
    pv_rooftop_to_buildings,
39
)
40
import egon.data.config
41
import egon.data.datasets.power_plants.assign_weather_data as assign_weather_data  # noqa: E501
42
import egon.data.datasets.power_plants.metadata as pp_metadata
43
import egon.data.datasets.power_plants.pv_ground_mounted as pv_ground_mounted
44
import egon.data.datasets.power_plants.wind_farms as wind_onshore
45
import egon.data.datasets.power_plants.wind_offshore as wind_offshore
46
47
Base = declarative_base()
48
49
50 View Code Duplication
class EgonPowerPlants(Base):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
51
    __tablename__ = "egon_power_plants"
52
    __table_args__ = {"schema": "supply"}
53
    id = Column(BigInteger, Sequence("pp_seq"), primary_key=True)
54
    sources = Column(JSONB)
55
    source_id = Column(JSONB)
56
    carrier = Column(String)
57
    el_capacity = Column(Float)
58
    bus_id = Column(Integer)
59
    voltage_level = Column(Integer)
60
    weather_cell_id = Column(Integer)
61
    scenario = Column(String)
62
    geom = Column(Geometry("POINT", 4326), index=True)
63
64
65
def create_tables():
66
    """Create tables for power plant data
67
    Returns
68
    -------
69
    None.
70
    """
71
72
    # Tables for future scenarios
73
    cfg = egon.data.config.datasets()["power_plants"]
74
    db.execute_sql(f"CREATE SCHEMA IF NOT EXISTS {cfg['target']['schema']};")
75
    engine = db.engine()
76
    db.execute_sql(
77
        f"""DROP TABLE IF EXISTS
78
        {cfg['target']['schema']}.{cfg['target']['table']}"""
79
    )
80
81
    db.execute_sql("""DROP SEQUENCE IF EXISTS pp_seq""")
82
    EgonPowerPlants.__table__.create(bind=engine, checkfirst=True)
83
84
    # Tables for status quo
85
    tables = [
86
        EgonPowerPlantsWind,
87
        EgonPowerPlantsPv,
88
        EgonPowerPlantsBiomass,
89
        EgonPowerPlantsHydro,
90
    ]
91
    for t in tables:
92
        db.execute_sql(
93
            f"""
94
            DROP TABLE IF EXISTS {t.__table_args__['schema']}.
95
            {t.__tablename__} CASCADE;
96
            """
97
        )
98
        t.__table__.create(bind=engine, checkfirst=True)
99
100
101
def scale_prox2now(df, target, level="federal_state"):
102
    """Scale installed capacities linear to status quo power plants
103
104
    Parameters
105
    ----------
106
    df : pandas.DataFrame
107
        Status Quo power plants
108
    target : pandas.Series
109
        Target values for future scenario
110
    level : str, optional
111
        Scale per 'federal_state' or 'country'. The default is 'federal_state'.
112
113
    Returns
114
    -------
115
    df : pandas.DataFrame
116
        Future power plants
117
118
    """
119
    if level == "federal_state":
120
        df.loc[:, "Nettonennleistung"] = (
121
            df.groupby(df.Bundesland)
122
            .Nettonennleistung.apply(lambda grp: grp / grp.sum())
123
            .mul(target[df.Bundesland.values].values)
124
        )
125
    else:
126
        df.loc[:, "Nettonennleistung"] = df.Nettonennleistung * (
127
            target / df.Nettonennleistung.sum()
128
        )
129
130
    df = df[df.Nettonennleistung > 0]
131
132
    return df
133
134
135
def select_target(carrier, scenario):
136
    """Select installed capacity per scenario and carrier
137
138
    Parameters
139
    ----------
140
    carrier : str
141
        Name of energy carrier
142
    scenario : str
143
        Name of scenario
144
145
    Returns
146
    -------
147
    pandas.Series
148
        Target values for carrier and scenario
149
150
    """
151
    cfg = egon.data.config.datasets()["power_plants"]
152
153
    return (
154
        pd.read_sql(
155
            f"""SELECT DISTINCT ON (b.gen)
156
                         REPLACE(REPLACE(b.gen, '-', ''), 'ü', 'ue') as state,
157
                         a.capacity
158
                         FROM {cfg['sources']['capacities']} a,
159
                         {cfg['sources']['geom_federal_states']} b
160
                         WHERE a.nuts = b.nuts
161
                         AND scenario_name = '{scenario}'
162
                         AND carrier = '{carrier}'
163
                         AND b.gen NOT IN ('Baden-Württemberg (Bodensee)',
164
                                           'Bayern (Bodensee)')""",
165
            con=db.engine(),
166
        )
167
        .set_index("state")
168
        .capacity
169
    )
170
171
172
def filter_mastr_geometry(mastr, federal_state=None):
173
    """Filter data from MaStR by geometry
174
175
    Parameters
176
    ----------
177
    mastr : pandas.DataFrame
178
        All power plants listed in MaStR
179
    federal_state : str or None
180
        Name of federal state whoes power plants are returned.
181
        If None, data for Germany is returned
182
183
    Returns
184
    -------
185
    mastr_loc : pandas.DataFrame
186
        Power plants listed in MaStR with geometry inside German boundaries
187
188
    """
189
    cfg = egon.data.config.datasets()["power_plants"]
190
191
    if type(mastr) == pd.core.frame.DataFrame:
192
        # Drop entries without geometry for insert
193
        mastr_loc = mastr[
194
            mastr.Laengengrad.notnull() & mastr.Breitengrad.notnull()
195
        ]
196
197
        # Create geodataframe
198
        mastr_loc = gpd.GeoDataFrame(
199
            mastr_loc,
200
            geometry=gpd.points_from_xy(
201
                mastr_loc.Laengengrad, mastr_loc.Breitengrad, crs=4326
202
            ),
203
        )
204
    else:
205
        mastr_loc = mastr.copy()
206
207
    # Drop entries outside of germany or federal state
208
    if not federal_state:
209
        sql = f"SELECT geometry as geom FROM {cfg['sources']['geom_germany']}"
210
    else:
211
        sql = f"""
212
        SELECT geometry as geom
213
        FROM boundaries.vg250_lan_union
214
        WHERE REPLACE(REPLACE(gen, '-', ''), 'ü', 'ue') = '{federal_state}'"""
215
216
    mastr_loc = (
217
        gpd.sjoin(
218
            gpd.read_postgis(sql, con=db.engine()).to_crs(4326),
219
            mastr_loc,
220
            how="right",
221
        )
222
        .query("index_left==0")
223
        .drop("index_left", axis=1)
224
    )
225
226
    return mastr_loc
227
228
229
def insert_biomass_plants(scenario):
230
    """Insert biomass power plants of future scenario
231
232
    Parameters
233
    ----------
234
    scenario : str
235
        Name of scenario.
236
237
    Returns
238
    -------
239
    None.
240
241
    """
242
    cfg = egon.data.config.datasets()["power_plants"]
243
244
    # import target values
245
    target = select_target("biomass", scenario)
246
247
    # import data for MaStR
248
    mastr = pd.read_csv(
249
        WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_biomass"]
250
    ).query("EinheitBetriebsstatus=='InBetrieb'")
251
252
    # Drop entries without federal state or 'AusschließlichWirtschaftszone'
253
    mastr = mastr[
254
        mastr.Bundesland.isin(
255
            pd.read_sql(
256
                f"""SELECT DISTINCT ON (gen)
257
        REPLACE(REPLACE(gen, '-', ''), 'ü', 'ue') as states
258
        FROM {cfg['sources']['geom_federal_states']}""",
259
                con=db.engine(),
260
            ).states.values
261
        )
262
    ]
263
264
    # Scaling will be done per federal state in case of eGon2035 scenario.
265
    if scenario == "eGon2035":
266
        level = "federal_state"
267
    else:
268
        level = "country"
269
270
    # Choose only entries with valid geometries inside DE/test mode
271
    mastr_loc = filter_mastr_geometry(mastr).set_geometry("geometry")
272
273
    # Scale capacities to meet target values
274
    mastr_loc = scale_prox2now(mastr_loc, target, level=level)
275
276
    # Assign bus_id
277
    if len(mastr_loc) > 0:
278
        mastr_loc["voltage_level"] = assign_voltage_level(
279
            mastr_loc, cfg, WORKING_DIR_MASTR_OLD
280
        )
281
        mastr_loc = assign_bus_id(mastr_loc, cfg)
282
283
    # Insert entries with location
284
    session = sessionmaker(bind=db.engine())()
285
286
    for i, row in mastr_loc.iterrows():
287
        if not row.ThermischeNutzleistung > 0:
288
            entry = EgonPowerPlants(
289
                sources={"el_capacity": "MaStR scaled with NEP 2021"},
290
                source_id={"MastrNummer": row.EinheitMastrNummer},
291
                carrier="biomass",
292
                el_capacity=row.Nettonennleistung,
293
                scenario=scenario,
294
                bus_id=row.bus_id,
295
                voltage_level=row.voltage_level,
296
                geom=f"SRID=4326;POINT({row.Laengengrad} {row.Breitengrad})",
297
            )
298
            session.add(entry)
299
300
    session.commit()
301
302
303
def insert_hydro_plants(scenario):
304
    """Insert hydro power plants of future scenario.
305
306
    Hydro power plants are diveded into run_of_river and reservoir plants
307
    according to Marktstammdatenregister.
308
    Additional hydro technologies (e.g. turbines inside drinking water
309
    systems) are not considered.
310
311
    Parameters
312
    ----------
313
    scenario : str
314
        Name of scenario.
315
316
    Returns
317
    -------
318
    None.
319
320
    """
321
    cfg = egon.data.config.datasets()["power_plants"]
322
323
    # Map MaStR carriers to eGon carriers
324
    map_carrier = {
325
        "run_of_river": ["Laufwasseranlage"],
326
        "reservoir": ["Speicherwasseranlage"],
327
    }
328
329
    for carrier in map_carrier.keys():
330
        # import target values
331
        if scenario == "eGon100RE":
332
            try:
333
                target = pd.read_sql(
334
                    f"""SELECT capacity FROM supply.egon_scenario_capacities
335
                            WHERE scenario_name = '{scenario}'
336
                            AND carrier = '{carrier}'
337
                            """,
338
                    con=db.engine(),
339
                ).capacity[0]
340
            except:
341
                logger.info(
342
                    f"No assigned capacity for {carrier} in {scenario}"
343
                )
344
                continue
345
346
        elif scenario == "eGon2035":
347
            target = select_target(carrier, scenario)
348
349
        # import data for MaStR
350
        mastr = pd.read_csv(
351
            WORKING_DIR_MASTR_NEW / cfg["sources"]["mastr_hydro"]
352
        ).query("EinheitBetriebsstatus=='InBetrieb'")
353
354
        # Choose only plants with specific carriers
355
        mastr = mastr[mastr.ArtDerWasserkraftanlage.isin(map_carrier[carrier])]
356
357
        # Drop entries without federal state or 'AusschließlichWirtschaftszone'
358
        mastr = mastr[
359
            mastr.Bundesland.isin(
360
                pd.read_sql(
361
                    f"""SELECT DISTINCT ON (gen)
362
            REPLACE(REPLACE(gen, '-', ''), 'ü', 'ue') as states
363
            FROM {cfg['sources']['geom_federal_states']}""",
364
                    con=db.engine(),
365
                ).states.values
366
            )
367
        ]
368
369
        # Scaling will be done per federal state in case of eGon2035 scenario.
370
        if scenario == "eGon2035":
371
            level = "federal_state"
372
        else:
373
            level = "country"
374
375
        # Scale capacities to meet target values
376
        mastr = scale_prox2now(mastr, target, level=level)
0 ignored issues
show
introduced by
The variable target does not seem to be defined in case the for loop on line 329 is not entered. Are you sure this can never be the case?
Loading history...
377
378
        # Choose only entries with valid geometries inside DE/test mode
379
        mastr_loc = filter_mastr_geometry(mastr).set_geometry("geometry")
380
        # TODO: Deal with power plants without geometry
381
382
        # Assign bus_id and voltage level
383
        if len(mastr_loc) > 0:
384
            mastr_loc["voltage_level"] = assign_voltage_level(
385
                mastr_loc, cfg, WORKING_DIR_MASTR_NEW
386
            )
387
            mastr_loc = assign_bus_id(mastr_loc, cfg)
388
389
        # Insert entries with location
390
        session = sessionmaker(bind=db.engine())()
391
        for i, row in mastr_loc.iterrows():
392
            entry = EgonPowerPlants(
393
                sources={"el_capacity": "MaStR scaled with NEP 2021"},
394
                source_id={"MastrNummer": row.EinheitMastrNummer},
395
                carrier=carrier,
396
                el_capacity=row.Nettonennleistung,
397
                scenario=scenario,
398
                bus_id=row.bus_id,
399
                voltage_level=row.voltage_level,
400
                geom=f"SRID=4326;POINT({row.Laengengrad} {row.Breitengrad})",
401
            )
402
            session.add(entry)
403
404
        session.commit()
405
406
407
def assign_voltage_level(mastr_loc, cfg, mastr_working_dir):
408
    """Assigns voltage level to power plants.
409
410
    If location data inluding voltage level is available from
411
    Marktstammdatenregister, this is used. Otherwise the voltage level is
412
    assigned according to the electrical capacity.
413
414
    Parameters
415
    ----------
416
    mastr_loc : pandas.DataFrame
417
        Power plants listed in MaStR with geometry inside German boundaries
418
419
    Returns
420
    -------
421
    pandas.DataFrame
422
        Power plants including voltage_level
423
424
    """
425
    mastr_loc["Spannungsebene"] = np.nan
426
    mastr_loc["voltage_level"] = np.nan
427
428
    if "LokationMastrNummer" in mastr_loc.columns:
429
        # Adjust column names to format of MaStR location dataset
430
        if mastr_working_dir == WORKING_DIR_MASTR_OLD:
431
            cols = ["LokationMastrNummer", "Spannungsebene"]
432
        elif mastr_working_dir == WORKING_DIR_MASTR_NEW:
433
            cols = ["MaStRNummer", "Spannungsebene"]
434
        else:
435
            raise ValueError("Invalid MaStR working directory!")
436
437
        location = (
438
            pd.read_csv(
439
                mastr_working_dir / cfg["sources"]["mastr_location"],
440
                usecols=cols,
441
            )
442
            .rename(columns={"MaStRNummer": "LokationMastrNummer"})
443
            .set_index("LokationMastrNummer")
444
        )
445
446
        location = location[~location.index.duplicated(keep="first")]
447
448
        mastr_loc.loc[
449
            mastr_loc[
450
                mastr_loc.LokationMastrNummer.isin(location.index)
451
            ].index,
452
            "Spannungsebene",
453
        ] = location.Spannungsebene[
454
            mastr_loc[
455
                mastr_loc.LokationMastrNummer.isin(location.index)
456
            ].LokationMastrNummer
457
        ].values
458
459
        # Transfer voltage_level as integer from Spanungsebene
460
        map_voltage_levels = pd.Series(
461
            data={
462
                "Höchstspannung": 1,
463
                "Hoechstspannung": 1,
464
                "UmspannungZurHochspannung": 2,
465
                "Hochspannung": 3,
466
                "UmspannungZurMittelspannung": 4,
467
                "Mittelspannung": 5,
468
                "UmspannungZurNiederspannung": 6,
469
                "Niederspannung": 7,
470
            }
471
        )
472
473
        mastr_loc.loc[
474
            mastr_loc[mastr_loc["Spannungsebene"].notnull()].index,
475
            "voltage_level",
476
        ] = map_voltage_levels[
477
            mastr_loc.loc[
478
                mastr_loc[mastr_loc["Spannungsebene"].notnull()].index,
479
                "Spannungsebene",
480
            ].values
481
        ].values
482
483
    else:
484
        print(
485
            "No information about MaStR location available. "
486
            "All voltage levels are assigned using threshold values."
487
        )
488
489
    # If no voltage level is available from mastr, choose level according
490
    # to threshold values
491
492
    mastr_loc.voltage_level = assign_voltage_level_by_capacity(mastr_loc)
493
494
    return mastr_loc.voltage_level
495
496
497
def assign_voltage_level_by_capacity(mastr_loc):
498
499
    for i, row in mastr_loc[mastr_loc.voltage_level.isnull()].iterrows():
500
501
        if row.Nettonennleistung > 120:
502
            level = 1
503
        elif row.Nettonennleistung > 20:
504
            level = 3
505
        elif row.Nettonennleistung > 5.5:
506
            level = 4
507
        elif row.Nettonennleistung > 0.2:
508
            level = 5
509
        elif row.Nettonennleistung > 0.1:
510
            level = 6
511
        else:
512
            level = 7
513
514
        mastr_loc.loc[i, "voltage_level"] = level
515
516
    mastr_loc.voltage_level = mastr_loc.voltage_level.astype(int)
517
518
    return mastr_loc.voltage_level
519
520
521 View Code Duplication
def assign_bus_id(power_plants, cfg, drop_missing=False):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
522
    """Assigns bus_ids to power plants according to location and voltage level
523
524
    Parameters
525
    ----------
526
    power_plants : pandas.DataFrame
527
        Power plants including voltage level
528
529
    Returns
530
    -------
531
    power_plants : pandas.DataFrame
532
        Power plants including voltage level and bus_id
533
534
    """
535
536
    mv_grid_districts = db.select_geodataframe(
537
        f"""
538
        SELECT * FROM {cfg['sources']['egon_mv_grid_district']}
539
        """,
540
        epsg=4326,
541
    )
542
543
    ehv_grid_districts = db.select_geodataframe(
544
        f"""
545
        SELECT * FROM {cfg['sources']['ehv_voronoi']}
546
        """,
547
        epsg=4326,
548
    )
549
550
    # Assign power plants in hv and below to hvmv bus
551
    power_plants_hv = power_plants[power_plants.voltage_level >= 3].index
552
    if len(power_plants_hv) > 0:
553
        power_plants.loc[power_plants_hv, "bus_id"] = gpd.sjoin(
554
            power_plants[power_plants.index.isin(power_plants_hv)],
555
            mv_grid_districts,
556
        ).bus_id
557
558
    # Assign power plants in ehv to ehv bus
559
    power_plants_ehv = power_plants[power_plants.voltage_level < 3].index
560
561
    if len(power_plants_ehv) > 0:
562
        ehv_join = gpd.sjoin(
563
            power_plants[power_plants.index.isin(power_plants_ehv)],
564
            ehv_grid_districts,
565
        )
566
567
        if "bus_id_right" in ehv_join.columns:
568
            power_plants.loc[power_plants_ehv, "bus_id"] = gpd.sjoin(
569
                power_plants[power_plants.index.isin(power_plants_ehv)],
570
                ehv_grid_districts,
571
            ).bus_id_right
572
573
        else:
574
            power_plants.loc[power_plants_ehv, "bus_id"] = gpd.sjoin(
575
                power_plants[power_plants.index.isin(power_plants_ehv)],
576
                ehv_grid_districts,
577
            ).bus_id
578
579
    if drop_missing:
580
        power_plants = power_plants[~power_plants.bus_id.isnull()]
581
582
    # Assert that all power plants have a bus_id
583
    assert power_plants.bus_id.notnull().all(), f"""Some power plants are
584
    not attached to a bus: {power_plants[power_plants.bus_id.isnull()]}"""
585
586
    return power_plants
587
588
589
def insert_hydro_biomass():
590
    """Insert hydro and biomass power plants in database
591
592
    Returns
593
    -------
594
    None.
595
596
    """
597
    cfg = egon.data.config.datasets()["power_plants"]
598
    db.execute_sql(
599
        f"""
600
        DELETE FROM {cfg['target']['schema']}.{cfg['target']['table']}
601
        WHERE carrier IN ('biomass', 'reservoir', 'run_of_river')
602
        AND scenario IN ('eGon2035', 'eGon100RE')
603
        """
604
    )
605
606
    s = egon.data.config.settings()["egon-data"]["--scenarios"]
607
    scenarios = []
608
    if "eGon2035" in s:
609
        scenarios.append("eGon2035")
610
        insert_biomass_plants("eGon2035")
611
    if "eGon100RE" in s:
612
        scenarios.append("eGon100RE")
613
614
    for scenario in scenarios:
615
        insert_hydro_plants(scenario)
616
617
618
def allocate_conventional_non_chp_power_plants():
619
    """Allocate conventional power plants without CHPs based on the NEP target
620
    values and data from power plant registry (MaStR) by assigning them in a
621
    cascaded manner.
622
623
    Returns
624
    -------
625
    None.
626
627
    """
628
    # This function is only designed to work for the eGon2035 scenario
629
    if (
630
        "eGon2035"
631
        not in egon.data.config.settings()["egon-data"]["--scenarios"]
632
    ):
633
        return
634
635
    carrier = ["oil", "gas"]
636
637
    cfg = egon.data.config.datasets()["power_plants"]
638
639
    # Delete existing plants in the target table
640
    db.execute_sql(
641
        f"""
642
         DELETE FROM {cfg ['target']['schema']}.{cfg ['target']['table']}
643
         WHERE carrier IN ('gas', 'oil')
644
         AND scenario='eGon2035';
645
         """
646
    )
647
648
    for carrier in carrier:
649
650
        nep = select_nep_power_plants(carrier)
651
652
        if nep.empty:
653
            print(f"DataFrame from NEP for carrier {carrier} is empty!")
654
655
        else:
656
657
            mastr = select_no_chp_combustion_mastr(carrier)
658
659
            # Assign voltage level to MaStR
660
            mastr["voltage_level"] = assign_voltage_level(
661
                mastr.rename({"el_capacity": "Nettonennleistung"}, axis=1),
662
                cfg,
663
                WORKING_DIR_MASTR_OLD,
664
            )
665
666
            # Initalize DataFrame for matching power plants
667
            matched = gpd.GeoDataFrame(
668
                columns=[
669
                    "carrier",
670
                    "el_capacity",
671
                    "scenario",
672
                    "geometry",
673
                    "MaStRNummer",
674
                    "source",
675
                    "voltage_level",
676
                ]
677
            )
678
679
            # Match combustion plants of a certain carrier from NEP list
680
            # using PLZ and capacity
681
            matched, mastr, nep = match_nep_no_chp(
682
                nep,
683
                mastr,
684
                matched,
685
                buffer_capacity=0.1,
686
                consider_carrier=False,
687
            )
688
689
            # Match plants from NEP list using city and capacity
690
            matched, mastr, nep = match_nep_no_chp(
691
                nep,
692
                mastr,
693
                matched,
694
                buffer_capacity=0.1,
695
                consider_carrier=False,
696
                consider_location="city",
697
            )
698
699
            # Match plants from NEP list using plz,
700
            # neglecting the capacity
701
            matched, mastr, nep = match_nep_no_chp(
702
                nep,
703
                mastr,
704
                matched,
705
                consider_location="plz",
706
                consider_carrier=False,
707
                consider_capacity=False,
708
            )
709
710
            # Match plants from NEP list using city,
711
            # neglecting the capacity
712
            matched, mastr, nep = match_nep_no_chp(
713
                nep,
714
                mastr,
715
                matched,
716
                consider_location="city",
717
                consider_carrier=False,
718
                consider_capacity=False,
719
            )
720
721
            # Match remaining plants from NEP using the federal state
722
            matched, mastr, nep = match_nep_no_chp(
723
                nep,
724
                mastr,
725
                matched,
726
                buffer_capacity=0.1,
727
                consider_location="federal_state",
728
                consider_carrier=False,
729
            )
730
731
            # Match remaining plants from NEP using the federal state
732
            matched, mastr, nep = match_nep_no_chp(
733
                nep,
734
                mastr,
735
                matched,
736
                buffer_capacity=0.7,
737
                consider_location="federal_state",
738
                consider_carrier=False,
739
            )
740
741
            print(f"{matched.el_capacity.sum()} MW of {carrier} matched")
742
            print(f"{nep.c2035_capacity.sum()} MW of {carrier} not matched")
743
744
            matched.crs = "EPSG:4326"
745
746
            # Assign bus_id
747
            # Load grid district polygons
748
            mv_grid_districts = db.select_geodataframe(
749
                f"""
750
            SELECT * FROM {cfg['sources']['egon_mv_grid_district']}
751
            """,
752
                epsg=4326,
753
            )
754
755
            ehv_grid_districts = db.select_geodataframe(
756
                f"""
757
            SELECT * FROM {cfg['sources']['ehv_voronoi']}
758
            """,
759
                epsg=4326,
760
            )
761
762
            # Perform spatial joins for plants in ehv and hv level seperately
763
            power_plants_hv = gpd.sjoin(
764
                matched[matched.voltage_level >= 3],
765
                mv_grid_districts[["bus_id", "geom"]],
766
                how="left",
767
            ).drop(columns=["index_right"])
768
            power_plants_ehv = gpd.sjoin(
769
                matched[matched.voltage_level < 3],
770
                ehv_grid_districts[["bus_id", "geom"]],
771
                how="left",
772
            ).drop(columns=["index_right"])
773
774
            # Combine both dataframes
775
            power_plants = pd.concat([power_plants_hv, power_plants_ehv])
776
777
            # Insert into target table
778
            session = sessionmaker(bind=db.engine())()
779
            for i, row in power_plants.iterrows():
780
                entry = EgonPowerPlants(
781
                    sources={"el_capacity": row.source},
782
                    source_id={"MastrNummer": row.MaStRNummer},
783
                    carrier=row.carrier,
784
                    el_capacity=row.el_capacity,
785
                    voltage_level=row.voltage_level,
786
                    bus_id=row.bus_id,
787
                    scenario=row.scenario,
788
                    geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})",
789
                )
790
                session.add(entry)
791
            session.commit()
792
793
794
def allocate_other_power_plants():
795
    # This function is only designed to work for the eGon2035 scenario
796
    if (
797
        "eGon2035"
798
        not in egon.data.config.settings()["egon-data"]["--scenarios"]
799
    ):
800
        return
801
802
    # Get configuration
803
    cfg = egon.data.config.datasets()["power_plants"]
804
    boundary = egon.data.config.settings()["egon-data"]["--dataset-boundary"]
805
806
    db.execute_sql(
807
        f"""
808
        DELETE FROM {cfg['target']['schema']}.{cfg['target']['table']}
809
        WHERE carrier ='others'
810
        """
811
    )
812
813
    # Define scenario, carrier 'others' is only present in 'eGon2035'
814
    scenario = "eGon2035"
815
816
    # Select target values for carrier 'others'
817
    target = db.select_dataframe(
818
        f"""
819
        SELECT sum(capacity) as capacity, carrier, scenario_name, nuts
820
            FROM {cfg['sources']['capacities']}
821
            WHERE scenario_name = '{scenario}'
822
            AND carrier = 'others'
823
            GROUP BY carrier, nuts, scenario_name;
824
        """
825
    )
826
827
    # Assign name of federal state
828
829
    map_states = {
830
        "DE1": "BadenWuerttemberg",
831
        "DEA": "NordrheinWestfalen",
832
        "DE7": "Hessen",
833
        "DE4": "Brandenburg",
834
        "DE5": "Bremen",
835
        "DEB": "RheinlandPfalz",
836
        "DEE": "SachsenAnhalt",
837
        "DEF": "SchleswigHolstein",
838
        "DE8": "MecklenburgVorpommern",
839
        "DEG": "Thueringen",
840
        "DE9": "Niedersachsen",
841
        "DED": "Sachsen",
842
        "DE6": "Hamburg",
843
        "DEC": "Saarland",
844
        "DE3": "Berlin",
845
        "DE2": "Bayern",
846
    }
847
848
    target = (
849
        target.replace({"nuts": map_states})
850
        .rename(columns={"nuts": "Bundesland"})
851
        .set_index("Bundesland")
852
    )
853
    target = target.capacity
854
855
    # Select 'non chp' power plants from mastr table
856
    mastr_combustion = select_no_chp_combustion_mastr("others")
857
858
    # Rename columns
859
    mastr_combustion = mastr_combustion.rename(
860
        columns={
861
            "carrier": "Energietraeger",
862
            "plz": "Postleitzahl",
863
            "city": "Ort",
864
            "federal_state": "Bundesland",
865
            "el_capacity": "Nettonennleistung",
866
        }
867
    )
868
869
    # Select power plants representing carrier 'others' from MaStR files
870
    mastr_sludge = pd.read_csv(
871
        WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_gsgk"]
872
    ).query(
873
        """EinheitBetriebsstatus=='InBetrieb'and Energietraeger=='Klärschlamm'"""  # noqa: E501
874
    )
875
    mastr_geothermal = pd.read_csv(
876
        WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_gsgk"]
877
    ).query(
878
        "EinheitBetriebsstatus=='InBetrieb' and Energietraeger=='Geothermie' "
879
        "and Technologie == 'ORCOrganicRankineCycleAnlage'"
880
    )
881
882
    mastr_sg = pd.concat([mastr_sludge, mastr_geothermal])
883
884
    # Insert geometry column
885
    mastr_sg = mastr_sg[~(mastr_sg["Laengengrad"].isnull())]
886
    mastr_sg = gpd.GeoDataFrame(
887
        mastr_sg,
888
        geometry=gpd.points_from_xy(
889
            mastr_sg["Laengengrad"], mastr_sg["Breitengrad"], crs=4326
890
        ),
891
    )
892
893
    # Exclude columns which are not essential
894
    mastr_sg = mastr_sg.filter(
895
        [
896
            "EinheitMastrNummer",
897
            "Nettonennleistung",
898
            "geometry",
899
            "Energietraeger",
900
            "Postleitzahl",
901
            "Ort",
902
            "Bundesland",
903
        ],
904
        axis=1,
905
    )
906
    # Rename carrier
907
    mastr_sg.Energietraeger = "others"
908
909
    # Change data type
910
    mastr_sg["Postleitzahl"] = mastr_sg["Postleitzahl"].astype(int)
911
912
    # Capacity in MW
913
    mastr_sg.loc[:, "Nettonennleistung"] *= 1e-3
914
915
    # Merge different sources to one df
916
    mastr_others = pd.concat([mastr_sg, mastr_combustion]).reset_index()
917
918
    # Delete entries outside Schleswig-Holstein for test mode
919
    if boundary == "Schleswig-Holstein":
920
        mastr_others = mastr_others[
921
            mastr_others["Bundesland"] == "SchleswigHolstein"
922
        ]
923
924
    # Scale capacities prox to now to meet target values
925
    mastr_prox = scale_prox2now(mastr_others, target, level="federal_state")
926
927
    # Assign voltage_level based on scaled capacity
928
    mastr_prox["voltage_level"] = np.nan
929
    mastr_prox["voltage_level"] = assign_voltage_level_by_capacity(mastr_prox)
930
931
    # Rename columns
932
    mastr_prox = mastr_prox.rename(
933
        columns={
934
            "Energietraeger": "carrier",
935
            "Postleitzahl": "plz",
936
            "Ort": "city",
937
            "Bundesland": "federal_state",
938
            "Nettonennleistung": "el_capacity",
939
        }
940
    )
941
942
    # Assign bus_id
943
    mastr_prox = assign_bus_id(mastr_prox, cfg)
944
    mastr_prox = mastr_prox.set_crs(4326, allow_override=True)
945
946
    # Insert into target table
947
    session = sessionmaker(bind=db.engine())()
948
    for i, row in mastr_prox.iterrows():
949
        entry = EgonPowerPlants(
950
            sources=row.el_capacity,
951
            source_id={"MastrNummer": row.EinheitMastrNummer},
952
            carrier=row.carrier,
953
            el_capacity=row.el_capacity,
954
            voltage_level=row.voltage_level,
955
            bus_id=row.bus_id,
956
            scenario=scenario,
957
            geom=f"SRID=4326; {row.geometry}",
958
        )
959
        session.add(entry)
960
    session.commit()
961
962
963
def discard_not_available_generators(gen, max_date):
964
    gen["decommissioning_date"] = pd.to_datetime(
965
        gen["decommissioning_date"]
966
    )
967
    gen["commissioning_date"] = pd.to_datetime(gen["commissioning_date"])
968
    # drop plants that are commissioned after the max date
969
    gen = gen[gen["commissioning_date"] < max_date]
970
971
    # drop decommissioned plants while keeping the ones decommissioned
972
    # after the max date
973
    gen.loc[(gen["decommissioning_date"] > max_date), "status"] = (
974
        "InBetrieb"
975
    )
976
977
    gen = gen.loc[
978
        gen["status"].isin(["InBetrieb", "VoruebergehendStillgelegt"])
979
    ]
980
981
    # drop unnecessary columns
982
    gen = gen.drop(columns=["commissioning_date", "decommissioning_date"])
983
984
    return gen
985
986
987
def fill_missing_bus_and_geom(
988
    gens, carrier, geom_municipalities, mv_grid_districts
989
):
990
    # drop generators without data to get geometry.
991
    drop_id = gens[
992
        (gens.geom.is_empty) & ~(gens.location.isin(geom_municipalities.index))
993
    ].index
994
    new_geom = gens["capacity"][
995
        (gens.geom.is_empty) & (gens.location.isin(geom_municipalities.index))
996
    ]
997
    logger.info(
998
        f"""{len(drop_id)} {carrier} generator(s) ({int(gens.loc[drop_id, 'capacity']
999
        .sum())}MW) were drop"""
1000
    )
1001
1002
    logger.info(
1003
        f"""{len(new_geom)} {carrier} generator(s) ({int(new_geom
1004
        .sum())}MW) received a geom based on location
1005
          """
1006
    )
1007
    gens.drop(index=drop_id, inplace=True)
1008
1009
    # assign missing geometries based on location and buses based on geom
1010
1011
    gens["geom"] = gens.apply(
1012
        lambda x: (
1013
            geom_municipalities.at[x["location"], "geom"]
1014
            if x["geom"].is_empty
1015
            else x["geom"]
1016
        ),
1017
        axis=1,
1018
    )
1019
    gens["bus_id"] = gens.sjoin(
1020
        mv_grid_districts[["bus_id", "geom"]], how="left"
1021
    ).bus_id_right.values
1022
1023
    gens = gens.dropna(subset=["bus_id"])
1024
    # convert geom to WKB
1025
    gens["geom"] = gens["geom"].to_wkt()
1026
1027
    return gens
1028
1029
1030
def power_plants_status_quo(scn_name="status2019"):
1031
    def convert_master_info(df):
1032
        # Add further information
1033
        df["sources"] = [{"el_capacity": "MaStR"}] * df.shape[0]
1034
        df["source_id"] = df["gens_id"].apply(lambda x: {"MastrNummer": x})
1035
        return df
1036
1037
    def log_insert_capacity(df, tech):
1038
        logger.info(
1039
            f"""
1040
            {len(df)} {tech} generators with a total installed capacity of
1041
            {int(df["el_capacity"].sum())} MW were inserted into the db
1042
              """
1043
        )
1044
1045
    con = db.engine()
1046
    cfg = egon.data.config.datasets()["power_plants"]
1047
1048
    db.execute_sql(
1049
        f"""
1050
        DELETE FROM {cfg['target']['schema']}.{cfg['target']['table']}
1051
        WHERE carrier IN ('wind_onshore', 'solar', 'biomass',
1052
                          'run_of_river', 'reservoir', 'solar_rooftop',
1053
                          'wind_offshore', 'nuclear', 'coal', 'lignite', 'oil',
1054
                          'gas')
1055
        AND scenario = '{scn_name}'
1056
        """
1057
    )
1058
1059
    # import municipalities to assign missing geom and bus_id
1060
    geom_municipalities = gpd.GeoDataFrame.from_postgis(
1061
        """
1062
        SELECT gen, ST_UNION(geometry) as geom
1063
        FROM boundaries.vg250_gem
1064
        GROUP BY gen
1065
        """,
1066
        con,
1067
        geom_col="geom",
1068
    ).set_index("gen")
1069
    geom_municipalities["geom"] = geom_municipalities["geom"].centroid
1070
1071
    mv_grid_districts = gpd.GeoDataFrame.from_postgis(
1072
        f"""
1073
        SELECT * FROM {cfg['sources']['egon_mv_grid_district']}
1074
        """,
1075
        con,
1076
    )
1077
    mv_grid_districts.geom = mv_grid_districts.geom.to_crs(4326)
1078
1079
    # Conventional non CHP
1080
    #  ###################
1081
    conv = get_conventional_power_plants_non_chp(scn_name)
1082
1083
    conv = fill_missing_bus_and_geom(
1084
        conv, "conventional", geom_municipalities, mv_grid_districts
1085
    )
1086
1087
    conv = conv.rename(columns={"capacity": "el_capacity"})
1088
1089
    # Write into DB
1090
    with db.session_scope() as session:
1091
        session.bulk_insert_mappings(
1092
            EgonPowerPlants,
1093
            conv.to_dict(orient="records"),
1094
        )
1095
1096
    log_insert_capacity(conv, tech="conventional non chp")
1097
1098
    # Hydro Power Plants
1099
    #  ###################
1100
    hydro = gpd.GeoDataFrame.from_postgis(
1101
        f"""SELECT *, city AS location FROM {cfg['sources']['hydro']}
1102
        WHERE plant_type IN ('Laufwasseranlage', 'Speicherwasseranlage')""",
1103
        con,
1104
        geom_col="geom",
1105
    )
1106
1107
    hydro = fill_missing_bus_and_geom(
1108
        hydro, "hydro", geom_municipalities, mv_grid_districts
1109
    )
1110
1111
    hydro = convert_master_info(hydro)
1112
    hydro["carrier"] = hydro["plant_type"].replace(
1113
        to_replace={
1114
            "Laufwasseranlage": "run_of_river",
1115
            "Speicherwasseranlage": "reservoir",
1116
        }
1117
    )
1118
    hydro["scenario"] = scn_name
1119
    hydro = hydro.rename(columns={"capacity": "el_capacity"})
1120
    hydro = hydro.drop(columns="id")
1121
1122
    # Write into DB
1123
    with db.session_scope() as session:
1124
        session.bulk_insert_mappings(
1125
            EgonPowerPlants,
1126
            hydro.to_dict(orient="records"),
1127
        )
1128
1129
    log_insert_capacity(hydro, tech="hydro")
1130
1131
    # Biomass
1132
    #  ###################
1133
    biomass = gpd.GeoDataFrame.from_postgis(
1134
        f"""SELECT *, city AS location FROM {cfg['sources']['biomass']}""",
1135
        con,
1136
        geom_col="geom",
1137
    )
1138
1139
    # drop chp generators
1140
    biomass["th_capacity"] = biomass["th_capacity"].fillna(0)
1141
    biomass = biomass[biomass.th_capacity == 0]
1142
1143
    biomass = fill_missing_bus_and_geom(
1144
        biomass, "biomass", geom_municipalities, mv_grid_districts
1145
    )
1146
1147
    biomass = convert_master_info(biomass)
1148
    biomass["scenario"] = scn_name
1149
    biomass["carrier"] = "biomass"
1150
    biomass = biomass.rename(columns={"capacity": "el_capacity"})
1151
    biomass = biomass.drop(columns="id")
1152
1153
    # Write into DB
1154
    with db.session_scope() as session:
1155
        session.bulk_insert_mappings(
1156
            EgonPowerPlants,
1157
            biomass.to_dict(orient="records"),
1158
        )
1159
1160
    log_insert_capacity(biomass, tech="biomass")
1161
1162
    # Solar
1163
    #  ###################
1164
    solar = gpd.GeoDataFrame.from_postgis(
1165
        f"""SELECT *, city AS location FROM {cfg['sources']['pv']}
1166
        WHERE site_type IN ('Freifläche',
1167
        'Bauliche Anlagen (Hausdach, Gebäude und Fassade)') """,
1168
        con,
1169
        geom_col="geom",
1170
    )
1171
    map_solar = {
1172
        "Freifläche": "solar",
1173
        "Bauliche Anlagen (Hausdach, Gebäude und Fassade)": "solar_rooftop",
1174
    }
1175
    solar["carrier"] = solar["site_type"].replace(to_replace=map_solar)
1176
1177
    solar = fill_missing_bus_and_geom(
1178
        solar, "solar", geom_municipalities, mv_grid_districts
1179
    )
1180
1181
    solar = convert_master_info(solar)
1182
    solar["scenario"] = scn_name
1183
    solar = solar.rename(columns={"capacity": "el_capacity"})
1184
    solar = solar.drop(columns="id")
1185
1186
    # Write into DB
1187
    with db.session_scope() as session:
1188
        session.bulk_insert_mappings(
1189
            EgonPowerPlants,
1190
            solar.to_dict(orient="records"),
1191
        )
1192
1193
    log_insert_capacity(solar, tech="solar")
1194
1195
    # Wind
1196
    #  ###################
1197
    wind_onshore = gpd.GeoDataFrame.from_postgis(
1198
        f"""SELECT *, city AS location FROM {cfg['sources']['wind']}""",
1199
        con,
1200
        geom_col="geom",
1201
    )
1202
1203
    wind_onshore = fill_missing_bus_and_geom(
1204
        wind_onshore, "wind_onshore", geom_municipalities, mv_grid_districts
1205
    )
1206
1207
    wind_onshore = convert_master_info(wind_onshore)
1208
    wind_onshore["scenario"] = scn_name
1209
    wind_onshore = wind_onshore.rename(columns={"capacity": "el_capacity"})
1210
    wind_onshore["carrier"] = "wind_onshore"
1211
    wind_onshore = wind_onshore.drop(columns="id")
1212
1213
    # Write into DB
1214
    with db.session_scope() as session:
1215
        session.bulk_insert_mappings(
1216
            EgonPowerPlants,
1217
            wind_onshore.to_dict(orient="records"),
1218
        )
1219
1220
    log_insert_capacity(wind_onshore, tech="wind_onshore")
1221
1222
1223
def get_conventional_power_plants_non_chp(scn_name):
1224
1225
    cfg = egon.data.config.datasets()["power_plants"]
1226
    # Write conventional power plants in supply.egon_power_plants
1227
    common_columns = [
1228
        "EinheitMastrNummer",
1229
        "Energietraeger",
1230
        "Nettonennleistung",
1231
        "Laengengrad",
1232
        "Breitengrad",
1233
        "Gemeinde",
1234
        "Inbetriebnahmedatum",
1235
        "EinheitBetriebsstatus",
1236
        "DatumEndgueltigeStilllegung",
1237
    ]
1238
    # import nuclear power plants
1239
    nuclear = pd.read_csv(
1240
        WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_nuclear"],
1241
        usecols=common_columns,
1242
    )
1243
    # import combustion power plants
1244
    comb = pd.read_csv(
1245
        WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_combustion"],
1246
        usecols=common_columns + ["ThermischeNutzleistung"],
1247
    )
1248
1249
    conv = pd.concat([comb, nuclear])
1250
1251
    conv = conv[
1252
        conv.Energietraeger.isin(
1253
            [
1254
                "Braunkohle",
1255
                "Mineralölprodukte",
1256
                "Steinkohle",
1257
                "Kernenergie",
1258
                "Erdgas",
1259
            ]
1260
        )
1261
    ]
1262
1263
    # drop plants that are decommissioned
1264
    conv["DatumEndgueltigeStilllegung"] = pd.to_datetime(
1265
        conv["DatumEndgueltigeStilllegung"]
1266
    )
1267
1268
    # keep plants that were decommissioned after the max date
1269
    conv.loc[
1270
        (
1271
            conv.DatumEndgueltigeStilllegung
1272
            > egon.data.config.datasets()["mastr_new"][f"{scn_name}_date_max"]
1273
        ),
1274
        "EinheitBetriebsstatus",
1275
    ] = "InBetrieb"
1276
1277
    conv = conv.loc[conv.EinheitBetriebsstatus == "InBetrieb"]
1278
1279
    conv = conv.drop(
1280
        columns=["EinheitBetriebsstatus", "DatumEndgueltigeStilllegung"]
1281
    )
1282
1283
    # convert from KW to MW
1284
    conv["Nettonennleistung"] = conv["Nettonennleistung"] / 1000
1285
1286
    # drop generators installed after 2019
1287
    conv["Inbetriebnahmedatum"] = pd.to_datetime(conv["Inbetriebnahmedatum"])
1288
    conv = conv[
1289
        conv["Inbetriebnahmedatum"]
1290
        < egon.data.config.datasets()["mastr_new"][f"{scn_name}_date_max"]
1291
    ]
1292
1293
    conv_cap_chp = (
1294
        conv.groupby("Energietraeger")["Nettonennleistung"].sum() / 1e3
1295
    )
1296
    # drop chp generators
1297
    conv["ThermischeNutzleistung"] = conv["ThermischeNutzleistung"].fillna(0)
1298
    conv = conv[conv.ThermischeNutzleistung == 0]
1299
    conv_cap_no_chp = (
1300
        conv.groupby("Energietraeger")["Nettonennleistung"].sum() / 1e3
1301
    )
1302
1303
    logger.info("Dropped CHP generators in GW")
1304
    logger.info(conv_cap_chp - conv_cap_no_chp)
1305
1306
    # rename carriers
1307
    # rename carriers
1308
    conv["Energietraeger"] = conv["Energietraeger"].replace(
1309
        to_replace={
1310
            "Braunkohle": "lignite",
1311
            "Steinkohle": "coal",
1312
            "Erdgas": "gas",
1313
            "Mineralölprodukte": "oil",
1314
            "Kernenergie": "nuclear",
1315
        }
1316
    )
1317
1318
    # rename columns
1319
    conv.rename(
1320
        columns={
1321
            "EinheitMastrNummer": "gens_id",
1322
            "Energietraeger": "carrier",
1323
            "Nettonennleistung": "capacity",
1324
            "Gemeinde": "location",
1325
        },
1326
        inplace=True,
1327
    )
1328
    conv["bus_id"] = np.nan
1329
    conv["geom"] = gpd.points_from_xy(
1330
        conv.Laengengrad, conv.Breitengrad, crs=4326
1331
    )
1332
    conv.loc[(conv.Laengengrad.isna() | conv.Breitengrad.isna()), "geom"] = (
1333
        Point()
1334
    )
1335
    conv = gpd.GeoDataFrame(conv, geometry="geom")
1336
1337
    # assign voltage level by capacity
1338
    conv["voltage_level"] = np.nan
1339
    conv["voltage_level"] = assign_voltage_level_by_capacity(
1340
        conv.rename(columns={"capacity": "Nettonennleistung"})
1341
    )
1342
    # Add further information
1343
    conv["sources"] = [{"el_capacity": "MaStR"}] * conv.shape[0]
1344
    conv["source_id"] = conv["gens_id"].apply(lambda x: {"MastrNummer": x})
1345
    conv["scenario"] = scn_name
1346
1347
    return conv
1348
1349
1350
def import_gas_gen_egon100():
1351
    scn_name = "eGon100RE"
1352
    if scn_name not in egon.data.config.settings()["egon-data"]["--scenarios"]:
1353
        return
1354
    con = db.engine()
1355
    session = sessionmaker(bind=db.engine())()
1356
    cfg = egon.data.config.datasets()["power_plants"]
1357
    scenario_date_max = "2045-12-31 23:59:00"
1358
1359
    db.execute_sql(
1360
        f"""
1361
        DELETE FROM {cfg['target']['schema']}.{cfg['target']['table']}
1362
        WHERE carrier = 'gas'
1363
        AND bus_id IN (SELECT bus_id from grid.egon_etrago_bus
1364
                WHERE scn_name = '{scn_name}'
1365
                AND country = 'DE')
1366
        AND scenario = '{scn_name}'
1367
        """
1368
    )
1369
1370
    # import municipalities to assign missing geom and bus_id
1371
    geom_municipalities = gpd.GeoDataFrame.from_postgis(
1372
        """
1373
        SELECT gen, ST_UNION(geometry) as geom
1374
        FROM boundaries.vg250_gem
1375
        GROUP BY gen
1376
        """,
1377
        con,
1378
        geom_col="geom",
1379
    ).set_index("gen")
1380
    geom_municipalities["geom"] = geom_municipalities["geom"].centroid
1381
1382
    mv_grid_districts = gpd.GeoDataFrame.from_postgis(
1383
        f"""
1384
        SELECT * FROM {cfg['sources']['egon_mv_grid_district']}
1385
        """,
1386
        con,
1387
    )
1388
    mv_grid_districts.geom = mv_grid_districts.geom.to_crs(4326)
1389
1390
    target = db.select_dataframe(
1391
        f"""
1392
        SELECT capacity FROM supply.egon_scenario_capacities
1393
        WHERE scenario_name = '{scn_name}'
1394
        AND carrier = 'gas'
1395
        """,
1396
    ).iat[0, 0]
1397
1398
    conv = pd.read_csv(
1399
            WORKING_DIR_MASTR_OLD / cfg["sources"]["mastr_combustion"],
1400
        usecols=[
1401
            "EinheitMastrNummer",
1402
            "Energietraeger",
1403
            "Nettonennleistung",
1404
            "Laengengrad",
1405
            "Breitengrad",
1406
            "Gemeinde",
1407
            "Inbetriebnahmedatum",
1408
            "EinheitBetriebsstatus",
1409
            "DatumEndgueltigeStilllegung",
1410
            "ThermischeNutzleistung",
1411
        ],
1412
    )
1413
1414
    conv = conv[conv.Energietraeger == "Erdgas"]
1415
1416
    conv.rename(
1417
        columns={
1418
            "Inbetriebnahmedatum": "commissioning_date",
1419
            "EinheitBetriebsstatus": "status",
1420
            "DatumEndgueltigeStilllegung": "decommissioning_date",
1421
            "EinheitMastrNummer": "gens_id",
1422
            "Energietraeger": "carrier",
1423
            "Nettonennleistung": "capacity",
1424
            "Gemeinde": "location",
1425
        },
1426
        inplace=True,
1427
    )
1428
1429
    conv = discard_not_available_generators(conv, scenario_date_max)
1430
1431
    # convert from KW to MW
1432
    conv["capacity"] = conv["capacity"] / 1000
1433
1434
    # drop chp generators
1435
    conv["ThermischeNutzleistung"] = conv["ThermischeNutzleistung"].fillna(0)
1436
    conv = conv[conv.ThermischeNutzleistung == 0]
1437
1438
    # rename carriers
1439
    map_carrier_conv = {"Erdgas": "gas"}
1440
    conv["carrier"] = conv["carrier"].map(map_carrier_conv)
1441
1442
    conv["bus_id"] = np.nan
1443
1444
    conv["geom"] = gpd.points_from_xy(
1445
        conv.Laengengrad, conv.Breitengrad, crs=4326
1446
    )
1447
    conv.loc[(conv.Laengengrad.isna() | conv.Breitengrad.isna()), "geom"] = (
1448
        Point()
1449
    )
1450
    conv = gpd.GeoDataFrame(conv, geometry="geom")
1451
1452
    conv = fill_missing_bus_and_geom(
1453
        conv, "conventional", geom_municipalities, mv_grid_districts
1454
    )
1455
    conv["voltage_level"] = np.nan
1456
1457
    conv["voltage_level"] = assign_voltage_level_by_capacity(
1458
        conv.rename(columns={"capacity": "Nettonennleistung"})
1459
    )
1460
1461
    conv["capacity"] = conv["capacity"] * (target / conv["capacity"].sum())
1462
1463
    max_id = db.select_dataframe(
1464
        """
1465
            SELECT max(id) FROM supply.egon_power_plants
1466
            """,
1467
    ).iat[0, 0]
1468
1469
    conv["id"] = range(max_id + 1, max_id + 1 + len(conv))
1470
1471
    for i, row in conv.iterrows():
1472
        entry = EgonPowerPlants(
1473
            id=row.id,
1474
            sources={"el_capacity": "MaStR"},
1475
            source_id={"MastrNummer": row.gens_id},
1476
            carrier=row.carrier,
1477
            el_capacity=row.capacity,
1478
            scenario=scn_name,
1479
            bus_id=row.bus_id,
1480
            voltage_level=row.voltage_level,
1481
            geom=row.geom,
1482
        )
1483
        session.add(entry)
1484
    session.commit()
1485
1486
    logging.info(
1487
        f"""
1488
          {len(conv)} gas generators with a total installed capacity of
1489
          {conv.capacity.sum()}MW were inserted into the db
1490
          """
1491
    )
1492
1493
    return
1494
1495
1496
tasks = (
1497
    create_tables,
1498
    import_mastr,
1499
)
1500
1501
for scn_name in egon.data.config.settings()["egon-data"]["--scenarios"]:
1502
    if "status" in scn_name:
1503
        tasks += (
1504
            wrapped_partial(
1505
                power_plants_status_quo,
1506
                scn_name=scn_name,
1507
                postfix=f"_{scn_name[-4:]}",
1508
            ),
1509
        )
1510
1511
if (
1512
    "eGon2035" in egon.data.config.settings()["egon-data"]["--scenarios"]
1513
    or "eGon100RE" in egon.data.config.settings()["egon-data"]["--scenarios"]
1514
):
1515
    tasks = tasks + (
1516
        insert_hydro_biomass,
1517
        allocate_conventional_non_chp_power_plants,
1518
        allocate_other_power_plants,
1519
        {
1520
            wind_onshore.insert,
1521
            pv_ground_mounted.insert,
1522
            pv_rooftop_per_mv_grid,
1523
        },
1524
    )
1525
1526
if "eGon100RE" in egon.data.config.settings()["egon-data"]["--scenarios"]:
1527
    tasks = tasks + (import_gas_gen_egon100,)
1528
1529
tasks = tasks + (
1530
    pv_rooftop_to_buildings,
1531
    wind_offshore.insert,
1532
)
1533
1534
for scn_name in egon.data.config.settings()["egon-data"]["--scenarios"]:
1535
    tasks += (wrapped_partial(assign_weather_data.weatherId_and_busId,
1536
                              scn_name=scn_name,
1537
                              postfix=f"_{scn_name}"
1538
                              ),)
1539
1540
tasks += (pp_metadata.metadata,)
1541
1542
class PowerPlants(Dataset):
1543
    """
1544
    This dataset deals with the distribution and allocation of power plants
1545
1546
    For the distribution and allocation of power plants to their corresponding
1547
    grid connection point different technology-specific methods are applied.
1548
    In a first step separate tables are created for wind, pv, hydro and biomass
1549
    based power plants by running :py:func:`create_tables`.
1550
    Different methods rely on the locations of existing power plants retrieved
1551
    from the official power plant registry 'Marktstammdatenregister' applying
1552
    function :py:func:`ìmport_mastr`.
1553
1554
    *Hydro and Biomass*
1555
    Hydro and biomass power plants are distributed based on the status quo
1556
    locations of existing power plants assuming that no further expansion of
1557
    these technologies is to be expected in Germany. Hydro power plants include
1558
    reservoir and run-of-river plants.
1559
    Power plants without a correct geolocation are not taken into account.
1560
    To compensate this, the installed capacities of the suitable plants are
1561
    scaled up to meet the target value using function :py:func:`scale_prox2now`
1562
1563
    *Conventional power plants without CHP*
1564
    The distribution of conventional plants, excluding CHPs, takes place in
1565
    function :py:func:`allocate_conventional_non_chp_power_plants`. Therefore
1566
    information about future power plants from the grid development plan
1567
    function as the target value and are matched with actual existing power
1568
    plants with correct geolocations from MaStR registry.
1569
1570
    *Wind onshore*
1571
1572
1573
    *Wind offshore*
1574
1575
    *PV ground-mounted*
1576
1577
    *PV rooftop*
1578
1579
    *others*
1580
1581
    *Dependencies*
1582
      * :py:class:`Chp <egon.data.datasets.chp.Chp>`
1583
      * :py:class:`CtsElectricityDemand
1584
      <egon.data.datasets.electricity_demand.CtsElectricityDemand>`
1585
      * :py:class:`HouseholdElectricityDemand
1586
      <egon.data.datasets.electricity_demand.HouseholdElectricityDemand>`
1587
      * :py:class:`mastr_data <egon.data.datasets.mastr.mastr_data>`
1588
      * :py:func:`define_mv_grid_districts
1589
      <egon.data.datasets.mv_grid_districts.define_mv_grid_districts>`
1590
      * :py:class:`RePotentialAreas
1591
      <egon.data.datasets.re_potential_areas.RePotentialAreas>`
1592
      * :py:class:`ZensusVg250
1593
      <egon.data.datasets.RenewableFeedin>`
1594
      * :py:class:`ScenarioCapacities
1595
      <egon.data.datasets.scenario_capacities.ScenarioCapacities>`
1596
      * :py:class:`ScenarioParameters
1597
      <egon.data.datasets.scenario_parameters.ScenarioParameters>`
1598
      * :py:func:`Setup <egon.data.datasets.database.setup>`
1599
      * :py:class:`substation_extraction
1600
      <egon.data.datasets.substation.substation_extraction>`
1601
      * :py:class:`Vg250MvGridDistricts
1602
      <egon.data.datasets.Vg250MvGridDistricts>`
1603
      * :py:class:`ZensusMvGridDistricts
1604
      <egon.data.datasets.zensus_mv_grid_districts.ZensusMvGridDistricts>`
1605
1606
    *Resulting tables*
1607
      * :py:class:`supply.egon_power_plants
1608
      <egon.data.datasets.power_plants.EgonPowerPlants>` is filled
1609
1610
    """
1611
1612
    #:
1613
    name: str = "PowerPlants"
1614
    #:
1615
    version: str = "0.0.28"
1616
1617
    def __init__(self, dependencies):
1618
        super().__init__(
1619
            name=self.name,
1620
            version=self.version,
1621
            dependencies=dependencies,
1622
            tasks=tasks,
1623
        )
1624
1625