Passed
Pull Request — dev (#826)
by
unknown
02:03
created

data.datasets.electricity_demand_timeseries.cts_buildings   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 948
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 38
eloc 472
dl 0
loc 948
rs 9.36
c 0
b 0
f 0

14 Functions

Rating   Name   Duplication   Size   Complexity  
A place_buildings_with_amenities() 0 41 3
B create_synthetic_buildings() 0 67 6
A delete_synthetic_cts_buildings() 0 14 2
A get_peak_load_cts_buildings() 0 26 2
C calc_building_profiles() 0 64 9
A amenities_without_buildings() 0 60 2
A select_cts_buildings() 0 23 1
B cells_with_cts_demand_only() 0 72 2
A write_synthetic_buildings_to_db() 0 31 2
B cts_to_buildings() 0 147 1
A calc_building_demand_profile_share() 0 67 1
B buildings_with_amenities() 0 69 2
A calc_census_cell_share() 0 56 2
B buildings_without_amenities() 0 99 2

1 Method

Rating   Name   Duplication   Size   Complexity  
A CtsElectricityBuildings.__init__() 0 8 1
1
from geoalchemy2 import Geometry
2
from geoalchemy2.shape import to_shape
3
from sqlalchemy import REAL, Column, Float, Integer, String, func
4
from sqlalchemy.ext.declarative import declarative_base
5
import geopandas as gpd
6
import pandas as pd
7
import saio
8
9
from egon.data import db
10
from egon.data.datasets import Dataset
11
from egon.data.datasets.electricity_demand import (
12
    EgonDemandRegioZensusElectricity,
13
)
14
from egon.data.datasets.electricity_demand.temporal import calc_load_curves_cts
15
from egon.data.datasets.electricity_demand_timeseries.hh_buildings import (
16
    OsmBuildingsSynthetic,
17
)
18
from egon.data.datasets.electricity_demand_timeseries.tools import (
19
    random_ints_until_sum,
20
    random_point_in_square,
21
    specific_int_until_sum,
22
    write_table_to_postgis,
23
    write_table_to_postgres,
24
)
25
from egon.data.datasets.zensus_mv_grid_districts import MapZensusGridDistricts
26
from egon.data.datasets.zensus_vg250 import DestatisZensusPopulationPerHa
27
import egon.data.config
28
29
engine = db.engine()
30
Base = declarative_base()
31
32
data_config = egon.data.config.datasets()
33
RANDOM_SEED = egon.data.config.settings()["egon-data"]["--random-seed"]
34
35
# import db tables
36
saio.register_schema("openstreetmap", engine=engine)
37
saio.register_schema("society", engine=engine)
38
saio.register_schema("demand", engine=engine)
39
saio.register_schema("boundaries", engine=engine)
40
41
42
class EgonCtsElectricityDemandBuildingShare(Base):
43
    __tablename__ = "egon_cts_electricity_demand_building_share"
44
    __table_args__ = {"schema": "demand"}
45
46
    id = Column(Integer, primary_key=True)
47
    scenario = Column(String, primary_key=True)
48
    bus_id = Column(Integer, index=True)
49
    profile_share = Column(Float)
50
51
52
class CtsPeakLoads(Base):
53
    __tablename__ = "egon_cts_peak_loads"
54
    __table_args__ = {"schema": "demand"}
55
56
    id = Column(String, primary_key=True)
57
    cts_peak_load_in_w_2035 = Column(REAL)
58
    cts_peak_load_in_w_100RE = Column(REAL)
59
60
61
class CtsBuildings(Base):
62
    __tablename__ = "egon_cts_buildings"
63
    __table_args__ = {"schema": "openstreetmap"}
64
65
    id = Column(Integer, primary_key=True)
66
    zensus_population_id = Column(Integer, index=True)
67
    geom_building = Column(Geometry("Polygon", 3035))
68
    n_amenities_inside = Column(Integer)
69
    source = Column(String)
70
71
72
def amenities_without_buildings():
73
    """
74
    Amenities which have no buildings assigned and are in
75
    a cell with cts demand are determined.
76
77
    Returns
78
    -------
79
    pd.DataFrame
80
        Table of amenities without buildings
81
    """
82
    from saio.openstreetmap import osm_amenities_not_in_buildings_filtered
83
84
    with db.session_scope() as session:
85
        cells_query = (
86
            session.query(
87
                DestatisZensusPopulationPerHa.id.label("zensus_population_id"),
88
                # TODO can be used for square around amenity
89
                #  (1 geom_amenity: 1 geom_building)
90
                #  not unique amenity_ids yet
91
                osm_amenities_not_in_buildings_filtered.geom_amenity,
92
                osm_amenities_not_in_buildings_filtered.egon_amenity_id,
93
                # EgonDemandRegioZensusElectricity.demand,
94
                # # TODO can be used to generate n random buildings
95
                # # (n amenities : 1 randombuilding)
96
                # func.count(
97
                #     osm_amenities_not_in_buildings_filtered.egon_amenity_id
98
                # ).label("n_amenities_inside"),
99
                # DestatisZensusPopulationPerHa.geom,
100
            )
101
            .filter(
102
                func.st_within(
103
                    osm_amenities_not_in_buildings_filtered.geom_amenity,
104
                    DestatisZensusPopulationPerHa.geom,
105
                )
106
            )
107
            .filter(
108
                DestatisZensusPopulationPerHa.id
109
                == EgonDemandRegioZensusElectricity.zensus_population_id
110
            )
111
            .filter(
112
                EgonDemandRegioZensusElectricity.sector == "service",
113
                EgonDemandRegioZensusElectricity.scenario == "eGon2035"
114
                #         ).group_by(
115
                #             EgonDemandRegioZensusElectricity.zensus_population_id,
116
                #             DestatisZensusPopulationPerHa.geom,
117
            )
118
        )
119
    # # TODO can be used to generate n random buildings
120
    # df_cells_with_amenities_not_in_buildings = gpd.read_postgis(
121
    #     cells_query.statement, cells_query.session.bind, geom_col="geom"
122
    # )
123
    #
124
125
    # # TODO can be used for square around amenity
126
    df_amenities_without_buildings = gpd.read_postgis(
127
        cells_query.statement,
128
        cells_query.session.bind,
129
        geom_col="geom_amenity",
130
    )
131
    return df_amenities_without_buildings
132
133
134
def place_buildings_with_amenities(df, amenities=None, max_amenities=None):
135
    """
136
    Building centroids are placed randomly within census cells.
137
    The Number of buildings is derived from n_amenity_inside, the selected
138
    method and number of amenities per building.
139
140
    Returns
141
    -------
142
    df: gpd.GeoDataFrame
143
        Table of buildings centroids
144
    """
145
    if isinstance(max_amenities, int):
146
        # amount of amenities is randomly generated within bounds
147
        # (max_amenities, amenities per cell)
148
        df["n_amenities_inside"] = df["n_amenities_inside"].apply(
149
            random_ints_until_sum, args=[max_amenities]
150
        )
151
    if isinstance(amenities, int):
152
        # Specific amount of amenities per building
153
        df["n_amenities_inside"] = df["n_amenities_inside"].apply(
154
            specific_int_until_sum, args=[amenities]
155
        )
156
157
    # Unnest each building
158
    df = df.explode(column="n_amenities_inside")
159
160
    # building count per cell
161
    df["building_count"] = df.groupby(["zensus_population_id"]).cumcount() + 1
162
163
    # generate random synthetic buildings
164
    edge_length = 5
165
    # create random points within census cells
166
    points = random_point_in_square(geom=df["geom"], tol=edge_length / 2)
167
168
    df.reset_index(drop=True, inplace=True)
169
    # Store center of polygon
170
    df["geom_point"] = points
171
    # Drop geometry of census cell
172
    df = df.drop(columns=["geom"])
173
174
    return df
175
176
177
def create_synthetic_buildings(df, points=None, crs="EPSG:3035"):
178
    """
179
    Synthetic buildings are generated around points.
180
181
    Parameters
182
    ----------
183
    df: pd.DataFrame
184
        Table of census cells
185
    points: gpd.GeoSeries or str
186
        List of points to place buildings around or column name of df
187
    crs: str
188
        CRS of result table
189
190
    Returns
191
    -------
192
    df: gpd.GeoDataFrame
193
        Synthetic buildings
194
    """
195
196
    if isinstance(points, str) and points in df.columns:
197
        points = df[points]
198
    elif isinstance(points, gpd.GeoSeries):
199
        pass
200
    else:
201
        raise ValueError("Points are of the wrong type")
202
203
    # Create building using a square around point
204
    edge_length = 5
205
    df["geom_building"] = points.buffer(distance=edge_length / 2, cap_style=3)
206
207
    if "geom_point" not in df.columns:
208
        df["geom_point"] = df["geom_building"].centroid
209
210
    # TODO Check CRS
211
    df = gpd.GeoDataFrame(
212
        df,
213
        crs=crs,
214
        geometry="geom_building",
215
    )
216
217
    # TODO remove after implementation of egon_building_id
218
    df.rename(columns={"id": "egon_building_id"}, inplace=True)
219
220
    # get max number of building ids from synthetic residential table
221
    with db.session_scope() as session:
222
        max_synth_residential_id = session.execute(
223
            func.max(OsmBuildingsSynthetic.id)
224
        ).scalar()
225
    max_synth_residential_id = int(max_synth_residential_id)
226
227
    # create sequential ids
228
    df["egon_building_id"] = range(
229
        max_synth_residential_id + 1,
230
        max_synth_residential_id + df.shape[0] + 1,
231
    )
232
233
    df["area"] = df["geom_building"].area
234
    # set building type of synthetic building
235
    df["building"] = "cts"
236
    # TODO remove in #772
237
    df = df.rename(
238
        columns={
239
            # "zensus_population_id": "cell_id",
240
            "egon_building_id": "id",
241
        }
242
    )
243
    return df
244
245
246
def buildings_with_amenities():
247
    """
248
    Amenities which are assigned to buildings are determined
249
    and grouped per building and zensus cell. Buildings
250
    covering multiple cells therefore exists multiple times
251
    but in different zensus cells. This is necessary to cover
252
    all cells with a cts demand. The buildings are aggregated
253
    afterwards during the calculation of the profile_share.
254
255
    Returns
256
    -------
257
    df_buildings_with_amenities: gpd.GeoDataFrame
258
        Contains all buildings with amenities per zensus cell.
259
    """
260
261
    from saio.openstreetmap import osm_amenities_in_buildings_filtered
262
263
    with db.session_scope() as session:
264
        cells_query = (
265
            session.query(osm_amenities_in_buildings_filtered)
266
            .filter(
267
                EgonDemandRegioZensusElectricity.zensus_population_id
268
                == osm_amenities_in_buildings_filtered.zensus_population_id
269
            )
270
            .filter(
271
                EgonDemandRegioZensusElectricity.sector == "service",
272
                EgonDemandRegioZensusElectricity.scenario == "eGon2035",
273
            )
274
        )
275
    df_amenities_in_buildings = pd.read_sql(
276
        cells_query.statement, cells_query.session.bind, index_col=None
277
    )
278
279
    df_amenities_in_buildings["geom_building"] = df_amenities_in_buildings[
280
        "geom_building"
281
    ].apply(to_shape)
282
    df_amenities_in_buildings["geom_amenity"] = df_amenities_in_buildings[
283
        "geom_amenity"
284
    ].apply(to_shape)
285
286
    df_amenities_in_buildings["n_amenities_inside"] = 1
287
    # amenities per building
288
    # if building covers multiple cells, it exists multiple times
289
    df_amenities_in_buildings[
290
        "n_amenities_inside"
291
    ] = df_amenities_in_buildings.groupby(["zensus_population_id", "id"])[
292
        "n_amenities_inside"
293
    ].transform(
294
        "sum"
295
    )
296
    # reduce to buildings
297
    df_buildings_with_amenities = df_amenities_in_buildings.drop_duplicates(
298
        ["id", "zensus_population_id"]
299
    )
300
    df_buildings_with_amenities = df_buildings_with_amenities.reset_index(
301
        drop=True
302
    )
303
    df_buildings_with_amenities = df_buildings_with_amenities[
304
        ["id", "zensus_population_id", "geom_building", "n_amenities_inside"]
305
    ]
306
    df_buildings_with_amenities.rename(
307
        columns={
308
            # "zensus_population_id": "cell_id",
309
            "egon_building_id": "id"
310
        },
311
        inplace=True,
312
    )
313
314
    return df_buildings_with_amenities
315
316
317
# TODO Remove as depricated
318
def write_synthetic_buildings_to_db(df_synthetic_buildings):
319
    """"""
320
    if "geom_point" not in df_synthetic_buildings.columns:
321
        df_synthetic_buildings["geom_point"] = df_synthetic_buildings[
322
            "geom_building"
323
        ].centroid
324
325
    df_synthetic_buildings = df_synthetic_buildings.rename(
326
        columns={
327
            "zensus_population_id": "cell_id",
328
            "egon_building_id": "id",
329
        }
330
    )
331
    # Only take existing columns
332
    columns = [
333
        column.key for column in OsmBuildingsSynthetic.__table__.columns
334
    ]
335
    df_synthetic_buildings = df_synthetic_buildings.loc[:, columns]
336
337
    dtypes = {
338
        i: OsmBuildingsSynthetic.__table__.columns[i].type
339
        for i in OsmBuildingsSynthetic.__table__.columns.keys()
340
    }
341
342
    # Write new buildings incl coord into db
343
    df_synthetic_buildings.to_postgis(
344
        name=OsmBuildingsSynthetic.__tablename__,
345
        con=engine,
346
        if_exists="append",
347
        schema=OsmBuildingsSynthetic.__table_args__["schema"],
348
        dtype=dtypes,
349
    )
350
351
352
def buildings_without_amenities():
353
    """
354
    Buildings (filtered and synthetic) in cells with
355
    cts demand but no amenities are determined.
356
357
    Returns
358
    -------
359
    df_buildings_without_amenities: gpd.GeoDataFrame
360
        Table of buildings without amenities in zensus cells
361
        with cts demand.
362
    """
363
    from saio.boundaries import egon_map_zensus_buildings_filtered_all
364
    from saio.openstreetmap import (
365
        osm_amenities_shops_filtered,
366
        osm_buildings_filtered,
367
        osm_buildings_synthetic,
368
    )
369
370
    # buildings_filtered in cts-demand-cells without amenities
371
    with db.session_scope() as session:
372
373
        # Synthetic Buildings
374
        q_synth_buildings = session.query(
375
            osm_buildings_synthetic.cell_id.cast(Integer).label(
376
                "zensus_population_id"
377
            ),
378
            osm_buildings_synthetic.id.cast(Integer).label("id"),
379
            osm_buildings_synthetic.area.label("area"),
380
            osm_buildings_synthetic.geom_building.label("geom_building"),
381
            osm_buildings_synthetic.geom_point.label("geom_point"),
382
        )
383
384
        # Buildings filtered
385
        q_buildings_filtered = session.query(
386
            egon_map_zensus_buildings_filtered_all.zensus_population_id,
387
            osm_buildings_filtered.id,
388
            osm_buildings_filtered.area,
389
            osm_buildings_filtered.geom_building,
390
            osm_buildings_filtered.geom_point,
391
        ).filter(
392
            osm_buildings_filtered.id
393
            == egon_map_zensus_buildings_filtered_all.id
394
        )
395
396
        # Amenities + zensus_population_id
397
        q_amenities = (
398
            session.query(
399
                DestatisZensusPopulationPerHa.id.label("zensus_population_id"),
400
            )
401
            .filter(
402
                func.st_within(
403
                    osm_amenities_shops_filtered.geom_amenity,
404
                    DestatisZensusPopulationPerHa.geom,
405
                )
406
            )
407
            .distinct(DestatisZensusPopulationPerHa.id)
408
        )
409
410
        # Cells with CTS demand but without amenities
411
        q_cts_without_amenities = (
412
            session.query(
413
                EgonDemandRegioZensusElectricity.zensus_population_id,
414
            )
415
            .filter(
416
                EgonDemandRegioZensusElectricity.sector == "service",
417
                EgonDemandRegioZensusElectricity.scenario == "eGon2035",
418
            )
419
            .filter(
420
                EgonDemandRegioZensusElectricity.zensus_population_id.notin_(
421
                    q_amenities
422
                )
423
            )
424
            .distinct()
425
        )
426
427
        # Buildings filtered + synthetic buildings residential in
428
        # cells with CTS demand but without amenities
429
        cells_query = q_synth_buildings.union(q_buildings_filtered).filter(
430
            egon_map_zensus_buildings_filtered_all.zensus_population_id.in_(
431
                q_cts_without_amenities
432
            )
433
        )
434
435
    # df_buildings_without_amenities = pd.read_sql(
436
    #     cells_query.statement, cells_query.session.bind, index_col=None)
437
    df_buildings_without_amenities = gpd.read_postgis(
438
        cells_query.statement,
439
        cells_query.session.bind,
440
        geom_col="geom_building",
441
    )
442
443
    df_buildings_without_amenities = df_buildings_without_amenities.rename(
444
        columns={
445
            # "zensus_population_id": "cell_id",
446
            "egon_building_id": "id",
447
        }
448
    )
449
450
    return df_buildings_without_amenities
451
452
453
def select_cts_buildings(df_buildings_wo_amenities):
454
    """
455
    Buildings (filtered and synthetic) in cells with
456
    cts demand are selected. Only the first building
457
    is taken for each cell and 1 amenities is assigned.
458
459
    Returns
460
    -------
461
    df_buildings_with_cts_demand: gpd.GeoDataFrame
462
        Table of buildings
463
    """
464
    # TODO Adapt method
465
    # Select one building each cell
466
    # take the first
467
    df_buildings_with_cts_demand = df_buildings_wo_amenities.drop_duplicates(
468
        # subset="cell_id", keep="first"
469
        subset="zensus_population_id",
470
        keep="first",
471
    ).reset_index(drop=True)
472
    df_buildings_with_cts_demand["n_amenities_inside"] = 1
473
    df_buildings_with_cts_demand["building"] = "cts"
474
475
    return df_buildings_with_cts_demand
476
477
478
def cells_with_cts_demand_only(df_buildings_without_amenities):
479
    """
480
    Cells with cts demand but no amenities or buildilngs
481
    are determined.
482
483
    Returns
484
    -------
485
    df_cells_only_cts_demand: gpd.GeoDataFrame
486
        Table of cells with cts demand but no amenities or buildings
487
    """
488
    from saio.openstreetmap import osm_amenities_shops_filtered
489
490
    # cells mit amenities
491
    with db.session_scope() as session:
492
        sub_query = (
493
            session.query(
494
                DestatisZensusPopulationPerHa.id.label("zensus_population_id"),
495
            )
496
            .filter(
497
                func.st_within(
498
                    osm_amenities_shops_filtered.geom_amenity,
499
                    DestatisZensusPopulationPerHa.geom,
500
                )
501
            )
502
            .distinct(DestatisZensusPopulationPerHa.id)
503
        )
504
505
        cells_query = (
506
            session.query(
507
                EgonDemandRegioZensusElectricity.zensus_population_id,
508
                EgonDemandRegioZensusElectricity.scenario,
509
                EgonDemandRegioZensusElectricity.sector,
510
                EgonDemandRegioZensusElectricity.demand,
511
                DestatisZensusPopulationPerHa.geom,
512
            )
513
            .filter(
514
                EgonDemandRegioZensusElectricity.sector == "service",
515
                EgonDemandRegioZensusElectricity.scenario == "eGon2035",
516
            )
517
            .filter(
518
                EgonDemandRegioZensusElectricity.zensus_population_id.notin_(
519
                    sub_query
520
                )
521
            )
522
            .filter(
523
                EgonDemandRegioZensusElectricity.zensus_population_id
524
                == DestatisZensusPopulationPerHa.id
525
            )
526
        )
527
528
    df_cts_cell_without_amenities = gpd.read_postgis(
529
        cells_query.statement,
530
        cells_query.session.bind,
531
        geom_col="geom",
532
        index_col=None,
533
    )
534
535
    # TODO maybe remove
536
    df_buildings_without_amenities = df_buildings_without_amenities.rename(
537
        columns={"cell_id": "zensus_population_id"}
538
    )
539
540
    # Census cells with only cts demand
541
    df_cells_only_cts_demand = df_cts_cell_without_amenities.loc[
542
        ~df_cts_cell_without_amenities["zensus_population_id"].isin(
543
            df_buildings_without_amenities["zensus_population_id"].unique()
544
        )
545
    ]
546
547
    df_cells_only_cts_demand.reset_index(drop=True, inplace=True)
548
549
    return df_cells_only_cts_demand
550
551
552
def calc_census_cell_share(scenario="eGon2035"):
553
    """
554
    The profile share for each census cell is calculated by it's
555
    share of annual demand per substation bus. The annual demand
556
    per cell is defined by DemandRegio. The share is for both
557
    scenarios identical as the annual demand is linearly scaled.
558
559
    Parameters
560
    ----------
561
    scenario: str
562
        Scenario for which the share is calculated.
563
564
    Returns
565
    -------
566
    df_census_share: pd.DataFrame
567
    """
568
569
    with db.session_scope() as session:
570
        cells_query = (
571
            session.query(
572
                EgonDemandRegioZensusElectricity, MapZensusGridDistricts.bus_id
573
            )
574
            .filter(EgonDemandRegioZensusElectricity.sector == "service")
575
            .filter(EgonDemandRegioZensusElectricity.scenario == scenario)
576
            .filter(
577
                EgonDemandRegioZensusElectricity.zensus_population_id
578
                == MapZensusGridDistricts.zensus_population_id
579
            )
580
        )
581
582
    df_demand_regio_electricity_demand = pd.read_sql(
583
        cells_query.statement,
584
        cells_query.session.bind,
585
        index_col="zensus_population_id",
586
    )
587
588
    # get demand share of cell per bus
589
    df_census_share = df_demand_regio_electricity_demand[
590
        "demand"
591
    ] / df_demand_regio_electricity_demand.groupby("bus_id")[
592
        "demand"
593
    ].transform(
594
        "sum"
595
    )
596
    df_census_share = df_census_share.rename("cell_share")
597
598
    df_census_share = pd.concat(
599
        [
600
            df_census_share,
601
            df_demand_regio_electricity_demand[["bus_id", "scenario"]],
602
        ],
603
        axis=1,
604
    )
605
606
    df_census_share.reset_index(inplace=True)
607
    return df_census_share
608
609
610
def calc_building_demand_profile_share(df_cts_buildings, scenario="eGon2035"):
611
    """
612
    Share of cts electricity demand profile per bus for every selected building
613
    is calculated. Building-amenity share is multiplied with census cell share
614
    to get the substation bus profile share for each building. The share is
615
    grouped and aggregated per building as some cover multiple cells.
616
617
    Parameters
618
    ----------
619
    df_cts_buildings: gpd.GeoDataFrame
620
        Table of all buildings with cts demand assigned
621
    scenario: str
622
        Scenario for which the share is calculated.
623
624
    Returns
625
    -------
626
    df_building_share: pd.DataFrame
627
        Table of bus profile share per building
628
629
    """
630
631
    def calc_building_amenity_share(df_cts_buildings):
632
        """
633
        Calculate the building share by the number amenities per building
634
        within a census cell.
635
        """
636
        df_building_amenity_share = df_cts_buildings[
637
            "n_amenities_inside"
638
        ] / df_cts_buildings.groupby("zensus_population_id")[
639
            "n_amenities_inside"
640
        ].transform(
641
            "sum"
642
        )
643
        df_building_amenity_share = pd.concat(
644
            [
645
                df_building_amenity_share.rename("building_amenity_share"),
646
                df_cts_buildings[["zensus_population_id", "id"]],
647
            ],
648
            axis=1,
649
        )
650
        return df_building_amenity_share
651
652
    df_building_amenity_share = calc_building_amenity_share(df_cts_buildings)
653
654
    df_census_cell_share = calc_census_cell_share(scenario)
655
656
    df_demand_share = pd.merge(
657
        left=df_building_amenity_share,
658
        right=df_census_cell_share,
659
        left_on="zensus_population_id",
660
        right_on="zensus_population_id",
661
    )
662
    df_demand_share["profile_share"] = df_demand_share[
663
        "building_amenity_share"
664
    ].multiply(df_demand_share["cell_share"])
665
666
    df_demand_share = df_demand_share[
667
        ["id", "bus_id", "scenario", "profile_share"]
668
    ]
669
    # Group and aggregate per building for multi cell buildings
670
    df_demand_share = (
671
        df_demand_share.groupby(["scenario", "bus_id", "id"])
672
        .sum()
673
        .reset_index()
674
    )
675
676
    return df_demand_share
677
678
679
def calc_building_profiles(
680
    df_demand_share=None,
681
    egon_building_id=None,
682
    bus_id=None,
683
    scenario="eGon2035",
684
):
685
    """
686
    Calculate the demand profile for each building. The profile is
687
    calculated by the demand share of the building per substation bus.
688
689
    Parameters
690
    ----------
691
    df_demand_share: pd.DataFrame
692
        Table of demand share per building. If not given, table is
693
        sourced from database.
694
    egon_building_id: int
695
        Id of the building for which the profile is calculated. If not
696
        given, the profiles are calculated for all buildings.
697
698
    Returns
699
    -------
700
    df_building_profiles: pd.DataFrame
701
        Table of demand profile per building
702
    """
703
704
    if not isinstance(df_demand_share, pd.DataFrame):
705
        with db.session_scope() as session:
706
            cells_query = session.query(EgonCtsElectricityDemandBuildingShare)
707
708
        df_demand_share = pd.read_sql(
709
            cells_query.statement, cells_query.session.bind, index_col=None
710
        )
711
712
    df_cts_profiles = calc_load_curves_cts(scenario)
713
714
    # get demand share of selected building id
715
    if isinstance(egon_building_id, int):
716
        if egon_building_id in df_demand_share["id"]:
717
            df_demand_share = df_demand_share.loc[
718
                df_demand_share["id"] == egon_building_id
719
            ]
720
        else:
721
            raise KeyError(f"Building with id {egon_building_id} not found")
722
723
    # get demand share of all buildings for selected bus id
724
    if isinstance(bus_id, int):
725
        if bus_id in df_demand_share["bus_id"]:
726
            df_demand_share = df_demand_share.loc[
727
                df_demand_share["bus_id"] == bus_id
728
            ]
729
        else:
730
            raise KeyError(f"Bus with id {bus_id} not found")
731
732
    # get demand profile for all buildings for selected demand share
733
    df_building_profiles = pd.DataFrame()
734
    for bus_id, df in df_demand_share.groupby("bus_id"):
735
        shares = df.set_index("id", drop=True)["profile_share"]
736
        profile = df_cts_profiles.loc[:, bus_id]
737
        building_profiles = profile.apply(lambda x: x * shares)
0 ignored issues
show
introduced by
The variable shares does not seem to be defined in case the for loop on line 734 is not entered. Are you sure this can never be the case?
Loading history...
738
        df_building_profiles = pd.concat(
739
            [df_building_profiles, building_profiles], axis=1
740
        )
741
742
    return df_building_profiles
743
744
745
def cts_to_buildings():
746
    """
747
    Assigns CTS demand to buildings and calculates the respective demand
748
    profiles. The demand profile per substation are disaggregated per
749
    annual demand share of each census cell and by the number of amenities
750
    per building within the cell. If no building data is available,
751
    synthetic buildings are generated around the amenities. If no amenities
752
    but cts demand is available, buildings are randomly selected. If no
753
    building nor amenity is available, random synthetic buildings are
754
    generated. The demand share is stored in the database.
755
756
    Note:
757
    -----
758
    Cells with CTS demand, amenities and buildings do not change within
759
    the scenarios, only the demand itself. Therefore scenario eGon2035
760
    can be used universally to determine the cts buildings but not for
761
    he demand share.
762
    """
763
764
    # Buildings with amenities
765
    df_buildings_with_amenities = buildings_with_amenities()
766
767
    # Remove synthetic CTS buildings if existing
768
    delete_synthetic_cts_buildings()
769
770
    # Create synthetic buildings for amenites without buildings
771
    df_amenities_without_buildings = amenities_without_buildings()
772
    df_amenities_without_buildings["n_amenities_inside"] = 1
773
    df_synthetic_buildings_with_amenities = create_synthetic_buildings(
774
        df_amenities_without_buildings, points="geom_amenity"
775
    )
776
777
    # TODO write to DB and remove renaming
778
    # write_synthetic_buildings_to_db(df_synthetic_buildings_with_amenities)
779
    write_table_to_postgis(
780
        df_synthetic_buildings_with_amenities.rename(
781
            columns={
782
                "zensus_population_id": "cell_id",
783
                "egon_building_id": "id",
784
            }
785
        ),
786
        OsmBuildingsSynthetic,
787
        drop=False,
788
    )
789
790
    # Cells without amenities but CTS demand and buildings
791
    df_buildings_without_amenities = buildings_without_amenities()
792
793
    # TODO Fix Adhoc Bugfix duplicated buildings
794
    mask = df_buildings_without_amenities.loc[
795
        df_buildings_without_amenities["id"].isin(
796
            df_buildings_with_amenities["id"]
797
        )
798
    ].index
799
    df_buildings_without_amenities = df_buildings_without_amenities.drop(
800
        index=mask
801
    ).reset_index(drop=True)
802
803
    df_buildings_without_amenities = select_cts_buildings(
804
        df_buildings_without_amenities
805
    )
806
    df_buildings_without_amenities["n_amenities_inside"] = 1
807
808
    # Create synthetic amenities and buildings in cells with only CTS demand
809
    df_cells_with_cts_demand_only = cells_with_cts_demand_only(
810
        df_buildings_without_amenities
811
    )
812
    # Only 1 Amenity per cell
813
    df_cells_with_cts_demand_only["n_amenities_inside"] = 1
814
    # Only 1 Amenity per Building
815
    df_cells_with_cts_demand_only = place_buildings_with_amenities(
816
        df_cells_with_cts_demand_only, amenities=1
817
    )
818
    # Leads to only 1 building per cell
819
    df_synthetic_buildings_without_amenities = create_synthetic_buildings(
820
        df_cells_with_cts_demand_only, points="geom_point"
821
    )
822
823
    # TODO write to DB and remove renaming
824
    # write_synthetic_buildings_to_db(df_synthetic_buildings_without_amenities)
825
    write_table_to_postgis(
826
        df_synthetic_buildings_without_amenities.rename(
827
            columns={
828
                "zensus_population_id": "cell_id",
829
                "egon_building_id": "id",
830
            }
831
        ),
832
        OsmBuildingsSynthetic,
833
        drop=False,
834
    )
835
836
    # Concat all buildings
837
    columns = [
838
        "zensus_population_id",
839
        "id",
840
        "geom_building",
841
        "n_amenities_inside",
842
        "source",
843
    ]
844
845
    df_buildings_with_amenities["source"] = "bwa"
846
    df_synthetic_buildings_with_amenities["source"] = "sbwa"
847
    df_buildings_without_amenities["source"] = "bwoa"
848
    df_synthetic_buildings_without_amenities["source"] = "sbwoa"
849
850
    df_cts_buildings = pd.concat(
851
        [
852
            df_buildings_with_amenities[columns],
853
            df_synthetic_buildings_with_amenities[columns],
854
            df_buildings_without_amenities[columns],
855
            df_synthetic_buildings_without_amenities[columns],
856
        ],
857
        axis=0,
858
        ignore_index=True,
859
    )
860
    # TODO maybe remove after #772
861
    df_cts_buildings["id"] = df_cts_buildings["id"].astype(int)
862
863
    # Write table to db for debugging
864
    # TODO remove later
865
    df_cts_buildings = gpd.GeoDataFrame(
866
        df_cts_buildings, geometry="geom_building", crs=3035
867
    )
868
    write_table_to_postgis(
869
        df_cts_buildings,
870
        CtsBuildings,
871
        drop=True,
872
    )
873
874
    df_demand_share_2035 = calc_building_demand_profile_share(
875
        df_cts_buildings, scenario="eGon2035"
876
    )
877
    df_demand_share_100RE = calc_building_demand_profile_share(
878
        df_cts_buildings, scenario="eGon100RE"
879
    )
880
881
    df_demand_share = pd.concat(
882
        [df_demand_share_2035, df_demand_share_100RE],
883
        axis=0,
884
        ignore_index=True,
885
    )
886
887
    write_table_to_postgres(
888
        df_demand_share, EgonCtsElectricityDemandBuildingShare, drop=True
889
    )
890
891
    return df_cts_buildings, df_demand_share
892
893
894
def get_peak_load_cts_buildings():
895
    """
896
    Get peak load of all CTS buildings for both scenarios and store in DB.
897
    """
898
899
    # TODO Check units, maybe MwH?
900
    df_building_profiles = calc_building_profiles(scenario="eGon2035")
901
    df_peak_load_2035 = df_building_profiles.max(axis=0).rename(
902
        "cts_peak_load_in_w_2035"
903
    )
904
    df_building_profiles = calc_building_profiles(scenario="eGon2035")
905
    df_peak_load_100RE = df_building_profiles.max(axis=0).rename(
906
        "cts_peak_load_in_w_100RE"
907
    )
908
    df_peak_load = pd.concat(
909
        [df_peak_load_2035, df_peak_load_100RE], axis=1
910
    ).reset_index()
911
912
    CtsPeakLoads.__table__.drop(bind=engine, checkfirst=True)
913
    CtsPeakLoads.__table__.create(bind=engine, checkfirst=True)
914
915
    # Write peak loads into db
916
    with db.session_scope() as session:
917
        session.bulk_insert_mappings(
918
            CtsPeakLoads,
919
            df_peak_load.to_dict(orient="records"),
920
        )
921
922
923
def delete_synthetic_cts_buildings():
924
    """
925
    All synthetic cts buildings are deleted from the DB. This is necessary if
926
    the task is run multiple times as the existing synthetic buildings
927
    influence the results.
928
    """
929
    # import db tables
930
    from saio.openstreetmap import osm_buildings_synthetic
931
932
    # cells mit amenities
933
    with db.session_scope() as session:
934
        session.query(osm_buildings_synthetic).filter(
935
            osm_buildings_synthetic.building == "cts"
936
        ).delete()
937
938
939
class CtsElectricityBuildings(Dataset):
940
    def __init__(self, dependencies):
941
        super().__init__(
942
            name="CtsElectricityBuildings",
943
            version="0.0.0.",
944
            dependencies=dependencies,
945
            tasks=(
946
                cts_to_buildings,
947
                get_peak_load_cts_buildings,
948
                # get_all_cts_building_profiles,
949
            ),
950
        )
951