Passed
Pull Request — dev (#1170)
by
unknown
05:05
created

data.datasets.heat_supply.district_heating()   B

Complexity

Conditions 5

Size

Total Lines 76
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 76
rs 8.6213
c 0
b 0
f 0
cc 5
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
"""The central module containing all code dealing with heat supply data
2
3
"""
4
5
import datetime
6
import json
7
import time
8
9
from geoalchemy2.types import Geometry
10
from sqlalchemy import Column, Float, ForeignKey, Integer, String
11
from sqlalchemy.ext.declarative import declarative_base
12
import pandas as pd
13
14
from egon.data import config, db
15
from egon.data.datasets import Dataset
16
from egon.data.datasets.district_heating_areas import EgonDistrictHeatingAreas
17
from egon.data.datasets.heat_supply.district_heating import (
18
    backup_gas_boilers,
19
    backup_resistive_heaters,
20
    cascade_heat_supply,
21
)
22
from egon.data.datasets.heat_supply.geothermal import potential_germany
23
from egon.data.datasets.heat_supply.individual_heating import (
24
    cascade_heat_supply_indiv,
25
)
26
from egon.data.metadata import (
27
    context,
28
    generate_resource_fields_from_sqla_model,
29
    license_ccby,
30
    license_egon_data_odbl,
31
    meta_metadata,
32
    sources,
33
)
34
35
# Will later be imported from another file.
36
Base = declarative_base()
37
38
39
class EgonDistrictHeatingSupply(Base):
40
    __tablename__ = "egon_district_heating"
41
    __table_args__ = {"schema": "supply"}
42
    index = Column(Integer, primary_key=True, autoincrement=True)
43
    district_heating_id = Column(
44
        Integer, ForeignKey(EgonDistrictHeatingAreas.id)
45
    )
46
    carrier = Column(String(25))
47
    category = Column(String(25))
48
    capacity = Column(Float)
49
    geometry = Column(Geometry("POINT", 3035))
50
    scenario = Column(String(50))
51
52
53
class EgonIndividualHeatingSupply(Base):
54
    __tablename__ = "egon_individual_heating"
55
    __table_args__ = {"schema": "supply"}
56
    index = Column(Integer, primary_key=True, autoincrement=True)
57
    mv_grid_id = Column(Integer)
58
    carrier = Column(String(25))
59
    category = Column(String(25))
60
    capacity = Column(Float)
61
    geometry = Column(Geometry("POINT", 3035))
62
    scenario = Column(String(50))
63
64
65
def create_tables():
66
    """Create tables for district heating areas
67
68
    Returns
69
    -------
70
        None
71
    """
72
73
    engine = db.engine()
74
    EgonDistrictHeatingSupply.__table__.drop(bind=engine, checkfirst=True)
75
    EgonDistrictHeatingSupply.__table__.create(bind=engine, checkfirst=True)
76
    EgonIndividualHeatingSupply.__table__.drop(bind=engine, checkfirst=True)
77
    EgonIndividualHeatingSupply.__table__.create(bind=engine, checkfirst=True)
78
79
80
def district_heating():
81
    """Insert supply for district heating areas
82
83
    Returns
84
    -------
85
    None.
86
87
    """
88
    sources = config.datasets()["heat_supply"]["sources"]
89
    targets = config.datasets()["heat_supply"]["targets"]
90
91
    db.execute_sql(
92
        f"""
93
        DELETE FROM {targets['district_heating_supply']['schema']}.
94
        {targets['district_heating_supply']['table']}
95
        """
96
    )
97
98
    for scenario in config.settings()["egon-data"]["--scenarios"]:
99
        supply = cascade_heat_supply(scenario, plotting=False)
100
101
        supply["scenario"] = scenario
102
103
        supply.to_postgis(
104
            targets["district_heating_supply"]["table"],
105
            schema=targets["district_heating_supply"]["schema"],
106
            con=db.engine(),
107
            if_exists="append",
108
        )
109
110
111
        # Do not check data for status quo as is it not listed in the table
112
        if "status" not in scenario:
113
            # Compare target value with sum of distributed heat supply
114
            df_check = db.select_dataframe(
115
                f"""
116
                SELECT a.carrier,
117
                (SUM(a.capacity) - b.capacity) / SUM(a.capacity) as deviation
118
                FROM {targets['district_heating_supply']['schema']}.
119
                {targets['district_heating_supply']['table']} a,
120
                {sources['scenario_capacities']['schema']}.
121
                {sources['scenario_capacities']['table']} b
122
                WHERE a.scenario = '{scenario}'
123
                AND b.scenario_name = '{scenario}'
124
                AND b.carrier = CONCAT('urban_central_', a.carrier)
125
                GROUP BY (a.carrier,  b.capacity);
126
                """
127
            )
128
            # If the deviation is > 1%, throw an error
129
            assert (
130
                df_check.deviation.abs().max() < 1
131
            ), f"""Unexpected deviation between target value and distributed
132
                heat supply: {df_check}
133
            """
134
135
        # Add gas boilers as conventional backup capacities
136
        backup = backup_gas_boilers(scenario)
137
138
        backup.to_postgis(
139
            targets["district_heating_supply"]["table"],
140
            schema=targets["district_heating_supply"]["schema"],
141
            con=db.engine(),
142
            if_exists="append",
143
        )
144
145
146
        # Insert resistive heaters which are not available in status quo
147
        if "status" not in scenario:
148
            backup_rh = backup_resistive_heaters(scenario)
149
150
            if not backup_rh.empty:
151
                backup_rh.to_postgis(
152
                    targets["district_heating_supply"]["table"],
153
                    schema=targets["district_heating_supply"]["schema"],
154
                    con=db.engine(),
155
                    if_exists="append",
156
                )
157
158
159
def individual_heating():
160
    """Insert supply for individual heating
161
162
    Returns
163
    -------
164
    None.
165
166
    """
167
    targets = config.datasets()["heat_supply"]["targets"]
168
169
    for scenario in config.settings()["egon-data"]["--scenarios"]:
170
        db.execute_sql(
171
            f"""
172
            DELETE FROM {targets['individual_heating_supply']['schema']}.
173
            {targets['individual_heating_supply']['table']}
174
            WHERE scenario = '{scenario}'
175
            """
176
        )
177
        if scenario == "eGon2035":
178
            distribution_level = "federal_states"
179
        else:
180
            distribution_level = "national"
181
182
        supply = cascade_heat_supply_indiv(
183
            scenario, distribution_level=distribution_level, plotting=False
184
        )
185
186
        supply["scenario"] = scenario
187
188
        supply.to_postgis(
189
            targets["individual_heating_supply"]["table"],
190
            schema=targets["individual_heating_supply"]["schema"],
191
            con=db.engine(),
192
            if_exists="append",
193
        )
194
195
196
def metadata():
197
    """Write metadata for heat supply tables
198
199
    Returns
200
    -------
201
    None.
202
203
    """
204
205
    fields = generate_resource_fields_from_sqla_model(
206
        EgonDistrictHeatingSupply
207
    )
208
209
    fields_df = pd.DataFrame(data=fields).set_index("name")
210
    fields_df.loc["index", "description"] = "Unique identifyer"
211
    fields_df.loc[
212
        "district_heating_id", "description"
213
    ] = "Index of the corresponding district heating grid"
214
    fields_df.loc["carrier", "description"] = "Name of energy carrier"
215
    fields_df.loc[
216
        "category", "description"
217
    ] = "Size-category of district heating grid"
218
    fields_df.loc["capacity", "description"] = "Installed heating capacity"
219
    fields_df.loc[
220
        "geometry", "description"
221
    ] = "Location of thermal power plant"
222
    fields_df.loc["scenario", "description"] = "Name of corresponing scenario"
223
224
    fields_df.loc["capacity", "unit"] = "MW_th"
225
    fields_df.unit.fillna("none", inplace=True)
226
227
    fields = fields_df.reset_index().to_dict(orient="records")
228
229
    meta_district = {
230
        "name": "supply.egon_district_heating",
231
        "title": "eGon heat supply for district heating grids",
232
        "id": "WILL_BE_SET_AT_PUBLICATION",
233
        "description": "Heat supply technologies for district heating grids",
234
        "language": ["EN"],
235
        "publicationDate": datetime.date.today().isoformat(),
236
        "context": context(),
237
        "spatial": {
238
            "location": None,
239
            "extent": "Germany",
240
            "resolution": None,
241
        },
242
        "sources": [
243
            sources()["era5"],
244
            sources()["vg250"],
245
            sources()["egon-data"],
246
            sources()["egon-data_bundle"],
247
            sources()["openstreetmap"],
248
            sources()["mastr"],
249
            sources()["peta"],
250
        ],
251
        "licenses": [license_egon_data_odbl()],
252
        "contributors": [
253
            {
254
                "title": "Clara Büttner",
255
                "email": "http://github.com/ClaraBuettner",
256
                "date": time.strftime("%Y-%m-%d"),
257
                "object": None,
258
                "comment": "Imported data",
259
            },
260
        ],
261
        "resources": [
262
            {
263
                "profile": "tabular-data-resource",
264
                "name": "supply.egon_district_heating",
265
                "path": None,
266
                "format": "PostgreSQL",
267
                "encoding": "UTF-8",
268
                "schema": {
269
                    "fields": fields,
270
                    "primaryKey": ["index"],
271
                    "foreignKeys": [],
272
                },
273
                "dialect": {"delimiter": None, "decimalSeparator": "."},
274
            }
275
        ],
276
        "metaMetadata": meta_metadata(),
277
    }
278
279
    # Add metadata as a comment to the table
280
    db.submit_comment(
281
        "'" + json.dumps(meta_district) + "'",
282
        EgonDistrictHeatingSupply.__table__.schema,
283
        EgonDistrictHeatingSupply.__table__.name,
284
    )
285
286
    fields = generate_resource_fields_from_sqla_model(
287
        EgonIndividualHeatingSupply
288
    )
289
290
    fields_df = pd.DataFrame(data=fields).set_index("name")
291
    fields_df.loc["index", "description"] = "Unique identifyer"
292
    fields_df.loc[
293
        "mv_grid_id", "description"
294
    ] = "Index of the corresponding mv grid district"
295
    fields_df.loc["carrier", "description"] = "Name of energy carrier"
296
    fields_df.loc["category", "description"] = "Size-category"
297
    fields_df.loc["capacity", "description"] = "Installed heating capacity"
298
    fields_df.loc[
299
        "geometry", "description"
300
    ] = "Location of thermal power plant"
301
    fields_df.loc["scenario", "description"] = "Name of corresponing scenario"
302
303
    fields_df.loc["capacity", "unit"] = "MW_th"
304
    fields_df.unit.fillna("none", inplace=True)
305
306
    fields = fields_df.reset_index().to_dict(orient="records")
307
308
    meta_district = {
309
        "name": "supply.egon_individual_heating",
310
        "title": "eGon heat supply for individual supplied buildings",
311
        "id": "WILL_BE_SET_AT_PUBLICATION",
312
        "description": "Heat supply technologies for individual supplied buildings",
313
        "language": ["EN"],
314
        "publicationDate": datetime.date.today().isoformat(),
315
        "context": context(),
316
        "spatial": {
317
            "location": None,
318
            "extent": "Germany",
319
            "resolution": None,
320
        },
321
        "sources": [
322
            sources()["era5"],
323
            sources()["vg250"],
324
            sources()["egon-data"],
325
            sources()["egon-data_bundle"],
326
            sources()["openstreetmap"],
327
            sources()["mastr"],
328
            sources()["peta"],
329
        ],
330
        "licenses": [license_egon_data_odbl()],
331
        "contributors": [
332
            {
333
                "title": "Clara Büttner",
334
                "email": "http://github.com/ClaraBuettner",
335
                "date": time.strftime("%Y-%m-%d"),
336
                "object": None,
337
                "comment": "Imported data",
338
            },
339
        ],
340
        "resources": [
341
            {
342
                "profile": "tabular-data-resource",
343
                "name": "supply.egon_individual_heating",
344
                "path": None,
345
                "format": "PostgreSQL",
346
                "encoding": "UTF-8",
347
                "schema": {
348
                    "fields": fields,
349
                    "primaryKey": ["index"],
350
                    "foreignKeys": [],
351
                },
352
                "dialect": {"delimiter": None, "decimalSeparator": "."},
353
            }
354
        ],
355
        "metaMetadata": meta_metadata(),
356
    }
357
358
    # Add metadata as a comment to the table
359
    db.submit_comment(
360
        "'" + json.dumps(meta_district) + "'",
361
        EgonIndividualHeatingSupply.__table__.schema,
362
        EgonIndividualHeatingSupply.__table__.name,
363
    )
364
365
366
class HeatSupply(Dataset):
367
    """
368
    Select and store heat supply technologies for inidvidual and district heating
369
370
    This dataset distributes heat supply technologies to each district heating grid
371
    and individual supplies buildings per medium voltage grid district.
372
    National installed capacities are predefined from external sources within
373
    :py:class:`ScenarioCapacities <egon.data.datasets.scenario_capacities.ScenarioCapacities>`.
374
    The further distribution is done using a cascade that follows a specific order of supply technologies
375
    and the heat demand.
376
377
378
    *Dependencies*
379
      * :py:class:`DataBundle <egon.data.datasets.data_bundle.DataBundle>`
380
      * :py:class:`DistrictHeatingAreas <egon.data.datasets.district_heating_areas.DistrictHeatingAreas>`
381
      * :py:class:`ZensusMvGridDistricts <egon.data.datasets.zensus_mv_grid_districts.ZensusMvGridDistricts>`
382
      * :py:class:`Chp <egon.data.datasets.chp.Chp>`
383
384
385
    *Resulting tables*
386
      * :py:class:`demand.egon_district_heating <egon.data.datasets.heat_supply.EgonDistrictHeatingSupply>` is created and filled
387
      * :py:class:`demand.egon_individual_heating <egon.data.datasets.heat_supply.EgonIndividualHeatingSupply>` is created and filled
388
389
    """
390
391
    #:
392
    name: str = "HeatSupply"
393
    #:
394
    version: str = "0.0.9"
395
396
    def __init__(self, dependencies):
397
        super().__init__(
398
            name=self.name,
399
            version=self.version,
400
            dependencies=dependencies,
401
            tasks=(
402
                create_tables,
403
                {
404
                    district_heating,
405
                    individual_heating,
406
                    potential_germany,
407
                },
408
                metadata,
409
            ),
410
        )
411