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

data.datasets.gas_grid   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 1157
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 49
eloc 597
dl 0
loc 1157
rs 8.48
c 0
b 0
f 0

11 Functions

Rating   Name   Duplication   Size   Complexity  
A ch4_nodes_number_G() 0 22 1
B download_SciGRID_gas_data() 0 47 6
A define_gas_nodes_list() 0 46 1
A insert_gas_pipeline_list() 0 47 1
B insert_CH4_nodes_list() 0 99 3
A insert_gas_data() 0 42 4
A insert_gas_data_status() 0 55 1
A insert_gas_buses_abroad() 0 46 1
A remove_isolated_gas_buses() 0 14 1
F define_gas_pipeline_list() 0 419 25
B define_gas_buses_abroad() 0 182 4

1 Method

Rating   Name   Duplication   Size   Complexity  
A GasNodesAndPipes.__init__() 0 6 1

How to fix   Complexity   

Complexity

Complex classes like data.datasets.gas_grid 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
# -*- coding: utf-8 -*-
2
"""
3
The module contains code used to insert the methane grid into the database
4
5
The central module contains all code dealing with the import of data
6
from SciGRID_gas (IGGIELGN dataset) and inserting the CH4 buses and links 
7
into the database for the scenarios eGon2035 and eGon100RE.
8
9
The SciGRID_gas data downloaded with :py:func:`download_SciGRID_gas_data`
10
into the folder ./datasets/gas_data/data is also used by other modules.
11
12
In this module, only the IGGIELGN_Nodes and IGGIELGN_PipeSegments csv files
13
are used in the function :py:func:`insert_gas_data` that inserts the CH4
14
buses and links, which for the case of gas represent pipelines, into the
15
database.
16
17
"""
18
from pathlib import Path
19
from urllib.request import urlretrieve
20
from zipfile import ZipFile
21
import ast
22
import json
23
import os
24
25
from sqlalchemy.orm import sessionmaker
26
from geoalchemy2.types import Geometry
27
from shapely import geometry
28
import geopandas
29
import numpy as np
30
import pandas as pd
31
32
from egon.data import config, db
33
from egon.data.config import settings
34
from egon.data.datasets import Dataset, wrapped_partial
35
from egon.data.datasets.electrical_neighbours import central_buses_pypsaeur
36
from egon.data.datasets.etrago_helpers import copy_and_modify_buses
37
from egon.data.datasets.scenario_parameters import get_sector_parameters
38
39
40
def download_SciGRID_gas_data():
41
    """
42
    Download SciGRID_gas IGGIELGN data from Zenodo
43
44
    The following data for CH4 is downloaded into the folder
45
    ./datasets/gas_data/data:
46
      * Buses (file IGGIELGN_Nodes.csv),
47
      * Pipelines (file IGGIELGN_PipeSegments.csv),
48
      * Productions (file IGGIELGN_Productions.csv),
49
      * Storages (file IGGIELGN_Storages.csv),
50
      * LNG terminals (file IGGIELGN_LNGs.csv).
51
52
    For more information on this data refer, to the
53
    `SciGRID_gas IGGIELGN documentation <https://zenodo.org/record/4767098>`_.
54
55
    Returns
56
    -------
57
    None
58
59
    """
60
    path = Path(".") / "datasets" / "gas_data"
61
    os.makedirs(path, exist_ok=True)
62
63
    basename = "IGGIELGN"
64
    zip_file = Path(".") / "datasets" / "gas_data" / "IGGIELGN.zip"
65
    zenodo_zip_file_url = (
66
        "https://zenodo.org/record/4767098/files/" + basename + ".zip"
67
    )
68
    if not os.path.isfile(zip_file):
69
        urlretrieve(zenodo_zip_file_url, zip_file)
70
71
    components = [
72
        "Nodes",
73
        "PipeSegments",
74
        "Productions",
75
        "Storages",
76
        "LNGs",
77
    ]  #'Compressors'
78
    files = []
79
    for i in components:
80
        files.append("data/" + basename + "_" + i + ".csv")
81
82
    with ZipFile(zip_file, "r") as zipObj:
83
        listOfFileNames = zipObj.namelist()
84
        for fileName in listOfFileNames:
85
            if fileName in files:
86
                zipObj.extract(fileName, path)
87
88
89
def define_gas_nodes_list():
90
    """
91
    Define list of CH4 buses from SciGRID_gas IGGIELGN data
92
93
    The CH4 nodes are modelled as buses. Therefore the SciGRID_gas nodes
94
    are read from the IGGIELGN_Nodes csv file previously downloaded in the
95
    function :py:func:`download_SciGRID_gas_data`, corrected (erroneous country),
96
    and returned in a dataframe.
97
98
    Returns
99
    -------
100
    gas_nodes_list : pandas.DataFrame
101
        Dataframe containing the gas nodes in Europe
102
103
    """
104
    # Select next id value
105
    new_id = db.next_etrago_id("bus")
106
107
    target_file = (
108
        Path(".") / "datasets" / "gas_data" / "data" / "IGGIELGN_Nodes.csv"
109
    )
110
111
    gas_nodes_list = pd.read_csv(
112
        target_file,
113
        delimiter=";",
114
        decimal=".",
115
        usecols=["lat", "long", "id", "country_code", "param"],
116
    )
117
118
    # Correct non valid neighbouring country nodes
119
    gas_nodes_list.loc[
120
        gas_nodes_list["id"] == "INET_N_1182", "country_code"
121
    ] = "AT"
122
    gas_nodes_list.loc[
123
        gas_nodes_list["id"] == "SEQ_10608_p", "country_code"
124
    ] = "NL"
125
    gas_nodes_list.loc[
126
        gas_nodes_list["id"] == "N_88_NS_LMGN", "country_code"
127
    ] = "XX"
128
129
    gas_nodes_list = gas_nodes_list.rename(columns={"lat": "y", "long": "x"})
130
131
    gas_nodes_list["bus_id"] = range(new_id, new_id + len(gas_nodes_list))
132
    gas_nodes_list = gas_nodes_list.set_index("id")
133
134
    return gas_nodes_list
135
136
137
def ch4_nodes_number_G(gas_nodes_list):
138
    """
139
    Return the number of CH4 buses in Germany
140
141
    Parameters
142
    ----------
143
    gas_nodes_list : pandas.DataFrame
144
        Dataframe containing the gas nodes in Europe
145
146
    Returns
147
    -------
148
    N_ch4_nodes_G : int
149
        Number of CH4 buses in Germany
150
151
    """
152
153
    ch4_nodes_list = gas_nodes_list[
154
        gas_nodes_list["country_code"].str.match("DE")
155
    ]
156
    N_ch4_nodes_G = len(ch4_nodes_list)
157
158
    return N_ch4_nodes_G
159
160
161
def insert_CH4_nodes_list(gas_nodes_list, scn_name="eGon2035"):
162
    """
163
    Insert list of German CH4 nodes into the database for a required scenario
164
165
    Insert the list of German CH4 nodes into the database by executing
166
    the following steps:
167
      * Receive the buses as parameter (from SciGRID_gas IGGIELGN data)
168
      * Add the missing information: scn_name and carrier
169
      * Clean the database table grid.egon_etrago_bus of the
170
        CH4 buses of the specific scenario in Germany
171
      * Insert the buses in the table grid.egon_etrago_bus
172
173
    Parameters
174
    ----------
175
    gas_nodes_list : pandas.DataFrame
176
        Dataframe containing the gas nodes in Europe
177
178
    Returns
179
    -------
180
    None
181
182
    """
183
    # Connect to local database
184
    engine = db.engine()
185
186
    gas_nodes_list = gas_nodes_list[
187
        gas_nodes_list["country_code"].str.match("DE")
188
    ]  # To eventually replace with a test if the nodes are in the german boundaries.
189
190
    # Cut data to federal state if in testmode
191
    NUTS1 = []
192
    for index, row in gas_nodes_list.iterrows():
193
        param = ast.literal_eval(row["param"])
194
        NUTS1.append(param["nuts_id_1"])
195
    gas_nodes_list = gas_nodes_list.assign(NUTS1=NUTS1)
196
197
    boundary = settings()["egon-data"]["--dataset-boundary"]
198
    if boundary != "Everything":
199
        map_states = {
200
            "Baden-Württemberg": "DE1",
201
            "Nordrhein-Westfalen": "DEA",
202
            "Hessen": "DE7",
203
            "Brandenburg": "DE4",
204
            "Bremen": "DE5",
205
            "Rheinland-Pfalz": "DEB",
206
            "Sachsen-Anhalt": "DEE",
207
            "Schleswig-Holstein": "DEF",
208
            "Mecklenburg-Vorpommern": "DE8",
209
            "Thüringen": "DEG",
210
            "Niedersachsen": "DE9",
211
            "Sachsen": "DED",
212
            "Hamburg": "DE6",
213
            "Saarland": "DEC",
214
            "Berlin": "DE3",
215
            "Bayern": "DE2",
216
        }
217
218
        gas_nodes_list = gas_nodes_list[
219
            gas_nodes_list["NUTS1"].isin([map_states[boundary], np.nan])
220
        ]
221
222
        # A completer avec nodes related to pipelines which have an end in the selected area et evt deplacer ds define_gas_nodes_list
223
224
    # Add missing columns
225
    c = {"scn_name": "eGon2035", "carrier": "CH4"}
226
    gas_nodes_list = gas_nodes_list.assign(**c)
227
228
    gas_nodes_list = geopandas.GeoDataFrame(
229
        gas_nodes_list,
230
        geometry=geopandas.points_from_xy(
231
            gas_nodes_list["x"], gas_nodes_list["y"]
232
        ),
233
    )
234
    gas_nodes_list = gas_nodes_list.rename(
235
        columns={"geometry": "geom"}
236
    ).set_geometry("geom", crs=4326)
237
238
    gas_nodes_list = gas_nodes_list.reset_index(drop=True)
239
    gas_nodes_list = gas_nodes_list.drop(
240
        columns=["NUTS1", "param", "country_code"]
241
    )
242
243
    # Insert data to db
244
    db.execute_sql(
245
        f"""
246
    DELETE FROM grid.egon_etrago_bus WHERE "carrier" = 'CH4' AND
247
    scn_name = '{c['scn_name']}' AND country = 'DE';
248
    """
249
    )
250
251
    # Insert CH4 data to db
252
    print(gas_nodes_list)
253
    gas_nodes_list.to_postgis(
254
        "egon_etrago_bus",
255
        engine,
256
        schema="grid",
257
        index=False,
258
        if_exists="append",
259
        dtype={"geom": Geometry()},
260
    )
261
262
263
def define_gas_buses_abroad(scn_name="eGon2035"):
264
    """
265
    Define central CH4 buses in foreign countries for eGon2035
266
267
    For the scenario eGon2035, define central CH4 buses in foreign
268
    countries. The considered foreign countries are the direct 
269
    neighbouring countries, with the addition of Russia that is
270
    considered as a source of fossil CH4.
271
    Therefore, the following steps are executed:
272
      * Definition of the foreign buses with the function
273
        :py:func:`central_buses_pypsaeur <egon.data.datasets.electrical_neighbours.central_buses_pypsaeur>` from
274
        the module :py:mod:`electrical_neighbours <egon.data.datasets.electrical_neighbours>`
275
      * Removal of the superfluous buses in order to have only one bus
276
        in each neighbouring country
277
      * Removal of the irrelevant columns
278
      * Addition of the missing information: scn_name and carrier
279
      * Attribution of an id to each bus
280
281
    Parameters
282
    ----------
283
    scn_name : str
284
        Name of the scenario
285
286
    Returns
287
    -------
288
    gdf_abroad_buses : pandas.DataFrame
289
        Dataframe containing the gas buses in the neighbouring countries
290
        and one in the center of Germany in test mode
291
292
    """
293
    # Select sources and targets from dataset configuration
294
    sources = config.datasets()["electrical_neighbours"]["sources"]
295
    gas_carrier = "CH4"
296
    # Connect to local database
297
    engine = db.engine()
298
299
    # for the eGon100RE scenario the CH4 buses are created by electrical_neighbours_egon100()
300
    # therefore instead of created the buses, for this scenario the buses are just read.
301
    if scn_name == "eGon100RE":
302
        gdf_abroad_buses = geopandas.read_postgis(
303
            f"""
304
            SELECT * FROM grid.egon_etrago_bus WHERE "carrier" = '{gas_carrier}' AND
305
            scn_name = '{scn_name}' AND country != 'DE';
306
            """,
307
            con=engine,
308
            crs=4326,
309
        )
310
        gdf_abroad_buses.drop_duplicates(
311
            subset="country", keep="first", inplace=True
312
        )
313
314
        if settings()["egon-data"]["--dataset-boundary"] != "Everything":
315
            gdf_abroad_buses_insert = pd.DataFrame(
316
                        index=[gdf_abroad_buses.index.max() + 1],
317
                        data={
318
                            "scn_name": scn_name,
319
                            "bus_id": (db.next_etrago_id("bus") + len(gdf_abroad_buses) + 1),
320
                            "x": 10.4234469,
321
                            "y": 51.0834196,
322
                            "country": "DE",
323
                            "carrier": gas_carrier,
324
                        },
325
                    )
326
327
            gdf_abroad_buses_insert = geopandas.GeoDataFrame(
328
                gdf_abroad_buses_insert,
329
                geometry=geopandas.points_from_xy(
330
                    gdf_abroad_buses_insert["x"], gdf_abroad_buses_insert["y"]
331
                ),
332
            )
333
            gdf_abroad_buses_insert = gdf_abroad_buses_insert.rename(
334
                columns={"geometry": "geom"}
335
            ).set_geometry("geom", crs=4326)
336
337
            # Insert to db
338
            print(gdf_abroad_buses_insert)
339
            gdf_abroad_buses_insert.to_postgis(
340
                "egon_etrago_bus",
341
                engine,
342
                schema="grid",
343
                index=False,
344
                if_exists="append",
345
                dtype={"geom": Geometry()},
346
            )
347
348
            gdf_abroad_buses = pd.concat(
349
                [
350
                    gdf_abroad_buses,
351
                    gdf_abroad_buses_insert
352
                ],
353
                ignore_index=True,
354
            )
355
356
        return gdf_abroad_buses
357
358
    else:
359
        db.execute_sql(
360
            f"""
361
        DELETE FROM grid.egon_etrago_bus WHERE "carrier" = '{gas_carrier}' AND
362
        scn_name = '{scn_name}' AND country != 'DE';
363
        """
364
        )
365
366
        # Select the foreign buses
367
        gdf_abroad_buses = central_buses_pypsaeur(sources, scenario=scn_name)
368
        gdf_abroad_buses = gdf_abroad_buses.drop_duplicates(subset=["country"])
369
370
        # Select next id value
371
        new_id = db.next_etrago_id("bus")
372
373
        gdf_abroad_buses = gdf_abroad_buses.drop(
374
            columns=[
375
                "v_nom",
376
                "v_mag_pu_set",
377
                "v_mag_pu_min",
378
                "v_mag_pu_max",
379
                "geom",
380
                "control",
381
                "generator",
382
                "location",
383
                "unit",
384
                "sub_network",
385
            ],
386
            errors="ignore",
387
        )
388
        gdf_abroad_buses["scn_name"] = scn_name
389
        gdf_abroad_buses["carrier"] = gas_carrier
390
        gdf_abroad_buses["bus_id"] = range(
391
            new_id, new_id + len(gdf_abroad_buses)
392
        )
393
394
        # Add central bus in Russia
395
        gdf_abroad_buses = pd.concat(
396
            [
397
                gdf_abroad_buses,
398
                pd.DataFrame(
399
                    index=["RU"],
400
                    data={
401
                        "scn_name": scn_name,
402
                        "bus_id": (new_id + len(gdf_abroad_buses) + 1),
403
                        "x": 41,
404
                        "y": 55,
405
                        "country": "RU",
406
                        "carrier": gas_carrier,
407
                    },
408
                ),
409
            ],
410
            ignore_index=True,
411
        )
412
        # if in test mode, add bus in center of Germany
413
        boundary = settings()["egon-data"]["--dataset-boundary"]
414
415
        if boundary != "Everything":
416
            gdf_abroad_buses = pd.concat(
417
                [
418
                    gdf_abroad_buses,
419
                    pd.DataFrame(
420
                        index=[gdf_abroad_buses.index.max() + 1],
421
                        data={
422
                            "scn_name": scn_name,
423
                            "bus_id": (new_id + len(gdf_abroad_buses) + 1),
424
                            "x": 10.4234469,
425
                            "y": 51.0834196,
426
                            "country": "DE",
427
                            "carrier": gas_carrier,
428
                        },
429
                    ),
430
                ],
431
                ignore_index=True,
432
            )
433
434
        gdf_abroad_buses = geopandas.GeoDataFrame(
435
            gdf_abroad_buses,
436
            geometry=geopandas.points_from_xy(
437
                gdf_abroad_buses["x"], gdf_abroad_buses["y"]
438
            ),
439
        )
440
        gdf_abroad_buses = gdf_abroad_buses.rename(
441
            columns={"geometry": "geom"}
442
        ).set_geometry("geom", crs=4326)
443
444
    return gdf_abroad_buses
445
446
447
def insert_gas_buses_abroad(scn_name="eGon2035"):
448
    """
449
    Insert CH4 buses in neighbouring countries into database for eGon2035
450
451
      * Definition of the CH4 buses abroad with the function 
452
        :py:func:`define_gas_buses_abroad`
453
      * Cleaning of the database table grid.egon_etrago_bus of the
454
        foreign CH4 buses of the specific scenario (eGon2035)
455
      * Insertion of the neighbouring buses into the table grid.egon_etrago_bus.   
456
457
    Parameters
458
    ----------
459
    scn_name : str
460
        Name of the scenario
461
462
    Returns
463
    -------
464
    gdf_abroad_buses : dataframe
465
        Dataframe containing the CH4 buses in the neighbouring countries
466
        and one in the center of Germany in test mode
467
468
    """
469
    gas_carrier = "CH4"
470
471
    # Connect to local database
472
    engine = db.engine()
473
    db.execute_sql(
474
        f"""
475
    DELETE FROM grid.egon_etrago_bus WHERE "carrier" = '{gas_carrier}' AND
476
    scn_name = '{scn_name}' AND country != 'DE';
477
    """
478
    )
479
480
    gdf_abroad_buses = define_gas_buses_abroad(scn_name)
481
482
    # Insert to db
483
    print(gdf_abroad_buses)
484
    gdf_abroad_buses.to_postgis(
485
        "egon_etrago_bus",
486
        engine,
487
        schema="grid",
488
        index=False,
489
        if_exists="append",
490
        dtype={"geom": Geometry()},
491
    )
492
    return gdf_abroad_buses
493
494
495
def define_gas_pipeline_list(
496
    gas_nodes_list, abroad_gas_nodes_list, scn_name="eGon2035"
497
):
498
    """
499
    Define gas pipelines in Germany from SciGRID_gas IGGIELGN data
500
501
    The gas pipelines, modelled as PyPSA links are read from the IGGIELGN_PipeSegments
502
    csv file previously downloded in the function :py:func:`download_SciGRID_gas_data`.
503
504
    The capacities of the pipelines are determined by the correspondance
505
    table given by the parameters for the classification of gas pipelines
506
    in `Electricity, heat, and gas sector data for modeling the German system
507
    <https://www.econstor.eu/bitstream/10419/173388/1/1011162628.pdf>`_
508
    related to the pipeline diameter given in the SciGRID_gas dataset.
509
510
    The manual corrections allow to:
511
      * Delete gas pipelines disconnected of the rest of the gas grid
512
      * Connect one pipeline (also connected to Norway) disconnected of
513
        the rest of the gas grid
514
      * Correct countries of some erroneous pipelines
515
516
    Parameters
517
    ----------
518
    gas_nodes_list : dataframe
519
        Dataframe containing the gas nodes in Europe
520
    abroad_gas_nodes_list: dataframe
521
        Dataframe containing the gas buses in the neighbouring countries
522
        and one in the center of Germany in test mode
523
    scn_name : str
524
        Name of the scenario
525
526
    Returns
527
    -------
528
    gas_pipelines_list : pandas.DataFrame
529
        Dataframe containing the gas pipelines in Germany
530
531
    """
532
    scn_params = get_sector_parameters("gas", scn_name)
533
534
    abroad_gas_nodes_list = abroad_gas_nodes_list.set_index("country")
535
536
    gas_carrier = "CH4"
537
538
    # Select next id value
539
    new_id = db.next_etrago_id("link")
540
541
    classifiaction_file = (
542
        Path(".")
543
        / "data_bundle_egon_data"
544
        / "pipeline_classification_gas"
545
        / "pipeline_classification.csv"
546
    )
547
548
    classification = pd.read_csv(
549
        classifiaction_file,
550
        delimiter=",",
551
        usecols=["classification", "max_transport_capacity_Gwh/d"],
552
    )
553
554
    target_file = (
555
        Path(".")
556
        / "datasets"
557
        / "gas_data"
558
        / "data"
559
        / "IGGIELGN_PipeSegments.csv"
560
    )
561
562
    gas_pipelines_list = pd.read_csv(
563
        target_file,
564
        delimiter=";",
565
        decimal=".",
566
        usecols=["id", "node_id", "lat", "long", "country_code", "param"],
567
    )
568
569
    # Correct some country codes (also changed in define_gas_nodes_list())
570
    gas_pipelines_list["bus0"] = gas_pipelines_list["node_id"].apply(
571
        lambda x: x.split(",")[0]
572
    )
573
    gas_pipelines_list["bus1"] = gas_pipelines_list["node_id"].apply(
574
        lambda x: x.split(",")[1]
575
    )
576
    gas_pipelines_list["country0"] = gas_pipelines_list["country_code"].apply(
577
        lambda x: x.split(",")[0]
578
    )
579
    gas_pipelines_list["country1"] = gas_pipelines_list["country_code"].apply(
580
        lambda x: x.split(",")[1]
581
    )
582
583
    gas_pipelines_list.loc[
584
        gas_pipelines_list["bus0"].str.contains("INET_N_1182"), "country0"
585
    ] = "['AT'"
586
    gas_pipelines_list.loc[
587
        gas_pipelines_list["bus1"].str.contains("INET_N_1182"), "country1"
588
    ] = "'AT']"
589
    gas_pipelines_list.loc[
590
        gas_pipelines_list["bus0"].str.contains("SEQ_10608_p"), "country0"
591
    ] = "['NL'"
592
    gas_pipelines_list.loc[
593
        gas_pipelines_list["bus1"].str.contains("SEQ_10608_p"), "country1"
594
    ] = "'NL']"
595
    gas_pipelines_list.loc[
596
        gas_pipelines_list["bus0"].str.contains("N_88_NS_LMGN"), "country0"
597
    ] = "['XX'"
598
    gas_pipelines_list.loc[
599
        gas_pipelines_list["bus1"].str.contains("N_88_NS_LMGN"), "country1"
600
    ] = "'XX']"
601
602
    gas_pipelines_list["country_code"] = gas_pipelines_list.apply(
603
        lambda x: x["country0"] + "," + x["country1"], axis=1
604
    )
605
    gas_pipelines_list.drop(
606
        columns=["bus0", "bus1", "country0", "country1"], inplace=True
607
    )
608
609
    # Select the links having at least one bus in Germany
610
    gas_pipelines_list = gas_pipelines_list[
611
        gas_pipelines_list["country_code"].str.contains("DE")
612
    ]
613
    # Remove links disconnected of the rest of the grid
614
    # Remove manually for disconnected link EntsoG_Map__ST_195 and EntsoG_Map__ST_108
615
    gas_pipelines_list = gas_pipelines_list[
616
        gas_pipelines_list["node_id"] != "['SEQ_11790_p', 'Stor_EU_107']"
617
    ]
618
    gas_pipelines_list = gas_pipelines_list[
619
        ~gas_pipelines_list["id"].str.match("EntsoG_Map__ST_108")
620
    ]
621
622
    # Manually add pipeline to artificially connect isolated pipeline
623
    gas_pipelines_list.at["new_pipe", "param"] = gas_pipelines_list[
624
        gas_pipelines_list["id"] == "NO_PS_8_Seg_0_Seg_23"
625
    ]["param"].values[0]
626
    gas_pipelines_list.at["new_pipe", "node_id"] = (
627
        "['SEQ_12442_p', 'LKD_N_200']"
628
    )
629
    gas_pipelines_list.at["new_pipe", "lat"] = "[53.358536, 53.412719]"
630
    gas_pipelines_list.at["new_pipe", "long"] = "[7.041677, 7.093251]"
631
    gas_pipelines_list.at["new_pipe", "country_code"] = "['DE', 'DE']"
632
633
    gas_pipelines_list["link_id"] = range(
634
        new_id, new_id + len(gas_pipelines_list)
635
    )
636
    gas_pipelines_list["link_id"] = gas_pipelines_list["link_id"].astype(int)
637
638
    # Cut data to federal state if in testmode
639
    NUTS1 = []
640
    for index, row in gas_pipelines_list.iterrows():
641
        param = ast.literal_eval(row["param"])
642
        NUTS1.append(param["nuts_id_1"])
643
    gas_pipelines_list["NUTS1"] = NUTS1
644
645
    map_states = {
646
        "Baden-Württemberg": "DE1",
647
        "Nordrhein-Westfalen": "DEA",
648
        "Hessen": "DE7",
649
        "Brandenburg": "DE4",
650
        "Bremen": "DE5",
651
        "Rheinland-Pfalz": "DEB",
652
        "Sachsen-Anhalt": "DEE",
653
        "Schleswig-Holstein": "DEF",
654
        "Mecklenburg-Vorpommern": "DE8",
655
        "Thüringen": "DEG",
656
        "Niedersachsen": "DE9",
657
        "Sachsen": "DED",
658
        "Hamburg": "DE6",
659
        "Saarland": "DEC",
660
        "Berlin": "DE3",
661
        "Bayern": "DE2",
662
        "Everything": "Nan",
663
    }
664
    gas_pipelines_list["NUTS1_0"] = [x[0] for x in gas_pipelines_list["NUTS1"]]
665
    gas_pipelines_list["NUTS1_1"] = [x[1] for x in gas_pipelines_list["NUTS1"]]
666
667
    boundary = settings()["egon-data"]["--dataset-boundary"]
668
669
    if boundary != "Everything":
670
        gas_pipelines_list = gas_pipelines_list[
671
            gas_pipelines_list["NUTS1_0"].str.contains(map_states[boundary])
672
            | gas_pipelines_list["NUTS1_1"].str.contains(map_states[boundary])
673
        ]
674
675
    # Add missing columns
676
    gas_pipelines_list["scn_name"] = scn_name
677
    gas_pipelines_list["carrier"] = gas_carrier
678
    gas_pipelines_list["p_nom_extendable"] = False
679
    gas_pipelines_list["p_min_pu"] = -1.0
680
681
    diameter = []
682
    geom = []
683
    topo = []
684
    length_km = []
685
686
    for index, row in gas_pipelines_list.iterrows():
687
        param = ast.literal_eval(row["param"])
688
        diameter.append(param["diameter_mm"])
689
        length_km.append(param["length_km"])
690
691
        long_e = json.loads(row["long"])
692
        lat_e = json.loads(row["lat"])
693
        crd_e = list(zip(long_e, lat_e))
694
        topo.append(geometry.LineString(crd_e))
695
696
        long_path = param["path_long"]
697
        lat_path = param["path_lat"]
698
        crd = list(zip(long_path, lat_path))
699
        crd.insert(0, crd_e[0])
700
        crd.append(crd_e[1])
701
        lines = []
702
        for i in range(len(crd) - 1):
703
            lines.append(geometry.LineString([crd[i], crd[i + 1]]))
704
        geom.append(geometry.MultiLineString(lines))
705
706
    gas_pipelines_list["diameter"] = diameter
707
    gas_pipelines_list["geom"] = geom
708
    gas_pipelines_list["topo"] = topo
709
    gas_pipelines_list["length_km"] = length_km
710
    gas_pipelines_list = gas_pipelines_list.set_geometry("geom", crs=4326)
711
712
    country_0 = []
713
    country_1 = []
714
    for index, row in gas_pipelines_list.iterrows():
715
        c = ast.literal_eval(row["country_code"])
716
        country_0.append(c[0])
717
        country_1.append(c[1])
718
719
    gas_pipelines_list["country_0"] = country_0
720
    gas_pipelines_list["country_1"] = country_1
721
722
    # Correct non valid neighbouring country nodes
723
    gas_pipelines_list.loc[
724
        gas_pipelines_list["country_0"] == "XX", "country_0"
725
    ] = "NO"
726
    gas_pipelines_list.loc[
727
        gas_pipelines_list["country_1"] == "FI", "country_1"
728
    ] = "RU"
729
    gas_pipelines_list.loc[
730
        gas_pipelines_list["id"] == "ST_2612_Seg_0_Seg_0", "country_0"
731
    ] = "AT"  # bus "INET_N_1182" DE -> AT
732
    gas_pipelines_list.loc[
733
        gas_pipelines_list["id"] == "INET_PL_385_EE_3_Seg_0_Seg_1", "country_1"
734
    ] = "AT"  # "INET_N_1182" DE -> AT
735
    gas_pipelines_list.loc[
736
        gas_pipelines_list["id"] == "LKD_PS_0_Seg_0_Seg_3", "country_0"
737
    ] = "NL"  # bus "SEQ_10608_p" DE -> NL
738
739
    if scn_name == "eGon100RE":
740
        gas_pipelines_list = gas_pipelines_list[
741
            gas_pipelines_list["country_1"] != "RU"
742
        ]
743
744
    # Remove uncorrect pipelines
745
    gas_pipelines_list = gas_pipelines_list[
746
        (gas_pipelines_list["id"] != "PLNG_2637_Seg_0_Seg_0_Seg_0")
747
        & (gas_pipelines_list["id"] != "NSG_6650_Seg_2_Seg_0")
748
        & (gas_pipelines_list["id"] != "NSG_6734_Seg_2_Seg_0")
749
    ]
750
751
    # Remove link test if length = 0
752
    gas_pipelines_list = gas_pipelines_list[
753
        gas_pipelines_list["length_km"] != 0
754
    ]
755
756
    # Adjust columns
757
    bus0 = []
758
    bus1 = []
759
    geom_adjusted = []
760
    topo_adjusted = []
761
    length_adjusted = []
762
    pipe_class = []
763
764
    for index, row in gas_pipelines_list.iterrows():
765
        buses = row["node_id"].strip("][").split(", ")
766
767
        if (
768
            (boundary != "Everything")
769
            & (row["NUTS1_0"] != map_states[boundary])
770
            & (row["country_0"] == "DE")
771
        ):
772
            bus0.append(abroad_gas_nodes_list.loc["DE", "bus_id"])
773
            bus1.append(gas_nodes_list.loc[buses[1][1:-1], "bus_id"])
774
            long_e = [
775
                abroad_gas_nodes_list.loc["DE", "x"],
776
                json.loads(row["long"])[1],
777
            ]
778
            lat_e = [
779
                abroad_gas_nodes_list.loc["DE", "y"],
780
                json.loads(row["lat"])[1],
781
            ]
782
            geom_pipe = geometry.MultiLineString(
783
                [geometry.LineString(list(zip(long_e, lat_e)))]
784
            )
785
            topo_adjusted.append(geometry.LineString(list(zip(long_e, lat_e))))
786
787
        elif row["country_0"] != "DE":
788
            country = str(row["country_0"])
789
            bus0.append(abroad_gas_nodes_list.loc[country, "bus_id"])
790
            bus1.append(gas_nodes_list.loc[buses[1][1:-1], "bus_id"])
791
            long_e = [
792
                abroad_gas_nodes_list.loc[country, "x"],
793
                json.loads(row["long"])[1],
794
            ]
795
            lat_e = [
796
                abroad_gas_nodes_list.loc[country, "y"],
797
                json.loads(row["lat"])[1],
798
            ]
799
            geom_pipe = geometry.MultiLineString(
800
                [geometry.LineString(list(zip(long_e, lat_e)))]
801
            )
802
            topo_adjusted.append(geometry.LineString(list(zip(long_e, lat_e))))
803
804
        elif (
805
            (boundary != "Everything")
806
            & (row["NUTS1_1"] != map_states[boundary])
807
            & (row["country_1"] == "DE")
808
        ):
809
            bus0.append(gas_nodes_list.loc[buses[0][1:-1], "bus_id"])
810
            bus1.append(abroad_gas_nodes_list.loc["DE", "bus_id"])
811
            long_e = [
812
                json.loads(row["long"])[0],
813
                abroad_gas_nodes_list.loc["DE", "x"],
814
            ]
815
            lat_e = [
816
                json.loads(row["lat"])[0],
817
                abroad_gas_nodes_list.loc["DE", "y"],
818
            ]
819
            geom_pipe = geometry.MultiLineString(
820
                [geometry.LineString(list(zip(long_e, lat_e)))]
821
            )
822
            topo_adjusted.append(geometry.LineString(list(zip(long_e, lat_e))))
823
824
        elif row["country_1"] != "DE":
825
            country = str(row["country_1"])
826
            bus0.append(gas_nodes_list.loc[buses[0][1:-1], "bus_id"])
827
            bus1.append(abroad_gas_nodes_list.loc[country, "bus_id"])
828
            long_e = [
829
                json.loads(row["long"])[0],
830
                abroad_gas_nodes_list.loc[country, "x"],
831
            ]
832
            lat_e = [
833
                json.loads(row["lat"])[0],
834
                abroad_gas_nodes_list.loc[country, "y"],
835
            ]
836
            geom_pipe = geometry.MultiLineString(
837
                [geometry.LineString(list(zip(long_e, lat_e)))]
838
            )
839
            topo_adjusted.append(geometry.LineString(list(zip(long_e, lat_e))))
840
841
        else:
842
            bus0.append(gas_nodes_list.loc[buses[0][1:-1], "bus_id"])
843
            bus1.append(gas_nodes_list.loc[buses[1][1:-1], "bus_id"])
844
            geom_pipe = row["geom"]
845
            topo_adjusted.append(row["topo"])
846
847
        geom_adjusted.append(geom_pipe)
848
        length_adjusted.append(geom_pipe.length)
849
850
        if row["diameter"] >= 1000:
851
            pipe_class = "A"
852
        elif 700 <= row["diameter"] <= 1000:
853
            pipe_class = "B"
854
        elif 500 <= row["diameter"] <= 700:
855
            pipe_class = "C"
856
        elif 350 <= row["diameter"] <= 500:
857
            pipe_class = "D"
858
        elif 200 <= row["diameter"] <= 350:
859
            pipe_class = "E"
860
        elif 100 <= row["diameter"] <= 200:
861
            pipe_class = "F"
862
        elif row["diameter"] <= 100:
863
            pipe_class = "G"
864
865
    gas_pipelines_list["bus0"] = bus0
866
    gas_pipelines_list["bus1"] = bus1
867
    gas_pipelines_list["geom"] = geom_adjusted
868
    gas_pipelines_list["topo"] = topo_adjusted
869
    gas_pipelines_list["length"] = length_adjusted
870
    gas_pipelines_list["pipe_class"] = pipe_class
871
872
    # Remove pipes having the same node for start and end
873
    gas_pipelines_list = gas_pipelines_list[
874
        gas_pipelines_list["bus0"] != gas_pipelines_list["bus1"]
875
    ]
876
877
    gas_pipelines_list = gas_pipelines_list.merge(
878
        classification,
879
        how="left",
880
        left_on="pipe_class",
881
        right_on="classification",
882
    )
883
    gas_pipelines_list["p_nom"] = gas_pipelines_list[
884
        "max_transport_capacity_Gwh/d"
885
    ] * (1000 / 24)
886
887
    if scn_name == "eGon100RE":
888
        # remaining CH4 share is 1 - retroffited pipeline share
889
        gas_pipelines_list["p_nom"] *= (
890
            1 - scn_params["retrofitted_CH4pipeline-to-H2pipeline_share"]
891
        )
892
893
    # Remove useless columns
894
    gas_pipelines_list = gas_pipelines_list.drop(
895
        columns=[
896
            "id",
897
            "node_id",
898
            "param",
899
            "NUTS1",
900
            "NUTS1_0",
901
            "NUTS1_1",
902
            "country_code",
903
            "diameter",
904
            "pipe_class",
905
            "classification",
906
            "max_transport_capacity_Gwh/d",
907
            "lat",
908
            "long",
909
            "length_km",
910
        ]
911
    )
912
913
    return gas_pipelines_list
914
915
916
def insert_gas_pipeline_list(gas_pipelines_list, scn_name="eGon2035"):
917
    """
918
    Insert list of gas pipelines into the database
919
920
    Receive as argument a list of gas pipelines and insert them into the
921
    database after cleaning it.
922
923
    Parameters
924
    ----------
925
    gas_pipelines_list : pandas.DataFrame
926
        Dataframe containing the gas pipelines in Germany
927
    scn_name : str
928
        Name of the scenario
929
        
930
    Returns
931
    -------
932
    None
933
934
    """
935
    gas_carrier = "CH4"
936
937
    engine = db.engine()
938
    gas_pipelines_list = gas_pipelines_list.drop(
939
        columns=["country_0", "country_1"]
940
    )
941
942
    # Clean db
943
    db.execute_sql(
944
        f"""DELETE FROM grid.egon_etrago_link
945
        WHERE "carrier" = '{gas_carrier}'
946
        AND scn_name = '{scn_name}';
947
        """
948
    )
949
950
    print(gas_pipelines_list)
951
    # Insert data to db
952
    gas_pipelines_list.to_postgis(
953
        "egon_etrago_gas_link",
954
        engine,
955
        schema="grid",
956
        index=False,
957
        if_exists="replace",
958
        dtype={"geom": Geometry(), "topo": Geometry()},
959
    )
960
961
    db.execute_sql(
962
        """
963
    select UpdateGeometrySRID('grid', 'egon_etrago_gas_link', 'topo', 4326) ;
964
965
    INSERT INTO grid.egon_etrago_link (scn_name,
966
                                              link_id, carrier,
967
                                              bus0, bus1, p_min_pu,
968
                                              p_nom, p_nom_extendable, length,
969
                                              geom, topo)
970
    SELECT scn_name,
971
                link_id, carrier,
972
                bus0, bus1, p_min_pu,
973
                p_nom, p_nom_extendable, length,
974
                geom, topo
975
976
    FROM grid.egon_etrago_gas_link;
977
978
    DROP TABLE grid.egon_etrago_gas_link;
979
        """
980
    )
981
982
983
def remove_isolated_gas_buses(scn_name="eGon2035"):
984
    """
985
    Delete CH4 buses which are disconnected of the CH4 grid for the required
986
    scenario
987
988
    Returns
989
    -------
990
    None
991
992
    """
993
    targets = config.datasets()["gas_grid"]["targets"]
994
995
    db.execute_sql(
996
        f"""
997
        DELETE FROM {targets['buses']['schema']}.{targets['buses']['table']}
998
        WHERE "carrier" = 'CH4'
999
        AND scn_name = '{scn_name}'
1000
        AND country = 'DE'
1001
        AND "bus_id" NOT IN
1002
            (SELECT bus0 FROM {targets['links']['schema']}.{targets['links']['table']}
1003
            WHERE scn_name = '{scn_name}'
1004
            AND carrier = 'CH4')
1005
        AND "bus_id" NOT IN
1006
            (SELECT bus1 FROM {targets['links']['schema']}.{targets['links']['table']}
1007
            WHERE scn_name = '{scn_name}'
1008
            AND carrier = 'CH4');
1009
    """
1010
    )
1011
1012
1013
def insert_gas_data():
1014
    """
1015
    Overall function for importing methane data for all the scenarios in the
1016
    configuration file.
1017
1018
    This function imports the methane data (buses and pipelines) for
1019
    each required scenario, by executing the following steps:
1020
      * Download the SciGRID_gas datasets with the function :py:func:`download_SciGRID_gas_data`
1021
      * Define CH4 buses with the function :py:func:`define_gas_nodes_list`
1022
      * Insert the CH4 buses in Germany into the database with the
1023
        function :py:func:`insert_CH4_nodes_list`
1024
      * Insert the CH4 buses abroad into the database with the function
1025
        :py:func:`insert_gas_buses_abroad`
1026
      * Insert the CH4 links representing the CH4 pipeline into the
1027
        database with the function :py:func:`insert_gas_pipeline_list`
1028
      * Remove the isolated CH4 buses directly from the database using
1029
        the function :py:func:`remove_isolated_gas_buses`
1030
1031
    Returns
1032
    -------
1033
    None
1034
1035
    """
1036
    s = config.settings()["egon-data"]["--scenarios"]
1037
    scenarios = []
1038
    if "eGon2035" in s:
1039
        scenarios.append("eGon2035")
1040
    if "eGon100RE" in s:
1041
        scenarios.append("eGon100RE")
1042
1043
    download_SciGRID_gas_data()
1044
1045
    for scn_name in scenarios:
1046
        gas_nodes_list = define_gas_nodes_list()
1047
1048
        insert_CH4_nodes_list(gas_nodes_list, scn_name=scn_name)
1049
        abroad_gas_nodes_list = insert_gas_buses_abroad(scn_name=scn_name)
1050
1051
        insert_gas_pipeline_list(
1052
            gas_nodes_list, abroad_gas_nodes_list, scn_name=scn_name
1053
        )
1054
        remove_isolated_gas_buses(scn_name=scn_name)
1055
1056
1057
def insert_gas_data_status(scn_name):
1058
    """
1059
    Function to deal with the gas network for the status2019 scenario.
1060
    For this scenario just one CH4 bus is consider in the center of Germany.
1061
    Since OCGTs in the foreign countries are modelled as generators and not
1062
    as links between the gas and electricity sectors, CH4 foreign buses are
1063
    considered not necessary.
1064
1065
    This function does not require any input.
1066
1067
    Returns
1068
    -------
1069
    None.
1070
1071
    """
1072
1073
    # delete old entries
1074
    db.execute_sql(
1075
        f"""
1076
        DELETE FROM grid.egon_etrago_link
1077
        WHERE carrier = 'CH4' AND scn_name = '{scn_name}'
1078
        """
1079
    )
1080
    db.execute_sql(
1081
        f"""
1082
        DELETE FROM grid.egon_etrago_bus
1083
        WHERE carrier = 'CH4' AND scn_name = '{scn_name}'
1084
        """
1085
    )
1086
1087
    # Select next id value
1088
    new_id = db.next_etrago_id("bus")
1089
1090
    df = pd.DataFrame(
1091
        index=[new_id],
1092
        data={
1093
            "scn_name": scn_name,
1094
            "v_nom": 1,
1095
            "carrier": "CH4",
1096
            "v_mag_pu_set": 1,
1097
            "v_mag_pu_min": 0,
1098
            "v_mag_pu_max": np.inf,
1099
            "x": 10,
1100
            "y": 51,
1101
            "country": "DE",
1102
        },
1103
    )
1104
    gdf = geopandas.GeoDataFrame(
1105
        df, geometry=geopandas.points_from_xy(df.x, df.y, crs=4326)
1106
    ).rename_geometry("geom")
1107
1108
    gdf.index.name = "bus_id"
1109
1110
    gdf.reset_index().to_postgis(
1111
        "egon_etrago_bus", schema="grid", con=db.engine(), if_exists="append"
1112
    )
1113
1114
1115
class GasNodesAndPipes(Dataset):
1116
    """
1117
    Insert the CH4 buses and links into the database.
1118
1119
    Insert the CH4 buses and links, which for the case of gas represent
1120
    pipelines, into the database for the scenarios status2019, eGon2035 and eGon100RE
1121
    with the functions :py:func:`insert_gas_data` and :py:func:`insert_gas_data_eGon100RE`.
1122
1123
    *Dependencies*
1124
      * :py:class:`DataBundle <egon.data.datasets.data_bundle.DataBundle>`
1125
      * :py:class:`ElectricalNeighbours <egon.data.datasets.electrical_neighbours.ElectricalNeighbours>`
1126
      * :py:class:`Osmtgmod <egon.data.datasets.osmtgmod.Osmtgmod>`
1127
      * :py:class:`ScenarioParameters <egon.data.datasets.scenario_parameters.ScenarioParameters>`
1128
      * :py:class:`EtragoSetup <egon.data.datasets.etrago_setup.EtragoSetup>` (more specifically the :func:`create_tables <egon.data.datasets.etrago_setup.create_tables>` task)
1129
1130
    *Resulting tables*
1131
      * :py:class:`grid.egon_etrago_bus <egon.data.datasets.etrago_setup.EgonPfHvBus>` is extended
1132
      * :py:class:`grid.egon_etrago_link <egon.data.datasets.etrago_setup.EgonPfHvLink>` is extended
1133
1134
    """
1135
1136
    #:
1137
    name: str = "GasNodesAndPipes"
1138
    #:
1139
    version: str = "0.0.11"
1140
1141
    tasks = ()
1142
1143
    for scn_name in config.settings()["egon-data"]["--scenarios"]:
1144
        if "status" in scn_name:
1145
            tasks += (wrapped_partial(
1146
                insert_gas_data_status, scn_name=scn_name, postfix=f"_{scn_name[-4:]}"
1147
            ),)
1148
1149
    tasks += (insert_gas_data,)
1150
1151
    def __init__(self, dependencies):
1152
        super().__init__(
1153
            name=self.name,
1154
            version=self.version,
1155
            dependencies=dependencies,
1156
            tasks=self.tasks,
1157
        )
1158