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

data.datasets.electricity_demand_timeseries.cts_buildings   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 1448
Duplicated Lines 4.35 %

Importance

Changes 0
Metric Value
wmc 60
eloc 700
dl 63
loc 1448
rs 3.5
c 0
b 0
f 0

17 Functions

Rating   Name   Duplication   Size   Complexity  
A cts_heat() 32 32 2
A place_buildings_with_amenities() 0 41 3
B create_synthetic_buildings() 0 66 6
A delete_synthetic_cts_buildings() 0 14 2
A remove_double_bus_id() 0 43 3
B get_cts_electricity_peak_load() 0 56 5
C calc_building_profiles() 0 104 10
A amenities_without_buildings() 0 41 2
A select_cts_buildings() 0 25 1
B cells_with_cts_demand_only() 0 72 2
B get_cts_heat_peak_load() 0 68 6
A calc_building_demand_profile_share() 0 81 2
C buildings_with_amenities() 0 183 5
A cts_electricity() 31 31 2
A calc_census_cell_share() 0 56 4
B cts_buildings() 0 185 2
B buildings_without_amenities() 0 99 2

1 Method

Rating   Name   Duplication   Size   Complexity  
A CtsDemandBuildings.__init__() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like data.datasets.electricity_demand_timeseries.cts_buildings often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
CTS electricity and heat demand time series for scenarios in 2035 and 2050
3
assigned to OSM-buildings.
4
5
Disaggregation of cts heat & electricity demand time series from MV Substation
6
to census cells via annual demand and then to OSM buildings via
7
amenity tags or randomly if no sufficient OSM-data is available in the
8
respective census cell. If no OSM-buildings or synthetic residential buildings
9
are available new synthetic 5x5m buildings are generated.
10
11
The resulting data is stored in separate tables
12
13
* `openstreetmap.osm_buildings_synthetic`:
14
    Lists generated synthetic building with id, zensus_population_id and
15
    building type. This table is already created within
16
    :func:`hh_buildings.map_houseprofiles_to_buildings()`
17
* `openstreetmap.egon_cts_buildings`:
18
    Table of all selected cts buildings with id, census cell id, geometry and
19
    amenity count in building. This table is created within
20
    :func:`cts_buildings()`
21
* `demand.egon_cts_electricity_demand_building_share`:
22
    Table including the mv substation electricity profile share of all selected
23
    cts buildings for scenario eGon2035 and eGon100RE. This table is created
24
    within :func:`cts_electricity()`
25
* `demand.egon_cts_heat_demand_building_share`:
26
    Table including the mv substation heat profile share of all selected
27
    cts buildings for scenario eGon2035 and eGon100RE. This table is created
28
    within :func:`cts_heat()`
29
* `demand.egon_building_peak_loads`:
30
    Mapping of demand time series and buildings including cell_id, building
31
    area and peak load. This table is already created within
32
    :func:`hh_buildings.get_building_peak_loads()`
33
34
**The following datasets from the database are mainly used for creation:**
35
36
* `openstreetmap.osm_buildings_filtered`:
37
    Table of OSM-buildings filtered by tags to selecting residential and cts
38
    buildings only.
39
* `openstreetmap.osm_amenities_shops_filtered`:
40
    Table of OSM-amenities filtered by tags to select cts only.
41
* `openstreetmap.osm_amenities_not_in_buildings_filtered`:
42
    Table of amenities which do not intersect with any building from
43
    `openstreetmap.osm_buildings_filtered`
44
* `openstreetmap.osm_buildings_synthetic`:
45
    Table of synthetic residential buildings
46
* `boundaries.egon_map_zensus_buildings_filtered_all`:
47
    Mapping table of census cells and buildings filtered even if population
48
    in census cell = 0.
49
* `demand.egon_demandregio_zensus_electricity`:
50
    Table of annual electricity load demand for residential and cts at census
51
    cell level. Residential load demand is derived from aggregated residential
52
    building profiles. DemandRegio CTS load demand at NUTS3 is distributed to
53
    census cells linearly to heat demand from peta5.
54
* `demand.egon_peta_heat`:
55
    Table of annual heat load demand for residential and cts at census cell
56
    level from peta5.
57
* `demand.egon_etrago_electricity_cts`:
58
    Scaled cts electricity time series for every MV substation. Derived from
59
    DemandRegio SLP for selected economic sectors at nuts3. Scaled with annual
60
    demand from `demand.egon_demandregio_zensus_electricity`
61
* `demand.egon_etrago_heat_cts`:
62
    Scaled cts heat time series for every MV substation. Derived from
63
    DemandRegio SLP Gas for selected economic sectors at nuts3. Scaled with
64
    annual demand from `demand.egon_peta_heat`.
65
66
**What is the goal?**
67
68
To disaggregate cts heat and electricity time series from MV substation level
69
to geo-referenced buildings. DemandRegio and Peta5 is used to identify census
70
cells with load demand. Openstreetmap data is used and filtered via tags to
71
identify buildings and count amenities within. The number of amenities serve
72
to assign the appropriate load demand share to the building.
73
74
**What is the challenge?**
75
76
The OSM, DemandRegio and Peta5 dataset differ from each other. The OSM dataset
77
is a community based dataset which is extended throughout and does not claim to
78
be complete. Therefore not all census cells which have a demand assigned by
79
DemandRegio or Peta5 methodology also have buildings with respective tags or no
80
buildings at all. Merging these datasets inconsistencies need
81
to be addressed. For example: not yet tagged buildings or amenities in OSM
82
83
**How are these datasets combined?**
84
85
------>>>>>> continue
86
87
Firstly, all cts buildings are selected. Buildings which have cts amenities
88
inside.
89
90
91
**What are central assumptions during the data processing?**
92
93
* Mapping census to OSM data is not trivial. Discrepancies are substituted.
94
* Missing OSM buildings are generated by census building count.
95
* If no census building count data is available, the number of buildings is
96
derived by an average rate of households/buildings applied to the number of
97
households.
98
99
**Drawbacks and limitations of the data**
100
101
* Missing OSM buildings in cells without census building count are derived by
102
an average rate of households/buildings applied to the number of households.
103
As only whole houses can exist, the substitute is ceiled to the next higher
104
integer. Ceiling is applied to avoid rounding to amount of 0 buildings.
105
106
* As this datasets is a cascade after profile assignement at census cells
107
also check drawbacks and limitations in hh_profiles.py.
108
109
110
111
Example Query
112
-----
113
114
115
Notes
116
-----
117
118
This module docstring is rather a dataset documentation. Once, a decision
119
is made in ... the content of this module docstring needs to be moved to
120
docs attribute of the respective dataset class.
121
"""
122
123
from geoalchemy2 import Geometry
124
from geoalchemy2.shape import to_shape
125
from sqlalchemy import REAL, Column, Integer, String, func
126
from sqlalchemy.ext.declarative import declarative_base
127
import geopandas as gpd
128
import numpy as np
129
import pandas as pd
130
import saio
131
132
from egon.data import db
133
from egon.data import logger as log
134
from egon.data.datasets import Dataset
135
from egon.data.datasets.electricity_demand import (
136
    EgonDemandRegioZensusElectricity,
137
)
138
from egon.data.datasets.electricity_demand.temporal import (
139
    EgonEtragoElectricityCts,
140
    calc_load_curves_cts,
141
)
142
from egon.data.datasets.electricity_demand_timeseries.hh_buildings import (
143
    BuildingElectricityPeakLoads,
144
    OsmBuildingsSynthetic,
145
)
146
from egon.data.datasets.electricity_demand_timeseries.tools import (
147
    random_ints_until_sum,
148
    random_point_in_square,
149
    specific_int_until_sum,
150
    write_table_to_postgis,
151
    write_table_to_postgres,
152
)
153
from egon.data.datasets.heat_demand import EgonPetaHeat
154
from egon.data.datasets.zensus_mv_grid_districts import MapZensusGridDistricts
155
from egon.data.datasets.zensus_vg250 import DestatisZensusPopulationPerHa
156
157
engine = db.engine()
158
Base = declarative_base()
159
160
# import db tables
161
saio.register_schema("openstreetmap", engine=engine)
162
saio.register_schema("boundaries", engine=engine)
163
164
165
class EgonCtsElectricityDemandBuildingShare(Base):
166
    __tablename__ = "egon_cts_electricity_demand_building_share"
167
    __table_args__ = {"schema": "demand"}
168
169
    id = Column(Integer, primary_key=True)
170
    scenario = Column(String, primary_key=True)
171
    bus_id = Column(Integer, index=True)
172
    profile_share = Column(REAL)
173
174
175
class EgonCtsHeatDemandBuildingShare(Base):
176
    __tablename__ = "egon_cts_heat_demand_building_share"
177
    __table_args__ = {"schema": "demand"}
178
179
    id = Column(Integer, primary_key=True)
180
    scenario = Column(String, primary_key=True)
181
    bus_id = Column(Integer, index=True)
182
    profile_share = Column(REAL)
183
184
185
class CtsBuildings(Base):
186
    __tablename__ = "egon_cts_buildings"
187
    __table_args__ = {"schema": "openstreetmap"}
188
189
    serial = Column(Integer, primary_key=True)
190
    id = Column(Integer, index=True)
191
    zensus_population_id = Column(Integer, index=True)
192
    geom_building = Column(Geometry("Polygon", 3035))
193
    n_amenities_inside = Column(Integer)
194
    source = Column(String)
195
196
197
class BuildingHeatPeakLoads(Base):
198
    __tablename__ = "egon_building_heat_peak_loads"
199
    __table_args__ = {"schema": "demand"}
200
201
    building_id = Column(Integer, primary_key=True)
202
    scenario = Column(String, primary_key=True)
203
    sector = Column(String, primary_key=True)
204
    peak_load_in_w = Column(REAL)
205
206
207
def amenities_without_buildings():
208
    """
209
    Amenities which have no buildings assigned and are in
210
    a cell with cts demand are determined.
211
212
    Returns
213
    -------
214
    pd.DataFrame
215
        Table of amenities without buildings
216
    """
217
    from saio.openstreetmap import osm_amenities_not_in_buildings_filtered
218
219
    with db.session_scope() as session:
220
        cells_query = (
221
            session.query(
222
                DestatisZensusPopulationPerHa.id.label("zensus_population_id"),
223
                osm_amenities_not_in_buildings_filtered.geom_amenity,
224
                osm_amenities_not_in_buildings_filtered.egon_amenity_id,
225
            )
226
            .filter(
227
                func.st_within(
228
                    osm_amenities_not_in_buildings_filtered.geom_amenity,
229
                    DestatisZensusPopulationPerHa.geom,
230
                )
231
            )
232
            .filter(
233
                DestatisZensusPopulationPerHa.id
234
                == EgonDemandRegioZensusElectricity.zensus_population_id
235
            )
236
            .filter(
237
                EgonDemandRegioZensusElectricity.sector == "service",
238
                EgonDemandRegioZensusElectricity.scenario == "eGon2035",
239
            )
240
        )
241
242
    df_amenities_without_buildings = gpd.read_postgis(
243
        cells_query.statement,
244
        cells_query.session.bind,
245
        geom_col="geom_amenity",
246
    )
247
    return df_amenities_without_buildings
248
249
250
def place_buildings_with_amenities(df, amenities=None, max_amenities=None):
251
    """
252
    Building centroids are placed randomly within census cells.
253
    The Number of buildings is derived from n_amenity_inside, the selected
254
    method and number of amenities per building.
255
256
    Returns
257
    -------
258
    df: gpd.GeoDataFrame
259
        Table of buildings centroids
260
    """
261
    if isinstance(max_amenities, int):
262
        # amount of amenities is randomly generated within bounds
263
        # (max_amenities, amenities per cell)
264
        df["n_amenities_inside"] = df["n_amenities_inside"].apply(
265
            random_ints_until_sum, args=[max_amenities]
266
        )
267
    if isinstance(amenities, int):
268
        # Specific amount of amenities per building
269
        df["n_amenities_inside"] = df["n_amenities_inside"].apply(
270
            specific_int_until_sum, args=[amenities]
271
        )
272
273
    # Unnest each building
274
    df = df.explode(column="n_amenities_inside")
275
276
    # building count per cell
277
    df["building_count"] = df.groupby(["zensus_population_id"]).cumcount() + 1
278
279
    # generate random synthetic buildings
280
    edge_length = 5
281
    # create random points within census cells
282
    points = random_point_in_square(geom=df["geom"], tol=edge_length / 2)
283
284
    df.reset_index(drop=True, inplace=True)
285
    # Store center of polygon
286
    df["geom_point"] = points
287
    # Drop geometry of census cell
288
    df = df.drop(columns=["geom"])
289
290
    return df
291
292
293
def create_synthetic_buildings(df, points=None, crs="EPSG:3035"):
294
    """
295
    Synthetic buildings are generated around points.
296
297
    Parameters
298
    ----------
299
    df: pd.DataFrame
300
        Table of census cells
301
    points: gpd.GeoSeries or str
302
        List of points to place buildings around or column name of df
303
    crs: str
304
        CRS of result table
305
306
    Returns
307
    -------
308
    df: gpd.GeoDataFrame
309
        Synthetic buildings
310
    """
311
312
    if isinstance(points, str) and points in df.columns:
313
        points = df[points]
314
    elif isinstance(points, gpd.GeoSeries):
315
        pass
316
    else:
317
        raise ValueError("Points are of the wrong type")
318
319
    # Create building using a square around point
320
    edge_length = 5
321
    df["geom_building"] = points.buffer(distance=edge_length / 2, cap_style=3)
322
323
    if "geom_point" not in df.columns:
324
        df["geom_point"] = df["geom_building"].centroid
325
326
    df = gpd.GeoDataFrame(
327
        df,
328
        crs=crs,
329
        geometry="geom_building",
330
    )
331
332
    # TODO remove after #772 implementation of egon_building_id
333
    df.rename(columns={"id": "egon_building_id"}, inplace=True)
334
335
    # get max number of building ids from synthetic residential table
336
    with db.session_scope() as session:
337
        max_synth_residential_id = session.execute(
338
            func.max(OsmBuildingsSynthetic.id)
339
        ).scalar()
340
    max_synth_residential_id = int(max_synth_residential_id)
341
342
    # create sequential ids
343
    df["egon_building_id"] = range(
344
        max_synth_residential_id + 1,
345
        max_synth_residential_id + df.shape[0] + 1,
346
    )
347
348
    df["area"] = df["geom_building"].area
349
    # set building type of synthetic building
350
    df["building"] = "cts"
351
    # TODO remove in #772
352
    df = df.rename(
353
        columns={
354
            # "zensus_population_id": "cell_id",
355
            "egon_building_id": "id",
356
        }
357
    )
358
    return df
359
360
361
def buildings_with_amenities():
362
    """
363
    Amenities which are assigned to buildings are determined
364
    and grouped per building and zensus cell. Buildings
365
    covering multiple cells therefore exists multiple times
366
    but in different zensus cells. This is necessary to cover
367
    all cells with a cts demand. If buildings exist in multiple
368
    substations, their amenities are summed and assigned and kept in
369
    one substation only. If as a result, a census cell is uncovered,
370
    a synthetic amenity is placed. The buildings are aggregated
371
    afterwards during the calculation of the profile_share.
372
373
    Returns
374
    -------
375
    df_buildings_with_amenities: gpd.GeoDataFrame
376
        Contains all buildings with amenities per zensus cell.
377
    df_lost_cells: gpd.GeoDataFrame
378
        Contains synthetic amenities in lost cells. Might be empty
379
    """
380
381
    from saio.openstreetmap import osm_amenities_in_buildings_filtered
382
383
    with db.session_scope() as session:
384
        cells_query = (
385
            session.query(
386
                osm_amenities_in_buildings_filtered,
387
                MapZensusGridDistricts.bus_id,
388
            )
389
            .filter(
390
                MapZensusGridDistricts.zensus_population_id
391
                == osm_amenities_in_buildings_filtered.zensus_population_id
392
            )
393
            .filter(
394
                EgonDemandRegioZensusElectricity.zensus_population_id
395
                == osm_amenities_in_buildings_filtered.zensus_population_id
396
            )
397
            .filter(
398
                EgonDemandRegioZensusElectricity.sector == "service",
399
                EgonDemandRegioZensusElectricity.scenario == "eGon2035",
400
            )
401
        )
402
    df_amenities_in_buildings = pd.read_sql(
403
        cells_query.statement, cells_query.session.bind, index_col=None
404
    )
405
406
    df_amenities_in_buildings["geom_building"] = df_amenities_in_buildings[
407
        "geom_building"
408
    ].apply(to_shape)
409
    df_amenities_in_buildings["geom_amenity"] = df_amenities_in_buildings[
410
        "geom_amenity"
411
    ].apply(to_shape)
412
413
    df_amenities_in_buildings["n_amenities_inside"] = 1
414
415
    # add identifier column for buildings in multiple substations
416
    df_amenities_in_buildings[
417
        "duplicate_identifier"
418
    ] = df_amenities_in_buildings.groupby(["id", "bus_id"])[
419
        "n_amenities_inside"
420
    ].transform(
421
        "cumsum"
422
    )
423
    df_amenities_in_buildings = df_amenities_in_buildings.sort_values(
424
        ["id", "duplicate_identifier"]
425
    )
426
    # sum amenities of buildings with multiple substations
427
    df_amenities_in_buildings[
428
        "n_amenities_inside"
429
    ] = df_amenities_in_buildings.groupby(["id", "duplicate_identifier"])[
430
        "n_amenities_inside"
431
    ].transform(
432
        "sum"
433
    )
434
435
    # create column to always go for bus_id with max amenities
436
    df_amenities_in_buildings[
437
        "max_amenities"
438
    ] = df_amenities_in_buildings.groupby(["id", "bus_id"])[
439
        "n_amenities_inside"
440
    ].transform(
441
        "sum"
442
    )
443
    # sort to go for
444
    df_amenities_in_buildings.sort_values(
445
        ["id", "max_amenities"], ascending=False, inplace=True
446
    )
447
448
    # identify lost zensus cells
449
    df_lost_cells = df_amenities_in_buildings.loc[
450
        df_amenities_in_buildings.duplicated(
451
            subset=["id", "duplicate_identifier"], keep="first"
452
        )
453
    ]
454
    df_lost_cells.drop_duplicates(
455
        subset=["zensus_population_id"], inplace=True
456
    )
457
458
    # drop buildings with multiple substation and lower max amenity
459
    df_amenities_in_buildings.drop_duplicates(
460
        subset=["id", "duplicate_identifier"], keep="first", inplace=True
461
    )
462
463
    # check if lost zensus cells are already covered
464
    if not df_lost_cells.empty:
465
        if not (
466
            df_amenities_in_buildings["zensus_population_id"]
467
            .isin(df_lost_cells["zensus_population_id"])
468
            .empty
469
        ):
470
            # query geom data for cell if not
471
            with db.session_scope() as session:
472
                cells_query = session.query(
473
                    DestatisZensusPopulationPerHa.id,
474
                    DestatisZensusPopulationPerHa.geom,
475
                ).filter(
476
                    DestatisZensusPopulationPerHa.id.in_(
477
                        df_lost_cells["zensus_population_id"]
478
                    )
479
                )
480
481
            df_lost_cells = gpd.read_postgis(
482
                cells_query.statement,
483
                cells_query.session.bind,
484
                geom_col="geom",
485
            )
486
            # TODO maybe adapt method
487
            # place random amenity in cell
488
            df_lost_cells["n_amenities_inside"] = 1
489
            df_lost_cells.rename(
490
                columns={
491
                    "id": "zensus_population_id",
492
                },
493
                inplace=True,
494
            )
495
            df_lost_cells = place_buildings_with_amenities(
496
                df_lost_cells, amenities=1
497
            )
498
            df_lost_cells.rename(
499
                columns={
500
                    # "id": "zensus_population_id",
501
                    "geom_point": "geom_amenity",
502
                },
503
                inplace=True,
504
            )
505
            df_lost_cells.drop(
506
                columns=["building_count", "n_amenities_inside"], inplace=True
507
            )
508
        else:
509
            df_lost_cells = None
510
    else:
511
        df_lost_cells = None
512
513
    # drop helper columns
514
    df_amenities_in_buildings.drop(
515
        columns=["duplicate_identifier"], inplace=True
516
    )
517
518
    # sum amenities per building and cell
519
    df_amenities_in_buildings[
520
        "n_amenities_inside"
521
    ] = df_amenities_in_buildings.groupby(["zensus_population_id", "id"])[
522
        "n_amenities_inside"
523
    ].transform(
524
        "sum"
525
    )
526
    # drop duplicated buildings
527
    df_buildings_with_amenities = df_amenities_in_buildings.drop_duplicates(
528
        ["id", "zensus_population_id"]
529
    )
530
    df_buildings_with_amenities.reset_index(inplace=True, drop=True)
531
532
    df_buildings_with_amenities = df_buildings_with_amenities[
533
        ["id", "zensus_population_id", "geom_building", "n_amenities_inside"]
534
    ]
535
    df_buildings_with_amenities.rename(
536
        columns={
537
            # "zensus_population_id": "cell_id",
538
            "egon_building_id": "id"
539
        },
540
        inplace=True,
541
    )
542
543
    return df_buildings_with_amenities, df_lost_cells
544
545
546
def buildings_without_amenities():
547
    """
548
    Buildings (filtered and synthetic) in cells with
549
    cts demand but no amenities are determined.
550
551
    Returns
552
    -------
553
    df_buildings_without_amenities: gpd.GeoDataFrame
554
        Table of buildings without amenities in zensus cells
555
        with cts demand.
556
    """
557
    from saio.boundaries import egon_map_zensus_buildings_filtered_all
558
    from saio.openstreetmap import (
559
        osm_amenities_shops_filtered,
560
        osm_buildings_filtered,
561
        osm_buildings_synthetic,
562
    )
563
564
    # buildings_filtered in cts-demand-cells without amenities
565
    with db.session_scope() as session:
566
567
        # Synthetic Buildings
568
        q_synth_buildings = session.query(
569
            osm_buildings_synthetic.cell_id.cast(Integer).label(
570
                "zensus_population_id"
571
            ),
572
            osm_buildings_synthetic.id.cast(Integer).label("id"),
573
            osm_buildings_synthetic.area.label("area"),
574
            osm_buildings_synthetic.geom_building.label("geom_building"),
575
            osm_buildings_synthetic.geom_point.label("geom_point"),
576
        )
577
578
        # Buildings filtered
579
        q_buildings_filtered = session.query(
580
            egon_map_zensus_buildings_filtered_all.zensus_population_id,
581
            osm_buildings_filtered.id,
582
            osm_buildings_filtered.area,
583
            osm_buildings_filtered.geom_building,
584
            osm_buildings_filtered.geom_point,
585
        ).filter(
586
            osm_buildings_filtered.id
587
            == egon_map_zensus_buildings_filtered_all.id
588
        )
589
590
        # Amenities + zensus_population_id
591
        q_amenities = (
592
            session.query(
593
                DestatisZensusPopulationPerHa.id.label("zensus_population_id"),
594
            )
595
            .filter(
596
                func.st_within(
597
                    osm_amenities_shops_filtered.geom_amenity,
598
                    DestatisZensusPopulationPerHa.geom,
599
                )
600
            )
601
            .distinct(DestatisZensusPopulationPerHa.id)
602
        )
603
604
        # Cells with CTS demand but without amenities
605
        q_cts_without_amenities = (
606
            session.query(
607
                EgonDemandRegioZensusElectricity.zensus_population_id,
608
            )
609
            .filter(
610
                EgonDemandRegioZensusElectricity.sector == "service",
611
                EgonDemandRegioZensusElectricity.scenario == "eGon2035",
612
            )
613
            .filter(
614
                EgonDemandRegioZensusElectricity.zensus_population_id.notin_(
615
                    q_amenities
616
                )
617
            )
618
            .distinct()
619
        )
620
621
        # Buildings filtered + synthetic buildings residential in
622
        # cells with CTS demand but without amenities
623
        cells_query = q_synth_buildings.union(q_buildings_filtered).filter(
624
            egon_map_zensus_buildings_filtered_all.zensus_population_id.in_(
625
                q_cts_without_amenities
626
            )
627
        )
628
629
    # df_buildings_without_amenities = pd.read_sql(
630
    #     cells_query.statement, cells_query.session.bind, index_col=None)
631
    df_buildings_without_amenities = gpd.read_postgis(
632
        cells_query.statement,
633
        cells_query.session.bind,
634
        geom_col="geom_building",
635
    )
636
637
    df_buildings_without_amenities = df_buildings_without_amenities.rename(
638
        columns={
639
            # "zensus_population_id": "cell_id",
640
            "egon_building_id": "id",
641
        }
642
    )
643
644
    return df_buildings_without_amenities
645
646
647
def select_cts_buildings(df_buildings_wo_amenities, max_n):
648
    """
649
    N Buildings (filtered and synthetic) in each cell with
650
    cts demand are selected. Only the first n buildings
651
    are taken for each cell. The buildings are sorted by surface
652
    area.
653
654
    Returns
655
    -------
656
    df_buildings_with_cts_demand: gpd.GeoDataFrame
657
        Table of buildings
658
    """
659
660
    df_buildings_wo_amenities.sort_values(
661
        "area", ascending=False, inplace=True
662
    )
663
    # select first n ids each census cell if available
664
    df_buildings_with_cts_demand = (
665
        df_buildings_wo_amenities.groupby("zensus_population_id")
666
        .nth(list(range(max_n)))
667
        .reset_index()
668
    )
669
    df_buildings_with_cts_demand.reset_index(drop=True, inplace=True)
670
671
    return df_buildings_with_cts_demand
672
673
674
def cells_with_cts_demand_only(df_buildings_without_amenities):
675
    """
676
    Cells with cts demand but no amenities or buildilngs
677
    are determined.
678
679
    Returns
680
    -------
681
    df_cells_only_cts_demand: gpd.GeoDataFrame
682
        Table of cells with cts demand but no amenities or buildings
683
    """
684
    from saio.openstreetmap import osm_amenities_shops_filtered
685
686
    # cells mit amenities
687
    with db.session_scope() as session:
688
        sub_query = (
689
            session.query(
690
                DestatisZensusPopulationPerHa.id.label("zensus_population_id"),
691
            )
692
            .filter(
693
                func.st_within(
694
                    osm_amenities_shops_filtered.geom_amenity,
695
                    DestatisZensusPopulationPerHa.geom,
696
                )
697
            )
698
            .distinct(DestatisZensusPopulationPerHa.id)
699
        )
700
701
        cells_query = (
702
            session.query(
703
                EgonDemandRegioZensusElectricity.zensus_population_id,
704
                EgonDemandRegioZensusElectricity.scenario,
705
                EgonDemandRegioZensusElectricity.sector,
706
                EgonDemandRegioZensusElectricity.demand,
707
                DestatisZensusPopulationPerHa.geom,
708
            )
709
            .filter(
710
                EgonDemandRegioZensusElectricity.sector == "service",
711
                EgonDemandRegioZensusElectricity.scenario == "eGon2035",
712
            )
713
            .filter(
714
                EgonDemandRegioZensusElectricity.zensus_population_id.notin_(
715
                    sub_query
716
                )
717
            )
718
            .filter(
719
                EgonDemandRegioZensusElectricity.zensus_population_id
720
                == DestatisZensusPopulationPerHa.id
721
            )
722
        )
723
724
    df_cts_cell_without_amenities = gpd.read_postgis(
725
        cells_query.statement,
726
        cells_query.session.bind,
727
        geom_col="geom",
728
        index_col=None,
729
    )
730
731
    # TODO maybe remove
732
    df_buildings_without_amenities = df_buildings_without_amenities.rename(
733
        columns={"cell_id": "zensus_population_id"}
734
    )
735
736
    # Census cells with only cts demand
737
    df_cells_only_cts_demand = df_cts_cell_without_amenities.loc[
738
        ~df_cts_cell_without_amenities["zensus_population_id"].isin(
739
            df_buildings_without_amenities["zensus_population_id"].unique()
740
        )
741
    ]
742
743
    df_cells_only_cts_demand.reset_index(drop=True, inplace=True)
744
745
    return df_cells_only_cts_demand
746
747
748
def calc_census_cell_share(scenario="eGon2035", sector="electricity"):
749
    """
750
    The profile share for each census cell is calculated by it's
751
    share of annual demand per substation bus. The annual demand
752
    per cell is defined by DemandRegio/Peta5. The share is for both
753
    scenarios identical as the annual demand is linearly scaled.
754
755
    Parameters
756
    ----------
757
    scenario: str
758
        Scenario for which the share is calculated.
759
    sector: str
760
        Scenario for which the share is calculated.
761
762
    Returns
763
    -------
764
    df_census_share: pd.DataFrame
765
    """
766
    if sector == "electricity":
767
        demand_table = EgonDemandRegioZensusElectricity
768
    elif sector == "heat":
769
        demand_table = EgonPetaHeat
770
771
    with db.session_scope() as session:
772
        cells_query = (
773
            session.query(demand_table, MapZensusGridDistricts.bus_id)
0 ignored issues
show
introduced by
The variable demand_table does not seem to be defined for all execution paths.
Loading history...
774
            .filter(demand_table.sector == "service")
775
            .filter(demand_table.scenario == scenario)
776
            .filter(
777
                demand_table.zensus_population_id
778
                == MapZensusGridDistricts.zensus_population_id
779
            )
780
        )
781
782
    df_demand = pd.read_sql(
783
        cells_query.statement,
784
        cells_query.session.bind,
785
        index_col="zensus_population_id",
786
    )
787
788
    # get demand share of cell per bus
789
    df_census_share = df_demand["demand"] / df_demand.groupby("bus_id")[
790
        "demand"
791
    ].transform("sum")
792
    df_census_share = df_census_share.rename("cell_share")
793
794
    df_census_share = pd.concat(
795
        [
796
            df_census_share,
797
            df_demand[["bus_id", "scenario"]],
798
        ],
799
        axis=1,
800
    )
801
802
    df_census_share.reset_index(inplace=True)
803
    return df_census_share
804
805
806
def calc_building_demand_profile_share(
807
    df_cts_buildings, scenario="eGon2035", sector="electricity"
808
):
809
    """
810
    Share of cts electricity demand profile per bus for every selected building
811
    is calculated. Building-amenity share is multiplied with census cell share
812
    to get the substation bus profile share for each building. The share is
813
    grouped and aggregated per building as some buildings exceed the shape of
814
    census cells and have amenities assigned from multiple cells. Building
815
    therefore get the amenity share of all census cells.
816
817
    Parameters
818
    ----------
819
    df_cts_buildings: gpd.GeoDataFrame
820
        Table of all buildings with cts demand assigned
821
    scenario: str
822
        Scenario for which the share is calculated.
823
    sector: str
824
        Sector for which the share is calculated.
825
826
    Returns
827
    -------
828
    df_building_share: pd.DataFrame
829
        Table of bus profile share per building
830
831
    """
832
833
    def calc_building_amenity_share(df_cts_buildings):
834
        """
835
        Calculate the building share by the number amenities per building
836
        within a census cell. Building ids can exist multiple time but with
837
        different zensus_population_ids.
838
        """
839
        df_building_amenity_share = df_cts_buildings[
840
            "n_amenities_inside"
841
        ] / df_cts_buildings.groupby("zensus_population_id")[
842
            "n_amenities_inside"
843
        ].transform(
844
            "sum"
845
        )
846
        df_building_amenity_share = pd.concat(
847
            [
848
                df_building_amenity_share.rename("building_amenity_share"),
849
                df_cts_buildings[["zensus_population_id", "id"]],
850
            ],
851
            axis=1,
852
        )
853
        return df_building_amenity_share
854
855
    df_building_amenity_share = calc_building_amenity_share(df_cts_buildings)
856
857
    df_census_cell_share = calc_census_cell_share(
858
        scenario=scenario, sector=sector
859
    )
860
861
    df_demand_share = pd.merge(
862
        left=df_building_amenity_share,
863
        right=df_census_cell_share,
864
        left_on="zensus_population_id",
865
        right_on="zensus_population_id",
866
    )
867
    df_demand_share["profile_share"] = df_demand_share[
868
        "building_amenity_share"
869
    ].multiply(df_demand_share["cell_share"])
870
871
    # TODO bus_id fix
872
    df_demand_share = df_demand_share[
873
        ["id", "bus_id", "scenario", "profile_share"]
874
    ]
875
876
    # Group and aggregate per building for multi cell buildings
877
    df_demand_share = (
878
        df_demand_share.groupby(["scenario", "id", "bus_id"])
879
        .sum()
880
        .reset_index()
881
    )
882
    if df_demand_share.duplicated("id", keep=False).any():
883
        print(
884
            df_demand_share.loc[df_demand_share.duplicated("id", keep=False)]
885
        )
886
    return df_demand_share
887
888
889
def calc_building_profiles(
890
    egon_building_id=None,
891
    bus_id=None,
892
    scenario="eGon2035",
893
    sector="electricity",
894
):
895
    """
896
    Calculate the demand profile for each building. The profile is
897
    calculated by the demand share of the building per substation bus.
898
899
    Parameters
900
    ----------
901
    egon_building_id: int
902
        Id of the building for which the profile is calculated.
903
        If not given, the profiles are calculated for all buildings.
904
    bus_id: int
905
        Id of the substation for which the all profiles are calculated.
906
        If not given, the profiles are calculated for all buildings.
907
    scenario: str
908
        Scenario for which the share is calculated.
909
    sector: str
910
        Sector for which the share is calculated.
911
912
    Returns
913
    -------
914
    df_building_profiles: pd.DataFrame
915
        Table of demand profile per building
916
    """
917
    if sector == "electricity":
918
        with db.session_scope() as session:
919
            cells_query = session.query(
920
                EgonCtsElectricityDemandBuildingShare,
921
            ).filter(
922
                EgonCtsElectricityDemandBuildingShare.scenario == scenario
923
            )
924
925
        df_demand_share = pd.read_sql(
926
            cells_query.statement, cells_query.session.bind, index_col=None
927
        )
928
929
        # TODO maybe use demand.egon_etrago_electricity_cts
930
        # with db.session_scope() as session:
931
        #     cells_query = (
932
        #         session.query(
933
        #             EgonEtragoElectricityCts
934
        #         ).filter(
935
        #             EgonEtragoElectricityCts.scn_name == scenario)
936
        #     )
937
        #
938
        # df_cts_profiles = pd.read_sql(
939
        #     cells_query.statement,
940
        #     cells_query.session.bind,
941
        # )
942
        # df_cts_profiles = pd.DataFrame.from_dict(
943
        #   df_cts_profiles.set_index('bus_id')['p_set'].to_dict(),
944
        #   orient="index")
945
        df_cts_profiles = calc_load_curves_cts(scenario)
946
947
    elif sector == "heat":
948
        with db.session_scope() as session:
949
            cells_query = session.query(
950
                EgonCtsHeatDemandBuildingShare,
951
            ).filter(EgonCtsHeatDemandBuildingShare.scenario == scenario)
952
953
        df_demand_share = pd.read_sql(
954
            cells_query.statement, cells_query.session.bind, index_col=None
955
        )
956
957
        # TODO cts heat substation profiles missing
958
959
    # get demand share of selected building id
960
    if isinstance(egon_building_id, int):
961
        if egon_building_id in df_demand_share["id"]:
0 ignored issues
show
introduced by
The variable df_demand_share does not seem to be defined for all execution paths.
Loading history...
962
            df_demand_share = df_demand_share.loc[
963
                df_demand_share["id"] == egon_building_id
964
            ]
965
        else:
966
            raise KeyError(f"Building with id {egon_building_id} not found")
967
    # TODO maybe add list
968
    # elif isinstance(egon_building_id, list):
969
970
    # get demand share of all buildings for selected bus id
971
    if isinstance(bus_id, int):
972
        if bus_id in df_demand_share["bus_id"]:
973
            df_demand_share = df_demand_share.loc[
974
                df_demand_share["bus_id"] == bus_id
975
            ]
976
        else:
977
            raise KeyError(f"Bus with id {bus_id} not found")
978
979
    # get demand profile for all buildings for selected demand share
980
    df_building_profiles = pd.DataFrame()
981
    for bus_id, df in df_demand_share.groupby("bus_id"):
982
        shares = df.set_index("id", drop=True)["profile_share"]
983
        profile = df_cts_profiles.loc[:, bus_id]
0 ignored issues
show
introduced by
The variable df_cts_profiles does not seem to be defined for all execution paths.
Loading history...
984
        building_profiles = np.outer(profile, shares)
985
        building_profiles = pd.DataFrame(
986
            building_profiles, index=profile.index, columns=shares.index
987
        )
988
        df_building_profiles = pd.concat(
989
            [df_building_profiles, building_profiles], axis=1
990
        )
991
992
    return df_building_profiles
993
994
995
def delete_synthetic_cts_buildings():
996
    """
997
    All synthetic cts buildings are deleted from the DB. This is necessary if
998
    the task is run multiple times as the existing synthetic buildings
999
    influence the results.
1000
    """
1001
    # import db tables
1002
    from saio.openstreetmap import osm_buildings_synthetic
1003
1004
    # cells mit amenities
1005
    with db.session_scope() as session:
1006
        session.query(osm_buildings_synthetic).filter(
1007
            osm_buildings_synthetic.building == "cts"
1008
        ).delete()
1009
1010
1011
def remove_double_bus_id(df_cts_buildings):
1012
    """This is an backup adhoc fix if there should still be a building which
1013
    is assigned to 2 substations. In this case one of the buildings is just
1014
    dropped. As this currently accounts for only one building with one amenity
1015
    the deviation is neglectable."""
1016
    # assign bus_id via census cell of amenity
1017
    with db.session_scope() as session:
1018
        cells_query = session.query(
1019
            MapZensusGridDistricts.zensus_population_id,
1020
            MapZensusGridDistricts.bus_id,
1021
        )
1022
1023
    df_egon_map_zensus_buildings_buses = pd.read_sql(
1024
        cells_query.statement,
1025
        cells_query.session.bind,
1026
        index_col=None,
1027
    )
1028
    df_cts_buildings = pd.merge(
1029
        left=df_cts_buildings,
1030
        right=df_egon_map_zensus_buildings_buses,
1031
        on="zensus_population_id",
1032
    )
1033
1034
    substation_per_building = df_cts_buildings.groupby("id")[
1035
        "bus_id"
1036
    ].nunique()
1037
    building_id = substation_per_building.loc[
1038
        substation_per_building > 1
1039
    ].index
1040
    df_duplicates = df_cts_buildings.loc[
1041
        df_cts_buildings["id"].isin(building_id)
1042
    ]
1043
    for unique_id in df_duplicates["id"].unique():
1044
        drop_index = df_duplicates[df_duplicates["id"] == unique_id].index[0]
1045
        print(
1046
            f"Buildings {df_cts_buildings.loc[drop_index, 'id']}"
1047
            f" dropped because of double substation"
1048
        )
1049
        df_cts_buildings.drop(index=drop_index, inplace=True)
1050
1051
    df_cts_buildings.drop(columns="bus_id", inplace=True)
1052
1053
    return df_cts_buildings
1054
1055
1056
def cts_buildings():
1057
    """
1058
    Assigns CTS demand to buildings and calculates the respective demand
1059
    profiles. The demand profile per substation are disaggregated per
1060
    annual demand share of each census cell and by the number of amenities
1061
    per building within the cell. If no building data is available,
1062
    synthetic buildings are generated around the amenities. If no amenities
1063
    but cts demand is available, buildings are randomly selected. If no
1064
    building nor amenity is available, random synthetic buildings are
1065
    generated. The demand share is stored in the database.
1066
1067
    Note:
1068
    -----
1069
    Cells with CTS demand, amenities and buildings do not change within
1070
    the scenarios, only the demand itself. Therefore scenario eGon2035
1071
    can be used universally to determine the cts buildings but not for
1072
    he demand share.
1073
    """
1074
1075
    log.info("Start logging!")
1076
    # Buildings with amenities
1077
    df_buildings_with_amenities, df_lost_cells = buildings_with_amenities()
1078
    log.info("Buildings with amenities selected!")
1079
1080
    # Median number of amenities per cell
1081
    median_n_amenities = int(
1082
        df_buildings_with_amenities.groupby("zensus_population_id")[
1083
            "n_amenities_inside"
1084
        ]
1085
        .sum()
1086
        .median()
1087
    )
1088
    log.info(f"Median amenity value: {median_n_amenities}")
1089
1090
    # Remove synthetic CTS buildings if existing
1091
    delete_synthetic_cts_buildings()
1092
    log.info("Old synthetic cts buildings deleted!")
1093
1094
    # Amenities not assigned to buildings
1095
    df_amenities_without_buildings = amenities_without_buildings()
1096
    log.info("Amenities without buildlings selected!")
1097
1098
    # Append lost cells due to duplicated ids, to cover all demand cells
1099
    if not df_lost_cells.empty:
1100
1101
        df_lost_cells["amenities"] = median_n_amenities
1102
        # create row for every amenity
1103
        df_lost_cells["amenities"] = (
1104
            df_lost_cells["amenities"].astype(int).apply(range)
1105
        )
1106
        df_lost_cells = df_lost_cells.explode("amenities")
1107
        df_lost_cells.drop(columns="amenities", inplace=True)
1108
        df_amenities_without_buildings = df_amenities_without_buildings.append(
1109
            df_lost_cells, ignore_index=True
1110
        )
1111
        log.info("Lost cells due to substation intersection appended!")
1112
1113
    # One building per amenity
1114
    df_amenities_without_buildings["n_amenities_inside"] = 1
1115
    # Create synthetic buildings for amenites without buildings
1116
    df_synthetic_buildings_with_amenities = create_synthetic_buildings(
1117
        df_amenities_without_buildings, points="geom_amenity"
1118
    )
1119
    log.info("Synthetic buildings created!")
1120
1121
    # TODO write to DB and remove renaming
1122
    write_table_to_postgis(
1123
        df_synthetic_buildings_with_amenities.rename(
1124
            columns={
1125
                "zensus_population_id": "cell_id",
1126
                "egon_building_id": "id",
1127
            }
1128
        ),
1129
        OsmBuildingsSynthetic,
1130
        drop=False,
1131
    )
1132
    log.info("Synthetic buildings exported to DB!")
1133
1134
    # Cells without amenities but CTS demand and buildings
1135
    df_buildings_without_amenities = buildings_without_amenities()
1136
    log.info("Buildings without amenities in demand cells identified!")
1137
1138
    # Backup Bugfix for duplicated buildings which occure in SQL-Querry
1139
    # drop building ids which have already been used
1140
    mask = df_buildings_without_amenities.loc[
1141
        df_buildings_without_amenities["id"].isin(
1142
            df_buildings_with_amenities["id"]
1143
        )
1144
    ].index
1145
    df_buildings_without_amenities = df_buildings_without_amenities.drop(
1146
        index=mask
1147
    ).reset_index(drop=True)
1148
    log.info(f"{len(mask)} duplicated ids removed!")
1149
1150
    # select median n buildings per cell
1151
    df_buildings_without_amenities = select_cts_buildings(
1152
        df_buildings_without_amenities, max_n=median_n_amenities
1153
    )
1154
    df_buildings_without_amenities["n_amenities_inside"] = 1
1155
    log.info(f"{median_n_amenities} buildings per cell selected!")
1156
1157
    # Create synthetic amenities and buildings in cells with only CTS demand
1158
    df_cells_with_cts_demand_only = cells_with_cts_demand_only(
1159
        df_buildings_without_amenities
1160
    )
1161
    log.info("Cells with only demand identified!")
1162
1163
    # Median n Amenities per cell
1164
    df_cells_with_cts_demand_only["amenities"] = median_n_amenities
1165
    # create row for every amenity
1166
    df_cells_with_cts_demand_only["amenities"] = (
1167
        df_cells_with_cts_demand_only["amenities"].astype(int).apply(range)
1168
    )
1169
    df_cells_with_cts_demand_only = df_cells_with_cts_demand_only.explode(
1170
        "amenities"
1171
    )
1172
    df_cells_with_cts_demand_only.drop(columns="amenities", inplace=True)
1173
1174
    # Only 1 Amenity per Building
1175
    df_cells_with_cts_demand_only["n_amenities_inside"] = 1
1176
    df_cells_with_cts_demand_only = place_buildings_with_amenities(
1177
        df_cells_with_cts_demand_only, amenities=1
1178
    )
1179
    df_synthetic_buildings_without_amenities = create_synthetic_buildings(
1180
        df_cells_with_cts_demand_only, points="geom_point"
1181
    )
1182
    log.info(f"{median_n_amenities} synthetic buildings per cell created")
1183
1184
    # TODO remove (backup) renaming after #871
1185
    write_table_to_postgis(
1186
        df_synthetic_buildings_without_amenities.rename(
1187
            columns={
1188
                "zensus_population_id": "cell_id",
1189
                "egon_building_id": "id",
1190
            }
1191
        ),
1192
        OsmBuildingsSynthetic,
1193
        drop=False,
1194
    )
1195
    log.info("Synthetic buildings exported to DB")
1196
1197
    # Concat all buildings
1198
    columns = [
1199
        "zensus_population_id",
1200
        "id",
1201
        "geom_building",
1202
        "n_amenities_inside",
1203
        "source",
1204
    ]
1205
1206
    df_buildings_with_amenities["source"] = "bwa"
1207
    df_synthetic_buildings_with_amenities["source"] = "sbwa"
1208
    df_buildings_without_amenities["source"] = "bwoa"
1209
    df_synthetic_buildings_without_amenities["source"] = "sbwoa"
1210
1211
    df_cts_buildings = pd.concat(
1212
        [
1213
            df_buildings_with_amenities[columns],
1214
            df_synthetic_buildings_with_amenities[columns],
1215
            df_buildings_without_amenities[columns],
1216
            df_synthetic_buildings_without_amenities[columns],
1217
        ],
1218
        axis=0,
1219
        ignore_index=True,
1220
    )
1221
    df_cts_buildings = remove_double_bus_id(df_cts_buildings)
1222
    log.info("Double bus_id checked")
1223
1224
    # TODO maybe remove after #772
1225
    df_cts_buildings["id"] = df_cts_buildings["id"].astype(int)
1226
1227
    # Write table to db for debugging
1228
    # TODO remove later? Check if cts-builings are querried in other functions
1229
    df_cts_buildings = gpd.GeoDataFrame(
1230
        df_cts_buildings, geometry="geom_building", crs=3035
1231
    )
1232
    df_cts_buildings = df_cts_buildings.reset_index().rename(
1233
        columns={"index": "serial"}
1234
    )
1235
    write_table_to_postgis(
1236
        df_cts_buildings,
1237
        CtsBuildings,
1238
        drop=True,
1239
    )
1240
    log.info("CTS buildings exported to DB!")
1241
1242
1243 View Code Duplication
def cts_electricity():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1244
    """
1245
    Calculate cts electricity demand share of hvmv substation profile
1246
     for buildings.
1247
    """
1248
    log.info("Start logging!")
1249
    with db.session_scope() as session:
1250
        cells_query = session.query(CtsBuildings)
1251
1252
    df_cts_buildings = pd.read_sql(
1253
        cells_query.statement, cells_query.session.bind, index_col=None
1254
    )
1255
    log.info("CTS buildings from DB imported!")
1256
    df_demand_share_2035 = calc_building_demand_profile_share(
1257
        df_cts_buildings, scenario="eGon2035", sector="electricity"
1258
    )
1259
    log.info("Profile share for egon2035 calculated!")
1260
    df_demand_share_100RE = calc_building_demand_profile_share(
1261
        df_cts_buildings, scenario="eGon100RE", sector="electricity"
1262
    )
1263
    log.info("Profile share for egon100RE calculated!")
1264
    df_demand_share = pd.concat(
1265
        [df_demand_share_2035, df_demand_share_100RE],
1266
        axis=0,
1267
        ignore_index=True,
1268
    )
1269
1270
    write_table_to_postgres(
1271
        df_demand_share, EgonCtsElectricityDemandBuildingShare, drop=True
1272
    )
1273
    log.info("Profile share exported to DB!")
1274
1275
1276 View Code Duplication
def cts_heat():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1277
    """
1278
    Calculate cts electricity demand share of hvmv substation profile
1279
     for buildings.
1280
    """
1281
    log.info("Start logging!")
1282
    with db.session_scope() as session:
1283
        cells_query = session.query(CtsBuildings)
1284
1285
    df_cts_buildings = pd.read_sql(
1286
        cells_query.statement, cells_query.session.bind, index_col=None
1287
    )
1288
    log.info("CTS buildings from DB imported!")
1289
1290
    df_demand_share_2035 = calc_building_demand_profile_share(
1291
        df_cts_buildings, scenario="eGon2035", sector="heat"
1292
    )
1293
    log.info("Profile share for egon2035 calculated!")
1294
    df_demand_share_100RE = calc_building_demand_profile_share(
1295
        df_cts_buildings, scenario="eGon100RE", sector="heat"
1296
    )
1297
    log.info("Profile share for egon100RE calculated!")
1298
    df_demand_share = pd.concat(
1299
        [df_demand_share_2035, df_demand_share_100RE],
1300
        axis=0,
1301
        ignore_index=True,
1302
    )
1303
1304
    write_table_to_postgres(
1305
        df_demand_share, EgonCtsHeatDemandBuildingShare, drop=True
1306
    )
1307
    log.info("Profile share exported to DB!")
1308
1309
1310
def get_cts_electricity_peak_load():
1311
    """
1312
    Get electricity peak load of all CTS buildings for both scenarios and
1313
    store in DB.
1314
    """
1315
    log.info("Start logging!")
1316
    # Delete rows with cts demand
1317
    with db.session_scope() as session:
1318
        session.query(BuildingElectricityPeakLoads).filter(
1319
            BuildingElectricityPeakLoads.sector == "cts"
1320
        ).delete()
1321
    log.info("CTS Peak load removed from DB!")
1322
1323
    for scenario in ["eGon2035", "eGon100RE"]:
1324
1325
        with db.session_scope() as session:
1326
            cells_query = session.query(
1327
                EgonCtsElectricityDemandBuildingShare
1328
            ).filter(
1329
                EgonCtsElectricityDemandBuildingShare.scenario == scenario
1330
            )
1331
1332
        df_demand_share = pd.read_sql(
1333
            cells_query.statement, cells_query.session.bind, index_col=None
1334
        )
1335
1336
        df_cts_profiles = calc_load_curves_cts(scenario=scenario)
1337
1338
        df_peak_load = pd.merge(
1339
            left=df_cts_profiles.max(axis=0).astype(float).rename("max"),
1340
            right=df_demand_share,
1341
            left_on="bus_id",
1342
            right_on="bus_id",
1343
        )
1344
1345
        # Convert unit from MWh to W
1346
        df_peak_load["max"] = df_peak_load["max"] * 1e6
1347
        df_peak_load["peak_load_in_w"] = (
1348
            df_peak_load["max"] * df_peak_load["profile_share"]
1349
        )
1350
        log.info(f"Peak load for {scenario} determined!")
1351
1352
        df_peak_load.rename(columns={"id": "building_id"}, inplace=True)
1353
        df_peak_load["sector"] = "cts"
1354
1355
        df_peak_load = df_peak_load[
1356
            ["building_id", "sector", "scenario", "peak_load_in_w"]
1357
        ]
1358
1359
        # Write peak loads into db
1360
        with db.session_scope() as session:
1361
            session.bulk_insert_mappings(
1362
                BuildingElectricityPeakLoads,
1363
                df_peak_load.to_dict(orient="records"),
1364
            )
1365
        log.info(f"Peak load for {scenario} exported to DB!")
1366
1367
1368
def get_cts_heat_peak_load():
1369
    """
1370
    Get heat peak load of all CTS buildings for both scenarios and store in DB.
1371
    """
1372
    log.info("Start logging!")
1373
1374
    BuildingHeatPeakLoads.__table__.create(bind=engine, checkfirst=True)
1375
1376
    # Delete rows with cts demand
1377
    with db.session_scope() as session:
1378
        session.query(BuildingHeatPeakLoads).filter(
1379
            BuildingHeatPeakLoads.sector == "cts"
1380
        ).delete()
1381
    log.info("CTS Peak load removed from DB!")
1382
1383
    for scenario in ["eGon2035", "eGon100RE"]:
1384
1385
        with db.session_scope() as session:
1386
            cells_query = session.query(EgonCtsHeatDemandBuildingShare).filter(
1387
                EgonCtsHeatDemandBuildingShare.scenario == scenario
1388
            )
1389
1390
        df_demand_share = pd.read_sql(
1391
            cells_query.statement, cells_query.session.bind, index_col=None
1392
        )
1393
1394
        with db.session_scope() as session:
1395
            cells_query = session.query(EgonEtragoElectricityCts).filter(
1396
                EgonEtragoElectricityCts.scn_name == scenario
1397
            )
1398
1399
        df_cts_profiles = pd.read_sql(
1400
            cells_query.statement,
1401
            cells_query.session.bind,
1402
        )
1403
        df_cts_profiles = pd.DataFrame.from_dict(
1404
            df_cts_profiles.set_index("bus_id")["p_set"].to_dict(),
1405
            orient="index",
1406
        )
1407
1408
        df_peak_load = pd.merge(
1409
            left=df_cts_profiles.max(axis=0).astype(float).rename("max"),
1410
            right=df_demand_share,
1411
            left_on="bus_id",
1412
            right_on="bus_id",
1413
        )
1414
1415
        # Convert unit from MWh to W
1416
        df_peak_load["max"] = df_peak_load["max"] * 1e6
1417
        df_peak_load["peak_load_in_w"] = (
1418
            df_peak_load["max"] * df_peak_load["profile_share"]
1419
        )
1420
        log.info(f"Peak load for {scenario} determined!")
1421
1422
        df_peak_load.rename(columns={"id": "building_id"}, inplace=True)
1423
        df_peak_load["sector"] = "cts"
1424
1425
        df_peak_load = df_peak_load[
1426
            ["building_id", "sector", "scenario", "peak_load_in_w"]
1427
        ]
1428
1429
        # Write peak loads into db
1430
        with db.session_scope() as session:
1431
            session.bulk_insert_mappings(
1432
                BuildingHeatPeakLoads,
1433
                df_peak_load.to_dict(orient="records"),
1434
            )
1435
        log.info(f"Peak load for {scenario} exported to DB!")
1436
1437
1438
class CtsDemandBuildings(Dataset):
1439
    def __init__(self, dependencies):
1440
        super().__init__(
1441
            name="CtsDemandBuildings",
1442
            version="0.0.0",
1443
            dependencies=dependencies,
1444
            tasks=(
1445
                cts_buildings,
1446
                {cts_electricity, cts_heat},
1447
                get_cts_electricity_peak_load,
1448
            ),
1449
        )
1450