Passed
Pull Request — dev (#826)
by
unknown
01:52
created

data.datasets.electricity_demand_timeseries.cts_buildings   F

Complexity

Total Complexity 59

Size/Duplication

Total Lines 1483
Duplicated Lines 4.45 %

Importance

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