data.datasets.power_plants.assign_bus_id()   B
last analyzed

Complexity

Conditions 5

Size

Total Lines 66
Code Lines 31

Duplication

Lines 66
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 31
dl 66
loc 66
rs 8.6693
c 0
b 0
f 0
cc 5
nop 3

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