From 6e2a0d5776e13bc445c4c694894abd158ac0693d Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 7 Aug 2023 15:29:20 +0200 Subject: [PATCH 001/109] include exclusion area --- etrago/appl.py | 2 + etrago/cluster/electrical.py | 89 +++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index ab64b5e17..48952bd44 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -109,6 +109,7 @@ "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False + "exclusion_area": ["Cuxhaven", "Bremerhaven", "Wesermarsch", "Osterholz", "Bremen"], # path to shapefile or list of nust names of not cluster area "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 17, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False @@ -464,6 +465,7 @@ def run_etrago(args, json_path): # spatial clustering etrago.spatial_clustering() + etrago.spatial_clustering_gas() # snapshot clustering diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 8a7bf7549..9ef05a76d 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -34,6 +34,7 @@ get_clustering_from_busmap, ) from six import iteritems + import geopandas as gpd import numpy as np import pandas as pd import pypsa.io as io @@ -47,6 +48,7 @@ strategies_one_ports, ) from etrago.tools.utilities import * + from shapely.geometry import Point logger = logging.getLogger(__name__) @@ -69,7 +71,7 @@ def _leading(busmap, df): """ - Returns a function that computes the leading bus_id for a given mapped + Returns a function that computes the leading bus_id for a given mapped list of buses. Parameters @@ -95,7 +97,7 @@ def leader(x): def adjust_no_electric_network(etrago, busmap, cluster_met): """ - Adjusts the non-electric network based on the electrical network + Adjusts the non-electric network based on the electrical network (esp. eHV network), adds the gas buses to the busmap, and creates the new buses for the non-electric network. @@ -117,7 +119,7 @@ def adjust_no_electric_network(etrago, busmap, cluster_met): Maps old bus_ids to new bus_ids including all sectors. """ network = etrago.network - # network2 is supposed to contain all the not electrical or gas buses + # network2 is supposed to contain all the not electrical or gas buses # and links network2 = network.copy(with_time=False) network2.buses = network2.buses[ @@ -386,9 +388,9 @@ def cluster_on_extra_high_voltage(etrago, busmap, with_time=True): def delete_ehv_buses_no_lines(network): """ - When there are AC buses totally isolated, this function deletes them in + When there are AC buses totally isolated, this function deletes them in order to make possible the creation of busmaps based on electrical - connections and other purposes. Additionally, it throws a warning to + connections and other purposes. Additionally, it throws a warning to inform the user in case that any correction should be done. Parameters @@ -504,6 +506,46 @@ def select_elec_network(etrago): """ elec_network = etrago.network.copy() settings = etrago.args["network_clustering"] + + # Exclude buses in the area that should not be clustered + if settings["exclusion_area"]: + con = etrago.engine + query = "SELECT gen, geometry FROM boundaries.vg250_krs" + + de_areas = gpd.read_postgis(query, con, geom_col="geometry") + de_areas = de_areas[de_areas["gen"].isin(settings["exclusion_area"])] + + try: + buses_area = gpd.GeoDataFrame( + etrago.network.buses, geometry="geom", crs=4326 + ) + except: + buses_area = etrago.network.buses[ + [ + "x", + "y", + ] + ] + buses_area["geom"] = buses_area.apply( + lambda x: Point(x["x"], x["y"]), axis=1 + ) + buses_area = gpd.GeoDataFrame( + buses_area, geometry="geom", crs=4326 + ) + + buses_area = gpd.clip(buses_area, de_areas) + elec_network.buses = elec_network.buses[ + ~elec_network.buses.index.isin(buses_area.index) + ] + + busmap_area = pd.Series( + buses_area.index.rename("bus_area"), + index=buses_area.index.rename("bus"), + ) + else: + busmap_area = pd.DataFrame() + + # Exclude foreign buses when it is set to don't include them in the clustering if settings["cluster_foreign_AC"]: elec_network.buses = elec_network.buses[ elec_network.buses.carrier == "AC" @@ -582,7 +624,7 @@ def select_elec_network(etrago): ), ] - return elec_network, n_clusters + return elec_network, n_clusters, busmap_area def unify_foreign_buses(etrago): @@ -764,14 +806,14 @@ def preprocessing(etrago): else: busmap_foreign = pd.Series(name="foreign", dtype=str) - network_elec, n_clusters = select_elec_network(etrago) + network_elec, n_clusters, busmap_area = select_elec_network(etrago) if settings["method"] == "kmedoids-dijkstra": lines_col = network_elec.lines.columns - # The Dijkstra clustering works using the shortest electrical path + # The Dijkstra clustering works using the shortest electrical path # between buses. In some cases, a bus has just DC connections, which - # are considered links. Therefore it is necessary to include + # are considered links. Therefore it is necessary to include # temporarily the DC links into the lines table. dc = network.links[network.links.carrier == "DC"] str1 = "DC_" @@ -795,10 +837,12 @@ def preprocessing(etrago): else: weight = weighting_for_scenario(network=network_elec, save=False) - return network_elec, weight, n_clusters, busmap_foreign + return network_elec, weight, n_clusters, busmap_foreign, busmap_area -def postprocessing(etrago, busmap, busmap_foreign, medoid_idx=None): +def postprocessing( + etrago, busmap, busmap_foreign, medoid_idx=None, busmap_area={} +): """ Postprocessing function for network clustering. @@ -830,6 +874,7 @@ def postprocessing(etrago, busmap, busmap_foreign, medoid_idx=None): busmap_elec = pd.DataFrame(busmap.copy(), dtype="string") busmap_elec.index.name = "bus" busmap_elec = busmap_elec.join(busmap_foreign, how="outer") + busmap_elec = busmap_elec.join(busmap_area, how="outer") busmap_elec = busmap_elec.join( pd.Series( medoid_idx.index.values.astype(str), @@ -866,6 +911,11 @@ def postprocessing(etrago, busmap, busmap_foreign, medoid_idx=None): medoid_idx.index.values.astype(str), medoid_idx.values.astype(int) ) + # add the not clustered buses to the busmap + if settings["exclusion_area"]: + for bus in busmap_area.index: + busmap[bus] = busmap_area[bus] + network, busmap = adjust_no_electric_network( etrago, busmap, cluster_met=method ) @@ -936,7 +986,6 @@ def weighting_for_scenario(network, save=None): """ def calc_availability_factor(gen): - """ Calculate the availability factor for a given generator. @@ -952,10 +1001,10 @@ def calc_availability_factor(gen): Notes ----- - Availability factor is defined as the ratio of the average power - output of the generator over the maximum power output capacity of + Availability factor is defined as the ratio of the average power + output of the generator over the maximum power output capacity of the generator. If the generator is time-dependent, its average power - output is calculated using the `network.generators_t` DataFrame. + output is calculated using the `network.generators_t` DataFrame. Otherwise, its availability factor is obtained from the `fixed_capacity_fac` dictionary, which contains pre-defined factors for fixed capacity generators. If the generator's availability factor @@ -1055,7 +1104,13 @@ def run_spatial_clustering(self): if self.args["network_clustering"]["active"]: self.network.generators.control = "PV" - elec_network, weight, n_clusters, busmap_foreign = preprocessing(self) + ( + elec_network, + weight, + n_clusters, + busmap_foreign, + busmap_area, + ) = preprocessing(self) if self.args["network_clustering"]["method"] == "kmeans": if self.args["network_clustering"]["k_elec_busmap"] == False: @@ -1086,7 +1141,7 @@ def run_spatial_clustering(self): medoid_idx = pd.Series(dtype=str) self.clustering, busmap = postprocessing( - self, busmap, busmap_foreign, medoid_idx + self, busmap, busmap_foreign, medoid_idx, busmap_area ) self.update_busmap(busmap) From 670ff5e09c81d8c622d4ce3a0dae8f69dc820754 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 7 Aug 2023 15:33:03 +0200 Subject: [PATCH 002/109] add plot_carrier to the etrago methods --- etrago/tools/network.py | 3 +++ etrago/tools/plot.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/etrago/tools/network.py b/etrago/tools/network.py index 97ffbfe77..732ced23b 100644 --- a/etrago/tools/network.py +++ b/etrago/tools/network.py @@ -54,6 +54,7 @@ heat_stores, hydrogen_stores, plot_clusters, + plot_carrier, plot_gas_generation, plot_gas_summary, plot_grid, @@ -267,6 +268,8 @@ def __init__( plot_grid = plot_grid plot_clusters = plot_clusters + + plot_carrier = plot_carrier plot_gas_generation = plot_gas_generation diff --git a/etrago/tools/plot.py b/etrago/tools/plot.py index 89573506d..707092ec2 100644 --- a/etrago/tools/plot.py +++ b/etrago/tools/plot.py @@ -2134,7 +2134,7 @@ def flexibility_usage( fig_e.savefig(pre_path + f"stored_e_{flexibility}") -def plot_carrier(network, carrier_links=["AC"], carrier_buses=["AC"]): +def plot_carrier(etrago, carrier_links=["AC"], carrier_buses=["AC"]): """ Parameters ---------- @@ -2152,7 +2152,7 @@ def plot_carrier(network, carrier_links=["AC"], carrier_buses=["AC"]): None. """ - + network = etrago.network colors = coloring() line_colors = "lightblue" From 4dac3b1f834cfb50e00826c7f5a2fe51f930e629 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 9 Aug 2023 09:12:09 +0200 Subject: [PATCH 003/109] include engine and session to loaded etrago obj --- etrago/tools/network.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etrago/tools/network.py b/etrago/tools/network.py index 732ced23b..d9fc85eb0 100644 --- a/etrago/tools/network.py +++ b/etrago/tools/network.py @@ -195,6 +195,12 @@ def __init__( self.get_clustering_data(csv_folder_name) + conn = db.connection(section=self.args["db"]) + self.engine = conn + + session = sessionmaker(bind=conn) + self.session = session() + else: logger.error("Set args or csv_folder_name") From 761b50330b6c16252854f6847a4c1e6f2e79fc0b Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 9 Aug 2023 09:22:10 +0200 Subject: [PATCH 004/109] include function find_buses_area for clustering --- etrago/cluster/electrical.py | 42 ++++---------------------------- etrago/cluster/gas.py | 23 +++++++++++++----- etrago/cluster/spatial.py | 46 ++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 9ef05a76d..e54e49fe9 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -34,7 +34,6 @@ get_clustering_from_busmap, ) from six import iteritems - import geopandas as gpd import numpy as np import pandas as pd import pypsa.io as io @@ -46,9 +45,9 @@ kmedoids_dijkstra_clustering, strategies_generators, strategies_one_ports, + find_buses_area ) from etrago.tools.utilities import * - from shapely.geometry import Point logger = logging.getLogger(__name__) @@ -508,42 +507,9 @@ def select_elec_network(etrago): settings = etrago.args["network_clustering"] # Exclude buses in the area that should not be clustered - if settings["exclusion_area"]: - con = etrago.engine - query = "SELECT gen, geometry FROM boundaries.vg250_krs" - - de_areas = gpd.read_postgis(query, con, geom_col="geometry") - de_areas = de_areas[de_areas["gen"].isin(settings["exclusion_area"])] - - try: - buses_area = gpd.GeoDataFrame( - etrago.network.buses, geometry="geom", crs=4326 - ) - except: - buses_area = etrago.network.buses[ - [ - "x", - "y", - ] - ] - buses_area["geom"] = buses_area.apply( - lambda x: Point(x["x"], x["y"]), axis=1 - ) - buses_area = gpd.GeoDataFrame( - buses_area, geometry="geom", crs=4326 - ) - - buses_area = gpd.clip(buses_area, de_areas) - elec_network.buses = elec_network.buses[ - ~elec_network.buses.index.isin(buses_area.index) - ] - - busmap_area = pd.Series( - buses_area.index.rename("bus_area"), - index=buses_area.index.rename("bus"), - ) - else: - busmap_area = pd.DataFrame() + busmap_area = find_buses_area(etrago, "AC") + elec_network.buses = elec_network.buses[ + ~elec_network.buses.index.isin(busmap_area.index)] # Exclude foreign buses when it is set to don't include them in the clustering if settings["cluster_foreign_AC"]: diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index 733b9905a..30993b8e0 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -39,6 +39,7 @@ group_links, kmedoids_dijkstra_clustering, sum_with_inf, + find_buses_area ) from etrago.tools.utilities import * @@ -101,7 +102,8 @@ def preprocessing(etrago): ) ] - # select buses dependent on whether they should be clustered in (only DE or DE+foreign) + # select buses dependent on whether they should be clustered in (only DE + # or DE+foreign DE except specified area) if not settings["cluster_foreign_gas"]: network_ch4.buses = network_ch4.buses.loc[ ch4_filter & (network_ch4.buses["country"].values == "DE") @@ -122,6 +124,11 @@ def preprocessing(etrago): network_ch4.buses = network_ch4.buses.loc[ch4_filter] n_clusters = settings["n_clusters_gas"] + # Exclude buses in the area that should not be clustered + busmap_area = find_buses_area(etrago, "CH4") + network_ch4.buses = network_ch4.buses[ + ~network_ch4.buses.index.isin(busmap_area.index)] + def weighting_for_scenario(ch4_buses, save=None): """ Calculate CH4-bus weightings dependant on the connected @@ -215,7 +222,7 @@ def weighting_for_scenario(ch4_buses, save=None): weight_ch4.loc[loaded_weights.index] = loaded_weights else: weight_ch4 = weighting_for_scenario(network_ch4.buses, save=False) - return network_ch4, weight_ch4.squeeze(), n_clusters + return network_ch4, weight_ch4.squeeze(), n_clusters, busmap_area def kmean_clustering_gas(etrago, network_ch4, weight, n_clusters): @@ -293,7 +300,7 @@ def get_h2_clusters(etrago, busmap_ch4): return busmap -def gas_postprocessing(etrago, busmap, medoid_idx=None): +def gas_postprocessing(etrago, busmap, medoid_idx=None, busmap_area=pd.DataFrame()): """ Performs the postprocessing for the gas grid clustering based on the provided busmap @@ -345,7 +352,11 @@ def gas_postprocessing(etrago, busmap, medoid_idx=None): + str(settings["n_clusters_gas"]) + "_result.csv" ) - + breakpoint() + ########################################################################### + #include busmap_area to busmap + ########################################################################### + busmap = get_h2_clusters(etrago, busmap) # Add all other buses to busmap @@ -947,7 +958,7 @@ def run_spatial_clustering_gas(self): method = settings["method_gas"] logger.info(f"Start {method} clustering GAS") - gas_network, weight, n_clusters = preprocessing(self) + gas_network, weight, n_clusters, busmap_area = preprocessing(self) if method == "kmeans": if settings["k_gas_busmap"]: @@ -991,7 +1002,7 @@ def run_spatial_clustering_gas(self): "spatial clustering method for the gas network" ) raise ValueError(msg) - self.network, busmap = gas_postprocessing(self, busmap, medoid_idx) + self.network, busmap = gas_postprocessing(self, busmap, medoid_idx, busmap_area) self.update_busmap(busmap) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index bbf176b36..d73b201cd 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -38,9 +38,11 @@ ) from sklearn.cluster import KMeans from threadpoolctl import threadpool_limits + from shapely.geometry import Point import networkx as nx import numpy as np import pandas as pd + import geopandas as gpd from etrago.tools.utilities import * @@ -797,3 +799,47 @@ def kmedoids_dijkstra_clustering( busmap.index.name = "bus_id" return busmap, medoid_idx + +def find_buses_area(etrago, carrier): + """ + Find buses of a specified carrier in a defined area. Usually used to + findout the buses that sould not be clustered. + """ + settings = etrago.args["network_clustering"] + + if settings["exclusion_area"]: + con = etrago.engine + query = "SELECT gen, geometry FROM boundaries.vg250_krs" + + de_areas = gpd.read_postgis(query, con, geom_col="geometry") + de_areas = de_areas[de_areas["gen"].isin(settings["exclusion_area"])] + + try: + buses_area = gpd.GeoDataFrame( + etrago.network.buses, geometry="geom", crs=4326 + ) + except: + buses_area = etrago.network.buses[ + [ + "x", + "y", + "carrier" + ] + ] + buses_area["geom"] = buses_area.apply( + lambda x: Point(x["x"], x["y"]), axis=1 + ) + buses_area = gpd.GeoDataFrame( + buses_area, geometry="geom", crs=4326 + ) + + buses_area = gpd.clip(buses_area, de_areas) + buses_area = buses_area[buses_area.carrier == carrier] + busmap_area = pd.Series( + buses_area.index.rename("bus_area"), + index=buses_area.index.rename("bus"), + ) + else: + busmap_area = pd.DataFrame() + + return busmap_area \ No newline at end of file From ea86aa0f417ec19f8ae953971e98175bc60c33c5 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 21 Sep 2023 09:28:03 +0200 Subject: [PATCH 005/109] Simplify adjust_no_electric_network by not creating new buses --- etrago/cluster/electrical.py | 87 +++++++----------------------------- 1 file changed, 17 insertions(+), 70 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index d0cb02b78..a090141df 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -137,16 +137,9 @@ def adjust_no_electric_network(etrago, busmap, cluster_met): "rural_heat": "rural_heat_pump", } - # no_elec_to_cluster maps the no electrical buses to the eHV/kmean bus - no_elec_to_cluster = pd.DataFrame( - columns=["cluster", "carrier", "new_bus"] - ).set_index("new_bus") - - max_bus = max([int(item) for item in network.buses.index.to_list()]) - no_elec_conex = [] - # busmap2 maps all the no electrical buses to the new buses based on the - # eHV network + # busmap2 defines how the no electrical buses directly connected to AC + # are going to be clustered busmap2 = {} # Map crossborder AC buses in case that they were not part of the k-mean clustering @@ -161,52 +154,26 @@ def adjust_no_electric_network(etrago, busmap, cluster_met): for bus_out in ac_buses_out.index: busmap2[bus_out] = bus_out + busmap3 = pd.DataFrame(columns=["elec_bus", "carrier", "cluster"]) for bus_ne in network2.buses.index: - bus_hv = -1 carry = network2.buses.loc[bus_ne, "carrier"] - - if ( - len( - network2.links[ - (network2.links["bus1"] == bus_ne) - & (network2.links["carrier"] == map_carrier[carry]) - ] - ) - > 0 - ): + busmap3.at[bus_ne, "carrier"] = carry + try: df = network2.links[ (network2.links["bus1"] == bus_ne) & (network2.links["carrier"] == map_carrier[carry]) ].copy() df["elec"] = df["bus0"].isin(busmap.keys()) - df = df[df["elec"] == True] - if len(df) > 0: - bus_hv = df["bus0"][0] - - if bus_hv == -1: - busmap2[bus_ne] = str(bus_ne) + bus_hv = df[df["elec"] == True]["bus0"][0] + busmap3.at[bus_ne, "elec_bus"] = busmap[bus_hv] + except: no_elec_conex.append(bus_ne) - continue - - if ( - (no_elec_to_cluster.cluster == busmap[bus_hv]) - & (no_elec_to_cluster.carrier == carry) - ).any(): - bus_cluster = no_elec_to_cluster[ - (no_elec_to_cluster.cluster == busmap[bus_hv]) - & (no_elec_to_cluster.carrier == carry) - ].index[0] - else: - bus_cluster = str(max_bus + 1) - max_bus = max_bus + 1 - new = pd.DataFrame( - {"cluster": busmap[bus_hv], "carrier": carry}, - index=[bus_cluster], - ) + busmap3.at[bus_ne, "elec_bus"] = bus_ne - no_elec_to_cluster = pd.concat([no_elec_to_cluster, new]) + for a, df in busmap3.groupby(["elec_bus", "carrier"]): + busmap3.loc[df.index, "cluster"] = df.index[0] - busmap2[bus_ne] = bus_cluster + busmap3 = busmap3["cluster"].to_dict() if no_elec_conex: logger.info( @@ -215,17 +182,17 @@ def adjust_no_electric_network(etrago, busmap, cluster_met): ) # rural_heat_store buses are clustered based on the AC buses connected to - # their corresponding rural_heat buses + # their corresponding rural_heat buses. Results saved in busmap4 links_rural_store = etrago.network.links[ etrago.network.links.carrier == "rural_heat_store_charger" ].copy() - busmap3 = {} - links_rural_store["to_ac"] = links_rural_store["bus0"].map(busmap2) + busmap4 = {} + links_rural_store["to_ac"] = links_rural_store["bus0"].map(busmap3) for rural_heat_bus, df in links_rural_store.groupby("to_ac"): cluster_bus = df.bus1.iat[0] for rural_store_bus in df.bus1: - busmap3[rural_store_bus] = cluster_bus + busmap4[rural_store_bus] = cluster_bus # Add the gas buses to the busmap and map them to themself for gas_bus in network.buses[ @@ -236,28 +203,8 @@ def adjust_no_electric_network(etrago, busmap, cluster_met): ].index: busmap2[gas_bus] = gas_bus - busmap = {**busmap, **busmap2, **busmap3} - - # The new buses based on the eHV network for not electrical buses are - # created - if cluster_met in ["kmeans", "kmedoids-dijkstra"]: - network.madd( - "Bus", - names=no_elec_to_cluster.index, - carrier=no_elec_to_cluster.carrier, - ) + busmap = {**busmap, **busmap2, **busmap3, **busmap4} - else: - network.madd( - "Bus", - names=no_elec_to_cluster.index, - carrier=no_elec_to_cluster.carrier.values, - x=network.buses.loc[no_elec_to_cluster.cluster.values, "x"].values, - y=network.buses.loc[no_elec_to_cluster.cluster.values, "y"].values, - country=network.buses.loc[ - no_elec_to_cluster.cluster.values, "country" - ].values, - ) return network, busmap From 030f8ecc5cc0b574f322d3645021d09c27b032a2 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 21 Sep 2023 10:49:14 +0200 Subject: [PATCH 006/109] receive also shapefiles to not clustered area --- etrago/cluster/spatial.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 6d578a1b8..dcbc26396 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -802,6 +802,7 @@ def kmedoids_dijkstra_clustering( return busmap, medoid_idx + def find_buses_area(etrago, carrier): """ Find buses of a specified carrier in a defined area. Usually used to @@ -810,24 +811,27 @@ def find_buses_area(etrago, carrier): settings = etrago.args["network_clustering"] if settings["exclusion_area"]: - con = etrago.engine - query = "SELECT gen, geometry FROM boundaries.vg250_krs" + if isinstance(settings["exclusion_area"], list): + con = etrago.engine + query = "SELECT gen, geometry FROM boundaries.vg250_krs" - de_areas = gpd.read_postgis(query, con, geom_col="geometry") - de_areas = de_areas[de_areas["gen"].isin(settings["exclusion_area"])] + de_areas = gpd.read_postgis(query, con, geom_col="geometry") + de_areas = de_areas[ + de_areas["gen"].isin(settings["exclusion_area"]) + ] + elif isinstance(settings["exclusion_area"], str): + de_areas = gpd.read_file(settings["exclusion_area"]) + else: + raise Exception( + "not supported format supplied to the 'exclusion_area' argument" + ) try: buses_area = gpd.GeoDataFrame( etrago.network.buses, geometry="geom", crs=4326 ) except: - buses_area = etrago.network.buses[ - [ - "x", - "y", - "carrier" - ] - ] + buses_area = etrago.network.buses[["x", "y", "carrier"]] buses_area["geom"] = buses_area.apply( lambda x: Point(x["x"], x["y"]), axis=1 ) @@ -844,4 +848,4 @@ def find_buses_area(etrago, carrier): else: busmap_area = pd.DataFrame() - return busmap_area \ No newline at end of file + return busmap_area From 0d78999f097ec3cbd111d7384a5b1b18c9d78090 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 21 Sep 2023 10:58:05 +0200 Subject: [PATCH 007/109] Document exclusion_area --- etrago/appl.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/etrago/appl.py b/etrago/appl.py index 41852335a..fee8cbcfb 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -109,7 +109,7 @@ "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False - "exclusion_area": ["Cuxhaven", "Bremerhaven", "Wesermarsch", "Osterholz", "Bremen"], # path to shapefile or list of nust names of not cluster area + "exclusion_area": False, # False, path to shapefile or list of nuts names of not cluster area "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 17, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False @@ -411,6 +411,13 @@ def run_etrago(args, json_path): If set to True, foreign AC buses are clustered as well and included in number of clusters specified through ``'n_clusters_AC'``. Default: False. + * "exclusion_area": False, list, string + Area of especial interest that will be not clustered. It is by + default set to false. When an exclusion area is provided, the given + value for n_clusters_AC will mean the total of AC buses outside the + area.The areas can be provided in two ways: list of + nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string + with a path to a shape file. * "method_gas" : str Method used for gas clustering. You can choose between two clustering methods: From adccc1fd667d1be96bc237f9e394265d5c77b25d Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 21 Sep 2023 11:14:45 +0200 Subject: [PATCH 008/109] apply Black --- etrago/appl.py | 2 +- etrago/cluster/gas.py | 19 ++++++++++++------- etrago/tools/network.py | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index fee8cbcfb..6b690ac20 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -109,7 +109,7 @@ "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False - "exclusion_area": False, # False, path to shapefile or list of nuts names of not cluster area + "exclusion_area": False, # False, path to shapefile or list of nuts names of not cluster area "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 17, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index 2ec1aa6d9..4760c05ae 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -39,7 +39,7 @@ group_links, kmedoids_dijkstra_clustering, sum_with_inf, - find_buses_area + find_buses_area, ) from etrago.tools.utilities import * @@ -127,7 +127,8 @@ def preprocessing(etrago): # Exclude buses in the area that should not be clustered busmap_area = find_buses_area(etrago, "CH4") network_ch4.buses = network_ch4.buses[ - ~network_ch4.buses.index.isin(busmap_area.index)] + ~network_ch4.buses.index.isin(busmap_area.index) + ] def weighting_for_scenario(ch4_buses, save=None): """ @@ -300,7 +301,9 @@ def get_h2_clusters(etrago, busmap_ch4): return busmap -def gas_postprocessing(etrago, busmap, medoid_idx=None, busmap_area=pd.DataFrame()): +def gas_postprocessing( + etrago, busmap, medoid_idx=None, busmap_area=pd.DataFrame() +): """ Performs the postprocessing for the gas grid clustering based on the provided busmap @@ -352,12 +355,12 @@ def gas_postprocessing(etrago, busmap, medoid_idx=None, busmap_area=pd.DataFrame + "_result.csv" ) - #breakpoint() + # breakpoint() ########################################################################### - #include busmap_area to busmap + # include busmap_area to busmap ########################################################################### - if 'H2' in etrago.network.buses.carrier.unique(): + if "H2" in etrago.network.buses.carrier.unique(): busmap = get_h2_clusters(etrago, busmap) # Add all other buses to busmap @@ -1004,7 +1007,9 @@ def run_spatial_clustering_gas(self): "spatial clustering method for the gas network" ) raise ValueError(msg) - self.network, busmap = gas_postprocessing(self, busmap, medoid_idx, busmap_area) + self.network, busmap = gas_postprocessing( + self, busmap, medoid_idx, busmap_area + ) self.update_busmap(busmap) diff --git a/etrago/tools/network.py b/etrago/tools/network.py index 64e26bae0..9c3eac12e 100644 --- a/etrago/tools/network.py +++ b/etrago/tools/network.py @@ -277,7 +277,7 @@ def __init__( plot_grid = plot_grid plot_clusters = plot_clusters - + plot_carrier = plot_carrier plot_gas_generation = plot_gas_generation From 75cbf94627500632438c3fb00f1b9273470e29e5 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 21 Sep 2023 11:20:24 +0200 Subject: [PATCH 009/109] use isort --- etrago/cluster/electrical.py | 2 +- etrago/cluster/gas.py | 2 +- etrago/cluster/spatial.py | 4 ++-- etrago/tools/network.py | 2 +- etrago/tools/plot.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index a090141df..eb093fd4b 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -40,12 +40,12 @@ from etrago.cluster.spatial import ( busmap_from_psql, + find_buses_area, group_links, kmean_clustering, kmedoids_dijkstra_clustering, strategies_generators, strategies_one_ports, - find_buses_area ) from etrago.tools.utilities import * diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index 4760c05ae..00b17014f 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -36,10 +36,10 @@ import pypsa.io as io from etrago.cluster.spatial import ( + find_buses_area, group_links, kmedoids_dijkstra_clustering, sum_with_inf, - find_buses_area, ) from etrago.tools.utilities import * diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index dcbc26396..15e2da439 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -36,13 +36,13 @@ busmap_by_stubs, get_clustering_from_busmap, ) + from shapely.geometry import Point from sklearn.cluster import KMeans from threadpoolctl import threadpool_limits - from shapely.geometry import Point + import geopandas as gpd import networkx as nx import numpy as np import pandas as pd - import geopandas as gpd from etrago.tools.utilities import * diff --git a/etrago/tools/network.py b/etrago/tools/network.py index 9c3eac12e..5e2ba1ac6 100644 --- a/etrago/tools/network.py +++ b/etrago/tools/network.py @@ -56,8 +56,8 @@ flexibility_usage, heat_stores, hydrogen_stores, - plot_clusters, plot_carrier, + plot_clusters, plot_gas_generation, plot_gas_summary, plot_grid, diff --git a/etrago/tools/plot.py b/etrago/tools/plot.py index 26b1ee4eb..a78ddaa4d 100644 --- a/etrago/tools/plot.py +++ b/etrago/tools/plot.py @@ -45,9 +45,9 @@ if "READTHEDOCS" not in os.environ: from geoalchemy2.shape import to_shape - import geopandas as gpd from pyproj import Proj, transform from shapely.geometry import LineString, MultiPoint, Point, Polygon + import geopandas as gpd import tilemapbase __copyright__ = ( From bf2ca6c19b2fc28c5c7be3e560d0591d04368654 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 26 Oct 2023 10:38:38 +0200 Subject: [PATCH 010/109] Black and delete comments --- etrago/cluster/electrical.py | 3 ++- etrago/cluster/gas.py | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index ca638e4ea..acd11fee2 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -485,7 +485,8 @@ def select_elec_network(etrago): # Exclude buses in the area that should not be clustered busmap_area = find_buses_area(etrago, "AC") elec_network.buses = elec_network.buses[ - ~elec_network.buses.index.isin(busmap_area.index)] + ~elec_network.buses.index.isin(busmap_area.index) + ] # Exclude foreign buses when it is set to don't include them in the clustering if settings["cluster_foreign_AC"]: diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index 467a0423c..b370f4c7c 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -358,11 +358,6 @@ def gas_postprocessing( + "_result.csv" ) - # breakpoint() - ########################################################################### - # include busmap_area to busmap - ########################################################################### - if "H2" in etrago.network.buses.carrier.unique(): busmap = get_h2_clusters(etrago, busmap) From c2436da1bdf9bab711c7b05ff25644fc32d2711e Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Tue, 31 Oct 2023 11:32:11 +0100 Subject: [PATCH 011/109] Allow importing extnesion scenario, e.g. for lines from NEP --- etrago/tools/io.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/etrago/tools/io.py b/etrago/tools/io.py index c87bda98b..1065b5c81 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -112,12 +112,14 @@ def __init__( start_snapshot=1, end_snapshot=20, temp_id=1, + scenario_extension=False, **kwargs, ): self.scn_name = scn_name self.start_snapshot = start_snapshot self.end_snapshot = end_snapshot self.temp_id = temp_id + self.scenario_extension = scenario_extension super().__init__(engine, session, **kwargs) @@ -207,6 +209,14 @@ def fetch_by_relname(self, name): egon_etrago_transformer, ) + if self.scenario_extension: + from saio.grid import ( # noqa: F401 + egon_etrago_extension_bus as egon_etrago_bus, + egon_etrago_extension_line as egon_etrago_line, + egon_etrago_extension_link as egon_etrago_link, + egon_etrago_extension_transformer as egon_etrago_transformer, + ) + index = f"{name.lower()}_id" if name == "Transformer": @@ -798,22 +808,17 @@ def extension(self, **kwargs): """ if self.args["scn_extension"] is not None: - if self.args["gridversion"] is None: - ormcls_prefix = "EgoGridPfHvExtension" - else: - ormcls_prefix = "EgoPfHvExtension" - for i in range(len(self.args["scn_extension"])): scn_extension = self.args["scn_extension"][i] # Adding overlay-network to existing network scenario = NetworkScenario( + self.engine, self.session, version=self.args["gridversion"], - prefix=ormcls_prefix, - method=kwargs.get("method", "lopf"), start_snapshot=self.args["start_snapshot"], end_snapshot=self.args["end_snapshot"], - scn_name="extension_" + scn_extension, + scn_name=scn_extension, + scenario_extension=True, ) self.network = scenario.build_network(self.network) From 92d64800d536e821bc4aa4c6da12744360a507f7 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Tue, 31 Oct 2023 11:35:19 +0100 Subject: [PATCH 012/109] Update documentation for scenario_extension --- etrago/appl.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index fde0be7ae..e3c956f34 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -252,22 +252,17 @@ def run_etrago(args, json_path): scn_name : str Choose your scenario. Currently, there are two different scenarios: "eGon2035", "eGon100RE". Default: "eGon2035". - scn_extension : None or str - This option does currently not work! + scn_extension : None or list of str Choose extension-scenarios which will be added to the existing network container. Data of the extension scenarios are located in - extension-tables (e.g. model_draft.ego_grid_pf_hv_extension_bus) - with the prefix 'extension\_'. - There are three overlay networks: - - * 'nep2035_confirmed' includes all planed new lines confirmed by the - Bundesnetzagentur - * 'nep2035_b2' includes all new lines planned by the - Netzentwicklungsplan 2025 in scenario 2035 B2 - * 'BE_NO_NEP 2035' includes planned lines to Belgium and Norway and - adds BE and NO as electrical neighbours + extension-tables (e.g. grid.egon_etrago_extension_line) + There are two overlay networks: + * 'nep2021_confirmed' includes all planed new lines confirmed by the + Bundesnetzagentur included in the NEP version 2021 + * 'nep2021_c2035' includes all new lines planned by the + Netzentwicklungsplan 2021 in scenario 2035 C Default: None. scn_decommissioning : NoneType or str This option does currently not work! From da2ee155fe9a15e2e6b48b0505597c7e2709d031 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 1 Nov 2023 09:06:31 +0100 Subject: [PATCH 013/109] using flake8 --- etrago/cluster/electrical.py | 2 +- etrago/cluster/spatial.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index ffc117c1c..61ad7e90e 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -486,7 +486,7 @@ def select_elec_network(etrago): ~elec_network.buses.index.isin(busmap_area.index) ] - # Exclude foreign buses when it is set to don't include them in the clustering + # Exclude foreign buses when it is set to don't include them in clustering if settings["cluster_foreign_AC"]: elec_network.buses = elec_network.buses[ elec_network.buses.carrier == "AC" diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index ed01ff888..bc4c50550 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -792,7 +792,7 @@ def find_buses_area(etrago, carrier): de_areas = gpd.read_file(settings["exclusion_area"]) else: raise Exception( - "not supported format supplied to the 'exclusion_area' argument" + "not supported format supplied to 'exclusion_area' argument" ) try: From eee79113629b5e7e0c0be58a962446a56762fea8 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 1 Nov 2023 10:41:45 +0100 Subject: [PATCH 014/109] adapt select_elec_network --- etrago/cluster/electrical.py | 84 +++++++++++++++++++++++++----------- etrago/cluster/spatial.py | 7 +-- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 61ad7e90e..7a5dfc510 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -461,8 +461,8 @@ def ehv_clustering(self): def select_elec_network(etrago): """ - Selects the electric network based on the clustering settings specified - in the Etrago object. + Creates networks to be used on the clustering based on settings specified + in the args. Parameters ---------- @@ -476,43 +476,78 @@ def select_elec_network(etrago): Contains the electric network n_clusters : int number of clusters used in the clustering process. + area_network : pypsa.Network + Contains the electric network in the area of interest defined in + network_clustering - exclusion_area. """ - elec_network = etrago.network.copy() settings = etrago.args["network_clustering"] - # Exclude buses in the area that should not be clustered - busmap_area = find_buses_area(etrago, "AC") - elec_network.buses = elec_network.buses[ - ~elec_network.buses.index.isin(busmap_area.index) - ] + # Find buses in the area that should not be clustered + buses_area = find_buses_area(etrago, "AC") + + elec_network_buses = etrago.network.buses[ + (~etrago.network.buses.index.isin(buses_area)) + & (etrago.network.buses.carrier == "AC") + ].index # Exclude foreign buses when it is set to don't include them in clustering if settings["cluster_foreign_AC"]: - elec_network.buses = elec_network.buses[ - elec_network.buses.carrier == "AC" - ] - elec_network.links = elec_network.links[ - (elec_network.links.carrier == "AC") - | (elec_network.links.carrier == "DC") - ] n_clusters = settings["n_clusters_AC"] else: - AC_filter = elec_network.buses.carrier.values == "AC" - - foreign_buses = elec_network.buses[ - (elec_network.buses.country != "DE") - & (elec_network.buses.carrier == "AC") + foreign_buses = etrago.network.buses[ + (etrago.network.buses.country != "DE") + & (etrago.network.buses.carrier == "AC") ] num_neighboring_country = len( - foreign_buses[foreign_buses.index.isin(elec_network.loads.bus)] + foreign_buses[foreign_buses.index.isin(etrago.network.loads.bus)] ) - elec_network.buses = elec_network.buses[ - AC_filter & (elec_network.buses.country.values == "DE") + elec_network_buses = elec_network_buses[ + ~elec_network_buses.isin(foreign_buses.index) ] n_clusters = settings["n_clusters_AC"] - num_neighboring_country + elec_network = network_based_on_buses(etrago.network, elec_network_buses) + area_network = network_based_on_buses(etrago.network, buses_area) + + return elec_network, n_clusters, area_network + + +def network_based_on_buses(network, buses): + """ + Extract all the elements in a network related to the supplied list of + buses and return it like a new network. + + Parameters + ---------- + network : pypsa.Network + Original network that contains the buses of interest and other buses. + buses : Pandas.Series + Series that contains the name of all the buses that the new network + will contain. + + Returns + ------- + elec_network : pypsa.Network + network containing only electrical elements attached to the supplied + list of buses. + + """ + elec_network = network.copy() + elec_network.buses = elec_network.buses[ + elec_network.buses.index.isin(buses) + ] + # Dealing with links + elec_network.links = elec_network.links[ + ( + (elec_network.links.carrier == "AC") + | (elec_network.links.carrier == "DC") + ) + & (elec_network.links.bus0.isin(elec_network.buses.index)) + & (elec_network.links.bus1.isin(elec_network.buses.index)) + ] + # Dealing with generators elec_network.generators = elec_network.generators[ elec_network.generators.bus.isin(elec_network.buses.index) @@ -564,8 +599,7 @@ def select_elec_network(etrago): elec_network.stores.index ), ] - - return elec_network, n_clusters, busmap_area + return elec_network def unify_foreign_buses(etrago): diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index bc4c50550..a7e97f0b2 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -810,11 +810,8 @@ def find_buses_area(etrago, carrier): buses_area = gpd.clip(buses_area, de_areas) buses_area = buses_area[buses_area.carrier == carrier] - busmap_area = pd.Series( - buses_area.index.rename("bus_area"), - index=buses_area.index.rename("bus"), - ) + else: busmap_area = pd.DataFrame() - return busmap_area + return buses_area.index From b817a1049b48acf707e87c9dcaeefd7cb2c22897 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 1 Nov 2023 10:53:51 +0100 Subject: [PATCH 015/109] include weighting for area --- etrago/cluster/electrical.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 7a5dfc510..d4f5f79cd 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -775,7 +775,7 @@ def preprocessing(etrago): else: busmap_foreign = pd.Series(name="foreign", dtype=str) - network_elec, n_clusters, busmap_area = select_elec_network(etrago) + network_elec, n_clusters, network_area = select_elec_network(etrago) if settings["method"] == "kmedoids-dijkstra": lines_col = network_elec.lines.columns @@ -805,8 +805,16 @@ def preprocessing(etrago): weight.index = weight.index.astype(str) else: weight = weighting_for_scenario(network=network_elec, save=False) - - return network_elec, weight, n_clusters, busmap_foreign, busmap_area + weight_area = weighting_for_scenario(network=network_area, save=False) + + return ( + network_elec, + weight, + n_clusters, + busmap_foreign, + network_area, + weight_area, + ) def postprocessing( @@ -1080,7 +1088,8 @@ def run_spatial_clustering(self): weight, n_clusters, busmap_foreign, - busmap_area, + network_area, + weight_area, ) = preprocessing(self) if self.args["network_clustering"]["method"] == "kmeans": From 80303b701508d6632105189457a6c24c8efa8cea Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 1 Nov 2023 15:24:31 +0100 Subject: [PATCH 016/109] perform clustering area of interest --- etrago/cluster/electrical.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index d4f5f79cd..7434eda5d 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -1108,7 +1108,7 @@ def run_spatial_clustering(self): if not self.args["network_clustering"]["k_elec_busmap"]: logger.info("Start k-medoids Dijkstra Clustering") - busmap, medoid_idx = kmedoids_dijkstra_clustering( + busmap_elec, medoid_idx_elec = kmedoids_dijkstra_clustering( self, elec_network.buses, elec_network.lines, @@ -1116,6 +1116,25 @@ def run_spatial_clustering(self): n_clusters, ) + busmap_area, medoid_idx_area = kmedoids_dijkstra_clustering( + self, + network_area.buses, + network_area.lines, + weight_area, + self.args["network_clustering"]["cluster_exclusion_area"], + ) + + medoid_idx_area.index = ( + medoid_idx_area.index.astype(int) + + busmap_elec.apply(int).max() + + 1 + ) + busmap_area = ( + busmap_area.astype(int) + busmap_elec.apply(int).max() + 1 + ).apply(str) + busmap = pd.concat([busmap_elec, busmap_area]) + medoid_idx = pd.concat([medoid_idx_elec, medoid_idx_area]) + else: busmap = pd.Series(dtype=str) medoid_idx = pd.Series(dtype=str) From 9703ed0bf11dd6062d24d8e486b63835be74b682 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 1 Nov 2023 15:26:16 +0100 Subject: [PATCH 017/109] gas adaptation to cluster interest area --- etrago/cluster/gas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index d74065f9e..3924639e7 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -131,7 +131,7 @@ def preprocessing(etrago): # Exclude buses in the area that should not be clustered busmap_area = find_buses_area(etrago, "CH4") network_ch4.buses = network_ch4.buses[ - ~network_ch4.buses.index.isin(busmap_area.index) + ~network_ch4.buses.index.isin(busmap_area) ] def weighting_for_scenario(ch4_buses, save=None): From b5433661cb067311056c5f0356b13b7ac80f81f6 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 1 Nov 2023 15:30:08 +0100 Subject: [PATCH 018/109] using isort --- etrago/cluster/electrical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 7434eda5d..dad277c72 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -39,8 +39,8 @@ import pypsa.io as io from etrago.cluster.spatial import ( - find_buses_area, busmap_ehv_clustering, + find_buses_area, group_links, kmean_clustering, kmedoids_dijkstra_clustering, From c267178ca19a3bc707cbf253745ecded84a30fd1 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 1 Nov 2023 15:37:02 +0100 Subject: [PATCH 019/109] fix incorrect name --- etrago/cluster/spatial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index a7e97f0b2..1377c5ec5 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -812,6 +812,6 @@ def find_buses_area(etrago, carrier): buses_area = buses_area[buses_area.carrier == carrier] else: - busmap_area = pd.DataFrame() + buses_area = pd.DataFrame() return buses_area.index From 33fa5e95c20751e92530482882b20291d405796f Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 2 Nov 2023 10:54:02 +0100 Subject: [PATCH 020/109] include area of interest in kmeans clustering --- etrago/cluster/electrical.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index dad277c72..06db0dd0b 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -817,9 +817,7 @@ def preprocessing(etrago): ) -def postprocessing( - etrago, busmap, busmap_foreign, medoid_idx=None, busmap_area={} -): +def postprocessing(etrago, busmap, busmap_foreign, medoid_idx=None): """ Postprocessing function for network clustering. @@ -851,7 +849,6 @@ def postprocessing( busmap_elec = pd.DataFrame(busmap.copy(), dtype="string") busmap_elec.index.name = "bus" busmap_elec = busmap_elec.join(busmap_foreign, how="outer") - busmap_elec = busmap_elec.join(busmap_area, how="outer") busmap_elec = busmap_elec.join( pd.Series( medoid_idx.index.values.astype(str), @@ -888,11 +885,6 @@ def postprocessing( medoid_idx.index.values.astype(str), medoid_idx.values.astype(int) ) - # add the not clustered buses to the busmap - if settings["exclusion_area"]: - for bus in busmap_area.index: - busmap[bus] = busmap_area[bus] - network, busmap = adjust_no_electric_network( etrago, busmap, cluster_met=method ) @@ -1096,9 +1088,19 @@ def run_spatial_clustering(self): if not self.args["network_clustering"]["k_elec_busmap"]: logger.info("Start k-means Clustering") - busmap = kmean_clustering( + busmap_elec = kmean_clustering( self, elec_network, weight, n_clusters ) + busmap_area = kmean_clustering( + self, + network_area, + weight_area, + self.args["network_clustering"]["cluster_exclusion_area"], + ) + busmap_area = ( + busmap_area.astype(int) + busmap_elec.apply(int).max() + 1 + ).apply(str) + busmap = pd.concat([busmap_elec, busmap_area]) medoid_idx = pd.Series(dtype=str) else: busmap = pd.Series(dtype=str) @@ -1140,7 +1142,7 @@ def run_spatial_clustering(self): medoid_idx = pd.Series(dtype=str) clustering, busmap = postprocessing( - self, busmap, busmap_foreign, medoid_idx, busmap_area + self, busmap, busmap_foreign, medoid_idx ) self.update_busmap(busmap) From 4acd3713b3dd84382858f295db4d6938648bfdd8 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 8 Nov 2023 11:08:12 +0100 Subject: [PATCH 021/109] include cluster_exclusion_area in args --- etrago/appl.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/etrago/appl.py b/etrago/appl.py index 6f578b5a4..34d52501c 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -113,6 +113,7 @@ "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False "exclusion_area": False, # False, path to shapefile or list of nuts names of not cluster area + "cluster_exclusion_area": False, # False or number of buses. "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 17, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False @@ -446,6 +447,11 @@ def run_etrago(args, json_path): area.The areas can be provided in two ways: list of nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string with a path to a shape file. + Default: False. + * "cluster_exclusion_area": False, int + Number of buses to cluster all the electrical buses in the + exclusion area. Method provided in the arg "method" is used. + Default: False. * "method_gas" : str Method used for gas clustering. You can choose between two clustering methods: From 06e34760f4091beb2aff7dc560998c62f8dfa351 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Tue, 14 Nov 2023 16:31:04 +0100 Subject: [PATCH 022/109] create function include_busmap_area --- etrago/appl.py | 8 ++-- etrago/cluster/electrical.py | 80 +++++++++++++++++++++++------------- etrago/cluster/spatial.py | 12 +++--- 3 files changed, 61 insertions(+), 39 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 34d52501c..c8c0d207f 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -112,8 +112,8 @@ "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False - "exclusion_area": False, # False, path to shapefile or list of nuts names of not cluster area - "cluster_exclusion_area": False, # False or number of buses. + "interest_area": False, # False, path to shapefile or list of nuts names of not cluster area + "cluster_interest_area": False, # False or number of buses. "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 17, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False @@ -440,7 +440,7 @@ def run_etrago(args, json_path): as well and included in number of clusters specified through ``'n_clusters_AC'``. Default: False. - * "exclusion_area": False, list, string + * "interest_area": False, list, string Area of especial interest that will be not clustered. It is by default set to false. When an exclusion area is provided, the given value for n_clusters_AC will mean the total of AC buses outside the @@ -448,7 +448,7 @@ def run_etrago(args, json_path): nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string with a path to a shape file. Default: False. - * "cluster_exclusion_area": False, int + * "cluster_interest_area": False, int Number of buses to cluster all the electrical buses in the exclusion area. Method provided in the arg "method" is used. Default: False. diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 06db0dd0b..af0848520 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -478,7 +478,7 @@ def select_elec_network(etrago): number of clusters used in the clustering process. area_network : pypsa.Network Contains the electric network in the area of interest defined in - network_clustering - exclusion_area. + network_clustering - interest_area. """ settings = etrago.args["network_clustering"] @@ -1054,6 +1054,49 @@ def calc_availability_factor(gen): return weight +def include_busmap_area(etrago, busmap, medoid_idx, network_area, weight_area): + args = etrago.args["network_clustering"] + + if not args["interest_area"]: + return busmap, medoid_idx + if not args["cluster_interest_area"]: + for bus in network_area.buses.index: + busmap[bus] = bus + return busmap, medoid_idx + else: + if args["method"] == "kmeans": + busmap_area = kmean_clustering( + etrago, + network_area, + weight_area, + args["cluster_interest_area"], + ) + busmap_area = ( + busmap_area.astype(int) + busmap.apply(int).max() + 1 + ).apply(str) + + if args["method"] == "kmedoids-dijkstra": + busmap_area, medoid_idx_area = kmedoids_dijkstra_clustering( + etrago, + network_area.buses, + network_area.lines, + weight_area, + args["cluster_interest_area"], + ) + + medoid_idx_area.index = ( + medoid_idx_area.index.astype(int) + busmap.apply(int).max() + 1 + ) + busmap_area = ( + busmap_area.astype(int) + busmap.apply(int).max() + 1 + ).apply(str) + medoid_idx = pd.concat([medoid_idx, medoid_idx_area]) + + busmap = pd.concat([busmap, busmap_area]) + + return busmap, medoid_idx + + def run_spatial_clustering(self): """ Main method for running spatial clustering on the electrical network. @@ -1091,17 +1134,11 @@ def run_spatial_clustering(self): busmap_elec = kmean_clustering( self, elec_network, weight, n_clusters ) - busmap_area = kmean_clustering( - self, - network_area, - weight_area, - self.args["network_clustering"]["cluster_exclusion_area"], - ) - busmap_area = ( - busmap_area.astype(int) + busmap_elec.apply(int).max() + 1 - ).apply(str) - busmap = pd.concat([busmap_elec, busmap_area]) medoid_idx = pd.Series(dtype=str) + busmap, medoid_idx = include_busmap_area( + self, busmap_elec, medoid_idx, network_area, weight_area + ) + else: busmap = pd.Series(dtype=str) medoid_idx = pd.Series(dtype=str) @@ -1110,7 +1147,7 @@ def run_spatial_clustering(self): if not self.args["network_clustering"]["k_elec_busmap"]: logger.info("Start k-medoids Dijkstra Clustering") - busmap_elec, medoid_idx_elec = kmedoids_dijkstra_clustering( + busmap_elec, medoid_idx = kmedoids_dijkstra_clustering( self, elec_network.buses, elec_network.lines, @@ -1118,24 +1155,9 @@ def run_spatial_clustering(self): n_clusters, ) - busmap_area, medoid_idx_area = kmedoids_dijkstra_clustering( - self, - network_area.buses, - network_area.lines, - weight_area, - self.args["network_clustering"]["cluster_exclusion_area"], - ) - - medoid_idx_area.index = ( - medoid_idx_area.index.astype(int) - + busmap_elec.apply(int).max() - + 1 + busmap, medoid_idx = include_busmap_area( + self, busmap_elec, medoid_idx, network_area, weight_area ) - busmap_area = ( - busmap_area.astype(int) + busmap_elec.apply(int).max() + 1 - ).apply(str) - busmap = pd.concat([busmap_elec, busmap_area]) - medoid_idx = pd.concat([medoid_idx_elec, medoid_idx_area]) else: busmap = pd.Series(dtype=str) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 1377c5ec5..3b1e26974 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -779,20 +779,20 @@ def find_buses_area(etrago, carrier): """ settings = etrago.args["network_clustering"] - if settings["exclusion_area"]: - if isinstance(settings["exclusion_area"], list): + if settings["interest_area"]: + if isinstance(settings["interest_area"], list): con = etrago.engine query = "SELECT gen, geometry FROM boundaries.vg250_krs" de_areas = gpd.read_postgis(query, con, geom_col="geometry") de_areas = de_areas[ - de_areas["gen"].isin(settings["exclusion_area"]) + de_areas["gen"].isin(settings["interest_area"]) ] - elif isinstance(settings["exclusion_area"], str): - de_areas = gpd.read_file(settings["exclusion_area"]) + elif isinstance(settings["interest_area"], str): + de_areas = gpd.read_file(settings["interest_area"]) else: raise Exception( - "not supported format supplied to 'exclusion_area' argument" + "not supported format supplied to 'interest_area' argument" ) try: From 6d9995582d3bbd8f64bee82b8fe26bf5f45b6e44 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Fri, 22 Mar 2024 14:46:41 +0100 Subject: [PATCH 023/109] hot fix DLR --- etrago/appl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 26a7f86fb..07c24baee 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -743,12 +743,12 @@ def run_etrago(args, json_path): # snapshot clustering etrago.snapshot_clustering() - + breakpoint() # skip snapshots etrago.skip_snapshots() # Temporary drop DLR as it is currently not working with sclopf - if etrago.args["method"] != "lopf": + if etrago.args["method"]["type"] != "lopf": etrago.network.lines_t.s_max_pu = pd.DataFrame( index=etrago.network.snapshots, columns=etrago.network.lines.index, From 033df5006d3fa00f06bccd4435e2da54874abf82 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Fri, 22 Mar 2024 14:47:51 +0100 Subject: [PATCH 024/109] avoid error when no generators --- etrago/cluster/electrical.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 5bcea1afd..0636e4f0a 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -1061,6 +1061,9 @@ def calc_availability_factor(gen): gen = network.generators[network.generators.carrier != "load shedding"][ ["bus", "carrier", "p_nom"] ].copy() + if len(gen) == 0: + return pd.Series() + gen["cf"] = gen.apply(calc_availability_factor, axis=1) gen["weight"] = gen["p_nom"] * gen["cf"] From 3142f152fee80caf27c94115d53573c1435f421c Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Fri, 22 Mar 2024 22:30:12 +0100 Subject: [PATCH 025/109] include busmap_area in general busmap --- etrago/cluster/gas.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index be5d89b51..de4799602 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -312,7 +312,7 @@ def get_h2_clusters(etrago, busmap_ch4): def gas_postprocessing( - etrago, busmap, medoid_idx=None, busmap_area=pd.DataFrame() + etrago, busmap, medoid_idx=None, busmap_area=pd.Series() ): """ Performs the postprocessing for the gas grid clustering based on the @@ -365,6 +365,10 @@ def gas_postprocessing( + "_result.csv" ) + if len(busmap_area) > 0: + for bus_area in busmap_area.values: + busmap[bus_area] = bus_area + if "H2_grid" in etrago.network.buses.carrier.unique(): busmap = get_h2_clusters(etrago, busmap) From fbaee5a789c323b9d73f6570fc202ebbdb05f20d Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Tue, 26 Mar 2024 11:00:32 +0100 Subject: [PATCH 026/109] Revert "delete provisional fixes for status2019 - part2" This reverts commit 90a8cdf1236e0d9dfa24590d11d4170e4443c2d4. --- etrago/appl.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/etrago/appl.py b/etrago/appl.py index 36dfd5887..bbdfdea18 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -704,6 +704,11 @@ def run_etrago(args, json_path): ) # start linear optimal powerflow calculations + + etrago.network.storage_units.cyclic_state_of_charge = True + + etrago.network.lines.loc[etrago.network.lines.r == 0.0, "r"] = 10 + etrago.optimize() # conduct lopf with full complex timeseries for dispatch disaggregation From 3a49e1949e484ab5d4064f1b263aa42d667bb049 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Sun, 31 Mar 2024 16:18:16 +0200 Subject: [PATCH 027/109] use network instead of etrago --- etrago/cluster/spatial.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 497d7c02a..207d2f1bf 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -383,7 +383,7 @@ def shortest_path(paths, graph): return df -def busmap_by_shortest_path(etrago, fromlvl, tolvl, cpu_cores=4): +def busmap_by_shortest_path(network, fromlvl, tolvl, cpu_cores=4): """ Creates a busmap for the EHV-Clustering between voltage levels based on dijkstra shortest path. The result is automatically written to the @@ -413,15 +413,15 @@ def busmap_by_shortest_path(etrago, fromlvl, tolvl, cpu_cores=4): """ # data preperation - s_buses = buses_grid_linked(etrago.network, fromlvl) - lines = connected_grid_lines(etrago.network, s_buses) - transformer = connected_transformer(etrago.network, s_buses) - mask = transformer.bus1.isin(buses_of_vlvl(etrago.network, tolvl)) + s_buses = buses_grid_linked(network, fromlvl) + lines = connected_grid_lines(network, s_buses) + transformer = connected_transformer(network, s_buses) + mask = transformer.bus1.isin(buses_of_vlvl(network, tolvl)) - dc = etrago.network.links[etrago.network.links.carrier == "DC"] + dc = network.links[network.links.carrier == "DC"] dc.index = "DC_" + dc.index lines_plus_dc = pd.concat([lines, dc]) - lines_plus_dc = lines_plus_dc[etrago.network.lines.columns] + lines_plus_dc = lines_plus_dc[network.lines.columns] lines_plus_dc["carrier"] = "AC" # temporary end points, later replaced by bus1 pendant @@ -456,16 +456,16 @@ def busmap_by_shortest_path(etrago, fromlvl, tolvl, cpu_cores=4): df.target = df.target.map( dict( zip( - etrago.network.transformers.bus0, - etrago.network.transformers.bus1, + network.transformers.bus0, + network.transformers.bus1, ) ) ) # append to busmap buses only connected to transformer - transformer = etrago.network.transformers + transformer = network.transformers idx = list( - set(buses_of_vlvl(etrago.network, fromlvl)).symmetric_difference( + set(buses_of_vlvl(network, fromlvl)).symmetric_difference( set(s_buses) ) ) @@ -480,7 +480,7 @@ def busmap_by_shortest_path(etrago, fromlvl, tolvl, cpu_cores=4): df = pd.concat([df, toappend], ignore_index=True, axis=0) # append all other buses - buses = etrago.network.buses[etrago.network.buses.carrier == "AC"] + buses = network.buses[network.buses.carrier == "AC"] mask = buses.index.isin(df.source) assert (buses[~mask].v_nom.astype(int).isin(tolvl)).all() @@ -524,7 +524,7 @@ def busmap_ehv_clustering(etrago): cpu_cores = int(cpu_cores) busmap = busmap_by_shortest_path( - etrago, + etrago.network, fromlvl=[110], tolvl=[220, 380, 400, 450], cpu_cores=cpu_cores, From 3e8d4fe4f472b2403010308703b8207ee14ea5a9 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 1 Apr 2024 12:57:11 +0200 Subject: [PATCH 028/109] move select_elec_network to utilities --- etrago/cluster/electrical.py | 173 +--------------------------- etrago/cluster/gas.py | 6 +- etrago/cluster/spatial.py | 84 +++++--------- etrago/tools/utilities.py | 214 +++++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 228 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 0636e4f0a..f8a20fcdf 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -39,7 +39,6 @@ from etrago.cluster.spatial import ( busmap_ehv_clustering, - find_buses_area, drop_nan_values, group_links, kmean_clustering, @@ -49,7 +48,10 @@ strategies_lines, strategies_one_ports, ) - from etrago.tools.utilities import set_control_strategies + from etrago.tools.utilities import ( + set_control_strategies, + select_elec_network + ) logger = logging.getLogger(__name__) @@ -470,173 +472,6 @@ def ehv_clustering(self): logger.info("Network clustered to EHV-grid") -def select_elec_network(etrago, apply_on="grid_model"): - """ - Creates networks to be used on the clustering based on settings specified - in the args. - - Parameters - ---------- - etrago : Etrago - An instance of the Etrago class - apply_on: str - gives information about the objective of the output network. If - "grid_model" is provided, the value assigned in the args for - ["network_clustering"]["cluster_foreign_AC""] will define if the - foreign buses will be included in the network. if "market_model" is - provided, foreign buses will be always included. - - Returns - ------- - Tuple containing: - elec_network : pypsa.Network - Contains the electric network - n_clusters : int - number of clusters used in the clustering process. - area_network : pypsa.Network - Contains the electric network in the area of interest defined in - network_clustering - interest_area. - """ - settings = etrago.args["network_clustering"] - - if apply_on == "grid_model": - # Find buses in the area that should not be clustered - buses_area = find_buses_area(etrago, "AC") - - elec_network_buses = etrago.network.buses[ - (~etrago.network.buses.index.isin(buses_area)) - & (etrago.network.buses.carrier == "AC") - ].index - - # Exclude foreign buses when it is set to don't include them in clustering - if settings["cluster_foreign_AC"]: - n_clusters = settings["n_clusters_AC"] - else: - foreign_buses = etrago.network.buses[ - (etrago.network.buses.country != "DE") - & (etrago.network.buses.carrier == "AC") - ] - - num_neighboring_country = len( - foreign_buses[ - foreign_buses.index.isin(etrago.network.loads.bus) - ] - ) - - elec_network_buses = elec_network_buses[ - ~elec_network_buses.isin(foreign_buses.index) - ] - n_clusters = settings["n_clusters_AC"] - num_neighboring_country - - elec_network = network_based_on_buses( - etrago.network, elec_network_buses - ) - area_network = network_based_on_buses(etrago.network, buses_area) - - elif apply_on == "market_model": - elec_network_buses = etrago.network_tsa.buses[ - etrago.network_tsa.buses.carrier == "AC" - ].index - elec_network = network_based_on_buses( - etrago.network_tsa, elec_network_buses - ) - area_network = Network() - - else: - logger.warning( - """Parameter apply_on must be either 'grid_model' or 'market_model' - """ - ) - - return elec_network, n_clusters, area_network - - -def network_based_on_buses(network, buses): - """ - Extract all the elements in a network related to the supplied list of - buses and return it like a new network. - - Parameters - ---------- - network : pypsa.Network - Original network that contains the buses of interest and other buses. - buses : Pandas.Series - Series that contains the name of all the buses that the new network - will contain. - - Returns - ------- - elec_network : pypsa.Network - network containing only electrical elements attached to the supplied - list of buses. - - """ - elec_network = network.copy() - elec_network.buses = elec_network.buses[ - elec_network.buses.index.isin(buses) - ] - # Dealing with links - elec_network.links = elec_network.links[ - ( - (elec_network.links.carrier == "AC") - | (elec_network.links.carrier == "DC") - ) - & (elec_network.links.bus0.isin(elec_network.buses.index)) - & (elec_network.links.bus1.isin(elec_network.buses.index)) - ] - - # Dealing with generators - elec_network.generators = elec_network.generators[ - elec_network.generators.bus.isin(elec_network.buses.index) - ] - - for attr in elec_network.generators_t: - elec_network.generators_t[attr] = elec_network.generators_t[attr].loc[ - :, - elec_network.generators_t[attr].columns.isin( - elec_network.generators.index - ), - ] - - # Dealing with loads - elec_network.loads = elec_network.loads[ - elec_network.loads.bus.isin(elec_network.buses.index) - ] - - for attr in elec_network.loads_t: - elec_network.loads_t[attr] = elec_network.loads_t[attr].loc[ - :, - elec_network.loads_t[attr].columns.isin(elec_network.loads.index), - ] - - # Dealing with storage_units - elec_network.storage_units = elec_network.storage_units[ - elec_network.storage_units.bus.isin(elec_network.buses.index) - ] - - for attr in elec_network.storage_units_t: - elec_network.storage_units_t[attr] = elec_network.storage_units_t[ - attr - ].loc[ - :, - elec_network.storage_units_t[attr].columns.isin( - elec_network.storage_units.index - ), - ] - - # Dealing with stores - elec_network.stores = elec_network.stores[ - elec_network.stores.bus.isin(elec_network.buses.index) - ] - - for attr in elec_network.stores_t: - elec_network.stores_t[attr] = elec_network.stores_t[attr].loc[ - :, - elec_network.stores_t[attr].columns.isin( - elec_network.stores.index - ), - ] - return elec_network def unify_foreign_buses(etrago): diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index de4799602..c9b3fb179 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -38,13 +38,15 @@ import pypsa.io as io from etrago.cluster.spatial import ( - find_buses_area, drop_nan_values, group_links, kmedoids_dijkstra_clustering, sum_with_inf, ) - from etrago.tools.utilities import set_control_strategies + from etrago.tools.utilities import ( + find_buses_area, + set_control_strategies, + ) logger = logging.getLogger(__name__) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 207d2f1bf..4db8cc228 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -49,6 +49,7 @@ buses_of_vlvl, connected_grid_lines, connected_transformer, + select_elec_network, ) logger = logging.getLogger(__name__) @@ -515,24 +516,38 @@ def busmap_ehv_clustering(etrago): busmap : dict Maps old bus_ids to new bus_ids. """ - if etrago.args["network_clustering_ehv"]["busmap"] is False: cpu_cores = etrago.args["network_clustering"]["CPU_cores"] if cpu_cores == "max": cpu_cores = mp.cpu_count() else: cpu_cores = int(cpu_cores) - - busmap = busmap_by_shortest_path( - etrago.network, - fromlvl=[110], - tolvl=[220, 380, 400, 450], - cpu_cores=cpu_cores, - ) - pd.DataFrame(busmap.items(), columns=["bus0", "bus1"]).to_csv( - "ehv_elecgrid_busmap_result.csv", - index=False, - ) + + if etrago.args["network_clustering_ehv"]["interest_area"] is False: + busmap = busmap_by_shortest_path( + etrago.network, + fromlvl=[110], + tolvl=[220, 380, 400, 450], + cpu_cores=cpu_cores, + ) + pd.DataFrame(busmap.items(), columns=["bus0", "bus1"]).to_csv( + "ehv_elecgrid_busmap_result.csv", + index=False, + ) + else: + network, _, area = select_elec_network(etrago, apply_on="grid_model-ehv") + busmap = busmap_by_shortest_path( + network, + fromlvl=[110], + tolvl=[220, 380, 400, 450], + cpu_cores=cpu_cores, + ) + for bus in area.buses.index: + busmap[bus] = bus + pd.DataFrame(busmap.items(), columns=["bus0", "bus1"]).to_csv( + "ehv_elecgrid_busmap_result.csv", + index=False, + ) else: busmap = pd.read_csv(etrago.args["network_clustering_ehv"]["busmap"]) busmap = pd.Series( @@ -789,51 +804,6 @@ def kmedoids_dijkstra_clustering( return busmap, medoid_idx -def find_buses_area(etrago, carrier): - """ - Find buses of a specified carrier in a defined area. Usually used to - findout the buses that sould not be clustered. - """ - settings = etrago.args["network_clustering"] - - if settings["interest_area"]: - if isinstance(settings["interest_area"], list): - con = etrago.engine - query = "SELECT gen, geometry FROM boundaries.vg250_krs" - - de_areas = gpd.read_postgis(query, con, geom_col="geometry") - de_areas = de_areas[ - de_areas["gen"].isin(settings["interest_area"]) - ] - elif isinstance(settings["interest_area"], str): - de_areas = gpd.read_file(settings["interest_area"]) - else: - raise Exception( - "not supported format supplied to 'interest_area' argument" - ) - - try: - buses_area = gpd.GeoDataFrame( - etrago.network.buses, geometry="geom", crs=4326 - ) - except: - buses_area = etrago.network.buses[["x", "y", "carrier"]] - buses_area["geom"] = buses_area.apply( - lambda x: Point(x["x"], x["y"]), axis=1 - ) - buses_area = gpd.GeoDataFrame( - buses_area, geometry="geom", crs=4326 - ) - - buses_area = gpd.clip(buses_area, de_areas) - buses_area = buses_area[buses_area.carrier == carrier] - - else: - buses_area = pd.DataFrame() - - return buses_area.index - - def drop_nan_values(network): """ Drops nan values after clustering an replaces output data time series with diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 552055793..51f6210bf 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -2954,3 +2954,217 @@ def manual_fixes_datamodel(etrago): etrago.network.transformers["v_nom"] = etrago.network.buses.loc[ etrago.network.transformers.bus0.values, "v_nom" ].values + + +def select_elec_network(etrago, apply_on="grid_model"): + """ + Creates networks to be used on the clustering based on settings specified + in the args. + + Parameters + ---------- + etrago : Etrago + An instance of the Etrago class + apply_on: str + gives information about the objective of the output network. If + "grid_model" is provided, the value assigned in the args for + ["network_clustering"]["cluster_foreign_AC""] will define if the + foreign buses will be included in the network. if "market_model" is + provided, foreign buses will be always included. + + Returns + ------- + Tuple containing: + elec_network : pypsa.Network + Contains the electric network + n_clusters : int + number of clusters used in the clustering process. + area_network : pypsa.Network + Contains the electric network in the area of interest defined in + network_clustering - interest_area. + """ + settings = etrago.args["network_clustering"] + + if apply_on in["grid_model", "grid_model-ehv"]: + # Find buses in the area that should not be clustered + buses_area = find_buses_area(etrago, "AC") + + elec_network_buses = etrago.network.buses[ + (~etrago.network.buses.index.isin(buses_area)) + & (etrago.network.buses.carrier == "AC") + ].index + if apply_on == "grid_model-ehv": + n_clusters = pd.NA + # Exclude foreign buses when it is set to don't include them in clustering + elif settings["cluster_foreign_AC"]: + n_clusters = settings["n_clusters_AC"] + else: + foreign_buses = etrago.network.buses[ + (etrago.network.buses.country != "DE") + & (etrago.network.buses.carrier == "AC") + ] + + num_neighboring_country = len( + foreign_buses[ + foreign_buses.index.isin(etrago.network.loads.bus) + ] + ) + + elec_network_buses = elec_network_buses[ + ~elec_network_buses.isin(foreign_buses.index) + ] + n_clusters = settings["n_clusters_AC"] - num_neighboring_country + + elec_network = network_based_on_buses( + etrago.network, elec_network_buses + ) + area_network = network_based_on_buses(etrago.network, buses_area) + + elif apply_on == "market_model": + elec_network_buses = etrago.network_tsa.buses[ + etrago.network_tsa.buses.carrier == "AC" + ].index + elec_network = network_based_on_buses( + etrago.network_tsa, elec_network_buses + ) + area_network = pypsa.Network() + + else: + logger.warning( + """Parameter apply_on must be either 'grid_model' or 'market_model' + """ + ) + + return elec_network, n_clusters, area_network + + +def network_based_on_buses(network, buses): + """ + Extract all the elements in a network related to the supplied list of + buses and return it like a new network. + + Parameters + ---------- + network : pypsa.Network + Original network that contains the buses of interest and other buses. + buses : Pandas.Series + Series that contains the name of all the buses that the new network + will contain. + + Returns + ------- + elec_network : pypsa.Network + network containing only electrical elements attached to the supplied + list of buses. + + """ + elec_network = network.copy() + elec_network.buses = elec_network.buses[ + elec_network.buses.index.isin(buses) + ] + # Dealing with links + elec_network.links = elec_network.links[ + ( + (elec_network.links.carrier == "AC") + | (elec_network.links.carrier == "DC") + ) + & (elec_network.links.bus0.isin(elec_network.buses.index)) + & (elec_network.links.bus1.isin(elec_network.buses.index)) + ] + + # Dealing with generators + elec_network.generators = elec_network.generators[ + elec_network.generators.bus.isin(elec_network.buses.index) + ] + + for attr in elec_network.generators_t: + elec_network.generators_t[attr] = elec_network.generators_t[attr].loc[ + :, + elec_network.generators_t[attr].columns.isin( + elec_network.generators.index + ), + ] + + # Dealing with loads + elec_network.loads = elec_network.loads[ + elec_network.loads.bus.isin(elec_network.buses.index) + ] + + for attr in elec_network.loads_t: + elec_network.loads_t[attr] = elec_network.loads_t[attr].loc[ + :, + elec_network.loads_t[attr].columns.isin(elec_network.loads.index), + ] + + # Dealing with storage_units + elec_network.storage_units = elec_network.storage_units[ + elec_network.storage_units.bus.isin(elec_network.buses.index) + ] + + for attr in elec_network.storage_units_t: + elec_network.storage_units_t[attr] = elec_network.storage_units_t[ + attr + ].loc[ + :, + elec_network.storage_units_t[attr].columns.isin( + elec_network.storage_units.index + ), + ] + + # Dealing with stores + elec_network.stores = elec_network.stores[ + elec_network.stores.bus.isin(elec_network.buses.index) + ] + + for attr in elec_network.stores_t: + elec_network.stores_t[attr] = elec_network.stores_t[attr].loc[ + :, + elec_network.stores_t[attr].columns.isin( + elec_network.stores.index + ), + ] + return elec_network + +def find_buses_area(etrago, carrier): + """ + Find buses of a specified carrier in a defined area. Usually used to + findout the buses that sould not be clustered. + """ + settings = etrago.args["network_clustering"] + + if settings["interest_area"]: + if isinstance(settings["interest_area"], list): + con = etrago.engine + query = "SELECT gen, geometry FROM boundaries.vg250_krs" + + de_areas = gpd.read_postgis(query, con, geom_col="geometry") + de_areas = de_areas[ + de_areas["gen"].isin(settings["interest_area"]) + ] + elif isinstance(settings["interest_area"], str): + de_areas = gpd.read_file(settings["interest_area"]) + else: + raise Exception( + "not supported format supplied to 'interest_area' argument" + ) + + try: + buses_area = gpd.GeoDataFrame( + etrago.network.buses, geometry="geom", crs=4326 + ) + except: + buses_area = etrago.network.buses[["x", "y", "carrier"]] + buses_area["geom"] = buses_area.apply( + lambda x: Point(x["x"], x["y"]), axis=1 + ) + buses_area = gpd.GeoDataFrame( + buses_area, geometry="geom", crs=4326 + ) + + buses_area = gpd.clip(buses_area, de_areas) + buses_area = buses_area[buses_area.carrier == carrier] + + else: + buses_area = pd.DataFrame() + + return buses_area.index \ No newline at end of file From 8bbfff93bf07a030ad8db1e8b630c21d01751a41 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 1 Apr 2024 12:58:45 +0200 Subject: [PATCH 029/109] keep just lines connecting remaining buses --- etrago/cluster/electrical.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index f8a20fcdf..9d56713ab 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -279,7 +279,6 @@ def cluster_on_extra_high_voltage(etrago, busmap, with_time=True): busmap : dict Maps old bus_ids to new bus_ids including all sectors. """ - network_c = Network() network, busmap = adjust_no_electric_network( @@ -299,7 +298,7 @@ def cluster_on_extra_high_voltage(etrago, busmap, with_time=True): # keep attached lines lines = network.lines.copy() - mask = lines.bus0.isin(buses.index) + mask = lines.bus0.isin(buses.index) & lines.bus1.isin(buses.index) lines = lines.loc[mask, :] # keep attached transformer From d05e051252bd613fa31c4911e7651922938a0a93 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 1 Apr 2024 13:18:01 +0200 Subject: [PATCH 030/109] Using black --- etrago/cluster/electrical.py | 4 +--- etrago/cluster/gas.py | 1 - etrago/cluster/spatial.py | 10 +++++----- etrago/tools/utilities.py | 5 +++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 9d56713ab..7503d21ab 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -50,7 +50,7 @@ ) from etrago.tools.utilities import ( set_control_strategies, - select_elec_network + select_elec_network, ) logger = logging.getLogger(__name__) @@ -471,8 +471,6 @@ def ehv_clustering(self): logger.info("Network clustered to EHV-grid") - - def unify_foreign_buses(etrago): """ Unifies foreign AC buses into clusters using the k-medoids algorithm with diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index 8578ed96e..4230b38d5 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -239,7 +239,6 @@ def weighting_for_scenario(ch4_buses, save=None): return network_ch4, weight_ch4.squeeze(axis=1), n_clusters, busmap_area - def kmean_clustering_gas(etrago, network_ch4, weight, n_clusters): """ Performs K-means clustering on the gas network data in the given diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 3245e524d..45ca52f09 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -466,9 +466,7 @@ def busmap_by_shortest_path(network, fromlvl, tolvl, cpu_cores=4): # append to busmap buses only connected to transformer transformer = network.transformers idx = list( - set(buses_of_vlvl(network, fromlvl)).symmetric_difference( - set(s_buses) - ) + set(buses_of_vlvl(network, fromlvl)).symmetric_difference(set(s_buses)) ) mask = transformer.bus0.isin(idx) @@ -522,7 +520,7 @@ def busmap_ehv_clustering(etrago): cpu_cores = mp.cpu_count() else: cpu_cores = int(cpu_cores) - + if etrago.args["network_clustering_ehv"]["interest_area"] is False: busmap = busmap_by_shortest_path( etrago.network, @@ -535,7 +533,9 @@ def busmap_ehv_clustering(etrago): index=False, ) else: - network, _, area = select_elec_network(etrago, apply_on="grid_model-ehv") + network, _, area = select_elec_network( + etrago, apply_on="grid_model-ehv" + ) busmap = busmap_by_shortest_path( network, fromlvl=[110], diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 51f6210bf..47c9b3a10 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -2985,7 +2985,7 @@ def select_elec_network(etrago, apply_on="grid_model"): """ settings = etrago.args["network_clustering"] - if apply_on in["grid_model", "grid_model-ehv"]: + if apply_on in ["grid_model", "grid_model-ehv"]: # Find buses in the area that should not be clustered buses_area = find_buses_area(etrago, "AC") @@ -3125,6 +3125,7 @@ def network_based_on_buses(network, buses): ] return elec_network + def find_buses_area(etrago, carrier): """ Find buses of a specified carrier in a defined area. Usually used to @@ -3167,4 +3168,4 @@ def find_buses_area(etrago, carrier): else: buses_area = pd.DataFrame() - return buses_area.index \ No newline at end of file + return buses_area.index From 8704ea1796389b0331bb2d2bbcbda326fb0adc41 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 1 Apr 2024 15:35:46 +0200 Subject: [PATCH 031/109] fix gas clustering when interest_area --- etrago/cluster/gas.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index 4230b38d5..919ae075e 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -298,9 +298,12 @@ def get_h2_clusters(etrago, busmap_ch4): to its corresponding cluster ID. """ # Mapping of H2 buses to new CH4 cluster IDs + map_ch4_h2 = etrago.ch4_h2_mapping[ + etrago.ch4_h2_mapping.index.isin(busmap_ch4.index) + ] busmap_h2 = pd.Series( - busmap_ch4.loc[etrago.ch4_h2_mapping.index].values, - index=etrago.ch4_h2_mapping.values, + busmap_ch4.loc[map_ch4_h2.index].values, + index=map_ch4_h2.values, ) # Create unique H2 cluster IDs @@ -368,13 +371,13 @@ def gas_postprocessing( + "_result.csv" ) + if "H2_grid" in etrago.network.buses.carrier.unique(): + busmap = get_h2_clusters(etrago, busmap) + if len(busmap_area) > 0: for bus_area in busmap_area.values: busmap[bus_area] = bus_area - if "H2_grid" in etrago.network.buses.carrier.unique(): - busmap = get_h2_clusters(etrago, busmap) - # Add all other buses to busmap missing_idx = list( etrago.network.buses[ From b327cf01a201fb640e8e8e819b1c7b23188af7ec Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 4 Apr 2024 11:24:30 +0200 Subject: [PATCH 032/109] Make possible to cluster just the interest area --- etrago/cluster/spatial.py | 11 +++++++++++ etrago/tools/utilities.py | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 45ca52f09..57d483e7f 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -748,6 +748,17 @@ def kmedoids_dijkstra_clustering( settings = etrago.args["network_clustering"] + if n_clusters is False: + busmap = pd.Series( + range(len(buses)), index=buses.index, name="final_assignment" + ) + busmap.index.name = "bus_id" + busmap = busmap.apply(str) + + medoid_idx = pd.Series(busmap.index, index=busmap.values, name=0) + + return busmap, medoid_idx + # n_jobs was deprecated for the function fit(). scikit-learn recommends # to use threadpool_limits: # https://scikit-learn.org/stable/computing/parallelism.html diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 47c9b3a10..d7b995b3f 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3013,7 +3013,16 @@ def select_elec_network(etrago, apply_on="grid_model"): elec_network_buses = elec_network_buses[ ~elec_network_buses.isin(foreign_buses.index) ] - n_clusters = settings["n_clusters_AC"] - num_neighboring_country + if settings["n_clusters_AC"] is False: + n_clusters = False + else: + n_clusters = ( + settings["n_clusters_AC"] - num_neighboring_country + ) + assert ( + n_clusters > 1 + ), f"""'n_clusters_AC' must be greater than the number of foreign + countries({num_neighboring_country})""" elec_network = network_based_on_buses( etrago.network, elec_network_buses From 0bab5d45787db73e24885b2dd7b1385ee4916d70 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 4 Apr 2024 11:41:39 +0200 Subject: [PATCH 033/109] update args and parameters description --- etrago/appl.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 090697c27..30a00900a 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -52,7 +52,7 @@ "db": "egon-data", # database session "gridversion": None, # None for model_draft or Version number "method": { # Choose method and settings for optimization - "type": "market_grid", # type of optimization, 'lopf', 'sclopf' or 'market_grid' + "type": "lopf", # type of optimization, 'lopf', 'sclopf' or 'market_grid' "n_iter": 1, # abort criterion of iterative optimization, 'n_iter' or 'threshold' "pyomo": True, # set if pyomo is used for model building "formulation": "pyomo", @@ -112,6 +112,7 @@ "network_clustering_ehv": { "active": False, # choose if clustering of HV buses to EHV buses is activated "busmap": False, # False or path to stored busmap + "interest_area": False, # False, path to shapefile or list of nuts names of not cluster area }, "network_clustering": { "active": True, # choose if clustering is activated @@ -119,7 +120,7 @@ "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False "interest_area": False, # False, path to shapefile or list of nuts names of not cluster area - "cluster_interest_area": False, # False or number of buses. + "cluster_interest_area": 30, # False or number of buses. "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 14, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False @@ -397,6 +398,7 @@ def run_etrago(args, json_path): connected lines are merged. This reduces the spatial complexity without losing any accuracy. Default: True. + network_clustering_ehv : dict Choose if you want to apply an extra high voltage clustering to the electrical network. @@ -412,6 +414,13 @@ def run_etrago(args, json_path): a new busmap must be calculated. False or path to the busmap in csv format should be given. Default: False + * "interest_area": False, list, string + Area of especial interest that will be not clustered. It is by + default set to false. When an interest_area is provided, the given + value for n_clusters_AC will mean the total of AC buses outside the + area.The area can be provided in two ways: list of + nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string + with a path to a shape file (.shp). network_clustering : dict Choose if you want to apply a clustering of all network buses and @@ -428,12 +437,14 @@ def run_etrago(args, json_path): * "kmeans": considers geographical locations of buses * "kmedoids-dijkstra": considers electrical distances between buses - Default: "kmedoids-dijkstra". - * "n_clusters_AC" : int + * "n_clusters_AC" : int, False Defines total number of resulting AC nodes including DE and foreign nodes if `cluster_foreign_AC` is set to True, otherwise only DE - nodes. + nodes. When using the interest_area parameter, n_clusters_AC could + be set to False, which means that only the buses inside the + provided area are clustered. + Default: 30. * "cluster_foreign_AC" : bool If set to False, the AC buses outside Germany are not clustered @@ -444,11 +455,11 @@ def run_etrago(args, json_path): Default: False. * "interest_area": False, list, string Area of especial interest that will be not clustered. It is by - default set to false. When an exclusion area is provided, the given + default set to false. When an interest_area is provided, the given value for n_clusters_AC will mean the total of AC buses outside the - area.The areas can be provided in two ways: list of + area.The area can be provided in two ways: list of nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string - with a path to a shape file. + with a path to a shape file (.shp). Default: False. * "cluster_interest_area": False, int Number of buses to cluster all the electrical buses in the @@ -460,7 +471,6 @@ def run_etrago(args, json_path): * "kmeans": considers geographical locations of buses * "kmedoids-dijkstra": considers 'electrical' distances between buses - Default: "kmedoids-dijkstra". * "n_clusters_gas" : int Defines total number of resulting CH4 nodes including DE and From 6dfb1d5c1e4d8d145ab4fc29c0e8cd392290d814 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Sun, 21 Apr 2024 22:00:37 +0200 Subject: [PATCH 034/109] unify interest_area in args --- etrago/cluster/electrical.py | 2 +- etrago/cluster/spatial.py | 6 +++++- etrago/tools/utilities.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 7503d21ab..b096d1e34 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -934,7 +934,7 @@ def calc_availability_factor(gen): def include_busmap_area(etrago, busmap, medoid_idx, network_area, weight_area): args = etrago.args["network_clustering"] - if not args["interest_area"]: + if not etrago.args["interest_area"]: return busmap, medoid_idx if not args["cluster_interest_area"]: for bus in network_area.buses.index: diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 57d483e7f..7806151be 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -118,6 +118,10 @@ def strategies_buses(): def strategies_lines(): return { "geom": nan_links, + "cables": np.sum, + "topo": "first", + "country": "first", + "total_cables": np.sum, } @@ -521,7 +525,7 @@ def busmap_ehv_clustering(etrago): else: cpu_cores = int(cpu_cores) - if etrago.args["network_clustering_ehv"]["interest_area"] is False: + if etrago.args["interest_area"] is False: busmap = busmap_by_shortest_path( etrago.network, fromlvl=[110], diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index d7b995b3f..d6853d3a6 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3140,7 +3140,7 @@ def find_buses_area(etrago, carrier): Find buses of a specified carrier in a defined area. Usually used to findout the buses that sould not be clustered. """ - settings = etrago.args["network_clustering"] + settings = etrago.args if settings["interest_area"]: if isinstance(settings["interest_area"], list): From 8591d81e2c9d693389e39c5951c6007093a72c8e Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 22 Apr 2024 07:03:42 +0200 Subject: [PATCH 035/109] update args --- etrago/appl.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 30a00900a..644508c70 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -109,18 +109,17 @@ "extra_functionality": {}, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses + "interest_area": False, # False, path to shapefile or list of nuts names of the area that is excluded from the clustering "network_clustering_ehv": { - "active": False, # choose if clustering of HV buses to EHV buses is activated + "active": True, # choose if clustering of HV buses to EHV buses is activated "busmap": False, # False or path to stored busmap - "interest_area": False, # False, path to shapefile or list of nuts names of not cluster area }, "network_clustering": { "active": True, # choose if clustering is activated "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False - "interest_area": False, # False, path to shapefile or list of nuts names of not cluster area - "cluster_interest_area": 30, # False or number of buses. + "cluster_interest_area": False, # False or number of buses. "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 14, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False @@ -398,6 +397,14 @@ def run_etrago(args, json_path): connected lines are merged. This reduces the spatial complexity without losing any accuracy. Default: True. + + interest_area: False, list, string + Area of especial interest that will be not clustered. It is by + default set to false. When an interest_area is provided, the given + value for n_clusters_AC will mean the total of AC buses outside the + area.The area can be provided in two ways: list of + nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string + with a path to a shape file (.shp). network_clustering_ehv : dict Choose if you want to apply an extra high voltage clustering to the @@ -414,13 +421,6 @@ def run_etrago(args, json_path): a new busmap must be calculated. False or path to the busmap in csv format should be given. Default: False - * "interest_area": False, list, string - Area of especial interest that will be not clustered. It is by - default set to false. When an interest_area is provided, the given - value for n_clusters_AC will mean the total of AC buses outside the - area.The area can be provided in two ways: list of - nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string - with a path to a shape file (.shp). network_clustering : dict Choose if you want to apply a clustering of all network buses and @@ -462,8 +462,9 @@ def run_etrago(args, json_path): with a path to a shape file (.shp). Default: False. * "cluster_interest_area": False, int - Number of buses to cluster all the electrical buses in the - exclusion area. Method provided in the arg "method" is used. + Number of buses to cluster all the electrical buses in the area + of interest. Method provided in the arg "method" is used. If + it is set to False, the area of interest is not clustered. Default: False. * "method_gas" : str Method used for gas clustering. You can choose between two @@ -710,7 +711,7 @@ def run_etrago(args, json_path): # snapshot clustering etrago.snapshot_clustering() - breakpoint() + # skip snapshots etrago.skip_snapshots() From 2ff73a1584dd7ff127a0b1aad69c78edd88c5eac Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 22 Apr 2024 07:30:34 +0200 Subject: [PATCH 036/109] update args description --- etrago/appl.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 644508c70..026fd4ef8 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -109,7 +109,7 @@ "extra_functionality": {}, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses - "interest_area": False, # False, path to shapefile or list of nuts names of the area that is excluded from the clustering + "interest_area": False, # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. "network_clustering_ehv": { "active": True, # choose if clustering of HV buses to EHV buses is activated "busmap": False, # False or path to stored busmap @@ -399,12 +399,13 @@ def run_etrago(args, json_path): Default: True. interest_area: False, list, string - Area of especial interest that will be not clustered. It is by - default set to false. When an interest_area is provided, the given - value for n_clusters_AC will mean the total of AC buses outside the - area.The area can be provided in two ways: list of - nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string - with a path to a shape file (.shp). + Area of especial interest that will be not clustered, except when + n_cluster_interest_area is provided. It is by default set to false. + When an interest_area is provided, the given value for n_clusters_AC + will mean the total of AC buses outside the area.The area can be + provided in two ways: list of nuts names e.G. + ["Cuxhaven", "Bremerhaven", "Bremen"] or a string with a path to a + shape file (.shp). network_clustering_ehv : dict Choose if you want to apply an extra high voltage clustering to the From 0cb1fa618a4699827c03a4e307a5f3d4dae82a94 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Mon, 22 Apr 2024 07:31:54 +0200 Subject: [PATCH 037/109] rename cluster_interest_area --- etrago/appl.py | 15 ++++----------- etrago/cluster/electrical.py | 6 +++--- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 026fd4ef8..0a50b98cc 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -117,9 +117,9 @@ "network_clustering": { "active": True, # choose if clustering is activated "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra - "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign) + "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign-interest_area) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False - "cluster_interest_area": False, # False or number of buses. + "n_cluster_interest_area": False, # False or number of buses. "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 14, # total number of resulting CH4 nodes (DE+foreign) "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False @@ -454,15 +454,8 @@ def run_etrago(args, json_path): as well and included in number of clusters specified through ``'n_clusters_AC'``. Default: False. - * "interest_area": False, list, string - Area of especial interest that will be not clustered. It is by - default set to false. When an interest_area is provided, the given - value for n_clusters_AC will mean the total of AC buses outside the - area.The area can be provided in two ways: list of - nuts names e.G. ["Cuxhaven", "Bremerhaven", "Bremen"] or a string - with a path to a shape file (.shp). - Default: False. - * "cluster_interest_area": False, int + + * "n_cluster_interest_area": False, int Number of buses to cluster all the electrical buses in the area of interest. Method provided in the arg "method" is used. If it is set to False, the area of interest is not clustered. diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index b096d1e34..2bbd8070c 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -936,7 +936,7 @@ def include_busmap_area(etrago, busmap, medoid_idx, network_area, weight_area): if not etrago.args["interest_area"]: return busmap, medoid_idx - if not args["cluster_interest_area"]: + if not args["n_cluster_interest_area"]: for bus in network_area.buses.index: busmap[bus] = bus return busmap, medoid_idx @@ -946,7 +946,7 @@ def include_busmap_area(etrago, busmap, medoid_idx, network_area, weight_area): etrago, network_area, weight_area, - args["cluster_interest_area"], + args["n_cluster_interest_area"], ) busmap_area = ( busmap_area.astype(int) + busmap.apply(int).max() + 1 @@ -958,7 +958,7 @@ def include_busmap_area(etrago, busmap, medoid_idx, network_area, weight_area): network_area.buses, network_area.lines, weight_area, - args["cluster_interest_area"], + args["n_cluster_interest_area"], ) medoid_idx_area.index = ( From e8f0fcef82a406d76b5b572f2300d54961da7345 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Tue, 23 Apr 2024 06:45:11 +0200 Subject: [PATCH 038/109] include interest area in args json --- etrago/args.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etrago/args.json b/etrago/args.json index 8dbbc3753..c1befcade 100644 --- a/etrago/args.json +++ b/etrago/args.json @@ -53,6 +53,7 @@ "generator_noise": 789456, "extra_functionality": {}, "delete_dispensable_ac_buses": true, + "interest_area": false, "network_clustering_ehv": { "active": false, "busmap": false @@ -62,6 +63,7 @@ "method": "kmedoids-dijkstra", "n_clusters_AC": 30, "cluster_foreign_AC": false, + "n_cluster_interest_area": false, "method_gas": "kmedoids-dijkstra", "n_clusters_gas": 17, "cluster_foreign_gas": false, From 7fa846e97b16bf5466c1c50233a6aeb5da8c82d3 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Tue, 23 Apr 2024 06:46:29 +0200 Subject: [PATCH 039/109] make ehv clustering off again by default --- etrago/appl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etrago/appl.py b/etrago/appl.py index 0a50b98cc..768f80c16 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -111,7 +111,7 @@ "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses "interest_area": False, # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. "network_clustering_ehv": { - "active": True, # choose if clustering of HV buses to EHV buses is activated + "active": False, # choose if clustering of HV buses to EHV buses is activated "busmap": False, # False or path to stored busmap }, "network_clustering": { From 4b464fffc20ccef2759f5c509405f21d2a1358ec Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Tue, 23 Apr 2024 07:02:09 +0200 Subject: [PATCH 040/109] flake8 fixes --- etrago/cluster/spatial.py | 2 -- etrago/tools/utilities.py | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 7806151be..0dbbabe32 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -35,10 +35,8 @@ flatten_multiindex, get_clustering_from_busmap, ) - from shapely.geometry import Point from sklearn.cluster import KMeans from threadpoolctl import threadpool_limits - import geopandas as gpd import networkx as nx import numpy as np import pandas as pd diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index d6853d3a6..7176a70e2 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -2995,7 +2995,7 @@ def select_elec_network(etrago, apply_on="grid_model"): ].index if apply_on == "grid_model-ehv": n_clusters = pd.NA - # Exclude foreign buses when it is set to don't include them in clustering + #Exclude foreign buses when set to don't include them in clustering elif settings["cluster_foreign_AC"]: n_clusters = settings["n_clusters_AC"] else: @@ -3021,8 +3021,8 @@ def select_elec_network(etrago, apply_on="grid_model"): ) assert ( n_clusters > 1 - ), f"""'n_clusters_AC' must be greater than the number of foreign - countries({num_neighboring_country})""" + ), f"""'n_clusters_AC' must be greater than the number of + foreign countries({num_neighboring_country})""" elec_network = network_based_on_buses( etrago.network, elec_network_buses From 05baf9ee9505eaa85f0ce6ae69b8a697be5c9a27 Mon Sep 17 00:00:00 2001 From: birgits Date: Sun, 5 May 2024 16:22:49 +0200 Subject: [PATCH 041/109] Add option to plot interest area --- etrago/tools/plot.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/etrago/tools/plot.py b/etrago/tools/plot.py index d35679857..7394ada31 100644 --- a/etrago/tools/plot.py +++ b/etrago/tools/plot.py @@ -26,6 +26,7 @@ import os from etrago.execute import import_gen_from_links +from etrago.tools.utilities import find_buses_area from matplotlib import pyplot as plt from matplotlib.legend_handler import HandlerPatch from matplotlib.patches import Circle, Ellipse @@ -2467,6 +2468,8 @@ def plot_grid( * 'q_flow_max': maximal reactive flows * 'dlr': energy above nominal capacity * 'grey': plot all lines and DC links grey colored + * 'interest_area': plot all AC buses inside the interest area larger than buses + outside the interest area bus_sizes : float, optional Size of buses. The default is 0.001. @@ -2842,6 +2845,20 @@ def plot_grid( bus_sizes[bus_sizes != "AC"] = 0 bus_sizes[bus_sizes == "AC"] = 1 * bus_scaling bus_scaling = bus_sizes + elif bus_colors == "interest_area": + bus_scaling = bus_sizes + # only plot AC buses + bus_sizes = pd.Series( + data=network.buses.carrier, index=network.buses.index + ) + bus_sizes[bus_sizes != "AC"] = 0 + bus_sizes[bus_sizes == "AC"] = 1 * bus_scaling + # only plot buses inside interest area + buses_interest_area = find_buses_area(self, "AC") + buses_outside = [_ for _ in bus_sizes.index if _ not in buses_interest_area] + bus_sizes.loc[buses_outside] = bus_sizes.loc[buses_outside] * 0.3 + bus_scaling = bus_sizes + bus_colors = coloring()["AC"] else: logger.warning("bus_color {} undefined".format(bus_colors)) From 47eef9035c89cc12be14f1ea7d64f606edba033b Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Sat, 8 Jun 2024 12:13:04 +0200 Subject: [PATCH 042/109] Drop isolated buses when lines are decomissioned --- etrago/tools/io.py | 92 +++++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/etrago/tools/io.py b/etrago/tools/io.py index 1065b5c81..2e36f0e71 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -855,40 +855,72 @@ def decommissioning(self, **kwargs): """ if self.args["scn_decommissioning"] is not None: - if self.args["gridversion"] is None: - ormclass = getattr( - import_module("egoio.db_tables.model_draft"), - "EgoGridPfHvExtensionLine", - ) - else: - ormclass = getattr( - import_module("egoio.db_tables.grid"), "EgoPfHvExtensionLine" + for i in range(len(self.args["scn_extension"])): + scn_decom = self.args["scn_extension"][i] + + df_decommisionning = pd.read_sql( + f""" + SELECT * FROM + grid.egon_etrago_extension_line + WHERE scn_name = 'decomissioining_{scn_decom}' + """, + self.session.bind ) - query = self.session.query(ormclass).filter( - ormclass.scn_name - == "decommissioning_" + self.args["scn_decommissioning"] - ) + self.network.mremove( + "Line", + df_decommisionning.line_id.astype(str).values, + + ) + + # buses only between removed lines + candidates = pd.concat([df_decommisionning.bus0, + df_decommisionning.bus1]) + candidates.drop_duplicates(inplace=True, keep="first") + candidates = candidates.astype(str) + + # Drop buses that are connecting other lines + candidates = candidates[~candidates.isin(self.network.lines.bus0)] + candidates = candidates[~candidates.isin(self.network.lines.bus1)] + + # Drop buses that are connection other DC-lines + candidates = candidates[~candidates.isin(self.dc_lines().bus0)] + candidates = candidates[~candidates.isin(self.dc_lines().bus1)] + + drop_buses = self.network.buses[ + (self.network.buses.index.isin(candidates.values)) & + (self.network.buses.country=="DE")] + + drop_links = self.network.links[ + (self.network.links.bus0.isin(drop_buses.index)) | + (self.network.links.bus1.isin(drop_buses.index))].index + + drop_trafos = self.network.transformers[ + (self.network.transformers.bus0.isin(drop_buses.index)) | + (self.network.transformers.bus1.isin(drop_buses.index))].index + + drop_su = self.network.storage_units[ + self.network.storage_units.bus.isin(candidates.values)].index + + self.network.mremove( + "StorageUnit", + drop_su + ) - df_decommisionning = pd.read_sql( - query.statement, self.session.bind, index_col="line_id" - ) - df_decommisionning.index = df_decommisionning.index.astype(str) - - for idx, row in self.network.lines.iterrows(): - if (row["s_nom_min"] != 0) & ( - row["scn_name"] - == "extension_" + self.args["scn_decommissioning"] - ): - self.network.lines.s_nom_min[ - self.network.lines.index == idx - ] = self.network.lines.s_nom_min - - # Drop decommissioning-lines from existing network - self.network.lines = self.network.lines[ - ~self.network.lines.index.isin(df_decommisionning.index) - ] + self.network.mremove( + "Transformer", + drop_trafos + ) + self.network.mremove( + "Link", + drop_links + ) + + self.network.mremove( + "Bus", + drop_buses.index + ) def distance(x0, x1, y0, y1): """ From 9edf0041aa22c7c40b8cac864aae5f6ee330cb12 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Sat, 8 Jun 2024 12:13:43 +0200 Subject: [PATCH 043/109] Apply black --- etrago/tools/io.py | 55 ++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/etrago/tools/io.py b/etrago/tools/io.py index 2e36f0e71..749c2061e 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -857,70 +857,63 @@ def decommissioning(self, **kwargs): if self.args["scn_decommissioning"] is not None: for i in range(len(self.args["scn_extension"])): scn_decom = self.args["scn_extension"][i] - + df_decommisionning = pd.read_sql( f""" SELECT * FROM grid.egon_etrago_extension_line WHERE scn_name = 'decomissioining_{scn_decom}' """, - self.session.bind + self.session.bind, ) self.network.mremove( "Line", df_decommisionning.line_id.astype(str).values, - - ) - + ) + # buses only between removed lines - candidates = pd.concat([df_decommisionning.bus0, - df_decommisionning.bus1]) + candidates = pd.concat( + [df_decommisionning.bus0, df_decommisionning.bus1] + ) candidates.drop_duplicates(inplace=True, keep="first") candidates = candidates.astype(str) # Drop buses that are connecting other lines candidates = candidates[~candidates.isin(self.network.lines.bus0)] candidates = candidates[~candidates.isin(self.network.lines.bus1)] - + # Drop buses that are connection other DC-lines candidates = candidates[~candidates.isin(self.dc_lines().bus0)] candidates = candidates[~candidates.isin(self.dc_lines().bus1)] drop_buses = self.network.buses[ - (self.network.buses.index.isin(candidates.values)) & - (self.network.buses.country=="DE")] + (self.network.buses.index.isin(candidates.values)) + & (self.network.buses.country == "DE") + ] drop_links = self.network.links[ - (self.network.links.bus0.isin(drop_buses.index)) | - (self.network.links.bus1.isin(drop_buses.index))].index + (self.network.links.bus0.isin(drop_buses.index)) + | (self.network.links.bus1.isin(drop_buses.index)) + ].index drop_trafos = self.network.transformers[ - (self.network.transformers.bus0.isin(drop_buses.index)) | - (self.network.transformers.bus1.isin(drop_buses.index))].index + (self.network.transformers.bus0.isin(drop_buses.index)) + | (self.network.transformers.bus1.isin(drop_buses.index)) + ].index drop_su = self.network.storage_units[ - self.network.storage_units.bus.isin(candidates.values)].index + self.network.storage_units.bus.isin(candidates.values) + ].index - self.network.mremove( - "StorageUnit", - drop_su - ) + self.network.mremove("StorageUnit", drop_su) - self.network.mremove( - "Transformer", - drop_trafos - ) + self.network.mremove("Transformer", drop_trafos) - self.network.mremove( - "Link", - drop_links - ) + self.network.mremove("Link", drop_links) + + self.network.mremove("Bus", drop_buses.index) - self.network.mremove( - "Bus", - drop_buses.index - ) def distance(x0, x1, y0, y1): """ From 45c6be8d5587fa717d4e52d40394795b82ccdf49 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Sat, 8 Jun 2024 12:27:30 +0200 Subject: [PATCH 044/109] Apply black --- etrago/cluster/disaggregation.py | 20 +++++---- etrago/cluster/gas.py | 12 +++--- etrago/cluster/snapshot.py | 72 ++++++++++++++++---------------- etrago/cluster/spatial.py | 6 +-- etrago/tools/calc_results.py | 42 +++++++++---------- etrago/tools/execute.py | 32 +++++++------- etrago/tools/extendable.py | 56 +++++++++++++------------ 7 files changed, 120 insertions(+), 120 deletions(-) diff --git a/etrago/cluster/disaggregation.py b/etrago/cluster/disaggregation.py index babd0b3bc..aa21770b1 100644 --- a/etrago/cluster/disaggregation.py +++ b/etrago/cluster/disaggregation.py @@ -124,9 +124,9 @@ def from_busmap(x): if not left_external_connectors.empty: ca_option = pd.get_option("mode.chained_assignment") pd.set_option("mode.chained_assignment", None) - left_external_connectors.loc[ - :, "bus0" - ] = left_external_connectors.loc[:, "bus0"].apply(from_busmap) + left_external_connectors.loc[:, "bus0"] = ( + left_external_connectors.loc[:, "bus0"].apply(from_busmap) + ) pd.set_option("mode.chained_assignment", ca_option) external_buses = pd.concat( (external_buses, left_external_connectors.bus0) @@ -139,9 +139,9 @@ def from_busmap(x): if not right_external_connectors.empty: ca_option = pd.get_option("mode.chained_assignment") pd.set_option("mode.chained_assignment", None) - right_external_connectors.loc[ - :, "bus1" - ] = right_external_connectors.loc[:, "bus1"].apply(from_busmap) + right_external_connectors.loc[:, "bus1"] = ( + right_external_connectors.loc[:, "bus1"].apply(from_busmap) + ) pd.set_option("mode.chained_assignment", ca_option) external_buses = pd.concat( (external_buses, right_external_connectors.bus1) @@ -738,9 +738,11 @@ def solve_partial_network( weight = reduce( multiply, ( - filtered.loc[:, key] - if not timed(key) - else pn_t[key].loc[:, filtered.index] + ( + filtered.loc[:, key] + if not timed(key) + else pn_t[key].loc[:, filtered.index] + ) for key in weights[s] ), 1, diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index cca92bbf6..a188d2cbe 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -507,12 +507,12 @@ def gas_postprocessing(etrago, busmap, medoid_idx=None): ] if len(h2_idx) > 0: h2_idx = h2_idx.index.tolist()[0] - network_gasgrid_c.buses.at[ - h2_idx, "x" - ] = etrago.network.buses["x"].loc[medoid] - network_gasgrid_c.buses.at[ - h2_idx, "y" - ] = etrago.network.buses["y"].loc[medoid] + network_gasgrid_c.buses.at[h2_idx, "x"] = ( + etrago.network.buses["x"].loc[medoid] + ) + network_gasgrid_c.buses.at[h2_idx, "y"] = ( + etrago.network.buses["y"].loc[medoid] + ) network_gasgrid_c.buses.at[i, "x"] = etrago.network.buses[ "x" ].loc[medoid] diff --git a/etrago/cluster/snapshot.py b/etrago/cluster/snapshot.py index a476e93ac..566ecd1b3 100644 --- a/etrago/cluster/snapshot.py +++ b/etrago/cluster/snapshot.py @@ -373,9 +373,9 @@ def segmentation_extreme_periods( if date < maxi: i = i + 1 else: - timeseries[ - "SegmentDuration_Extreme" - ] = timeseries.index.get_level_values("SegmentDuration") + timeseries["SegmentDuration_Extreme"] = ( + timeseries.index.get_level_values("SegmentDuration") + ) old_row = timeseries.iloc[i].copy() old_row = pd.DataFrame(old_row).transpose() @@ -395,12 +395,12 @@ def segmentation_extreme_periods( if new_date.isin( timeseries.index.get_level_values("dates") ): - timeseries[ - "dates" - ] = timeseries.index.get_level_values("dates") - timeseries[ - "SegmentNo" - ] = timeseries.index.get_level_values("SegmentNo") + timeseries["dates"] = ( + timeseries.index.get_level_values("dates") + ) + timeseries["SegmentNo"] = ( + timeseries.index.get_level_values("SegmentNo") + ) timeseries["SegmentDuration"] = timeseries[ "SegmentDuration_Extreme" ] @@ -428,12 +428,12 @@ def segmentation_extreme_periods( for col in new_row.columns: new_row[col][0] = old_row[col][0] - timeseries[ - "dates" - ] = timeseries.index.get_level_values("dates") - timeseries[ - "SegmentNo" - ] = timeseries.index.get_level_values("SegmentNo") + timeseries["dates"] = ( + timeseries.index.get_level_values("dates") + ) + timeseries["SegmentNo"] = ( + timeseries.index.get_level_values("SegmentNo") + ) timeseries["SegmentDuration"] = timeseries[ "SegmentDuration_Extreme" ] @@ -457,9 +457,9 @@ def segmentation_extreme_periods( else: if i == -1: i = 0 - max_val[ - "SegmentDuration" - ] = timeseries.index.get_level_values("SegmentDuration")[i] + max_val["SegmentDuration"] = ( + timeseries.index.get_level_values("SegmentDuration")[i] + ) max_val.set_index( ["dates", "SegmentNo", "SegmentDuration"], inplace=True ) @@ -496,9 +496,9 @@ def segmentation_extreme_periods( if date < mini: i = i + 1 else: - timeseries[ - "SegmentDuration_Extreme" - ] = timeseries.index.get_level_values("SegmentDuration") + timeseries["SegmentDuration_Extreme"] = ( + timeseries.index.get_level_values("SegmentDuration") + ) old_row = timeseries.iloc[i].copy() old_row = pd.DataFrame(old_row).transpose() @@ -518,12 +518,12 @@ def segmentation_extreme_periods( if new_date.isin( timeseries.index.get_level_values("dates") ): - timeseries[ - "dates" - ] = timeseries.index.get_level_values("dates") - timeseries[ - "SegmentNo" - ] = timeseries.index.get_level_values("SegmentNo") + timeseries["dates"] = ( + timeseries.index.get_level_values("dates") + ) + timeseries["SegmentNo"] = ( + timeseries.index.get_level_values("SegmentNo") + ) timeseries["SegmentDuration"] = timeseries[ "SegmentDuration_Extreme" ] @@ -550,12 +550,12 @@ def segmentation_extreme_periods( ) for col in new_row.columns: new_row[col][0] = old_row[col][0] - timeseries[ - "dates" - ] = timeseries.index.get_level_values("dates") - timeseries[ - "SegmentNo" - ] = timeseries.index.get_level_values("SegmentNo") + timeseries["dates"] = ( + timeseries.index.get_level_values("dates") + ) + timeseries["SegmentNo"] = ( + timeseries.index.get_level_values("SegmentNo") + ) timeseries["SegmentDuration"] = timeseries[ "SegmentDuration_Extreme" ] @@ -579,9 +579,9 @@ def segmentation_extreme_periods( else: if i == -1: i = 0 - min_val[ - "SegmentDuration" - ] = timeseries.index.get_level_values("SegmentDuration")[i] + min_val["SegmentDuration"] = ( + timeseries.index.get_level_values("SegmentDuration")[i] + ) min_val.set_index( ["dates", "SegmentNo", "SegmentDuration"], inplace=True ) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 37dbb75e6..f5de2322a 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -571,9 +571,9 @@ def kmean_clustering(etrago, selected_network, weight, n_clusters): if kmean_settings["use_reduced_coordinates"]: # TODO : FIX THIS HACK THAT HAS UNEXPECTED SIDE-EFFECTS, # i.e. network is changed in place!! - network.buses.loc[ - busmap.index, ["x", "y"] - ] = network.buses.loc[busmap, ["x", "y"]].values + network.buses.loc[busmap.index, ["x", "y"]] = ( + network.buses.loc[busmap, ["x", "y"]].values + ) clustering = get_clustering_from_busmap( network, diff --git a/etrago/tools/calc_results.py b/etrago/tools/calc_results.py index bf1edac83..8d486936b 100755 --- a/etrago/tools/calc_results.py +++ b/etrago/tools/calc_results.py @@ -674,12 +674,12 @@ def calc_etrago_results(self): # system costs - self.results.value[ - "annual ac grid investment costs" - ] = calc_investment_cost(self)[0][0] - self.results.value[ - "annual dc grid investment costs" - ] = calc_investment_cost(self)[0][1] + self.results.value["annual ac grid investment costs"] = ( + calc_investment_cost(self)[0][0] + ) + self.results.value["annual dc grid investment costs"] = ( + calc_investment_cost(self)[0][1] + ) self.results.value["annual electrical grid investment costs"] = sum( calc_investment_cost(self)[0] ) @@ -688,9 +688,9 @@ def calc_etrago_results(self): self )[1] - self.results.value[ - "annual electrical storage investment costs" - ] = calc_investment_cost(self)[2][0] + self.results.value["annual electrical storage investment costs"] = ( + calc_investment_cost(self)[2][0] + ) self.results.value["annual store investment costs"] = calc_investment_cost( self )[2][1] @@ -715,9 +715,9 @@ def calc_etrago_results(self): network = self.network if not network.storage_units[network.storage_units.p_nom_extendable].empty: - self.results.value[ - "battery storage expansion" - ] = _calc_storage_expansion(self).sum() + self.results.value["battery storage expansion"] = ( + _calc_storage_expansion(self).sum() + ) store = _calc_store_expansion(self) self.results.value["store expansion"] = store.sum() @@ -743,19 +743,19 @@ def calc_etrago_results(self): self.results.value["fuel cell links expansion"] = links[0] self.results.value["electrolyzer links expansion"] = links[1] self.results.value["methanisation links expansion"] = links[2] - self.results.value[ - "Steam Methane Reformation links expansion" - ] = links[3] + self.results.value["Steam Methane Reformation links expansion"] = ( + links[3] + ) # grid expansion if not network.lines[network.lines.s_nom_extendable].empty: - self.results.value[ - "abs. electrical ac grid expansion" - ] = _calc_network_expansion(self)[0].sum() - self.results.value[ - "abs. electrical dc grid expansion" - ] = _calc_network_expansion(self)[1].sum() + self.results.value["abs. electrical ac grid expansion"] = ( + _calc_network_expansion(self)[0].sum() + ) + self.results.value["abs. electrical dc grid expansion"] = ( + _calc_network_expansion(self)[1].sum() + ) self.results.value["abs. electrical grid expansion"] = ( self.results.value["abs. electrical ac grid expansion"] + self.results.value["abs. electrical dc grid expansion"] diff --git a/etrago/tools/execute.py b/etrago/tools/execute.py index 838a66a22..95d474318 100755 --- a/etrago/tools/execute.py +++ b/etrago/tools/execute.py @@ -280,9 +280,9 @@ def iterate_lopf( ] etrago.network_tsa.transformers.s_nom_extendable = False - etrago.network_tsa.storage_units[ - "p_nom" - ] = etrago.network.storage_units["p_nom_opt"] + etrago.network_tsa.storage_units["p_nom"] = ( + etrago.network.storage_units["p_nom_opt"] + ) etrago.network_tsa.storage_units["p_nom_extendable"] = False etrago.network_tsa.stores["e_nom"] = etrago.network.stores["e_nom_opt"] @@ -448,13 +448,13 @@ def dispatch_disaggregation(self): index=transits, ) for storage in self.network.storage_units.index: - self.conduct_dispatch_disaggregation[ - storage - ] = self.network.storage_units_t.state_of_charge[storage] + self.conduct_dispatch_disaggregation[storage] = ( + self.network.storage_units_t.state_of_charge[storage] + ) for store in sto.index: - self.conduct_dispatch_disaggregation[ - store - ] = self.network.stores_t.e[store] + self.conduct_dispatch_disaggregation[store] = ( + self.network.stores_t.e[store] + ) extra_func = self.args["extra_functionality"] self.args["extra_functionality"] = {} @@ -494,9 +494,9 @@ def dispatch_disaggregation(self): self.network.transformers.s_nom_extendable = ( self.network_tsa.transformers.s_nom_extendable ) - self.network.storage_units[ - "p_nom_extendable" - ] = self.network_tsa.storage_units["p_nom_extendable"] + self.network.storage_units["p_nom_extendable"] = ( + self.network_tsa.storage_units["p_nom_extendable"] + ) self.network.stores["e_nom_extendable"] = self.network_tsa.stores[ "e_nom_extendable" ] @@ -1034,9 +1034,7 @@ def calc_line_losses(network, converged): """ # Line losses # calculate apparent power S = sqrt(p² + q²) [in MW] - s0_lines = (network.lines_t.p0**2 + network.lines_t.q0**2).apply( - np.sqrt - ) + s0_lines = (network.lines_t.p0**2 + network.lines_t.q0**2).apply(np.sqrt) # in case some snapshots did not converge, discard them from the # calculation s0_lines.loc[converged[converged is False].index, :] = np.nan @@ -1046,9 +1044,7 @@ def calc_line_losses(network, converged): ) # calculate losses per line and timestep network.\ # lines_t.line_losses = I² * R [in MW] - network.lines_t.losses = np.divide( - i0_lines**2 * network.lines.r, 1000000 - ) + network.lines_t.losses = np.divide(i0_lines**2 * network.lines.r, 1000000) # calculate total losses per line [in MW] network.lines = network.lines.assign( losses=np.sum(network.lines_t.losses).values diff --git a/etrago/tools/extendable.py b/etrago/tools/extendable.py index a1da98b45..cb11782a3 100644 --- a/etrago/tools/extendable.py +++ b/etrago/tools/extendable.py @@ -100,12 +100,12 @@ def extendable( network.links.loc[ network.links.carrier == "DC", "p_nom_extendable" ] = True - network.links.loc[ - network.links.carrier == "DC", "p_nom_min" - ] = network.links.p_nom - network.links.loc[ - network.links.carrier == "DC", "p_nom_max" - ] = float("inf") + network.links.loc[network.links.carrier == "DC", "p_nom_min"] = ( + network.links.p_nom + ) + network.links.loc[network.links.carrier == "DC", "p_nom_max"] = ( + float("inf") + ) if "german_network" in extendable_settings["extendable_components"]: buses = network.buses[network.buses.country == "DE"] @@ -305,21 +305,21 @@ def extendable( network.storage_units.loc[foreign_battery, "p_nom_extendable"] = True - network.storage_units.loc[ - foreign_battery, "p_nom_max" - ] = network.storage_units.loc[foreign_battery, "p_nom"] + network.storage_units.loc[foreign_battery, "p_nom_max"] = ( + network.storage_units.loc[foreign_battery, "p_nom"] + ) - network.storage_units.loc[ - foreign_battery, "p_nom" - ] = network.storage_units.loc[foreign_battery, "p_nom_min"] + network.storage_units.loc[foreign_battery, "p_nom"] = ( + network.storage_units.loc[foreign_battery, "p_nom_min"] + ) - network.storage_units.loc[ - foreign_battery, "capital_cost" - ] = network.storage_units.loc[de_battery, "capital_cost"].max() + network.storage_units.loc[foreign_battery, "capital_cost"] = ( + network.storage_units.loc[de_battery, "capital_cost"].max() + ) - network.storage_units.loc[ - foreign_battery, "marginal_cost" - ] = network.storage_units.loc[de_battery, "marginal_cost"].max() + network.storage_units.loc[foreign_battery, "marginal_cost"] = ( + network.storage_units.loc[de_battery, "marginal_cost"].max() + ) # Extension settings for extension-NEP 2035 scenarios if "overlay_network" in extendable_settings["extendable_components"]: @@ -651,9 +651,11 @@ def transformer_max_abs(network, buses): # the calculated maximum. For these cases, max capacity is set to be the # equal to the min capacity. network.transformers["s_nom_max"] = network.transformers.apply( - lambda x: x["s_nom_max"] - if float(x["s_nom_max"]) > float(x["s_nom_min"]) - else x["s_nom_min"], + lambda x: ( + x["s_nom_max"] + if float(x["s_nom_max"]) > float(x["s_nom_min"]) + else x["s_nom_min"] + ), axis=1, ) @@ -741,17 +743,17 @@ def extension_preselection(etrago, method, days=3): network.lines.loc[ ~network.lines.index.isin(extended_lines), "s_nom_extendable" ] = False - network.lines.loc[ - network.lines.s_nom_extendable, "s_nom_min" - ] = network.lines.s_nom + network.lines.loc[network.lines.s_nom_extendable, "s_nom_min"] = ( + network.lines.s_nom + ) network.lines.loc[network.lines.s_nom_extendable, "s_nom_max"] = np.inf network.links.loc[ ~network.links.index.isin(extended_links), "p_nom_extendable" ] = False - network.links.loc[ - network.links.p_nom_extendable, "p_nom_min" - ] = network.links.p_nom + network.links.loc[network.links.p_nom_extendable, "p_nom_min"] = ( + network.links.p_nom + ) network.links.loc[network.links.p_nom_extendable, "p_nom_max"] = np.inf network.snapshot_weightings = weighting From 976759a2da4763cbfb9c5e9d18c0571df1cbe3f8 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Tue, 11 Jun 2024 22:36:18 +0200 Subject: [PATCH 045/109] create method adjust_before_optimization --- etrago/appl.py | 16 ---------------- etrago/execute/__init__.py | 2 ++ etrago/tools/network.py | 3 +++ etrago/tools/utilities.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 7e8626f62..a0cbd9253 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -709,22 +709,6 @@ def run_etrago(args, json_path): # skip snapshots etrago.skip_snapshots() - # Temporary drop DLR as it is currently not working with sclopf - if etrago.args["method"]["type"] != "lopf": - etrago.network.lines_t.s_max_pu = pd.DataFrame( - index=etrago.network.snapshots, - columns=etrago.network.lines.index, - data=1.0, - ) - - etrago.network.lines.loc[etrago.network.lines.r == 0.0, "r"] = 10 - - # start linear optimal powerflow calculations - - etrago.network.storage_units.cyclic_state_of_charge = True - - etrago.network.lines.loc[etrago.network.lines.r == 0.0, "r"] = 10 - etrago.optimize() # conduct lopf with full complex timeseries for dispatch disaggregation diff --git a/etrago/execute/__init__.py b/etrago/execute/__init__.py index 1beffd144..071a6a1ff 100644 --- a/etrago/execute/__init__.py +++ b/etrago/execute/__init__.py @@ -407,6 +407,8 @@ def optimize(self): None. """ + # Verify feasibility + self.adjust_before_optimization() if self.args["method"]["type"] == "lopf": self.lopf() diff --git a/etrago/tools/network.py b/etrago/tools/network.py index 8b633bb42..901c68c21 100644 --- a/etrago/tools/network.py +++ b/etrago/tools/network.py @@ -114,6 +114,7 @@ set_random_noise, set_trafo_costs, update_busmap, + adjust_before_optimization, ) logger = logging.getLogger(__name__) @@ -382,6 +383,8 @@ def __init__( sclopf = iterate_sclopf + adjust_before_optimization = adjust_before_optimization + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 7176a70e2..3351c1ae1 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3178,3 +3178,35 @@ def find_buses_area(etrago, carrier): buses_area = pd.DataFrame() return buses_area.index + +def adjust_before_optimization(self): + + def check_e_initial(etrago): + stores = etrago.network.stores + stores_t = etrago.network.stores_t + for st in stores_t["e_max_pu"].columns: + e_initial_pu = stores.at[st, "e_initial"] / stores.at[st, "e_nom"] + min_e = stores_t["e_min_pu"].iloc[0, :][st] + max_e = stores_t["e_max_pu"].iloc[0, :][st] + if (e_initial_pu >= min_e) & (e_initial_pu <= max_e): + continue + else: + stores.at[st, "e_initial"] = ( + stores.at[st, "e_nom"] * (min_e + max_e) / 2 + ) + + return stores + + # Temporary drop DLR as it is currently not working with sclopf + if self.args["method"]["type"] != "lopf": + self.network.lines_t.s_max_pu = pd.DataFrame( + index=self.network.snapshots, + columns=self.network.lines.index, + data=1.0, + ) + + self.network.storage_units.cyclic_state_of_charge = True + + self.network.lines.loc[self.network.lines.r == 0.0, "r"] = 10 + + self.network.stores = check_e_initial(self) From e11cf54727b1f8e112cbe74e31a777dd2800943b Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 10:44:01 +0200 Subject: [PATCH 046/109] Remove extra argument for scn_decomissioing In case components are replaced by an extension scenario these are always dropped. Thus, only the argument scn_extension is needed --- etrago/appl.py | 22 +++------------------- etrago/args.json | 1 - etrago/tools/io.py | 2 +- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 3c2e2b0f5..9f4eca97c 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -85,8 +85,7 @@ "model_formulation": "kirchhoff", # angles or kirchhoff "scn_name": "eGon2035", # scenario: eGon2035, eGon100RE or status2019 # Scenario variations: - "scn_extension": None, # None or array of extension scenarios - "scn_decommissioning": None, # None or decommissioning scenario + "scn_extension": ["nep2021_c2035"], # None or array of extension scenarios # Export options: "lpfile": False, # save pyomo's lp file: False or /path/to/lpfile.lp "csv_export": "results", # save results as csv: False or /path/tofolder @@ -266,7 +265,8 @@ def run_etrago(args, json_path): scn_extension : None or list of str Choose extension-scenarios which will be added to the existing - network container. Data of the extension scenarios are located in + network container. In case new lines replace existing ones, these are + dropped from the network. Data of the extension scenarios is located in extension-tables (e.g. grid.egon_etrago_extension_line) There are two overlay networks: @@ -275,23 +275,7 @@ def run_etrago(args, json_path): * 'nep2021_c2035' includes all new lines planned by the Netzentwicklungsplan 2021 in scenario 2035 C Default: None. - scn_decommissioning : NoneType or str - This option does currently not work! - - Choose an extra scenario which includes lines you want to decommission - from the existing network. Data of the decommissioning scenarios are - located in extension-tables - (e.g. model_draft.ego_grid_pf_hv_extension_bus) with the prefix - 'decommissioning\_'. - Currently, there are two decommissioning_scenarios which are linked to - extension-scenarios: - - * 'nep2035_confirmed' includes all lines that will be replaced in - confirmed projects - * 'nep2035_b2' includes all lines that will be replaced in - NEP-scenario 2035 B2 - Default: None. lpfile : bool or str State if and where you want to save pyomo's lp file. Options: False or '/path/tofile.lp'. Default: False. diff --git a/etrago/args.json b/etrago/args.json index d5ea057c3..63c1fc1d4 100644 --- a/etrago/args.json +++ b/etrago/args.json @@ -28,7 +28,6 @@ "model_formulation": "kirchhoff", "scn_name": "eGon2035", "scn_extension": null, - "scn_decommissioning": null, "lpfile": false, "csv_export": "results", "extendable": { diff --git a/etrago/tools/io.py b/etrago/tools/io.py index 21c819fe5..db1bedaab 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -863,7 +863,7 @@ def decommissioning(self, **kwargs): Network container including decommissioning """ - if self.args["scn_decommissioning"] is not None: + if self.args["scn_extension"] is not None: for i in range(len(self.args["scn_extension"])): scn_decom = self.args["scn_extension"][i] From c8b1a72787dc191e4d1b06380dd50e6b4cdc1465 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 10:53:19 +0200 Subject: [PATCH 047/109] Fix flake8 issues --- etrago/tools/io.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/etrago/tools/io.py b/etrago/tools/io.py index db1bedaab..87f7537eb 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -48,7 +48,6 @@ __license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)" __author__ = "ulfmueller, mariusves, pieterhexen, ClaraBuettner" -from importlib import import_module import os import numpy as np @@ -210,7 +209,7 @@ def fetch_by_relname(self, name): ) if self.scenario_extension: - from saio.grid import ( # noqa: F401 + from saio.grid import ( # noqa: F401, F811 egon_etrago_extension_bus as egon_etrago_bus, egon_etrago_extension_line as egon_etrago_line, egon_etrago_extension_link as egon_etrago_link, @@ -869,7 +868,7 @@ def decommissioning(self, **kwargs): df_decommisionning = pd.read_sql( f""" - SELECT * FROM + SELECT * FROM grid.egon_etrago_extension_line WHERE scn_name = 'decomissioining_{scn_decom}' """, From 05c04e9b5e0a569e3e7af5f1f52ca9588a68c055 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 11:05:12 +0200 Subject: [PATCH 048/109] Apply black --- etrago/analyze/plot.py | 4 +++- etrago/tools/utilities.py | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/etrago/analyze/plot.py b/etrago/analyze/plot.py index ce7488e8b..fcbf2772d 100644 --- a/etrago/analyze/plot.py +++ b/etrago/analyze/plot.py @@ -2920,7 +2920,9 @@ def plot_grid( bus_sizes[bus_sizes == "AC"] = 1 * bus_scaling # only plot buses inside interest area buses_interest_area = find_buses_area(self, "AC") - buses_outside = [_ for _ in bus_sizes.index if _ not in buses_interest_area] + buses_outside = [ + _ for _ in bus_sizes.index if _ not in buses_interest_area + ] bus_sizes.loc[buses_outside] = bus_sizes.loc[buses_outside] * 0.3 bus_scaling = bus_sizes bus_colors = coloring()["AC"] diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index cdee13357..a3911e275 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -2974,6 +2974,7 @@ def manual_fixes_datamodel(etrago): inplace=True, ) + def select_elec_network(etrago, apply_on="grid_model"): """ Creates networks to be used on the clustering based on settings specified @@ -3013,7 +3014,7 @@ def select_elec_network(etrago, apply_on="grid_model"): ].index if apply_on == "grid_model-ehv": n_clusters = pd.NA - #Exclude foreign buses when set to don't include them in clustering + # Exclude foreign buses when set to don't include them in clustering elif settings["cluster_foreign_AC"]: n_clusters = settings["n_clusters_AC"] else: @@ -3197,6 +3198,7 @@ def find_buses_area(etrago, carrier): return buses_area.index + def adjust_before_optimization(self): def check_e_initial(etrago): @@ -3227,4 +3229,4 @@ def check_e_initial(etrago): self.network.lines.loc[self.network.lines.r == 0.0, "r"] = 10 - self.network.stores = check_e_initial(self) \ No newline at end of file + self.network.stores = check_e_initial(self) From 0d9ac96ac6c165ab55725df1416cefd44d685708 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 11:06:47 +0200 Subject: [PATCH 049/109] Fix flake8 issue --- etrago/analyze/plot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etrago/analyze/plot.py b/etrago/analyze/plot.py index fcbf2772d..d84462b45 100644 --- a/etrago/analyze/plot.py +++ b/etrago/analyze/plot.py @@ -2475,8 +2475,8 @@ def plot_grid( * 'q_flow_max': maximal reactive flows * 'dlr': energy above nominal capacity * 'grey': plot all lines and DC links grey colored - * 'interest_area': plot all AC buses inside the interest area larger than buses - outside the interest area + * 'interest_area': plot all AC buses inside the interest area larger + than buses outside the interest area bus_sizes : float, optional Size of buses. The default is 0.001. From 52a10222d7372adce542ddc655bc6b66e2f589b7 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 11:08:48 +0200 Subject: [PATCH 050/109] Apply isort --- etrago/cluster/electrical.py | 2 +- etrago/cluster/gas.py | 5 +---- etrago/network.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 35f6dc603..2a73ca24b 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -49,8 +49,8 @@ strategies_one_ports, ) from etrago.tools.utilities import ( - set_control_strategies, select_elec_network, + set_control_strategies, ) logger = logging.getLogger(__name__) diff --git a/etrago/cluster/gas.py b/etrago/cluster/gas.py index 919ae075e..5768b3cd9 100644 --- a/etrago/cluster/gas.py +++ b/etrago/cluster/gas.py @@ -43,10 +43,7 @@ kmedoids_dijkstra_clustering, sum_with_inf, ) - from etrago.tools.utilities import ( - find_buses_area, - set_control_strategies, - ) + from etrago.tools.utilities import find_buses_area, set_control_strategies logger = logging.getLogger(__name__) diff --git a/etrago/network.py b/etrago/network.py index bb6d67e2e..51dbc0749 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -86,6 +86,7 @@ ) from etrago.tools.utilities import ( add_missing_components, + adjust_before_optimization, adjust_CH4_gen_carriers, buses_by_country, check_args, @@ -110,7 +111,6 @@ set_random_noise, set_trafo_costs, update_busmap, - adjust_before_optimization, ) logger = logging.getLogger(__name__) From f932d496edcfe6ece3e62c7acb87f2a557bca184 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 13:50:39 +0200 Subject: [PATCH 051/109] Add import of shapely --- etrago/tools/utilities.py | 1 + 1 file changed, 1 insertion(+) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index a3911e275..628c2a487 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -29,6 +29,7 @@ import math import os +from geoalchemy2.shape import to_shape # noqa: F401 from pyomo.environ import Constraint, PositiveReals, Var import numpy as np import pandas as pd From 1f4f31744543574b8f3f98c03316a999009c43f0 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 14:22:50 +0200 Subject: [PATCH 052/109] Check if type of geom is a geometry entry --- etrago/tools/utilities.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 628c2a487..df8e60586 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -29,8 +29,8 @@ import math import os -from geoalchemy2.shape import to_shape # noqa: F401 from pyomo.environ import Constraint, PositiveReals, Var +import geoalchemy2 import numpy as np import pandas as pd import pypsa @@ -1126,9 +1126,16 @@ def agg_parallel_lines(l0): lines_2["bus0"] = bus_max lines_2["bus1"] = bus_min lines_2.reset_index(inplace=True) + lines_2["geom"] = lines_2.apply( - lambda x: None if x.geom is None else x.geom.wkt, axis=1 + lambda x: ( + x.geom.wkt + if isinstance(x.geom, geoalchemy2.elements.WKBElement) + else None + ), + axis=1, ) + network.lines = ( lines_2.groupby(["bus0", "bus1"]) .apply(agg_parallel_lines) From fef99904e3018d0ceabf574078434d6bb624865e Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 16 Aug 2024 14:56:27 +0200 Subject: [PATCH 053/109] Add cluster strategy for scn_name --- etrago/cluster/spatial.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 0dbbabe32..36b33e049 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -110,7 +110,7 @@ def sum_with_inf(x): def strategies_buses(): - return {"geom": nan_links, "country": "first"} + return {"geom": nan_links, "country": "first", "scn_name": "first",} def strategies_lines(): @@ -120,6 +120,7 @@ def strategies_lines(): "topo": "first", "country": "first", "total_cables": np.sum, + "scn_name": "first", } From e23dcd7aaa502bd3c696edab5e4f7380dadf591f Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Mon, 19 Aug 2024 17:49:02 +0200 Subject: [PATCH 054/109] Drop duplicate HV buses for eHv clustering busmap When scn_extension is selected, some HV buses are connected to more than one eHV bus. This leads to duplicated indices in the path matrix. To avoid this, each HV bus is only considered once using transformers.bus1.unique(). --- etrago/cluster/spatial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index 36b33e049..96124b32e 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -429,7 +429,7 @@ def busmap_by_shortest_path(network, fromlvl, tolvl, cpu_cores=4): lines_plus_dc["carrier"] = "AC" # temporary end points, later replaced by bus1 pendant - t_buses = transformer[mask].bus0 + t_buses = transformer[mask].bus0.unique() # create all possible pathways ppaths = list(product(s_buses, t_buses)) From 0f4e7ab545c019372b7edf66c46dc1df44f7e985 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Tue, 20 Aug 2024 10:39:23 +0200 Subject: [PATCH 055/109] Add option for negative load shedding at e-Mobility buses This is needed when some e-Mobility buses are not clustered. In some cases, the driving load is not high enough to match the e_min_pu limits of the ev stores. --- etrago/tools/utilities.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 3214981e1..6a6df00d1 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -622,7 +622,12 @@ def connected_transformer(network, busids): return network.transformers[mask] -def load_shedding(self, temporal_disaggregation=False, **kwargs): +def load_shedding( + self, + temporal_disaggregation=False, + negative_load_shedding_ev=False, + **kwargs, +): """Implement load shedding in existing network to identify feasibility problems @@ -681,6 +686,31 @@ def load_shedding(self, temporal_disaggregation=False, **kwargs): "Generator", ) + if negative_load_shedding_ev: + # Add negative load shedding generators for e-Mobility buses + ev_buses = network.buses[network.buses.carrier == "Li_ion"].index + index_ev = list( + range( + start + len(network.buses.index), + start + len(network.buses.index) + len(ev_buses), + ) + ) + network.import_components_from_dataframe( + pd.DataFrame( + dict( + marginal_cost=-marginal_cost, + p_nom=p_nom, + p_min_pu=-1, + p_max_pu=0, + carrier="negative load shedding", + bus=ev_buses, + control="PQ", + ), + index=index_ev, + ), + "Generator", + ) + def set_control_strategies(network): """Sets control strategies for AC generators and storage units From 4265a3275d3e609ef86b3bf34d835e5b4e875c3d Mon Sep 17 00:00:00 2001 From: birgits Date: Tue, 20 Aug 2024 11:23:20 +0200 Subject: [PATCH 056/109] Fix bug when there are different scenario names --- etrago/cluster/electrical.py | 1 + 1 file changed, 1 insertion(+) diff --git a/etrago/cluster/electrical.py b/etrago/cluster/electrical.py index 2a73ca24b..b20cace62 100755 --- a/etrago/cluster/electrical.py +++ b/etrago/cluster/electrical.py @@ -296,6 +296,7 @@ def cluster_on_extra_high_voltage(etrago, busmap, with_time=True): "y": _leading(busmap, network.buses), "geom": lambda x: np.nan, "country": lambda x: "", + "scn_name": "first", }, ) From cec6836f640fda2a50adb113f550452122673af5 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 21 Aug 2024 21:48:24 +0200 Subject: [PATCH 057/109] assign value to n_clusters when apply_on is market_model --- etrago/execute/market_optimization.py | 2 +- etrago/tools/utilities.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/etrago/execute/market_optimization.py b/etrago/execute/market_optimization.py index e175ed442..7db695289 100644 --- a/etrago/execute/market_optimization.py +++ b/etrago/execute/market_optimization.py @@ -257,7 +257,7 @@ def build_market_model(self): """ # use existing preprocessing to get only the electricity system - net, weight, n_clusters, busmap_foreign = preprocessing( + net, _, _, busmap_foreign, _, _ = preprocessing( self, apply_on="market_model" ) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index df8e60586..5173f4068 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3065,6 +3065,8 @@ def select_elec_network(etrago, apply_on="grid_model"): ) area_network = pypsa.Network() + n_clusters = pd.NA + else: logger.warning( """Parameter apply_on must be either 'grid_model' or 'market_model' From b4af09f973247c88aec56a71392886b38836608b Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Thu, 22 Aug 2024 19:03:50 +0200 Subject: [PATCH 058/109] Delete outdated fix --- etrago/tools/io.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/etrago/tools/io.py b/etrago/tools/io.py index 87f7537eb..7f1153e57 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -831,10 +831,6 @@ def extension(self, **kwargs): self.network = scenario.build_network(self.network) - # Allow lossless links to conduct bidirectional - self.network.links.loc[ - self.network.links.efficiency == 1.0, "p_min_pu" - ] = -1 def decommissioning(self, **kwargs): From 86e2d8f92543dd8ebee517e7862c63de70025dd3 Mon Sep 17 00:00:00 2001 From: ClaraBuettner Date: Fri, 23 Aug 2024 10:15:45 +0200 Subject: [PATCH 059/109] Set negative load shedding for ev buses to True by default --- etrago/tools/utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 6a6df00d1..33dd1c700 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -625,7 +625,7 @@ def connected_transformer(network, busids): def load_shedding( self, temporal_disaggregation=False, - negative_load_shedding_ev=False, + negative_load_shedding_ev=True, **kwargs, ): """Implement load shedding in existing network to identify From e23e136ffbe41acb8f0a8b3a2c865c860845a16e Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 13 Nov 2024 16:44:48 +0100 Subject: [PATCH 060/109] Revert "Set negative load shedding for ev buses to True by default" This reverts commit 86e2d8f92543dd8ebee517e7862c63de70025dd3. --- etrago/tools/utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index ccaabca1f..c5c889268 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -626,7 +626,7 @@ def connected_transformer(network, busids): def load_shedding( self, temporal_disaggregation=False, - negative_load_shedding_ev=True, + negative_load_shedding_ev=False, **kwargs, ): """Implement load shedding in existing network to identify From ec512676a9cc5331489cf207cc4fe7a2cb198e95 Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Wed, 13 Nov 2024 16:45:44 +0100 Subject: [PATCH 061/109] Revert "Add option for negative load shedding at e-Mobility buses" This reverts commit 0f4e7ab545c019372b7edf66c46dc1df44f7e985. --- etrago/tools/utilities.py | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index c5c889268..5173f4068 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -623,12 +623,7 @@ def connected_transformer(network, busids): return network.transformers[mask] -def load_shedding( - self, - temporal_disaggregation=False, - negative_load_shedding_ev=False, - **kwargs, -): +def load_shedding(self, temporal_disaggregation=False, **kwargs): """Implement load shedding in existing network to identify feasibility problems @@ -687,31 +682,6 @@ def load_shedding( "Generator", ) - if negative_load_shedding_ev: - # Add negative load shedding generators for e-Mobility buses - ev_buses = network.buses[network.buses.carrier == "Li_ion"].index - index_ev = list( - range( - start + len(network.buses.index), - start + len(network.buses.index) + len(ev_buses), - ) - ) - network.import_components_from_dataframe( - pd.DataFrame( - dict( - marginal_cost=-marginal_cost, - p_nom=p_nom, - p_min_pu=-1, - p_max_pu=0, - carrier="negative load shedding", - bus=ev_buses, - control="PQ", - ), - index=index_ev, - ), - "Generator", - ) - def set_control_strategies(network): """Sets control strategies for AC generators and storage units From 067768ae1c21b6a630e8cf7158c53656b264c0ef Mon Sep 17 00:00:00 2001 From: CarlosEpia Date: Thu, 14 Nov 2024 11:20:48 +0100 Subject: [PATCH 062/109] Black --- etrago/cluster/spatial.py | 6 +++++- etrago/tools/io.py | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/etrago/cluster/spatial.py b/etrago/cluster/spatial.py index e61c915e9..813c326e1 100755 --- a/etrago/cluster/spatial.py +++ b/etrago/cluster/spatial.py @@ -110,7 +110,11 @@ def sum_with_inf(x): def strategies_buses(): - return {"geom": nan_links, "country": "first", "scn_name": "first",} + return { + "geom": nan_links, + "country": "first", + "scn_name": "first", + } def strategies_lines(): diff --git a/etrago/tools/io.py b/etrago/tools/io.py index 7f1153e57..579d0fada 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -832,7 +832,6 @@ def extension(self, **kwargs): self.network = scenario.build_network(self.network) - def decommissioning(self, **kwargs): """ Function that removes components in a decommissioning-scenario from From d13b9417f5cb956efe13738bd964e6b888723282 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 29 Apr 2025 15:54:10 +0200 Subject: [PATCH 063/109] Adapt solver-options and run etrago for interest_area = Ingolstadt --- etrago/appl.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 4e08e68a5..cdd275978 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -49,7 +49,7 @@ args = { # Setup and Configuration: - "db": "egon-data", # database session + "db": "egon-data-wam02", # database session # "egon-data-wam02" "gridversion": None, # None for model_draft or Version number "method": { # Choose method and settings for optimization "type": "lopf", # type of optimization, 'lopf' or 'sclopf' @@ -72,15 +72,16 @@ "q_allocation": "p_nom", # allocate reactive power via 'p_nom' or 'p' }, "start_snapshot": 1, - "end_snapshot": 10, + "end_snapshot": 8760, "solver": "gurobi", # glpk, cplex or gurobi "solver_options": { "BarConvTol": 1.0e-5, "FeasibilityTol": 1.0e-5, "method": 2, - "crossover": 0, + "crossover": 1, "logFile": "solver_etrago.log", - "threads": 7, + "threads": 4, + "NumericFocus": 0, "BarHomogeneous": 1, }, "model_formulation": "kirchhoff", # angles or kirchhoff @@ -113,7 +114,7 @@ "extra_functionality": {}, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses - "interest_area": False, # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. + "interest_area": ["Ingolstadt"], # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. "network_clustering_ehv": { "active": False, # choose if clustering of HV buses to EHV buses is activated "busmap": False, # False or path to stored busmap @@ -179,7 +180,7 @@ "n_clusters": 5, # number of periods - only relevant for 'typical_periods' "n_segments": 5, # number of segments - only relevant for segmentation }, - "skip_snapshots": 5, # False or number of snapshots to skip + "skip_snapshots": 3, # False or number of snapshots to skip "temporal_disaggregation": { "active": False, # choose if temporally full complex dispatch optimization should be conducted "no_slices": 8, # number of subproblems optimization is divided into From 1ad0836921cf0be3aa59d53ffd47a592b8cfe253 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 4 May 2025 21:04:14 +0200 Subject: [PATCH 064/109] Add sensivity - Test for Electrolyser --- etrago/appl.py | 7 +++++++ etrago/tools/utilities.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/etrago/appl.py b/etrago/appl.py index cdd275978..e6ddb40dc 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -707,6 +707,13 @@ def run_etrago(args, json_path): # adjust network regarding eTraGo setting etrago.adjust_network() + # sensitivity test + # change capital_cost of Electrolyser + etrago.network.links.loc[etrago.network.links.carrier == "power_to_H2", "capital_cost"] *= 2 + + import pdb + pdb.set_trace() + # ehv network clustering etrago.ehv_clustering() diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index f79b218ac..6e99e3d8b 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -2827,7 +2827,7 @@ def adjust_CH4_gen_carriers(self): if "eGon2035" in self.args["scn_name"]: # Define marginal cost - marginal_cost_def = {"CH4": 40.9765, "biogas": 25.6} + marginal_cost_def = {"CH4": 40.9765, "biogas": 25.6} # Standard_ CH4 = 40.9765 engine = db.connection(section=self.args["db"]) try: From 3adfb6592f9735e72e7eee20080f1b9bc5551cd7 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 3 Jun 2025 20:09:45 +0200 Subject: [PATCH 065/109] Start to adapt interest generators p_nom_extendable = True --- etrago/appl.py | 20 ++++++++++++++------ etrago/tools/constraints.py | 1 - 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index e6ddb40dc..5a258cfb4 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -75,8 +75,8 @@ "end_snapshot": 8760, "solver": "gurobi", # glpk, cplex or gurobi "solver_options": { - "BarConvTol": 1.0e-5, - "FeasibilityTol": 1.0e-5, + "BarConvTol": 1.0e-9, + "FeasibilityTol": 1.0e-9, "method": 2, "crossover": 1, "logFile": "solver_etrago.log", @@ -124,7 +124,7 @@ "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign-interest_area) "cluster_foreign_AC": False, # take foreign AC buses into account, True or False - "n_cluster_interest_area": False, # False or number of buses. + "n_cluster_interest_area": 1, # False or number of buses. "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra "n_clusters_gas": 15, # total number of resulting CH4 nodes (DE+foreign) "n_clusters_h2": 15, # total number of resulting H2 nodes (DE+foreign) @@ -707,12 +707,18 @@ def run_etrago(args, json_path): # adjust network regarding eTraGo setting etrago.adjust_network() + #import pdb + #pdb.set_trace() + + # set interest components to extendable + + # sensitivity test # change capital_cost of Electrolyser - etrago.network.links.loc[etrago.network.links.carrier == "power_to_H2", "capital_cost"] *= 2 + # etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] *= 2 - import pdb - pdb.set_trace() + # change capital_cost of Battery + #etrago.network.storage_units.loc[etrago.network.storage_units.carrier == "battery", "capital_cost"] *= 0.5 # ehv network clustering etrago.ehv_clustering() @@ -727,6 +733,8 @@ def run_etrago(args, json_path): # skip snapshots etrago.skip_snapshots() + print(datetime.datetime.now()) + for comp in etrago.network.iterate_components(): for key in comp.pnl: comp.pnl[key].where( diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index e3633cda6..8cd4dd9dc 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -182,7 +182,6 @@ def _rule(m): ) links_opt = sum(m.link_p_nom[index] for index in links_index) - return (lines_opt + links_opt) <= ( lines_snom + links_pnom ) * self.args["extra_functionality"]["max_line_ext"] From d8e41dd69e54fdc9c63ba3d447236bfd7e7baf75 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Wed, 4 Jun 2025 16:00:15 +0200 Subject: [PATCH 066/109] Clone solar_rooftop generator in Ingolstadt and set p_nom_extendable = TRUE --- etrago/appl.py | 13 ++- etrago/germany-de-nuts-3-regions.geojson | 1 + etrago/network.py | 11 +- etrago/tools/utilities.py | 136 ++++++++++++++++++++++- 4 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 etrago/germany-de-nuts-3-regions.geojson diff --git a/etrago/appl.py b/etrago/appl.py index 5a258cfb4..492f9792e 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -48,6 +48,7 @@ from etrago import Etrago args = { + "nuts_3_map" : "germany-de-nuts-3-regions.geojson", # Setup and Configuration: "db": "egon-data-wam02", # database session # "egon-data-wam02" "gridversion": None, # None for model_draft or Version number @@ -710,9 +711,6 @@ def run_etrago(args, json_path): #import pdb #pdb.set_trace() - # set interest components to extendable - - # sensitivity test # change capital_cost of Electrolyser # etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] *= 2 @@ -725,7 +723,14 @@ def run_etrago(args, json_path): # spatial clustering etrago.spatial_clustering() - etrago.spatial_clustering_gas() + etrago.spatial_clustering_gas() + + # set interest components to extendable + + etrago.add_extendable_solar_to_interest_area() + + import pdb + pdb.set_trace() # snapshot clustering etrago.snapshot_clustering() diff --git a/etrago/germany-de-nuts-3-regions.geojson b/etrago/germany-de-nuts-3-regions.geojson new file mode 100644 index 000000000..0ee4f4f7c --- /dev/null +++ b/etrago/germany-de-nuts-3-regions.geojson @@ -0,0 +1 @@ +{"crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:EPSG::4258"}}, "type": "FeatureCollection", "features": [{"geometry": {"type": "Polygon", "coordinates": [[[14.08849, 51.87831], [14.13236, 51.72386], [14.10401, 51.66934], [14.19202, 51.6187], [14.15679, 51.57558], [14.16332, 51.54104], [14.10794, 51.51416], [14.00175, 51.38452], [13.83531, 51.37679], [13.69125, 51.37401], [13.71195, 51.44564], [13.66199, 51.52055], [13.84767, 51.55672], [13.8822, 51.61417], [13.79809, 51.73713], [13.82586, 51.75132], [13.83377, 51.84878], [13.86473, 51.88664], [14.08849, 51.87831]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oberspreewald-Lausitz", "LEVL_CODE": 3, "FID": "DE40B", "NUTS_ID": "DE40B"}, "id": "DE40B"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.42376, 52.37397], [14.40668, 52.27411], [14.60089, 52.27205], [14.70339, 52.24075], [14.6854, 52.1239], [14.75523, 52.07003], [14.71672, 52.00119], [14.61132, 52.03064], [14.52039, 52.00865], [14.47955, 52.04649], [14.446, 52.02636], [14.36521, 52.06566], [14.2547, 52.03766], [13.96624, 52.13106], [13.87855, 52.12591], [13.83763, 52.22573], [13.83998, 52.30186], [13.75123, 52.32424], [13.73952, 52.36956], [13.69974, 52.37788], [13.73542, 52.43866], [13.66698, 52.47417], [13.71109, 52.49348], [13.81392, 52.44142], [13.91405, 52.4737], [14.00218, 52.45604], [14.20817, 52.49256], [14.32577, 52.38843], [14.42376, 52.37397]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oder-Spree", "LEVL_CODE": 3, "FID": "DE40C", "NUTS_ID": "DE40C"}, "id": "DE40C"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.33175, 53.31801], [12.45787, 53.2568], [12.65448, 53.24306], [12.98468, 53.16499], [12.95058, 53.09853], [13.02197, 53.06074], [13.0482, 52.95287], [13.0304, 52.86432], [12.961, 52.81169], [12.82876, 52.69476], [12.74211, 52.70622], [12.64034, 52.79428], [12.55331, 52.80289], [12.50928, 52.75459], [12.41656, 52.75833], [12.37678, 52.79959], [12.2492, 52.79186], [12.25129, 52.81169], [12.21959, 52.86161], [12.14009, 52.86345], [12.12681, 52.8902], [12.25983, 52.92135], [12.40189, 53.02117], [12.30998, 53.05664], [12.3251, 53.3213], [12.33175, 53.31801]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ostprignitz-Ruppin", "LEVL_CODE": 3, "FID": "DE40D", "NUTS_ID": "DE40D"}, "id": "DE40D"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.89353, 52.46076], [13.01803, 52.35435], [13.12653, 52.35545], [13.1659, 52.39428], [13.2288, 52.41586], [13.31193, 52.39919], [12.98098, 52.14549], [12.95046, 52.04119], [12.76978, 51.97927], [12.66408, 52.00687], [12.5535, 51.98541], [12.37612, 52.04512], [12.27672, 52.10402], [12.22917, 52.16814], [12.31718, 52.4541], [12.36118, 52.44277], [12.44043, 52.31551], [12.58604, 52.35481], [12.67731, 52.42154], [12.64074, 52.45507], [12.42818, 52.45814], [12.42305, 52.5248], [12.45342, 52.55252], [12.56657, 52.52931], [12.70115, 52.54495], [12.75548, 52.46533], [12.89353, 52.46076]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Potsdam-Mittelmark", "LEVL_CODE": 3, "FID": "DE40E", "NUTS_ID": "DE40E"}, "id": "DE40E"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.3251, 53.3213], [12.30998, 53.05664], [12.40189, 53.02117], [12.25983, 52.92135], [12.12681, 52.8902], [11.86911, 52.9028], [11.82421, 52.94981], [11.59778, 53.03593], [11.45163, 53.07273], [11.33618, 53.06189], [11.26573, 53.12198], [11.51056, 53.13092], [11.609, 53.22752], [11.8222, 53.23347], [12.01513, 53.3124], [12.05913, 53.35495], [12.3251, 53.3213]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Prignitz", "LEVL_CODE": 3, "FID": "DE40F", "NUTS_ID": "DE40F"}, "id": "DE40F"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.71672, 52.00119], [14.70106, 51.91786], [14.59998, 51.82596], [14.75204, 51.66326], [14.7592, 51.61396], [14.72986, 51.58178], [14.66003, 51.55265], [14.56367, 51.57376], [14.44777, 51.54207], [14.32836, 51.51954], [14.16332, 51.54104], [14.15679, 51.57558], [14.19202, 51.6187], [14.10401, 51.66934], [14.13236, 51.72386], [14.08849, 51.87831], [14.18681, 51.86262], [14.24766, 51.91146], [14.37765, 51.93785], [14.38071, 51.99615], [14.446, 52.02636], [14.47955, 52.04649], [14.52039, 52.00865], [14.61132, 52.03064], [14.71672, 52.00119]], [[14.27349, 51.79503], [14.28746, 51.77177], [14.27602, 51.73914], [14.3099, 51.72281], [14.30257, 51.70215], [14.37566, 51.69747], [14.44292, 51.70387], [14.42935, 51.7358], [14.49427, 51.79068], [14.44598, 51.81451], [14.40606, 51.80117], [14.37201, 51.82993], [14.36964, 51.85367], [14.30613, 51.86392], [14.31087, 51.82117], [14.27349, 51.79503]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Spree-Nei\u00dfe", "LEVL_CODE": 3, "FID": "DE40G", "NUTS_ID": "DE40G"}, "id": "DE40G"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.42099, 52.37625], [13.4966, 52.29825], [13.47157, 52.24452], [13.56484, 52.21119], [13.54996, 52.10151], [13.64734, 52.04013], [13.62631, 52.00961], [13.42654, 51.96051], [13.42552, 51.9347], [13.55144, 51.90795], [13.54522, 51.81654], [13.50709, 51.79203], [13.37153, 51.82817], [13.26052, 51.82382], [13.20481, 51.87949], [13.15091, 51.85961], [12.76978, 51.97927], [12.95046, 52.04119], [12.98098, 52.14549], [13.31193, 52.39919], [13.42099, 52.37625]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Teltow-Fl\u00e4ming", "LEVL_CODE": 3, "FID": "DE40H", "NUTS_ID": "DE40H"}, "id": "DE40H"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.41216, 53.32964], [14.41851, 53.3124], [14.43838, 53.25843], [14.37608, 53.19026], [14.35522, 53.06748], [14.14366, 52.96137], [14.00762, 52.94095], [13.85376, 52.97849], [13.83136, 52.99522], [13.85792, 53.03812], [13.83946, 53.05481], [13.5179, 52.98867], [13.31065, 53.12371], [13.29846, 53.17661], [13.2414, 53.2324], [13.29253, 53.26938], [13.40103, 53.26032], [13.50018, 53.3124], [13.56139, 53.3959], [13.71007, 53.47906], [13.79514, 53.48336], [13.79932, 53.53974], [13.93081, 53.42751], [14.23352, 53.41656], [14.22238, 53.36515], [14.16665, 53.3124], [14.14539, 53.26946], [14.2235, 53.26103], [14.37164, 53.3124], [14.41216, 53.32964]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Uckermark", "LEVL_CODE": 3, "FID": "DE40I", "NUTS_ID": "DE40I"}, "id": "DE40I"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[8.91583, 53.01102], [8.71142, 53.04463], [8.6549, 53.10886], [8.61556, 53.16321], [8.48533, 53.22712], [8.98419, 53.12607], [8.9596, 53.1117], [8.96868, 53.04485], [8.91583, 53.01102]]], [[[8.59226, 53.58509], [8.56538, 53.53303], [8.52846, 53.57501], [8.53711, 53.59181], [8.59226, 53.58509]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bremen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE501", "NUTS_ID": "DE501"}, "id": "DE501"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.49265, 53.47242], [8.5549, 53.52513], [8.49735, 53.58905], [8.52041, 53.60621], [8.63666, 53.59812], [8.64546, 53.52133], [8.60933, 53.49019], [8.49265, 53.47242]], [[8.59226, 53.58509], [8.53711, 53.59181], [8.52846, 53.57501], [8.56538, 53.53303], [8.59226, 53.58509]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bremerhaven, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE502", "NUTS_ID": "DE502"}, "id": "DE502"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[10.23668, 53.49635], [10.30795, 53.4332], [10.2045, 53.39675], [10.05909, 53.45534], [9.98419, 53.42269], [9.8626, 53.43964], [9.76886, 53.50528], [9.76626, 53.54682], [9.7301, 53.55758], [9.76283, 53.60761], [9.8626, 53.60746], [9.94538, 53.65293], [10.07143, 53.69702], [10.15226, 53.7118], [10.18964, 53.61027], [10.16603, 53.55111], [10.23668, 53.49635]]], [[[8.52965, 53.91954], [8.50238, 53.89361], [8.4637, 53.90046], [8.50193, 53.95466], [8.52965, 53.91954]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hamburg", "LEVL_CODE": 3, "FID": "DE600", "NUTS_ID": "DE600"}, "id": "DE600"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.6745, 49.95145], [8.72571, 49.95421], [8.73946, 49.8892], [8.66584, 49.80756], [8.57844, 49.83657], [8.63904, 49.93959], [8.6745, 49.95145]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Darmstadt, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE711", "NUTS_ID": "DE711"}, "id": "DE711"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.78987, 50.17605], [8.78065, 50.13673], [8.71783, 50.06241], [8.615, 50.04735], [8.59132, 50.01769], [8.54369, 50.0302], [8.55307, 50.04735], [8.54308, 50.06913], [8.52072, 50.06171], [8.48687, 50.09933], [8.59024, 50.15954], [8.71138, 50.22626], [8.7235, 50.17372], [8.78987, 50.17605]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Frankfurt am Main, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE712", "NUTS_ID": "DE712"}, "id": "DE712"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.81796, 50.13351], [8.8237, 50.07466], [8.71783, 50.06241], [8.78065, 50.13673], [8.81796, 50.13351]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Offenbach am Main, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE713", "NUTS_ID": "DE713"}, "id": "DE713"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.33064, 50.00054], [8.28825, 49.99513], [8.24413, 50.02535], [8.19004, 50.0353], [8.17514, 50.03426], [8.12925, 50.08421], [8.14074, 50.10829], [8.32765, 50.14589], [8.37744, 50.04735], [8.33064, 50.00054]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wiesbaden, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE714", "NUTS_ID": "DE714"}, "id": "DE714"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[8.79733, 49.73829], [8.80345, 49.68682], [8.87108, 49.64241], [8.89957, 49.50366], [8.724, 49.53045], [8.65499, 49.61775], [8.60449, 49.60679], [8.61203, 49.54906], [8.58138, 49.51978], [8.42244, 49.58339], [8.41477, 49.59505], [8.36336, 49.68552], [8.44638, 49.7308], [8.44837, 49.73366], [8.55927, 49.72879], [8.79733, 49.73829]]], [[[8.89936, 49.48455], [8.93188, 49.47064], [8.88338, 49.41548], [8.81904, 49.40438], [8.83863, 49.47066], [8.89936, 49.48455]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bergstra\u00dfe", "LEVL_CODE": 3, "FID": "DE715", "NUTS_ID": "DE715"}, "id": "DE715"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.05008, 49.86632], [9.03608, 49.8465], [8.79733, 49.73829], [8.55927, 49.72879], [8.53096, 49.86154], [8.56441, 49.93386], [8.62011, 49.96548], [8.6745, 49.95145], [8.63904, 49.93959], [8.57844, 49.83657], [8.66584, 49.80756], [8.73946, 49.8892], [8.72571, 49.95421], [9.01609, 49.99134], [9.05008, 49.86632]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Darmstadt-Dieburg", "LEVL_CODE": 3, "FID": "DE716", "NUTS_ID": "DE716"}, "id": "DE716"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.59132, 50.01769], [8.62011, 49.96548], [8.56441, 49.93386], [8.53096, 49.86154], [8.55927, 49.72879], [8.44837, 49.73366], [8.45711, 49.75617], [8.40006, 49.80368], [8.34303, 49.94051], [8.28825, 49.99513], [8.33064, 50.00054], [8.41086, 50.00203], [8.49412, 50.04735], [8.52072, 50.06171], [8.54308, 50.06913], [8.55307, 50.04735], [8.54369, 50.0302], [8.59132, 50.01769]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Gro\u00df-Gerau", "LEVL_CODE": 3, "FID": "DE717", "NUTS_ID": "DE717"}, "id": "DE717"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.51414, 50.39935], [8.61534, 50.34582], [8.63228, 50.28991], [8.71138, 50.22626], [8.59024, 50.15954], [8.35389, 50.20101], [8.40617, 50.24949], [8.35456, 50.29423], [8.35618, 50.36835], [8.46722, 50.41505], [8.51414, 50.39935]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hochtaunuskreis", "LEVL_CODE": 3, "FID": "DE718", "NUTS_ID": "DE718"}, "id": "DE718"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.47911, 50.44069], [9.54451, 50.3889], [9.73291, 50.35615], [9.73276, 50.30439], [9.62315, 50.22904], [9.50981, 50.23381], [9.50854, 50.10636], [9.40498, 50.08773], [9.35943, 50.12903], [9.22451, 50.14568], [9.15724, 50.11524], [9.03266, 50.11147], [8.99056, 50.06712], [8.81796, 50.13351], [8.78065, 50.13673], [8.78987, 50.17605], [8.86873, 50.27414], [9.00932, 50.24131], [9.14979, 50.25342], [9.24734, 50.34208], [9.28674, 50.4373], [9.32674, 50.4515], [9.37342, 50.38986], [9.47911, 50.44069]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Main-Kinzig-Kreis", "LEVL_CODE": 3, "FID": "DE719", "NUTS_ID": "DE719"}, "id": "DE719"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.59024, 50.15954], [8.48687, 50.09933], [8.52072, 50.06171], [8.49412, 50.04735], [8.41086, 50.00203], [8.33064, 50.00054], [8.37744, 50.04735], [8.32765, 50.14589], [8.35389, 50.20101], [8.59024, 50.15954]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Main-Taunus-Kreis", "LEVL_CODE": 3, "FID": "DE71A", "NUTS_ID": "DE71A"}, "id": "DE71A"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.10301, 49.57746], [9.12235, 49.51849], [9.08328, 49.52583], [8.93188, 49.47064], [8.89936, 49.48455], [8.89957, 49.50366], [8.87108, 49.64241], [8.80345, 49.68682], [8.79733, 49.73829], [9.03608, 49.8465], [9.11961, 49.78829], [9.14073, 49.73841], [9.07717, 49.62027], [9.10301, 49.57746]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Odenwaldkreis", "LEVL_CODE": 3, "FID": "DE71B", "NUTS_ID": "DE71B"}, "id": "DE71B"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.99056, 50.06712], [8.97817, 50.04735], [9.02897, 50.0294], [9.01609, 49.99134], [8.72571, 49.95421], [8.6745, 49.95145], [8.62011, 49.96548], [8.59132, 50.01769], [8.615, 50.04735], [8.71783, 50.06241], [8.8237, 50.07466], [8.81796, 50.13351], [8.99056, 50.06712]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Offenbach, Landkreis", "LEVL_CODE": 3, "FID": "DE71C", "NUTS_ID": "DE71C"}, "id": "DE71C"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.35389, 50.20101], [8.32765, 50.14589], [8.14074, 50.10829], [8.12925, 50.08421], [8.17514, 50.03426], [7.90815, 49.97491], [7.78659, 50.04735], [7.774, 50.06654], [7.91809, 50.12672], [7.89455, 50.18002], [7.91236, 50.20041], [8.02773, 50.22247], [8.03783, 50.26057], [8.12191, 50.27723], [8.2785, 50.26921], [8.35456, 50.29423], [8.40617, 50.24949], [8.35389, 50.20101]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rheingau-Taunus-Kreis", "LEVL_CODE": 3, "FID": "DE71D", "NUTS_ID": "DE71D"}, "id": "DE71D"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.28674, 50.4373], [9.24734, 50.34208], [9.14979, 50.25342], [9.00932, 50.24131], [8.86873, 50.27414], [8.78987, 50.17605], [8.7235, 50.17372], [8.71138, 50.22626], [8.63228, 50.28991], [8.61534, 50.34582], [8.51414, 50.39935], [8.57754, 50.41502], [8.64768, 50.4789], [8.91231, 50.43012], [9.04243, 50.49799], [9.10679, 50.43799], [9.24615, 50.45692], [9.28674, 50.4373]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wetteraukreis", "LEVL_CODE": 3, "FID": "DE71E", "NUTS_ID": "DE71E"}, "id": "DE71E"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.04243, 50.49799], [8.91231, 50.43012], [8.64768, 50.4789], [8.57754, 50.41502], [8.53985, 50.46495], [8.62381, 50.51349], [8.6021, 50.58594], [8.53029, 50.63641], [8.57319, 50.69926], [8.60936, 50.67936], [8.91837, 50.70561], [9.01639, 50.60445], [9.12156, 50.55258], [9.04243, 50.49799]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Gie\u00dfen, Landkreis", "LEVL_CODE": 3, "FID": "DE721", "NUTS_ID": "DE721"}, "id": "DE721"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.3555, 50.862], [8.4388, 50.78957], [8.4547, 50.73294], [8.57319, 50.69926], [8.53029, 50.63641], [8.6021, 50.58594], [8.62381, 50.51349], [8.53985, 50.46495], [8.57754, 50.41502], [8.51414, 50.39935], [8.46722, 50.41505], [8.25065, 50.57332], [8.15159, 50.59937], [8.12578, 50.68581], [8.15699, 50.73037], [8.15445, 50.80528], [8.25925, 50.8711], [8.3555, 50.862]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Lahn-Dill-Kreis", "LEVL_CODE": 3, "FID": "DE722", "NUTS_ID": "DE722"}, "id": "DE722"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.46722, 50.41505], [8.35618, 50.36835], [8.35456, 50.29423], [8.2785, 50.26921], [8.12191, 50.27723], [8.05454, 50.37231], [7.97156, 50.40622], [7.99936, 50.52279], [8.04171, 50.5499], [8.11737, 50.54301], [8.15159, 50.59937], [8.25065, 50.57332], [8.46722, 50.41505]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Limburg-Weilburg", "LEVL_CODE": 3, "FID": "DE723", "NUTS_ID": "DE723"}, "id": "DE723"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.9746, 50.9383], [9.11167, 50.91238], [9.14887, 50.83665], [8.94445, 50.74421], [8.91837, 50.70561], [8.60936, 50.67936], [8.57319, 50.69926], [8.4547, 50.73294], [8.4388, 50.78957], [8.3555, 50.862], [8.47789, 50.96905], [8.58928, 50.95241], [8.68916, 50.99192], [8.9746, 50.9383]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Marburg-Biedenkopf", "LEVL_CODE": 3, "FID": "DE724", "NUTS_ID": "DE724"}, "id": "DE724"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.44106, 50.79564], [9.50323, 50.7434], [9.57768, 50.75785], [9.61146, 50.73548], [9.63846, 50.67845], [9.62577, 50.6204], [9.48488, 50.62909], [9.4373, 50.49028], [9.47911, 50.44069], [9.37342, 50.38986], [9.32674, 50.4515], [9.28674, 50.4373], [9.24615, 50.45692], [9.10679, 50.43799], [9.04243, 50.49799], [9.12156, 50.55258], [9.01639, 50.60445], [8.91837, 50.70561], [8.94445, 50.74421], [9.14887, 50.83665], [9.39307, 50.78106], [9.44106, 50.79564]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Vogelsbergkreis", "LEVL_CODE": 3, "FID": "DE725", "NUTS_ID": "DE725"}, "id": "DE725"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.56803, 51.34], [9.54441, 51.29199], [9.48748, 51.26697], [9.36868, 51.30347], [9.38919, 51.34897], [9.55729, 51.35138], [9.56803, 51.34]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kassel, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE731", "NUTS_ID": "DE731"}, "id": "DE731"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.92618, 50.76739], [9.89996, 50.64992], [9.94368, 50.63634], [10.01691, 50.66832], [10.07756, 50.63762], [10.04611, 50.60897], [10.04134, 50.51647], [9.95493, 50.42369], [9.93566, 50.41961], [9.86753, 50.40222], [9.77446, 50.42127], [9.73291, 50.35615], [9.54451, 50.3889], [9.47911, 50.44069], [9.4373, 50.49028], [9.48488, 50.62909], [9.62577, 50.6204], [9.63846, 50.67845], [9.61146, 50.73548], [9.67285, 50.72854], [9.78465, 50.79918], [9.92618, 50.76739]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Fulda", "LEVL_CODE": 3, "FID": "DE732", "NUTS_ID": "DE732"}, "id": "DE732"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.73462, 52.6383], [9.95493, 52.59048], [9.99913, 52.54041], [10.13487, 52.50538], [10.27374, 52.51064], [10.29191, 52.4479], [10.15531, 52.39322], [10.13397, 52.34571], [10.03447, 52.28377], [9.95493, 52.28402], [9.81167, 52.25406], [9.78929, 52.18418], [9.70624, 52.19268], [9.7058, 52.15025], [9.62965, 52.13126], [9.51522, 52.16765], [9.44831, 52.27432], [9.40753, 52.31238], [9.41699, 52.38935], [9.33268, 52.39126], [9.3452, 52.43342], [9.27291, 52.45474], [9.25554, 52.53569], [9.34012, 52.5589], [9.36213, 52.6046], [9.51478, 52.63152], [9.53466, 52.66007], [9.58767, 52.66711], [9.67583, 52.60366], [9.73462, 52.6383]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Region Hannover", "LEVL_CODE": 3, "FID": "DE929", "NUTS_ID": "DE929"}, "id": "DE929"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.43955, 52.81169], [10.37445, 52.72135], [10.40082, 52.58132], [10.27374, 52.51064], [10.13487, 52.50538], [9.99913, 52.54041], [9.95493, 52.59048], [9.73462, 52.6383], [9.74276, 52.71334], [9.82387, 52.75205], [9.82606, 52.81169], [9.85366, 52.84314], [9.84402, 52.8715], [9.8626, 52.88625], [9.98516, 52.8743], [10.23713, 52.9363], [10.33266, 52.84319], [10.43955, 52.81169]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Celle", "LEVL_CODE": 3, "FID": "DE931", "NUTS_ID": "DE931"}, "id": "DE931"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.25676, 49.0595], [10.0162, 49.04421], [9.95493, 48.97558], [9.8631, 48.92475], [9.69331, 48.96402], [9.59895, 49.04937], [9.50088, 49.079], [9.52749, 49.10855], [9.65353, 49.14011], [9.83775, 49.27597], [9.85556, 49.36837], [9.95493, 49.36013], [10.11197, 49.38491], [10.14612, 49.28266], [10.13509, 49.216], [10.24365, 49.14465], [10.2307, 49.09679], [10.25676, 49.0595]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schw\u00e4bisch Hall", "LEVL_CODE": 3, "FID": "DE11A", "NUTS_ID": "DE11A"}, "id": "DE11A"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.08372, 49.54356], [10.11833, 49.47317], [10.13416, 49.41223], [10.11197, 49.38491], [9.95493, 49.36013], [9.85556, 49.36837], [9.80726, 49.39951], [9.60382, 49.42658], [9.51441, 49.46398], [9.50859, 49.57841], [9.53412, 49.63029], [9.41092, 49.66351], [9.40409, 49.71459], [9.35012, 49.71554], [9.32498, 49.76062], [9.4715, 49.77973], [9.64874, 49.79148], [9.63811, 49.70177], [9.81397, 49.70894], [9.86067, 49.6147], [9.83953, 49.56227], [9.90825, 49.56429], [9.92211, 49.50029], [9.95493, 49.48229], [10.0188, 49.4908], [10.08372, 49.54356]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Main-Tauber-Kreis", "LEVL_CODE": 3, "FID": "DE11B", "NUTS_ID": "DE11B"}, "id": "DE11B"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.42369, 48.7445], [10.48726, 48.69666], [10.36297, 48.66722], [10.27235, 48.69198], [10.31085, 48.54907], [10.27825, 48.5161], [10.23078, 48.51051], [9.94407, 48.63176], [9.95493, 48.66714], [9.98777, 48.72725], [10.03621, 48.75673], [10.21456, 48.77758], [10.32931, 48.73479], [10.42369, 48.7445]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Heidenheim", "LEVL_CODE": 3, "FID": "DE11C", "NUTS_ID": "DE11C"}, "id": "DE11C"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.41015, 48.97746], [10.45376, 48.90438], [10.42369, 48.7445], [10.32931, 48.73479], [10.21456, 48.77758], [10.03621, 48.75673], [9.98777, 48.72725], [9.81447, 48.731], [9.6266, 48.77438], [9.65012, 48.81718], [9.75847, 48.83853], [9.69331, 48.96402], [9.8631, 48.92475], [9.95493, 48.97558], [10.0162, 49.04421], [10.25676, 49.0595], [10.41015, 48.97746]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ostalbkreis", "LEVL_CODE": 3, "FID": "DE11D", "NUTS_ID": "DE11D"}, "id": "DE11D"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.1535, 48.73404], [8.1877, 48.75552], [8.16265, 48.80385], [8.17922, 48.83697], [8.27344, 48.79549], [8.32042, 48.70489], [8.2826, 48.67227], [8.23704, 48.67017], [8.20827, 48.70489], [8.1535, 48.73404]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Baden-Baden, Stadtkreis", "LEVL_CODE": 3, "FID": "DE121", "NUTS_ID": "DE121"}, "id": "DE121"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.33997, 49.08015], [8.49872, 49.04438], [8.50974, 48.9534], [8.27735, 48.98994], [8.33997, 49.08015]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Karlsruhe, Stadtkreis", "LEVL_CODE": 3, "FID": "DE122", "NUTS_ID": "DE122"}, "id": "DE122"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.81823, 49.1945], [8.83142, 49.14366], [8.88212, 49.10718], [8.87779, 49.05848], [8.75614, 49.03902], [8.71415, 48.99522], [8.56986, 48.97925], [8.48181, 48.84637], [8.49357, 48.82018], [8.41663, 48.83537], [8.31184, 48.86621], [8.3021, 48.90453], [8.33058, 48.93331], [8.26128, 48.98092], [8.27735, 48.98994], [8.50974, 48.9534], [8.49872, 49.04438], [8.33997, 49.08015], [8.41307, 49.24982], [8.46699, 49.28298], [8.48727, 49.29003], [8.81823, 49.1945]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Karlsruhe, Landkreis", "LEVL_CODE": 3, "FID": "DE123", "NUTS_ID": "DE123"}, "id": "DE123"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.41663, 48.83537], [8.42175, 48.7816], [8.47495, 48.74909], [8.46128, 48.70581], [8.40932, 48.63946], [8.31383, 48.5919], [8.22201, 48.60323], [7.95963, 48.71858], [8.08964, 48.80897], [8.16889, 48.92663], [8.23263, 48.96657], [8.26128, 48.98092], [8.33058, 48.93331], [8.3021, 48.90453], [8.31184, 48.86621], [8.41663, 48.83537]], [[8.1535, 48.73404], [8.20827, 48.70489], [8.23704, 48.67017], [8.2826, 48.67227], [8.32042, 48.70489], [8.27344, 48.79549], [8.17922, 48.83697], [8.16265, 48.80385], [8.1877, 48.75552], [8.1535, 48.73404]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rastatt", "LEVL_CODE": 3, "FID": "DE124", "NUTS_ID": "DE124"}, "id": "DE124"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.6003, 49.42229], [8.60805, 49.4303], [8.75404, 49.45028], [8.78437, 49.40892], [8.72453, 49.36143], [8.64767, 49.35366], [8.61451, 49.36611], [8.63066, 49.40412], [8.6003, 49.42229]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Heidelberg, Stadtkreis", "LEVL_CODE": 3, "FID": "DE125", "NUTS_ID": "DE125"}, "id": "DE125"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.58138, 49.51978], [8.57077, 49.42308], [8.49732, 49.41135], [8.50153, 49.43486], [8.47251, 49.44351], [8.42307, 49.54182], [8.4227, 49.57419], [8.42244, 49.58339], [8.58138, 49.51978]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mannheim, Stadtkreis", "LEVL_CODE": 3, "FID": "DE126", "NUTS_ID": "DE126"}, "id": "DE126"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.60382, 49.42658], [9.56855, 49.38056], [9.46944, 49.38821], [9.44349, 49.36434], [9.33421, 49.37742], [9.24945, 49.30849], [9.17723, 49.32412], [9.12769, 49.27665], [9.04913, 49.29286], [8.9561, 49.38581], [9.03006, 49.43415], [9.08328, 49.52583], [9.12235, 49.51849], [9.10301, 49.57746], [9.2241, 49.58015], [9.29593, 49.64494], [9.41092, 49.66351], [9.53412, 49.63029], [9.50859, 49.57841], [9.51441, 49.46398], [9.60382, 49.42658]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neckar-Odenwald-Kreis", "LEVL_CODE": 3, "FID": "DE127", "NUTS_ID": "DE127"}, "id": "DE127"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.89957, 49.50366], [8.89936, 49.48455], [8.83863, 49.47066], [8.81904, 49.40438], [8.88338, 49.41548], [8.93188, 49.47064], [9.08328, 49.52583], [9.03006, 49.43415], [8.9561, 49.38581], [9.04913, 49.29286], [8.87727, 49.18082], [8.81823, 49.1945], [8.48727, 49.29003], [8.47092, 49.34071], [8.49732, 49.41135], [8.57077, 49.42308], [8.58138, 49.51978], [8.61203, 49.54906], [8.60449, 49.60679], [8.65499, 49.61775], [8.724, 49.53045], [8.89957, 49.50366]], [[8.6003, 49.42229], [8.63066, 49.40412], [8.61451, 49.36611], [8.64767, 49.35366], [8.72453, 49.36143], [8.78437, 49.40892], [8.75404, 49.45028], [8.60805, 49.4303], [8.6003, 49.42229]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rhein-Neckar-Kreis", "LEVL_CODE": 3, "FID": "DE128", "NUTS_ID": "DE128"}, "id": "DE128"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.69736, 48.8372], [8.69103, 48.83527], [8.64352, 48.85166], [8.64333, 48.90829], [8.73875, 48.92086], [8.78868, 48.88037], [8.76166, 48.83432], [8.69736, 48.8372]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Pforzheim, Stadtkreis", "LEVL_CODE": 3, "FID": "DE129", "NUTS_ID": "DE129"}, "id": "DE129"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.80418, 48.77778], [8.85644, 48.70581], [8.75884, 48.58816], [8.7689, 48.52184], [8.75571, 48.50414], [8.6202, 48.50911], [8.61226, 48.53915], [8.40932, 48.63946], [8.46128, 48.70581], [8.47495, 48.74909], [8.42175, 48.7816], [8.41663, 48.83537], [8.49357, 48.82018], [8.53309, 48.79447], [8.69103, 48.83527], [8.69736, 48.8372], [8.74457, 48.79102], [8.80418, 48.77778]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Calw", "LEVL_CODE": 3, "FID": "DE12A", "NUTS_ID": "DE12A"}, "id": "DE12A"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.87603, 49.03517], [8.93638, 48.95555], [8.91296, 48.93047], [8.9288, 48.86656], [8.88603, 48.8398], [8.88347, 48.80206], [8.80418, 48.77778], [8.74457, 48.79102], [8.69736, 48.8372], [8.76166, 48.83432], [8.78868, 48.88037], [8.73875, 48.92086], [8.64333, 48.90829], [8.64352, 48.85166], [8.69103, 48.83527], [8.53309, 48.79447], [8.49357, 48.82018], [8.48181, 48.84637], [8.56986, 48.97925], [8.71415, 48.99522], [8.75614, 49.03902], [8.87779, 49.05848], [8.87603, 49.03517]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Enzkreis", "LEVL_CODE": 3, "FID": "DE12B", "NUTS_ID": "DE12B"}, "id": "DE12B"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.75571, 48.50414], [8.81457, 48.46532], [8.77406, 48.41628], [8.73719, 48.37798], [8.52011, 48.40176], [8.45274, 48.30972], [8.34197, 48.37909], [8.30399, 48.34909], [8.24432, 48.39777], [8.26434, 48.47152], [8.21905, 48.52404], [8.22201, 48.60323], [8.31383, 48.5919], [8.40932, 48.63946], [8.61226, 48.53915], [8.6202, 48.50911], [8.75571, 48.50414]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Freudenstadt", "LEVL_CODE": 3, "FID": "DE12C", "NUTS_ID": "DE12C"}, "id": "DE12C"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.8506, 48.05204], [7.92069, 48.00192], [7.89408, 47.91261], [7.82066, 47.96896], [7.68554, 47.9748], [7.71174, 48.02472], [7.7716, 48.02719], [7.82471, 48.06903], [7.8506, 48.05204]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Freiburg im Breisgau, Stadtkreis", "LEVL_CODE": 3, "FID": "DE131", "NUTS_ID": "DE131"}, "id": "DE131"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.82471, 48.06903], [7.7716, 48.02719], [7.71174, 48.02472], [7.68554, 47.9748], [7.82066, 47.96896], [7.89408, 47.91261], [7.92069, 48.00192], [7.8506, 48.05204], [8.05783, 48.05915], [8.13665, 48.00915], [8.30371, 47.98453], [8.43903, 47.85364], [8.25741, 47.8445], [8.21035, 47.77305], [8.13196, 47.78693], [8.06747, 47.84683], [8.01811, 47.84139], [7.90843, 47.87766], [7.78478, 47.7986], [7.58343, 47.77206], [7.54599, 47.74357], [7.53824, 47.79282], [7.61439, 47.97575], [7.57408, 48.04033], [7.57729, 48.11566], [7.73015, 48.11288], [7.82471, 48.06903]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Breisgau-Hochschwarzwald", "LEVL_CODE": 3, "FID": "DE132", "NUTS_ID": "DE132"}, "id": "DE132"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.18034, 48.18392], [8.13341, 48.14629], [8.14528, 48.09216], [8.11502, 48.04566], [8.13665, 48.00915], [8.05783, 48.05915], [7.8506, 48.05204], [7.82471, 48.06903], [7.73015, 48.11288], [7.57729, 48.11566], [7.57792, 48.12139], [7.68071, 48.25727], [7.95456, 48.20656], [8.15329, 48.221], [8.18034, 48.18392]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Emmendingen", "LEVL_CODE": 3, "FID": "DE133", "NUTS_ID": "DE133"}, "id": "DE133"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.22201, 48.60323], [8.21905, 48.52404], [8.26434, 48.47152], [8.24432, 48.39777], [8.30399, 48.34909], [8.3198, 48.30971], [8.28894, 48.28456], [8.30443, 48.20689], [8.28273, 48.17167], [8.18034, 48.18392], [8.15329, 48.221], [7.95456, 48.20656], [7.68071, 48.25727], [7.73613, 48.3358], [7.81259, 48.599], [7.94368, 48.70581], [7.95963, 48.71858], [8.22201, 48.60323]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ortenaukreis", "LEVL_CODE": 3, "FID": "DE134", "NUTS_ID": "DE134"}, "id": "DE134"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.73719, 48.37798], [8.67388, 48.2949], [8.75124, 48.15954], [8.60428, 48.07472], [8.49361, 48.17406], [8.28273, 48.17167], [8.30443, 48.20689], [8.28894, 48.28456], [8.3198, 48.30971], [8.30399, 48.34909], [8.34197, 48.37909], [8.45274, 48.30972], [8.52011, 48.40176], [8.73719, 48.37798]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rottweil", "LEVL_CODE": 3, "FID": "DE135", "NUTS_ID": "DE135"}, "id": "DE135"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.28273, 48.17167], [8.49361, 48.17406], [8.60428, 48.07472], [8.65748, 47.99], [8.59578, 47.93412], [8.64015, 47.85481], [8.61383, 47.80108], [8.51012, 47.77619], [8.49573, 47.82305], [8.43903, 47.85364], [8.30371, 47.98453], [8.13665, 48.00915], [8.11502, 48.04566], [8.14528, 48.09216], [8.13341, 48.14629], [8.18034, 48.18392], [8.28273, 48.17167]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schwarzwald-Baar-Kreis", "LEVL_CODE": 3, "FID": "DE136", "NUTS_ID": "DE136"}, "id": "DE136"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.95376, 48.10728], [8.98377, 48.07689], [8.96665, 48.04586], [9.01216, 48.00531], [9.01205, 47.93692], [8.64015, 47.85481], [8.59578, 47.93412], [8.65748, 47.99], [8.60428, 48.07472], [8.75124, 48.15954], [8.79953, 48.18042], [8.8905, 48.11141], [8.95376, 48.10728]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Tuttlingen", "LEVL_CODE": 3, "FID": "DE137", "NUTS_ID": "DE137"}, "id": "DE137"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.12858, 47.84682], [9.04452, 47.79946], [9.1771, 47.72435], [9.2063, 47.67728], [9.18219, 47.65589], [9.03586, 47.68395], [8.87489, 47.65467], [8.81439, 47.72001], [8.78152, 47.71253], [8.79571, 47.6756], [8.67046, 47.68486], [8.66335, 47.68589], [8.70673, 47.71025], [8.70534, 47.73718], [8.61383, 47.80108], [8.64015, 47.85481], [9.01205, 47.93692], [9.13501, 47.896], [9.12858, 47.84682]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Konstanz", "LEVL_CODE": 3, "FID": "DE138", "NUTS_ID": "DE138"}, "id": "DE138"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.01811, 47.84139], [7.96622, 47.69473], [7.89008, 47.64011], [7.89411, 47.58638], [7.71378, 47.53941], [7.6341, 47.56111], [7.58904, 47.58988], [7.5205, 47.6782], [7.54599, 47.74357], [7.58343, 47.77206], [7.78478, 47.7986], [7.90843, 47.87766], [8.01811, 47.84139]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "L\u00f6rrach", "LEVL_CODE": 3, "FID": "DE139", "NUTS_ID": "DE139"}, "id": "DE139"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.51012, 47.77619], [8.41842, 47.70026], [8.42477, 47.67726], [8.50282, 47.64896], [8.60637, 47.66901], [8.5956, 47.60554], [8.56284, 47.59943], [8.51855, 47.62564], [8.42643, 47.56755], [8.22046, 47.61682], [8.03864, 47.55345], [7.92276, 47.55494], [7.89411, 47.58638], [7.89008, 47.64011], [7.96622, 47.69473], [8.01811, 47.84139], [8.06747, 47.84683], [8.13196, 47.78693], [8.21035, 47.77305], [8.25741, 47.8445], [8.43903, 47.85364], [8.49573, 47.82305], [8.51012, 47.77619]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Waldshut", "LEVL_CODE": 3, "FID": "DE13A", "NUTS_ID": "DE13A"}, "id": "DE13A"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.58248, 48.53915], [9.58517, 48.53674], [9.62964, 48.41787], [9.49005, 48.23271], [9.40281, 48.2031], [9.34757, 48.2395], [9.29419, 48.27624], [9.18703, 48.28084], [9.20195, 48.32932], [9.12948, 48.3729], [9.13068, 48.47315], [9.16616, 48.53787], [9.12918, 48.60127], [9.15123, 48.60406], [9.39251, 48.53643], [9.58248, 48.53915]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Reutlingen", "LEVL_CODE": 3, "FID": "DE141", "NUTS_ID": "DE141"}, "id": "DE141"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.12948, 48.3729], [8.77406, 48.41628], [8.81457, 48.46532], [8.75571, 48.50414], [8.7689, 48.52184], [8.84156, 48.50725], [8.95868, 48.58352], [9.12918, 48.60127], [9.16616, 48.53787], [9.13068, 48.47315], [9.12948, 48.3729]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "T\u00fcbingen, Landkreis", "LEVL_CODE": 3, "FID": "DE142", "NUTS_ID": "DE142"}, "id": "DE142"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.12948, 48.3729], [9.20195, 48.32932], [9.18703, 48.28084], [9.1168, 48.24465], [9.18199, 48.19981], [9.16485, 48.14702], [9.01592, 48.15455], [8.95376, 48.10728], [8.8905, 48.11141], [8.79953, 48.18042], [8.75124, 48.15954], [8.67388, 48.2949], [8.73719, 48.37798], [8.77406, 48.41628], [9.12948, 48.3729]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Zollernalbkreis", "LEVL_CODE": 3, "FID": "DE143", "NUTS_ID": "DE143"}, "id": "DE143"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.99761, 48.35012], [9.95493, 48.31144], [9.84914, 48.36582], [9.90467, 48.4046], [9.91785, 48.43891], [9.95493, 48.44904], [10.03269, 48.4572], [9.99761, 48.35012]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ulm, Stadtkreis", "LEVL_CODE": 3, "FID": "DE144", "NUTS_ID": "DE144"}, "id": "DE144"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.23078, 48.51051], [10.1339, 48.45487], [10.03269, 48.4572], [9.95493, 48.44904], [9.91785, 48.43891], [9.90467, 48.4046], [9.84914, 48.36582], [9.95493, 48.31144], [9.99761, 48.35012], [10.05785, 48.27958], [10.09536, 48.16402], [10.0535, 48.15363], [10.02552, 48.22869], [9.95493, 48.26971], [9.88295, 48.28593], [9.66804, 48.16823], [9.49005, 48.23271], [9.62964, 48.41787], [9.58517, 48.53674], [9.68557, 48.52813], [9.94407, 48.63176], [10.23078, 48.51051]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Alb-Donau-Kreis", "LEVL_CODE": 3, "FID": "DE145", "NUTS_ID": "DE145"}, "id": "DE145"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.09536, 48.16402], [10.13646, 48.10846], [10.11495, 47.97626], [10.10421, 47.97436], [9.95493, 47.95921], [9.89493, 47.98149], [9.8152, 47.95591], [9.63671, 47.97261], [9.60491, 48.00228], [9.52122, 48.066], [9.32531, 48.12084], [9.29798, 48.16185], [9.34757, 48.2395], [9.40281, 48.2031], [9.49005, 48.23271], [9.66804, 48.16823], [9.88295, 48.28593], [9.95493, 48.26971], [10.02552, 48.22869], [10.0535, 48.15363], [10.09536, 48.16402]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Biberach", "LEVL_CODE": 3, "FID": "DE146", "NUTS_ID": "DE146"}, "id": "DE146"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.40941, 47.82161], [9.44598, 47.81753], [9.44735, 47.77623], [9.50361, 47.74188], [9.72541, 47.67549], [9.69215, 47.61042], [9.55872, 47.54189], [9.4956, 47.55146], [9.37943, 47.62392], [9.18219, 47.65589], [9.2063, 47.67728], [9.1771, 47.72435], [9.04452, 47.79946], [9.12858, 47.84682], [9.28317, 47.871], [9.40941, 47.82161]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bodenseekreis", "LEVL_CODE": 3, "FID": "DE147", "NUTS_ID": "DE147"}, "id": "DE147"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.10421, 47.97436], [10.09191, 47.95527], [10.11014, 47.93715], [10.1065, 47.85503], [10.13193, 47.82009], [10.09185, 47.78568], [10.12996, 47.68973], [10.07729, 47.63927], [10.03718, 47.67869], [9.95493, 47.65387], [9.85083, 47.67162], [9.69215, 47.61042], [9.72541, 47.67549], [9.50361, 47.74188], [9.44735, 47.77623], [9.44598, 47.81753], [9.40941, 47.82161], [9.38585, 47.89114], [9.42892, 47.9507], [9.60491, 48.00228], [9.63671, 47.97261], [9.8152, 47.95591], [9.89493, 47.98149], [9.95493, 47.95921], [10.10421, 47.97436]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ravensburg", "LEVL_CODE": 3, "FID": "DE148", "NUTS_ID": "DE148"}, "id": "DE148"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.34757, 48.2395], [9.29798, 48.16185], [9.32531, 48.12084], [9.52122, 48.066], [9.60491, 48.00228], [9.42892, 47.9507], [9.38585, 47.89114], [9.40941, 47.82161], [9.28317, 47.871], [9.12858, 47.84682], [9.13501, 47.896], [9.01205, 47.93692], [9.01216, 48.00531], [8.96665, 48.04586], [8.98377, 48.07689], [8.95376, 48.10728], [9.01592, 48.15455], [9.16485, 48.14702], [9.18199, 48.19981], [9.1168, 48.24465], [9.18703, 48.28084], [9.29419, 48.27624], [9.34757, 48.2395]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Sigmaringen", "LEVL_CODE": 3, "FID": "DE149", "NUTS_ID": "DE149"}, "id": "DE149"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.48932, 48.745], [11.43311, 48.69937], [11.3761, 48.69727], [11.25592, 48.77943], [11.36848, 48.78181], [11.41186, 48.81268], [11.48299, 48.78439], [11.48932, 48.745]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ingolstadt, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE211", "NUTS_ID": "DE211"}, "id": "DE211"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.41245, 48.12712], [11.37186, 48.15946], [11.40294, 48.20312], [11.49982, 48.24815], [11.63871, 48.21391], [11.70614, 48.13639], [11.70212, 48.09745], [11.51004, 48.06779], [11.45449, 48.12344], [11.41245, 48.12712]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00fcnchen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE212", "NUTS_ID": "DE212"}, "id": "DE212"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.06723, 47.82487], [12.07734, 47.86105], [12.10328, 47.88434], [12.13793, 47.88634], [12.15016, 47.84708], [12.12858, 47.81427], [12.06723, 47.82487]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rosenheim, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE213", "NUTS_ID": "DE213"}, "id": "DE213"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.94468, 48.20669], [12.87572, 48.19709], [12.75156, 48.11281], [12.68301, 48.05182], [12.62054, 48.04848], [12.57919, 48.08456], [12.56621, 48.15889], [12.57593, 48.32934], [12.60729, 48.35363], [12.81539, 48.31542], [12.94468, 48.20669]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Alt\u00f6tting", "LEVL_CODE": 3, "FID": "DE214", "NUTS_ID": "DE214"}, "id": "DE214"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.04606, 47.5205], [13.00844, 47.47168], [12.91903, 47.49477], [12.80383, 47.55905], [12.79017, 47.63067], [12.6958, 47.68222], [12.72894, 47.73147], [12.8211, 47.76349], [12.75037, 47.81096], [12.73881, 47.85551], [12.85008, 47.88814], [12.87579, 47.96261], [12.93888, 47.93604], [12.99537, 47.85542], [12.91804, 47.72201], [13.02804, 47.71399], [13.08378, 47.66872], [13.04606, 47.5205]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Berchtesgadener Land", "LEVL_CODE": 3, "FID": "DE215", "NUTS_ID": "DE215"}, "id": "DE215"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.62346, 47.92033], [11.65318, 47.89006], [11.63115, 47.83712], [11.66687, 47.70945], [11.66439, 47.63422], [11.6229, 47.60986], [11.63288, 47.59245], [11.55857, 47.51511], [11.41022, 47.49532], [11.27162, 47.57995], [11.2815, 47.6515], [11.34408, 47.6937], [11.40531, 47.76358], [11.32917, 47.82109], [11.33849, 47.92268], [11.39466, 47.93004], [11.42858, 47.97854], [11.62346, 47.92033]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bad T\u00f6lz-Wolfratshausen", "LEVL_CODE": 3, "FID": "DE216", "NUTS_ID": "DE216"}, "id": "DE216"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.49318, 48.44051], [11.51526, 48.36889], [11.5787, 48.29955], [11.49982, 48.24815], [11.40294, 48.20312], [11.19326, 48.28772], [11.11677, 48.26848], [11.19241, 48.4188], [11.31168, 48.45227], [11.41653, 48.41607], [11.49318, 48.44051]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dachau", "LEVL_CODE": 3, "FID": "DE217", "NUTS_ID": "DE217"}, "id": "DE217"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.04319, 48.14167], [12.08566, 48.12234], [12.08876, 48.00901], [12.06643, 47.97964], [12.01271, 47.96148], [11.94227, 47.97999], [11.92452, 47.93875], [11.84795, 47.94825], [11.77507, 47.99481], [11.75617, 48.22111], [12.04319, 48.14167]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ebersberg", "LEVL_CODE": 3, "FID": "DE218", "NUTS_ID": "DE218"}, "id": "DE218"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.59931, 48.9515], [11.72559, 48.90096], [11.71944, 48.78235], [11.61376, 48.81002], [11.53568, 48.74507], [11.48932, 48.745], [11.48299, 48.78439], [11.41186, 48.81268], [11.36848, 48.78181], [11.25592, 48.77943], [11.00594, 48.82188], [10.93684, 48.86328], [11.04921, 48.89925], [11.07067, 48.94691], [11.14359, 48.97307], [11.20136, 49.04655], [11.32508, 49.00501], [11.39824, 49.03201], [11.38571, 49.07886], [11.53012, 49.06528], [11.59931, 48.9515]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Eichst\u00e4tt", "LEVL_CODE": 3, "FID": "DE219", "NUTS_ID": "DE219"}, "id": "DE219"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.01712, 48.43068], [12.15731, 48.41637], [12.25055, 48.32195], [12.21583, 48.21389], [12.04319, 48.14167], [11.75617, 48.22111], [11.73819, 48.27633], [11.83562, 48.40569], [11.95681, 48.44699], [12.01712, 48.43068]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Erding", "LEVL_CODE": 3, "FID": "DE21A", "NUTS_ID": "DE21A"}, "id": "DE21A"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.91019, 48.58523], [11.95538, 48.56798], [12.01712, 48.43068], [11.95681, 48.44699], [11.83562, 48.40569], [11.73819, 48.27633], [11.6253, 48.27285], [11.5787, 48.29955], [11.51526, 48.36889], [11.49318, 48.44051], [11.55983, 48.48129], [11.64058, 48.4834], [11.70674, 48.61665], [11.91019, 48.58523]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Freising", "LEVL_CODE": 3, "FID": "DE21B", "NUTS_ID": "DE21B"}, "id": "DE21B"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.40294, 48.20312], [11.37186, 48.15946], [11.41245, 48.12712], [11.38487, 48.11145], [11.25909, 48.13108], [11.13697, 48.09762], [11.08733, 48.09741], [11.0179, 48.15715], [11.03451, 48.19299], [11.05797, 48.26192], [11.11677, 48.26848], [11.19326, 48.28772], [11.40294, 48.20312]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "F\u00fcrstenfeldbruck", "LEVL_CODE": 3, "FID": "DE21C", "NUTS_ID": "DE21C"}, "id": "DE21C"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.34408, 47.6937], [11.2815, 47.6515], [11.27162, 47.57995], [11.41022, 47.49532], [11.39562, 47.47201], [11.42143, 47.44485], [11.33283, 47.44425], [11.26506, 47.40418], [11.20839, 47.43053], [11.0165, 47.39637], [10.9912, 47.39613], [10.95779, 47.44936], [10.88427, 47.48404], [10.8862, 47.53685], [10.88452, 47.57069], [10.95279, 47.61627], [10.9872, 47.70901], [11.12873, 47.73197], [11.34408, 47.6937]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Garmisch-Partenkirchen", "LEVL_CODE": 3, "FID": "DE21D", "NUTS_ID": "DE21D"}, "id": "DE21D"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.03451, 48.19299], [11.0179, 48.15715], [11.08733, 48.09741], [11.13697, 48.09762], [11.14959, 47.95341], [10.9726, 47.87553], [10.76709, 47.84379], [10.81935, 47.99373], [10.77879, 48.05214], [10.80153, 48.10417], [10.79569, 48.14295], [10.86307, 48.16464], [10.90341, 48.23668], [11.03451, 48.19299]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Landsberg am Lech", "LEVL_CODE": 3, "FID": "DE21E", "NUTS_ID": "DE21E"}, "id": "DE21E"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.06066, 47.61874], [11.82889, 47.58556], [11.63288, 47.59245], [11.6229, 47.60986], [11.66439, 47.63422], [11.66687, 47.70945], [11.63115, 47.83712], [11.65318, 47.89006], [11.62346, 47.92033], [11.80647, 47.92575], [11.85189, 47.87277], [11.94118, 47.83961], [11.94045, 47.80217], [11.99005, 47.76442], [12.06066, 47.61874]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Miesbach", "LEVL_CODE": 3, "FID": "DE21F", "NUTS_ID": "DE21F"}, "id": "DE21F"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.60729, 48.35363], [12.57593, 48.32934], [12.56621, 48.15889], [12.38756, 48.11738], [12.30412, 48.10769], [12.22014, 48.13765], [12.16115, 48.09138], [12.12984, 48.12378], [12.08566, 48.12234], [12.04319, 48.14167], [12.21583, 48.21389], [12.25055, 48.32195], [12.40037, 48.35977], [12.48379, 48.42968], [12.54338, 48.41528], [12.60729, 48.35363]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00fchldorf a. Inn", "LEVL_CODE": 3, "FID": "DE21G", "NUTS_ID": "DE21G"}, "id": "DE21G"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.73819, 48.27633], [11.75617, 48.22111], [11.77507, 47.99481], [11.84795, 47.94825], [11.80647, 47.92575], [11.62346, 47.92033], [11.42858, 47.97854], [11.41244, 48.09278], [11.38487, 48.11145], [11.41245, 48.12712], [11.45449, 48.12344], [11.51004, 48.06779], [11.70212, 48.09745], [11.70614, 48.13639], [11.63871, 48.21391], [11.49982, 48.24815], [11.5787, 48.29955], [11.6253, 48.27285], [11.73819, 48.27633]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00fcnchen, Landkreis", "LEVL_CODE": 3, "FID": "DE21H", "NUTS_ID": "DE21H"}, "id": "DE21H"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.25592, 48.77943], [11.3761, 48.69727], [11.43311, 48.69937], [11.43224, 48.65584], [11.36954, 48.61685], [11.29541, 48.47457], [11.22761, 48.49036], [11.13574, 48.59638], [11.02299, 48.62017], [10.98362, 48.71263], [10.97843, 48.79066], [11.00594, 48.82188], [11.25592, 48.77943]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neuburg-Schrobenhausen", "LEVL_CODE": 3, "FID": "DE21I", "NUTS_ID": "DE21I"}, "id": "DE21I"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.71944, 48.78235], [11.65692, 48.72266], [11.70674, 48.61665], [11.64058, 48.4834], [11.55983, 48.48129], [11.49318, 48.44051], [11.41653, 48.41607], [11.31168, 48.45227], [11.29541, 48.47457], [11.36954, 48.61685], [11.43224, 48.65584], [11.43311, 48.69937], [11.48932, 48.745], [11.53568, 48.74507], [11.61376, 48.81002], [11.71944, 48.78235]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Pfaffenhofen a. d. Ilm", "LEVL_CODE": 3, "FID": "DE21J", "NUTS_ID": "DE21J"}, "id": "DE21J"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.38756, 48.11738], [12.34524, 48.08477], [12.33947, 47.98758], [12.42999, 47.90898], [12.37216, 47.8639], [12.41483, 47.82124], [12.33805, 47.69709], [12.26407, 47.68489], [12.25335, 47.72412], [12.22836, 47.72096], [12.17222, 47.69503], [12.1946, 47.61679], [12.06066, 47.61874], [11.99005, 47.76442], [11.94045, 47.80217], [11.94118, 47.83961], [11.85189, 47.87277], [11.80647, 47.92575], [11.84795, 47.94825], [11.92452, 47.93875], [11.94227, 47.97999], [12.01271, 47.96148], [12.06643, 47.97964], [12.08876, 48.00901], [12.08566, 48.12234], [12.12984, 48.12378], [12.16115, 48.09138], [12.22014, 48.13765], [12.30412, 48.10769], [12.38756, 48.11738]], [[12.06723, 47.82487], [12.12858, 47.81427], [12.15016, 47.84708], [12.13793, 47.88634], [12.10328, 47.88434], [12.07734, 47.86105], [12.06723, 47.82487]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rosenheim, Landkreis", "LEVL_CODE": 3, "FID": "DE21K", "NUTS_ID": "DE21K"}, "id": "DE21K"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.38487, 48.11145], [11.41244, 48.09278], [11.42858, 47.97854], [11.39466, 47.93004], [11.33849, 47.92268], [11.32917, 47.82109], [11.28498, 47.87634], [11.21381, 47.89198], [11.207, 47.92704], [11.14959, 47.95341], [11.13697, 48.09762], [11.25909, 48.13108], [11.38487, 48.11145]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Starnberg", "LEVL_CODE": 3, "FID": "DE21L", "NUTS_ID": "DE21L"}, "id": "DE21L"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.75156, 48.11281], [12.86018, 47.99664], [12.87579, 47.96261], [12.85008, 47.88814], [12.73881, 47.85551], [12.75037, 47.81096], [12.8211, 47.76349], [12.72894, 47.73147], [12.6958, 47.68222], [12.61397, 47.67111], [12.57503, 47.63232], [12.50472, 47.62962], [12.42904, 47.68912], [12.33805, 47.69709], [12.41483, 47.82124], [12.37216, 47.8639], [12.42999, 47.90898], [12.33947, 47.98758], [12.34524, 48.08477], [12.38756, 48.11738], [12.56621, 48.15889], [12.57919, 48.08456], [12.62054, 48.04848], [12.68301, 48.05182], [12.75156, 48.11281]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Traunstein", "LEVL_CODE": 3, "FID": "DE21M", "NUTS_ID": "DE21M"}, "id": "DE21M"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.32917, 47.82109], [11.40531, 47.76358], [11.34408, 47.6937], [11.12873, 47.73197], [10.9872, 47.70901], [10.95279, 47.61627], [10.8961, 47.66072], [10.7948, 47.66509], [10.78479, 47.69756], [10.73572, 47.69734], [10.76709, 47.84379], [10.9726, 47.87553], [11.14959, 47.95341], [11.207, 47.92704], [11.21381, 47.89198], [11.28498, 47.87634], [11.32917, 47.82109]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Weilheim-Schongau", "LEVL_CODE": 3, "FID": "DE21N", "NUTS_ID": "DE21N"}, "id": "DE21N"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.0804, 48.52544], [12.12809, 48.56825], [12.16929, 48.55087], [12.22398, 48.58496], [12.27082, 48.5886], [12.25562, 48.55895], [12.18988, 48.52134], [12.148, 48.51003], [12.0804, 48.52544]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Landshut, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE221", "NUTS_ID": "DE221"}, "id": "DE221"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.51337, 48.59098], [13.43843, 48.5489], [13.36139, 48.56539], [13.33576, 48.59405], [13.39672, 48.61152], [13.51337, 48.59098]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Passau, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE222", "NUTS_ID": "DE222"}, "id": "DE222"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.50075, 48.86369], [12.54288, 48.92496], [12.59114, 48.9016], [12.66714, 48.90528], [12.64917, 48.8638], [12.53982, 48.84106], [12.50075, 48.86369]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Straubing, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE223", "NUTS_ID": "DE223"}, "id": "DE223"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.19546, 48.86277], [13.25871, 48.74106], [13.22489, 48.70666], [13.13392, 48.69602], [13.0853, 48.63525], [12.99429, 48.60061], [12.96738, 48.61127], [12.93647, 48.65067], [12.82565, 48.68898], [12.764, 48.79791], [12.76482, 48.82624], [12.83827, 48.84104], [12.89143, 48.95447], [12.99572, 48.94423], [13.05695, 48.89017], [13.19546, 48.86277]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Deggendorf", "LEVL_CODE": 3, "FID": "DE224", "NUTS_ID": "DE224"}, "id": "DE224"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.55158, 48.96779], [13.67285, 48.8876], [13.73596, 48.8768], [13.83951, 48.77161], [13.79611, 48.7136], [13.60556, 48.68519], [13.40787, 48.75452], [13.25871, 48.74106], [13.19546, 48.86277], [13.27579, 48.88046], [13.2944, 48.95042], [13.41665, 48.98004], [13.49318, 48.95058], [13.55158, 48.96779]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Freyung-Grafenau", "LEVL_CODE": 3, "FID": "DE225", "NUTS_ID": "DE225"}, "id": "DE225"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.0975, 48.77199], [11.90958, 48.69945], [11.88529, 48.65002], [11.91019, 48.58523], [11.70674, 48.61665], [11.65692, 48.72266], [11.71944, 48.78235], [11.72559, 48.90096], [11.59931, 48.9515], [11.61839, 48.99942], [11.65979, 49.01613], [11.76892, 48.993], [11.85982, 49.01682], [11.92906, 48.95826], [12.06798, 48.94184], [12.11911, 48.88279], [12.0975, 48.77199]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kelheim", "LEVL_CODE": 3, "FID": "DE226", "NUTS_ID": "DE226"}, "id": "DE226"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.13528, 48.76591], [12.32939, 48.72144], [12.38865, 48.65608], [12.35864, 48.57561], [12.40927, 48.54688], [12.47917, 48.55604], [12.50815, 48.50298], [12.47758, 48.47922], [12.48379, 48.42968], [12.40037, 48.35977], [12.25055, 48.32195], [12.15731, 48.41637], [12.01712, 48.43068], [11.95538, 48.56798], [11.91019, 48.58523], [11.88529, 48.65002], [11.90958, 48.69945], [12.0975, 48.77199], [12.13528, 48.76591]], [[12.0804, 48.52544], [12.148, 48.51003], [12.18988, 48.52134], [12.25562, 48.55895], [12.27082, 48.5886], [12.22398, 48.58496], [12.16929, 48.55087], [12.12809, 48.56825], [12.0804, 48.52544]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Landshut, Landkreis", "LEVL_CODE": 3, "FID": "DE227", "NUTS_ID": "DE227"}, "id": "DE227"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.79611, 48.7136], [13.81937, 48.61293], [13.72709, 48.51302], [13.51337, 48.59098], [13.39672, 48.61152], [13.33576, 48.59405], [13.36139, 48.56539], [13.43843, 48.5489], [13.44987, 48.50526], [13.40162, 48.37149], [13.27713, 48.3024], [13.17704, 48.29439], [13.09614, 48.3683], [13.14631, 48.42278], [13.08618, 48.53466], [12.99429, 48.60061], [13.0853, 48.63525], [13.13392, 48.69602], [13.22489, 48.70666], [13.25871, 48.74106], [13.40787, 48.75452], [13.60556, 48.68519], [13.79611, 48.7136]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Passau, Landkreis", "LEVL_CODE": 3, "FID": "DE228", "NUTS_ID": "DE228"}, "id": "DE228"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.41665, 48.98004], [13.2944, 48.95042], [13.27579, 48.88046], [13.19546, 48.86277], [13.05695, 48.89017], [12.99572, 48.94423], [12.89143, 48.95447], [12.7682, 49.11476], [12.98649, 49.16236], [13.12575, 49.11842], [13.17091, 49.17358], [13.19894, 49.13117], [13.3763, 49.06482], [13.41665, 48.98004]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Regen", "LEVL_CODE": 3, "FID": "DE229", "NUTS_ID": "DE229"}, "id": "DE229"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.99429, 48.60061], [13.08618, 48.53466], [13.14631, 48.42278], [13.09614, 48.3683], [13.17704, 48.29439], [13.03692, 48.26127], [12.94468, 48.20669], [12.81539, 48.31542], [12.60729, 48.35363], [12.54338, 48.41528], [12.48379, 48.42968], [12.47758, 48.47922], [12.50815, 48.50298], [12.66665, 48.49798], [12.79281, 48.59381], [12.96738, 48.61127], [12.99429, 48.60061]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rottal-Inn", "LEVL_CODE": 3, "FID": "DE22A", "NUTS_ID": "DE22A"}, "id": "DE22A"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.89143, 48.95447], [12.83827, 48.84104], [12.76482, 48.82624], [12.764, 48.79791], [12.59492, 48.72899], [12.45159, 48.76545], [12.32939, 48.72144], [12.13528, 48.76591], [12.24695, 48.83371], [12.41372, 48.86949], [12.47903, 49.03199], [12.56873, 49.08867], [12.7682, 49.11476], [12.89143, 48.95447]], [[12.50075, 48.86369], [12.53982, 48.84106], [12.64917, 48.8638], [12.66714, 48.90528], [12.59114, 48.9016], [12.54288, 48.92496], [12.50075, 48.86369]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Straubing-Bogen", "LEVL_CODE": 3, "FID": "DE22B", "NUTS_ID": "DE22B"}, "id": "DE22B"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.96738, 48.61127], [12.79281, 48.59381], [12.66665, 48.49798], [12.50815, 48.50298], [12.47917, 48.55604], [12.40927, 48.54688], [12.35864, 48.57561], [12.38865, 48.65608], [12.32939, 48.72144], [12.45159, 48.76545], [12.59492, 48.72899], [12.764, 48.79791], [12.82565, 48.68898], [12.93647, 48.65067], [12.96738, 48.61127]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dingolfing-Landau", "LEVL_CODE": 3, "FID": "DE22C", "NUTS_ID": "DE22C"}, "id": "DE22C"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.77799, 49.44354], [11.79493, 49.47032], [11.84514, 49.46934], [11.87419, 49.49678], [11.91353, 49.44569], [11.83649, 49.41206], [11.78154, 49.42779], [11.77799, 49.44354]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Amberg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE231", "NUTS_ID": "DE231"}, "id": "DE231"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.03092, 49.00987], [12.05166, 49.03882], [12.09908, 49.043], [12.09524, 49.06265], [12.13867, 49.0676], [12.16376, 49.04811], [12.15949, 49.01927], [12.18605, 49.00376], [12.17467, 48.97337], [12.07818, 48.97067], [12.06591, 48.99787], [12.03092, 49.00987]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Regensburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE232", "NUTS_ID": "DE232"}, "id": "DE232"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.07667, 49.69594], [12.13111, 49.71076], [12.18144, 49.70238], [12.25967, 49.66088], [12.23996, 49.63221], [12.22056, 49.65755], [12.16945, 49.66231], [12.14028, 49.61688], [12.08415, 49.65746], [12.07667, 49.69594]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Weiden i. d. Opf, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE233", "NUTS_ID": "DE233"}, "id": "DE233"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.11549, 49.54796], [12.03885, 49.48613], [12.02401, 49.43634], [11.98783, 49.42683], [12.05576, 49.36745], [11.98034, 49.3139], [11.93422, 49.22107], [11.88554, 49.27401], [11.65633, 49.33014], [11.55776, 49.41904], [11.58564, 49.47122], [11.54912, 49.54412], [11.58743, 49.63177], [11.5588, 49.68872], [11.58331, 49.74053], [11.63066, 49.76057], [11.65427, 49.63437], [11.95103, 49.6408], [11.99745, 49.58196], [12.11549, 49.54796]], [[11.77799, 49.44354], [11.78154, 49.42779], [11.83649, 49.41206], [11.91353, 49.44569], [11.87419, 49.49678], [11.84514, 49.46934], [11.79493, 49.47032], [11.77799, 49.44354]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Amberg-Sulzbach", "LEVL_CODE": 3, "FID": "DE234", "NUTS_ID": "DE234"}, "id": "DE234"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.17091, 49.17358], [13.12575, 49.11842], [12.98649, 49.16236], [12.7682, 49.11476], [12.56873, 49.08867], [12.47903, 49.03199], [12.43273, 49.05199], [12.40936, 49.09589], [12.32624, 49.09544], [12.31105, 49.15247], [12.38811, 49.2591], [12.47736, 49.28139], [12.49071, 49.38102], [12.54181, 49.44775], [12.63376, 49.4762], [12.79174, 49.34986], [12.93187, 49.34404], [13.01076, 49.30647], [13.17091, 49.17358]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Cham", "LEVL_CODE": 3, "FID": "DE235", "NUTS_ID": "DE235"}, "id": "DE235"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.93422, 49.22107], [11.9359, 49.19507], [11.80109, 49.1594], [11.68047, 49.07315], [11.65979, 49.01613], [11.61839, 48.99942], [11.59931, 48.9515], [11.53012, 49.06528], [11.38571, 49.07886], [11.283, 49.19132], [11.28866, 49.2513], [11.20608, 49.28809], [11.24718, 49.32535], [11.34483, 49.31108], [11.46419, 49.41535], [11.55776, 49.41904], [11.65633, 49.33014], [11.88554, 49.27401], [11.93422, 49.22107]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neumarkt i. d. OPf.", "LEVL_CODE": 3, "FID": "DE236", "NUTS_ID": "DE236"}, "id": "DE236"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.40152, 49.75837], [12.51586, 49.67998], [12.59378, 49.54219], [12.33056, 49.51147], [12.20755, 49.58585], [12.11549, 49.54796], [11.99745, 49.58196], [11.95103, 49.6408], [11.65427, 49.63437], [11.63066, 49.76057], [11.65225, 49.80189], [11.85484, 49.84711], [11.89659, 49.80867], [12.06027, 49.78822], [12.17133, 49.84383], [12.24907, 49.77509], [12.40152, 49.75837]], [[12.07667, 49.69594], [12.08415, 49.65746], [12.14028, 49.61688], [12.16945, 49.66231], [12.22056, 49.65755], [12.23996, 49.63221], [12.25967, 49.66088], [12.18144, 49.70238], [12.13111, 49.71076], [12.07667, 49.69594]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neustadt a. d. Waldnaab", "LEVL_CODE": 3, "FID": "DE237", "NUTS_ID": "DE237"}, "id": "DE237"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.31105, 49.15247], [12.32624, 49.09544], [12.40936, 49.09589], [12.43273, 49.05199], [12.47903, 49.03199], [12.41372, 48.86949], [12.24695, 48.83371], [12.13528, 48.76591], [12.0975, 48.77199], [12.11911, 48.88279], [12.06798, 48.94184], [11.92906, 48.95826], [11.85982, 49.01682], [11.76892, 48.993], [11.65979, 49.01613], [11.68047, 49.07315], [11.80109, 49.1594], [11.9359, 49.19507], [12.09511, 49.15078], [12.17083, 49.21315], [12.24764, 49.15602], [12.31105, 49.15247]], [[12.03092, 49.00987], [12.06591, 48.99787], [12.07818, 48.97067], [12.17467, 48.97337], [12.18605, 49.00376], [12.15949, 49.01927], [12.16376, 49.04811], [12.13867, 49.0676], [12.09524, 49.06265], [12.09908, 49.043], [12.05166, 49.03882], [12.03092, 49.00987]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Regensburg, Landkreis", "LEVL_CODE": 3, "FID": "DE238", "NUTS_ID": "DE238"}, "id": "DE238"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.59378, 49.54219], [12.63599, 49.51865], [12.63376, 49.4762], [12.54181, 49.44775], [12.49071, 49.38102], [12.47736, 49.28139], [12.38811, 49.2591], [12.31105, 49.15247], [12.24764, 49.15602], [12.17083, 49.21315], [12.09511, 49.15078], [11.9359, 49.19507], [11.93422, 49.22107], [11.98034, 49.3139], [12.05576, 49.36745], [11.98783, 49.42683], [12.02401, 49.43634], [12.03885, 49.48613], [12.11549, 49.54796], [12.20755, 49.58585], [12.33056, 49.51147], [12.59378, 49.54219]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schwandorf", "LEVL_CODE": 3, "FID": "DE239", "NUTS_ID": "DE239"}, "id": "DE239"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.55099, 49.90509], [12.46346, 49.78847], [12.40152, 49.75837], [12.24907, 49.77509], [12.17133, 49.84383], [12.06027, 49.78822], [11.89659, 49.80867], [11.85484, 49.84711], [11.82257, 49.95335], [11.90369, 49.97902], [12.1294, 49.98575], [12.2608, 50.05816], [12.4815, 49.97546], [12.49203, 49.9363], [12.55099, 49.90509]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Tirschenreuth", "LEVL_CODE": 3, "FID": "DE23A", "NUTS_ID": "DE23A"}, "id": "DE23A"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.82773, 49.89805], [10.83339, 49.91439], [10.91635, 49.92668], [10.9429, 49.9109], [10.94865, 49.8561], [10.92703, 49.84335], [10.83386, 49.88087], [10.82773, 49.89805]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bamberg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE241", "NUTS_ID": "DE241"}, "id": "DE241"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.51257, 49.9493], [11.56159, 49.9741], [11.63239, 49.96971], [11.65216, 49.93938], [11.62371, 49.92226], [11.61442, 49.89375], [11.57335, 49.89222], [11.52214, 49.92867], [11.51257, 49.9493]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bayreuth, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE242", "NUTS_ID": "DE242"}, "id": "DE242"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.88658, 50.28253], [10.96017, 50.29816], [11.02496, 50.25289], [10.97803, 50.22117], [10.96028, 50.24343], [10.91627, 50.24793], [10.88658, 50.28253]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Coburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE243", "NUTS_ID": "DE243"}, "id": "DE243"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.81855, 50.31053], [11.86895, 50.32593], [11.88137, 50.35337], [11.95034, 50.34468], [11.9652, 50.32598], [11.93494, 50.28495], [11.8848, 50.26873], [11.84036, 50.28655], [11.81855, 50.31053]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hof, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE244", "NUTS_ID": "DE244"}, "id": "DE244"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.2464, 49.98956], [11.20002, 49.90135], [11.23811, 49.87047], [11.21776, 49.84197], [11.12589, 49.8377], [11.07291, 49.79955], [10.92734, 49.76843], [10.80855, 49.74622], [10.71719, 49.78099], [10.65442, 49.72112], [10.55144, 49.75577], [10.44896, 49.82218], [10.4578, 49.86821], [10.50525, 49.87668], [10.67288, 49.89091], [10.86012, 50.09179], [10.88477, 50.09314], [10.92291, 50.04996], [10.99649, 50.02898], [11.18839, 50.0609], [11.19899, 50.01471], [11.2464, 49.98956]], [[10.82773, 49.89805], [10.83386, 49.88087], [10.92703, 49.84335], [10.94865, 49.8561], [10.9429, 49.9109], [10.91635, 49.92668], [10.83339, 49.91439], [10.82773, 49.89805]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bamberg, Landkreis", "LEVL_CODE": 3, "FID": "DE245", "NUTS_ID": "DE245"}, "id": "DE245"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.78911, 50.10325], [11.8005, 50.083], [11.90369, 49.97902], [11.82257, 49.95335], [11.85484, 49.84711], [11.65225, 49.80189], [11.63066, 49.76057], [11.58331, 49.74053], [11.5588, 49.68872], [11.54539, 49.64704], [11.4363, 49.62695], [11.36869, 49.66671], [11.37063, 49.78955], [11.23811, 49.87047], [11.20002, 49.90135], [11.2464, 49.98956], [11.25681, 49.99996], [11.38217, 49.94246], [11.42654, 49.98114], [11.59962, 50.01935], [11.67401, 50.083], [11.67809, 50.1246], [11.78911, 50.10325]], [[11.51257, 49.9493], [11.52214, 49.92867], [11.57335, 49.89222], [11.61442, 49.89375], [11.62371, 49.92226], [11.65216, 49.93938], [11.63239, 49.96971], [11.56159, 49.9741], [11.51257, 49.9493]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bayreuth, Landkreis", "LEVL_CODE": 3, "FID": "DE246", "NUTS_ID": "DE246"}, "id": "DE246"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.18994, 50.27119], [11.19819, 50.2021], [11.17563, 50.19033], [11.01266, 50.21445], [11.00132, 50.18073], [10.92614, 50.14617], [10.93862, 50.11911], [10.88477, 50.09314], [10.86012, 50.09179], [10.869, 50.13605], [10.78817, 50.15012], [10.7292, 50.23001], [10.81267, 50.26466], [10.73648, 50.3202], [10.74028, 50.35929], [10.94572, 50.38647], [11.03729, 50.35152], [11.1207, 50.35826], [11.18994, 50.27119]], [[10.88658, 50.28253], [10.91627, 50.24793], [10.96028, 50.24343], [10.97803, 50.22117], [11.02496, 50.25289], [10.96017, 50.29816], [10.88658, 50.28253]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Coburg, Landkreis", "LEVL_CODE": 3, "FID": "DE247", "NUTS_ID": "DE247"}, "id": "DE247"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.36869, 49.66671], [11.2803, 49.6043], [11.10005, 49.59811], [11.0356, 49.67121], [10.93364, 49.70989], [10.92734, 49.76843], [11.07291, 49.79955], [11.12589, 49.8377], [11.21776, 49.84197], [11.23811, 49.87047], [11.37063, 49.78955], [11.36869, 49.66671]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Forchheim", "LEVL_CODE": 3, "FID": "DE248", "NUTS_ID": "DE248"}, "id": "DE248"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.1009, 50.31803], [12.12728, 50.28509], [12.11139, 50.24552], [12.16068, 50.21985], [12.08178, 50.21935], [12.00784, 50.1779], [11.95731, 50.19055], [11.78911, 50.10325], [11.67809, 50.1246], [11.64812, 50.22205], [11.54126, 50.26541], [11.53848, 50.34036], [11.60329, 50.39877], [11.83701, 50.3974], [11.91932, 50.42473], [12.02138, 50.3395], [12.1009, 50.31803]], [[11.81855, 50.31053], [11.84036, 50.28655], [11.8848, 50.26873], [11.93494, 50.28495], [11.9652, 50.32598], [11.95034, 50.34468], [11.88137, 50.35337], [11.86895, 50.32593], [11.81855, 50.31053]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hof, Landkreis", "LEVL_CODE": 3, "FID": "DE249", "NUTS_ID": "DE249"}, "id": "DE249"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.48157, 50.43162], [11.52534, 50.38395], [11.60329, 50.39877], [11.53848, 50.34036], [11.54126, 50.26541], [11.3422, 50.16031], [11.19819, 50.2021], [11.18994, 50.27119], [11.25049, 50.29011], [11.27682, 50.37124], [11.26594, 50.47942], [11.40122, 50.51758], [11.43091, 50.44101], [11.48157, 50.43162]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kronach", "LEVL_CODE": 3, "FID": "DE24A", "NUTS_ID": "DE24A"}, "id": "DE24A"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.54126, 50.26541], [11.64812, 50.22205], [11.67809, 50.1246], [11.67401, 50.083], [11.59962, 50.01935], [11.42654, 49.98114], [11.38217, 49.94246], [11.25681, 49.99996], [11.30376, 50.083], [11.3422, 50.16031], [11.54126, 50.26541]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kulmbach", "LEVL_CODE": 3, "FID": "DE24B", "NUTS_ID": "DE24B"}, "id": "DE24B"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.19819, 50.2021], [11.3422, 50.16031], [11.30376, 50.083], [11.25681, 49.99996], [11.2464, 49.98956], [11.19899, 50.01471], [11.18839, 50.0609], [10.99649, 50.02898], [10.92291, 50.04996], [10.88477, 50.09314], [10.93862, 50.11911], [10.92614, 50.14617], [11.00132, 50.18073], [11.01266, 50.21445], [11.17563, 50.19033], [11.19819, 50.2021]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Lichtenfels", "LEVL_CODE": 3, "FID": "DE24C", "NUTS_ID": "DE24C"}, "id": "DE24C"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.2608, 50.05816], [12.1294, 49.98575], [11.90369, 49.97902], [11.8005, 50.083], [11.78911, 50.10325], [11.95731, 50.19055], [12.00784, 50.1779], [12.08178, 50.21935], [12.16068, 50.21985], [12.20444, 50.17132], [12.20652, 50.11358], [12.26126, 50.083], [12.2608, 50.05816]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wunsiedel i. Fichtelgebirge", "LEVL_CODE": 3, "FID": "DE24D", "NUTS_ID": "DE24D"}, "id": "DE24D"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.48259, 49.3154], [10.54699, 49.34035], [10.58751, 49.34283], [10.65819, 49.31539], [10.61463, 49.28319], [10.64714, 49.2568], [10.64136, 49.2337], [10.55226, 49.25981], [10.49141, 49.26761], [10.48259, 49.3154]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ansbach, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE251", "NUTS_ID": "DE251"}, "id": "DE251"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.03021, 49.53537], [10.98793, 49.53679], [10.93402, 49.53447], [10.93283, 49.53825], [10.94613, 49.62938], [11.03214, 49.60685], [11.03021, 49.53537]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Erlangen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE252", "NUTS_ID": "DE252"}, "id": "DE252"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.99045, 49.43654], [10.90961, 49.47709], [10.93402, 49.53447], [10.98793, 49.53679], [11.01673, 49.47678], [10.99045, 49.43654]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "F\u00fcrth, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE253", "NUTS_ID": "DE253"}, "id": "DE253"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.03021, 49.53537], [11.17818, 49.47409], [11.14423, 49.44328], [11.16925, 49.38847], [11.1228, 49.38379], [11.07981, 49.33928], [10.9978, 49.37589], [10.99272, 49.38149], [11.01201, 49.40031], [10.99045, 49.43654], [11.01673, 49.47678], [10.98793, 49.53679], [11.03021, 49.53537]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "N\u00fcrnberg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE254", "NUTS_ID": "DE254"}, "id": "DE254"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.07981, 49.33928], [11.05537, 49.30727], [11.01716, 49.30122], [10.98021, 49.35317], [10.9978, 49.37589], [11.07981, 49.33928]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schwabach, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE255", "NUTS_ID": "DE255"}, "id": "DE255"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.69735, 49.43627], [10.86784, 49.35613], [10.90204, 49.26161], [10.83764, 49.19775], [10.74421, 49.1921], [10.66478, 49.13406], [10.65211, 49.10109], [10.67988, 49.05517], [10.64225, 49.01663], [10.57237, 49.03165], [10.41015, 48.97746], [10.25676, 49.0595], [10.2307, 49.09679], [10.24365, 49.14465], [10.13509, 49.216], [10.14612, 49.28266], [10.11197, 49.38491], [10.13416, 49.41223], [10.11833, 49.47317], [10.2252, 49.48611], [10.28784, 49.42414], [10.36325, 49.41421], [10.69735, 49.43627]], [[10.48259, 49.3154], [10.49141, 49.26761], [10.55226, 49.25981], [10.64136, 49.2337], [10.64714, 49.2568], [10.61463, 49.28319], [10.65819, 49.31539], [10.58751, 49.34283], [10.54699, 49.34035], [10.48259, 49.3154]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ansbach, Landkreis", "LEVL_CODE": 3, "FID": "DE256", "NUTS_ID": "DE256"}, "id": "DE256"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.92734, 49.76843], [10.93364, 49.70989], [11.0356, 49.67121], [11.10005, 49.59811], [11.2803, 49.6043], [11.19869, 49.55426], [11.17818, 49.47409], [11.03021, 49.53537], [11.03214, 49.60685], [10.94613, 49.62938], [10.93283, 49.53825], [10.81577, 49.53561], [10.77025, 49.58958], [10.75909, 49.6577], [10.65171, 49.68639], [10.65442, 49.72112], [10.71719, 49.78099], [10.80855, 49.74622], [10.92734, 49.76843]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Erlangen-H\u00f6chstadt", "LEVL_CODE": 3, "FID": "DE257", "NUTS_ID": "DE257"}, "id": "DE257"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.93402, 49.53447], [10.90961, 49.47709], [10.99045, 49.43654], [11.01201, 49.40031], [10.99272, 49.38149], [10.86784, 49.35613], [10.69735, 49.43627], [10.69546, 49.49321], [10.81577, 49.53561], [10.93283, 49.53825], [10.93402, 49.53447]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "F\u00fcrth, Landkreis", "LEVL_CODE": 3, "FID": "DE258", "NUTS_ID": "DE258"}, "id": "DE258"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.55776, 49.41904], [11.46419, 49.41535], [11.34483, 49.31108], [11.24718, 49.32535], [11.16925, 49.38847], [11.14423, 49.44328], [11.17818, 49.47409], [11.19869, 49.55426], [11.2803, 49.6043], [11.36869, 49.66671], [11.4363, 49.62695], [11.54539, 49.64704], [11.5588, 49.68872], [11.58743, 49.63177], [11.54912, 49.54412], [11.58564, 49.47122], [11.55776, 49.41904]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "N\u00fcrnberger Land", "LEVL_CODE": 3, "FID": "DE259", "NUTS_ID": "DE259"}, "id": "DE259"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.65442, 49.72112], [10.65171, 49.68639], [10.75909, 49.6577], [10.77025, 49.58958], [10.81577, 49.53561], [10.69546, 49.49321], [10.69735, 49.43627], [10.36325, 49.41421], [10.28784, 49.42414], [10.2252, 49.48611], [10.11833, 49.47317], [10.08372, 49.54356], [10.07679, 49.58743], [10.11032, 49.62141], [10.16542, 49.5983], [10.22396, 49.62687], [10.36327, 49.62811], [10.3824, 49.71886], [10.55144, 49.75577], [10.65442, 49.72112]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neustadt a. d. Aisch-Bad Windsheim", "LEVL_CODE": 3, "FID": "DE25A", "NUTS_ID": "DE25A"}, "id": "DE25A"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.24718, 49.32535], [11.20608, 49.28809], [11.28866, 49.2513], [11.283, 49.19132], [11.38571, 49.07886], [11.39824, 49.03201], [11.32508, 49.00501], [11.20136, 49.04655], [10.99354, 49.1498], [10.88237, 49.1521], [10.83764, 49.19775], [10.90204, 49.26161], [10.86784, 49.35613], [10.99272, 49.38149], [10.9978, 49.37589], [10.98021, 49.35317], [11.01716, 49.30122], [11.05537, 49.30727], [11.07981, 49.33928], [11.1228, 49.38379], [11.16925, 49.38847], [11.24718, 49.32535]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Roth", "LEVL_CODE": 3, "FID": "DE25B", "NUTS_ID": "DE25B"}, "id": "DE25B"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.20136, 49.04655], [11.14359, 48.97307], [11.07067, 48.94691], [11.04921, 48.89925], [10.93684, 48.86328], [10.85604, 48.88666], [10.80261, 48.93852], [10.68875, 48.91685], [10.64225, 49.01663], [10.67988, 49.05517], [10.65211, 49.10109], [10.66478, 49.13406], [10.74421, 49.1921], [10.83764, 49.19775], [10.88237, 49.1521], [10.99354, 49.1498], [11.20136, 49.04655]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wei\u00dfenburg-Gunzenhausen", "LEVL_CODE": 3, "FID": "DE25C", "NUTS_ID": "DE25C"}, "id": "DE25C"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.2378, 49.9348], [9.10724, 49.93086], [9.09461, 49.95427], [9.10159, 50.01143], [9.17462, 49.98697], [9.2378, 49.9348]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Aschaffenburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE261", "NUTS_ID": "DE261"}, "id": "DE261"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.17448, 50.02846], [10.22704, 50.08313], [10.2624, 50.08313], [10.26719, 50.05845], [10.24025, 50.01655], [10.17448, 50.02846]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schweinfurt, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE262", "NUTS_ID": "DE262"}, "id": "DE262"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.8829, 49.81922], [9.95493, 49.83721], [10.00133, 49.80307], [9.97795, 49.72206], [9.95493, 49.71341], [9.90067, 49.75396], [9.8829, 49.81922]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "W\u00fcrzburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE263", "NUTS_ID": "DE263"}, "id": "DE263"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.01851, 53.83484], [9.28431, 53.74193], [9.22215, 53.68504], [9.26671, 53.66641], [9.26278, 53.64292], [9.14861, 53.5924], [8.92924, 53.52646], [8.95309, 53.47106], [8.94094, 53.40739], [8.90611, 53.38167], [8.78993, 53.38483], [8.7521, 53.32092], [8.72426, 53.3124], [8.63017, 53.2857], [8.52583, 53.29774], [8.51343, 53.3124], [8.49999, 53.36685], [8.49265, 53.47242], [8.60933, 53.49019], [8.64546, 53.52133], [8.63666, 53.59812], [8.52041, 53.60621], [8.4861, 53.68827], [8.55313, 53.82059], [8.60672, 53.87394], [8.68717, 53.89191], [8.79258, 53.83597], [9.01851, 53.83484]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Cuxhaven", "LEVL_CODE": 3, "FID": "DE932", "NUTS_ID": "DE932"}, "id": "DE932"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.30795, 53.4332], [10.46918, 53.38584], [10.24047, 53.3124], [10.22294, 53.29324], [10.24265, 53.26003], [10.2083, 53.23472], [10.22181, 53.1935], [10.1281, 53.17501], [10.0754, 53.12901], [9.8626, 53.20085], [9.6826, 53.21948], [9.61775, 53.2383], [9.57667, 53.3124], [9.5627, 53.33759], [9.65102, 53.40481], [9.76833, 53.43944], [9.76886, 53.50528], [9.8626, 53.43964], [9.98419, 53.42269], [10.05909, 53.45534], [10.2045, 53.39675], [10.30795, 53.4332]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Harburg", "LEVL_CODE": 3, "FID": "DE933", "NUTS_ID": "DE933"}, "id": "DE933"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.17186, 53.15664], [11.26573, 53.12198], [11.33618, 53.06189], [11.45163, 53.07273], [11.59778, 53.03593], [11.50981, 52.99303], [11.50503, 52.94103], [11.29941, 52.8782], [11.0428, 52.90917], [10.93636, 52.85729], [10.84156, 52.85221], [10.815, 52.90269], [10.88242, 53.01056], [10.75482, 53.12334], [10.89394, 53.15306], [10.87576, 53.24153], [11.07497, 53.14719], [11.17186, 53.15664]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "L\u00fcchow-Dannenberg", "LEVL_CODE": 3, "FID": "DE934", "NUTS_ID": "DE934"}, "id": "DE934"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.59505, 53.36393], [10.69901, 53.36277], [10.82622, 53.31593], [10.96579, 53.33036], [11.17186, 53.15664], [11.07497, 53.14719], [10.87576, 53.24153], [10.89394, 53.15306], [10.75482, 53.12334], [10.5006, 53.1961], [10.3767, 53.11729], [10.21179, 53.06773], [10.08243, 53.04739], [10.0754, 53.12901], [10.1281, 53.17501], [10.22181, 53.1935], [10.2083, 53.23472], [10.24265, 53.26003], [10.22294, 53.29324], [10.24047, 53.3124], [10.46918, 53.38584], [10.59505, 53.36393]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "L\u00fcneburg, Landkreis", "LEVL_CODE": 3, "FID": "DE935", "NUTS_ID": "DE935"}, "id": "DE935"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.90611, 53.38167], [8.99883, 53.3124], [9.07145, 53.1513], [8.98419, 53.12607], [8.48533, 53.22712], [8.49428, 53.3124], [8.49999, 53.36685], [8.51343, 53.3124], [8.52583, 53.29774], [8.63017, 53.2857], [8.72426, 53.3124], [8.7521, 53.32092], [8.78993, 53.38483], [8.90611, 53.38167]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Osterholz", "LEVL_CODE": 3, "FID": "DE936", "NUTS_ID": "DE936"}, "id": "DE936"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.5627, 53.33759], [9.57667, 53.3124], [9.61775, 53.2383], [9.6826, 53.21948], [9.72099, 53.16937], [9.63821, 53.11899], [9.62554, 53.04517], [9.66338, 53.01868], [9.65789, 52.9878], [9.5855, 52.93577], [9.45472, 52.91741], [9.43558, 52.99353], [9.36635, 52.99013], [9.19141, 53.05091], [9.16551, 53.08569], [9.17759, 53.16612], [9.07145, 53.1513], [8.99883, 53.3124], [8.90611, 53.38167], [8.94094, 53.40739], [8.95309, 53.47106], [8.92924, 53.52646], [9.14861, 53.5924], [9.18151, 53.54082], [9.27095, 53.53731], [9.24088, 53.4758], [9.32353, 53.45598], [9.37869, 53.37894], [9.45454, 53.34985], [9.53487, 53.37904], [9.5627, 53.33759]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rotenburg (W\u00fcmme)", "LEVL_CODE": 3, "FID": "DE937", "NUTS_ID": "DE937"}, "id": "DE937"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.0754, 53.12901], [10.08243, 53.04739], [10.21179, 53.06773], [10.23713, 52.9363], [9.98516, 52.8743], [9.8626, 52.88625], [9.84402, 52.8715], [9.85366, 52.84314], [9.82606, 52.81169], [9.82387, 52.75205], [9.74276, 52.71334], [9.73462, 52.6383], [9.67583, 52.60366], [9.58767, 52.66711], [9.53466, 52.66007], [9.48196, 52.72052], [9.34755, 52.72678], [9.3256, 52.76771], [9.35172, 52.78867], [9.34354, 52.81169], [9.45472, 52.91741], [9.5855, 52.93577], [9.65789, 52.9878], [9.66338, 53.01868], [9.62554, 53.04517], [9.63821, 53.11899], [9.72099, 53.16937], [9.6826, 53.21948], [9.8626, 53.20085], [10.0754, 53.12901]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Heidekreis", "LEVL_CODE": 3, "FID": "DE938", "NUTS_ID": "DE938"}, "id": "DE938"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.48589, 53.70766], [9.56276, 53.61286], [9.7301, 53.55758], [9.76626, 53.54682], [9.76886, 53.50528], [9.76833, 53.43944], [9.65102, 53.40481], [9.5627, 53.33759], [9.53487, 53.37904], [9.45454, 53.34985], [9.37869, 53.37894], [9.32353, 53.45598], [9.24088, 53.4758], [9.27095, 53.53731], [9.18151, 53.54082], [9.14861, 53.5924], [9.26278, 53.64292], [9.26671, 53.66641], [9.22215, 53.68504], [9.28431, 53.74193], [9.01851, 53.83484], [9.27273, 53.86766], [9.48589, 53.70766]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Stade", "LEVL_CODE": 3, "FID": "DE939", "NUTS_ID": "DE939"}, "id": "DE939"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.75482, 53.12334], [10.88242, 53.01056], [10.815, 52.90269], [10.84156, 52.85221], [10.75931, 52.79583], [10.66143, 52.77493], [10.54411, 52.82753], [10.43955, 52.81169], [10.33266, 52.84319], [10.23713, 52.9363], [10.21179, 53.06773], [10.3767, 53.11729], [10.5006, 53.1961], [10.75482, 53.12334]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Uelzen", "LEVL_CODE": 3, "FID": "DE93A", "NUTS_ID": "DE93A"}, "id": "DE93A"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.45472, 52.91741], [9.34354, 52.81169], [9.35172, 52.78867], [9.3256, 52.76771], [9.30044, 52.80362], [9.21436, 52.82056], [9.17732, 52.88322], [9.116, 52.89695], [9.02928, 52.92347], [8.95288, 52.89631], [8.91705, 52.94678], [8.91583, 53.01102], [8.96868, 53.04485], [8.9596, 53.1117], [8.98419, 53.12607], [9.07145, 53.1513], [9.17759, 53.16612], [9.16551, 53.08569], [9.19141, 53.05091], [9.36635, 52.99013], [9.43558, 52.99353], [9.45472, 52.91741]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Verden", "LEVL_CODE": 3, "FID": "DE93B", "NUTS_ID": "DE93B"}, "id": "DE93B"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.71142, 53.04463], [8.65716, 53.00899], [8.58875, 53.00103], [8.63567, 53.09928], [8.6549, 53.10886], [8.71142, 53.04463]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Delmenhorst, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE941", "NUTS_ID": "DE941"}, "id": "DE941"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.30732, 53.35016], [7.2643, 53.32553], [6.99945, 53.35989], [7.25437, 53.40259], [7.30732, 53.35016]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Emden, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE942", "NUTS_ID": "DE942"}, "id": "DE942"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.30391, 53.18908], [8.31226, 53.15305], [8.24867, 53.08981], [8.12809, 53.10784], [8.19367, 53.19576], [8.30391, 53.18908]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oldenburg (Oldenburg), Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE943", "NUTS_ID": "DE943"}, "id": "DE943"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.95647, 52.27249], [7.96463, 52.32486], [8.16604, 52.28412], [8.10262, 52.23062], [8.01159, 52.23256], [7.95647, 52.27249]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Osnabr\u00fcck, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE944", "NUTS_ID": "DE944"}, "id": "DE944"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.06133, 53.50596], [8.01783, 53.60503], [8.09129, 53.63811], [8.14258, 53.59479], [8.16211, 53.53377], [8.06133, 53.50596]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wilhelmshaven, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE945", "NUTS_ID": "DE945"}, "id": "DE945"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.30391, 53.18908], [8.19367, 53.19576], [8.12809, 53.10784], [8.05083, 53.05962], [7.87255, 53.12058], [7.8325, 53.1669], [7.71902, 53.17889], [7.71511, 53.22241], [7.83756, 53.28821], [7.85579, 53.3124], [7.88561, 53.35197], [7.99672, 53.31218], [8.17392, 53.35404], [8.26378, 53.27735], [8.30391, 53.18908]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ammerland", "LEVL_CODE": 3, "FID": "DE946", "NUTS_ID": "DE946"}, "id": "DE946"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[7.81548, 53.37546], [7.66002, 53.34219], [7.48433, 53.36643], [7.30732, 53.35016], [7.25437, 53.40259], [6.99945, 53.35989], [7.04299, 53.52413], [7.12271, 53.53362], [7.1006, 53.57572], [7.16503, 53.63027], [7.29288, 53.67823], [7.55017, 53.67504], [7.49543, 53.62523], [7.42736, 53.60955], [7.43006, 53.54472], [7.63701, 53.55645], [7.68281, 53.47191], [7.77099, 53.43787], [7.81548, 53.37546]]], [[[7.28981, 53.70633], [7.17571, 53.70087], [7.17, 53.713], [7.18881, 53.72342], [7.29169, 53.72462], [7.28981, 53.70633]]], [[[6.89382, 53.65239], [6.90004, 53.64305], [6.89139, 53.62546], [6.87691, 53.62854], [6.86495, 53.64019], [6.86641, 53.6451], [6.89382, 53.65239]]], [[[6.89382, 53.65239], [6.88384, 53.66308], [6.90274, 53.67276], [6.90659, 53.65847], [6.89566, 53.65084], [6.89382, 53.65239]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Aurich", "LEVL_CODE": 3, "FID": "DE947", "NUTS_ID": "DE947"}, "id": "DE947"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.05083, 53.05962], [8.11622, 53.00066], [8.13622, 52.92328], [8.25885, 52.8742], [8.24367, 52.81169], [8.1915, 52.76699], [8.06721, 52.74955], [8.01815, 52.68391], [7.84464, 52.70557], [7.68962, 52.67892], [7.64693, 52.72442], [7.70175, 52.76556], [7.69155, 52.81169], [7.81875, 52.917], [7.61188, 53.05771], [7.65149, 53.17196], [7.71902, 53.17889], [7.8325, 53.1669], [7.87255, 53.12058], [8.05083, 53.05962]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Cloppenburg", "LEVL_CODE": 3, "FID": "DE948", "NUTS_ID": "DE948"}, "id": "DE948"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.61188, 53.05771], [7.81875, 52.917], [7.69155, 52.81169], [7.70175, 52.76556], [7.64693, 52.72442], [7.68962, 52.67892], [7.62064, 52.61924], [7.60804, 52.47402], [7.56493, 52.37971], [7.31748, 52.28027], [7.22205, 52.42654], [7.26746, 52.47546], [7.25577, 52.53113], [7.00642, 52.61398], [7.00623, 52.63876], [7.05342, 52.65717], [7.07266, 52.81169], [7.09269, 52.8382], [7.20108, 52.9855], [7.20279, 53.11328], [7.46648, 53.08565], [7.54551, 53.04074], [7.61188, 53.05771]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Emsland", "LEVL_CODE": 3, "FID": "DE949", "NUTS_ID": "DE949"}, "id": "DE949"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.09129, 53.63811], [8.01783, 53.60503], [8.06133, 53.50596], [8.08839, 53.45667], [8.19556, 53.40917], [8.17392, 53.35404], [7.99672, 53.31218], [7.88561, 53.35197], [7.87152, 53.37588], [7.9645, 53.45897], [7.92304, 53.50932], [7.82415, 53.51339], [7.85047, 53.61161], [7.8099, 53.70766], [8.00554, 53.70845], [8.04869, 53.64775], [8.09129, 53.63811]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Friesland (DE)", "LEVL_CODE": 3, "FID": "DE94A", "NUTS_ID": "DE94A"}, "id": "DE94A"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.31748, 52.28027], [7.09915, 52.24306], [7.06569, 52.24137], [7.03443, 52.29149], [7.06305, 52.37304], [6.99588, 52.45441], [6.92412, 52.44163], [6.69787, 52.48629], [6.72977, 52.57415], [6.70973, 52.62782], [7.00623, 52.63876], [7.00642, 52.61398], [7.25577, 52.53113], [7.26746, 52.47546], [7.22205, 52.42654], [7.31748, 52.28027]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Grafschaft Bentheim", "LEVL_CODE": 3, "FID": "DE94B", "NUTS_ID": "DE94B"}, "id": "DE94B"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[7.88561, 53.35197], [7.85579, 53.3124], [7.83756, 53.28821], [7.71511, 53.22241], [7.71902, 53.17889], [7.65149, 53.17196], [7.61188, 53.05771], [7.54551, 53.04074], [7.46648, 53.08565], [7.20279, 53.11328], [7.20894, 53.24306], [7.2643, 53.32553], [7.30732, 53.35016], [7.48433, 53.36643], [7.66002, 53.34219], [7.81548, 53.37546], [7.87152, 53.37588], [7.88561, 53.35197]]], [[[6.78364, 53.60864], [6.71483, 53.55976], [6.66516, 53.57809], [6.65495, 53.59595], [6.75864, 53.61764], [6.78364, 53.60864]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Leer", "LEVL_CODE": 3, "FID": "DE94C", "NUTS_ID": "DE94C"}, "id": "DE94C"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.63567, 53.09928], [8.58875, 53.00103], [8.65716, 53.00899], [8.68305, 52.91833], [8.62305, 52.85206], [8.5302, 52.81169], [8.48662, 52.79914], [8.45928, 52.80106], [8.34078, 52.87105], [8.25885, 52.8742], [8.13622, 52.92328], [8.11622, 53.00066], [8.05083, 53.05962], [8.12809, 53.10784], [8.24867, 53.08981], [8.31226, 53.15305], [8.37339, 53.1648], [8.41098, 53.12924], [8.63567, 53.09928]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oldenburg, Landkreis", "LEVL_CODE": 3, "FID": "DE94D", "NUTS_ID": "DE94D"}, "id": "DE94D"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.01815, 52.68391], [8.07751, 52.62355], [8.03449, 52.54602], [8.07347, 52.44827], [8.15573, 52.44037], [8.30832, 52.49911], [8.29721, 52.4565], [8.33064, 52.40323], [8.43712, 52.35826], [8.46619, 52.26761], [8.45144, 52.22068], [8.48855, 52.17629], [8.41059, 52.11512], [8.26542, 52.12667], [8.09645, 52.05715], [7.96336, 52.04106], [7.88516, 52.0833], [8.00194, 52.12396], [7.99508, 52.16713], [7.91207, 52.2044], [7.95647, 52.27249], [8.01159, 52.23256], [8.10262, 52.23062], [8.16604, 52.28412], [7.96463, 52.32486], [7.91999, 52.36958], [7.74599, 52.39098], [7.67656, 52.45433], [7.60804, 52.47402], [7.62064, 52.61924], [7.68962, 52.67892], [7.84464, 52.70557], [8.01815, 52.68391]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Osnabr\u00fcck, Landkreis", "LEVL_CODE": 3, "FID": "DE94E", "NUTS_ID": "DE94E"}, "id": "DE94E"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.45928, 52.80106], [8.45812, 52.73746], [8.30417, 52.668], [8.34008, 52.55858], [8.30832, 52.49911], [8.15573, 52.44037], [8.07347, 52.44827], [8.03449, 52.54602], [8.07751, 52.62355], [8.01815, 52.68391], [8.06721, 52.74955], [8.1915, 52.76699], [8.24367, 52.81169], [8.25885, 52.8742], [8.34078, 52.87105], [8.45928, 52.80106]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Vechta", "LEVL_CODE": 3, "FID": "DE94F", "NUTS_ID": "DE94F"}, "id": "DE94F"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.5549, 53.52513], [8.49265, 53.47242], [8.49999, 53.36685], [8.49428, 53.3124], [8.48533, 53.22712], [8.61556, 53.16321], [8.6549, 53.10886], [8.63567, 53.09928], [8.41098, 53.12924], [8.37339, 53.1648], [8.31226, 53.15305], [8.30391, 53.18908], [8.26378, 53.27735], [8.17392, 53.35404], [8.19556, 53.40917], [8.25838, 53.40555], [8.30614, 53.45379], [8.29947, 53.51603], [8.23678, 53.53774], [8.29672, 53.60846], [8.5549, 53.52513]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wesermarsch", "LEVL_CODE": 3, "FID": "DE94G", "NUTS_ID": "DE94G"}, "id": "DE94G"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[7.87152, 53.37588], [7.81548, 53.37546], [7.77099, 53.43787], [7.68281, 53.47191], [7.63701, 53.55645], [7.43006, 53.54472], [7.42736, 53.60955], [7.49543, 53.62523], [7.55017, 53.67504], [7.8099, 53.70766], [7.85047, 53.61161], [7.82415, 53.51339], [7.92304, 53.50932], [7.9645, 53.45897], [7.87152, 53.37588]]], [[[7.78299, 53.78692], [7.77025, 53.75163], [7.69678, 53.77322], [7.70836, 53.7886], [7.78299, 53.78692]]], [[[7.4986, 53.7195], [7.44754, 53.71786], [7.4767, 53.76209], [7.51875, 53.76362], [7.4986, 53.7195]]], [[[7.70844, 53.76536], [7.6839, 53.7408], [7.65075, 53.75935], [7.67685, 53.78483], [7.70844, 53.76536]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wittmund", "LEVL_CODE": 3, "FID": "DE94H", "NUTS_ID": "DE94H"}, "id": "DE94H"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.85605, 51.12619], [6.72473, 51.20656], [6.71486, 51.3332], [6.8052, 51.3488], [6.812, 51.28365], [6.92143, 51.25814], [6.88348, 51.22328], [6.91189, 51.13967], [6.85605, 51.12619]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "D\u00fcsseldorf, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA11", "NUTS_ID": "DEA11"}, "id": "DEA11"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.78402, 51.53323], [6.81004, 51.45526], [6.83046, 51.35227], [6.8052, 51.3488], [6.71486, 51.3332], [6.70617, 51.33669], [6.63574, 51.39297], [6.67202, 51.44663], [6.64185, 51.50115], [6.69107, 51.54996], [6.78402, 51.53323]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Duisburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA12", "NUTS_ID": "DEA12"}, "id": "DEA12"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.01021, 51.53273], [7.1042, 51.48127], [7.13603, 51.42604], [7.11768, 51.38027], [6.90374, 51.36743], [6.95129, 51.42676], [6.89992, 51.47129], [6.92841, 51.49796], [6.99865, 51.5337], [7.01021, 51.53273]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Essen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA13", "NUTS_ID": "DEA13"}, "id": "DEA13"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.63574, 51.39297], [6.70617, 51.33669], [6.5855, 51.28683], [6.51909, 51.29346], [6.48642, 51.36094], [6.53704, 51.40631], [6.63574, 51.39297]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Krefeld, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA14", "NUTS_ID": "DEA14"}, "id": "DEA14"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.02156, 50.99295], [9.98991, 50.9233], [10.0171, 50.87603], [9.92618, 50.76739], [9.78465, 50.79918], [9.67285, 50.72854], [9.61146, 50.73548], [9.57768, 50.75785], [9.50323, 50.7434], [9.44106, 50.79564], [9.50117, 50.82417], [9.49565, 50.91351], [9.57849, 51.0149], [9.67591, 51.06764], [9.77753, 51.08316], [9.95493, 51.04584], [10.02156, 50.99295]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hersfeld-Rotenburg", "LEVL_CODE": 3, "FID": "DE733", "NUTS_ID": "DE733"}, "id": "DE733"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.68533, 51.58202], [9.67238, 51.5684], [9.64303, 51.58189], [9.62972, 51.58213], [9.62856, 51.5682], [9.64776, 51.55251], [9.60736, 51.51295], [9.63243, 51.42097], [9.55729, 51.35138], [9.38919, 51.34897], [9.36868, 51.30347], [9.48748, 51.26697], [9.54441, 51.29199], [9.56803, 51.34], [9.7323, 51.29523], [9.69793, 51.28897], [9.71458, 51.24959], [9.63596, 51.1831], [9.32288, 51.25003], [9.18953, 51.17042], [9.10307, 51.27294], [9.12019, 51.35229], [9.16673, 51.37686], [9.15541, 51.44268], [9.3, 51.51811], [9.35177, 51.61231], [9.44046, 51.65039], [9.61493, 51.63075], [9.68533, 51.58202]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kassel, Landkreis", "LEVL_CODE": 3, "FID": "DE734", "NUTS_ID": "DE734"}, "id": "DE734"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.63596, 51.1831], [9.76523, 51.14002], [9.77753, 51.08316], [9.67591, 51.06764], [9.57849, 51.0149], [9.49565, 50.91351], [9.50117, 50.82417], [9.44106, 50.79564], [9.39307, 50.78106], [9.14887, 50.83665], [9.11167, 50.91238], [8.9746, 50.9383], [9.01379, 50.94357], [9.02497, 50.9783], [9.2038, 51.11784], [9.18953, 51.17042], [9.32288, 51.25003], [9.63596, 51.1831]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schwalm-Eder-Kreis", "LEVL_CODE": 3, "FID": "DE735", "NUTS_ID": "DE735"}, "id": "DE735"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.15541, 51.44268], [9.16673, 51.37686], [9.12019, 51.35229], [9.10307, 51.27294], [9.18953, 51.17042], [9.2038, 51.11784], [9.02497, 50.9783], [9.01379, 50.94357], [8.9746, 50.9383], [8.68916, 50.99192], [8.58928, 50.95241], [8.47789, 50.96905], [8.52778, 51.01607], [8.51534, 51.08024], [8.54909, 51.10187], [8.67266, 51.10266], [8.75279, 51.18889], [8.7196, 51.26435], [8.59026, 51.2575], [8.5671, 51.27815], [8.58325, 51.30214], [8.69915, 51.37232], [8.93129, 51.39628], [8.94019, 51.42677], [8.90313, 51.47932], [8.97065, 51.50677], [9.06897, 51.4961], [9.09933, 51.45218], [9.15541, 51.44268]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Waldeck-Frankenberg", "LEVL_CODE": 3, "FID": "DE736", "NUTS_ID": "DE736"}, "id": "DE736"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.92834, 51.3753], [9.95493, 51.30432], [10.20694, 51.19065], [10.21001, 51.14408], [10.14656, 51.1362], [10.15582, 51.05963], [10.20218, 51.01201], [10.18235, 50.99849], [10.02156, 50.99295], [9.95493, 51.04584], [9.77753, 51.08316], [9.76523, 51.14002], [9.63596, 51.1831], [9.71458, 51.24959], [9.69793, 51.28897], [9.7323, 51.29523], [9.76536, 51.32096], [9.72438, 51.36169], [9.79975, 51.40066], [9.89816, 51.4132], [9.92834, 51.3753]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Werra-Mei\u00dfner-Kreis", "LEVL_CODE": 3, "FID": "DE737", "NUTS_ID": "DE737"}, "id": "DE737"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.99909, 54.17475], [12.12912, 54.18569], [12.20107, 54.2447], [12.25543, 54.23647], [12.28098, 54.19955], [12.20092, 54.17475], [12.18892, 54.07698], [12.1075, 54.056], [12.05913, 54.10899], [11.99909, 54.17475]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rostock, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE803", "NUTS_ID": "DE803"}, "id": "DE803"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.30195, 53.65295], [11.44947, 53.68068], [11.49682, 53.6133], [11.45134, 53.5509], [11.39158, 53.55184], [11.30195, 53.65295]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schwerin, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE804", "NUTS_ID": "DE804"}, "id": "DE804"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.98671, 54.00397], [13.1815, 53.93113], [13.22202, 53.8159], [13.35893, 53.84143], [13.38655, 53.82209], [13.35582, 53.7932], [13.39105, 53.76368], [13.55545, 53.70003], [13.73378, 53.68979], [13.71, 53.65177], [13.73603, 53.61984], [13.71928, 53.55452], [13.68442, 53.52247], [13.71007, 53.47906], [13.56139, 53.3959], [13.50018, 53.3124], [13.40103, 53.26032], [13.29253, 53.26938], [13.2414, 53.2324], [13.13895, 53.24522], [12.98468, 53.16499], [12.65448, 53.24306], [12.45787, 53.2568], [12.33175, 53.31801], [12.28656, 53.37096], [12.33312, 53.476], [12.29865, 53.51863], [12.30562, 53.5677], [12.38971, 53.58453], [12.43773, 53.66884], [12.62397, 53.66039], [12.64564, 53.75637], [12.79687, 53.91069], [12.80903, 54.00478], [12.87168, 53.9798], [12.98671, 54.00397]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mecklenburgische Seenplatte", "LEVL_CODE": 3, "FID": "DE80J", "NUTS_ID": "DE80J"}, "id": "DE80J"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[12.80903, 54.00478], [12.79687, 53.91069], [12.64564, 53.75637], [12.62397, 53.66039], [12.43773, 53.66884], [12.38971, 53.58453], [12.30562, 53.5677], [12.18905, 53.63694], [12.07599, 53.65295], [12.05913, 53.66772], [11.99007, 53.72822], [11.79735, 53.77078], [11.76678, 53.83607], [11.79602, 53.96147], [11.67673, 53.94732], [11.56178, 54.02809], [11.68657, 54.14521], [11.99909, 54.17475], [12.05913, 54.10899], [12.1075, 54.056], [12.18892, 54.07698], [12.20092, 54.17475], [12.28098, 54.19955], [12.25543, 54.23647], [12.20107, 54.2447], [12.28553, 54.27495], [12.3728, 54.17475], [12.39786, 54.11861], [12.80903, 54.00478]]], [[[11.58958, 54.08055], [11.53952, 54.05075], [11.51732, 54.07485], [11.56783, 54.10882], [11.58958, 54.08055]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Landkreis Rostock", "LEVL_CODE": 3, "FID": "DE80K", "NUTS_ID": "DE80K"}, "id": "DE80K"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[13.3194, 54.19351], [13.29613, 54.17475], [13.21034, 54.04066], [12.98671, 54.00397], [12.87168, 53.9798], [12.80903, 54.00478], [12.39786, 54.11861], [12.3728, 54.17475], [12.28553, 54.27495], [12.357, 54.3178], [12.41688, 54.29475], [12.46927, 54.31675], [12.50348, 54.36005], [12.49564, 54.41702], [12.55675, 54.45431], [12.797, 54.37293], [12.86837, 54.42454], [12.9849, 54.42039], [13.13311, 54.30788], [13.18542, 54.39429], [13.16719, 54.50195], [13.29499, 54.65181], [13.38221, 54.64687], [13.4006, 54.59162], [13.37058, 54.55525], [13.4925, 54.45599], [13.52184, 54.47582], [13.50132, 54.55308], [13.63672, 54.55013], [13.60766, 54.44732], [13.70065, 54.33412], [13.49059, 54.32356], [13.32789, 54.23254], [13.3194, 54.19351]]], [[[13.11828, 54.58215], [13.12957, 54.52894], [13.07274, 54.52941], [13.09168, 54.57366], [13.11828, 54.58215]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Vorpommern-R\u00fcgen", "LEVL_CODE": 3, "FID": "DE80L", "NUTS_ID": "DE80L"}, "id": "DE80L"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.79735, 53.77078], [11.71089, 53.78924], [11.50628, 53.76251], [11.44947, 53.68068], [11.30195, 53.65295], [11.29638, 53.60005], [11.16138, 53.57431], [10.95192, 53.64762], [10.92546, 53.68873], [10.77023, 53.74937], [10.76296, 53.81115], [10.76306, 53.862], [10.90366, 53.95682], [11.1577, 54.0138], [11.26748, 53.93825], [11.337, 53.94967], [11.44455, 53.91429], [11.47617, 53.95733], [11.39892, 53.97093], [11.38819, 53.99562], [11.56178, 54.02809], [11.67673, 53.94732], [11.79602, 53.96147], [11.76678, 53.83607], [11.79735, 53.77078]], [[10.95359, 53.89843], [10.9514, 53.92402], [10.90523, 53.92877], [10.88366, 53.90003], [10.89671, 53.88801], [10.95359, 53.89843]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Nordwestmecklenburg", "LEVL_CODE": 3, "FID": "DE80M", "NUTS_ID": "DE80M"}, "id": "DE80M"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.2263, 53.92865], [14.21308, 53.86648], [14.04577, 53.86649], [13.93884, 53.82047], [14.05776, 53.75699], [14.22266, 53.7441], [14.26754, 53.69781], [14.41216, 53.32964], [14.37164, 53.3124], [14.2235, 53.26103], [14.14539, 53.26946], [14.16665, 53.3124], [14.22238, 53.36515], [14.23352, 53.41656], [13.93081, 53.42751], [13.79932, 53.53974], [13.79514, 53.48336], [13.71007, 53.47906], [13.68442, 53.52247], [13.71928, 53.55452], [13.73603, 53.61984], [13.71, 53.65177], [13.73378, 53.68979], [13.55545, 53.70003], [13.39105, 53.76368], [13.35582, 53.7932], [13.38655, 53.82209], [13.35893, 53.84143], [13.22202, 53.8159], [13.1815, 53.93113], [12.98671, 54.00397], [13.21034, 54.04066], [13.29613, 54.17475], [13.3194, 54.19351], [13.47216, 54.10467], [13.81375, 54.1666], [13.91104, 54.0678], [13.89568, 54.02944], [13.80544, 54.00944], [13.89692, 53.89774], [13.92091, 53.911], [13.92312, 53.97654], [14.02235, 53.97462], [14.03356, 54.02841], [14.2263, 53.92865]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Vorpommern-Greifswald", "LEVL_CODE": 3, "FID": "DE80N", "NUTS_ID": "DE80N"}, "id": "DE80N"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.79735, 53.77078], [11.99007, 53.72822], [12.05913, 53.66772], [12.07599, 53.65295], [12.18905, 53.63694], [12.30562, 53.5677], [12.29865, 53.51863], [12.33312, 53.476], [12.28656, 53.37096], [12.33175, 53.31801], [12.3251, 53.3213], [12.05913, 53.35495], [12.01513, 53.3124], [11.8222, 53.23347], [11.609, 53.22752], [11.51056, 53.13092], [11.26573, 53.12198], [11.17186, 53.15664], [10.96579, 53.33036], [10.82622, 53.31593], [10.69901, 53.36277], [10.59505, 53.36393], [10.63928, 53.44817], [10.78623, 53.50213], [10.83477, 53.56714], [10.901, 53.57473], [10.95192, 53.64762], [11.16138, 53.57431], [11.29638, 53.60005], [11.30195, 53.65295], [11.39158, 53.55184], [11.45134, 53.5509], [11.49682, 53.6133], [11.44947, 53.68068], [11.50628, 53.76251], [11.71089, 53.78924], [11.79735, 53.77078]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ludwigslust-Parchim", "LEVL_CODE": 3, "FID": "DE80O", "NUTS_ID": "DE80O"}, "id": "DE80O"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.64299, 52.29542], [10.58115, 52.21253], [10.50692, 52.18685], [10.42959, 52.22128], [10.42433, 52.33023], [10.60944, 52.3442], [10.64299, 52.29542]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Braunschweig, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE911", "NUTS_ID": "DE911"}, "id": "DE911"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.50692, 52.18685], [10.44193, 52.12679], [10.4696, 52.04581], [10.28617, 52.03383], [10.33427, 52.062], [10.2557, 52.11753], [10.23401, 52.1702], [10.25748, 52.18302], [10.34851, 52.18406], [10.42959, 52.22128], [10.50692, 52.18685]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Salzgitter, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE912", "NUTS_ID": "DE912"}, "id": "DE912"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.89057, 52.46012], [10.8748, 52.34417], [10.84948, 52.32523], [10.67049, 52.37933], [10.67135, 52.45556], [10.79383, 52.49001], [10.89057, 52.46012]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wolfsburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE913", "NUTS_ID": "DE913"}, "id": "DE913"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.75931, 52.79583], [10.91084, 52.62672], [10.96423, 52.61782], [10.95267, 52.55533], [11.00878, 52.49675], [10.93454, 52.4718], [10.89057, 52.46012], [10.79383, 52.49001], [10.67135, 52.45556], [10.67049, 52.37933], [10.62781, 52.379], [10.60944, 52.3442], [10.42433, 52.33023], [10.34574, 52.42624], [10.29191, 52.4479], [10.27374, 52.51064], [10.40082, 52.58132], [10.37445, 52.72135], [10.43955, 52.81169], [10.54411, 52.82753], [10.66143, 52.77493], [10.75931, 52.79583]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Gifhorn", "LEVL_CODE": 3, "FID": "DE914", "NUTS_ID": "DE914"}, "id": "DE914"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.56123, 52.00407], [10.63911, 51.90068], [10.58455, 51.83917], [10.59032, 51.78536], [10.70137, 51.64219], [10.67728, 51.63838], [10.60264, 51.67346], [10.50791, 51.67226], [10.46975, 51.76678], [10.30814, 51.77073], [10.23831, 51.8267], [10.15371, 51.79354], [10.06553, 51.92736], [10.0917, 51.95353], [10.18191, 51.96038], [10.20469, 52.00699], [10.28617, 52.03383], [10.4696, 52.04581], [10.51656, 51.98257], [10.56123, 52.00407]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Goslar", "LEVL_CODE": 3, "FID": "DE916", "NUTS_ID": "DE916"}, "id": "DE916"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.96441, 52.05664], [10.80149, 52.048], [10.87675, 52.17151], [10.77276, 52.21887], [10.70951, 52.29605], [10.64299, 52.29542], [10.60944, 52.3442], [10.62781, 52.379], [10.67049, 52.37933], [10.84948, 52.32523], [10.8748, 52.34417], [10.89057, 52.46012], [10.93454, 52.4718], [11.06359, 52.36831], [11.01204, 52.33457], [11.0672, 52.23403], [11.02377, 52.19557], [11.05107, 52.15579], [10.96456, 52.10123], [10.96441, 52.05664]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Helmstedt", "LEVL_CODE": 3, "FID": "DE917", "NUTS_ID": "DE917"}, "id": "DE917"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[10.06553, 51.92736], [10.15371, 51.79354], [10.11582, 51.63265], [9.68533, 51.58202], [9.61493, 51.63075], [9.44046, 51.65039], [9.41732, 51.64727], [9.45803, 51.70285], [9.71402, 51.87165], [9.89425, 51.90616], [9.95493, 51.90611], [10.01652, 51.94126], [10.06553, 51.92736]]], [[[9.67238, 51.5684], [9.64776, 51.55251], [9.62856, 51.5682], [9.62972, 51.58213], [9.64303, 51.58189], [9.67238, 51.5684]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Northeim", "LEVL_CODE": 3, "FID": "DE918", "NUTS_ID": "DE918"}, "id": "DE918"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.42433, 52.33023], [10.42959, 52.22128], [10.34851, 52.18406], [10.25748, 52.18302], [10.19086, 52.23029], [10.01038, 52.2339], [10.03447, 52.28377], [10.13397, 52.34571], [10.15531, 52.39322], [10.29191, 52.4479], [10.34574, 52.42624], [10.42433, 52.33023]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Peine", "LEVL_CODE": 3, "FID": "DE91A", "NUTS_ID": "DE91A"}, "id": "DE91A"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[10.56123, 52.00407], [10.51656, 51.98257], [10.4696, 52.04581], [10.44193, 52.12679], [10.50692, 52.18685], [10.58115, 52.21253], [10.64299, 52.29542], [10.70951, 52.29605], [10.77276, 52.21887], [10.87675, 52.17151], [10.80149, 52.048], [10.67428, 52.04506], [10.56123, 52.00407]]], [[[10.28617, 52.03383], [10.20469, 52.00699], [10.18467, 52.14869], [10.23401, 52.1702], [10.2557, 52.11753], [10.33427, 52.062], [10.28617, 52.03383]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wolfenb\u00fcttel", "LEVL_CODE": 3, "FID": "DE91B", "NUTS_ID": "DE91B"}, "id": "DE91B"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.67728, 51.63838], [10.64271, 51.56707], [10.48855, 51.57478], [10.38816, 51.57968], [10.28585, 51.49328], [9.92834, 51.3753], [9.89816, 51.4132], [9.79975, 51.40066], [9.72438, 51.36169], [9.76536, 51.32096], [9.7323, 51.29523], [9.56803, 51.34], [9.55729, 51.35138], [9.63243, 51.42097], [9.60736, 51.51295], [9.64776, 51.55251], [9.67238, 51.5684], [9.68533, 51.58202], [10.11582, 51.63265], [10.15371, 51.79354], [10.23831, 51.8267], [10.30814, 51.77073], [10.46975, 51.76678], [10.50791, 51.67226], [10.60264, 51.67346], [10.67728, 51.63838]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "G\u00f6ttingen", "LEVL_CODE": 3, "FID": "DE91C", "NUTS_ID": "DE91C"}, "id": "DE91C"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.91583, 53.01102], [8.91705, 52.94678], [8.95288, 52.89631], [9.02928, 52.92347], [9.116, 52.89695], [9.04708, 52.81169], [9.05721, 52.75236], [9.00731, 52.66374], [8.88817, 52.62334], [8.88097, 52.55489], [8.70301, 52.50044], [8.63995, 52.52496], [8.49193, 52.50521], [8.41469, 52.44739], [8.29721, 52.4565], [8.30832, 52.49911], [8.34008, 52.55858], [8.30417, 52.668], [8.45812, 52.73746], [8.45928, 52.80106], [8.48662, 52.79914], [8.5302, 52.81169], [8.62305, 52.85206], [8.68305, 52.91833], [8.65716, 53.00899], [8.71142, 53.04463], [8.91583, 53.01102]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Diepholz", "LEVL_CODE": 3, "FID": "DE922", "NUTS_ID": "DE922"}, "id": "DE922"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.62965, 52.13126], [9.66229, 52.0955], [9.64781, 51.9648], [9.55059, 52.03835], [9.36182, 51.96811], [9.30889, 51.92272], [9.25376, 51.96964], [9.19628, 51.97329], [9.1556, 52.09783], [9.18952, 52.18635], [9.35361, 52.21961], [9.44831, 52.27432], [9.51522, 52.16765], [9.62965, 52.13126]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hameln-Pyrmont", "LEVL_CODE": 3, "FID": "DE923", "NUTS_ID": "DE923"}, "id": "DE923"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.03447, 52.28377], [10.01038, 52.2339], [10.19086, 52.23029], [10.25748, 52.18302], [10.23401, 52.1702], [10.18467, 52.14869], [10.20469, 52.00699], [10.18191, 51.96038], [10.0917, 51.95353], [10.06553, 51.92736], [10.01652, 51.94126], [9.95493, 51.90611], [9.89425, 51.90616], [9.75405, 51.97789], [9.64781, 51.9648], [9.66229, 52.0955], [9.62965, 52.13126], [9.7058, 52.15025], [9.70624, 52.19268], [9.78929, 52.18418], [9.81167, 52.25406], [9.95493, 52.28402], [10.03447, 52.28377]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hildesheim", "LEVL_CODE": 3, "FID": "DE925", "NUTS_ID": "DE925"}, "id": "DE925"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.64781, 51.9648], [9.75405, 51.97789], [9.89425, 51.90616], [9.71402, 51.87165], [9.45803, 51.70285], [9.41732, 51.64727], [9.38417, 51.66342], [9.43305, 51.83727], [9.32334, 51.85506], [9.33589, 51.88915], [9.30889, 51.92272], [9.36182, 51.96811], [9.55059, 52.03835], [9.64781, 51.9648]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Holzminden", "LEVL_CODE": 3, "FID": "DE926", "NUTS_ID": "DE926"}, "id": "DE926"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.3256, 52.76771], [9.34755, 52.72678], [9.48196, 52.72052], [9.53466, 52.66007], [9.51478, 52.63152], [9.36213, 52.6046], [9.34012, 52.5589], [9.25554, 52.53569], [9.27291, 52.45474], [9.12525, 52.41199], [9.11128, 52.47795], [9.06959, 52.4973], [8.94133, 52.40808], [8.7449, 52.39407], [8.70828, 52.42348], [8.70301, 52.50044], [8.88097, 52.55489], [8.88817, 52.62334], [9.00731, 52.66374], [9.05721, 52.75236], [9.04708, 52.81169], [9.116, 52.89695], [9.17732, 52.88322], [9.21436, 52.82056], [9.30044, 52.80362], [9.3256, 52.76771]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Nienburg (Weser)", "LEVL_CODE": 3, "FID": "DE927", "NUTS_ID": "DE927"}, "id": "DE927"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.44831, 52.27432], [9.35361, 52.21961], [9.18952, 52.18635], [9.1556, 52.09783], [9.12825, 52.13211], [9.02118, 52.14389], [8.99363, 52.19018], [9.03677, 52.19088], [9.05083, 52.22106], [8.98341, 52.26816], [8.99361, 52.31157], [9.12525, 52.41199], [9.27291, 52.45474], [9.3452, 52.43342], [9.33268, 52.39126], [9.41699, 52.38935], [9.40753, 52.31238], [9.44831, 52.27432]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schaumburg", "LEVL_CODE": 3, "FID": "DE928", "NUTS_ID": "DE928"}, "id": "DE928"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.24417, 48.84701], [9.25626, 48.79832], [9.31365, 48.77448], [9.22479, 48.69995], [9.09833, 48.70581], [9.06485, 48.75558], [9.09631, 48.82887], [9.15099, 48.8539], [9.24417, 48.84701]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Stuttgart, Stadtkreis", "LEVL_CODE": 3, "FID": "DE111", "NUTS_ID": "DE111"}, "id": "DE111"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.06485, 48.75558], [9.09833, 48.70581], [9.16142, 48.63938], [9.15123, 48.60406], [9.12918, 48.60127], [8.95868, 48.58352], [8.84156, 48.50725], [8.7689, 48.52184], [8.75884, 48.58816], [8.85644, 48.70581], [8.80418, 48.77778], [8.88347, 48.80206], [8.88603, 48.8398], [8.9288, 48.86656], [9.02346, 48.81928], [9.06485, 48.75558]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "B\u00f6blingen", "LEVL_CODE": 3, "FID": "DE112", "NUTS_ID": "DE112"}, "id": "DE112"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.5031, 48.75394], [9.48528, 48.70581], [9.58793, 48.60792], [9.58248, 48.53915], [9.39251, 48.53643], [9.15123, 48.60406], [9.16142, 48.63938], [9.09833, 48.70581], [9.22479, 48.69995], [9.31365, 48.77448], [9.5031, 48.75394]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Esslingen", "LEVL_CODE": 3, "FID": "DE113", "NUTS_ID": "DE113"}, "id": "DE113"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.98777, 48.72725], [9.95493, 48.66714], [9.94407, 48.63176], [9.68557, 48.52813], [9.58517, 48.53674], [9.58248, 48.53915], [9.58793, 48.60792], [9.48528, 48.70581], [9.5031, 48.75394], [9.6266, 48.77438], [9.81447, 48.731], [9.98777, 48.72725]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "G\u00f6ppingen", "LEVL_CODE": 3, "FID": "DE114", "NUTS_ID": "DE114"}, "id": "DE114"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.41624, 49.04738], [9.35763, 49.0004], [9.32721, 48.93899], [9.34598, 48.91928], [9.24417, 48.84701], [9.15099, 48.8539], [9.09631, 48.82887], [9.06485, 48.75558], [9.02346, 48.81928], [8.9288, 48.86656], [8.91296, 48.93047], [8.93638, 48.95555], [8.87603, 49.03517], [9.03605, 49.02494], [9.12046, 49.0606], [9.22484, 49.02422], [9.41624, 49.04738]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ludwigsburg", "LEVL_CODE": 3, "FID": "DE115", "NUTS_ID": "DE115"}, "id": "DE115"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.69331, 48.96402], [9.75847, 48.83853], [9.65012, 48.81718], [9.6266, 48.77438], [9.5031, 48.75394], [9.31365, 48.77448], [9.25626, 48.79832], [9.24417, 48.84701], [9.34598, 48.91928], [9.32721, 48.93899], [9.35763, 49.0004], [9.41624, 49.04738], [9.50088, 49.079], [9.59895, 49.04937], [9.69331, 48.96402]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rems-Murr-Kreis", "LEVL_CODE": 3, "FID": "DE116", "NUTS_ID": "DE116"}, "id": "DE116"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.0766, 49.18891], [9.11344, 49.21137], [9.21344, 49.18753], [9.27891, 49.13693], [9.25787, 49.10353], [9.20539, 49.1118], [9.16122, 49.09209], [9.13097, 49.12506], [9.15941, 49.1496], [9.12729, 49.17056], [9.09, 49.1704], [9.0766, 49.18891]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Heilbronn, Stadtkreis", "LEVL_CODE": 3, "FID": "DE117", "NUTS_ID": "DE117"}, "id": "DE117"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.44349, 49.36434], [9.47996, 49.30948], [9.37552, 49.17098], [9.45481, 49.11758], [9.52749, 49.10855], [9.50088, 49.079], [9.41624, 49.04738], [9.22484, 49.02422], [9.12046, 49.0606], [9.03605, 49.02494], [8.87603, 49.03517], [8.87779, 49.05848], [8.88212, 49.10718], [8.83142, 49.14366], [8.81823, 49.1945], [8.87727, 49.18082], [9.04913, 49.29286], [9.12769, 49.27665], [9.17723, 49.32412], [9.24945, 49.30849], [9.33421, 49.37742], [9.44349, 49.36434]], [[9.0766, 49.18891], [9.09, 49.1704], [9.12729, 49.17056], [9.15941, 49.1496], [9.13097, 49.12506], [9.16122, 49.09209], [9.20539, 49.1118], [9.25787, 49.10353], [9.27891, 49.13693], [9.21344, 49.18753], [9.11344, 49.21137], [9.0766, 49.18891]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Heilbronn, Landkreis", "LEVL_CODE": 3, "FID": "DE118", "NUTS_ID": "DE118"}, "id": "DE118"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.85556, 49.36837], [9.83775, 49.27597], [9.65353, 49.14011], [9.52749, 49.10855], [9.45481, 49.11758], [9.37552, 49.17098], [9.47996, 49.30948], [9.44349, 49.36434], [9.46944, 49.38821], [9.56855, 49.38056], [9.60382, 49.42658], [9.80726, 49.39951], [9.85556, 49.36837]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hohenlohekreis", "LEVL_CODE": 3, "FID": "DE119", "NUTS_ID": "DE119"}, "id": "DE119"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.40498, 50.08773], [9.41995, 50.04735], [9.44618, 49.94053], [9.42274, 49.88114], [9.32458, 49.83707], [9.2378, 49.9348], [9.17462, 49.98697], [9.10159, 50.01143], [9.09461, 49.95427], [9.10724, 49.93086], [9.09173, 49.88248], [9.05008, 49.86632], [9.01609, 49.99134], [9.02897, 50.0294], [8.97817, 50.04735], [8.99056, 50.06712], [9.03266, 50.11147], [9.15724, 50.11524], [9.22451, 50.14568], [9.35943, 50.12903], [9.40498, 50.08773]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Aschaffenburg, Landkreis", "LEVL_CODE": 3, "FID": "DE264", "NUTS_ID": "DE264"}, "id": "DE264"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.93566, 50.41961], [9.92851, 50.37172], [9.95493, 50.33626], [10.15677, 50.26465], [10.32216, 50.29573], [10.37013, 50.22916], [10.3069, 50.16103], [9.96927, 50.083], [9.96401, 50.05472], [9.82453, 50.04879], [9.76083, 50.14336], [9.62315, 50.22904], [9.73276, 50.30439], [9.73291, 50.35615], [9.77446, 50.42127], [9.86753, 50.40222], [9.93566, 50.41961]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bad Kissingen", "LEVL_CODE": 3, "FID": "DE265", "NUTS_ID": "DE265"}, "id": "DE265"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.45053, 50.40186], [10.59076, 50.32912], [10.61012, 50.228], [10.45054, 50.19671], [10.37013, 50.22916], [10.32216, 50.29573], [10.15677, 50.26465], [9.95493, 50.33626], [9.92851, 50.37172], [9.93566, 50.41961], [9.95493, 50.42369], [10.04134, 50.51647], [10.10473, 50.55549], [10.20334, 50.54809], [10.32859, 50.48859], [10.39977, 50.40382], [10.45053, 50.40186]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rh\u00f6n-Grabfeld", "LEVL_CODE": 3, "FID": "DE266", "NUTS_ID": "DE266"}, "id": "DE266"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.86012, 50.09179], [10.67288, 49.89091], [10.50525, 49.87668], [10.48537, 49.89325], [10.49956, 49.93353], [10.38122, 50.01554], [10.43014, 50.083], [10.3976, 50.12658], [10.45054, 50.19671], [10.61012, 50.228], [10.7292, 50.23001], [10.78817, 50.15012], [10.869, 50.13605], [10.86012, 50.09179]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ha\u00dfberge", "LEVL_CODE": 3, "FID": "DE267", "NUTS_ID": "DE267"}, "id": "DE267"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.44896, 49.82218], [10.55144, 49.75577], [10.3824, 49.71886], [10.36327, 49.62811], [10.22396, 49.62687], [10.16542, 49.5983], [10.11032, 49.62141], [10.09471, 49.64433], [10.12098, 49.68131], [10.05144, 49.74233], [10.0669, 49.81188], [10.18916, 49.89206], [10.36961, 49.86327], [10.44896, 49.82218]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kitzingen", "LEVL_CODE": 3, "FID": "DE268", "NUTS_ID": "DE268"}, "id": "DE268"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.42274, 49.88114], [9.4715, 49.77973], [9.32498, 49.76062], [9.35012, 49.71554], [9.40409, 49.71459], [9.41092, 49.66351], [9.29593, 49.64494], [9.2241, 49.58015], [9.10301, 49.57746], [9.07717, 49.62027], [9.14073, 49.73841], [9.11961, 49.78829], [9.03608, 49.8465], [9.05008, 49.86632], [9.09173, 49.88248], [9.10724, 49.93086], [9.2378, 49.9348], [9.32458, 49.83707], [9.42274, 49.88114]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Miltenberg", "LEVL_CODE": 3, "FID": "DE269", "NUTS_ID": "DE269"}, "id": "DE269"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.62315, 50.22904], [9.76083, 50.14336], [9.82453, 50.04879], [9.96401, 50.05472], [10.03511, 50.02408], [10.03092, 49.9522], [9.95493, 49.93868], [9.90256, 49.89763], [9.68412, 49.83087], [9.64874, 49.79148], [9.4715, 49.77973], [9.42274, 49.88114], [9.44618, 49.94053], [9.41995, 50.04735], [9.40498, 50.08773], [9.50854, 50.10636], [9.50981, 50.23381], [9.62315, 50.22904]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Main-Spessart", "LEVL_CODE": 3, "FID": "DE26A", "NUTS_ID": "DE26A"}, "id": "DE26A"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.45054, 50.19671], [10.3976, 50.12658], [10.43014, 50.083], [10.38122, 50.01554], [10.49956, 49.93353], [10.48537, 49.89325], [10.50525, 49.87668], [10.4578, 49.86821], [10.44896, 49.82218], [10.36961, 49.86327], [10.18916, 49.89206], [10.03092, 49.9522], [10.03511, 50.02408], [9.96401, 50.05472], [9.96927, 50.083], [10.3069, 50.16103], [10.37013, 50.22916], [10.45054, 50.19671]], [[10.17448, 50.02846], [10.24025, 50.01655], [10.26719, 50.05845], [10.2624, 50.08313], [10.22704, 50.08313], [10.17448, 50.02846]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schweinfurt, Landkreis", "LEVL_CODE": 3, "FID": "DE26B", "NUTS_ID": "DE26B"}, "id": "DE26B"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.18916, 49.89206], [10.0669, 49.81188], [10.05144, 49.74233], [10.12098, 49.68131], [10.09471, 49.64433], [10.11032, 49.62141], [10.07679, 49.58743], [10.08372, 49.54356], [10.0188, 49.4908], [9.95493, 49.48229], [9.92211, 49.50029], [9.90825, 49.56429], [9.83953, 49.56227], [9.86067, 49.6147], [9.81397, 49.70894], [9.63811, 49.70177], [9.64874, 49.79148], [9.68412, 49.83087], [9.90256, 49.89763], [9.95493, 49.93868], [10.03092, 49.9522], [10.18916, 49.89206]], [[9.8829, 49.81922], [9.90067, 49.75396], [9.95493, 49.71341], [9.97795, 49.72206], [10.00133, 49.80307], [9.95493, 49.83721], [9.8829, 49.81922]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "W\u00fcrzburg, Landkreis", "LEVL_CODE": 3, "FID": "DE26C", "NUTS_ID": "DE26C"}, "id": "DE26C"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.91075, 48.25831], [10.78685, 48.32542], [10.84536, 48.35455], [10.88408, 48.45844], [10.95444, 48.36933], [10.9471, 48.28808], [10.91075, 48.25831]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Augsburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE271", "NUTS_ID": "DE271"}, "id": "DE271"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.56691, 47.85955], [10.57617, 47.8952], [10.62795, 47.92086], [10.64919, 47.91565], [10.65517, 47.86052], [10.63139, 47.84799], [10.56691, 47.85955]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kaufbeuren, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE272", "NUTS_ID": "DE272"}, "id": "DE272"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.25318, 47.76396], [10.29359, 47.77731], [10.37755, 47.75684], [10.35633, 47.71288], [10.31841, 47.69485], [10.25903, 47.71436], [10.25318, 47.76396]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kempten (Allg\u00e4u), Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE273", "NUTS_ID": "DE273"}, "id": "DE273"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.11014, 47.93715], [10.09191, 47.95527], [10.10421, 47.97436], [10.11495, 47.97626], [10.15938, 48.02132], [10.21977, 48.02906], [10.17391, 47.93683], [10.11014, 47.93715]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Memmingen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE274", "NUTS_ID": "DE274"}, "id": "DE274"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.29541, 48.47457], [11.31168, 48.45227], [11.19241, 48.4188], [11.11677, 48.26848], [11.05797, 48.26192], [11.03451, 48.19299], [10.90341, 48.23668], [10.91075, 48.25831], [10.9471, 48.28808], [10.95444, 48.36933], [10.88408, 48.45844], [10.87683, 48.5245], [10.97339, 48.5494], [10.92393, 48.58695], [11.02299, 48.62017], [11.13574, 48.59638], [11.22761, 48.49036], [11.29541, 48.47457]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Aichach-Friedberg", "LEVL_CODE": 3, "FID": "DE275", "NUTS_ID": "DE275"}, "id": "DE275"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.92393, 48.58695], [10.97339, 48.5494], [10.87683, 48.5245], [10.88408, 48.45844], [10.84536, 48.35455], [10.78685, 48.32542], [10.91075, 48.25831], [10.90341, 48.23668], [10.86307, 48.16464], [10.79569, 48.14295], [10.80153, 48.10417], [10.70179, 48.09787], [10.6833, 48.16325], [10.58652, 48.16702], [10.57364, 48.21212], [10.58036, 48.30383], [10.50654, 48.32606], [10.52836, 48.47139], [10.73131, 48.49311], [10.77937, 48.61893], [10.87103, 48.63047], [10.92393, 48.58695]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Augsburg, Landkreis", "LEVL_CODE": 3, "FID": "DE276", "NUTS_ID": "DE276"}, "id": "DE276"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.77937, 48.61893], [10.73131, 48.49311], [10.52836, 48.47139], [10.43905, 48.48801], [10.41865, 48.5158], [10.32418, 48.48808], [10.27825, 48.5161], [10.31085, 48.54907], [10.27235, 48.69198], [10.36297, 48.66722], [10.48726, 48.69666], [10.53523, 48.74615], [10.62161, 48.74199], [10.65059, 48.7247], [10.65813, 48.6674], [10.77937, 48.61893]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dillingen a.d. Donau", "LEVL_CODE": 3, "FID": "DE277", "NUTS_ID": "DE277"}, "id": "DE277"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.52836, 48.47139], [10.50654, 48.32606], [10.58036, 48.30383], [10.57364, 48.21212], [10.48756, 48.21767], [10.34831, 48.1679], [10.27143, 48.22965], [10.28507, 48.31562], [10.18375, 48.38057], [10.1708, 48.43826], [10.1339, 48.45487], [10.23078, 48.51051], [10.27825, 48.5161], [10.32418, 48.48808], [10.41865, 48.5158], [10.43905, 48.48801], [10.52836, 48.47139]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "G\u00fcnzburg", "LEVL_CODE": 3, "FID": "DE278", "NUTS_ID": "DE278"}, "id": "DE278"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.1339, 48.45487], [10.1708, 48.43826], [10.18375, 48.38057], [10.28507, 48.31562], [10.27143, 48.22965], [10.19579, 48.13177], [10.13646, 48.10846], [10.09536, 48.16402], [10.05785, 48.27958], [9.99761, 48.35012], [10.03269, 48.4572], [10.1339, 48.45487]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neu-Ulm", "LEVL_CODE": 3, "FID": "DE279", "NUTS_ID": "DE279"}, "id": "DE279"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.07729, 47.63927], [10.05843, 47.58943], [9.96781, 47.54624], [9.85961, 47.53945], [9.79363, 47.58277], [9.6965, 47.53136], [9.55872, 47.54189], [9.69215, 47.61042], [9.85083, 47.67162], [9.95493, 47.65387], [10.03718, 47.67869], [10.07729, 47.63927]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Lindau (Bodensee)", "LEVL_CODE": 3, "FID": "DE27A", "NUTS_ID": "DE27A"}, "id": "DE27A"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.76709, 47.84379], [10.73572, 47.69734], [10.78479, 47.69756], [10.7948, 47.66509], [10.8961, 47.66072], [10.95279, 47.61627], [10.88452, 47.57069], [10.8862, 47.53685], [10.78738, 47.51978], [10.63704, 47.56468], [10.55315, 47.5372], [10.45444, 47.5558], [10.47264, 47.58695], [10.49645, 47.66333], [10.45751, 47.74631], [10.3269, 47.85808], [10.53253, 47.97538], [10.63112, 47.96169], [10.69465, 48.04079], [10.70179, 48.09787], [10.80153, 48.10417], [10.77879, 48.05214], [10.81935, 47.99373], [10.76709, 47.84379]], [[10.56691, 47.85955], [10.63139, 47.84799], [10.65517, 47.86052], [10.64919, 47.91565], [10.62795, 47.92086], [10.57617, 47.8952], [10.56691, 47.85955]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ostallg\u00e4u", "LEVL_CODE": 3, "FID": "DE27B", "NUTS_ID": "DE27B"}, "id": "DE27B"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.57364, 48.21212], [10.58652, 48.16702], [10.6833, 48.16325], [10.70179, 48.09787], [10.69465, 48.04079], [10.63112, 47.96169], [10.53253, 47.97538], [10.3269, 47.85808], [10.13193, 47.82009], [10.1065, 47.85503], [10.11014, 47.93715], [10.17391, 47.93683], [10.21977, 48.02906], [10.15938, 48.02132], [10.11495, 47.97626], [10.13646, 48.10846], [10.19579, 48.13177], [10.27143, 48.22965], [10.34831, 48.1679], [10.48756, 48.21767], [10.57364, 48.21212]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Unterallg\u00e4u", "LEVL_CODE": 3, "FID": "DE27C", "NUTS_ID": "DE27C"}, "id": "DE27C"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.64225, 49.01663], [10.68875, 48.91685], [10.80261, 48.93852], [10.85604, 48.88666], [10.93684, 48.86328], [11.00594, 48.82188], [10.97843, 48.79066], [10.98362, 48.71263], [11.02299, 48.62017], [10.92393, 48.58695], [10.87103, 48.63047], [10.77937, 48.61893], [10.65813, 48.6674], [10.65059, 48.7247], [10.62161, 48.74199], [10.53523, 48.74615], [10.48726, 48.69666], [10.42369, 48.7445], [10.45376, 48.90438], [10.41015, 48.97746], [10.57237, 49.03165], [10.64225, 49.01663]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Donau-Ries", "LEVL_CODE": 3, "FID": "DE27D", "NUTS_ID": "DE27D"}, "id": "DE27D"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.45444, 47.5558], [10.44031, 47.50839], [10.46831, 47.43701], [10.38325, 47.3546], [10.31008, 47.30182], [10.17835, 47.27011], [10.21751, 47.3546], [10.19751, 47.3843], [10.10207, 47.3666], [10.08401, 47.44765], [9.99953, 47.48302], [9.96781, 47.54624], [10.05843, 47.58943], [10.07729, 47.63927], [10.12996, 47.68973], [10.09185, 47.78568], [10.13193, 47.82009], [10.3269, 47.85808], [10.45751, 47.74631], [10.49645, 47.66333], [10.47264, 47.58695], [10.45444, 47.5558]], [[10.25318, 47.76396], [10.25903, 47.71436], [10.31841, 47.69485], [10.35633, 47.71288], [10.37755, 47.75684], [10.29359, 47.77731], [10.25318, 47.76396]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oberallg\u00e4u", "LEVL_CODE": 3, "FID": "DE27E", "NUTS_ID": "DE27E"}, "id": "DE27E"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.39854, 52.64819], [13.50153, 52.65057], [13.51237, 52.5963], [13.61083, 52.54424], [13.66698, 52.47417], [13.73542, 52.43866], [13.69974, 52.37788], [13.6567, 52.35054], [13.44791, 52.41434], [13.42099, 52.37625], [13.31193, 52.39919], [13.2288, 52.41586], [13.1659, 52.39428], [13.11014, 52.43252], [13.1537, 52.50182], [13.12747, 52.52703], [13.16426, 52.5989], [13.28774, 52.65365], [13.33408, 52.62895], [13.39854, 52.64819]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Berlin", "LEVL_CODE": 3, "FID": "DE300", "NUTS_ID": "DE300"}, "id": "DE300"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.36118, 52.44277], [12.42818, 52.45814], [12.64074, 52.45507], [12.67731, 52.42154], [12.58604, 52.35481], [12.44043, 52.31551], [12.36118, 52.44277]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Brandenburg an der Havel, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE401", "NUTS_ID": "DE401"}, "id": "DE401"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.27349, 51.79503], [14.31087, 51.82117], [14.30613, 51.86392], [14.36964, 51.85367], [14.37201, 51.82993], [14.40606, 51.80117], [14.44598, 51.81451], [14.49427, 51.79068], [14.42935, 51.7358], [14.44292, 51.70387], [14.37566, 51.69747], [14.30257, 51.70215], [14.3099, 51.72281], [14.27602, 51.73914], [14.28746, 51.77177], [14.27349, 51.79503]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Cottbus, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE402", "NUTS_ID": "DE402"}, "id": "DE402"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.60089, 52.27205], [14.40668, 52.27411], [14.42376, 52.37397], [14.53436, 52.39501], [14.60089, 52.27205]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Frankfurt (Oder), Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE403", "NUTS_ID": "DE403"}, "id": "DE403"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.1537, 52.50182], [13.11014, 52.43252], [13.1659, 52.39428], [13.12653, 52.35545], [13.01803, 52.35435], [12.89353, 52.46076], [12.97906, 52.50801], [13.06712, 52.46757], [13.1537, 52.50182]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Potsdam, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DE404", "NUTS_ID": "DE404"}, "id": "DE404"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.14366, 52.96137], [14.15669, 52.89559], [14.08762, 52.89583], [13.93779, 52.81169], [13.75852, 52.73128], [13.77993, 52.67876], [13.84367, 52.68252], [13.82929, 52.6325], [13.61083, 52.54424], [13.51237, 52.5963], [13.50153, 52.65057], [13.39854, 52.64819], [13.41377, 52.77137], [13.48692, 52.82559], [13.47539, 52.89183], [13.43603, 52.9309], [13.5179, 52.98867], [13.83946, 53.05481], [13.85792, 53.03812], [13.83136, 52.99522], [13.85376, 52.97849], [14.00762, 52.94095], [14.14366, 52.96137]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Barnim", "LEVL_CODE": 3, "FID": "DE405", "NUTS_ID": "DE405"}, "id": "DE405"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.69974, 52.37788], [13.73952, 52.36956], [13.75123, 52.32424], [13.83998, 52.30186], [13.83763, 52.22573], [13.87855, 52.12591], [13.96624, 52.13106], [14.2547, 52.03766], [14.36521, 52.06566], [14.446, 52.02636], [14.38071, 51.99615], [14.37765, 51.93785], [14.24766, 51.91146], [14.18681, 51.86262], [14.08849, 51.87831], [13.86473, 51.88664], [13.83377, 51.84878], [13.82586, 51.75132], [13.79809, 51.73713], [13.75724, 51.75792], [13.69205, 51.73535], [13.55429, 51.75286], [13.50709, 51.79203], [13.54522, 51.81654], [13.55144, 51.90795], [13.42552, 51.9347], [13.42654, 51.96051], [13.62631, 52.00961], [13.64734, 52.04013], [13.54996, 52.10151], [13.56484, 52.21119], [13.47157, 52.24452], [13.4966, 52.29825], [13.42099, 52.37625], [13.44791, 52.41434], [13.6567, 52.35054], [13.69974, 52.37788]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dahme-Spreewald", "LEVL_CODE": 3, "FID": "DE406", "NUTS_ID": "DE406"}, "id": "DE406"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.50709, 51.79203], [13.55429, 51.75286], [13.69205, 51.73535], [13.75724, 51.75792], [13.79809, 51.73713], [13.8822, 51.61417], [13.84767, 51.55672], [13.66199, 51.52055], [13.71195, 51.44564], [13.69125, 51.37401], [13.54719, 51.37859], [13.40816, 51.44277], [13.26468, 51.39298], [13.21015, 51.40474], [13.19302, 51.54223], [13.14039, 51.6038], [13.05103, 51.64768], [13.16444, 51.70612], [13.15091, 51.85961], [13.20481, 51.87949], [13.26052, 51.82382], [13.37153, 51.82817], [13.50709, 51.79203]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Elbe-Elster", "LEVL_CODE": 3, "FID": "DE407", "NUTS_ID": "DE407"}, "id": "DE407"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.82876, 52.69476], [12.95296, 52.71643], [13.09099, 52.66983], [13.16426, 52.5989], [13.12747, 52.52703], [13.1537, 52.50182], [13.06712, 52.46757], [12.97906, 52.50801], [12.89353, 52.46076], [12.75548, 52.46533], [12.70115, 52.54495], [12.56657, 52.52931], [12.45342, 52.55252], [12.42305, 52.5248], [12.42818, 52.45814], [12.36118, 52.44277], [12.31718, 52.4541], [12.25936, 52.50711], [12.17156, 52.50634], [12.17603, 52.61372], [12.23414, 52.64766], [12.21436, 52.76551], [12.2492, 52.79186], [12.37678, 52.79959], [12.41656, 52.75833], [12.50928, 52.75459], [12.55331, 52.80289], [12.64034, 52.79428], [12.74211, 52.70622], [12.82876, 52.69476]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Havelland", "LEVL_CODE": 3, "FID": "DE408", "NUTS_ID": "DE408"}, "id": "DE408"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.15669, 52.89559], [14.1393, 52.83339], [14.22302, 52.81169], [14.43644, 52.6799], [14.56506, 52.6245], [14.62516, 52.57624], [14.62109, 52.48798], [14.53436, 52.39501], [14.42376, 52.37397], [14.32577, 52.38843], [14.20817, 52.49256], [14.00218, 52.45604], [13.91405, 52.4737], [13.81392, 52.44142], [13.71109, 52.49348], [13.66698, 52.47417], [13.61083, 52.54424], [13.82929, 52.6325], [13.84367, 52.68252], [13.77993, 52.67876], [13.75852, 52.73128], [13.93779, 52.81169], [14.08762, 52.89583], [14.15669, 52.89559]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00e4rkisch-Oderland", "LEVL_CODE": 3, "FID": "DE409", "NUTS_ID": "DE409"}, "id": "DE409"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.2414, 53.2324], [13.29846, 53.17661], [13.31065, 53.12371], [13.5179, 52.98867], [13.43603, 52.9309], [13.47539, 52.89183], [13.48692, 52.82559], [13.41377, 52.77137], [13.39854, 52.64819], [13.33408, 52.62895], [13.28774, 52.65365], [13.16426, 52.5989], [13.09099, 52.66983], [12.95296, 52.71643], [12.82876, 52.69476], [12.961, 52.81169], [13.0304, 52.86432], [13.0482, 52.95287], [13.02197, 53.06074], [12.95058, 53.09853], [12.98468, 53.16499], [13.13895, 53.24522], [13.2414, 53.2324]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oberhavel", "LEVL_CODE": 3, "FID": "DE40A", "NUTS_ID": "DE40A"}, "id": "DE40A"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.41307, 49.24982], [8.33997, 49.08015], [8.27735, 48.98994], [8.26128, 48.98092], [8.23263, 48.96657], [8.06841, 48.99932], [8.07389, 49.05697], [8.12492, 49.12429], [8.25414, 49.12906], [8.21969, 49.18621], [8.23648, 49.25439], [8.32033, 49.29671], [8.41307, 49.24982]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Germersheim", "LEVL_CODE": 3, "FID": "DEB3E", "NUTS_ID": "DEB3E"}, "id": "DEB3E"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.96411, 49.48964], [7.96608, 49.41066], [7.85384, 49.36639], [7.83196, 49.32375], [7.78566, 49.31283], [7.59639, 49.36778], [7.51106, 49.33928], [7.40208, 49.36772], [7.3959, 49.37205], [7.48622, 49.49013], [7.69627, 49.58866], [7.7889, 49.53624], [7.9348, 49.52847], [7.96411, 49.48964]], [[7.64982, 49.43702], [7.69767, 49.38428], [7.76941, 49.39377], [7.80642, 49.37211], [7.81707, 49.39392], [7.8711, 49.40793], [7.84548, 49.4656], [7.81269, 49.46336], [7.79987, 49.48578], [7.75743, 49.48957], [7.73938, 49.47439], [7.70249, 49.49589], [7.68053, 49.48888], [7.67174, 49.44898], [7.64982, 49.43702]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kaiserslautern, Landkreis", "LEVL_CODE": 3, "FID": "DEB3F", "NUTS_ID": "DEB3F"}, "id": "DEB3F"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.70333, 49.65252], [7.69627, 49.58866], [7.48622, 49.49013], [7.3959, 49.37205], [7.2926, 49.40822], [7.25259, 49.43147], [7.28837, 49.47054], [7.27662, 49.54862], [7.42919, 49.60118], [7.43892, 49.65888], [7.52275, 49.70762], [7.65437, 49.69198], [7.67284, 49.64548], [7.70333, 49.65252]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kusel", "LEVL_CODE": 3, "FID": "DEB3G", "NUTS_ID": "DEB3G"}, "id": "DEB3G"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.31741, 49.30812], [8.32033, 49.29671], [8.23648, 49.25439], [8.21969, 49.18621], [8.25414, 49.12906], [8.12492, 49.12429], [8.07389, 49.05697], [8.06841, 48.99932], [7.91065, 49.04516], [7.86555, 49.13251], [7.91095, 49.18756], [7.87207, 49.28311], [7.94249, 49.26617], [7.93672, 49.30898], [7.99956, 49.34379], [8.21794, 49.29738], [8.31741, 49.30812]], [[8.05356, 49.16463], [8.09122, 49.15608], [8.10588, 49.17176], [8.17923, 49.16416], [8.1573, 49.22692], [8.10618, 49.23462], [8.05658, 49.22238], [8.06877, 49.1858], [8.05356, 49.16463]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "S\u00fcdliche Weinstra\u00dfe", "LEVL_CODE": 3, "FID": "DEB3H", "NUTS_ID": "DEB3H"}, "id": "DEB3H"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.42244, 49.58339], [8.4227, 49.57419], [8.33855, 49.55203], [8.30956, 49.48516], [8.39833, 49.43306], [8.47251, 49.44351], [8.50153, 49.43486], [8.49732, 49.41135], [8.47092, 49.34071], [8.42168, 49.36854], [8.38608, 49.34359], [8.41495, 49.30343], [8.46699, 49.28298], [8.41307, 49.24982], [8.32033, 49.29671], [8.31741, 49.30812], [8.31443, 49.31145], [8.24651, 49.42826], [8.27395, 49.4553], [8.28101, 49.58676], [8.41477, 49.59505], [8.42244, 49.58339]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rhein-Pfalz-Kreis", "LEVL_CODE": 3, "FID": "DEB3I", "NUTS_ID": "DEB3I"}, "id": "DEB3I"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.774, 50.06654], [7.78659, 50.04735], [7.90815, 49.97491], [8.17514, 50.03426], [8.19004, 50.0353], [8.15558, 49.97437], [8.23016, 49.94342], [8.23793, 49.90445], [8.34303, 49.94051], [8.40006, 49.80368], [8.2564, 49.76069], [8.15124, 49.89911], [7.96356, 49.8331], [7.89391, 49.92573], [7.73463, 49.99869], [7.68359, 50.04735], [7.76484, 50.08259], [7.774, 50.06654]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mainz-Bingen", "LEVL_CODE": 3, "FID": "DEB3J", "NUTS_ID": "DEB3J"}, "id": "DEB3J"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.83196, 49.32375], [7.86765, 49.31332], [7.87207, 49.28311], [7.91095, 49.18756], [7.86555, 49.13251], [7.91065, 49.04516], [7.63565, 49.05395], [7.52949, 49.09985], [7.48078, 49.16346], [7.36876, 49.16146], [7.32034, 49.1895], [7.4065, 49.23436], [7.42099, 49.28096], [7.39449, 49.31635], [7.40208, 49.36772], [7.51106, 49.33928], [7.59639, 49.36778], [7.78566, 49.31283], [7.83196, 49.32375]], [[7.51254, 49.20058], [7.58935, 49.15288], [7.63677, 49.16217], [7.66667, 49.20729], [7.57015, 49.23595], [7.51254, 49.20058]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "S\u00fcdwestpfalz", "LEVL_CODE": 3, "FID": "DEB3K", "NUTS_ID": "DEB3K"}, "id": "DEB3K"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.11702, 49.30952], [7.07905, 49.2737], [7.12775, 49.22109], [7.10107, 49.156], [7.05166, 49.12596], [7.02392, 49.18987], [6.85871, 49.21081], [6.81678, 49.15735], [6.73284, 49.17312], [6.72347, 49.21883], [6.84942, 49.2705], [6.87785, 49.34569], [6.94428, 49.37767], [7.02392, 49.3503], [7.11702, 49.30952]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Regionalverband Saarbr\u00fccken", "LEVL_CODE": 3, "FID": "DEC01", "NUTS_ID": "DEC01"}, "id": "DEC01"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.91018, 49.5017], [6.71099, 49.38003], [6.55699, 49.41921], [6.44957, 49.46867], [6.36711, 49.46951], [6.38005, 49.55111], [6.59236, 49.52804], [6.89145, 49.61342], [6.95289, 49.57222], [6.94895, 49.51839], [6.91018, 49.5017]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Merzig-Wadern", "LEVL_CODE": 3, "FID": "DEC02", "NUTS_ID": "DEC02"}, "id": "DEC02"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.25259, 49.43147], [7.2926, 49.40822], [7.24005, 49.37456], [7.22484, 49.30117], [7.11702, 49.30952], [7.02392, 49.3503], [6.94428, 49.37767], [6.93209, 49.41489], [6.98932, 49.43676], [7.02392, 49.42495], [7.25259, 49.43147]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neunkirchen", "LEVL_CODE": 3, "FID": "DEC03", "NUTS_ID": "DEC03"}, "id": "DEC03"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.98932, 49.43676], [6.93209, 49.41489], [6.94428, 49.37767], [6.87785, 49.34569], [6.84942, 49.2705], [6.72347, 49.21883], [6.58559, 49.33145], [6.55699, 49.41921], [6.71099, 49.38003], [6.91018, 49.5017], [6.98932, 49.43676]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Saarlouis", "LEVL_CODE": 3, "FID": "DEC04", "NUTS_ID": "DEC04"}, "id": "DEC04"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.3959, 49.37205], [7.40208, 49.36772], [7.39449, 49.31635], [7.30236, 49.24138], [7.32034, 49.1895], [7.36876, 49.16146], [7.29489, 49.12114], [7.19244, 49.1181], [7.10107, 49.156], [7.12775, 49.22109], [7.07905, 49.2737], [7.11702, 49.30952], [7.22484, 49.30117], [7.24005, 49.37456], [7.2926, 49.40822], [7.3959, 49.37205]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Saarpfalz-Kreis", "LEVL_CODE": 3, "FID": "DEC05", "NUTS_ID": "DEC05"}, "id": "DEC05"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.27662, 49.54862], [7.28837, 49.47054], [7.25259, 49.43147], [7.02392, 49.42495], [6.98932, 49.43676], [6.91018, 49.5017], [6.94895, 49.51839], [6.95289, 49.57222], [6.89145, 49.61342], [7.02798, 49.63944], [7.26392, 49.57426], [7.27662, 49.54862]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "St. Wendel", "LEVL_CODE": 3, "FID": "DEC06", "NUTS_ID": "DEC06"}, "id": "DEC06"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.9656, 51.0616], [13.89861, 50.99856], [13.79845, 50.98153], [13.58032, 51.06462], [13.61468, 51.0977], [13.69758, 51.10217], [13.76616, 51.17521], [13.87222, 51.1486], [13.92423, 51.07044], [13.9656, 51.0616]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dresden, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DED21", "NUTS_ID": "DED21"}, "id": "DED21"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.49122, 51.04353], [14.41509, 51.02507], [14.31787, 51.0547], [14.19817, 51.08225], [13.9656, 51.0616], [13.92423, 51.07044], [13.87222, 51.1486], [13.76616, 51.17521], [13.80814, 51.31156], [13.8459, 51.34579], [13.83531, 51.37679], [14.00175, 51.38452], [14.10794, 51.51416], [14.16332, 51.54104], [14.32836, 51.51954], [14.44777, 51.54207], [14.5107, 51.42831], [14.46921, 51.33939], [14.57727, 51.32166], [14.69166, 51.18074], [14.50404, 51.07444], [14.49122, 51.04353]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bautzen", "LEVL_CODE": 3, "FID": "DED2C", "NUTS_ID": "DED2C"}, "id": "DED2C"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.97418, 51.36395], [15.03355, 51.26941], [14.99569, 51.12516], [14.89909, 50.94956], [14.82336, 50.87055], [14.76856, 50.82354], [14.6188, 50.8578], [14.63815, 50.92261], [14.57442, 50.92402], [14.58915, 50.98779], [14.49122, 51.04353], [14.50404, 51.07444], [14.69166, 51.18074], [14.57727, 51.32166], [14.46921, 51.33939], [14.5107, 51.42831], [14.44777, 51.54207], [14.56367, 51.57376], [14.66003, 51.55265], [14.72986, 51.58178], [14.74085, 51.53022], [14.95227, 51.46244], [14.97418, 51.36395]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "G\u00f6rlitz", "LEVL_CODE": 3, "FID": "DED2D", "NUTS_ID": "DED2D"}, "id": "DED2D"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.69125, 51.37401], [13.83531, 51.37679], [13.8459, 51.34579], [13.80814, 51.31156], [13.76616, 51.17521], [13.69758, 51.10217], [13.61468, 51.0977], [13.58032, 51.06462], [13.46238, 51.06066], [13.42189, 51.0342], [13.26695, 51.03964], [13.2485, 51.14803], [13.19871, 51.23041], [13.21015, 51.40474], [13.26468, 51.39298], [13.40816, 51.44277], [13.54719, 51.37859], [13.69125, 51.37401]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mei\u00dfen", "LEVL_CODE": 3, "FID": "DED2E", "NUTS_ID": "DED2E"}, "id": "DED2E"}, {"geometry": {"type": "Polygon", "coordinates": [[[14.31787, 51.0547], [14.26954, 50.99315], [14.38816, 50.92519], [14.37591, 50.90015], [13.98214, 50.81161], [13.85298, 50.73153], [13.65217, 50.73036], [13.51538, 50.84166], [13.42189, 51.0342], [13.46238, 51.06066], [13.58032, 51.06462], [13.79845, 50.98153], [13.89861, 50.99856], [13.9656, 51.0616], [14.19817, 51.08225], [14.31787, 51.0547]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "S\u00e4chsische Schweiz-Osterzgebirge", "LEVL_CODE": 3, "FID": "DED2F", "NUTS_ID": "DED2F"}, "id": "DED2F"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.04877, 50.80657], [12.92892, 50.74733], [12.84639, 50.79624], [12.77885, 50.78961], [12.80427, 50.86692], [12.86636, 50.89918], [12.97291, 50.88833], [13.04877, 50.80657]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Chemnitz, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DED41", "NUTS_ID": "DED41"}, "id": "DED41"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.50185, 50.63364], [13.45312, 50.60794], [13.3872, 50.63493], [13.30723, 50.58188], [13.25285, 50.58418], [13.18255, 50.50653], [13.04352, 50.50481], [12.94814, 50.40431], [12.82138, 50.45316], [12.70309, 50.4061], [12.58378, 50.40708], [12.47159, 50.49565], [12.52775, 50.54667], [12.59928, 50.56671], [12.58607, 50.59314], [12.61023, 50.61951], [12.70173, 50.64173], [12.71179, 50.67959], [12.66605, 50.73527], [12.77885, 50.78961], [12.84639, 50.79624], [12.92892, 50.74733], [13.04877, 50.80657], [13.35825, 50.74388], [13.50185, 50.63364]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Erzgebirgskreis", "LEVL_CODE": 3, "FID": "DED42", "NUTS_ID": "DED42"}, "id": "DED42"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.42189, 51.0342], [13.51538, 50.84166], [13.65217, 50.73036], [13.54118, 50.70561], [13.50185, 50.63364], [13.35825, 50.74388], [13.04877, 50.80657], [12.97291, 50.88833], [12.86636, 50.89918], [12.80427, 50.86692], [12.65287, 50.92368], [12.61736, 50.98079], [12.69174, 50.99696], [12.74514, 51.0992], [12.87355, 51.10138], [12.86202, 51.165], [12.92602, 51.21821], [13.19871, 51.23041], [13.2485, 51.14803], [13.26695, 51.03964], [13.42189, 51.0342]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mittelsachsen", "LEVL_CODE": 3, "FID": "DED43", "NUTS_ID": "DED43"}, "id": "DED43"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.52775, 50.54667], [12.47159, 50.49565], [12.58378, 50.40708], [12.40941, 50.32169], [12.33896, 50.24545], [12.32022, 50.17595], [12.17959, 50.31439], [12.1009, 50.31803], [12.02138, 50.3395], [11.91932, 50.42473], [11.90694, 50.44742], [11.94625, 50.49647], [11.88319, 50.53683], [11.94414, 50.59128], [12.01046, 50.60492], [12.05581, 50.55576], [12.31892, 50.67653], [12.4097, 50.63771], [12.44914, 50.56745], [12.52775, 50.54667]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Vogtlandkreis", "LEVL_CODE": 3, "FID": "DED44", "NUTS_ID": "DED44"}, "id": "DED44"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.80427, 50.86692], [12.77885, 50.78961], [12.66605, 50.73527], [12.71179, 50.67959], [12.70173, 50.64173], [12.61023, 50.61951], [12.58607, 50.59314], [12.59928, 50.56671], [12.52775, 50.54667], [12.44914, 50.56745], [12.4097, 50.63771], [12.31892, 50.67653], [12.23849, 50.74289], [12.2752, 50.77964], [12.25083, 50.81832], [12.65287, 50.92368], [12.80427, 50.86692]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Zwickau", "LEVL_CODE": 3, "FID": "DED45", "NUTS_ID": "DED45"}, "id": "DED45"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.49874, 51.3605], [12.53198, 51.31779], [12.48085, 51.2692], [12.35873, 51.28858], [12.28318, 51.24155], [12.25765, 51.25791], [12.2432, 51.34847], [12.28032, 51.41543], [12.4522, 51.43647], [12.49874, 51.3605]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Leipzig, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DED51", "NUTS_ID": "DED51"}, "id": "DED51"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.92602, 51.21821], [12.86202, 51.165], [12.87355, 51.10138], [12.74514, 51.0992], [12.69174, 50.99696], [12.61736, 50.98079], [12.55198, 51.00085], [12.47089, 51.0772], [12.28355, 51.09192], [12.20777, 51.13113], [12.1709, 51.27551], [12.15855, 51.31441], [12.19354, 51.33252], [12.2432, 51.34847], [12.25765, 51.25791], [12.28318, 51.24155], [12.35873, 51.28858], [12.48085, 51.2692], [12.53198, 51.31779], [12.49874, 51.3605], [12.60856, 51.38632], [12.67088, 51.44215], [12.78602, 51.46294], [12.88153, 51.44197], [12.92713, 51.37018], [12.88004, 51.31205], [12.92602, 51.21821]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Leipzig", "LEVL_CODE": 3, "FID": "DED52", "NUTS_ID": "DED52"}, "id": "DED52"}, {"geometry": {"type": "Polygon", "coordinates": [[[13.05103, 51.64768], [13.14039, 51.6038], [13.19302, 51.54223], [13.21015, 51.40474], [13.19871, 51.23041], [12.92602, 51.21821], [12.88004, 51.31205], [12.92713, 51.37018], [12.88153, 51.44197], [12.78602, 51.46294], [12.67088, 51.44215], [12.60856, 51.38632], [12.49874, 51.3605], [12.4522, 51.43647], [12.28032, 51.41543], [12.2432, 51.34847], [12.19354, 51.33252], [12.15794, 51.45139], [12.19894, 51.53132], [12.58026, 51.62607], [12.83856, 51.676], [12.92021, 51.6442], [13.05103, 51.64768]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Nordsachsen", "LEVL_CODE": 3, "FID": "DED53", "NUTS_ID": "DED53"}, "id": "DED53"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.33591, 51.73381], [12.27597, 51.78181], [12.2057, 51.76359], [12.12877, 51.79428], [12.12631, 51.89048], [12.22191, 51.9601], [12.28127, 51.97029], [12.31201, 51.91742], [12.29604, 51.86034], [12.33591, 51.73381]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dessau-Ro\u00dflau, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEE01", "NUTS_ID": "DEE01"}, "id": "DEE01"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.85748, 51.51443], [11.87848, 51.53548], [11.91684, 51.52578], [11.96321, 51.5401], [12.01601, 51.54081], [12.02864, 51.50517], [12.07046, 51.49287], [12.01007, 51.41297], [11.95254, 51.40759], [11.92914, 51.45127], [11.88752, 51.46328], [11.85748, 51.51443]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Halle (Saale), Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEE02", "NUTS_ID": "DEE02"}, "id": "DEE02"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.70455, 52.19947], [11.74872, 52.0932], [11.73104, 52.04938], [11.63707, 52.03012], [11.53081, 52.07377], [11.54279, 52.12824], [11.64886, 52.22358], [11.70455, 52.19947]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Magdeburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEE03", "NUTS_ID": "DEE03"}, "id": "DEE03"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.58716, 52.45869], [11.55464, 52.41251], [11.47593, 52.39535], [11.37892, 52.42714], [11.14152, 52.43829], [11.05883, 52.49498], [11.00878, 52.49675], [10.95267, 52.55533], [10.96423, 52.61782], [10.91084, 52.62672], [10.75931, 52.79583], [10.84156, 52.85221], [10.93636, 52.85729], [11.0428, 52.90917], [11.29941, 52.8782], [11.50503, 52.94103], [11.6119, 52.87301], [11.52192, 52.83451], [11.53552, 52.74499], [11.44817, 52.62883], [11.57068, 52.59736], [11.58716, 52.45869]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Altmarkkreis Salzwedel", "LEVL_CODE": 3, "FID": "DEE04", "NUTS_ID": "DEE04"}, "id": "DEE04"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.37612, 52.04512], [12.28127, 51.97029], [12.22191, 51.9601], [12.12631, 51.89048], [12.12877, 51.79428], [12.2057, 51.76359], [12.27597, 51.78181], [12.33591, 51.73381], [12.35534, 51.69358], [12.45991, 51.69904], [12.56638, 51.65934], [12.58026, 51.62607], [12.19894, 51.53132], [12.15158, 51.57747], [12.05661, 51.58999], [12.01688, 51.6401], [11.86544, 51.67539], [11.84583, 51.70088], [11.87131, 51.84632], [11.99449, 51.88948], [11.89096, 52.00206], [12.05597, 52.06892], [12.27672, 52.10402], [12.37612, 52.04512]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Anhalt-Bitterfeld", "LEVL_CODE": 3, "FID": "DEE05", "NUTS_ID": "DEE05"}, "id": "DEE05"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.17156, 52.50634], [12.25936, 52.50711], [12.31718, 52.4541], [12.22917, 52.16814], [12.27672, 52.10402], [12.05597, 52.06892], [11.89096, 52.00206], [11.74872, 52.0932], [11.70455, 52.19947], [11.77575, 52.31327], [11.85189, 52.33158], [11.97848, 52.42516], [11.97814, 52.51307], [12.07067, 52.53181], [12.17156, 52.50634]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Jerichower Land", "LEVL_CODE": 3, "FID": "DEE06", "NUTS_ID": "DEE06"}, "id": "DEE06"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.58716, 52.45869], [11.67501, 52.46221], [11.73239, 52.40897], [11.82073, 52.40701], [11.85189, 52.33158], [11.77575, 52.31327], [11.70455, 52.19947], [11.64886, 52.22358], [11.54279, 52.12824], [11.53081, 52.07377], [11.63707, 52.03012], [11.57603, 51.96807], [11.40854, 51.99094], [11.35933, 51.96391], [11.34085, 51.90123], [11.18729, 51.92122], [11.14842, 51.99742], [10.96441, 52.05664], [10.96456, 52.10123], [11.05107, 52.15579], [11.02377, 52.19557], [11.0672, 52.23403], [11.01204, 52.33457], [11.06359, 52.36831], [10.93454, 52.4718], [11.00878, 52.49675], [11.05883, 52.49498], [11.14152, 52.43829], [11.37892, 52.42714], [11.47593, 52.39535], [11.55464, 52.41251], [11.58716, 52.45869]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "B\u00f6rde", "LEVL_CODE": 3, "FID": "DEE07", "NUTS_ID": "DEE07"}, "id": "DEE07"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.1709, 51.27551], [12.20777, 51.13113], [12.28355, 51.09192], [12.28207, 51.01877], [12.22417, 50.94294], [12.16353, 50.9586], [12.02102, 50.96912], [11.88622, 51.04996], [11.77548, 51.04879], [11.69694, 51.08749], [11.48461, 51.10544], [11.46565, 51.18627], [11.38177, 51.21505], [11.38537, 51.24589], [11.47397, 51.29506], [11.54134, 51.27856], [11.61888, 51.31297], [11.89074, 51.23948], [12.01597, 51.27895], [12.1709, 51.27551]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Burgenlandkreis", "LEVL_CODE": 3, "FID": "DEE08", "NUTS_ID": "DEE08"}, "id": "DEE08"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.34085, 51.90123], [11.32678, 51.85353], [11.26128, 51.80032], [11.30181, 51.76596], [11.38623, 51.76802], [11.40161, 51.72994], [11.20981, 51.59299], [11.12721, 51.5758], [10.96575, 51.62764], [10.91606, 51.61637], [10.70137, 51.64219], [10.59032, 51.78536], [10.58455, 51.83917], [10.63911, 51.90068], [10.56123, 52.00407], [10.67428, 52.04506], [10.80149, 52.048], [10.96441, 52.05664], [11.14842, 51.99742], [11.18729, 51.92122], [11.34085, 51.90123]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Harz", "LEVL_CODE": 3, "FID": "DEE09", "NUTS_ID": "DEE09"}, "id": "DEE09"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.75161, 51.63328], [11.69932, 51.5631], [11.7636, 51.44412], [11.56051, 51.43993], [11.40577, 51.3422], [11.38353, 51.38043], [11.3133, 51.40464], [10.97811, 51.42689], [10.89613, 51.57087], [10.91606, 51.61637], [10.96575, 51.62764], [11.12721, 51.5758], [11.20981, 51.59299], [11.40161, 51.72994], [11.75161, 51.63328]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mansfeld-S\u00fcdharz", "LEVL_CODE": 3, "FID": "DEE0A", "NUTS_ID": "DEE0A"}, "id": "DEE0A"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.19894, 51.53132], [12.15794, 51.45139], [12.19354, 51.33252], [12.15855, 51.31441], [12.1709, 51.27551], [12.01597, 51.27895], [11.89074, 51.23948], [11.61888, 51.31297], [11.54134, 51.27856], [11.47397, 51.29506], [11.40577, 51.3422], [11.56051, 51.43993], [11.7636, 51.44412], [11.69932, 51.5631], [11.75161, 51.63328], [11.86544, 51.67539], [12.01688, 51.6401], [12.05661, 51.58999], [12.15158, 51.57747], [12.19894, 51.53132]], [[11.85748, 51.51443], [11.88752, 51.46328], [11.92914, 51.45127], [11.95254, 51.40759], [12.01007, 51.41297], [12.07046, 51.49287], [12.02864, 51.50517], [12.01601, 51.54081], [11.96321, 51.5401], [11.91684, 51.52578], [11.87848, 51.53548], [11.85748, 51.51443]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Saalekreis", "LEVL_CODE": 3, "FID": "DEE0B", "NUTS_ID": "DEE0B"}, "id": "DEE0B"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.89096, 52.00206], [11.99449, 51.88948], [11.87131, 51.84632], [11.84583, 51.70088], [11.86544, 51.67539], [11.75161, 51.63328], [11.40161, 51.72994], [11.38623, 51.76802], [11.30181, 51.76596], [11.26128, 51.80032], [11.32678, 51.85353], [11.34085, 51.90123], [11.35933, 51.96391], [11.40854, 51.99094], [11.57603, 51.96807], [11.63707, 52.03012], [11.73104, 52.04938], [11.74872, 52.0932], [11.89096, 52.00206]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Salzlandkreis", "LEVL_CODE": 3, "FID": "DEE0C", "NUTS_ID": "DEE0C"}, "id": "DEE0C"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.12681, 52.8902], [12.14009, 52.86345], [12.21959, 52.86161], [12.25129, 52.81169], [12.2492, 52.79186], [12.21436, 52.76551], [12.23414, 52.64766], [12.17603, 52.61372], [12.17156, 52.50634], [12.07067, 52.53181], [11.97814, 52.51307], [11.97848, 52.42516], [11.85189, 52.33158], [11.82073, 52.40701], [11.73239, 52.40897], [11.67501, 52.46221], [11.58716, 52.45869], [11.57068, 52.59736], [11.44817, 52.62883], [11.53552, 52.74499], [11.52192, 52.83451], [11.6119, 52.87301], [11.50503, 52.94103], [11.50981, 52.99303], [11.59778, 53.03593], [11.82421, 52.94981], [11.86911, 52.9028], [12.12681, 52.8902]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Stendal", "LEVL_CODE": 3, "FID": "DEE0D", "NUTS_ID": "DEE0D"}, "id": "DEE0D"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.76978, 51.97927], [13.15091, 51.85961], [13.16444, 51.70612], [13.05103, 51.64768], [12.92021, 51.6442], [12.83856, 51.676], [12.58026, 51.62607], [12.56638, 51.65934], [12.45991, 51.69904], [12.35534, 51.69358], [12.33591, 51.73381], [12.29604, 51.86034], [12.31201, 51.91742], [12.28127, 51.97029], [12.37612, 52.04512], [12.5535, 51.98541], [12.66408, 52.00687], [12.76978, 51.97927]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wittenberg", "LEVL_CODE": 3, "FID": "DEE0E", "NUTS_ID": "DEE0E"}, "id": "DEE0E"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.49195, 54.82264], [9.48112, 54.76845], [9.38257, 54.76334], [9.37643, 54.78514], [9.42293, 54.82322], [9.44265, 54.8047], [9.49195, 54.82264]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Flensburg, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEF01", "NUTS_ID": "DEF01"}, "id": "DEF01"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.17416, 54.34575], [10.19552, 54.27397], [10.12343, 54.25551], [10.07123, 54.30116], [10.10572, 54.33205], [10.10981, 54.38434], [10.16852, 54.43285], [10.17416, 54.34575]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kiel, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEF02", "NUTS_ID": "DEF02"}, "id": "DEF02"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[10.90366, 53.95682], [10.76306, 53.862], [10.76296, 53.81115], [10.62628, 53.77403], [10.58228, 53.80465], [10.57958, 53.89763], [10.65452, 53.88832], [10.79628, 53.93064], [10.84089, 53.99189], [10.90366, 53.95682]]], [[[10.95359, 53.89843], [10.89671, 53.88801], [10.88366, 53.90003], [10.90523, 53.92877], [10.9514, 53.92402], [10.95359, 53.89843]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "L\u00fcbeck, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEF03", "NUTS_ID": "DEF03"}, "id": "DEF03"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.03485, 54.12879], [10.02492, 54.08614], [10.05589, 54.05713], [9.94436, 54.02176], [9.94191, 54.1103], [9.98863, 54.14178], [10.03485, 54.12879]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neum\u00fcnster, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEF04", "NUTS_ID": "DEF04"}, "id": "DEF04"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.17596, 54.33493], [9.25142, 54.33507], [9.29436, 54.2812], [9.39351, 54.28897], [9.43553, 54.24155], [9.43134, 54.18576], [9.31551, 54.1233], [9.31108, 54.06344], [9.19975, 53.8801], [8.9635, 53.89917], [8.83259, 54.02393], [8.9679, 54.05241], [8.92908, 54.11974], [8.85034, 54.132], [8.80945, 54.18576], [8.8448, 54.26633], [8.89344, 54.27018], [9.00835, 54.37267], [9.17596, 54.33493]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dithmarschen", "LEVL_CODE": 3, "FID": "DEF05", "NUTS_ID": "DEF05"}, "id": "DEF05"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.95192, 53.64762], [10.901, 53.57473], [10.83477, 53.56714], [10.78623, 53.50213], [10.63928, 53.44817], [10.59505, 53.36393], [10.46918, 53.38584], [10.30795, 53.4332], [10.23668, 53.49635], [10.32296, 53.55617], [10.49493, 53.60865], [10.41151, 53.68744], [10.45648, 53.75554], [10.58228, 53.80465], [10.62628, 53.77403], [10.76296, 53.81115], [10.77023, 53.74937], [10.92546, 53.68873], [10.95192, 53.64762]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Herzogtum Lauenburg", "LEVL_CODE": 3, "FID": "DEF06", "NUTS_ID": "DEF06"}, "id": "DEF06"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[9.17596, 54.33493], [9.00835, 54.37267], [8.89344, 54.27018], [8.8448, 54.26633], [8.69159, 54.28927], [8.64295, 54.32136], [8.69706, 54.38457], [8.8721, 54.41672], [8.93134, 54.46326], [8.84548, 54.49243], [8.85151, 54.61055], [8.65462, 54.80853], [8.63593, 54.91168], [9.1131, 54.8736], [9.08407, 54.71241], [9.16828, 54.68117], [9.25253, 54.60628], [9.29942, 54.55216], [9.31405, 54.47447], [9.26051, 54.39018], [9.17596, 54.33493]]], [[[8.56749, 54.68062], [8.48613, 54.68021], [8.4023, 54.70038], [8.39501, 54.71507], [8.43669, 54.74834], [8.53493, 54.75574], [8.58827, 54.7412], [8.59664, 54.71874], [8.56749, 54.68062]]], [[[8.38084, 54.89223], [8.45592, 54.87685], [8.46425, 54.86327], [8.41637, 54.84689], [8.33953, 54.87885], [8.29741, 54.85007], [8.28647, 54.85985], [8.29677, 54.91121], [8.33709, 54.97305], [8.36076, 54.95882], [8.36297, 54.90906], [8.38084, 54.89223]]], [[[8.67121, 54.49484], [8.62397, 54.48897], [8.58671, 54.52297], [8.59829, 54.53432], [8.6878, 54.55713], [8.71091, 54.55348], [8.69683, 54.51601], [8.67121, 54.49484]]], [[[8.40437, 54.61466], [8.36686, 54.60607], [8.28937, 54.66732], [8.33975, 54.69621], [8.36315, 54.65061], [8.40437, 54.61466]]], [[[8.44125, 55.0171], [8.36656, 54.9878], [8.35679, 54.99667], [8.39805, 55.05428], [8.43861, 55.04593], [8.44125, 55.0171]]], [[[8.32026, 54.7856], [8.29235, 54.73534], [8.26265, 54.76116], [8.2749, 54.79798], [8.32026, 54.7856]]], [[[8.57712, 54.54495], [8.54254, 54.54042], [8.51645, 54.57187], [8.56658, 54.59625], [8.57712, 54.54495]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Nordfriesland", "LEVL_CODE": 3, "FID": "DEF07", "NUTS_ID": "DEF07"}, "id": "DEF07"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.84089, 53.99189], [10.79628, 53.93064], [10.65452, 53.88832], [10.57958, 53.89763], [10.53386, 53.91881], [10.49006, 53.99714], [10.52769, 54.05922], [10.41747, 54.08325], [10.4344, 54.11412], [10.51499, 54.13484], [10.48418, 54.17475], [10.56181, 54.22821], [10.69407, 54.21421], [10.71375, 54.30507], [10.92506, 54.37749], [11.08283, 54.38556], [11.08495, 54.43153], [11.01368, 54.45815], [11.02531, 54.47447], [11.06278, 54.52705], [11.22354, 54.50046], [11.24602, 54.47447], [11.30074, 54.41122], [11.15715, 54.40373], [11.08899, 54.35348], [11.08612, 54.20556], [11.04188, 54.17475], [10.77995, 54.0767], [10.76489, 54.0277], [10.84089, 53.99189]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ostholstein", "LEVL_CODE": 3, "FID": "DEF08", "NUTS_ID": "DEF08"}, "id": "DEF08"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.94538, 53.65293], [9.8626, 53.60746], [9.76283, 53.60761], [9.7301, 53.55758], [9.56276, 53.61286], [9.48589, 53.70766], [9.56832, 53.72254], [9.64148, 53.78091], [9.66149, 53.88171], [9.75145, 53.89304], [9.8626, 53.82143], [9.90189, 53.75724], [9.97057, 53.75532], [9.94538, 53.65293]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Pinneberg", "LEVL_CODE": 3, "FID": "DEF09", "NUTS_ID": "DEF09"}, "id": "DEF09"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.71375, 54.30507], [10.69407, 54.21421], [10.56181, 54.22821], [10.48418, 54.17475], [10.51499, 54.13484], [10.4344, 54.11412], [10.41747, 54.08325], [10.31752, 54.10278], [10.05589, 54.05713], [10.02492, 54.08614], [10.03485, 54.12879], [10.10433, 54.13319], [10.12097, 54.17475], [10.12343, 54.25551], [10.19552, 54.27397], [10.17416, 54.34575], [10.23333, 54.41007], [10.34737, 54.43177], [10.71375, 54.30507]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Pl\u00f6n", "LEVL_CODE": 3, "FID": "DEF0A", "NUTS_ID": "DEF0A"}, "id": "DEF0A"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.16852, 54.43285], [10.10981, 54.38434], [10.10572, 54.33205], [10.07123, 54.30116], [10.12343, 54.25551], [10.12097, 54.17475], [10.10433, 54.13319], [10.03485, 54.12879], [9.98863, 54.14178], [9.94191, 54.1103], [9.94436, 54.02176], [9.86433, 54.01203], [9.53667, 54.08805], [9.31108, 54.06344], [9.31551, 54.1233], [9.43134, 54.18576], [9.43553, 54.24155], [9.39351, 54.28897], [9.4157, 54.33977], [9.54467, 54.3587], [9.56496, 54.42709], [9.6394, 54.47447], [9.67387, 54.49641], [9.8626, 54.59543], [9.93053, 54.62222], [9.94486, 54.63637], [10.03117, 54.63658], [9.93783, 54.48087], [10.09937, 54.47048], [10.16852, 54.43285]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rendsburg-Eckernf\u00f6rde", "LEVL_CODE": 3, "FID": "DEF0B", "NUTS_ID": "DEF0B"}, "id": "DEF0B"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.42015, 54.83196], [9.42293, 54.82322], [9.37643, 54.78514], [9.38257, 54.76334], [9.48112, 54.76845], [9.49195, 54.82264], [9.58314, 54.85427], [9.8626, 54.75358], [9.91535, 54.78915], [9.9495, 54.77874], [10.03117, 54.63658], [9.94486, 54.63637], [9.8626, 54.60417], [9.67489, 54.51996], [9.67387, 54.49641], [9.6394, 54.47447], [9.56496, 54.42709], [9.54467, 54.3587], [9.4157, 54.33977], [9.39351, 54.28897], [9.29436, 54.2812], [9.25142, 54.33507], [9.17596, 54.33493], [9.26051, 54.39018], [9.31405, 54.47447], [9.29942, 54.55216], [9.25253, 54.60628], [9.16828, 54.68117], [9.08407, 54.71241], [9.1131, 54.8736], [9.22352, 54.85127], [9.27178, 54.80999], [9.36776, 54.8242], [9.42015, 54.83196]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schleswig-Flensburg", "LEVL_CODE": 3, "FID": "DEF0C", "NUTS_ID": "DEF0C"}, "id": "DEF0C"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.41747, 54.08325], [10.52769, 54.05922], [10.49006, 53.99714], [10.53386, 53.91881], [10.2702, 53.83853], [10.24561, 53.78686], [10.0527, 53.76143], [10.0357, 53.72986], [10.07143, 53.69702], [9.94538, 53.65293], [9.97057, 53.75532], [9.90189, 53.75724], [9.8626, 53.82143], [9.75145, 53.89304], [9.78524, 53.94159], [9.77409, 53.9778], [9.86433, 54.01203], [9.94436, 54.02176], [10.05589, 54.05713], [10.31752, 54.10278], [10.41747, 54.08325]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Segeberg", "LEVL_CODE": 3, "FID": "DEF0D", "NUTS_ID": "DEF0D"}, "id": "DEF0D"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.86433, 54.01203], [9.77409, 53.9778], [9.78524, 53.94159], [9.75145, 53.89304], [9.66149, 53.88171], [9.64148, 53.78091], [9.56832, 53.72254], [9.48589, 53.70766], [9.27273, 53.86766], [9.19975, 53.8801], [9.31108, 54.06344], [9.53667, 54.08805], [9.86433, 54.01203]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Steinburg", "LEVL_CODE": 3, "FID": "DEF0E", "NUTS_ID": "DEF0E"}, "id": "DEF0E"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.57958, 53.89763], [10.58228, 53.80465], [10.45648, 53.75554], [10.41151, 53.68744], [10.49493, 53.60865], [10.32296, 53.55617], [10.23668, 53.49635], [10.16603, 53.55111], [10.18964, 53.61027], [10.15226, 53.7118], [10.07143, 53.69702], [10.0357, 53.72986], [10.0527, 53.76143], [10.24561, 53.78686], [10.2702, 53.83853], [10.53386, 53.91881], [10.57958, 53.89763]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Stormarn", "LEVL_CODE": 3, "FID": "DEF0F", "NUTS_ID": "DEF0F"}, "id": "DEF0F"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.17066, 51.01873], [11.11026, 50.91311], [10.93617, 50.89487], [10.95043, 50.92884], [10.87655, 50.96663], [10.86299, 51.02654], [10.93153, 51.02126], [11.0576, 51.07427], [11.12703, 51.01714], [11.17066, 51.01873]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Erfurt, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEG01", "NUTS_ID": "DEG01"}, "id": "DEG01"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.02117, 50.94795], [12.02102, 50.96912], [12.16353, 50.9586], [12.12746, 50.8133], [12.0078, 50.84807], [12.03777, 50.92731], [12.02117, 50.94795]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Gera, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEG02", "NUTS_ID": "DEG02"}, "id": "DEG02"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.50481, 50.90624], [11.51049, 50.96246], [11.54732, 50.98906], [11.65541, 50.95724], [11.65248, 50.89952], [11.57502, 50.86326], [11.50481, 50.90624]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Jena, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEG03", "NUTS_ID": "DEG03"}, "id": "DEG03"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.82699, 50.58095], [10.74934, 50.60586], [10.6791, 50.56885], [10.58246, 50.58544], [10.56717, 50.60935], [10.73373, 50.66349], [10.82699, 50.58095]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Suhl, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEG04", "NUTS_ID": "DEG04"}, "id": "DEG04"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.2352, 51.01948], [11.2485, 51.03685], [11.3466, 51.01995], [11.37001, 50.98504], [11.39239, 50.99552], [11.39925, 50.95573], [11.33419, 50.94188], [11.29248, 50.91695], [11.26035, 50.98743], [11.2352, 51.01948]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Weimar, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEG05", "NUTS_ID": "DEG05"}, "id": "DEG05"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.48855, 51.57478], [10.52468, 51.51828], [10.49384, 51.41354], [10.52317, 51.36974], [10.48357, 51.3433], [10.39661, 51.33834], [10.29598, 51.2415], [10.20977, 51.22257], [10.20694, 51.19065], [9.95493, 51.30432], [9.92834, 51.3753], [10.28585, 51.49328], [10.38816, 51.57968], [10.48855, 51.57478]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Eichsfeld", "LEVL_CODE": 3, "FID": "DEG06", "NUTS_ID": "DEG06"}, "id": "DEG06"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.91606, 51.61637], [10.89613, 51.57087], [10.97811, 51.42689], [10.52317, 51.36974], [10.49384, 51.41354], [10.52468, 51.51828], [10.48855, 51.57478], [10.64271, 51.56707], [10.67728, 51.63838], [10.70137, 51.64219], [10.91606, 51.61637]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Nordhausen", "LEVL_CODE": 3, "FID": "DEG07", "NUTS_ID": "DEG07"}, "id": "DEG07"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.92257, 51.20533], [10.88031, 51.09825], [10.73715, 51.10351], [10.69066, 51.04608], [10.61471, 51.03916], [10.48165, 51.07989], [10.42321, 51.06189], [10.32867, 51.14644], [10.21001, 51.14408], [10.20694, 51.19065], [10.20977, 51.22257], [10.29598, 51.2415], [10.39661, 51.33834], [10.48357, 51.3433], [10.52205, 51.31444], [10.60711, 51.31417], [10.73102, 51.21305], [10.92257, 51.20533]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Unstrut-Hainich-Kreis", "LEVL_CODE": 3, "FID": "DEG09", "NUTS_ID": "DEG09"}, "id": "DEG09"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.40577, 51.3422], [11.47397, 51.29506], [11.38537, 51.24589], [11.32992, 51.28135], [11.29599, 51.25147], [11.18338, 51.24881], [11.08104, 51.31507], [10.98247, 51.21095], [10.92257, 51.20533], [10.73102, 51.21305], [10.60711, 51.31417], [10.52205, 51.31444], [10.48357, 51.3433], [10.52317, 51.36974], [10.97811, 51.42689], [11.3133, 51.40464], [11.38353, 51.38043], [11.40577, 51.3422]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kyffh\u00e4userkreis", "LEVL_CODE": 3, "FID": "DEG0A", "NUTS_ID": "DEG0A"}, "id": "DEG0A"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.7527, 50.71847], [10.73373, 50.66349], [10.56717, 50.60935], [10.58246, 50.58544], [10.55197, 50.55814], [10.57336, 50.44795], [10.49341, 50.43893], [10.45053, 50.40186], [10.39977, 50.40382], [10.32859, 50.48859], [10.20334, 50.54809], [10.10473, 50.55549], [10.04134, 50.51647], [10.04611, 50.60897], [10.07756, 50.63762], [10.19779, 50.631], [10.18254, 50.69326], [10.21546, 50.76598], [10.37274, 50.79037], [10.41141, 50.85024], [10.52161, 50.8301], [10.60712, 50.72998], [10.7527, 50.71847]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Schmalkalden-Meiningen", "LEVL_CODE": 3, "FID": "DEG0B", "NUTS_ID": "DEG0B"}, "id": "DEG0B"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.88031, 51.09825], [10.86299, 51.02654], [10.87655, 50.96663], [10.95043, 50.92884], [10.93617, 50.89487], [10.86024, 50.86524], [10.7527, 50.71847], [10.60712, 50.72998], [10.52161, 50.8301], [10.41141, 50.85024], [10.49175, 50.93094], [10.49361, 50.99234], [10.61471, 51.03916], [10.69066, 51.04608], [10.73715, 51.10351], [10.88031, 51.09825]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Gotha", "LEVL_CODE": 3, "FID": "DEG0C", "NUTS_ID": "DEG0C"}, "id": "DEG0C"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.38537, 51.24589], [11.38177, 51.21505], [11.46565, 51.18627], [11.48461, 51.10544], [11.21178, 51.10039], [11.20583, 51.03564], [11.17066, 51.01873], [11.12703, 51.01714], [11.0576, 51.07427], [10.93153, 51.02126], [10.86299, 51.02654], [10.88031, 51.09825], [10.92257, 51.20533], [10.98247, 51.21095], [11.08104, 51.31507], [11.18338, 51.24881], [11.29599, 51.25147], [11.32992, 51.28135], [11.38537, 51.24589]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "S\u00f6mmerda", "LEVL_CODE": 3, "FID": "DEG0D", "NUTS_ID": "DEG0D"}, "id": "DEG0D"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.82699, 50.58095], [10.90064, 50.59884], [10.96865, 50.55275], [10.99261, 50.52862], [11.01522, 50.45839], [10.94572, 50.38647], [10.74028, 50.35929], [10.73648, 50.3202], [10.81267, 50.26466], [10.7292, 50.23001], [10.61012, 50.228], [10.59076, 50.32912], [10.45053, 50.40186], [10.49341, 50.43893], [10.57336, 50.44795], [10.55197, 50.55814], [10.58246, 50.58544], [10.6791, 50.56885], [10.74934, 50.60586], [10.82699, 50.58095]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hildburghausen", "LEVL_CODE": 3, "FID": "DEG0E", "NUTS_ID": "DEG0E"}, "id": "DEG0E"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.22189, 50.80389], [11.1883, 50.78848], [11.17447, 50.73566], [11.06428, 50.68737], [11.06115, 50.56476], [10.96865, 50.55275], [10.90064, 50.59884], [10.82699, 50.58095], [10.73373, 50.66349], [10.7527, 50.71847], [10.86024, 50.86524], [10.93617, 50.89487], [11.11026, 50.91311], [11.1696, 50.82796], [11.22189, 50.80389]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ilm-Kreis", "LEVL_CODE": 3, "FID": "DEG0F", "NUTS_ID": "DEG0F"}, "id": "DEG0F"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.69694, 51.08749], [11.6658, 51.03775], [11.54732, 50.98906], [11.51049, 50.96246], [11.50481, 50.90624], [11.42387, 50.8086], [11.22189, 50.80389], [11.1696, 50.82796], [11.11026, 50.91311], [11.17066, 51.01873], [11.20583, 51.03564], [11.21178, 51.10039], [11.48461, 51.10544], [11.69694, 51.08749]], [[11.2352, 51.01948], [11.26035, 50.98743], [11.29248, 50.91695], [11.33419, 50.94188], [11.39925, 50.95573], [11.39239, 50.99552], [11.37001, 50.98504], [11.3466, 51.01995], [11.2485, 51.03685], [11.2352, 51.01948]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Weimarer Land", "LEVL_CODE": 3, "FID": "DEG0G", "NUTS_ID": "DEG0G"}, "id": "DEG0G"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.26594, 50.47942], [11.27682, 50.37124], [11.25049, 50.29011], [11.18994, 50.27119], [11.1207, 50.35826], [11.03729, 50.35152], [10.94572, 50.38647], [11.01522, 50.45839], [10.99261, 50.52862], [11.14877, 50.53788], [11.16569, 50.50445], [11.25009, 50.50218], [11.26594, 50.47942]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Sonneberg", "LEVL_CODE": 3, "FID": "DEG0H", "NUTS_ID": "DEG0H"}, "id": "DEG0H"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.54471, 50.75079], [11.49651, 50.68815], [11.52264, 50.63699], [11.59781, 50.59285], [11.50164, 50.53376], [11.48157, 50.43162], [11.43091, 50.44101], [11.40122, 50.51758], [11.26594, 50.47942], [11.25009, 50.50218], [11.16569, 50.50445], [11.14877, 50.53788], [10.99261, 50.52862], [10.96865, 50.55275], [11.06115, 50.56476], [11.06428, 50.68737], [11.17447, 50.73566], [11.1883, 50.78848], [11.22189, 50.80389], [11.42387, 50.8086], [11.54471, 50.75079]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Saalfeld-Rudolstadt", "LEVL_CODE": 3, "FID": "DEG0I", "NUTS_ID": "DEG0I"}, "id": "DEG0I"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.02102, 50.96912], [12.02117, 50.94795], [11.89655, 50.88873], [11.89818, 50.82243], [11.86655, 50.78807], [11.54471, 50.75079], [11.42387, 50.8086], [11.50481, 50.90624], [11.57502, 50.86326], [11.65248, 50.89952], [11.65541, 50.95724], [11.54732, 50.98906], [11.6658, 51.03775], [11.69694, 51.08749], [11.77548, 51.04879], [11.88622, 51.04996], [12.02102, 50.96912]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Saale-Holzland-Kreis", "LEVL_CODE": 3, "FID": "DEG0J", "NUTS_ID": "DEG0J"}, "id": "DEG0J"}, {"geometry": {"type": "Polygon", "coordinates": [[[11.94414, 50.59128], [11.88319, 50.53683], [11.94625, 50.49647], [11.90694, 50.44742], [11.91932, 50.42473], [11.83701, 50.3974], [11.60329, 50.39877], [11.52534, 50.38395], [11.48157, 50.43162], [11.50164, 50.53376], [11.59781, 50.59285], [11.52264, 50.63699], [11.49651, 50.68815], [11.54471, 50.75079], [11.86655, 50.78807], [11.915, 50.76447], [11.87585, 50.69579], [11.89042, 50.63214], [11.94414, 50.59128]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Saale-Orla-Kreis", "LEVL_CODE": 3, "FID": "DEG0K", "NUTS_ID": "DEG0K"}, "id": "DEG0K"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.22417, 50.94294], [12.2186, 50.88184], [12.25083, 50.81832], [12.2752, 50.77964], [12.23849, 50.74289], [12.31892, 50.67653], [12.05581, 50.55576], [12.01046, 50.60492], [11.94414, 50.59128], [11.89042, 50.63214], [11.87585, 50.69579], [11.915, 50.76447], [11.86655, 50.78807], [11.89818, 50.82243], [11.89655, 50.88873], [12.02117, 50.94795], [12.03777, 50.92731], [12.0078, 50.84807], [12.12746, 50.8133], [12.16353, 50.9586], [12.22417, 50.94294]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Greiz", "LEVL_CODE": 3, "FID": "DEG0L", "NUTS_ID": "DEG0L"}, "id": "DEG0L"}, {"geometry": {"type": "Polygon", "coordinates": [[[12.61736, 50.98079], [12.65287, 50.92368], [12.25083, 50.81832], [12.2186, 50.88184], [12.22417, 50.94294], [12.28207, 51.01877], [12.28355, 51.09192], [12.47089, 51.0772], [12.55198, 51.00085], [12.61736, 50.98079]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Altenburger Land", "LEVL_CODE": 3, "FID": "DEG0M", "NUTS_ID": "DEG0M"}, "id": "DEG0M"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.18235, 50.99849], [10.20218, 51.01201], [10.2689, 51.00452], [10.34109, 51.0422], [10.37422, 51.03088], [10.38696, 50.99416], [10.37229, 50.96323], [10.31101, 50.93633], [10.1895, 50.97592], [10.18235, 50.99849]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Eisenach, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEG0N", "NUTS_ID": "DEG0N"}, "id": "DEG0N"}, {"geometry": {"type": "Polygon", "coordinates": [[[10.61471, 51.03916], [10.49361, 50.99234], [10.49175, 50.93094], [10.41141, 50.85024], [10.37274, 50.79037], [10.21546, 50.76598], [10.18254, 50.69326], [10.19779, 50.631], [10.07756, 50.63762], [10.01691, 50.66832], [9.94368, 50.63634], [9.89996, 50.64992], [9.92618, 50.76739], [10.0171, 50.87603], [9.98991, 50.9233], [10.02156, 50.99295], [10.18235, 50.99849], [10.1895, 50.97592], [10.31101, 50.93633], [10.37229, 50.96323], [10.38696, 50.99416], [10.37422, 51.03088], [10.34109, 51.0422], [10.2689, 51.00452], [10.20218, 51.01201], [10.15582, 51.05963], [10.14656, 51.1362], [10.21001, 51.14408], [10.32867, 51.14644], [10.42321, 51.06189], [10.48165, 51.07989], [10.61471, 51.03916]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wartburgkreis", "LEVL_CODE": 3, "FID": "DEG0P", "NUTS_ID": "DEG0P"}, "id": "DEG0P"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.52214, 51.22573], [6.49344, 51.20327], [6.5255, 51.15397], [6.44399, 51.08979], [6.36991, 51.10041], [6.29114, 51.17555], [6.34029, 51.21988], [6.46375, 51.24292], [6.52214, 51.22573]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00f6nchengladbach, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA15", "NUTS_ID": "DEA15"}, "id": "DEA15"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.90374, 51.36743], [6.83046, 51.35227], [6.81004, 51.45526], [6.89992, 51.47129], [6.95129, 51.42676], [6.90374, 51.36743]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00fclheim an der Ruhr, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA16", "NUTS_ID": "DEA16"}, "id": "DEA16"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.92841, 51.49796], [6.89992, 51.47129], [6.81004, 51.45526], [6.78402, 51.53323], [6.83315, 51.57965], [6.92841, 51.49796]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oberhausen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA17", "NUTS_ID": "DEA17"}, "id": "DEA17"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.29581, 51.20539], [7.26818, 51.14566], [7.16635, 51.15394], [7.13717, 51.16552], [7.1958, 51.21627], [7.29581, 51.20539]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Remscheid, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA18", "NUTS_ID": "DEA18"}, "id": "DEA18"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.13717, 51.16552], [7.16635, 51.15394], [7.15255, 51.12306], [6.99772, 51.11798], [6.97569, 51.16578], [7.04695, 51.20746], [7.0951, 51.21007], [7.13717, 51.16552]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Solingen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA19", "NUTS_ID": "DEA19"}, "id": "DEA19"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.30669, 51.23888], [7.29581, 51.20539], [7.1958, 51.21627], [7.13717, 51.16552], [7.0951, 51.21007], [7.04695, 51.20746], [7.06072, 51.26444], [7.16729, 51.3129], [7.25412, 51.30694], [7.30669, 51.23888]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wuppertal, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA1A", "NUTS_ID": "DEA1A"}, "id": "DEA1A"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.40778, 51.82809], [6.49021, 51.81138], [6.50658, 51.77506], [6.47509, 51.71481], [6.39277, 51.7202], [6.35924, 51.65788], [6.30616, 51.63126], [6.38763, 51.57059], [6.46818, 51.5582], [6.51664, 51.425], [6.30645, 51.36274], [6.22441, 51.36498], [6.2042, 51.51872], [6.10589, 51.60073], [6.10265, 51.65623], [5.95319, 51.74785], [5.97974, 51.77088], [5.96569, 51.8293], [6.06133, 51.85875], [6.15317, 51.846], [6.16777, 51.9008], [6.40778, 51.82809]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kleve", "LEVL_CODE": 3, "FID": "DEA1B", "NUTS_ID": "DEA1B"}, "id": "DEA1B"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.16729, 51.3129], [7.06072, 51.26444], [7.04695, 51.20746], [6.97569, 51.16578], [6.99772, 51.11798], [6.99059, 51.0916], [6.89805, 51.06489], [6.85349, 51.08426], [6.85605, 51.12619], [6.91189, 51.13967], [6.88348, 51.22328], [6.92143, 51.25814], [6.812, 51.28365], [6.8052, 51.3488], [6.83046, 51.35227], [6.90374, 51.36743], [7.11768, 51.38027], [7.16729, 51.3129]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mettmann", "LEVL_CODE": 3, "FID": "DEA1C", "NUTS_ID": "DEA1C"}, "id": "DEA1C"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.71486, 51.3332], [6.72473, 51.20656], [6.85605, 51.12619], [6.85349, 51.08426], [6.77363, 51.06439], [6.70449, 51.01914], [6.58848, 51.02335], [6.53329, 51.05439], [6.4805, 51.03418], [6.45921, 51.04307], [6.44399, 51.08979], [6.5255, 51.15397], [6.49344, 51.20327], [6.52214, 51.22573], [6.59323, 51.2485], [6.5855, 51.28683], [6.70617, 51.33669], [6.71486, 51.3332]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rhein-Kreis Neuss", "LEVL_CODE": 3, "FID": "DEA1D", "NUTS_ID": "DEA1D"}, "id": "DEA1D"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.53704, 51.40631], [6.48642, 51.36094], [6.51909, 51.29346], [6.5855, 51.28683], [6.59323, 51.2485], [6.52214, 51.22573], [6.46375, 51.24292], [6.34029, 51.21988], [6.29114, 51.17555], [6.17481, 51.18451], [6.08934, 51.17902], [6.07266, 51.24259], [6.22441, 51.36498], [6.30645, 51.36274], [6.51664, 51.425], [6.53704, 51.40631]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Viersen", "LEVL_CODE": 3, "FID": "DEA1E", "NUTS_ID": "DEA1E"}, "id": "DEA1E"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.91086, 51.7461], [6.92843, 51.71492], [6.90228, 51.63568], [6.83315, 51.57965], [6.78402, 51.53323], [6.69107, 51.54996], [6.64185, 51.50115], [6.67202, 51.44663], [6.63574, 51.39297], [6.53704, 51.40631], [6.51664, 51.425], [6.46818, 51.5582], [6.38763, 51.57059], [6.30616, 51.63126], [6.35924, 51.65788], [6.39277, 51.7202], [6.47509, 51.71481], [6.50658, 51.77506], [6.49021, 51.81138], [6.83641, 51.73424], [6.91086, 51.7461]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Wesel", "LEVL_CODE": 3, "FID": "DEA1F", "NUTS_ID": "DEA1F"}, "id": "DEA1F"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.21087, 50.64954], [7.1952, 50.64272], [7.04598, 50.64564], [7.03962, 50.74986], [7.17453, 50.75178], [7.21087, 50.64954]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bonn, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA22", "NUTS_ID": "DEA22"}, "id": "DEA22"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.89805, 51.06489], [6.98218, 51.01879], [7.06791, 51.01874], [7.16128, 50.86729], [6.98094, 50.84092], [6.94029, 50.84279], [6.82966, 50.92974], [6.83728, 51.01445], [6.77363, 51.06439], [6.85349, 51.08426], [6.89805, 51.06489]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "K\u00f6ln, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA23", "NUTS_ID": "DEA23"}, "id": "DEA23"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.99059, 51.0916], [7.08731, 51.06205], [7.06791, 51.01874], [6.98218, 51.01879], [6.89805, 51.06489], [6.99059, 51.0916]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Leverkusen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA24", "NUTS_ID": "DEA24"}, "id": "DEA24"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.4805, 51.03418], [6.47155, 50.98025], [6.56102, 50.8572], [6.702, 50.82471], [6.69382, 50.75383], [6.67482, 50.72447], [6.60121, 50.70848], [6.54875, 50.6065], [6.41691, 50.6093], [6.38629, 50.65798], [6.30232, 50.67015], [6.35062, 50.7681], [6.32352, 50.78303], [6.32824, 50.8467], [6.29946, 50.87722], [6.22551, 50.87509], [6.19449, 50.95013], [6.28135, 51.01109], [6.45921, 51.04307], [6.4805, 51.03418]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "D\u00fcren", "LEVL_CODE": 3, "FID": "DEA26", "NUTS_ID": "DEA26"}, "id": "DEA26"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.98094, 50.84092], [7.01541, 50.8008], [6.86422, 50.78496], [6.76835, 50.72466], [6.69382, 50.75383], [6.702, 50.82471], [6.56102, 50.8572], [6.47155, 50.98025], [6.4805, 51.03418], [6.53329, 51.05439], [6.58848, 51.02335], [6.70449, 51.01914], [6.77363, 51.06439], [6.83728, 51.01445], [6.82966, 50.92974], [6.94029, 50.84279], [6.98094, 50.84092]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rhein-Erft-Kreis", "LEVL_CODE": 3, "FID": "DEA27", "NUTS_ID": "DEA27"}, "id": "DEA27"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.9279, 50.55862], [6.88241, 50.46473], [6.77049, 50.4728], [6.80071, 50.36178], [6.70217, 50.34774], [6.5822, 50.37594], [6.43125, 50.3585], [6.4253, 50.32301], [6.40503, 50.32331], [6.3547, 50.3791], [6.36778, 50.44517], [6.31556, 50.49704], [6.41691, 50.6093], [6.54875, 50.6065], [6.60121, 50.70848], [6.67482, 50.72447], [6.69382, 50.75383], [6.76835, 50.72466], [6.86422, 50.78496], [6.90377, 50.73319], [6.86744, 50.65724], [6.9279, 50.55862]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Euskirchen", "LEVL_CODE": 3, "FID": "DEA28", "NUTS_ID": "DEA28"}, "id": "DEA28"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.29114, 51.17555], [6.36991, 51.10041], [6.44399, 51.08979], [6.45921, 51.04307], [6.28135, 51.01109], [6.19449, 50.95013], [6.13643, 50.9082], [6.08695, 50.91314], [6.03069, 50.93438], [6.00913, 50.98192], [5.90981, 50.98644], [5.87709, 51.0321], [5.90004, 51.05898], [5.96334, 51.04808], [6.17481, 51.18451], [6.29114, 51.17555]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Heinsberg", "LEVL_CODE": 3, "FID": "DEA29", "NUTS_ID": "DEA29"}, "id": "DEA29"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.43334, 51.21441], [7.43191, 51.18239], [7.50437, 51.11077], [7.71612, 51.07291], [7.73083, 51.00061], [7.7859, 50.93991], [7.75384, 50.85927], [7.661, 50.82036], [7.53555, 50.84285], [7.47461, 50.88048], [7.45292, 50.93482], [7.39848, 50.94975], [7.26464, 50.98621], [7.32568, 51.06057], [7.26818, 51.14566], [7.29581, 51.20539], [7.30669, 51.23888], [7.38338, 51.24244], [7.43334, 51.21441]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Oberbergischer Kreis", "LEVL_CODE": 3, "FID": "DEA2A", "NUTS_ID": "DEA2A"}, "id": "DEA2A"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.26818, 51.14566], [7.32568, 51.06057], [7.26464, 50.98621], [7.39848, 50.94975], [7.33216, 50.91215], [7.2491, 50.92002], [7.16128, 50.86729], [7.06791, 51.01874], [7.08731, 51.06205], [6.99059, 51.0916], [6.99772, 51.11798], [7.15255, 51.12306], [7.16635, 51.15394], [7.26818, 51.14566]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rheinisch-Bergischer Kreis", "LEVL_CODE": 3, "FID": "DEA2B", "NUTS_ID": "DEA2B"}, "id": "DEA2B"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.661, 50.82036], [7.65962, 50.77917], [7.44077, 50.71144], [7.38156, 50.71116], [7.33547, 50.64371], [7.2124, 50.62341], [7.21087, 50.64954], [7.17453, 50.75178], [7.03962, 50.74986], [7.04598, 50.64564], [7.1952, 50.64272], [7.14964, 50.60827], [6.9279, 50.55862], [6.86744, 50.65724], [6.90377, 50.73319], [6.86422, 50.78496], [7.01541, 50.8008], [6.98094, 50.84092], [7.16128, 50.86729], [7.2491, 50.92002], [7.33216, 50.91215], [7.39848, 50.94975], [7.45292, 50.93482], [7.47461, 50.88048], [7.53555, 50.84285], [7.661, 50.82036]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rhein-Sieg-Kreis", "LEVL_CODE": 3, "FID": "DEA2C", "NUTS_ID": "DEA2C"}, "id": "DEA2C"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.41691, 50.6093], [6.31556, 50.49704], [6.1922, 50.52106], [6.18931, 50.56609], [6.25606, 50.6221], [6.18382, 50.63843], [6.11859, 50.71241], [6.021, 50.7543], [6.01023, 50.80357], [6.07193, 50.85841], [6.08695, 50.91314], [6.13643, 50.9082], [6.19449, 50.95013], [6.22551, 50.87509], [6.29946, 50.87722], [6.32824, 50.8467], [6.32352, 50.78303], [6.35062, 50.7681], [6.30232, 50.67015], [6.38629, 50.65798], [6.41691, 50.6093]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "St\u00e4dteregion Aachen", "LEVL_CODE": 3, "FID": "DEA2D", "NUTS_ID": "DEA2D"}, "id": "DEA2D"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.99865, 51.5337], [6.92841, 51.49796], [6.83315, 51.57965], [6.90228, 51.63568], [6.97134, 51.62328], [6.93232, 51.5692], [6.99865, 51.5337]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bottrop, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA31", "NUTS_ID": "DEA31"}, "id": "DEA31"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.14506, 51.55224], [7.13956, 51.50616], [7.1042, 51.48127], [7.01021, 51.53273], [6.99833, 51.60772], [7.01864, 51.62224], [7.11926, 51.58385], [7.14506, 51.55224]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Gelsenkirchen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA32", "NUTS_ID": "DEA32"}, "id": "DEA32"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.70412, 52.03442], [7.7632, 51.91291], [7.61323, 51.84061], [7.51316, 51.90805], [7.49093, 51.95386], [7.51239, 51.99967], [7.5893, 52.05305], [7.70412, 52.03442]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00fcnster, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA33", "NUTS_ID": "DEA33"}, "id": "DEA33"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.26771, 52.05839], [7.06892, 52.00007], [7.06961, 51.87419], [7.17254, 51.8215], [7.08217, 51.78218], [6.98187, 51.79376], [6.91086, 51.7461], [6.83641, 51.73424], [6.49021, 51.81138], [6.40778, 51.82809], [6.41882, 51.86126], [6.76963, 51.92128], [6.81948, 51.99048], [6.6953, 52.05082], [6.76047, 52.11857], [6.85858, 52.12669], [6.99169, 52.22224], [7.06569, 52.24137], [7.09915, 52.24306], [7.15697, 52.14235], [7.24795, 52.11036], [7.26771, 52.05839]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Borken", "LEVL_CODE": 3, "FID": "DEA34", "NUTS_ID": "DEA34"}, "id": "DEA34"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.51239, 51.99967], [7.49093, 51.95386], [7.51316, 51.90805], [7.61323, 51.84061], [7.73579, 51.73737], [7.70189, 51.71238], [7.54961, 51.69373], [7.46121, 51.72727], [7.40955, 51.66458], [7.31784, 51.7034], [7.24966, 51.79791], [7.17254, 51.8215], [7.06961, 51.87419], [7.06892, 52.00007], [7.26771, 52.05839], [7.51239, 51.99967]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Coesfeld", "LEVL_CODE": 3, "FID": "DEA35", "NUTS_ID": "DEA35"}, "id": "DEA35"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.40955, 51.66458], [7.45032, 51.63008], [7.41793, 51.59952], [7.35361, 51.57703], [7.35187, 51.53264], [7.3146, 51.52232], [7.29359, 51.53226], [7.24876, 51.56763], [7.14506, 51.55224], [7.11926, 51.58385], [7.01864, 51.62224], [6.99833, 51.60772], [7.01021, 51.53273], [6.99865, 51.5337], [6.93232, 51.5692], [6.97134, 51.62328], [6.90228, 51.63568], [6.92843, 51.71492], [6.91086, 51.7461], [6.98187, 51.79376], [7.08217, 51.78218], [7.17254, 51.8215], [7.24966, 51.79791], [7.31784, 51.7034], [7.40955, 51.66458]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Recklinghausen", "LEVL_CODE": 3, "FID": "DEA36", "NUTS_ID": "DEA36"}, "id": "DEA36"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.96463, 52.32486], [7.95647, 52.27249], [7.91207, 52.2044], [7.99508, 52.16713], [8.00194, 52.12396], [7.88516, 52.0833], [7.76878, 52.09772], [7.70412, 52.03442], [7.5893, 52.05305], [7.51239, 51.99967], [7.26771, 52.05839], [7.24795, 52.11036], [7.15697, 52.14235], [7.09915, 52.24306], [7.31748, 52.28027], [7.56493, 52.37971], [7.60804, 52.47402], [7.67656, 52.45433], [7.74599, 52.39098], [7.91999, 52.36958], [7.96463, 52.32486]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Steinfurt", "LEVL_CODE": 3, "FID": "DEA37", "NUTS_ID": "DEA37"}, "id": "DEA37"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.88516, 52.0833], [7.96336, 52.04106], [8.09645, 52.05715], [8.08464, 52.01722], [8.16405, 51.89055], [8.32014, 51.72571], [8.23869, 51.66169], [8.15089, 51.70851], [7.94453, 51.70058], [7.84316, 51.73805], [7.73579, 51.73737], [7.61323, 51.84061], [7.7632, 51.91291], [7.70412, 52.03442], [7.76878, 52.09772], [7.88516, 52.0833]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Warendorf", "LEVL_CODE": 3, "FID": "DEA38", "NUTS_ID": "DEA38"}, "id": "DEA38"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.60634, 51.92737], [8.42847, 51.95628], [8.44969, 52.0135], [8.4318, 52.05109], [8.47154, 52.10075], [8.53433, 52.10805], [8.65428, 52.06015], [8.65139, 51.97914], [8.60634, 51.92737]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bielefeld, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA41", "NUTS_ID": "DEA41"}, "id": "DEA41"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.41059, 52.11512], [8.47154, 52.10075], [8.4318, 52.05109], [8.44969, 52.0135], [8.42847, 51.95628], [8.60634, 51.92737], [8.68411, 51.91108], [8.74839, 51.85867], [8.55892, 51.83945], [8.43314, 51.76798], [8.40311, 51.71936], [8.32014, 51.72571], [8.16405, 51.89055], [8.08464, 52.01722], [8.09645, 52.05715], [8.26542, 52.12667], [8.41059, 52.11512]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "G\u00fctersloh", "LEVL_CODE": 3, "FID": "DEA42", "NUTS_ID": "DEA42"}, "id": "DEA42"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.92712, 52.18443], [8.87786, 52.11239], [8.75866, 52.11764], [8.65428, 52.06015], [8.53433, 52.10805], [8.47154, 52.10075], [8.41059, 52.11512], [8.48855, 52.17629], [8.45144, 52.22068], [8.46619, 52.26761], [8.72021, 52.24301], [8.80056, 52.17489], [8.92712, 52.18443]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Herford", "LEVL_CODE": 3, "FID": "DEA43", "NUTS_ID": "DEA43"}, "id": "DEA43"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.32334, 51.85506], [9.43305, 51.83727], [9.38417, 51.66342], [9.41732, 51.64727], [9.44046, 51.65039], [9.35177, 51.61231], [9.3, 51.51811], [9.15541, 51.44268], [9.09933, 51.45218], [9.06897, 51.4961], [8.97065, 51.50677], [8.9054, 51.5315], [9.00583, 51.58114], [8.95926, 51.78974], [8.96539, 51.83459], [9.10161, 51.89343], [9.18231, 51.86095], [9.32334, 51.85506]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "H\u00f6xter", "LEVL_CODE": 3, "FID": "DEA44", "NUTS_ID": "DEA44"}, "id": "DEA44"}, {"geometry": {"type": "Polygon", "coordinates": [[[9.1556, 52.09783], [9.19628, 51.97329], [9.25376, 51.96964], [9.30889, 51.92272], [9.33589, 51.88915], [9.32334, 51.85506], [9.18231, 51.86095], [9.10161, 51.89343], [8.96539, 51.83459], [8.95926, 51.78974], [8.80089, 51.80159], [8.74839, 51.85867], [8.68411, 51.91108], [8.60634, 51.92737], [8.65139, 51.97914], [8.65428, 52.06015], [8.75866, 52.11764], [8.87786, 52.11239], [8.92712, 52.18443], [8.99363, 52.19018], [9.02118, 52.14389], [9.12825, 52.13211], [9.1556, 52.09783]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Lippe", "LEVL_CODE": 3, "FID": "DEA45", "NUTS_ID": "DEA45"}, "id": "DEA45"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.70301, 52.50044], [8.70828, 52.42348], [8.7449, 52.39407], [8.94133, 52.40808], [9.06959, 52.4973], [9.11128, 52.47795], [9.12525, 52.41199], [8.99361, 52.31157], [8.98341, 52.26816], [9.05083, 52.22106], [9.03677, 52.19088], [8.99363, 52.19018], [8.92712, 52.18443], [8.80056, 52.17489], [8.72021, 52.24301], [8.46619, 52.26761], [8.43712, 52.35826], [8.33064, 52.40323], [8.29721, 52.4565], [8.41469, 52.44739], [8.49193, 52.50521], [8.63995, 52.52496], [8.70301, 52.50044]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Minden-L\u00fcbbecke", "LEVL_CODE": 3, "FID": "DEA46", "NUTS_ID": "DEA46"}, "id": "DEA46"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.95926, 51.78974], [9.00583, 51.58114], [8.9054, 51.5315], [8.83099, 51.53816], [8.82125, 51.48955], [8.76574, 51.4539], [8.63353, 51.48132], [8.54793, 51.46781], [8.48378, 51.56659], [8.57252, 51.61635], [8.57298, 51.64776], [8.40311, 51.71936], [8.43314, 51.76798], [8.55892, 51.83945], [8.74839, 51.85867], [8.80089, 51.80159], [8.95926, 51.78974]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Paderborn", "LEVL_CODE": 3, "FID": "DEA47", "NUTS_ID": "DEA47"}, "id": "DEA47"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.3146, 51.52232], [7.34819, 51.48267], [7.24727, 51.41852], [7.13603, 51.42604], [7.1042, 51.48127], [7.13956, 51.50616], [7.29359, 51.53226], [7.3146, 51.52232]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bochum, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA51", "NUTS_ID": "DEA51"}, "id": "DEA51"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.50845, 51.41794], [7.46533, 51.41607], [7.34819, 51.48267], [7.3146, 51.52232], [7.35187, 51.53264], [7.35361, 51.57703], [7.41793, 51.59952], [7.58022, 51.57858], [7.62356, 51.53514], [7.50845, 51.41794]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Dortmund, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA52", "NUTS_ID": "DEA52"}, "id": "DEA52"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.58782, 51.39039], [7.57532, 51.29913], [7.51725, 51.26487], [7.48871, 51.31157], [7.39784, 51.34093], [7.4114, 51.38837], [7.46533, 51.41607], [7.50845, 51.41794], [7.58782, 51.39039]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hagen, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA53", "NUTS_ID": "DEA53"}, "id": "DEA53"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.94453, 51.70058], [7.9613, 51.68096], [7.90895, 51.60705], [7.82715, 51.57863], [7.79937, 51.61739], [7.6905, 51.62459], [7.70189, 51.71238], [7.73579, 51.73737], [7.84316, 51.73805], [7.94453, 51.70058]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hamm, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA54", "NUTS_ID": "DEA54"}, "id": "DEA54"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.29359, 51.53226], [7.13956, 51.50616], [7.14506, 51.55224], [7.24876, 51.56763], [7.29359, 51.53226]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Herne, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEA55", "NUTS_ID": "DEA55"}, "id": "DEA55"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.46533, 51.41607], [7.4114, 51.38837], [7.39784, 51.34093], [7.48871, 51.31157], [7.51725, 51.26487], [7.43334, 51.21441], [7.38338, 51.24244], [7.30669, 51.23888], [7.25412, 51.30694], [7.16729, 51.3129], [7.11768, 51.38027], [7.13603, 51.42604], [7.24727, 51.41852], [7.34819, 51.48267], [7.46533, 51.41607]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ennepe-Ruhr-Kreis", "LEVL_CODE": 3, "FID": "DEA56", "NUTS_ID": "DEA56"}, "id": "DEA56"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.9054, 51.5315], [8.97065, 51.50677], [8.90313, 51.47932], [8.94019, 51.42677], [8.93129, 51.39628], [8.69915, 51.37232], [8.58325, 51.30214], [8.5671, 51.27815], [8.59026, 51.2575], [8.7196, 51.26435], [8.75279, 51.18889], [8.67266, 51.10266], [8.54909, 51.10187], [8.47637, 51.14428], [8.33906, 51.0973], [8.2388, 51.10434], [8.15343, 51.15323], [8.08557, 51.23751], [7.93984, 51.23401], [7.86377, 51.45885], [7.90744, 51.48314], [8.0013, 51.47007], [8.31601, 51.3926], [8.54793, 51.46781], [8.63353, 51.48132], [8.76574, 51.4539], [8.82125, 51.48955], [8.83099, 51.53816], [8.9054, 51.5315]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Hochsauerlandkreis", "LEVL_CODE": 3, "FID": "DEA57", "NUTS_ID": "DEA57"}, "id": "DEA57"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.86377, 51.45885], [7.93984, 51.23401], [7.93204, 51.17953], [7.82758, 51.15195], [7.81875, 51.08762], [7.71612, 51.07291], [7.50437, 51.11077], [7.43191, 51.18239], [7.43334, 51.21441], [7.51725, 51.26487], [7.57532, 51.29913], [7.58782, 51.39039], [7.64899, 51.45781], [7.83763, 51.47853], [7.86377, 51.45885]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "M\u00e4rkischer Kreis", "LEVL_CODE": 3, "FID": "DEA58", "NUTS_ID": "DEA58"}, "id": "DEA58"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.2388, 51.10434], [8.1988, 51.01845], [7.97744, 51.03265], [7.91228, 50.93478], [7.8515, 50.92583], [7.7859, 50.93991], [7.73083, 51.00061], [7.71612, 51.07291], [7.81875, 51.08762], [7.82758, 51.15195], [7.93204, 51.17953], [7.93984, 51.23401], [8.08557, 51.23751], [8.15343, 51.15323], [8.2388, 51.10434]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Olpe", "LEVL_CODE": 3, "FID": "DEA59", "NUTS_ID": "DEA59"}, "id": "DEA59"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.54909, 51.10187], [8.51534, 51.08024], [8.52778, 51.01607], [8.47789, 50.96905], [8.3555, 50.862], [8.25925, 50.8711], [8.15445, 50.80528], [8.15699, 50.73037], [8.12578, 50.68581], [8.03969, 50.69738], [7.97336, 50.78337], [7.96507, 50.83744], [7.84088, 50.88589], [7.8515, 50.92583], [7.91228, 50.93478], [7.97744, 51.03265], [8.1988, 51.01845], [8.2388, 51.10434], [8.33906, 51.0973], [8.47637, 51.14428], [8.54909, 51.10187]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Siegen-Wittgenstein", "LEVL_CODE": 3, "FID": "DEA5A", "NUTS_ID": "DEA5A"}, "id": "DEA5A"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.40311, 51.71936], [8.57298, 51.64776], [8.57252, 51.61635], [8.48378, 51.56659], [8.54793, 51.46781], [8.31601, 51.3926], [8.0013, 51.47007], [7.90744, 51.48314], [7.86377, 51.45885], [7.83763, 51.47853], [7.82715, 51.57863], [7.90895, 51.60705], [7.9613, 51.68096], [7.94453, 51.70058], [8.15089, 51.70851], [8.23869, 51.66169], [8.32014, 51.72571], [8.40311, 51.71936]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Soest", "LEVL_CODE": 3, "FID": "DEA5B", "NUTS_ID": "DEA5B"}, "id": "DEA5B"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.70189, 51.71238], [7.6905, 51.62459], [7.79937, 51.61739], [7.82715, 51.57863], [7.83763, 51.47853], [7.64899, 51.45781], [7.58782, 51.39039], [7.50845, 51.41794], [7.62356, 51.53514], [7.58022, 51.57858], [7.41793, 51.59952], [7.45032, 51.63008], [7.40955, 51.66458], [7.46121, 51.72727], [7.54961, 51.69373], [7.70189, 51.71238]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Unna", "LEVL_CODE": 3, "FID": "DEA5C", "NUTS_ID": "DEA5C"}, "id": "DEA5C"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.66405, 50.38154], [7.68887, 50.36013], [7.6024, 50.32038], [7.60548, 50.29379], [7.53561, 50.2954], [7.49919, 50.36647], [7.5675, 50.40251], [7.66405, 50.38154]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Koblenz, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB11", "NUTS_ID": "DEB11"}, "id": "DEB11"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.2124, 50.62341], [7.34439, 50.47661], [7.26856, 50.4047], [7.07113, 50.39758], [6.98434, 50.34889], [6.90503, 50.31305], [6.82321, 50.32893], [6.80071, 50.36178], [6.77049, 50.4728], [6.88241, 50.46473], [6.9279, 50.55862], [7.14964, 50.60827], [7.1952, 50.64272], [7.21087, 50.64954], [7.2124, 50.62341]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ahrweiler", "LEVL_CODE": 3, "FID": "DEB12", "NUTS_ID": "DEB12"}, "id": "DEB12"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.8515, 50.92583], [7.84088, 50.88589], [7.96507, 50.83744], [7.97336, 50.78337], [8.03969, 50.69738], [7.97922, 50.68045], [7.74547, 50.73505], [7.70885, 50.70952], [7.70082, 50.62292], [7.59716, 50.64759], [7.5401, 50.57792], [7.50089, 50.57481], [7.44077, 50.71144], [7.65962, 50.77917], [7.661, 50.82036], [7.75384, 50.85927], [7.7859, 50.93991], [7.8515, 50.92583]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Altenkirchen (Westerwald)", "LEVL_CODE": 3, "FID": "DEB13", "NUTS_ID": "DEB13"}, "id": "DEB13"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.96356, 49.8331], [7.90544, 49.75226], [7.76227, 49.74402], [7.70333, 49.65252], [7.67284, 49.64548], [7.65437, 49.69198], [7.52275, 49.70762], [7.42885, 49.74801], [7.39874, 49.85606], [7.50447, 49.86946], [7.73463, 49.99869], [7.89391, 49.92573], [7.96356, 49.8331]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bad Kreuznach", "LEVL_CODE": 3, "FID": "DEB14", "NUTS_ID": "DEB14"}, "id": "DEB14"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.39874, 49.85606], [7.42885, 49.74801], [7.52275, 49.70762], [7.43892, 49.65888], [7.42919, 49.60118], [7.27662, 49.54862], [7.26392, 49.57426], [7.02798, 49.63944], [7.05609, 49.67276], [7.04431, 49.6893], [7.24663, 49.84717], [7.22251, 49.88495], [7.36216, 49.89622], [7.39874, 49.85606]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Birkenfeld", "LEVL_CODE": 3, "FID": "DEB15", "NUTS_ID": "DEB15"}, "id": "DEB15"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.59835, 50.46456], [7.66123, 50.42414], [7.66405, 50.38154], [7.5675, 50.40251], [7.49919, 50.36647], [7.53561, 50.2954], [7.60548, 50.29379], [7.64144, 50.26636], [7.63573, 50.24561], [7.53961, 50.26023], [7.38195, 50.16075], [7.37186, 50.20255], [7.30589, 50.20819], [7.25879, 50.25305], [7.06878, 50.27979], [6.98901, 50.30474], [7.00442, 50.33115], [6.98434, 50.34889], [7.07113, 50.39758], [7.26856, 50.4047], [7.34439, 50.47661], [7.49365, 50.41475], [7.59835, 50.46456]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mayen-Koblenz", "LEVL_CODE": 3, "FID": "DEB17", "NUTS_ID": "DEB17"}, "id": "DEB17"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.70082, 50.62292], [7.6777, 50.59971], [7.69247, 50.5356], [7.59835, 50.46456], [7.49365, 50.41475], [7.34439, 50.47661], [7.2124, 50.62341], [7.33547, 50.64371], [7.38156, 50.71116], [7.44077, 50.71144], [7.50089, 50.57481], [7.5401, 50.57792], [7.59716, 50.64759], [7.70082, 50.62292]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neuwied", "LEVL_CODE": 3, "FID": "DEB18", "NUTS_ID": "DEB18"}, "id": "DEB18"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.12191, 50.27723], [8.03783, 50.26057], [8.02773, 50.22247], [7.91236, 50.20041], [7.89455, 50.18002], [7.91809, 50.12672], [7.774, 50.06654], [7.76484, 50.08259], [7.61687, 50.2243], [7.63573, 50.24561], [7.64144, 50.26636], [7.60548, 50.29379], [7.6024, 50.32038], [7.68887, 50.36013], [7.76693, 50.3873], [7.85388, 50.35271], [7.90955, 50.40212], [7.97156, 50.40622], [8.05454, 50.37231], [8.12191, 50.27723]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rhein-Lahn-Kreis", "LEVL_CODE": 3, "FID": "DEB1A", "NUTS_ID": "DEB1A"}, "id": "DEB1A"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.03969, 50.69738], [8.12578, 50.68581], [8.15159, 50.59937], [8.11737, 50.54301], [8.04171, 50.5499], [7.99936, 50.52279], [7.97156, 50.40622], [7.90955, 50.40212], [7.85388, 50.35271], [7.76693, 50.3873], [7.68887, 50.36013], [7.66405, 50.38154], [7.66123, 50.42414], [7.59835, 50.46456], [7.69247, 50.5356], [7.6777, 50.59971], [7.70082, 50.62292], [7.70885, 50.70952], [7.74547, 50.73505], [7.97922, 50.68045], [8.03969, 50.69738]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Westerwaldkreis", "LEVL_CODE": 3, "FID": "DEB1B", "NUTS_ID": "DEB1B"}, "id": "DEB1B"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.38195, 50.16075], [7.30665, 50.1176], [7.33432, 50.04735], [7.33647, 50.00956], [7.26064, 49.96546], [7.20268, 49.96632], [7.10303, 50.04735], [6.97902, 50.09791], [6.96234, 50.20847], [7.04232, 50.23724], [7.06878, 50.27979], [7.25879, 50.25305], [7.30589, 50.20819], [7.37186, 50.20255], [7.38195, 50.16075]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Cochem-Zell", "LEVL_CODE": 3, "FID": "DEB1C", "NUTS_ID": "DEB1C"}, "id": "DEB1C"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.63573, 50.24561], [7.61687, 50.2243], [7.76484, 50.08259], [7.68359, 50.04735], [7.73463, 49.99869], [7.50447, 49.86946], [7.39874, 49.85606], [7.36216, 49.89622], [7.22251, 49.88495], [7.24252, 49.93987], [7.20268, 49.96632], [7.26064, 49.96546], [7.33647, 50.00956], [7.33432, 50.04735], [7.30665, 50.1176], [7.38195, 50.16075], [7.53961, 50.26023], [7.63573, 50.24561]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Rhein-Hunsr\u00fcck-Kreis", "LEVL_CODE": 3, "FID": "DEB1D", "NUTS_ID": "DEB1D"}, "id": "DEB1D"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.55304, 49.72861], [6.59418, 49.75744], [6.59752, 49.785], [6.63171, 49.78586], [6.67284, 49.85235], [6.69119, 49.85845], [6.73785, 49.77674], [6.70892, 49.75094], [6.71323, 49.71696], [6.61268, 49.69954], [6.59905, 49.71525], [6.56193, 49.70096], [6.55304, 49.72861]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Trier, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB21", "NUTS_ID": "DEB21"}, "id": "DEB21"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.97902, 50.09791], [7.10303, 50.04735], [7.20268, 49.96632], [7.24252, 49.93987], [7.22251, 49.88495], [7.24663, 49.84717], [7.04431, 49.6893], [7.02392, 49.68094], [6.98683, 49.68368], [6.91458, 49.75231], [6.90582, 49.79182], [6.93031, 49.81301], [6.89076, 49.84841], [6.71195, 49.90481], [6.69238, 49.96361], [6.71506, 50.04735], [6.70065, 50.08439], [6.84956, 50.12427], [6.91925, 50.08101], [6.97902, 50.09791]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bernkastel-Wittlich", "LEVL_CODE": 3, "FID": "DEB22", "NUTS_ID": "DEB22"}, "id": "DEB22"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.4253, 50.32301], [6.58106, 50.22403], [6.56298, 50.1609], [6.58336, 50.11896], [6.70065, 50.08439], [6.71506, 50.04735], [6.69238, 49.96361], [6.71195, 49.90481], [6.47496, 49.82128], [6.32671, 49.84497], [6.25336, 49.89093], [6.12792, 50.04735], [6.13766, 50.12995], [6.19327, 50.2405], [6.3148, 50.31306], [6.40503, 50.32331], [6.4253, 50.32301]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Eifelkreis Bitburg-Pr\u00fcm", "LEVL_CODE": 3, "FID": "DEB23", "NUTS_ID": "DEB23"}, "id": "DEB23"}, {"geometry": {"type": "Polygon", "coordinates": [[[6.80071, 50.36178], [6.82321, 50.32893], [6.90503, 50.31305], [6.98434, 50.34889], [7.00442, 50.33115], [6.98901, 50.30474], [7.06878, 50.27979], [7.04232, 50.23724], [6.96234, 50.20847], [6.97902, 50.09791], [6.91925, 50.08101], [6.84956, 50.12427], [6.70065, 50.08439], [6.58336, 50.11896], [6.56298, 50.1609], [6.58106, 50.22403], [6.4253, 50.32301], [6.43125, 50.3585], [6.5822, 50.37594], [6.70217, 50.34774], [6.80071, 50.36178]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Vulkaneifel", "LEVL_CODE": 3, "FID": "DEB24", "NUTS_ID": "DEB24"}, "id": "DEB24"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.04431, 49.6893], [7.05609, 49.67276], [7.02798, 49.63944], [6.89145, 49.61342], [6.59236, 49.52804], [6.38005, 49.55111], [6.43277, 49.662], [6.50101, 49.71871], [6.51109, 49.7828], [6.47496, 49.82128], [6.71195, 49.90481], [6.89076, 49.84841], [6.93031, 49.81301], [6.90582, 49.79182], [6.91458, 49.75231], [6.98683, 49.68368], [7.02392, 49.68094], [7.04431, 49.6893]], [[6.55304, 49.72861], [6.56193, 49.70096], [6.59905, 49.71525], [6.61268, 49.69954], [6.71323, 49.71696], [6.70892, 49.75094], [6.73785, 49.77674], [6.69119, 49.85845], [6.67284, 49.85235], [6.63171, 49.78586], [6.59752, 49.785], [6.59418, 49.75744], [6.55304, 49.72861]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Trier-Saarburg", "LEVL_CODE": 3, "FID": "DEB25", "NUTS_ID": "DEB25"}, "id": "DEB25"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.42307, 49.54182], [8.30956, 49.48516], [8.33855, 49.55203], [8.4227, 49.57419], [8.42307, 49.54182]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Frankenthal (Pfalz), Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB31", "NUTS_ID": "DEB31"}, "id": "DEB31"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.64982, 49.43702], [7.67174, 49.44898], [7.68053, 49.48888], [7.70249, 49.49589], [7.73938, 49.47439], [7.75743, 49.48957], [7.79987, 49.48578], [7.81269, 49.46336], [7.84548, 49.4656], [7.8711, 49.40793], [7.81707, 49.39392], [7.80642, 49.37211], [7.76941, 49.39377], [7.69767, 49.38428], [7.64982, 49.43702]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Kaiserslautern, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB32", "NUTS_ID": "DEB32"}, "id": "DEB32"}, {"geometry": {"type": "MultiPolygon", "coordinates": [[[[8.05356, 49.16463], [8.06877, 49.1858], [8.05658, 49.22238], [8.10618, 49.23462], [8.1573, 49.22692], [8.17923, 49.16416], [8.10588, 49.17176], [8.09122, 49.15608], [8.05356, 49.16463]]], [[[7.93672, 49.30898], [7.94249, 49.26617], [7.87207, 49.28311], [7.86765, 49.31332], [7.93672, 49.30898]]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Landau in der Pfalz, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB33", "NUTS_ID": "DEB33"}, "id": "DEB33"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.47251, 49.44351], [8.39833, 49.43306], [8.30956, 49.48516], [8.42307, 49.54182], [8.47251, 49.44351]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Ludwigshafen am Rhein, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB34", "NUTS_ID": "DEB34"}, "id": "DEB34"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.28825, 49.99513], [8.34303, 49.94051], [8.23793, 49.90445], [8.23016, 49.94342], [8.15558, 49.97437], [8.19004, 50.0353], [8.24413, 50.02535], [8.28825, 49.99513]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Mainz, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB35", "NUTS_ID": "DEB35"}, "id": "DEB35"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.31443, 49.31145], [8.31741, 49.30812], [8.21794, 49.29738], [7.99956, 49.34379], [8.17015, 49.38514], [8.31443, 49.31145]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Neustadt an der Weinstra\u00dfe, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB36", "NUTS_ID": "DEB36"}, "id": "DEB36"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.51254, 49.20058], [7.57015, 49.23595], [7.66667, 49.20729], [7.63677, 49.16217], [7.58935, 49.15288], [7.51254, 49.20058]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Pirmasens, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB37", "NUTS_ID": "DEB37"}, "id": "DEB37"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.47092, 49.34071], [8.48727, 49.29003], [8.46699, 49.28298], [8.41495, 49.30343], [8.38608, 49.34359], [8.42168, 49.36854], [8.47092, 49.34071]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Speyer, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB38", "NUTS_ID": "DEB38"}, "id": "DEB38"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.41477, 49.59505], [8.28101, 49.58676], [8.2562, 49.59126], [8.2699, 49.68375], [8.44638, 49.7308], [8.36336, 49.68552], [8.41477, 49.59505]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Worms, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB39", "NUTS_ID": "DEB39"}, "id": "DEB39"}, {"geometry": {"type": "Polygon", "coordinates": [[[7.32034, 49.1895], [7.30236, 49.24138], [7.39449, 49.31635], [7.42099, 49.28096], [7.4065, 49.23436], [7.32034, 49.1895]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Zweibr\u00fccken, Kreisfreie Stadt", "LEVL_CODE": 3, "FID": "DEB3A", "NUTS_ID": "DEB3A"}, "id": "DEB3A"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.40006, 49.80368], [8.45711, 49.75617], [8.44837, 49.73366], [8.44638, 49.7308], [8.2699, 49.68375], [8.2562, 49.59126], [8.15238, 49.62688], [8.10252, 49.69024], [7.97138, 49.7081], [7.90544, 49.75226], [7.96356, 49.8331], [8.15124, 49.89911], [8.2564, 49.76069], [8.40006, 49.80368]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Alzey-Worms", "LEVL_CODE": 3, "FID": "DEB3B", "NUTS_ID": "DEB3B"}, "id": "DEB3B"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.2562, 49.59126], [8.28101, 49.58676], [8.27395, 49.4553], [8.24651, 49.42826], [8.31443, 49.31145], [8.17015, 49.38514], [7.99956, 49.34379], [7.93672, 49.30898], [7.86765, 49.31332], [7.83196, 49.32375], [7.85384, 49.36639], [7.96608, 49.41066], [7.96411, 49.48964], [8.08672, 49.55213], [8.15238, 49.62688], [8.2562, 49.59126]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Bad D\u00fcrkheim", "LEVL_CODE": 3, "FID": "DEB3C", "NUTS_ID": "DEB3C"}, "id": "DEB3C"}, {"geometry": {"type": "Polygon", "coordinates": [[[8.15238, 49.62688], [8.08672, 49.55213], [7.96411, 49.48964], [7.9348, 49.52847], [7.7889, 49.53624], [7.69627, 49.58866], [7.70333, 49.65252], [7.76227, 49.74402], [7.90544, 49.75226], [7.97138, 49.7081], [8.10252, 49.69024], [8.15238, 49.62688]]]}, "type": "Feature", "properties": {"CNTR_CODE": "DE", "NUTS_NAME": "Donnersbergkreis", "LEVL_CODE": 3, "FID": "DEB3D", "NUTS_ID": "DEB3D"}, "id": "DEB3D"}]} \ No newline at end of file diff --git a/etrago/network.py b/etrago/network.py index a0b74eeab..3790aff59 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -112,7 +112,10 @@ update_busmap, adjust_chp_model, adjust_PtH2_model, - levelize_abroad_inland_parameters + levelize_abroad_inland_parameters, + find_interest_buses, + find_links_connected_to_interest_buses, + add_extendable_solar_to_interest_area ) logger = logging.getLogger(__name__) @@ -387,6 +390,12 @@ def __init__( levelize_abroad_inland_parameters = levelize_abroad_inland_parameters + find_interest_buses = find_interest_buses + + find_links_connected_to_interest_buses = find_links_connected_to_interest_buses + + add_extendable_solar_to_interest_area = add_extendable_solar_to_interest_area + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 6e99e3d8b..b1e5d5618 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -42,6 +42,7 @@ if "READTHEDOCS" not in os.environ: from shapely.geometry import LineString, Point import geopandas as gpd + import re from etrago.tools import db @@ -3895,4 +3896,137 @@ def filter_german_components(df, component, german_buses): logger.warning(f"⚠️ {parameter} doesn't exist for Carrier '{carrier}' in {component_type}.") logger.info(f"✅ All required parameters for inland and abroad {component_type} are levelized.") - + + +def find_interest_buses(self): + """ + Identifiziere alle Busse innerhalb von Regionen, deren Name + in args["interest_area"] als Teilstring vorkommt. + + args["interest_area"] ist eine Liste von Namensfragmenten. + """ + args = self.args + + # GeoJSON einlesen + nuts = gpd.read_file(args["nuts_3_map"]) + nuts["NUTS_NAME"] = nuts["NUTS_NAME"].str.strip() + + # Matchen über str.contains für alle Einträge in args["interest_area"] + area_filter = args["interest_area"] + mask = nuts["NUTS_NAME"].apply(lambda name: any(area.lower() in name.lower() for area in area_filter)) + interest_area = nuts[mask] + + if interest_area.empty: + raise ValueError(f"Keine Region mit Teilstrings {area_filter} in GeoJSON gefunden.") + + # Busse zu GeoDataFrame + buses = gpd.GeoDataFrame( + self.network.buses.copy(), + geometry=gpd.points_from_xy(self.network.buses.x, self.network.buses.y), + crs="EPSG:4326" + ) + + # CRS-Anpassung + buses = buses.to_crs(interest_area.crs) + + # Leere Geometrien ausschließen + interest_area = interest_area[~interest_area.geometry.is_empty & interest_area.geometry.notnull()] + + # Räumlicher Schnitt + buses_in_area = buses[buses.geometry.within(interest_area.unary_union)] + + # print(f"{len(buses_in_area)} Busse in {area_filter} gefunden.") + + return buses_in_area + +def find_links_connected_to_interest_buses(self): + # find buses in interst area + gdf_buses_interest = find_interest_buses(self) + buses_of_interest = gdf_buses_interest.index.tolist() + + # Links where bus0 or bus1 is in the area of interest + links = n.links.copy() + connected_links = links[ + (links["bus0"].isin(buses_of_interest)) | + (links["bus1"].isin(buses_of_interest)) + ] + + return connected_links + + +def get_next_index(etrago, component="Generator", carrier="solar_rooftop"): + """ + Gibt den nächsten freien Index im Format '{int} {carrier}' für das angegebene + PyPSA-Komponentenobjekt (z.B. 'Generator' oder 'Link') zurück. + + Beispiel: "17 solar_rooftop" + """ + n = etrago.network.copy() + comp_df = getattr(n, component.lower() + "s") + + # Nur Komponenten mit dem angegebenen Carrier + filtered = comp_df[comp_df.carrier == carrier] + + used_ids = [] + for idx in filtered.index: + pattern = rf"(\d+)\s+{re.escape(carrier)}" + match = re.match(pattern, idx) + if match: + used_ids.append(int(match.group(1))) + + next_id = max(used_ids) + 1 if used_ids else 0 + return f"{next_id} {carrier}" + + +def add_extendable_solar_to_interest_area(self): + # buses in ingolstadt + buses_ingolstadt = find_interest_buses(self) + bus_list = buses_ingolstadt.index.to_list() + # generator solar_rooftop from interest_area + + #generators_interest = n.generators[n.generators.bus.isin(bus_list)] + + # locate existing solar_rooftop in interest area + gens = self.network.generators.copy() + interest_solar_gen = gens[ + (gens.carrier == "solar_rooftop") & + (gens.bus.isin(bus_list)) + ] + + print(interest_solar_gen) + + # Determine the attributes for new generators by copying from similar existing generators + default_attrs = ['start_up_cost', 'shut_down_cost', 'min_up_time', + 'min_down_time', 'up_time_before', 'down_time_before', + 'ramp_limit_up', 'ramp_limit_down', 'ramp_limit_start_up', + 'ramp_limit_shut_down'] + + solar_attrs = {attr: interest_solar_gen.get(attr, 0) for attr in default_attrs} + + # timeseries of existing solar_rooftop in interest area + pv_time_series = self.network.generators_t.p_max_pu.loc[:, interest_solar_gen.index] + + # deactivate existing solar_rooftop in interest area + self.network.generators.loc[interest_solar_gen.index, "p_nom"] = 0 + + # == Add the solar generator with the new ID == + + # = get new solar_rooftop gen_id = + + solar_gen_id = get_next_index(self, component="Generator", carrier="solar_rooftop") + + # take marginal_cost from existing gens + marginal_cost_value = interest_solar_gen["marginal_cost"].iloc[0] + + # Add the solar generator with the new ID + self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=100, carrier="solar_rooftop", + marginal_cost=marginal_cost_value, + capital_cost=37361.94, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) + + # take time series from exisiting solar_gen + self.network.generators_t.p_max_pu[solar_gen_id] = pv_time_series + + # set scn_name + self.network.generators.loc[solar_gen_id, "scn_name"] = "eGon2035" + + print(f"Time series for Solar generator {solar_gen_id} added successfully.") \ No newline at end of file From 3d6538464a246a213cdf3817b5489ccd3c49846a Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 15 Jun 2025 16:13:33 +0200 Subject: [PATCH 067/109] Clone fossil links and P2H-components in Ingolstadt and set p_nom_extendable = TRUE --- etrago/appl.py | 29 +++++-- etrago/network.py | 11 ++- etrago/tools/utilities.py | 162 +++++++++++++++++++++++++++++++++++++- 3 files changed, 190 insertions(+), 12 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 492f9792e..7f59962ae 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -702,6 +702,8 @@ def run_etrago(args, json_path): """ etrago = Etrago(args, json_path=json_path) + print(datetime.datetime.now()) + # import network from database etrago.build_network_from_db() @@ -725,13 +727,6 @@ def run_etrago(args, json_path): etrago.spatial_clustering() etrago.spatial_clustering_gas() - # set interest components to extendable - - etrago.add_extendable_solar_to_interest_area() - - import pdb - pdb.set_trace() - # snapshot clustering etrago.snapshot_clustering() @@ -765,6 +760,26 @@ def run_etrago(args, json_path): n.generators.ramp_limit_up = np.nan n.generators.ramp_limit_down = np.nan + # set interest components to extendable + + etrago.add_extendable_solar_to_interest_area() + + etrago.add_extendable_links_without_efficiency() + + etrago.add_extendable_heat_pumps_to_interest_area() + + #etrago.set_battery_interest_area_p_nom_min() + + # lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost","p_nom_extendable"] + lcolumns = ["p_nom", "p_nom_opt", "marginal_cost", "capital_cost", "p_nom_extendable"] + + connected_links = etrago.find_links_connected_to_interest_buses() + + print(connected_links[lcolumns]) + + #import pdb + #pdb.set_trace() + # start linear optimal powerflow calculations etrago.optimize() diff --git a/etrago/network.py b/etrago/network.py index 3790aff59..8d82f190c 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -115,7 +115,10 @@ levelize_abroad_inland_parameters, find_interest_buses, find_links_connected_to_interest_buses, - add_extendable_solar_to_interest_area + add_extendable_solar_to_interest_area, + add_extendable_links_without_efficiency, + add_extendable_heat_pumps_to_interest_area, + set_battery_interest_area_p_nom_min ) logger = logging.getLogger(__name__) @@ -396,6 +399,12 @@ def __init__( add_extendable_solar_to_interest_area = add_extendable_solar_to_interest_area + add_extendable_links_without_efficiency = add_extendable_links_without_efficiency + + add_extendable_heat_pumps_to_interest_area = add_extendable_heat_pumps_to_interest_area + + set_battery_interest_area_p_nom_min = set_battery_interest_area_p_nom_min + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index b1e5d5618..30321fdb8 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3945,7 +3945,7 @@ def find_links_connected_to_interest_buses(self): buses_of_interest = gdf_buses_interest.index.tolist() # Links where bus0 or bus1 is in the area of interest - links = n.links.copy() + links = self.network.links.copy() connected_links = links[ (links["bus0"].isin(buses_of_interest)) | (links["bus1"].isin(buses_of_interest)) @@ -4019,9 +4019,9 @@ def add_extendable_solar_to_interest_area(self): marginal_cost_value = interest_solar_gen["marginal_cost"].iloc[0] # Add the solar generator with the new ID - self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=100, carrier="solar_rooftop", + self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=100, p_nom_min = 100 , carrier="solar_rooftop", marginal_cost=marginal_cost_value, - capital_cost=37361.94, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) + capital_cost=23311.26447, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) # take time series from exisiting solar_gen self.network.generators_t.p_max_pu[solar_gen_id] = pv_time_series @@ -4029,4 +4029,158 @@ def add_extendable_solar_to_interest_area(self): # set scn_name self.network.generators.loc[solar_gen_id, "scn_name"] = "eGon2035" - print(f"Time series for Solar generator {solar_gen_id} added successfully.") \ No newline at end of file + generators_interest = self.network.generators[self.network.generators.bus.isin(bus_list)] + cgens = ["bus", "carrier", "p_nom", "p_nom_opt", "p_nom_extendable", "marginal_cost", "capital_cost"] + print(generators_interest[cgens]) + + print(f"Time series for Solar generator {solar_gen_id} added successfully.") + +def add_extendable_links_without_efficiency(self): + """ + Dupliziert gasbasierte Links ohne Zeitreihe als investierbare Variante. + Setzt capital_cost und ggf. marginal_cost aus zentralem Mapping. + """ + + carriers = [ + "central_gas_CHP", + "central_gas_CHP_heat", + "central_gas_boiler", + "industrial_gas_CHP", + "central_resistive_heater" + ] + + copy_p_nom_carriers = [ + "central_gas_CHP", + "central_gas_CHP_heat", + "industrial_gas_CHP" + ] + + capital_cost_map = { + "central_gas_CHP": 41295.88, + "central_gas_CHP_heat": 41295.88, + "central_gas_boiler": 3754.17, + "industrial_gas_CHP": 41295.88, + "central_resistive_heater": 5094.87 + } + + marginal_cost_map = { + "central_resistive_heater": 1.0582, + "central_gas_CHP_heat":4.1125 + # andere Komponenten behalten vorhandene marginal_cost + } + + connected_links = find_links_connected_to_interest_buses(self) + gas_links = connected_links[connected_links.carrier.isin(carriers)] + + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 + + default_attrs = [ + 'efficiency', + 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', + 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', + "p_nom_mod", "marginal_cost_quadratic", "stand_by_cost" + ] + + for old_index, row in gas_links.iterrows(): + self.network.links.at[old_index, "p_nom"] = 0 + + link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} + carrier = row.carrier + + link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) + link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, row.get("marginal_cost", 0)) + + # Optional: p_nom übernehmen + if carrier in copy_p_nom_carriers: + link_attrs["p_nom"] = row.p_nom + link_attrs["p_nom_min"] = row.p_nom + + new_index = str(next_link_id) + next_link_id += 1 + + self.network.add("Link", + name=new_index, + bus0=row.bus0, + bus1=row.bus1, + carrier=carrier, + p_nom_extendable=True, + **link_attrs) + + self.network.links.at[new_index, "scn_name"] = "eGon2035" + print(f"Neuer Link {new_index} ({carrier}) hinzugefügt.") + +def add_extendable_heat_pumps_to_interest_area(self): + """ + Dupliziert zentrale und ländliche Wärmepumpen in der Region. + Setzt investierbare Variante, kopiert 'efficiency'-Zeitreihe, + und übernimmt spezifische Kostenparameter. + """ + + heat_pump_carriers = ["central_heat_pump", "rural_heat_pump"] + + capital_cost_map = { + "central_heat_pump": 64289.94, + "rural_heat_pump": 74910.98 + } + + marginal_cost_map = { + "central_heat_pump": 2.4868 + # rural_heat_pump bleibt bei vorhandenem Wert + } + + connected_links = find_links_connected_to_interest_buses(self) + hp_links = connected_links[connected_links.carrier.isin(heat_pump_carriers)] + + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 + + default_attrs = [ + 'efficiency', + 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', + 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', + "p_nom_mod", "marginal_cost_quadratic", "stand_by_cost" + ] + + for old_index, row in hp_links.iterrows(): + self.network.links.at[old_index, "p_nom"] = 0 + carrier = row.carrier + + link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} + link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) + link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, row.get("marginal_cost", 0)) + + new_index = str(next_link_id) + next_link_id += 1 + + self.network.add("Link", + name=new_index, + bus0=row.bus0, + bus1=row.bus1, + carrier=carrier, + p_nom_extendable=True, + **link_attrs) + + if old_index in self.network.links_t.efficiency.columns: + self.network.links_t.efficiency[new_index] = self.network.links_t.efficiency[old_index].copy() + + self.network.links.at[new_index, "scn_name"] = "eGon2035" + print(f"Wärmepumpe {new_index} ({carrier}) erfolgreich dupliziert mit Zeitreihe.") + +def set_battery_interest_area_p_nom_min(self): + """ + Setzt p_nom_min = 0 für alle Batterien (storage_units) in der interest area. + """ + # Busse in der Region bestimmen + buses_ingolstadt = find_interest_buses(self) + bus_list = buses_ingolstadt.index.to_list() + + # Filtermaske: nur Batterien in der Region + mask = (self.network.storage_units.bus.isin(bus_list)) & (self.network.storage_units.carrier == "battery") + + # Setze p_nom_min = 0 direkt in der Original-Tabelle + self.network.storage_units.loc[mask, "p_nom_min"] = 0 + + print(f"Für {mask.sum()} Batterien in der interest area wurde p_nom_min = 0 gesetzt.") \ No newline at end of file From 9b49bb3e9f7478826ef28aac0be74c36ed90d952 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 15 Jun 2025 21:19:24 +0200 Subject: [PATCH 068/109] Add waste-bus with generator with according links: waste_CHP, waste_CHP_heat --- etrago/appl.py | 4 ++ etrago/network.py | 5 ++- etrago/tools/utilities.py | 88 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 7f59962ae..a7198ca25 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -768,6 +768,10 @@ def run_etrago(args, json_path): etrago.add_extendable_heat_pumps_to_interest_area() + # add waste_CHP in Ingolstadt + + etrago.add_waste_CHP_ingolstadt() + #etrago.set_battery_interest_area_p_nom_min() # lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost","p_nom_extendable"] diff --git a/etrago/network.py b/etrago/network.py index 8d82f190c..0fd26cab2 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -118,7 +118,8 @@ add_extendable_solar_to_interest_area, add_extendable_links_without_efficiency, add_extendable_heat_pumps_to_interest_area, - set_battery_interest_area_p_nom_min + set_battery_interest_area_p_nom_min, + add_waste_CHP_ingolstadt ) logger = logging.getLogger(__name__) @@ -405,6 +406,8 @@ def __init__( set_battery_interest_area_p_nom_min = set_battery_interest_area_p_nom_min + add_waste_CHP_ingolstadt = add_waste_CHP_ingolstadt + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 30321fdb8..182c5171f 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3927,13 +3927,16 @@ def find_interest_buses(self): ) # CRS-Anpassung - buses = buses.to_crs(interest_area.crs) + # buses = buses.to_crs(interest_area.crs) + if interest_area.crs != "EPSG:4326": + interest_area = interest_area.to_crs("EPSG:4326") # Leere Geometrien ausschließen interest_area = interest_area[~interest_area.geometry.is_empty & interest_area.geometry.notnull()] # Räumlicher Schnitt - buses_in_area = buses[buses.geometry.within(interest_area.unary_union)] + #buses_in_area = buses[buses.geometry.within(interest_area.unary_union)] + buses_in_area = buses[buses.geometry.within(interest_area.buffer(0.005).unary_union)] # print(f"{len(buses_in_area)} Busse in {area_filter} gefunden.") @@ -4021,7 +4024,7 @@ def add_extendable_solar_to_interest_area(self): # Add the solar generator with the new ID self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=100, p_nom_min = 100 , carrier="solar_rooftop", marginal_cost=marginal_cost_value, - capital_cost=23311.26447, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) + capital_cost=17483.44835, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) # take time series from exisiting solar_gen self.network.generators_t.p_max_pu[solar_gen_id] = pv_time_series @@ -4183,4 +4186,81 @@ def set_battery_interest_area_p_nom_min(self): # Setze p_nom_min = 0 direkt in der Original-Tabelle self.network.storage_units.loc[mask, "p_nom_min"] = 0 - print(f"Für {mask.sum()} Batterien in der interest area wurde p_nom_min = 0 gesetzt.") \ No newline at end of file + print(f"Für {mask.sum()} Batterien in der interest area wurde p_nom_min = 0 gesetzt.") + +def add_waste_CHP_ingolstadt(self): + """ + add waste_CHP-Powerplant, located in Ingolstadt + """ + # Add new bus with additional attributes + new_bus = str(self.network.buses.index.astype(np.int64).max() + 1) + self.network.add("Bus", + new_bus, + carrier="waste", + v_nom=1.0, + x=11.491169, + y=48.764725 + ) + self.network.buses.at[new_bus, "scn_name"] = "eGon2035" + self.network.buses.at[new_bus, "country"] = "DE" + + # Add new generator for waste_carrier with additional attributes + + self.network.add("Generator", + name=f"{new_bus} waste", + bus=new_bus, + carrier="waste", + p_nom=10000, + p_nom_extendable=False, + marginal_cost=0, + capital_cost=0, + efficiency=1.0 + ) + + self.network.generators.at[f"{new_bus} waste", "scn_name"] = "eGon2035" + + bus_ingolstadt = find_interest_buses(self) + bus_list = bus_ingolstadt.index.to_list() + print(bus_ingolstadt) + print(self.network.generators[self.network.generators.bus.isin(bus_list)]) + + # Add Link waste -> electricity + ac_buses = bus_ingolstadt[bus_ingolstadt.carrier == "AC"] + ac_bus = ac_buses.index[0] + # create new link_id + new_link_id = str(self.network.links.index.astype(int).max() + 1) + + self.network.add("Link", + name=new_link_id, + bus0=new_bus, + bus1=ac_bus, + carrier="waste_CHP", + p_nom=18.24, + p_nom_min=18.24, + marginal_cost=27.8042, + p_nom_extendable=False, + efficiency=0.2102 + ) + + # Add Link waste -> central_heat + # create new link_id + new_link_id = str(self.network.links.index.astype(int).max() + 1) + central_heat_buses = bus_ingolstadt[bus_ingolstadt.carrier == "central_heat"] + central_heat_bus = central_heat_buses.index[0] + + self.network.add("Link", + name=new_link_id, + bus0=new_bus, + bus1=central_heat_bus, + carrier="waste_CHP_heat", + p_nom=45.0, + p_nom_min=45.0, + marginal_cost=27.8042, + p_nom_extendable=False, + efficiency=0.7635 + ) + + connected_links = find_links_connected_to_interest_buses(self) + lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost", "efficiency", + "p_nom_extendable", "p_nom_max", "p_nom_min", "p_min_pu", "p_max_pu", "p_set"] + print(connected_links[lcolumns]) \ No newline at end of file From a3d9e38ed3e1609a6b92521a9d32494b532d175e Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 21 Jun 2025 19:22:58 +0200 Subject: [PATCH 069/109] Add waste_CHP-contstraint with fixed dispatchratio for electricity and heat --- etrago/appl.py | 4 ++- etrago/tools/constraints.py | 61 +++++++++++++++++++++++++++++++++++++ etrago/tools/utilities.py | 4 +-- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index a7198ca25..3162ea35e 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -112,7 +112,9 @@ }, }, "generator_noise": 789456, # apply generator noise, False or seed number - "extra_functionality": {}, # Choose function name or {} + "extra_functionality": { + "fixed_waste_chp_ratio_linopy": {} + }, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses "interest_area": ["Ingolstadt"], # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 8cd4dd9dc..aef5bdfdd 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3822,3 +3822,64 @@ def add_chp_constraints_linopy(network, snapshots): "Link", "top_iso_fuel_line_" + i + "_" + str(snapshot), ) + +def fixed_waste_chp_ratio_linopy(network, snapshots): + """ + Implements fixed coupling between electricity and heat generation + for waste-based combined heat and power (CHP) plants. + + Parameters + ---------- + network : pypsa.Network + Network container + snapshots : pandas.DatetimeIndex + Timesteps to optimize + + Returns + ------- + None. + + """ + # electric efficiency + n_el = 0.2102 + # thermal efficiency + n_th = 0.762 + + # fixed ratio between thermal and electrical output + fixed_ratio = n_th/n_el + + electric_bool = network.links.carrier == "central_waste_CHP" + heat_bool = network.links.carrier == "central_waste_CHP_heat" + + if (network.links.carrier == "central_waste_CHP_heat").any(): + electric = network.links.index[electric_bool] + heat = network.links.index[heat_bool] + + ch4_nodes_with_waste_chp = network.buses.loc[ + network.links.loc[electric, "bus0"].values + ].index.unique() + + for i in ch4_nodes_with_waste_chp: + elec_chp = network.links[ + (network.links.carrier == "central_waste_CHP") + & (network.links.bus0 == i) + ].index + + heat_chp = network.links[ + (network.links.carrier == "central_waste_CHP_heat") + & (network.links.bus0 == i) + ].index + + for snapshot in snapshots: + dispatch_heat = get_var(network, "Link", "p").loc[snapshot, heat_chp].sum() + dispatch_elec = get_var(network, "Link", "p").loc[snapshot, elec_chp].sum() + + define_constraints( + network, + (dispatch_heat - fixed_ratio * dispatch_elec), + "=", + 0, + "Link", + "fixed_ratio_waste_" + i + "_" + str(snapshot), + ) + diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 182c5171f..a0c521e2f 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4234,7 +4234,7 @@ def add_waste_CHP_ingolstadt(self): name=new_link_id, bus0=new_bus, bus1=ac_bus, - carrier="waste_CHP", + carrier="central_waste_CHP", p_nom=18.24, p_nom_min=18.24, marginal_cost=27.8042, @@ -4252,7 +4252,7 @@ def add_waste_CHP_ingolstadt(self): name=new_link_id, bus0=new_bus, bus1=central_heat_bus, - carrier="waste_CHP_heat", + carrier="central_waste_CHP_heat", p_nom=45.0, p_nom_min=45.0, marginal_cost=27.8042, From df74c15ae4798747593f1abc03b2ff1e9bed4189 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 21 Jun 2025 19:24:50 +0200 Subject: [PATCH 070/109] Adapt installed capacities for in interest area in add_extendable_links_without_efficiency --- etrago/tools/utilities.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index a0c521e2f..493542bee 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4040,10 +4040,11 @@ def add_extendable_solar_to_interest_area(self): def add_extendable_links_without_efficiency(self): """ - Dupliziert gasbasierte Links ohne Zeitreihe als investierbare Variante. - Setzt capital_cost und ggf. marginal_cost aus zentralem Mapping. + Dupliziert gasbasierte Links ohne Effizienz-Zeitreihe als investierbare Variante. + Setzt capital_cost, ggf. marginal_cost und übernimmt p_nom bei ausgewählten Technologien. """ + # Ziel-Technologien carriers = [ "central_gas_CHP", "central_gas_CHP_heat", @@ -4052,12 +4053,14 @@ def add_extendable_links_without_efficiency(self): "central_resistive_heater" ] - copy_p_nom_carriers = [ - "central_gas_CHP", - "central_gas_CHP_heat", - "industrial_gas_CHP" - ] + # Technologie-spezifische p_nom-Vorgaben (in MW) + fixed_p_nom = { + "central_gas_CHP": 22.86, + "central_gas_CHP_heat": 35.97, + "industrial_gas_CHP": 0.0 + } + # Investitionskosten (annuitisiert, in €/MW/a) capital_cost_map = { "central_gas_CHP": 41295.88, "central_gas_CHP_heat": 41295.88, @@ -4066,10 +4069,10 @@ def add_extendable_links_without_efficiency(self): "central_resistive_heater": 5094.87 } + # Variable Betriebskosten (€/MWh) marginal_cost_map = { "central_resistive_heater": 1.0582, - "central_gas_CHP_heat":4.1125 - # andere Komponenten behalten vorhandene marginal_cost + "central_gas_CHP_heat": 4.1125 } connected_links = find_links_connected_to_interest_buses(self) @@ -4087,18 +4090,20 @@ def add_extendable_links_without_efficiency(self): ] for old_index, row in gas_links.iterrows(): + # Bestehende Komponente deaktivieren self.network.links.at[old_index, "p_nom"] = 0 - link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} carrier = row.carrier + link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} + # Kosten setzen link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, row.get("marginal_cost", 0)) - # Optional: p_nom übernehmen - if carrier in copy_p_nom_carriers: - link_attrs["p_nom"] = row.p_nom - link_attrs["p_nom_min"] = row.p_nom + # p_nom setzen, falls spezifiziert + if carrier in fixed_p_nom: + link_attrs["p_nom"] = fixed_p_nom[carrier] + link_attrs["p_nom_min"] = fixed_p_nom[carrier] new_index = str(next_link_id) next_link_id += 1 @@ -4112,7 +4117,8 @@ def add_extendable_links_without_efficiency(self): **link_attrs) self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Neuer Link {new_index} ({carrier}) hinzugefügt.") + print(f"Neuer Link {new_index} ({carrier}) mit fixer p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + def add_extendable_heat_pumps_to_interest_area(self): """ From 4d206dd02eb9cef7a9fdc23c69905b1e75037954 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 21 Jun 2025 19:51:26 +0200 Subject: [PATCH 071/109] Adapt _fixed_waste_chp_ratio_linopy constraint in constraints.py --- etrago/appl.py | 2 +- etrago/tools/constraints.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 3162ea35e..b301af795 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -113,7 +113,7 @@ }, "generator_noise": 789456, # apply generator noise, False or seed number "extra_functionality": { - "fixed_waste_chp_ratio_linopy": {} + "fixed_waste_chp_ratio": {} }, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index aef5bdfdd..5f5822b8a 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3823,10 +3823,10 @@ def add_chp_constraints_linopy(network, snapshots): "top_iso_fuel_line_" + i + "_" + str(snapshot), ) -def fixed_waste_chp_ratio_linopy(network, snapshots): +def _fixed_waste_chp_ratio_linopy(self, network, snapshots): """ - Implements fixed coupling between electricity and heat generation - for waste-based combined heat and power (CHP) plants. + Enforces fixed coupling between electricity and heat generation + for waste-based combined heat and power (CHP) plants using linopy. Parameters ---------- @@ -3840,13 +3840,15 @@ def fixed_waste_chp_ratio_linopy(network, snapshots): None. """ + logger.info("✔️ fixed_waste_chp_ratio constraint activated") + # electric efficiency n_el = 0.2102 # thermal efficiency n_th = 0.762 # fixed ratio between thermal and electrical output - fixed_ratio = n_th/n_el + fixed_ratio = n_th / n_el electric_bool = network.links.carrier == "central_waste_CHP" heat_bool = network.links.carrier == "central_waste_CHP_heat" @@ -3881,5 +3883,4 @@ def fixed_waste_chp_ratio_linopy(network, snapshots): 0, "Link", "fixed_ratio_waste_" + i + "_" + str(snapshot), - ) - + ) \ No newline at end of file From 64e4d231879b48d460b7c308084f6264b9fb8501 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 21 Jun 2025 20:54:31 +0200 Subject: [PATCH 072/109] Adapt installed capacities for solar and batteries in Ingolstadt --- etrago/tools/utilities.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 493542bee..99482675f 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4022,7 +4022,7 @@ def add_extendable_solar_to_interest_area(self): marginal_cost_value = interest_solar_gen["marginal_cost"].iloc[0] # Add the solar generator with the new ID - self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=100, p_nom_min = 100 , carrier="solar_rooftop", + self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=93.83, p_nom_min = 93.83 , carrier="solar_rooftop", marginal_cost=marginal_cost_value, capital_cost=17483.44835, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) @@ -4032,9 +4032,9 @@ def add_extendable_solar_to_interest_area(self): # set scn_name self.network.generators.loc[solar_gen_id, "scn_name"] = "eGon2035" - generators_interest = self.network.generators[self.network.generators.bus.isin(bus_list)] - cgens = ["bus", "carrier", "p_nom", "p_nom_opt", "p_nom_extendable", "marginal_cost", "capital_cost"] - print(generators_interest[cgens]) + #generators_interest = self.network.generators[self.network.generators.bus.isin(bus_list)] + #cgens = ["bus", "carrier", "p_nom", "p_nom_opt", "p_nom_extendable", "marginal_cost", "capital_cost"] + #print(generators_interest[cgens]) print(f"Time series for Solar generator {solar_gen_id} added successfully.") @@ -4190,7 +4190,7 @@ def set_battery_interest_area_p_nom_min(self): mask = (self.network.storage_units.bus.isin(bus_list)) & (self.network.storage_units.carrier == "battery") # Setze p_nom_min = 0 direkt in der Original-Tabelle - self.network.storage_units.loc[mask, "p_nom_min"] = 0 + self.network.storage_units.loc[mask, "p_nom_min"] = 18.15 print(f"Für {mask.sum()} Batterien in der interest area wurde p_nom_min = 0 gesetzt.") From 5bb261bb7ff65ce44543023e70c041994ef24019 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 21 Jun 2025 22:13:18 +0200 Subject: [PATCH 073/109] Adapt marginal_cost for waste_genererator with current CO2 price 76.5 and EF 0.1647 --- etrago/tools/utilities.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 99482675f..6a718afed 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4218,7 +4218,7 @@ def add_waste_CHP_ingolstadt(self): carrier="waste", p_nom=10000, p_nom_extendable=False, - marginal_cost=0, + marginal_cost=12.6, capital_cost=0, efficiency=1.0 ) @@ -4263,7 +4263,7 @@ def add_waste_CHP_ingolstadt(self): p_nom_min=45.0, marginal_cost=27.8042, p_nom_extendable=False, - efficiency=0.7635 + efficiency=3.63 # scaled thermal efficiency to enforce fixed ratio via constraint: η_th_model = η_th / η_el ) connected_links = find_links_connected_to_interest_buses(self) From f7ef69b2eb33372bcd761102c43e6e3939b5f000 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 21 Jun 2025 22:43:14 +0200 Subject: [PATCH 074/109] Adapt inv-parameter in for solar and links in Ingolstadt --- etrago/tools/utilities.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 6a718afed..e05519a0a 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4024,7 +4024,7 @@ def add_extendable_solar_to_interest_area(self): # Add the solar generator with the new ID self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=93.83, p_nom_min = 93.83 , carrier="solar_rooftop", marginal_cost=marginal_cost_value, - capital_cost=17483.44835, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) + capital_cost=46888.32, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) # take time series from exisiting solar_gen self.network.generators_t.p_max_pu[solar_gen_id] = pv_time_series @@ -4062,11 +4062,11 @@ def add_extendable_links_without_efficiency(self): # Investitionskosten (annuitisiert, in €/MW/a) capital_cost_map = { - "central_gas_CHP": 41295.88, - "central_gas_CHP_heat": 41295.88, - "central_gas_boiler": 3754.17, - "industrial_gas_CHP": 41295.88, - "central_resistive_heater": 5094.87 + "central_gas_CHP": 60819.81, + "central_gas_CHP_heat": 60819.81, + "central_gas_boiler": 5711.883, + "industrial_gas_CHP": 60819.81, + "central_resistive_heater": 6147.776 } # Variable Betriebskosten (€/MWh) @@ -4130,8 +4130,8 @@ def add_extendable_heat_pumps_to_interest_area(self): heat_pump_carriers = ["central_heat_pump", "rural_heat_pump"] capital_cost_map = { - "central_heat_pump": 64289.94, - "rural_heat_pump": 74910.98 + "central_heat_pump": 66406.58, + "rural_heat_pump": 76956.56 } marginal_cost_map = { From 41b4390aaeb3c4951a76f1a774c5b2c1709f7ce7 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 22 Jun 2025 00:34:32 +0200 Subject: [PATCH 075/109] Add sensitivity_runner.py for sensitivity analysis --- etrago/appl.py | 5 +- etrago/network.py | 5 +- etrago/sensitivity_runner.py | 224 +++++++++++++++++++++++++++++++++++ etrago/tools/utilities.py | 22 +++- 4 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 etrago/sensitivity_runner.py diff --git a/etrago/appl.py b/etrago/appl.py index b301af795..12b9b1f2e 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -777,12 +777,15 @@ def run_etrago(args, json_path): #etrago.set_battery_interest_area_p_nom_min() # lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost","p_nom_extendable"] - lcolumns = ["p_nom", "p_nom_opt", "marginal_cost", "capital_cost", "p_nom_extendable"] + print("Links") + lcolumns = ["p_nom", "p_nom_opt", "efficiency", "marginal_cost", "capital_cost", "p_nom_extendable"] connected_links = etrago.find_links_connected_to_interest_buses() print(connected_links[lcolumns]) + etrago.network.export_to_netcdf("base_network.nc") + #import pdb #pdb.set_trace() diff --git a/etrago/network.py b/etrago/network.py index 0fd26cab2..1ac751615 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -119,7 +119,8 @@ add_extendable_links_without_efficiency, add_extendable_heat_pumps_to_interest_area, set_battery_interest_area_p_nom_min, - add_waste_CHP_ingolstadt + add_waste_CHP_ingolstadt, + update_capital_cost_of_solar_ingolstadt ) logger = logging.getLogger(__name__) @@ -408,6 +409,8 @@ def __init__( add_waste_CHP_ingolstadt = add_waste_CHP_ingolstadt + update_capital_cost_of_solar_ingolstadt = update_capital_cost_of_solar_ingolstadt + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py new file mode 100644 index 000000000..c5f7ccef9 --- /dev/null +++ b/etrago/sensitivity_runner.py @@ -0,0 +1,224 @@ + +import datetime +import os +import os.path +import pandas as pd + +__copyright__ = ( + "Flensburg University of Applied Sciences, " + "Europa-Universität Flensburg, Centre for Sustainable Energy Systems, " + "DLR-Institute for Networked Energy Systems" +) +__license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)" +__author__ = ( + "ulfmueller, lukasol, wolfbunke, mariusves, s3pp, ClaraBuettner, " + "CarlosEpia, KathiEsterl, fwitte, gnn, pieterhexen, AmeliaNadal" +) + +if "READTHEDOCS" not in os.environ: + # Sphinx does not run this code. + # Do not import internal packages directly + + from etrago import Etrago + +args = { + "nuts_3_map" : "germany-de-nuts-3-regions.geojson", + # Setup and Configuration: + "db": "egon-data-wam02", # database session # "egon-data-wam02" + "gridversion": None, # None for model_draft or Version number + "method": { # Choose method and settings for optimization + "type": "lopf", # type of optimization, 'lopf' or 'sclopf' + "n_iter": 4, # abort criterion of iterative optimization, 'n_iter' or 'threshold' + "formulation": "linopy", + "market_optimization": + { + "active": False, + "market_zones": "status_quo", # only used if type='market_grid' + "rolling_horizon": {# Define parameter of market optimization + "planning_horizon": 168, # number of snapshots in each optimization + "overlap": 120, # number of overlapping hours + }, + "redispatch": True, + } + }, + "pf_post_lopf": { + "active": False, # choose if perform a pf after lopf + "add_foreign_lopf": True, # keep results of lopf for foreign DC-links + "q_allocation": "p_nom", # allocate reactive power via 'p_nom' or 'p' + }, + "start_snapshot": 1, + "end_snapshot": 8760, + "solver": "gurobi", # glpk, cplex or gurobi + "solver_options": { + "BarConvTol": 1.0e-9, + "FeasibilityTol": 1.0e-9, + "method": 2, + "crossover": 1, + "logFile": "solver_etrago.log", + "threads": 4, + "NumericFocus": 0, + "BarHomogeneous": 1, + }, + "model_formulation": "kirchhoff", # angles or kirchhoff + "scn_name": "eGon2035", # scenario: eGon2035, eGon100RE or status2019 + # Scenario variations: + "scn_extension": None, # None or array of extension scenarios + # Export options: + "lpfile": False, # save pyomo's lp file: False or /path/to/lpfile.lp + "csv_export": "results", # save results as csv: False or /path/tofolder + # Settings: + "extendable": { + "extendable_components": [ + "as_in_db" + ], # Array of components to optimize + "upper_bounds_grid": { # Set upper bounds for grid expansion + # lines in Germany + "grid_max_D": None, # relative to existing capacity + "grid_max_abs_D": { # absolute capacity per voltage level + "380": {"i": 1020, "wires": 4, "circuits": 4}, + "220": {"i": 1020, "wires": 4, "circuits": 4}, + "110": {"i": 1020, "wires": 4, "circuits": 2}, + "dc": 0, + }, + # border crossing lines + "grid_max_foreign": 4, # relative to existing capacity + "grid_max_abs_foreign": None, # absolute capacity per voltage level + }, + }, + "generator_noise": 789456, # apply generator noise, False or seed number + "extra_functionality": { + "fixed_waste_chp_ratio": {} + }, # Choose function name or {} + # Spatial Complexity: + "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses + "interest_area": ["Ingolstadt"], # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. + "network_clustering_ehv": { + "active": False, # choose if clustering of HV buses to EHV buses is activated + "busmap": False, # False or path to stored busmap + }, + "network_clustering": { + "active": True, # choose if clustering is activated + "method": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra + "n_clusters_AC": 30, # total number of resulting AC nodes (DE+foreign-interest_area) + "cluster_foreign_AC": False, # take foreign AC buses into account, True or False + "n_cluster_interest_area": 1, # False or number of buses. + "method_gas": "kmedoids-dijkstra", # choose clustering method: kmeans or kmedoids-dijkstra + "n_clusters_gas": 15, # total number of resulting CH4 nodes (DE+foreign) + "n_clusters_h2": 15, # total number of resulting H2 nodes (DE+foreign) + "cluster_foreign_gas": False, # take foreign CH4 buses into account, True or False + "k_elec_busmap": False, # False or path/to/busmap.csv + "k_gas_busmap": False, # False or path/to/ch4_busmap.csv + "bus_weight_tocsv": None, # None or path/to/bus_weight.csv + "bus_weight_fromcsv": None, # None or path/to/bus_weight.csv + "gas_weight_tocsv": None, # None or path/to/gas_bus_weight.csv + "gas_weight_fromcsv": None, # None or path/to/gas_bus_weight.csv + "line_length_factor": 1, # Factor to multiply distance between new buses for new line lengths + "remove_stubs": False, # remove stubs bevore kmeans clustering + "use_reduced_coordinates": False, # If True, do not average cluster coordinates + "random_state": 42, # random state for replicability of clustering results + "n_init": 10, # affects clustering algorithm, only change when neccesary + "max_iter": 100, # affects clustering algorithm, only change when neccesary + "tol": 1e-6, # affects clustering algorithm, only change when neccesary + "CPU_cores": 7, # number of cores used during clustering, "max" for all cores available. + }, + "sector_coupled_clustering": { + "active": True, # choose if clustering is activated + "carrier_data": { # select carriers affected by sector coupling + "central_heat": { + "base": ["CH4", "AC"], + "strategy": "simultaneous", # select strategy to cluster other sectors + }, + "rural_heat": { + "base": ["CH4", "AC"], + "strategy": "simultaneous", # select strategy to cluster other sectors + }, + "H2": { + "base": ["CH4"], + "strategy": "consecutive", # select strategy to cluster other sectors + }, + "H2_saltcavern": { + "base": ["H2_grid"], + "strategy": "consecutive", # select strategy to cluster other sectors + }, + "Li_ion": { + "base": ["AC"], + "strategy": "consecutive", # select strategy to cluster other sectors + }, + }, + }, + "spatial_disaggregation": None, # None or 'uniform' + # Temporal Complexity: + "snapshot_clustering": { + "active": False, # choose if clustering is activated + "method": "segmentation", # 'typical_periods' or 'segmentation' + "extreme_periods": None, # consideration of extreme timesteps; e.g. 'append' + "how": "daily", # type of period - only relevant for 'typical_periods' + "storage_constraints": "soc_constraints", # additional constraints for storages - only relevant for 'typical_periods' + "n_clusters": 5, # number of periods - only relevant for 'typical_periods' + "n_segments": 5, # number of segments - only relevant for segmentation + }, + "skip_snapshots": 3, # False or number of snapshots to skip + "temporal_disaggregation": { + "active": False, # choose if temporally full complex dispatch optimization should be conducted + "no_slices": 8, # number of subproblems optimization is divided into + }, + # Simplifications: + "branch_capacity_factor": {"HV": 0.5, "eHV": 0.7}, # p.u. branch derating + "load_shedding": True, # meet the demand at value of loss load cost + "foreign_lines": { + "carrier": "AC", # 'DC' for modeling foreign lines as links + "capacity": "osmTGmod", # 'osmTGmod', 'tyndp2020', 'ntc_acer' or 'thermal_acer' + }, + "comments": None, +} + +import pypsa +from etrago.network import Etrago, find_interest_buses # Pfad ggf. anpassen +import os + +class SensitivityEtrago(Etrago): + def __init__(self, nc_path="base_network.nc"): + # Initialisiere NICHT den vollen eTraGo-Workflow, sondern lade nur das gespeicherte Netzwerk + self.network = pypsa.Network(nc_path) + self.args = {"csv_export": "results"} # Default-Pfad für Ergebnisexport + + def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): + """ + Setzt den capital_cost für alle solar_rooftop Generatoren in der Interest Area Ingolstadt. + """ + buses_ingolstadt = find_interest_buses(self) + bus_list = buses_ingolstadt.index.to_list() + + gens = self.network.generators + is_solar_in_ingolstadt = (gens.carrier == "solar_rooftop") & (gens.bus.isin(bus_list)) + solar_generators = gens[is_solar_in_ingolstadt] + + if solar_generators.empty: + print("⚠️ Keine passenden Solar-Generatoren in Ingolstadt gefunden.") + return + + self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost + print(f"✅ capital_cost auf {new_capital_cost:.2f} €/kW für {len(solar_generators)} Solar-Generator(en) gesetzt.") + +if __name__ == "__main__": + # Liste der capital_cost-Werte, die getestet werden sollen + cost_values = [7310.61612, 10965.92417, 14621.23223, 18276.54029, 21931.84835, 25587.15641, 29242.46447, 32897.77252] + + for cost in cost_values: + print(f" Starte Sensitivitätslauf mit capital_cost = {cost} €/kW") + + # Neue eTraGo-Instanz mit geladenem Basismodell + etrago = SensitivityEtrago() + + # Parameteränderung + etrago.update_capital_cost_of_solar_ingolstadt(new_capital_cost=cost) + + # Ergebnisordner festlegen + export_dir = f"results/sensitivity_solar_cost_{cost:.5f}".replace(".", "_") + os.makedirs(export_dir, exist_ok=True) + etrago.args["csv_export"] = export_dir + + # Optimierung und Export + etrago.optimize() + + print(f"✅ Ergebnisse gespeichert unter: {export_dir}") \ No newline at end of file diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index e05519a0a..5ed3cac35 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4268,5 +4268,23 @@ def add_waste_CHP_ingolstadt(self): connected_links = find_links_connected_to_interest_buses(self) lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost", "efficiency", - "p_nom_extendable", "p_nom_max", "p_nom_min", "p_min_pu", "p_max_pu", "p_set"] - print(connected_links[lcolumns]) \ No newline at end of file + "p_nom_extendable", "p_nom_max", "p_nom_min"] + print(connected_links[lcolumns]) + +def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): + """ + Setzt den capital_cost für alle solar_rooftop Generatoren in der Interest Area Ingolstadt. + """ + buses_ingolstadt = find_interest_buses(self) + bus_list = buses_ingolstadt.index.to_list() + + gens = self.network.generators + is_solar_in_ingolstadt = (gens.carrier == "solar_rooftop") & (gens.bus.isin(bus_list)) + solar_generators = gens[is_solar_in_ingolstadt] + + if solar_generators.empty: + print("⚠️ Keine passenden Solar-Generatoren in Ingolstadt gefunden.") + return + + self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost + print(f"✅ capital_cost auf {new_capital_cost:.2f} €/kW für {len(solar_generators)} Solar-Generator(en) gesetzt.") \ No newline at end of file From 7d8999cb74c837e80d84f958715394d4e53f53b4 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 22 Jun 2025 21:27:31 +0200 Subject: [PATCH 076/109] Adapt capacity_costs for components for base_scenario --- etrago/appl.py | 12 ++++++++++++ etrago/sensitivity_runner.py | 19 +++++++++---------- etrago/tools/utilities.py | 4 ++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 12b9b1f2e..48ae0d615 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -784,6 +784,18 @@ def run_etrago(args, json_path): print(connected_links[lcolumns]) + # change capital_cost of Electrolyser + etrago.network.links.loc[etrago.network.links.carrier == "power_to_H2", "capital_cost"] *= 149785.8174 + + # change capital_cost of SMR + etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] *= 60079.97445 + + # change capital_cost of Fuel Cell + etrago.network.links.loc[etrago.network.links.carrier == "H2_to_power", "capital_cost"] *= 194704.5198 + + # change capital_cost of Methanisation + etrago.network.links.loc[etrago.network.links.carrier == "H2_to_CH4", "capital_cost"] *= 70533.05294 + etrago.network.export_to_netcdf("base_network.nc") #import pdb diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index c5f7ccef9..417d729af 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -15,12 +15,6 @@ "CarlosEpia, KathiEsterl, fwitte, gnn, pieterhexen, AmeliaNadal" ) -if "READTHEDOCS" not in os.environ: - # Sphinx does not run this code. - # Do not import internal packages directly - - from etrago import Etrago - args = { "nuts_3_map" : "germany-de-nuts-3-regions.geojson", # Setup and Configuration: @@ -177,10 +171,15 @@ import os class SensitivityEtrago(Etrago): - def __init__(self, nc_path="base_network.nc"): + def __init__(self, nc_path="base_network.nc", args=None): # Initialisiere NICHT den vollen eTraGo-Workflow, sondern lade nur das gespeicherte Netzwerk self.network = pypsa.Network(nc_path) - self.args = {"csv_export": "results"} # Default-Pfad für Ergebnisexport + self.args = args # Default-Pfad für Ergebnisexport + + # 🔧 Wichtige Initialisierungen für Kompatibilität + self.busmap = {} + self.ch4_h2_mapping = {} + self.tool_version = "manual_sensitivity" def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): """ @@ -202,13 +201,13 @@ def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): if __name__ == "__main__": # Liste der capital_cost-Werte, die getestet werden sollen - cost_values = [7310.61612, 10965.92417, 14621.23223, 18276.54029, 21931.84835, 25587.15641, 29242.46447, 32897.77252] + cost_values = [14621.23223, 18276.54029, 21931.84835] for cost in cost_values: print(f" Starte Sensitivitätslauf mit capital_cost = {cost} €/kW") # Neue eTraGo-Instanz mit geladenem Basismodell - etrago = SensitivityEtrago() + etrago = SensitivityEtrago(args=args) # Parameteränderung etrago.update_capital_cost_of_solar_ingolstadt(new_capital_cost=cost) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 5ed3cac35..6dd593122 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4130,8 +4130,8 @@ def add_extendable_heat_pumps_to_interest_area(self): heat_pump_carriers = ["central_heat_pump", "rural_heat_pump"] capital_cost_map = { - "central_heat_pump": 66406.58, - "rural_heat_pump": 76956.56 + "central_heat_pump": 66406.5832, + "rural_heat_pump": 101474.6834 } marginal_cost_map = { From 6c3bd5d3ee89088d8a7fd816fe2dea7eb0bd3369 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 22 Jun 2025 21:59:45 +0200 Subject: [PATCH 077/109] Add CH4-sensitivity method --- etrago/sensitivity_runner.py | 46 +++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 417d729af..c1128d083 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -197,27 +197,45 @@ def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): return self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost - print(f"✅ capital_cost auf {new_capital_cost:.2f} €/kW für {len(solar_generators)} Solar-Generator(en) gesetzt.") + print(f"✅ capital_cost auf {new_capital_cost:.2f} €/MW/a für {len(solar_generators)} Solar-Generator(en) gesetzt.") -if __name__ == "__main__": - # Liste der capital_cost-Werte, die getestet werden sollen - cost_values = [14621.23223, 18276.54029, 21931.84835] - - for cost in cost_values: - print(f" Starte Sensitivitätslauf mit capital_cost = {cost} €/kW") + def update_marginal_cost_of_CH4_generators(self, new_marginal_cost): + gens = self.network.generators + is_ch4 = gens.carrier == "CH4_NG" + if is_ch4.sum() == 0: + print("⚠️ Keine CH4_NG-Generatoren gefunden.") + return - # Neue eTraGo-Instanz mit geladenem Basismodell - etrago = SensitivityEtrago(args=args) + self.network.generators.loc[is_ch4, "marginal_cost"] = new_marginal_cost + print(f"✅ marginal_cost auf {new_marginal_cost:.2f} €/MWh gesetzt ({is_ch4.sum()} CH₄-Generatoren).") - # Parameteränderung - etrago.update_capital_cost_of_solar_ingolstadt(new_capital_cost=cost) +# === Sensitivitäten === - # Ergebnisordner festlegen +def run_solar_cost_sensitivity(): + cost_values = [14621.23223, 18276.54029, 21931.84835] + for cost in cost_values: + print(f" Starte Solar-Sensitivität mit capital_cost = {cost:.2f} €/MW/a") + etrago = SensitivityEtrago(args=args) + etrago.update_capital_cost_of_solar_ingolstadt(cost) export_dir = f"results/sensitivity_solar_cost_{cost:.5f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir + etrago.optimize() + print(f"✅ Ergebnisse gespeichert unter: {export_dir}") - # Optimierung und Export +def run_ch4_cost_sensitivity(): + ch4_prices = [20, 60, 80, 100] # Beispielpreise in €/MWh + for price in ch4_prices: + print(f" Starte CH₄-Sensitivität mit marginal_cost = {price:.2f} €/MWh_th") + etrago = SensitivityEtrago(args=args) + etrago.update_marginal_cost_of_CH4_generators(price) + export_dir = f"results/sensitivity_CH4_price_{price:.2f}".replace(".", "_") + os.makedirs(export_dir, exist_ok=True) + etrago.args["csv_export"] = export_dir etrago.optimize() + print(f"✅ Ergebnisse gespeichert unter: {export_dir}") - print(f"✅ Ergebnisse gespeichert unter: {export_dir}") \ No newline at end of file + +if __name__ == "__main__": + #run_solar_cost_sensitivity() + run_ch4_cost_sensitivity() From cd73182ce9c0ec2c16f8d93ad5b9c93461cfd3c2 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 23 Jun 2025 17:34:39 +0200 Subject: [PATCH 078/109] Adapt location of added waste_CHP --- etrago/sensitivity_runner.py | 18 +++++++++++++----- etrago/tools/utilities.py | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index c1128d083..502c792bc 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -167,7 +167,7 @@ } import pypsa -from etrago.network import Etrago, find_interest_buses # Pfad ggf. anpassen +from etrago.network import Etrago, find_interest_buses import os class SensitivityEtrago(Etrago): @@ -212,30 +212,38 @@ def update_marginal_cost_of_CH4_generators(self, new_marginal_cost): # === Sensitivitäten === def run_solar_cost_sensitivity(): - cost_values = [14621.23223, 18276.54029, 21931.84835] + cost_values = [15352.2938, 16083.35546, 16814.41707, 17545.47868] # 210,220,230,240 for cost in cost_values: print(f" Starte Solar-Sensitivität mit capital_cost = {cost:.2f} €/MW/a") + etrago = SensitivityEtrago(args=args) + etrago.update_capital_cost_of_solar_ingolstadt(cost) + export_dir = f"results/sensitivity_solar_cost_{cost:.5f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir + etrago.optimize() print(f"✅ Ergebnisse gespeichert unter: {export_dir}") def run_ch4_cost_sensitivity(): - ch4_prices = [20, 60, 80, 100] # Beispielpreise in €/MWh + ch4_prices = [20, 30, 60, 80, 100] # in €/MWh for price in ch4_prices: print(f" Starte CH₄-Sensitivität mit marginal_cost = {price:.2f} €/MWh_th") + etrago = SensitivityEtrago(args=args) + etrago.update_marginal_cost_of_CH4_generators(price) + export_dir = f"results/sensitivity_CH4_price_{price:.2f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir + etrago.optimize() print(f"✅ Ergebnisse gespeichert unter: {export_dir}") if __name__ == "__main__": - #run_solar_cost_sensitivity() - run_ch4_cost_sensitivity() + run_solar_cost_sensitivity() + #run_ch4_cost_sensitivity() diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 6dd593122..d4308b5dc 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4204,8 +4204,8 @@ def add_waste_CHP_ingolstadt(self): new_bus, carrier="waste", v_nom=1.0, - x=11.491169, - y=48.764725 + x=11.477725514411073, + y=48.76452002953957 ) self.network.buses.at[new_bus, "scn_name"] = "eGon2035" self.network.buses.at[new_bus, "country"] = "DE" From 32808762e0f6b1962d55b1f47bffd6ef73387f2b Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 24 Jun 2025 17:04:12 +0200 Subject: [PATCH 079/109] Adapt capital_cost for H2_techs with pypas-DB --- etrago/appl.py | 30 ++++++++++++++++++++++-------- etrago/sensitivity_runner.py | 4 ++-- etrago/tools/utilities.py | 12 +++++++----- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 48ae0d615..d6235f992 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -777,24 +777,38 @@ def run_etrago(args, json_path): #etrago.set_battery_interest_area_p_nom_min() # lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost","p_nom_extendable"] - print("Links") - lcolumns = ["p_nom", "p_nom_opt", "efficiency", "marginal_cost", "capital_cost", "p_nom_extendable"] + #print("Links") + #lcolumns = ["p_nom", "p_nom_opt", "efficiency", "marginal_cost", "capital_cost", "p_nom_extendable"] - connected_links = etrago.find_links_connected_to_interest_buses() + #connected_links = etrago.find_links_connected_to_interest_buses() - print(connected_links[lcolumns]) + #print(connected_links[lcolumns]) + + h2_tech_links = ["power_to_H2", "CH4_to_H2", "H2_to_power", "H2_to_CH4"] + + df_h2_links = etrago.network.links[etrago.network.links.carrier.isin(h2_tech_links)] + + df_unique = df_h2_links.drop_duplicates(subset='carrier') + + print(df_unique["capital_cost"]) # change capital_cost of Electrolyser - etrago.network.links.loc[etrago.network.links.carrier == "power_to_H2", "capital_cost"] *= 149785.8174 + etrago.network.links.loc[etrago.network.links.carrier == "power_to_H2", "capital_cost"] = 95785.81735 # change capital_cost of SMR - etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] *= 60079.97445 + etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] = 33969.92445 # change capital_cost of Fuel Cell - etrago.network.links.loc[etrago.network.links.carrier == "H2_to_power", "capital_cost"] *= 194704.5198 + etrago.network.links.loc[etrago.network.links.carrier == "H2_to_power", "capital_cost"] = 140470.6598 # change capital_cost of Methanisation - etrago.network.links.loc[etrago.network.links.carrier == "H2_to_CH4", "capital_cost"] *= 70533.05294 + etrago.network.links.loc[etrago.network.links.carrier == "H2_to_CH4", "capital_cost"] = 52478.65202 + + df_h2_links = etrago.network.links[etrago.network.links.carrier.isin(h2_tech_links)] + + df_unique = df_h2_links.drop_duplicates(subset='carrier') + + print(df_unique["capital_cost"]) etrago.network.export_to_netcdf("base_network.nc") diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 502c792bc..ac2b79597 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -245,5 +245,5 @@ def run_ch4_cost_sensitivity(): if __name__ == "__main__": - run_solar_cost_sensitivity() - #run_ch4_cost_sensitivity() + #run_solar_cost_sensitivity() + run_ch4_cost_sensitivity() diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index d4308b5dc..ce185bf4d 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3935,10 +3935,11 @@ def find_interest_buses(self): interest_area = interest_area[~interest_area.geometry.is_empty & interest_area.geometry.notnull()] # Räumlicher Schnitt - #buses_in_area = buses[buses.geometry.within(interest_area.unary_union)] - buses_in_area = buses[buses.geometry.within(interest_area.buffer(0.005).unary_union)] + buses_in_area = buses[buses.geometry.within(interest_area.unary_union)] + # buses_in_area = buses[buses.geometry.within(interest_area.buffer(0.005).unary_union)] - # print(f"{len(buses_in_area)} Busse in {area_filter} gefunden.") + print(f"{len(buses_in_area)} Busse in {area_filter} gefunden.") + print(buses_in_area.carrier) return buses_in_area @@ -4267,8 +4268,9 @@ def add_waste_CHP_ingolstadt(self): ) connected_links = find_links_connected_to_interest_buses(self) - lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost", "efficiency", - "p_nom_extendable", "p_nom_max", "p_nom_min"] + lcolumns = ["bus0", "bus1", "carrier"] + #lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost", "efficiency", + # "p_nom_extendable", "p_nom_max", "p_nom_min"] print(connected_links[lcolumns]) def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): From e285b50ab4d964f1ccb18f4c276ce56cb775f78a Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Thu, 26 Jun 2025 19:53:33 +0200 Subject: [PATCH 080/109] Add extendable options for interest area --- etrago/appl.py | 22 +- etrago/network.py | 15 +- etrago/tools/utilities.py | 416 +++++++++++++++++++++++++++++++++----- 3 files changed, 394 insertions(+), 59 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index d6235f992..f8fd59847 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -762,11 +762,17 @@ def run_etrago(args, json_path): n.generators.ramp_limit_up = np.nan n.generators.ramp_limit_down = np.nan - # set interest components to extendable + # set interest components to extendable and add additional components etrago.add_extendable_solar_to_interest_area() - etrago.add_extendable_links_without_efficiency() + etrago.reset_gas_CHP_capacities() + + etrago.add_gas_CHP_extendable() + + etrago.add_biogas_CHP_extendable() + + etrago.add_biomass_CHP_extendable() etrago.add_extendable_heat_pumps_to_interest_area() @@ -774,15 +780,13 @@ def run_etrago(args, json_path): etrago.add_waste_CHP_ingolstadt() - #etrago.set_battery_interest_area_p_nom_min() - - # lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost","p_nom_extendable"] - #print("Links") - #lcolumns = ["p_nom", "p_nom_opt", "efficiency", "marginal_cost", "capital_cost", "p_nom_extendable"] + etrago.set_battery_interest_area_p_nom_min() - #connected_links = etrago.find_links_connected_to_interest_buses() + links_ing = etrago.find_links_connected_to_interest_buses() + lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_extendable", "capital_cost", "marginal_cost"] + print(links_ing[lcolumns]) - #print(connected_links[lcolumns]) + # == change capital_cost for sector-coupling H2 - techs == h2_tech_links = ["power_to_H2", "CH4_to_H2", "H2_to_power", "H2_to_CH4"] diff --git a/etrago/network.py b/etrago/network.py index 1ac751615..06f5673b3 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -116,10 +116,13 @@ find_interest_buses, find_links_connected_to_interest_buses, add_extendable_solar_to_interest_area, - add_extendable_links_without_efficiency, add_extendable_heat_pumps_to_interest_area, set_battery_interest_area_p_nom_min, add_waste_CHP_ingolstadt, + reset_gas_CHP_capacities, + add_gas_CHP_extendable, + add_biogas_CHP_extendable, + add_biomass_CHP_extendable, update_capital_cost_of_solar_ingolstadt ) @@ -401,8 +404,6 @@ def __init__( add_extendable_solar_to_interest_area = add_extendable_solar_to_interest_area - add_extendable_links_without_efficiency = add_extendable_links_without_efficiency - add_extendable_heat_pumps_to_interest_area = add_extendable_heat_pumps_to_interest_area set_battery_interest_area_p_nom_min = set_battery_interest_area_p_nom_min @@ -411,6 +412,14 @@ def __init__( update_capital_cost_of_solar_ingolstadt = update_capital_cost_of_solar_ingolstadt + reset_gas_CHP_capacities = reset_gas_CHP_capacities + + add_gas_CHP_extendable = add_gas_CHP_extendable + + add_biogas_CHP_extendable = add_biogas_CHP_extendable + + add_biomass_CHP_extendable = add_biomass_CHP_extendable + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index ce185bf4d..9f363a3d2 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4032,94 +4032,316 @@ def add_extendable_solar_to_interest_area(self): # set scn_name self.network.generators.loc[solar_gen_id, "scn_name"] = "eGon2035" - - #generators_interest = self.network.generators[self.network.generators.bus.isin(bus_list)] - #cgens = ["bus", "carrier", "p_nom", "p_nom_opt", "p_nom_extendable", "marginal_cost", "capital_cost"] - #print(generators_interest[cgens]) - print(f"Time series for Solar generator {solar_gen_id} added successfully.") -def add_extendable_links_without_efficiency(self): + +def reset_gas_CHP_capacities(self): """ - Dupliziert gasbasierte Links ohne Effizienz-Zeitreihe als investierbare Variante. - Setzt capital_cost, ggf. marginal_cost und übernimmt p_nom bei ausgewählten Technologien. + reset_gas_CHP_capacities in interest area """ - # Ziel-Technologien carriers = [ "central_gas_CHP", "central_gas_CHP_heat", "central_gas_boiler", "industrial_gas_CHP", + # "central_resistive_heater" + ] + + connected_links = find_links_connected_to_interest_buses(self) + gas_links = connected_links[connected_links.carrier.isin(carriers)] + + for index in gas_links.index: + self.network.links.at[index, "p_nom"] = 0 + + +def add_gas_CHP_extendable(self): + """ + Dupliziert gasbasierte Links und central_resistive_heater ohne Effizienz-Zeitreihe als investierbare Variante. + Setzt capital_cost, ggf. marginal_cost und setzt p_nom auf aktuelle Daten bei ausgewählten Technologien. + """ + # carriers of gas links + carriers = [ + "central_gas_CHP", + "central_gas_CHP_heat", + "central_gas_boiler", "central_resistive_heater" ] - # Technologie-spezifische p_nom-Vorgaben (in MW) - fixed_p_nom = { - "central_gas_CHP": 22.86, - "central_gas_CHP_heat": 35.97, - "industrial_gas_CHP": 0.0 + # Technologie-spezifische p_nom-Vorgaben [MW] + installed_p_nom = { + "central_gas_CHP": 22.86 } - # Investitionskosten (annuitisiert, in €/MW/a) + # capital_cost (annualised investment costs [€/MW/a]) capital_cost_map = { - "central_gas_CHP": 60819.81, - "central_gas_CHP_heat": 60819.81, - "central_gas_boiler": 5711.883, - "industrial_gas_CHP": 60819.81, - "central_resistive_heater": 6147.776 + "central_gas_CHP": 41295.8840, + "central_gas_CHP_heat": 0, + "central_gas_boiler": 3754.1726, + "central_resistive_heater": 5094.8667 } - # Variable Betriebskosten (€/MWh) + # VOM (€/MWh) marginal_cost_map = { "central_resistive_heater": 1.0582, - "central_gas_CHP_heat": 4.1125 + "central_gas_CHP_heat": 0, + "central_gas_boiler": 1.0582 + } + default_attrs = [ + 'efficiency', 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", + "marginal_cost_quadratic", "stand_by_cost" + ] + + # filter for interest gas_links connected_links = find_links_connected_to_interest_buses(self) gas_links = connected_links[connected_links.carrier.isin(carriers)] next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 - default_attrs = [ - 'efficiency', - 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', - 'up_time_before', 'down_time_before', - 'ramp_limit_up', 'ramp_limit_down', - 'ramp_limit_start_up', 'ramp_limit_shut_down', - "p_nom_mod", "marginal_cost_quadratic", "stand_by_cost" - ] - - for old_index, row in gas_links.iterrows(): - # Bestehende Komponente deaktivieren - self.network.links.at[old_index, "p_nom"] = 0 + for exist_index, row in gas_links.iterrows(): carrier = row.carrier link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} - # Kosten setzen + # set capital_cost [€/MW/a] and marginal_cost [€/MWh] link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, row.get("marginal_cost", 0)) # p_nom setzen, falls spezifiziert - if carrier in fixed_p_nom: - link_attrs["p_nom"] = fixed_p_nom[carrier] - link_attrs["p_nom_min"] = fixed_p_nom[carrier] + if carrier in installed_p_nom: + link_attrs["p_nom"] = installed_p_nom[carrier] + link_attrs["p_nom_min"] = installed_p_nom[carrier] new_index = str(next_link_id) next_link_id += 1 self.network.add("Link", - name=new_index, - bus0=row.bus0, - bus1=row.bus1, - carrier=carrier, - p_nom_extendable=True, - **link_attrs) + name=new_index, + bus0=row.bus0, + bus1=row.bus1, + carrier=carrier, + p_nom_extendable=True, + **link_attrs) self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Neuer Link {new_index} ({carrier}) mit fixer p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + + +def get_matching_biogs_bus(self): + """ + get Bus_id of Bus only with CH4_biogas-generator + """ + + bus_groups = self.network.generators.groupby("bus") + + def is_exact_match(group): + return ( + len(group) == 2 and # genau 2 Generatoren + set(group.carrier) == {"CH4_biogas", "load shedding"} # bus only with biogas-generator + ) + + # Schritt 3: Filtere Büsse mit genau diesen Kriterien + matching_buses = [bus for bus, group in bus_groups if is_exact_match(group)] + matching_bus = str(matching_buses[0]) + + return matching_bus + +def add_biogas_CHP_extendable(self): + """ + add extendable biogas_CHP-Powerplant, located in Ingolstadt + """ + + carriers = [ + "central_biogas_CHP", + "central_biogas_CHP_heat", + "rural_biogas_CHP", + "rural_biogas_CHP_heat" + ] + + capital_cost_map = { + "central_biogas_CHP": 66960.9053, + "rural_biogas_CHP": 66960.9053 + } + + marginal_cost_map = { + "central_biogas_CHP": 7.18, + "rural_biogas_CHP": 7.18 + } + + efficiency_map = { + "central_biogas_CHP": 0.45, + "central_biogas_CHP_heat": 0.45, + "rural_biogas_CHP": 0.45, + "rural_biogas_CHP_heat": 0.45 + } + + default_attrs = [ + 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", + "marginal_cost_quadratic", "stand_by_cost" + ] + + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 + + # get default attributes from exitsting CHP-Link + existing_central_gas_CHP = self.network.links[self.network.links.carrier == "central_gas_CHP"].iloc[0] + link_attrs = {attr: existing_central_gas_CHP.get(attr, 0) for attr in default_attrs} + + # get AC-Bus and central_heat-Bus in Ingolstadt + buses_ing = self.find_interest_buses() + AC_bus_ing = buses_ing[buses_ing.carrier == "AC"].index[0] + cH_bus_ing = buses_ing[buses_ing.carrier == "central_heat"].index[0] + dH_bus_ing = buses_ing[buses_ing.carrier == "rural_heat"].index[0] + + biogas_bus_id = get_matching_biogs_bus(self) + + for carrier in carriers: + + link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) + link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, 0) + link_attrs["efficiency"] = efficiency_map.get(carrier, 0) + + new_index = str(next_link_id) + next_link_id += 1 + + if carrier in ("central_biogas_CHP", "rural_biogas_CHP"): + bus1 = AC_bus_ing + elif carrier == "central_biogas_CHP_heat": + bus1 = cH_bus_ing + elif carrier == "rural_biogas_CHP_heat": + bus1 = dH_bus_ing + + self.network.add("Link", + name=new_index, + bus0=biogas_bus_id, + bus1=bus1, + carrier=carrier, + p_nom=0, + p_nom_extendable=True, + **link_attrs) + + self.network.links.at[new_index, "scn_name"] = "eGon2035" + print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + + +def add_biomass_CHP_extendable(self): + """ + add extendable biomass_solid_CHP-Powerplant, located in Ingolstadt + """ + + carriers = [ + "central_biomass_solid_CHP", + "central_biomass_solid_CHP_heat", + "rural_biomass_solid_CHP", + "rural_biomass_solid_CHP_heat" + ] + + capital_cost_map = { + "central_biomass_solid_CHP": 232005.3902, + "rural_biomass_solid_CHP": 448355.1320 + } + + marginal_cost_map = { + "central_biomass_solid_CHP": 4.67, + "rural_biomass_solid_CHP": 9.86 + } + + efficiency_map = { + "central_biomass_solid_CHP": 0.29, + "central_biomass_solid_CHP_heat": 0.83, + "rural_biomass_solid_CHP": 0.14, + "rural_biomass_solid_CHP_heat": 0.9747 + } + + default_attrs = [ + 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", + "marginal_cost_quadratic", "stand_by_cost" + ] + + # Add new bus with additional attributes + new_bus = str(self.network.buses.index.astype(np.int64).max() + 1) + + self.network.add("Bus", + new_bus, + carrier="biomass_solid", + v_nom=1.0, + x=11.342843544333306, + y=48.76756488279032 + ) + self.network.buses.at[new_bus, "scn_name"] = "eGon2035" + self.network.buses.at[new_bus, "country"] = "DE" + + # Add new generator for biomass_carrier with additional attributes + + self.network.add("Generator", + name=f"{new_bus} biomass_solid", + bus=new_bus, + carrier="waste", + p_nom=10000, + p_nom_extendable=False, + marginal_cost=41.7655, + capital_cost=0, + efficiency=1.0 + ) + + self.network.generators.at[f"{new_bus} waste", "scn_name"] = "eGon2035" + + print(f"New Bus {new_bus} with carrier biomass_solid added successfully.") + + # Add Links biomass_solid + + # create new link_id + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 + + # get AC-Bus and central_heat-Bus in Ingolstadt + buses_ing = self.find_interest_buses() + AC_bus_ing = buses_ing[buses_ing.carrier == "AC"].index[0] + cH_bus_ing = buses_ing[buses_ing.carrier == "central_heat"].index[0] + dH_bus_ing = buses_ing[buses_ing.carrier == "rural_heat"].index[0] + + # get AC-Bus_id in Ingolstadt + ac_buses = buses_ing[buses_ing.carrier == "AC"] + ac_bus_ing = ac_buses.index[0] + + # get default attributes from exitsting CHP-Link + existing_central_gas_CHP = self.network.links[self.network.links.carrier == "central_gas_CHP"].iloc[0] + link_attrs = {attr: existing_central_gas_CHP.get(attr, 0) for attr in default_attrs} + + for carrier in carriers: + + link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) + link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, 0) + link_attrs["efficiency"] = efficiency_map.get(carrier, 0) + + new_index = str(next_link_id) + next_link_id += 1 + + if carrier in ("central_biomass_solid_CHP", "rural_biomass_solid_CHP"): + bus1 = AC_bus_ing + elif carrier == "central_biomass_solid_CHP_heat": + bus1 = cH_bus_ing + elif carrier == "rural_biomass_solid_CHP_heat": + bus1 = dH_bus_ing + + self.network.add("Link", + name=new_index, + bus0=new_bus, + bus1=bus1, + carrier=carrier, + p_nom=0, + p_nom_extendable=True, + **link_attrs) + + self.network.links.at[new_index, "scn_name"] = "eGon2035" + print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") def add_extendable_heat_pumps_to_interest_area(self): """ @@ -4190,10 +4412,12 @@ def set_battery_interest_area_p_nom_min(self): # Filtermaske: nur Batterien in der Region mask = (self.network.storage_units.bus.isin(bus_list)) & (self.network.storage_units.carrier == "battery") + p_nom_min = 18.15 + # Setze p_nom_min = 0 direkt in der Original-Tabelle - self.network.storage_units.loc[mask, "p_nom_min"] = 18.15 + self.network.storage_units.loc[mask, "p_nom_min"] = p_nom_min - print(f"Für {mask.sum()} Batterien in der interest area wurde p_nom_min = 0 gesetzt.") + print(f"Für {mask.sum()} Batterien in der interest area wurde p_nom_min = {p_nom_min} gesetzt.") def add_waste_CHP_ingolstadt(self): """ @@ -4273,6 +4497,104 @@ def add_waste_CHP_ingolstadt(self): # "p_nom_extendable", "p_nom_max", "p_nom_min"] print(connected_links[lcolumns]) + +def get_matching_biogs_bus(self): + """ + get Bus_id of Bus only with CH4_biogas-generator + """ + + bus_groups = self.network.generators.groupby("bus") + + def is_exact_match(group): + return ( + len(group) == 2 and # genau 2 Generatoren + set(group.carrier) == {"CH4_biogas", "load shedding"} # bus only with biogas-generator + ) + + # Schritt 3: Filtere Büsse mit genau diesen Kriterien + matching_buses = [bus for bus, group in bus_groups if is_exact_match(group)] + matching_bus = str(matching_buses[0]) + + return matching_bus + +def add_biogas_CHP_extendable(self): + """ + add extendable biogas_CHP-Powerplant, located in Ingolstadt + """ + + carriers = [ + "central_biogas_CHP", + "central_biogas_CHP_heat", + "rural_biogas_CHP", + "rural_biogas_CHP_heat" + ] + + capital_cost_map = { + "central_biogas_CHP": 66960.9053, + "rural_biogas_CHP": 66960.9053 + } + + marginal_cost_map = { + "central_biogas_CHP": 7.18, + "rural_biogas_CHP": 7.18 + } + + efficiency_map = { + "central_biogas_CHP": 0.45, + "central_biogas_CHP_heat": 0.45, + "rural_biogas_CHP": 0.45, + "rural_biogas_CHP_heat": 0.45 + } + + default_attrs = [ + 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", + "marginal_cost_quadratic", "stand_by_cost" + ] + + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 + + # get default attributes from exitsting CHP-Link + existing_central_gas_CHP = self.network.links[self.network.links.carrier == "central_gas_CHP"].iloc[0] + link_attrs = {attr: existing_central_gas_CHP.get(attr, 0) for attr in default_attrs} + + # get AC-Bus and central_heat-Bus in Ingolstadt + buses_ing = self.find_interest_buses() + AC_bus_ing = buses_ing[buses_ing.carrier == "AC"].index[0] + cH_bus_ing = buses_ing[buses_ing.carrier == "central_heat"].index[0] + dH_bus_ing = buses_ing[buses_ing.carrier == "rural_heat"].index[0] + + biogas_bus_id = get_matching_biogs_bus(self) + + for carrier in carriers: + + link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) + link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, 0) + link_attrs["efficiency"] = efficiency_map.get(carrier, 0) + + new_index = str(next_link_id) + next_link_id += 1 + + if carrier in ("central_biogas_CHP", "rural_biogas_CHP"): + bus1 = AC_bus_ing + elif carrier == "central_biogas_CHP_heat": + bus1 = cH_bus_ing + elif carrier == "rural_biogas_CHP_heat": + bus1 = dH_bus_ing + + self.network.add("Link", + name=new_index, + bus0=biogas_bus_id, + bus1=bus1, + carrier=carrier, + p_nom=0, + p_nom_extendable=True, + **link_attrs) + + self.network.links.at[new_index, "scn_name"] = "eGon2035" + print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): """ Setzt den capital_cost für alle solar_rooftop Generatoren in der Interest Area Ingolstadt. From e3dd4150ff9b5c69001a2fe3798585ecfe8cea3f Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Thu, 26 Jun 2025 19:55:10 +0200 Subject: [PATCH 081/109] Adapt add_chp_constraints_linopy() for other CHP-techs --- etrago/appl.py | 4 +--- etrago/tools/constraints.py | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index f8fd59847..12de16a07 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -112,9 +112,7 @@ }, }, "generator_noise": 789456, # apply generator noise, False or seed number - "extra_functionality": { - "fixed_waste_chp_ratio": {} - }, # Choose function name or {} + "extra_functionality": {}, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses "interest_area": ["Ingolstadt"], # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 5f5822b8a..0cf8c83c6 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3763,13 +3763,28 @@ def add_chp_constraints_linopy(network, snapshots): # marginal loss for each additional generation of heat c_v = 0.15 - electric_bool = network.links.carrier == "central_gas_CHP" - heat_bool = network.links.carrier == "central_gas_CHP_heat" - if (network.links.carrier == "central_gas_CHP_heat").any(): + chp_carrier_pairs = [ + ("central_gas_CHP", "central_gas_CHP_heat"), + ("central_biogas_CHP", "central_biogas_CHP_heat"), + ("rural_biogas_CHP", "rural_biogas_CHP_heat"), + ("central_biomass_solid_CHP", "central_biomass_solid_CHP_heat"), + ("rural_biomass_solid_CHP", "rural_biomass_solid_CHP_heat"), + ("central_waste_CHP", "central_waste_CHP_heat"), + ] + + for carrier_elec, carrier_heat in chp_carrier_pairs: + + electric_bool = network.links.carrier == carrier_elec + heat_bool = network.links.carrier == carrier_heat + + if not heat_bool.any(): + continue + electric = network.links.index[electric_bool] heat = network.links.index[heat_bool] + # Effizienz der Wärme-Links gemäß Literaturansatz korrigieren network.links.loc[heat, "efficiency"] = ( network.links.loc[electric, "efficiency"] / c_v ).values.mean() @@ -3780,12 +3795,12 @@ def add_chp_constraints_linopy(network, snapshots): for i in ch4_nodes_with_chp: elec_chp = network.links[ - (network.links.carrier == "central_gas_CHP") + (network.links.carrier == carrier_elec) & (network.links.bus0 == i) ].index heat_chp = network.links[ - (network.links.carrier == "central_gas_CHP_heat") + (network.links.carrier == carrier_heat) & (network.links.bus0 == i) ].index @@ -3812,17 +3827,17 @@ def add_chp_constraints_linopy(network, snapshots): define_constraints( network, get_var(network, "Link", "p").loc[snapshot, heat_chp].sum() - + get_var(network, "Link", - "p").loc[snapshot, elec_chp].sum(), + + get_var(network, "Link", "p").loc[snapshot, elec_chp].sum(), "<=", network.links[ - (network.links.carrier == "central_gas_CHP") + (network.links.carrier == carrier_elec) & (network.links.bus0 == i) ].p_nom.sum(), "Link", "top_iso_fuel_line_" + i + "_" + str(snapshot), ) + def _fixed_waste_chp_ratio_linopy(self, network, snapshots): """ Enforces fixed coupling between electricity and heat generation From bdd8c4366b10cdceab0cb96caa20d54861d14d83 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Fri, 27 Jun 2025 20:05:10 +0200 Subject: [PATCH 082/109] Add add_extendable_solar_generators_to_interest_area --- etrago/appl.py | 9 ++- etrago/network.py | 4 +- etrago/sensitivity_runner.py | 4 +- etrago/tools/constraints.py | 6 +- etrago/tools/utilities.py | 142 ++++++++++++++++++++++++----------- 5 files changed, 109 insertions(+), 56 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 12de16a07..e3d9638e3 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -762,7 +762,7 @@ def run_etrago(args, json_path): # set interest components to extendable and add additional components - etrago.add_extendable_solar_to_interest_area() + etrago.add_extendable_solar_generators_to_interest_area() etrago.reset_gas_CHP_capacities() @@ -774,12 +774,15 @@ def run_etrago(args, json_path): etrago.add_extendable_heat_pumps_to_interest_area() - # add waste_CHP in Ingolstadt - etrago.add_waste_CHP_ingolstadt() etrago.set_battery_interest_area_p_nom_min() + buses_ing = etrago.find_interest_buses() + gens_ing = etrago.network.generators[etrago.network.generators.bus.isin(buses_ing.index)] + gcolumns = ["bus", "carrier", "p_nom", "marginal_cost", "capital_cost", "p_nom_extendable"] + print(gens_ing[gcolumns]) + links_ing = etrago.find_links_connected_to_interest_buses() lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_extendable", "capital_cost", "marginal_cost"] print(links_ing[lcolumns]) diff --git a/etrago/network.py b/etrago/network.py index 06f5673b3..7501564da 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -115,7 +115,7 @@ levelize_abroad_inland_parameters, find_interest_buses, find_links_connected_to_interest_buses, - add_extendable_solar_to_interest_area, + add_extendable_solar_generators_to_interest_area, add_extendable_heat_pumps_to_interest_area, set_battery_interest_area_p_nom_min, add_waste_CHP_ingolstadt, @@ -402,7 +402,7 @@ def __init__( find_links_connected_to_interest_buses = find_links_connected_to_interest_buses - add_extendable_solar_to_interest_area = add_extendable_solar_to_interest_area + add_extendable_solar_generators_to_interest_area = add_extendable_solar_generators_to_interest_area add_extendable_heat_pumps_to_interest_area = add_extendable_heat_pumps_to_interest_area diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index ac2b79597..53ebd2729 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -80,9 +80,7 @@ }, }, "generator_noise": 789456, # apply generator noise, False or seed number - "extra_functionality": { - "fixed_waste_chp_ratio": {} - }, # Choose function name or {} + "extra_functionality": {}, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses "interest_area": ["Ingolstadt"], # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 0cf8c83c6..335488be1 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3821,7 +3821,7 @@ def add_chp_constraints_linopy(network, snapshots): "<=", 0, "Link", - "backpressure_" + i + "_" + str(snapshot), + f"backpressure_{carrier_elec}_{i}_{snapshot}", ) define_constraints( @@ -3832,9 +3832,9 @@ def add_chp_constraints_linopy(network, snapshots): network.links[ (network.links.carrier == carrier_elec) & (network.links.bus0 == i) - ].p_nom.sum(), + ].p_nom.sum(), "Link", - "top_iso_fuel_line_" + i + "_" + str(snapshot), + f"top_iso_fuel_line_{carrier_elec}_{i}_{snapshot}", ) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 9f363a3d2..c4578e325 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3982,57 +3982,106 @@ def get_next_index(etrago, component="Generator", carrier="solar_rooftop"): return f"{next_id} {carrier}" -def add_extendable_solar_to_interest_area(self): - # buses in ingolstadt - buses_ingolstadt = find_interest_buses(self) - bus_list = buses_ingolstadt.index.to_list() - # generator solar_rooftop from interest_area +def add_extendable_solar_generators_to_interest_area(self): + """ + Adds two extendable solar generators to the interest area: + 1. An extendable solar_rooftop generator on the AC bus (duplicate of existing, but p_nom=0 and new extendable gen). + 2. A solar_thermal_collector generator on the rural_heat bus. + """ + + # Define default attributes to be copied from existing generators + default_attrs = [ + 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down' + ] - #generators_interest = n.generators[n.generators.bus.isin(bus_list)] + # Locate all buses in interest area + buses_interest = self.find_interest_buses() + bus_list = buses_interest.index.to_list() - # locate existing solar_rooftop in interest area + # === SOLAR ROOFTOP GENERATOR on AC Bus === + + # Find existing solar_rooftop generator in interest area gens = self.network.generators.copy() interest_solar_gen = gens[ (gens.carrier == "solar_rooftop") & (gens.bus.isin(bus_list)) - ] - - print(interest_solar_gen) + ] - # Determine the attributes for new generators by copying from similar existing generators - default_attrs = ['start_up_cost', 'shut_down_cost', 'min_up_time', - 'min_down_time', 'up_time_before', 'down_time_before', - 'ramp_limit_up', 'ramp_limit_down', 'ramp_limit_start_up', - 'ramp_limit_shut_down'] + if interest_solar_gen.empty: + raise ValueError("No existing solar_rooftop generator found in the interest area.") - solar_attrs = {attr: interest_solar_gen.get(attr, 0) for attr in default_attrs} + # Deactivate existing solar_rooftop generator by setting p_nom=0 + self.network.generators.loc[interest_solar_gen.index, "p_nom"] = 0 + print(f"Set p_nom=0 for existing solar_rooftop generators: {list(interest_solar_gen.index)}") - # timeseries of existing solar_rooftop in interest area + # Retrieve time series from existing solar generator pv_time_series = self.network.generators_t.p_max_pu.loc[:, interest_solar_gen.index] - # deactivate existing solar_rooftop in interest area - self.network.generators.loc[interest_solar_gen.index, "p_nom"] = 0 + # Get the AC bus in interest area + AC_bus = buses_interest[buses_interest.carrier == "AC"].index[0] + + # Extract default attributes from existing solar generator + solar_attrs = {attr: interest_solar_gen[attr].iloc[0] for attr in default_attrs} + + # Generate new generator ID using your helper + new_solar_index = get_next_index(self, component="Generator", carrier="solar_rooftop") + + # Add new solar_rooftop generator + self.network.add("Generator", + name=new_solar_index, + bus=AC_bus, + carrier="solar_rooftop", + p_nom=88.66, + p_nom_extendable=True, + p_nom_max=597.597, + capital_cost=37378.03906, + marginal_cost=0.01, + **solar_attrs) + + # Copy time series + self.network.generators_t.p_max_pu[new_solar_index] = pv_time_series.iloc[:, 0] + self.network.generators.at[new_solar_index, "scn_name"] = "eGon2035" + + print(f"Extendable solar_rooftop generator {new_solar_index} added successfully on AC bus {AC_bus}.") - # == Add the solar generator with the new ID == + # === SOLAR THERMAL COLLECTOR GENERATOR on rural_heat Bus === - # = get new solar_rooftop gen_id = + # Get rural_heat bus in interest area + rural_heat_bus = buses_interest[buses_interest.carrier == "rural_heat"].index[0] - solar_gen_id = get_next_index(self, component="Generator", carrier="solar_rooftop") + # For solar thermal collector we can copy attributes from the first available existing solar thermal generator + solar_thermal_gens = gens[gens.carrier == "solar_thermal_collector"] - # take marginal_cost from existing gens - marginal_cost_value = interest_solar_gen["marginal_cost"].iloc[0] + if solar_thermal_gens.empty: + raise ValueError("No existing solar_thermal_collector generator found to copy attributes from.") - # Add the solar generator with the new ID - self.network.add("Generator", solar_gen_id, bus=interest_solar_gen.bus.iloc[0], p_nom=93.83, p_nom_min = 93.83 , carrier="solar_rooftop", - marginal_cost=marginal_cost_value, - capital_cost=46888.32, p_max_pu=1, control="PV", p_nom_extendable=True, **solar_attrs) + solar_thermal_attrs = {attr: solar_thermal_gens[attr].iloc[0] for attr in default_attrs} - # take time series from exisiting solar_gen - self.network.generators_t.p_max_pu[solar_gen_id] = pv_time_series + # Retrieve time series + solar_thermal_ts = self.network.generators_t.p_max_pu.loc[:, solar_thermal_gens.index[0]] + + # Generate new generator ID using helper + new_thermal_index = get_next_index(self, component="Generator", carrier="solar_thermal_collector") + + # Add new solar_thermal_collector generator + self.network.add("Generator", + name=new_thermal_index, + bus=rural_heat_bus, + carrier="solar_thermal_collector", + p_nom=14, + p_nom_extendable=True, + p_nom_max=33, + capital_cost=48641.64065, + marginal_cost=0.01, + **solar_thermal_attrs) + + self.network.generators_t.p_max_pu[new_thermal_index] = solar_thermal_ts + self.network.generators.at[new_thermal_index, "scn_name"] = "eGon2035" + + print(f"Extendable solar_thermal_collector generator {new_thermal_index} added successfully on rural_heat bus {rural_heat_bus}.") - # set scn_name - self.network.generators.loc[solar_gen_id, "scn_name"] = "eGon2035" - print(f"Time series for Solar generator {solar_gen_id} added successfully.") def reset_gas_CHP_capacities(self): @@ -4284,7 +4333,7 @@ def add_biomass_CHP_extendable(self): self.network.add("Generator", name=f"{new_bus} biomass_solid", bus=new_bus, - carrier="waste", + carrier="biomass_solid", p_nom=10000, p_nom_extendable=False, marginal_cost=41.7655, @@ -4292,7 +4341,9 @@ def add_biomass_CHP_extendable(self): efficiency=1.0 ) - self.network.generators.at[f"{new_bus} waste", "scn_name"] = "eGon2035" + self.network.generators.at[f"{new_bus} biomass_solid", "e_nom_max"] = 11305.00 + + self.network.generators.at[f"{new_bus} biomass_solid", "scn_name"] = "eGon2035" print(f"New Bus {new_bus} with carrier biomass_solid added successfully.") @@ -4435,6 +4486,8 @@ def add_waste_CHP_ingolstadt(self): self.network.buses.at[new_bus, "scn_name"] = "eGon2035" self.network.buses.at[new_bus, "country"] = "DE" + print(f"Neuer Bus {new_bus} mit carrier = waste hinzugefügt.") + # Add new generator for waste_carrier with additional attributes self.network.add("Generator", @@ -4451,9 +4504,6 @@ def add_waste_CHP_ingolstadt(self): self.network.generators.at[f"{new_bus} waste", "scn_name"] = "eGon2035" bus_ingolstadt = find_interest_buses(self) - bus_list = bus_ingolstadt.index.to_list() - print(bus_ingolstadt) - print(self.network.generators[self.network.generators.bus.isin(bus_list)]) # Add Link waste -> electricity ac_buses = bus_ingolstadt[bus_ingolstadt.carrier == "AC"] @@ -4473,6 +4523,10 @@ def add_waste_CHP_ingolstadt(self): efficiency=0.2102 ) + self.network.links.at[new_link_id, "scn_name"] = "eGon2035" + + print(f"Neuer Link {new_link_id} mit carrier = central_waste_CHP hinzugefügt.") + # Add Link waste -> central_heat # create new link_id new_link_id = str(self.network.links.index.astype(int).max() + 1) @@ -4486,16 +4540,14 @@ def add_waste_CHP_ingolstadt(self): carrier="central_waste_CHP_heat", p_nom=45.0, p_nom_min=45.0, - marginal_cost=27.8042, + marginal_cost=0, p_nom_extendable=False, - efficiency=3.63 # scaled thermal efficiency to enforce fixed ratio via constraint: η_th_model = η_th / η_el + efficiency=0.762 ) - connected_links = find_links_connected_to_interest_buses(self) - lcolumns = ["bus0", "bus1", "carrier"] - #lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_opt", "marginal_cost", "capital_cost", "efficiency", - # "p_nom_extendable", "p_nom_max", "p_nom_min"] - print(connected_links[lcolumns]) + self.network.links.at[new_link_id, "scn_name"] = "eGon2035" + + print(f"Neuer Link {new_link_id} mit carrier = central_waste_CHP_heat hinzugefügt.") def get_matching_biogs_bus(self): From 8fabc14475bc7f8dae14f3d59c25e277ff9b90e6 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Fri, 27 Jun 2025 20:06:46 +0200 Subject: [PATCH 083/109] Set central_resistive_heater p_nom = 0 and don't add new one. --- etrago/tools/utilities.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index c4578e325..2ca20aa1b 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4094,7 +4094,7 @@ def reset_gas_CHP_capacities(self): "central_gas_CHP_heat", "central_gas_boiler", "industrial_gas_CHP", - # "central_resistive_heater" + "central_resistive_heater" ] connected_links = find_links_connected_to_interest_buses(self) @@ -4114,7 +4114,7 @@ def add_gas_CHP_extendable(self): "central_gas_CHP", "central_gas_CHP_heat", "central_gas_boiler", - "central_resistive_heater" + #"central_resistive_heater" ] # Technologie-spezifische p_nom-Vorgaben [MW] @@ -4127,12 +4127,12 @@ def add_gas_CHP_extendable(self): "central_gas_CHP": 41295.8840, "central_gas_CHP_heat": 0, "central_gas_boiler": 3754.1726, - "central_resistive_heater": 5094.8667 + #"central_resistive_heater": 5094.8667 } # VOM (€/MWh) marginal_cost_map = { - "central_resistive_heater": 1.0582, + #"central_resistive_heater": 1.0582, "central_gas_CHP_heat": 0, "central_gas_boiler": 1.0582 From 1e918666ece2afc513ac5ec216c796c891269267 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Fri, 27 Jun 2025 23:40:15 +0200 Subject: [PATCH 084/109] Set p_nom_max = 1,83 for gas_boil in interest area. --- etrago/appl.py | 3 ++- etrago/tools/utilities.py | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index e3d9638e3..c8db15d90 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -785,7 +785,8 @@ def run_etrago(args, json_path): links_ing = etrago.find_links_connected_to_interest_buses() lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_extendable", "capital_cost", "marginal_cost"] - print(links_ing[lcolumns]) + with pd.option_context("display.max_columns", None): + print(links_ing[lcolumns]) # == change capital_cost for sector-coupling H2 - techs == diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 2ca20aa1b..c112d4511 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4060,7 +4060,7 @@ def add_extendable_solar_generators_to_interest_area(self): solar_thermal_attrs = {attr: solar_thermal_gens[attr].iloc[0] for attr in default_attrs} # Retrieve time series - solar_thermal_ts = self.network.generators_t.p_max_pu.loc[:, solar_thermal_gens.index[0]] + #solar_thermal_ts = self.network.generators_t.p_max_pu.loc[:, solar_thermal_gens.index[0]] # Generate new generator ID using helper new_thermal_index = get_next_index(self, component="Generator", carrier="solar_thermal_collector") @@ -4077,7 +4077,7 @@ def add_extendable_solar_generators_to_interest_area(self): marginal_cost=0.01, **solar_thermal_attrs) - self.network.generators_t.p_max_pu[new_thermal_index] = solar_thermal_ts + self.network.generators_t.p_max_pu[new_thermal_index] = pv_time_series self.network.generators.at[new_thermal_index, "scn_name"] = "eGon2035" print(f"Extendable solar_thermal_collector generator {new_thermal_index} added successfully on rural_heat bus {rural_heat_bus}.") @@ -4122,6 +4122,11 @@ def add_gas_CHP_extendable(self): "central_gas_CHP": 22.86 } + # Technologie-spezifische p_nom_max-Vorgaben [MW] + p_nom_max_map = { + "central_gas_boiler": 1.83 + } + # capital_cost (annualised investment costs [€/MW/a]) capital_cost_map = { "central_gas_CHP": 41295.8840, @@ -4165,6 +4170,10 @@ def add_gas_CHP_extendable(self): link_attrs["p_nom"] = installed_p_nom[carrier] link_attrs["p_nom_min"] = installed_p_nom[carrier] + # p_nom_max setzen, falls spezifiziert + if carrier in p_nom_max_map: + link_attrs["p_nom_max"] = p_nom_max_map[carrier] + new_index = str(next_link_id) next_link_id += 1 From 029b2517586c1346faadd08f1791bf5f549be486 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 28 Jun 2025 03:43:14 +0200 Subject: [PATCH 085/109] set p_nom = p_nom min for installed PV tech in ingolstadt --- etrago/tools/utilities.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index c112d4511..c7ced2ffc 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4034,6 +4034,7 @@ def add_extendable_solar_generators_to_interest_area(self): bus=AC_bus, carrier="solar_rooftop", p_nom=88.66, + p_nom_min = 88.66, p_nom_extendable=True, p_nom_max=597.597, capital_cost=37378.03906, @@ -4070,9 +4071,10 @@ def add_extendable_solar_generators_to_interest_area(self): name=new_thermal_index, bus=rural_heat_bus, carrier="solar_thermal_collector", - p_nom=14, + p_nom=33, + p_nom_min=33, p_nom_extendable=True, - p_nom_max=33, + p_nom_max=66, capital_cost=48641.64065, marginal_cost=0.01, **solar_thermal_attrs) From 5c5b66bf88d9dd009ca6ab4d351067439fe0358a Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 28 Jun 2025 22:46:07 +0200 Subject: [PATCH 086/109] Add run_co2_price_sensitivity() --- etrago/sensitivity_runner.py | 82 +++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 53ebd2729..8b91049f3 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -207,6 +207,56 @@ def update_marginal_cost_of_CH4_generators(self, new_marginal_cost): self.network.generators.loc[is_ch4, "marginal_cost"] = new_marginal_cost print(f"✅ marginal_cost auf {new_marginal_cost:.2f} €/MWh gesetzt ({is_ch4.sum()} CH₄-Generatoren).") + def update_marginal_cost_due_to_CO2_price(self, CO2_new): + """ + Passt die marginal costs von CH4_NG- und waste-Generatoren an, + um den veränderten CO2-Preis zu berücksichtigen. + + Formel: + marginal_cost += (CO2_new - CO2_default) * emissions_factor + + Args: + CO2_new (float): Neuer CO2-Preis in €/tCO2. + """ + + gens = self.network.generators + + # Fester Default-Preis + CO2_default = 76.5 # €/tCO2 + + # Delta CO2 + delta_CO2 = CO2_new - CO2_default + + # Emissionsfaktoren in tCO2/MWh + emissions_factors = { + "CH4_NG": 0.201, + "waste": 0.165 + } + + # Selektiere Generatoren mit Carrier CH4_NG oder waste + mask = gens.carrier.isin(emissions_factors.keys()) + + if mask.sum() == 0: + print("⚠️ Keine relevanten Generatoren mit CO2-Emissionen gefunden.") + return + + # Iteriere über die betroffenen Carrier + for carrier, ef in emissions_factors.items(): + carrier_mask = gens.carrier == carrier + n = carrier_mask.sum() + if n == 0: + continue + + # Berechne den Zuschlag + delta_marginal = delta_CO2 * ef + + # Addiere zum bestehenden marginal_cost + self.network.generators.loc[carrier_mask, "marginal_cost"] += delta_marginal + + print( + f"✅ {n} {carrier}-Generator(en): marginal_cost um {delta_marginal:.2f} €/MWh angepasst (neuer CO2-Preis: {CO2_new} €/tCO2, alt: {CO2_default} €/tCO2).") + + # === Sensitivitäten === def run_solar_cost_sensitivity(): @@ -241,7 +291,37 @@ def run_ch4_cost_sensitivity(): etrago.optimize() print(f"✅ Ergebnisse gespeichert unter: {export_dir}") +def run_co2_price_sensitivity(): + """ + Führt eine Sensitivitätsanalyse über verschiedene CO2-Preise durch. + Für jeden Preis wird das Modell mit angepassten marginal_costs gerechnet. + Ergebnisse werden in separate Ordner exportiert. + """ + + CO2_prices = [50, 100, 130, 160, 200] # in €/tCO2 + + for price in CO2_prices: + print(f"🔄 Starte CO₂-Sensitivität mit CO2-Preis = {price:.2f} €/tCO2") + + # Neues Modell initialisieren + etrago = SensitivityEtrago(args=args) + + # CO2-bedingte Marginalkosten anpassen + etrago.update_marginal_cost_due_to_CO2_price(price) + + # Export-Verzeichnis anlegen + export_dir = f"results/sensitivity_CO2_price_{price:.2f}".replace(".", "_") + os.makedirs(export_dir, exist_ok=True) + etrago.args["csv_export"] = export_dir + + # Optimierung starten + etrago.optimize() + + print(f"✅ Ergebnisse gespeichert unter: {export_dir}") + + if __name__ == "__main__": #run_solar_cost_sensitivity() - run_ch4_cost_sensitivity() + #run_ch4_cost_sensitivity() + run_co2_price_sensitivity() From d90d4964abe631eecfaa77f2822d286422e2e9d3 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 29 Jun 2025 23:47:25 +0200 Subject: [PATCH 087/109] Add constraint for resistive_heater in interest area for back-up --- etrago/appl.py | 10 +++++-- etrago/tools/constraints.py | 59 +++++++++++++++++++++++++++++++++++++ etrago/tools/utilities.py | 12 ++++---- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index c8db15d90..5e9aedbf3 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -112,7 +112,9 @@ }, }, "generator_noise": 789456, # apply generator noise, False or seed number - "extra_functionality": {}, # Choose function name or {} + "extra_functionality": { + "add_resistive_heater_vollaststunden_constraint": {} + }, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses "interest_area": ["Ingolstadt"], # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. @@ -779,14 +781,16 @@ def run_etrago(args, json_path): etrago.set_battery_interest_area_p_nom_min() buses_ing = etrago.find_interest_buses() + gens_ing = etrago.network.generators[etrago.network.generators.bus.isin(buses_ing.index)] gcolumns = ["bus", "carrier", "p_nom", "marginal_cost", "capital_cost", "p_nom_extendable"] - print(gens_ing[gcolumns]) links_ing = etrago.find_links_connected_to_interest_buses() lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_extendable", "capital_cost", "marginal_cost"] + with pd.option_context("display.max_columns", None): print(links_ing[lcolumns]) + print(gens_ing[gcolumns]) # == change capital_cost for sector-coupling H2 - techs == @@ -816,7 +820,7 @@ def run_etrago(args, json_path): print(df_unique["capital_cost"]) - etrago.network.export_to_netcdf("base_network.nc") + etrago.network.export_to_netcdf("base_network_3a.nc") #import pdb #pdb.set_trace() diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 335488be1..2b75c9212 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3838,6 +3838,65 @@ def add_chp_constraints_linopy(network, snapshots): ) +def _add_resistive_heater_vollaststunden_constraint(network, snapshots): + """ + Limits the annual full-load hours of the regional electric boiler (resistive heater) + with bus0 = "16" to max. 500 hours. + + Parameters + ---------- + network : pypsa.Network + Network container. + snapshots : pandas.Index + Timesteps to optimize. + + Returns + ------- + None. + """ + + logger.info("✔️ add_resistive_heater_vollaststunden_constraint constraint activated") + + # Filtere den spezifischen Link am Bus 16 + heater_links = network.links[ + (network.links.carrier == "central_resistive_heater") + & (network.links.bus0 == "16") + ] + + if heater_links.empty: + print("Keine passenden Links für den Elektroboiler an Bus 16 gefunden!") + return + + # Summe der Nennleistung (p_nom) in MW + p_nom_sum = heater_links.p_nom.sum() + + # Dispatch-Summe über alle Zeitschritte in MWh + dispatch_sum = 0 + + for snapshot in snapshots: + # Zeitschrittgewichtung in Stunden + timestep_hours = network.snapshot_weightings["objective"].loc[snapshot] + + # Summiere alle Dispatch-Werte dieses Zeitpunkts (MW) + dispatch_sum += ( + get_var(network, "Link", "p").loc[snapshot, heater_links.index].sum() + * timestep_hours + ) + + # Maximal zulässige Energie = Volllaststunden * Nennleistung (MWh) + max_energy_mwh = 500 * p_nom_sum + + # Constraint definieren: Gesamterzeugung <= max. zulässige MWh + define_constraints( + network, + dispatch_sum, + "<=", + max_energy_mwh, + "Link", + f"vollaststunden_limit_bus16", + ) + + def _fixed_waste_chp_ratio_linopy(self, network, snapshots): """ Enforces fixed coupling between electricity and heat generation diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index c7ced2ffc..767ae677e 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4113,10 +4113,10 @@ def add_gas_CHP_extendable(self): """ # carriers of gas links carriers = [ - "central_gas_CHP", - "central_gas_CHP_heat", - "central_gas_boiler", - #"central_resistive_heater" + #"central_gas_CHP", + #"central_gas_CHP_heat", + #"central_gas_boiler", + "central_resistive_heater" ] # Technologie-spezifische p_nom-Vorgaben [MW] @@ -4134,12 +4134,12 @@ def add_gas_CHP_extendable(self): "central_gas_CHP": 41295.8840, "central_gas_CHP_heat": 0, "central_gas_boiler": 3754.1726, - #"central_resistive_heater": 5094.8667 + "central_resistive_heater": 5094.8667 } # VOM (€/MWh) marginal_cost_map = { - #"central_resistive_heater": 1.0582, + "central_resistive_heater": 35, "central_gas_CHP_heat": 0, "central_gas_boiler": 1.0582 From f9910c4b30cda0656563a915ee36bba1bf5924ec Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sat, 5 Jul 2025 17:59:19 +0200 Subject: [PATCH 088/109] Adapt parameters for regional EnergySystem --- etrago/appl.py | 16 ++---- etrago/network.py | 7 ++- etrago/tools/constraints.py | 2 +- etrago/tools/utilities.py | 97 ++++++++++++++++++++++++++++++------- 4 files changed, 91 insertions(+), 31 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 5e9aedbf3..766948a76 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -113,7 +113,7 @@ }, "generator_noise": 789456, # apply generator noise, False or seed number "extra_functionality": { - "add_resistive_heater_vollaststunden_constraint": {} + # "add_resistive_heater_vollaststunden_constraint": {} }, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses @@ -715,13 +715,6 @@ def run_etrago(args, json_path): #import pdb #pdb.set_trace() - # sensitivity test - # change capital_cost of Electrolyser - # etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] *= 2 - - # change capital_cost of Battery - #etrago.network.storage_units.loc[etrago.network.storage_units.carrier == "battery", "capital_cost"] *= 0.5 - # ehv network clustering etrago.ehv_clustering() @@ -778,7 +771,8 @@ def run_etrago(args, json_path): etrago.add_waste_CHP_ingolstadt() - etrago.set_battery_interest_area_p_nom_min() + #etrago.set_battery_parameter_interest_area() + etrago.set_battery_and_heat_store_parameters_interest_area() buses_ing = etrago.find_interest_buses() @@ -806,7 +800,7 @@ def run_etrago(args, json_path): etrago.network.links.loc[etrago.network.links.carrier == "power_to_H2", "capital_cost"] = 95785.81735 # change capital_cost of SMR - etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] = 33969.92445 + etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] = 32360.1616 # change capital_cost of Fuel Cell etrago.network.links.loc[etrago.network.links.carrier == "H2_to_power", "capital_cost"] = 140470.6598 @@ -820,7 +814,7 @@ def run_etrago(args, json_path): print(df_unique["capital_cost"]) - etrago.network.export_to_netcdf("base_network_3a.nc") + etrago.network.export_to_netcdf("base_network.nc") #import pdb #pdb.set_trace() diff --git a/etrago/network.py b/etrago/network.py index 7501564da..34a40a17e 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -117,7 +117,8 @@ find_links_connected_to_interest_buses, add_extendable_solar_generators_to_interest_area, add_extendable_heat_pumps_to_interest_area, - set_battery_interest_area_p_nom_min, + set_battery_parameter_interest_area, + set_battery_and_heat_store_parameters_interest_area, add_waste_CHP_ingolstadt, reset_gas_CHP_capacities, add_gas_CHP_extendable, @@ -406,7 +407,9 @@ def __init__( add_extendable_heat_pumps_to_interest_area = add_extendable_heat_pumps_to_interest_area - set_battery_interest_area_p_nom_min = set_battery_interest_area_p_nom_min + set_battery_parameter_interest_area = set_battery_parameter_interest_area + + set_battery_and_heat_store_parameters_interest_area = set_battery_and_heat_store_parameters_interest_area add_waste_CHP_ingolstadt = add_waste_CHP_ingolstadt diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 2b75c9212..47ea35e90 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3838,7 +3838,7 @@ def add_chp_constraints_linopy(network, snapshots): ) -def _add_resistive_heater_vollaststunden_constraint(network, snapshots): +def _add_resistive_heater_vollaststunden_constraint_linopy(network, snapshots): """ Limits the annual full-load hours of the regional electric boiler (resistive heater) with bus0 = "16" to max. 500 hours. diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 767ae677e..975c90d70 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4074,8 +4074,8 @@ def add_extendable_solar_generators_to_interest_area(self): p_nom=33, p_nom_min=33, p_nom_extendable=True, - p_nom_max=66, - capital_cost=48641.64065, + p_nom_max=119, + capital_cost=33844.32, marginal_cost=0.01, **solar_thermal_attrs) @@ -4113,10 +4113,10 @@ def add_gas_CHP_extendable(self): """ # carriers of gas links carriers = [ - #"central_gas_CHP", - #"central_gas_CHP_heat", - #"central_gas_boiler", - "central_resistive_heater" + "central_gas_CHP", + "central_gas_CHP_heat", + "central_gas_boiler", + #"central_resistive_heater" ] # Technologie-spezifische p_nom-Vorgaben [MW] @@ -4126,7 +4126,8 @@ def add_gas_CHP_extendable(self): # Technologie-spezifische p_nom_max-Vorgaben [MW] p_nom_max_map = { - "central_gas_boiler": 1.83 + "central_gas_boiler": 1.83, + #"central_resistive_heater":1.83 } # capital_cost (annualised investment costs [€/MW/a]) @@ -4139,7 +4140,7 @@ def add_gas_CHP_extendable(self): # VOM (€/MWh) marginal_cost_map = { - "central_resistive_heater": 35, + "central_resistive_heater": 1.0582, "central_gas_CHP_heat": 0, "central_gas_boiler": 1.0582 @@ -4224,8 +4225,8 @@ def add_biogas_CHP_extendable(self): ] capital_cost_map = { - "central_biogas_CHP": 66960.9053, - "rural_biogas_CHP": 66960.9053 + "central_biogas_CHP": 66960.6721, + "rural_biogas_CHP": 66960.6721 } marginal_cost_map = { @@ -4303,8 +4304,8 @@ def add_biomass_CHP_extendable(self): ] capital_cost_map = { - "central_biomass_solid_CHP": 232005.3902, - "rural_biomass_solid_CHP": 448355.1320 + "central_biomass_solid_CHP": 232005.3115, + "rural_biomass_solid_CHP": 448354.9634 } marginal_cost_map = { @@ -4347,7 +4348,7 @@ def add_biomass_CHP_extendable(self): carrier="biomass_solid", p_nom=10000, p_nom_extendable=False, - marginal_cost=41.7655, + marginal_cost=39.74, capital_cost=0, efficiency=1.0 ) @@ -4415,8 +4416,8 @@ def add_extendable_heat_pumps_to_interest_area(self): heat_pump_carriers = ["central_heat_pump", "rural_heat_pump"] capital_cost_map = { - "central_heat_pump": 66406.5832, - "rural_heat_pump": 101474.6834 + "central_heat_pump": 64289.9364, + "rural_heat_pump": 74910.9791 } marginal_cost_map = { @@ -4463,7 +4464,7 @@ def add_extendable_heat_pumps_to_interest_area(self): self.network.links.at[new_index, "scn_name"] = "eGon2035" print(f"Wärmepumpe {new_index} ({carrier}) erfolgreich dupliziert mit Zeitreihe.") -def set_battery_interest_area_p_nom_min(self): +def set_battery_parameter_interest_area(self): """ Setzt p_nom_min = 0 für alle Batterien (storage_units) in der interest area. """ @@ -4475,11 +4476,73 @@ def set_battery_interest_area_p_nom_min(self): mask = (self.network.storage_units.bus.isin(bus_list)) & (self.network.storage_units.carrier == "battery") p_nom_min = 18.15 + capital_cost = 18744.86095 # Setze p_nom_min = 0 direkt in der Original-Tabelle self.network.storage_units.loc[mask, "p_nom_min"] = p_nom_min - print(f"Für {mask.sum()} Batterien in der interest area wurde p_nom_min = {p_nom_min} gesetzt.") + # capital_cost setzen + self.network.storage_units.loc[mask, "capital_cost"] = capital_cost + + print( + f"Für {mask.sum()} Batterien in der interest area wurde " + f"p_nom_min = {p_nom_min} und capital_cost = {capital_cost} gesetzt." + ) + +def set_battery_and_heat_store_parameters_interest_area(self): + """ + Setzt p_nom_min und capital_cost für Batterien (storage_units) sowie + capital_cost für Wärmespeicher (stores) in der interest area. + """ + + # Busse der interest area bestimmen + buses_ingolstadt = self.find_interest_buses() + bus_list = buses_ingolstadt.index.to_list() + + # Filtermaske für Batterien + mask_battery = ( + (self.network.storage_units.bus.isin(bus_list)) & + (self.network.storage_units.carrier == "battery") + ) + + # Parameter für Batterien + p_nom_min_battery = 18.15 + capital_cost_battery = 18744.86095 + + # Setze Batterie-Parameter + self.network.storage_units.loc[mask_battery, "p_nom_min"] = p_nom_min_battery + self.network.storage_units.loc[mask_battery, "capital_cost"] = capital_cost_battery + + # Filter für Wärmespeicher in der Region + mask_stores = self.network.stores.bus.isin(bus_list) + + # Selektiere nur relevante Stores + stores_of_interest = self.network.stores.loc[mask_stores] + + # Definition capital_cost nach carrier + capital_cost_store_values = { + "rural_heat_store": 27312.6386, + "central_heat_store": 69.0976 + } + + # Iteration über carrier und setzen der capital_cost + for carrier, cap_cost in capital_cost_store_values.items(): + mask_carrier = stores_of_interest.carrier == carrier + affected = mask_carrier.sum() + self.network.stores.loc[ + mask_stores & mask_carrier, + "capital_cost" + ] = cap_cost + print( + f"Für {affected} Wärmespeicher vom Typ '{carrier}' wurde capital_cost = {cap_cost} gesetzt." + ) + + # Zusammenfassung für Batterien + print( + f"Für {mask_battery.sum()} Batterien in der interest area wurde " + f"p_nom_min = {p_nom_min_battery} und capital_cost = {capital_cost_battery} gesetzt." + ) + def add_waste_CHP_ingolstadt(self): """ From d54e58d44d587edfea18c25460a9c1740a332f87 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 7 Jul 2025 20:38:34 +0200 Subject: [PATCH 089/109] Add extendable biomass_boiler to rural_heat_bus in interest_area --- etrago/appl.py | 4 ++- etrago/network.py | 5 ++- etrago/tools/constraints.py | 65 +------------------------------------ etrago/tools/utilities.py | 41 +++++++++++++++++++++-- 4 files changed, 46 insertions(+), 69 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 766948a76..c7374d18d 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -771,6 +771,8 @@ def run_etrago(args, json_path): etrago.add_waste_CHP_ingolstadt() + etrago.add_biomass_boiler_extendable() + #etrago.set_battery_parameter_interest_area() etrago.set_battery_and_heat_store_parameters_interest_area() @@ -814,7 +816,7 @@ def run_etrago(args, json_path): print(df_unique["capital_cost"]) - etrago.network.export_to_netcdf("base_network.nc") + etrago.network.export_to_netcdf("base_network_1a.nc") #import pdb #pdb.set_trace() diff --git a/etrago/network.py b/etrago/network.py index 34a40a17e..456ec701c 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -124,7 +124,8 @@ add_gas_CHP_extendable, add_biogas_CHP_extendable, add_biomass_CHP_extendable, - update_capital_cost_of_solar_ingolstadt + update_capital_cost_of_solar_ingolstadt, + add_biomass_boiler_extendable ) logger = logging.getLogger(__name__) @@ -423,6 +424,8 @@ def __init__( add_biomass_CHP_extendable = add_biomass_CHP_extendable + add_biomass_boiler_extendable = add_biomass_boiler_extendable + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 47ea35e90..b429cedac 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3894,67 +3894,4 @@ def _add_resistive_heater_vollaststunden_constraint_linopy(network, snapshots): max_energy_mwh, "Link", f"vollaststunden_limit_bus16", - ) - - -def _fixed_waste_chp_ratio_linopy(self, network, snapshots): - """ - Enforces fixed coupling between electricity and heat generation - for waste-based combined heat and power (CHP) plants using linopy. - - Parameters - ---------- - network : pypsa.Network - Network container - snapshots : pandas.DatetimeIndex - Timesteps to optimize - - Returns - ------- - None. - - """ - logger.info("✔️ fixed_waste_chp_ratio constraint activated") - - # electric efficiency - n_el = 0.2102 - # thermal efficiency - n_th = 0.762 - - # fixed ratio between thermal and electrical output - fixed_ratio = n_th / n_el - - electric_bool = network.links.carrier == "central_waste_CHP" - heat_bool = network.links.carrier == "central_waste_CHP_heat" - - if (network.links.carrier == "central_waste_CHP_heat").any(): - electric = network.links.index[electric_bool] - heat = network.links.index[heat_bool] - - ch4_nodes_with_waste_chp = network.buses.loc[ - network.links.loc[electric, "bus0"].values - ].index.unique() - - for i in ch4_nodes_with_waste_chp: - elec_chp = network.links[ - (network.links.carrier == "central_waste_CHP") - & (network.links.bus0 == i) - ].index - - heat_chp = network.links[ - (network.links.carrier == "central_waste_CHP_heat") - & (network.links.bus0 == i) - ].index - - for snapshot in snapshots: - dispatch_heat = get_var(network, "Link", "p").loc[snapshot, heat_chp].sum() - dispatch_elec = get_var(network, "Link", "p").loc[snapshot, elec_chp].sum() - - define_constraints( - network, - (dispatch_heat - fixed_ratio * dispatch_elec), - "=", - 0, - "Link", - "fixed_ratio_waste_" + i + "_" + str(snapshot), - ) \ No newline at end of file + ) \ No newline at end of file diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 975c90d70..18ee04951 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4075,7 +4075,7 @@ def add_extendable_solar_generators_to_interest_area(self): p_nom_min=33, p_nom_extendable=True, p_nom_max=119, - capital_cost=33844.32, + capital_cost=53711.0102, marginal_cost=0.01, **solar_thermal_attrs) @@ -4116,7 +4116,7 @@ def add_gas_CHP_extendable(self): "central_gas_CHP", "central_gas_CHP_heat", "central_gas_boiler", - #"central_resistive_heater" + "central_resistive_heater" ] # Technologie-spezifische p_nom-Vorgaben [MW] @@ -4127,7 +4127,7 @@ def add_gas_CHP_extendable(self): # Technologie-spezifische p_nom_max-Vorgaben [MW] p_nom_max_map = { "central_gas_boiler": 1.83, - #"central_resistive_heater":1.83 + "central_resistive_heater":1.83 } # capital_cost (annualised investment costs [€/MW/a]) @@ -4406,6 +4406,41 @@ def add_biomass_CHP_extendable(self): self.network.links.at[new_index, "scn_name"] = "eGon2035" print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") +def add_biomass_boiler_extendable(self): + """ + Add extendable biomass_solid_boiler to rural_heat bus in the interest area. + """ + + # Carrier name for the generator + carrier = "rural_biomass_solid_boiler" + + # Technical parameters as specified + capital_cost = 59700.4849 + marginal_cost = 39.74 + efficiency = 0.9 + p_nom = 33.32 + + # Identify the rural_heat bus in the interest area + buses_ing = self.find_interest_buses() + dH_bus_ing = buses_ing[buses_ing.carrier == "rural_heat"].index[0] + + # Add generator with defined parameters + self.network.add("Generator", + name=f"{dH_bus_ing} {carrier}", + bus=dH_bus_ing, + carrier=carrier, + p_nom=p_nom, + p_nom_min=p_nom, + p_nom_extendable=True, + capital_cost=capital_cost, + marginal_cost=marginal_cost, + efficiency=efficiency) + + # Set additional attributes + self.network.generators.at[f"{dH_bus_ing} {carrier}", "scn_name"] = "eGon2035" + + print(f"Biomass boiler {carrier} successfully added at bus {dH_bus_ing}.") + def add_extendable_heat_pumps_to_interest_area(self): """ Dupliziert zentrale und ländliche Wärmepumpen in der Region. From 1974d9cdfb5dcaab15aeac10855f79199386c961 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 8 Jul 2025 18:10:12 +0200 Subject: [PATCH 090/109] Add function to adjust global capital_cost for optimized components --- etrago/appl.py | 42 ++++------------- etrago/network.py | 7 ++- etrago/tools/utilities.py | 96 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 33 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index c7374d18d..05c5b5c14 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -757,6 +757,12 @@ def run_etrago(args, json_path): # set interest components to extendable and add additional components + etrago.print_capital_costs() + + etrago.adjust_capital_costs() + + etrago.print_capital_costs() + etrago.add_extendable_solar_generators_to_interest_area() etrago.reset_gas_CHP_capacities() @@ -771,10 +777,10 @@ def run_etrago(args, json_path): etrago.add_waste_CHP_ingolstadt() - etrago.add_biomass_boiler_extendable() + #etrago.add_biomass_boiler_extendable() - #etrago.set_battery_parameter_interest_area() - etrago.set_battery_and_heat_store_parameters_interest_area() + etrago.set_battery_parameter_interest_area() + #etrago.set_battery_and_heat_store_parameters_interest_area() buses_ing = etrago.find_interest_buses() @@ -788,35 +794,7 @@ def run_etrago(args, json_path): print(links_ing[lcolumns]) print(gens_ing[gcolumns]) - # == change capital_cost for sector-coupling H2 - techs == - - h2_tech_links = ["power_to_H2", "CH4_to_H2", "H2_to_power", "H2_to_CH4"] - - df_h2_links = etrago.network.links[etrago.network.links.carrier.isin(h2_tech_links)] - - df_unique = df_h2_links.drop_duplicates(subset='carrier') - - print(df_unique["capital_cost"]) - - # change capital_cost of Electrolyser - etrago.network.links.loc[etrago.network.links.carrier == "power_to_H2", "capital_cost"] = 95785.81735 - - # change capital_cost of SMR - etrago.network.links.loc[etrago.network.links.carrier == "CH4_to_H2", "capital_cost"] = 32360.1616 - - # change capital_cost of Fuel Cell - etrago.network.links.loc[etrago.network.links.carrier == "H2_to_power", "capital_cost"] = 140470.6598 - - # change capital_cost of Methanisation - etrago.network.links.loc[etrago.network.links.carrier == "H2_to_CH4", "capital_cost"] = 52478.65202 - - df_h2_links = etrago.network.links[etrago.network.links.carrier.isin(h2_tech_links)] - - df_unique = df_h2_links.drop_duplicates(subset='carrier') - - print(df_unique["capital_cost"]) - - etrago.network.export_to_netcdf("base_network_1a.nc") + etrago.network.export_to_netcdf("base_network_1.nc") #import pdb #pdb.set_trace() diff --git a/etrago/network.py b/etrago/network.py index 456ec701c..ba8ab391f 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -125,7 +125,9 @@ add_biogas_CHP_extendable, add_biomass_CHP_extendable, update_capital_cost_of_solar_ingolstadt, - add_biomass_boiler_extendable + add_biomass_boiler_extendable, + adjust_capital_costs, + print_capital_costs ) logger = logging.getLogger(__name__) @@ -426,6 +428,9 @@ def __init__( add_biomass_boiler_extendable = add_biomass_boiler_extendable + adjust_capital_costs = adjust_capital_costs + + print_capital_costs = print_capital_costs def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 18ee04951..5d75fc11e 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4756,6 +4756,102 @@ def add_biogas_CHP_extendable(self): self.network.links.at[new_index, "scn_name"] = "eGon2035" print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + +def adjust_capital_costs(self): + """ + Update the capital_cost values of selected PyPSA components + (links, storage_units, stores) globally based on specified carrier types. + """ + + # Define new capital costs for each component and carrier + link_costs = { + "power_to_H2": 95785.8174, + "CH4_to_H2": 32360.1616, + "H2_to_power": 140470.6598, + "H2_to_CH4": 49389.3124 + } + + storage_unit_costs = { + "battery": 18744.86 + } + + store_costs = { + "rural_heat_store": 27312.6386, + "central_heat_store": 69.0976 + } + + # Update link capital costs + for carrier, cost in link_costs.items(): + mask = self.network.links.carrier == carrier + self.network.links.loc[mask, "capital_cost"] = cost + + # Update storage unit capital costs + for carrier, cost in storage_unit_costs.items(): + mask = self.network.storage_units.carrier == carrier + self.network.storage_units.loc[mask, "capital_cost"] = cost + + # Update store capital costs + for carrier, cost in store_costs.items(): + mask = self.network.stores.carrier == carrier + self.network.stores.loc[mask, "capital_cost"] = cost + + logger.info("Capital cost parameters successfully updated.") + + +def print_capital_costs(self): + """ + Print a summary of capital_cost values for selected components + (links, storage_units, stores) grouped by carrier. + + Returns + ------- + dict of pd.DataFrame + Each key is a component name, each value is a DataFrame with capital_costs per carrier. + """ + + import pandas as pd # just in case not yet imported + + cost_summary = {} + + # Links: capital_cost by carrier + if not self.network.links.empty: + df_links = ( + self.network.links + .groupby("carrier")[["capital_cost"]] + .mean() + .sort_index() + ) + cost_summary["links"] = df_links + + # Storage Units: capital_cost by carrier + if not self.network.storage_units.empty: + df_storage = ( + self.network.storage_units + .groupby("carrier")[["capital_cost"]] + .mean() + .sort_index() + ) + cost_summary["storage_units"] = df_storage + + # Stores: capital_cost by carrier + if not self.network.stores.empty: + df_stores = ( + self.network.stores + .groupby("carrier")[["capital_cost"]] + .mean() + .sort_index() + ) + cost_summary["stores"] = df_stores + + # Print summary + for comp, df in cost_summary.items(): + print(f"\n=== Capital Costs: {comp} ===") + print(df) + + +# return cost_summary + + def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): """ Setzt den capital_cost für alle solar_rooftop Generatoren in der Interest Area Ingolstadt. From b0c7409253d5d3bc03c7f616c02eacde354fca20 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 14 Jul 2025 17:25:46 +0200 Subject: [PATCH 091/109] Remove add_chp_constraints_linopy to original version --- etrago/appl.py | 5 ++- etrago/network.py | 6 +++- etrago/tools/constraints.py | 36 +++++++-------------- etrago/tools/utilities.py | 64 ++++++++++++++++++++++++++++++++++--- 4 files changed, 80 insertions(+), 31 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 05c5b5c14..8e35a688a 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -769,6 +769,8 @@ def run_etrago(args, json_path): etrago.add_gas_CHP_extendable() + etrago.add_gas_CHP_fixed() + etrago.add_biogas_CHP_extendable() etrago.add_biomass_CHP_extendable() @@ -791,10 +793,11 @@ def run_etrago(args, json_path): lcolumns = ["bus0", "bus1", "carrier", "p_nom", "p_nom_extendable", "capital_cost", "marginal_cost"] with pd.option_context("display.max_columns", None): + print(buses_ing) print(links_ing[lcolumns]) print(gens_ing[gcolumns]) - etrago.network.export_to_netcdf("base_network_1.nc") + etrago.network.export_to_netcdf("base_network_Scenario_1_test.nc") #import pdb #pdb.set_trace() diff --git a/etrago/network.py b/etrago/network.py index ba8ab391f..aeaa38923 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -127,7 +127,9 @@ update_capital_cost_of_solar_ingolstadt, add_biomass_boiler_extendable, adjust_capital_costs, - print_capital_costs + print_capital_costs, + add_gas_CHP_fixed, + add_gas_CHP_fixed ) logger = logging.getLogger(__name__) @@ -432,6 +434,8 @@ def __init__( print_capital_costs = print_capital_costs + add_gas_CHP_fixed = add_gas_CHP_fixed + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index b429cedac..8bca2859d 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3763,28 +3763,13 @@ def add_chp_constraints_linopy(network, snapshots): # marginal loss for each additional generation of heat c_v = 0.15 + electric_bool = network.links.carrier == "central_gas_CHP" + heat_bool = network.links.carrier == "central_gas_CHP_heat" - chp_carrier_pairs = [ - ("central_gas_CHP", "central_gas_CHP_heat"), - ("central_biogas_CHP", "central_biogas_CHP_heat"), - ("rural_biogas_CHP", "rural_biogas_CHP_heat"), - ("central_biomass_solid_CHP", "central_biomass_solid_CHP_heat"), - ("rural_biomass_solid_CHP", "rural_biomass_solid_CHP_heat"), - ("central_waste_CHP", "central_waste_CHP_heat"), - ] - - for carrier_elec, carrier_heat in chp_carrier_pairs: - - electric_bool = network.links.carrier == carrier_elec - heat_bool = network.links.carrier == carrier_heat - - if not heat_bool.any(): - continue - + if (network.links.carrier == "central_gas_CHP_heat").any(): electric = network.links.index[electric_bool] heat = network.links.index[heat_bool] - # Effizienz der Wärme-Links gemäß Literaturansatz korrigieren network.links.loc[heat, "efficiency"] = ( network.links.loc[electric, "efficiency"] / c_v ).values.mean() @@ -3795,12 +3780,12 @@ def add_chp_constraints_linopy(network, snapshots): for i in ch4_nodes_with_chp: elec_chp = network.links[ - (network.links.carrier == carrier_elec) + (network.links.carrier == "central_gas_CHP") & (network.links.bus0 == i) ].index heat_chp = network.links[ - (network.links.carrier == carrier_heat) + (network.links.carrier == "central_gas_CHP_heat") & (network.links.bus0 == i) ].index @@ -3821,20 +3806,21 @@ def add_chp_constraints_linopy(network, snapshots): "<=", 0, "Link", - f"backpressure_{carrier_elec}_{i}_{snapshot}", + "backpressure_" + i + "_" + str(snapshot), ) define_constraints( network, get_var(network, "Link", "p").loc[snapshot, heat_chp].sum() - + get_var(network, "Link", "p").loc[snapshot, elec_chp].sum(), + + get_var(network, "Link", + "p").loc[snapshot, elec_chp].sum(), "<=", network.links[ - (network.links.carrier == carrier_elec) + (network.links.carrier == "central_gas_CHP") & (network.links.bus0 == i) - ].p_nom.sum(), + ].p_nom.sum(), "Link", - f"top_iso_fuel_line_{carrier_elec}_{i}_{snapshot}", + "top_iso_fuel_line_" + i + "_" + str(snapshot), ) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 5d75fc11e..2f8d8ecb9 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3938,8 +3938,8 @@ def find_interest_buses(self): buses_in_area = buses[buses.geometry.within(interest_area.unary_union)] # buses_in_area = buses[buses.geometry.within(interest_area.buffer(0.005).unary_union)] - print(f"{len(buses_in_area)} Busse in {area_filter} gefunden.") - print(buses_in_area.carrier) + #print(f"{len(buses_in_area)} Busse in {area_filter} gefunden.") + #print(buses_in_area.carrier) return buses_in_area @@ -4113,8 +4113,8 @@ def add_gas_CHP_extendable(self): """ # carriers of gas links carriers = [ - "central_gas_CHP", - "central_gas_CHP_heat", + #"central_gas_CHP", + #"central_gas_CHP_heat", "central_gas_boiler", "central_resistive_heater" ] @@ -4192,6 +4192,62 @@ def add_gas_CHP_extendable(self): print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") +def add_gas_CHP_fixed(self): + """ + Adds fixed-capacity gas CHP links (electric and heat output). + Sets p_nom = p_nom_min and disables investment (p_nom_extendable=False). + """ + + # Relevant technologies and their fixed capacities + fixed_links = { + "central_gas_CHP": 22.83, # [MW electric] + "central_gas_CHP_heat": 35.76 # [MW thermal] + } + + # Capital cost map [€/MW/a] + capital_cost_map = { + "central_gas_CHP": 41295.8840, + "central_gas_CHP_heat": 0 + } + + # Link attributes to transfer from existing links + default_attrs = [ + 'efficiency', 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", + "marginal_cost", "marginal_cost_quadratic", "stand_by_cost" + ] + + # Find matching links from area of interest + connected_links = find_links_connected_to_interest_buses(self) + gas_links = connected_links[connected_links.carrier.isin(fixed_links.keys())] + + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 + + for exist_index, row in gas_links.iterrows(): + carrier = row.carrier + + # Copy default attributes from original link + link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} + link_attrs["p_nom"] = fixed_links[carrier] + link_attrs["p_nom_min"] = fixed_links[carrier] + link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) + + new_index = str(next_link_id) + next_link_id += 1 + + self.network.add("Link", + name=new_index, + bus0=row.bus0, + bus1=row.bus1, + carrier=carrier, + p_nom_extendable=False, + **link_attrs) + + self.network.links.at[new_index, "scn_name"] = "eGon2035" + print(f"Fixer Link {new_index} ({carrier}) mit p_nom = {fixed_links[carrier]} MW hinzugefügt.") + + def get_matching_biogs_bus(self): """ get Bus_id of Bus only with CH4_biogas-generator From 2878e0cf200df24d7677a0a6d297f814c547e22f Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 14 Jul 2025 19:12:50 +0200 Subject: [PATCH 092/109] Add replace_gas_links_with_extendable() --- etrago/appl.py | 8 ++-- etrago/network.py | 5 ++- etrago/tools/utilities.py | 95 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 8e35a688a..f54a9468f 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -765,11 +765,13 @@ def run_etrago(args, json_path): etrago.add_extendable_solar_generators_to_interest_area() - etrago.reset_gas_CHP_capacities() + etrago.replace_gas_links_with_extendable() - etrago.add_gas_CHP_extendable() + #etrago.reset_gas_CHP_capacities() - etrago.add_gas_CHP_fixed() + #etrago.add_gas_CHP_extendable() + + #etrago.add_gas_CHP_fixed() etrago.add_biogas_CHP_extendable() diff --git a/etrago/network.py b/etrago/network.py index aeaa38923..58c8885f3 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -129,7 +129,8 @@ adjust_capital_costs, print_capital_costs, add_gas_CHP_fixed, - add_gas_CHP_fixed + add_gas_CHP_fixed, + replace_gas_links_with_extendable ) logger = logging.getLogger(__name__) @@ -436,6 +437,8 @@ def __init__( add_gas_CHP_fixed = add_gas_CHP_fixed + replace_gas_links_with_extendable = replace_gas_links_with_extendable + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 2f8d8ecb9..f83936606 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4084,6 +4084,101 @@ def add_extendable_solar_generators_to_interest_area(self): print(f"Extendable solar_thermal_collector generator {new_thermal_index} added successfully on rural_heat bus {rural_heat_bus}.") +def replace_gas_links_with_extendable(self): + """ + Replaces selected gas-based links in the interest area with extendable variants. + + - Duplicates original gas-based links as investable links with defined costs and constraints. + - Updates marginal_cost and efficiency for relevant carriers. + - Removes the original links from the network afterward. + + 'industrial_gas_CHP' is not affected. + """ + + # Carriers to duplicate and replace + replace_carriers = [ + "central_gas_CHP", + "central_gas_CHP_heat", + "central_gas_boiler", + "central_resistive_heater" + ] + + installed_p_nom = { + "central_gas_CHP": 22.86 + } + + p_nom_max_map = { + "central_gas_boiler": 1.83, + "central_resistive_heater": 1.83 + } + + capital_cost_map = { + "central_gas_CHP": 41295.8840, + "central_gas_CHP_heat": 0, + "central_gas_boiler": 3754.1726, + "central_resistive_heater": 5094.8667 + } + + marginal_cost_map = { + "central_gas_CHP": 4.3916, + "central_resistive_heater": 1.0582, + "central_gas_CHP_heat": 0, + "central_gas_boiler": 1.0582 + } + + efficiency_map = { + "central_gas_CHP": 0.445, + "central_gas_CHP_heat": 0.4541 + } + + default_attrs = [ + 'efficiency', 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', + 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', + 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", + "marginal_cost_quadratic", "stand_by_cost" + ] + + # Step 1️⃣ Filter links in interest area + connected_links = self.find_links_connected_to_buses() + to_process = connected_links[connected_links.carrier.isin(replace_carriers)] + + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()] + [0]) + 1 + + for exist_index, row in to_process.iterrows(): + carrier = row.carrier + link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} + + # update marginal cost and efficiency explicitly + link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) + link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, row.get("marginal_cost", 0)) + link_attrs["efficiency"] = efficiency_map.get(carrier, row.get("efficiency", 0)) + + if carrier in installed_p_nom: + link_attrs["p_nom"] = installed_p_nom[carrier] + link_attrs["p_nom_min"] = installed_p_nom[carrier] + + if carrier in p_nom_max_map: + link_attrs["p_nom_max"] = p_nom_max_map[carrier] + + new_index = str(next_link_id) + next_link_id += 1 + + self.network.add("Link", + name=new_index, + bus0=row.bus0, + bus1=row.bus1, + carrier=carrier, + p_nom_extendable=True, + **link_attrs) + self.network.links.at[new_index, "scn_name"] = "eGon2035" + print(f"New extendable link {new_index} ({carrier}) added with p_nom={link_attrs.get('p_nom', 'n/a')}.") + + # Step 2️⃣ Remove original links + for index in to_process.index: + carrier = self.network.links.at[index, "carrier"] + self.network.links.drop(index=index, inplace=True) + print(f"Removed original link {index} ({carrier})") + def reset_gas_CHP_capacities(self): From 9f6c942ea39707da7c41bcd97bceb26dc199e8fa Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 14 Jul 2025 19:14:08 +0200 Subject: [PATCH 093/109] Adapt effiency of interest biogas CHP --- etrago/tools/utilities.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index f83936606..2446094ba 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4386,10 +4386,10 @@ def add_biogas_CHP_extendable(self): } efficiency_map = { - "central_biogas_CHP": 0.45, - "central_biogas_CHP_heat": 0.45, - "rural_biogas_CHP": 0.45, - "rural_biogas_CHP_heat": 0.45 + "central_biogas_CHP": 0.445, + "central_biogas_CHP_heat": 0.4541, + "rural_biogas_CHP": 0.445, + "rural_biogas_CHP_heat": 0.4541 } default_attrs = [ From 36871d02f88f1497e7d91ecdfda6f65caf845635 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Wed, 16 Jul 2025 16:36:16 +0200 Subject: [PATCH 094/109] Add new contraints for CHP - dispatch in a fixed ratio --- etrago/appl.py | 4 +- etrago/tools/constraints.py | 87 ++++++++++++++++++++++++++++++++++++- etrago/tools/utilities.py | 36 +++++++++------ 3 files changed, 110 insertions(+), 17 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index f54a9468f..7c6a94518 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -113,7 +113,7 @@ }, "generator_noise": 789456, # apply generator noise, False or seed number "extra_functionality": { - # "add_resistive_heater_vollaststunden_constraint": {} + "add_chp_ratio_constraint": {} }, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses @@ -799,7 +799,7 @@ def run_etrago(args, json_path): print(links_ing[lcolumns]) print(gens_ing[gcolumns]) - etrago.network.export_to_netcdf("base_network_Scenario_1_test.nc") + etrago.network.export_to_netcdf("base_network_Scenario_1a.nc") #import pdb #pdb.set_trace() diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 8bca2859d..95cae03e7 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3457,7 +3457,7 @@ def functionality(self, network, snapshots): logger.info( "Added extra_functionality {}".format(constraint) ) - except: + except Exception as e: logger.warning( "Constraint {} not defined for linopy formulation".format( constraint @@ -3465,6 +3465,7 @@ def functionality(self, network, snapshots): + ". New constraints can be defined in" + " etrago/tools/constraint.py." ) + logger.warning(f"Constraint {constraint} failed: {e}") else: try: eval("_" + constraint + "_nmp(self, network, snapshots)") @@ -3824,6 +3825,90 @@ def add_chp_constraints_linopy(network, snapshots): ) +def _add_chp_ratio_constraint_linopy(self, network, snapshots): + """ + Adds ratio constraints between electricity and heat dispatch for all CHP types. + The constraint enforces: p_heat_out = (eta_th / eta_el) * p_el_out + + This is applied using: p_out = p_in * efficiency + Hence: + p_heat * eta_th - ratio * p_el * eta_el == 0 + + Parameters + ---------- + self : Constraints + Constraints object (not using etrago helpers here). + network : pypsa.Network + The PyPSA network object. + snapshots : pandas.Index + Optimization snapshots. + + Returns + ------- + None + """ + import logging + logger = logging.getLogger(__name__) + logger.info("🚀 Start writing CHP ratio constraints for all supported CHP types.") + + chp_techs = { + "central_gas_CHP_1": ("central_gas_CHP_heat_1", 0.445, 0.4541), + "central_biogas_CHP": ("central_biogas_CHP_heat", 0.445, 0.4541), + "rural_biogas_CHP": ("rural_biogas_CHP_heat", 0.445, 0.4541), + "central_biomass_solid_CHP": ("central_biomass_solid_CHP_heat", 0.14, 0.83), + "rural_biomass_solid_CHP": ("rural_biomass_solid_CHP_heat", 0.21, 0.9747), + "central_waste_CHP": ("central_waste_CHP_heat", 0.2102, 0.762), + } + + for carrier_el, (carrier_heat, eta_el, eta_th) in chp_techs.items(): + ratio = eta_th / eta_el + logger.info(f"🔧 Processing CHP pair: {carrier_el} ↔ {carrier_heat} with ratio {ratio:.3f}") + + el_links = network.links[network.links.carrier == carrier_el] + heat_links = network.links[network.links.carrier == carrier_heat] + + if el_links.empty or heat_links.empty: + logger.warning(f"⚠️ No links found for: {carrier_el} or {carrier_heat}") + continue + + for bus in el_links.bus0.unique(): + logger.info(f"📍 Matching links at bus: {bus}") + el_ids = el_links[el_links.bus0 == bus].index + heat_ids = heat_links[heat_links.bus0 == bus].index + + if el_ids.empty or heat_ids.empty: + logger.warning(f"⚠️ Skipping bus {bus}: no matching link pair.") + continue + + for snapshot in snapshots: + try: + p_el = get_var(network, "Link", "p").loc[snapshot, el_ids] + p_th = get_var(network, "Link", "p").loc[snapshot, heat_ids] + + # Calculate physical output (p1) by multiplying efficiency + eff_el = network.links.loc[el_ids, "efficiency"] + eff_th = network.links.loc[heat_ids, "efficiency"] + + p_el_out = (p_el * eff_el).sum() + p_th_out = (p_th * eff_th).sum() + + lhs = p_th_out - ratio * p_el_out + + define_constraints( + network, + lhs, + "=", + 0, + "Link", + f"chp_ratio_{carrier_el}_{bus}_{snapshot}" + ) + except Exception as e: + logger.error(f"❌ Error for {carrier_el}, bus {bus}, snapshot {snapshot}: {e}") + continue + + logger.info("✅ Finished writing CHP ratio constraints for all supported CHP types.") + + def _add_resistive_heater_vollaststunden_constraint_linopy(network, snapshots): """ Limits the annual full-load hours of the regional electric boiler (resistive heater) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 2446094ba..ab4ddad57 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4089,13 +4089,12 @@ def replace_gas_links_with_extendable(self): Replaces selected gas-based links in the interest area with extendable variants. - Duplicates original gas-based links as investable links with defined costs and constraints. - - Updates marginal_cost and efficiency for relevant carriers. + - For 'central_gas_CHP' and 'central_gas_CHP_heat', the new carriers are suffixed with '_1'. - Removes the original links from the network afterward. 'industrial_gas_CHP' is not affected. """ - # Carriers to duplicate and replace replace_carriers = [ "central_gas_CHP", "central_gas_CHP_heat", @@ -4104,7 +4103,8 @@ def replace_gas_links_with_extendable(self): ] installed_p_nom = { - "central_gas_CHP": 22.86 + "central_gas_CHP": 22.86, + "central_gas_CHP_heat": 35.76 } p_nom_max_map = { @@ -4138,17 +4138,23 @@ def replace_gas_links_with_extendable(self): "marginal_cost_quadratic", "stand_by_cost" ] - # Step 1️⃣ Filter links in interest area - connected_links = self.find_links_connected_to_buses() + connected_links = self.find_links_connected_to_interest_buses() to_process = connected_links[connected_links.carrier.isin(replace_carriers)] next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()] + [0]) + 1 for exist_index, row in to_process.iterrows(): carrier = row.carrier - link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} - # update marginal cost and efficiency explicitly + # Apply new carrier suffix if needed + if carrier == "central_gas_CHP": + new_carrier = "central_gas_CHP_1" + elif carrier == "central_gas_CHP_heat": + new_carrier = "central_gas_CHP_heat_1" + else: + new_carrier = carrier + + link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, row.get("marginal_cost", 0)) link_attrs["efficiency"] = efficiency_map.get(carrier, row.get("efficiency", 0)) @@ -4167,13 +4173,13 @@ def replace_gas_links_with_extendable(self): name=new_index, bus0=row.bus0, bus1=row.bus1, - carrier=carrier, + carrier=new_carrier, p_nom_extendable=True, **link_attrs) self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"New extendable link {new_index} ({carrier}) added with p_nom={link_attrs.get('p_nom', 'n/a')}.") + print(f"New extendable link {new_index} ({new_carrier}) added with p_nom={link_attrs.get('p_nom', 'n/a')}.") - # Step 2️⃣ Remove original links + # Remove originals for index in to_process.index: carrier = self.network.links.at[index, "carrier"] self.network.links.drop(index=index, inplace=True) @@ -4181,6 +4187,7 @@ def replace_gas_links_with_extendable(self): + def reset_gas_CHP_capacities(self): """ reset_gas_CHP_capacities in interest area @@ -4779,7 +4786,8 @@ def add_waste_CHP_ingolstadt(self): p_nom=18.24, p_nom_min=18.24, marginal_cost=27.8042, - p_nom_extendable=False, + capital_cost=58940.6178, + p_nom_extendable=True, efficiency=0.2102 ) @@ -4799,9 +4807,9 @@ def add_waste_CHP_ingolstadt(self): bus1=central_heat_bus, carrier="central_waste_CHP_heat", p_nom=45.0, - p_nom_min=45.0, + #p_nom_min=45.0, marginal_cost=0, - p_nom_extendable=False, + p_nom_extendable=True, efficiency=0.762 ) @@ -4927,7 +4935,7 @@ def adjust_capital_costs(self): } store_costs = { - "rural_heat_store": 27312.6386, + "rural_heat_store": 19118.7469, "central_heat_store": 69.0976 } From 3645df4b57bb7039c24606907a523df5bbd348dc Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Thu, 17 Jul 2025 11:16:18 +0200 Subject: [PATCH 095/109] Fix Settings for Base_scenario --- etrago/appl.py | 2 +- etrago/sensitivity_runner.py | 8 +++++--- etrago/tools/utilities.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 7c6a94518..c726e0da3 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -799,7 +799,7 @@ def run_etrago(args, json_path): print(links_ing[lcolumns]) print(gens_ing[gcolumns]) - etrago.network.export_to_netcdf("base_network_Scenario_1a.nc") + etrago.network.export_to_netcdf("base_network_Scenario_1b.nc") #import pdb #pdb.set_trace() diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 8b91049f3..85b9d6139 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -80,7 +80,9 @@ }, }, "generator_noise": 789456, # apply generator noise, False or seed number - "extra_functionality": {}, # Choose function name or {} + "extra_functionality": { + "add_chp_ratio_constraint": {} + }, # Choose function name or {} # Spatial Complexity: "delete_dispensable_ac_buses": True, # bool. Find and delete expendable buses "interest_area": ["Ingolstadt"], # False, path to shapefile or list of nuts names of the area that is excluded from the clustering. By default the buses inside remain the same, but the parameter "n_cluster_interest_area" inside "network clustering" defines if it should be clustered to a certain number of buses. @@ -169,7 +171,7 @@ import os class SensitivityEtrago(Etrago): - def __init__(self, nc_path="base_network.nc", args=None): + def __init__(self, nc_path="base_network_Scenario_1a.nc", args=None): # Initialisiere NICHT den vollen eTraGo-Workflow, sondern lade nur das gespeicherte Netzwerk self.network = pypsa.Network(nc_path) self.args = args # Default-Pfad für Ergebnisexport @@ -323,5 +325,5 @@ def run_co2_price_sensitivity(): if __name__ == "__main__": #run_solar_cost_sensitivity() - #run_ch4_cost_sensitivity() + run_ch4_cost_sensitivity() run_co2_price_sensitivity() diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index ab4ddad57..2a30bcf7e 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4104,7 +4104,7 @@ def replace_gas_links_with_extendable(self): installed_p_nom = { "central_gas_CHP": 22.86, - "central_gas_CHP_heat": 35.76 + #"central_gas_CHP_heat": 35.76 } p_nom_max_map = { From d804c8a55af0d664888596afe665bb93daf3f1d0 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 20 Jul 2025 11:21:20 +0200 Subject: [PATCH 096/109] Add new sensitivity-function for rural_heat_pump in sensitivity_runner.py --- etrago/sensitivity_runner.py | 60 ++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 85b9d6139..43fd59283 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -171,7 +171,7 @@ import os class SensitivityEtrago(Etrago): - def __init__(self, nc_path="base_network_Scenario_1a.nc", args=None): + def __init__(self, nc_path="base_network_Scenario_1b.nc", args=None): # Initialisiere NICHT den vollen eTraGo-Workflow, sondern lade nur das gespeicherte Netzwerk self.network = pypsa.Network(nc_path) self.args = args # Default-Pfad für Ergebnisexport @@ -258,6 +258,32 @@ def update_marginal_cost_due_to_CO2_price(self, CO2_new): print( f"✅ {n} {carrier}-Generator(en): marginal_cost um {delta_marginal:.2f} €/MWh angepasst (neuer CO2-Preis: {CO2_new} €/tCO2, alt: {CO2_default} €/tCO2).") + def update_capital_cost_rural_heat_pump(self, factor): + """ + Updates the capital cost of extendable rural heat pumps + by multiplying with a given factor. + + Parameters + ---------- + factor : float + Multiplication factor for capital costs (e.g. 0.5 for halving). + """ + # Get all links connected to interest area + connected_links = self.find_links_connected_to_interest_buses() + + # Filter rural heat pumps that are extendable + rural_hp_links = connected_links[ + (connected_links.carrier == "rural_heat_pump") & + (connected_links.p_nom_extendable == True) + ] + + # Apply cost adjustment + for idx in rural_hp_links.index: + old_cost = self.network.links.at[idx, "capital_cost"] + self.network.links.at[idx, "capital_cost"] = old_cost * factor + + print(f"✅ Updated capital_cost for rural_heat_pump by factor {factor}") + # === Sensitivitäten === @@ -321,9 +347,37 @@ def run_co2_price_sensitivity(): print(f"✅ Ergebnisse gespeichert unter: {export_dir}") +def run_rural_heat_pump_capital_cost_sensitivity(): + """ + Runs a sensitivity analysis over different capital cost factors + for rural heat pumps. For each factor, results are exported to + a separate folder. + """ + capital_cost_factors = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5] + + for factor in capital_cost_factors: + print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") + + # Neues Modell initialisieren + etrago = SensitivityEtrago(args=args) + + # Update capital cost of rural heat pumps + etrago.update_capital_cost_rural_heat_pump(factor) + + # Set export folder + export_dir = f"results/sensitivity_rural_HP_capital_cost_{factor:.2f}".replace(".", "_") + os.makedirs(export_dir, exist_ok=True) + etrago.args["results_folder"] = export_dir + + # Run optimization + etrago.optimize() + + print(f"✅ Results saved in: {export_dir}") + if __name__ == "__main__": #run_solar_cost_sensitivity() - run_ch4_cost_sensitivity() - run_co2_price_sensitivity() + #run_ch4_cost_sensitivity() + #run_co2_price_sensitivity() + run_rural_heat_pump_capital_cost_sensitivity() From 9b7602345d0fe35a4a9f1afd5cf38b6510bed333 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Sun, 20 Jul 2025 20:48:44 +0200 Subject: [PATCH 097/109] Fix Bugs --- etrago/appl.py | 4 +-- etrago/sensitivity_runner.py | 61 +++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index c726e0da3..bdbef92be 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -765,9 +765,9 @@ def run_etrago(args, json_path): etrago.add_extendable_solar_generators_to_interest_area() - etrago.replace_gas_links_with_extendable() + etrago.reset_gas_CHP_capacities() - #etrago.reset_gas_CHP_capacities() + etrago.replace_gas_links_with_extendable() #etrago.add_gas_CHP_extendable() diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 43fd59283..cd36f68e5 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -284,6 +284,31 @@ def update_capital_cost_rural_heat_pump(self, factor): print(f"✅ Updated capital_cost for rural_heat_pump by factor {factor}") + def update_capital_cost_central_heat_pump(self, factor): + """ + Updates the capital cost of extendable rural heat pumps + by multiplying with a given factor. + + Parameters + ---------- + factor : float + Multiplication factor for capital costs (e.g. 0.5 for halving). + """ + # Get all links connected to interest area + connected_links = self.find_links_connected_to_interest_buses() + + # Filter rural heat pumps that are extendable + rural_hp_links = connected_links[ + (connected_links.carrier == "central_heat_pump") & + (connected_links.p_nom_extendable == True) + ] + + # Apply cost adjustment + for idx in rural_hp_links.index: + old_cost = self.network.links.at[idx, "capital_cost"] + self.network.links.at[idx, "capital_cost"] = old_cost * factor + + print(f"✅ Updated capital_cost for rural_heat_pump by factor {factor}") # === Sensitivitäten === @@ -367,17 +392,45 @@ def run_rural_heat_pump_capital_cost_sensitivity(): # Set export folder export_dir = f"results/sensitivity_rural_HP_capital_cost_{factor:.2f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) - etrago.args["results_folder"] = export_dir + etrago.args["csv_export"] = export_dir # Run optimization etrago.optimize() print(f"✅ Results saved in: {export_dir}") +def run_rural_central_pump_capital_cost_sensitivity(): + """ + Runs a sensitivity analysis over different capital cost factors + for rural heat pumps. For each factor, results are exported to + a separate folder. + """ + capital_cost_factors = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5] + + for factor in capital_cost_factors: + print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") + + # Neues Modell initialisieren + etrago = SensitivityEtrago(args=args) + + # Update capital cost of rural heat pumps + etrago.update_capital_cost_central_heat_pump(factor) + + # Set export folder + export_dir = f"results/sensitivity_central_HP_capital_cost_{factor:.2f}".replace(".", "_") + os.makedirs(export_dir, exist_ok=True) + etrago.args["csv_export"] = export_dir + + # Run optimization + etrago.optimize() + + print(f"✅ Results saved in: {export_dir}") + + if __name__ == "__main__": #run_solar_cost_sensitivity() - #run_ch4_cost_sensitivity() - #run_co2_price_sensitivity() - run_rural_heat_pump_capital_cost_sensitivity() + run_ch4_cost_sensitivity() + run_co2_price_sensitivity() + #run_rural_heat_pump_capital_cost_sensitivity() From 03b5bbbc0aad63a6a6982fc041e1b3a0a0405b92 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Wed, 23 Jul 2025 16:51:55 +0200 Subject: [PATCH 098/109] Add new sensitivity-function for central_heat_store in sensitivity_runner.py --- etrago/sensitivity_runner.py | 57 ++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index cd36f68e5..730602998 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -310,6 +310,24 @@ def update_capital_cost_central_heat_pump(self, factor): print(f"✅ Updated capital_cost for rural_heat_pump by factor {factor}") + def update_capital_cost_central_heat_store(self, factor): + """ + Updates the capital cost of extendable central heat stores + by multiplying with a given factor. + + Parameters + ---------- + factor : float + Multiplication factor for capital costs (e.g. 0.5 for halving). + """ + cH_stores = self.network.stores[self.network.stores.carrier == "central_heat_store"] + # Apply cost adjustment + for idx in cH_stores.index: + old_cost = self.network.stores.at[idx, "capital_cost"] + self.network.stores.at[idx, "capital_cost"] = old_cost * factor + + print(f"✅ Updated capital_cost for central_heat_store by factor {factor}") + # === Sensitivitäten === def run_solar_cost_sensitivity(): @@ -378,7 +396,7 @@ def run_rural_heat_pump_capital_cost_sensitivity(): for rural heat pumps. For each factor, results are exported to a separate folder. """ - capital_cost_factors = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5] + capital_cost_factors = [0.5, 0.75, 1.25, 1.5, 2.0, 2.5] for factor in capital_cost_factors: print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") @@ -399,13 +417,13 @@ def run_rural_heat_pump_capital_cost_sensitivity(): print(f"✅ Results saved in: {export_dir}") -def run_rural_central_pump_capital_cost_sensitivity(): +def run_central_heat_pump_capital_cost_sensitivity(): """ Runs a sensitivity analysis over different capital cost factors for rural heat pumps. For each factor, results are exported to a separate folder. """ - capital_cost_factors = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5] + capital_cost_factors = [0.5, 0.9, 1.2, 1.5, 2] for factor in capital_cost_factors: print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") @@ -426,11 +444,40 @@ def run_rural_central_pump_capital_cost_sensitivity(): print(f"✅ Results saved in: {export_dir}") +def run_central_heat_store_capital_cost_sensitivity(): + """ + Runs a sensitivity analysis over different capital cost factors + for rural heat pumps. For each factor, results are exported to + a separate folder. + """ + capital_cost_factors = [0.5, 0.9, 1.2, 1.5, 2] + + for factor in capital_cost_factors: + print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") + + # Neues Modell initialisieren + etrago = SensitivityEtrago(args=args) + + # Update capital cost of rural heat pumps + etrago.update_capital_cost_central_heat_store(factor) + + # Set export folder + export_dir = f"results/sensitivity_central_HS_capital_cost_{factor:.2f}".replace(".", "_") + os.makedirs(export_dir, exist_ok=True) + etrago.args["csv_export"] = export_dir + + # Run optimization + etrago.optimize() + + print(f"✅ Results saved in: {export_dir}") + if __name__ == "__main__": #run_solar_cost_sensitivity() - run_ch4_cost_sensitivity() - run_co2_price_sensitivity() + #run_ch4_cost_sensitivity() + #run_co2_price_sensitivity() #run_rural_heat_pump_capital_cost_sensitivity() + #run_central_heat_pump_capital_cost_sensitivity() + run_central_heat_store_capital_cost_sensitivity() From 9f4e7bfbd515b946ce39b066e2f2a409268aef33 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Thu, 24 Jul 2025 17:00:09 +0200 Subject: [PATCH 099/109] Adapt capital_cost of interest CHP-plants --- etrago/appl.py | 2 +- etrago/tools/utilities.py | 132 +++++--------------------------------- 2 files changed, 18 insertions(+), 116 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index bdbef92be..a455ba62d 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -799,7 +799,7 @@ def run_etrago(args, json_path): print(links_ing[lcolumns]) print(gens_ing[gcolumns]) - etrago.network.export_to_netcdf("base_network_Scenario_1b.nc") + etrago.network.export_to_netcdf("base_network_Scenario_1c.nc") #import pdb #pdb.set_trace() diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 2a30bcf7e..8d8558237 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4103,7 +4103,7 @@ def replace_gas_links_with_extendable(self): ] installed_p_nom = { - "central_gas_CHP": 22.86, + "central_gas_CHP": 50.02, # elec_CHP_Power / n_elec #"central_gas_CHP_heat": 35.76 } @@ -4113,14 +4113,14 @@ def replace_gas_links_with_extendable(self): } capital_cost_map = { - "central_gas_CHP": 41295.8840, + "central_gas_CHP": 18376.6684, "central_gas_CHP_heat": 0, "central_gas_boiler": 3754.1726, "central_resistive_heater": 5094.8667 } marginal_cost_map = { - "central_gas_CHP": 4.3916, + "central_gas_CHP": 1.9543, "central_resistive_heater": 1.0582, "central_gas_CHP_heat": 0, "central_gas_boiler": 1.0582 @@ -4383,13 +4383,13 @@ def add_biogas_CHP_extendable(self): ] capital_cost_map = { - "central_biogas_CHP": 66960.6721, - "rural_biogas_CHP": 66960.6721 + "central_biogas_CHP": 20108.2898, + "rural_biogas_CHP": 20108.2898 } marginal_cost_map = { - "central_biogas_CHP": 7.18, - "rural_biogas_CHP": 7.18 + "central_biogas_CHP": 2.1562, + "rural_biogas_CHP": 2.1562 } efficiency_map = { @@ -4462,13 +4462,13 @@ def add_biomass_CHP_extendable(self): ] capital_cost_map = { - "central_biomass_solid_CHP": 232005.3115, - "rural_biomass_solid_CHP": 448354.9634 + "central_biomass_solid_CHP": 67281.5403, + "rural_biomass_solid_CHP": 67253.2445 } marginal_cost_map = { - "central_biomass_solid_CHP": 4.67, - "rural_biomass_solid_CHP": 9.86 + "central_biomass_solid_CHP": 1.3543, + "rural_biomass_solid_CHP": 1.4790 } efficiency_map = { @@ -4783,10 +4783,10 @@ def add_waste_CHP_ingolstadt(self): bus0=new_bus, bus1=ac_bus, carrier="central_waste_CHP", - p_nom=18.24, - p_nom_min=18.24, - marginal_cost=27.8042, - capital_cost=58940.6178, + p_nom=86.77, + p_nom_min=86.77, # elec_CHP_Power / n_elec + marginal_cost=5.8444, + capital_cost=123893.1786, p_nom_extendable=True, efficiency=0.2102 ) @@ -4806,8 +4806,8 @@ def add_waste_CHP_ingolstadt(self): bus0=new_bus, bus1=central_heat_bus, carrier="central_waste_CHP_heat", - p_nom=45.0, - #p_nom_min=45.0, + #p_nom=59.05, + #p_nom_min=59.05, marginal_cost=0, p_nom_extendable=True, efficiency=0.762 @@ -4818,104 +4818,6 @@ def add_waste_CHP_ingolstadt(self): print(f"Neuer Link {new_link_id} mit carrier = central_waste_CHP_heat hinzugefügt.") -def get_matching_biogs_bus(self): - """ - get Bus_id of Bus only with CH4_biogas-generator - """ - - bus_groups = self.network.generators.groupby("bus") - - def is_exact_match(group): - return ( - len(group) == 2 and # genau 2 Generatoren - set(group.carrier) == {"CH4_biogas", "load shedding"} # bus only with biogas-generator - ) - - # Schritt 3: Filtere Büsse mit genau diesen Kriterien - matching_buses = [bus for bus, group in bus_groups if is_exact_match(group)] - matching_bus = str(matching_buses[0]) - - return matching_bus - -def add_biogas_CHP_extendable(self): - """ - add extendable biogas_CHP-Powerplant, located in Ingolstadt - """ - - carriers = [ - "central_biogas_CHP", - "central_biogas_CHP_heat", - "rural_biogas_CHP", - "rural_biogas_CHP_heat" - ] - - capital_cost_map = { - "central_biogas_CHP": 66960.9053, - "rural_biogas_CHP": 66960.9053 - } - - marginal_cost_map = { - "central_biogas_CHP": 7.18, - "rural_biogas_CHP": 7.18 - } - - efficiency_map = { - "central_biogas_CHP": 0.45, - "central_biogas_CHP_heat": 0.45, - "rural_biogas_CHP": 0.45, - "rural_biogas_CHP_heat": 0.45 - } - - default_attrs = [ - 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', - 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', - 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", - "marginal_cost_quadratic", "stand_by_cost" - ] - - next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 - - # get default attributes from exitsting CHP-Link - existing_central_gas_CHP = self.network.links[self.network.links.carrier == "central_gas_CHP"].iloc[0] - link_attrs = {attr: existing_central_gas_CHP.get(attr, 0) for attr in default_attrs} - - # get AC-Bus and central_heat-Bus in Ingolstadt - buses_ing = self.find_interest_buses() - AC_bus_ing = buses_ing[buses_ing.carrier == "AC"].index[0] - cH_bus_ing = buses_ing[buses_ing.carrier == "central_heat"].index[0] - dH_bus_ing = buses_ing[buses_ing.carrier == "rural_heat"].index[0] - - biogas_bus_id = get_matching_biogs_bus(self) - - for carrier in carriers: - - link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) - link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, 0) - link_attrs["efficiency"] = efficiency_map.get(carrier, 0) - - new_index = str(next_link_id) - next_link_id += 1 - - if carrier in ("central_biogas_CHP", "rural_biogas_CHP"): - bus1 = AC_bus_ing - elif carrier == "central_biogas_CHP_heat": - bus1 = cH_bus_ing - elif carrier == "rural_biogas_CHP_heat": - bus1 = dH_bus_ing - - self.network.add("Link", - name=new_index, - bus0=biogas_bus_id, - bus1=bus1, - carrier=carrier, - p_nom=0, - p_nom_extendable=True, - **link_attrs) - - self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") - - def adjust_capital_costs(self): """ Update the capital_cost values of selected PyPSA components From 0c897320d5f336c60238566720484b16b6a6c42b Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 28 Jul 2025 19:31:07 +0200 Subject: [PATCH 100/109] Adapt capital_cost of interest CHP-plants --- etrago/appl.py | 4 ++- etrago/network.py | 5 +++- etrago/sensitivity_runner.py | 20 ++++++------- etrago/tools/utilities.py | 55 +++++++++++++++++++++++------------- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index a455ba62d..a5a2c4cc5 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -786,6 +786,8 @@ def run_etrago(args, json_path): etrago.set_battery_parameter_interest_area() #etrago.set_battery_and_heat_store_parameters_interest_area() + etrago.set_cyclic_constraints() + buses_ing = etrago.find_interest_buses() gens_ing = etrago.network.generators[etrago.network.generators.bus.isin(buses_ing.index)] @@ -799,7 +801,7 @@ def run_etrago(args, json_path): print(links_ing[lcolumns]) print(gens_ing[gcolumns]) - etrago.network.export_to_netcdf("base_network_Scenario_1c.nc") + etrago.network.export_to_netcdf("base_network_Scenario_1e.nc") #import pdb #pdb.set_trace() diff --git a/etrago/network.py b/etrago/network.py index 58c8885f3..b18ff34ee 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -130,7 +130,8 @@ print_capital_costs, add_gas_CHP_fixed, add_gas_CHP_fixed, - replace_gas_links_with_extendable + replace_gas_links_with_extendable, + set_cyclic_constraints ) logger = logging.getLogger(__name__) @@ -439,6 +440,8 @@ def __init__( replace_gas_links_with_extendable = replace_gas_links_with_extendable + set_cyclic_constraints = set_cyclic_constraints + def dc_lines(self): return self.filter_links_by_carrier("DC", like=False) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 730602998..4e637c1d5 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -171,7 +171,7 @@ import os class SensitivityEtrago(Etrago): - def __init__(self, nc_path="base_network_Scenario_1b.nc", args=None): + def __init__(self, nc_path="base_network_Scenario_1e.nc", args=None): # Initialisiere NICHT den vollen eTraGo-Workflow, sondern lade nur das gespeicherte Netzwerk self.network = pypsa.Network(nc_path) self.args = args # Default-Pfad für Ergebnisexport @@ -347,7 +347,7 @@ def run_solar_cost_sensitivity(): print(f"✅ Ergebnisse gespeichert unter: {export_dir}") def run_ch4_cost_sensitivity(): - ch4_prices = [20, 30, 60, 80, 100] # in €/MWh + ch4_prices = [20, 30, 60, 100] # in €/MWh for price in ch4_prices: print(f" Starte CH₄-Sensitivität mit marginal_cost = {price:.2f} €/MWh_th") @@ -369,7 +369,7 @@ def run_co2_price_sensitivity(): Ergebnisse werden in separate Ordner exportiert. """ - CO2_prices = [50, 100, 130, 160, 200] # in €/tCO2 + CO2_prices = [50, 100, 130, 200] # in €/tCO2 for price in CO2_prices: print(f"🔄 Starte CO₂-Sensitivität mit CO2-Preis = {price:.2f} €/tCO2") @@ -396,7 +396,7 @@ def run_rural_heat_pump_capital_cost_sensitivity(): for rural heat pumps. For each factor, results are exported to a separate folder. """ - capital_cost_factors = [0.5, 0.75, 1.25, 1.5, 2.0, 2.5] + capital_cost_factors = [0.5, 0.9, 1.2, 1.5, 2.0] for factor in capital_cost_factors: print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") @@ -423,7 +423,7 @@ def run_central_heat_pump_capital_cost_sensitivity(): for rural heat pumps. For each factor, results are exported to a separate folder. """ - capital_cost_factors = [0.5, 0.9, 1.2, 1.5, 2] + capital_cost_factors = [0.5, 0.9, 1.2, 1.5] for factor in capital_cost_factors: print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") @@ -450,7 +450,7 @@ def run_central_heat_store_capital_cost_sensitivity(): for rural heat pumps. For each factor, results are exported to a separate folder. """ - capital_cost_factors = [0.5, 0.9, 1.2, 1.5, 2] + capital_cost_factors = [0.5, 0.9, 1.2, 1.5] for factor in capital_cost_factors: print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") @@ -476,8 +476,8 @@ def run_central_heat_store_capital_cost_sensitivity(): if __name__ == "__main__": #run_solar_cost_sensitivity() - #run_ch4_cost_sensitivity() - #run_co2_price_sensitivity() - #run_rural_heat_pump_capital_cost_sensitivity() - #run_central_heat_pump_capital_cost_sensitivity() + run_ch4_cost_sensitivity() + run_co2_price_sensitivity() + run_rural_heat_pump_capital_cost_sensitivity() + run_central_heat_pump_capital_cost_sensitivity() run_central_heat_store_capital_cost_sensitivity() diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 8d8558237..e41a082b8 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4113,22 +4113,22 @@ def replace_gas_links_with_extendable(self): } capital_cost_map = { - "central_gas_CHP": 18376.6684, + "central_gas_CHP": 17963.7095, "central_gas_CHP_heat": 0, "central_gas_boiler": 3754.1726, "central_resistive_heater": 5094.8667 } marginal_cost_map = { - "central_gas_CHP": 1.9543, + "central_gas_CHP": 1.9103, "central_resistive_heater": 1.0582, "central_gas_CHP_heat": 0, "central_gas_boiler": 1.0582 } efficiency_map = { - "central_gas_CHP": 0.445, - "central_gas_CHP_heat": 0.4541 + "central_gas_CHP": 0.435, + "central_gas_CHP_heat": 0.448 } default_attrs = [ @@ -4383,20 +4383,20 @@ def add_biogas_CHP_extendable(self): ] capital_cost_map = { - "central_biogas_CHP": 20108.2898, - "rural_biogas_CHP": 20108.2898 + "central_biogas_CHP": 30467.1058, + "rural_biogas_CHP": 30467.1058 } marginal_cost_map = { - "central_biogas_CHP": 2.1562, - "rural_biogas_CHP": 2.1562 + "central_biogas_CHP": 3.2669, + "rural_biogas_CHP": 3.2669 } efficiency_map = { - "central_biogas_CHP": 0.445, - "central_biogas_CHP_heat": 0.4541, - "rural_biogas_CHP": 0.445, - "rural_biogas_CHP_heat": 0.4541 + "central_biogas_CHP": 0.455, + "central_biogas_CHP_heat": 0.484, + "rural_biogas_CHP": 0.455, + "rural_biogas_CHP_heat": 0.484 } default_attrs = [ @@ -4462,20 +4462,20 @@ def add_biomass_CHP_extendable(self): ] capital_cost_map = { - "central_biomass_solid_CHP": 67281.5403, - "rural_biomass_solid_CHP": 67253.2445 + "central_biomass_solid_CHP": 67513.5457, + "rural_biomass_solid_CHP": 65684.0021 } marginal_cost_map = { - "central_biomass_solid_CHP": 1.3543, - "rural_biomass_solid_CHP": 1.4790 + "central_biomass_solid_CHP": 1.3590, + "rural_biomass_solid_CHP": 1.4445 } efficiency_map = { - "central_biomass_solid_CHP": 0.29, - "central_biomass_solid_CHP_heat": 0.83, - "rural_biomass_solid_CHP": 0.14, - "rural_biomass_solid_CHP_heat": 0.9747 + "central_biomass_solid_CHP": 0.291, + "central_biomass_solid_CHP_heat": 0.8309, + "rural_biomass_solid_CHP": 0.1465, + "rural_biomass_solid_CHP_heat": 0.9733 } default_attrs = [ @@ -4912,6 +4912,21 @@ def print_capital_costs(self): # return cost_summary +def set_cyclic_constraints(self): + """ + Sets cyclic constraints for battery storage units and selected store carriers. + """ + # Set cyclic_state_of_charge = True for all batteries in storage_units + battery_mask = self.network.storage_units.carrier == "battery" + self.network.storage_units.loc[battery_mask, "cyclic_state_of_charge"] = True + + # Set e_cyclic = True for selected carriers in stores + stores_carrier = ["central_heat_store", "rural_heat_store", "battery_storage", "dsm"] + store_mask = self.network.stores.carrier.isin(stores_carrier) + self.network.stores.loc[store_mask, "e_cyclic"] = True + + print("Cyclic constraints set for battery storage_units and defined store carriers.") + def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): """ From b2e3c7deaac7be556466523b9249efb8f56ebca3 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 4 Aug 2025 15:37:20 +0200 Subject: [PATCH 101/109] Set max Solarpotential for interest area --- etrago/appl.py | 2 +- etrago/sensitivity_runner.py | 55 ++++++++++++++++++++++++++++++------ etrago/tools/constraints.py | 10 +++---- etrago/tools/utilities.py | 6 ++-- 4 files changed, 55 insertions(+), 18 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index a5a2c4cc5..4a97265bd 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -801,7 +801,7 @@ def run_etrago(args, json_path): print(links_ing[lcolumns]) print(gens_ing[gcolumns]) - etrago.network.export_to_netcdf("base_network_Scenario_1e.nc") + etrago.network.export_to_netcdf("base_network_Scenario_1f.nc") #import pdb #pdb.set_trace() diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 4e637c1d5..182ce002c 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -171,7 +171,7 @@ import os class SensitivityEtrago(Etrago): - def __init__(self, nc_path="base_network_Scenario_1e.nc", args=None): + def __init__(self, nc_path="base_network_Scenario_1f.nc", args=None): # Initialisiere NICHT den vollen eTraGo-Workflow, sondern lade nur das gespeicherte Netzwerk self.network = pypsa.Network(nc_path) self.args = args # Default-Pfad für Ergebnisexport @@ -181,6 +181,7 @@ def __init__(self, nc_path="base_network_Scenario_1e.nc", args=None): self.ch4_h2_mapping = {} self.tool_version = "manual_sensitivity" + def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): """ Setzt den capital_cost für alle solar_rooftop Generatoren in der Interest Area Ingolstadt. @@ -199,6 +200,41 @@ def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost print(f"✅ capital_cost auf {new_capital_cost:.2f} €/MW/a für {len(solar_generators)} Solar-Generator(en) gesetzt.") + + def limit_solar_rooftop_potential(self, max_capacity_mw=655.916): + """ + Limits the total extendable solar rooftop capacity in Ingolstadt to a given maximum. + + Parameters + ---------- + max_capacity_mw : float + Maximum allowed capacity for solar_rooftop in MW. + """ + + # Get all buses in interest area (Ingolstadt) + buses_ing = self.find_interest_buses() + bus_list = buses_ing.index.to_list() + + # Filter all extendable rooftop PV generators in the interest area + gens_ing = self.network.generators[ + self.network.generators.bus.isin(bus_list) + ] + solar_pv_ing = gens_ing[ + (gens_ing.carrier == "solar_rooftop") & + (gens_ing.p_nom_extendable == True) + ] + + # Check if any rooftop PV exist + if solar_pv_ing.empty: + print("No extendable rooftop PV generators found in the interest area.") + return + + # Apply the limit + self.network.generators.loc[solar_pv_ing.index, "p_nom_max"] = max_capacity_mw + + print(f"Set max PV rooftop capacity in Ingolstadt to {max_capacity_mw:.3f} MW") + + def update_marginal_cost_of_CH4_generators(self, new_marginal_cost): gens = self.network.generators is_ch4 = gens.carrier == "CH4_NG" @@ -331,13 +367,14 @@ def update_capital_cost_central_heat_store(self, factor): # === Sensitivitäten === def run_solar_cost_sensitivity(): - cost_values = [15352.2938, 16083.35546, 16814.41707, 17545.47868] # 210,220,230,240 + cost_values = [13403.9771, 13986.7587, 14569.5403, 15152.3219, 15735.1035, 16317.8851, 16900.6667, 17483.4483] # 230,240,250,260,270,280,290,300 for cost in cost_values: print(f" Starte Solar-Sensitivität mit capital_cost = {cost:.2f} €/MW/a") etrago = SensitivityEtrago(args=args) etrago.update_capital_cost_of_solar_ingolstadt(cost) + etrago.limit_solar_rooftop_potential(max_capacity_mw=655.916) export_dir = f"results/sensitivity_solar_cost_{cost:.5f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) @@ -369,7 +406,7 @@ def run_co2_price_sensitivity(): Ergebnisse werden in separate Ordner exportiert. """ - CO2_prices = [50, 100, 130, 200] # in €/tCO2 + CO2_prices = [200] # in €/tCO2 for price in CO2_prices: print(f"🔄 Starte CO₂-Sensitivität mit CO2-Preis = {price:.2f} €/tCO2") @@ -475,9 +512,9 @@ def run_central_heat_store_capital_cost_sensitivity(): if __name__ == "__main__": - #run_solar_cost_sensitivity() - run_ch4_cost_sensitivity() - run_co2_price_sensitivity() - run_rural_heat_pump_capital_cost_sensitivity() - run_central_heat_pump_capital_cost_sensitivity() - run_central_heat_store_capital_cost_sensitivity() + run_solar_cost_sensitivity() + #run_ch4_cost_sensitivity() + #run_co2_price_sensitivity() + #run_rural_heat_pump_capital_cost_sensitivity() + #run_central_heat_pump_capital_cost_sensitivity() + #run_central_heat_store_capital_cost_sensitivity() diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index 95cae03e7..d1d524f24 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3852,11 +3852,11 @@ def _add_chp_ratio_constraint_linopy(self, network, snapshots): logger.info("🚀 Start writing CHP ratio constraints for all supported CHP types.") chp_techs = { - "central_gas_CHP_1": ("central_gas_CHP_heat_1", 0.445, 0.4541), - "central_biogas_CHP": ("central_biogas_CHP_heat", 0.445, 0.4541), - "rural_biogas_CHP": ("rural_biogas_CHP_heat", 0.445, 0.4541), - "central_biomass_solid_CHP": ("central_biomass_solid_CHP_heat", 0.14, 0.83), - "rural_biomass_solid_CHP": ("rural_biomass_solid_CHP_heat", 0.21, 0.9747), + "central_gas_CHP_1": ("central_gas_CHP_heat_1", 0.435, 0.448), + "central_biogas_CHP": ("central_biogas_CHP_heat", 0.455, 0.484), + "rural_biogas_CHP": ("rural_biogas_CHP_heat", 0.455, 0.484), + "central_biomass_solid_CHP": ("central_biomass_solid_CHP_heat", 0.291, 0.831), + "rural_biomass_solid_CHP": ("rural_biomass_solid_CHP_heat", 0.1465, 0.9733), "central_waste_CHP": ("central_waste_CHP_heat", 0.2102, 0.762), } diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index e41a082b8..4936828af 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4573,10 +4573,10 @@ def add_biomass_boiler_extendable(self): carrier = "rural_biomass_solid_boiler" # Technical parameters as specified - capital_cost = 59700.4849 + capital_cost = 46554.2819 marginal_cost = 39.74 - efficiency = 0.9 - p_nom = 33.32 + efficiency = 0.865 + p_nom = 12.26 # Identify the rural_heat bus in the interest area buses_ing = self.find_interest_buses() From f350d48a8065a752f73d7d33783752a2a335022d Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 5 Aug 2025 21:14:02 +0200 Subject: [PATCH 102/109] Add method run_modified_base_model_with_biomass_and_solar in sensitivity_runner.py for new scenario --- etrago/sensitivity_runner.py | 137 ++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 1 deletion(-) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 182ce002c..c36ffa7be 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -235,6 +235,113 @@ def limit_solar_rooftop_potential(self, max_capacity_mw=655.916): print(f"Set max PV rooftop capacity in Ingolstadt to {max_capacity_mw:.3f} MW") + def set_biomass_CHP_and_add_boiler(self): + """ + Fixes capacity of central_biomass_solid_CHP in Ingolstadt to 3.45 MW, + and adds an extendable rural_biomass_solid_boiler to the rural_heat bus. + """ + + # === 1. Set fixed capacity for central biomass CHP === + connected_links = self.find_links_connected_to_interest_buses() + biomass_chp_links = connected_links[connected_links.carrier == "central_biomass_solid_CHP"] + + if biomass_chp_links.empty: + print("No central_biomass_solid_CHP link found in Ingolstadt.") + else: + for idx in biomass_chp_links.index: + self.network.links.at[idx, "p_nom_min"] = 3.45 + self.network.links.at[idx, "p_nom_extendable"] = True + print(f"Set fixed capacity of {idx} to 3.45 MW.") + + # === 2. Add biomass boiler to rural_heat bus === + # Technical parameters + carrier = "rural_biomass_solid_boiler" + capital_cost = 46554.2819 + marginal_cost = 39.74 + efficiency = 0.865 + p_nom = 12.26 + + # Locate rural_heat bus + buses_ing = self.find_interest_buses() + rural_heat_buses = buses_ing[buses_ing.carrier == "rural_heat"] + + if rural_heat_buses.empty: + print("No rural_heat bus found in Ingolstadt.") + return + + dH_bus_ing = rural_heat_buses.index[0] + + # Add generator + gen_name = f"{dH_bus_ing} {carrier}" + self.network.add("Generator", + name=gen_name, + bus=dH_bus_ing, + carrier=carrier, + p_nom=0, + p_nom_min=p_nom, + p_nom_extendable=True, + capital_cost=capital_cost, + marginal_cost=marginal_cost, + efficiency=efficiency) + + self.network.generators.at[gen_name, "scn_name"] = "eGon2035" + + print(f"Biomass boiler {gen_name} successfully added at bus {dH_bus_ing}.") + + + + def set_solar_PV_and_batterie_capacities(etrago, solar_p_nom=655.916, battery_p_nom=109.32, battery_p_set=18.15): + """ + Sets fixed capacities for rooftop PV and battery storage in the interest area (e.g. Ingolstadt). + + Parameters + ---------- + etrago : Etrago1 + Instance of the Etrago1 class containing the loaded PyPSA network. + solar_p_nom : float, optional + Installed capacity [MW] for rooftop PV to assign (default: 655.916). + battery_p_nom : float, optional + Minimum allowed capacity [MW] for battery storage (default: 109.32). + battery_p_set : float, optional + Fixed installed capacity [MW] to use for battery storage (default: 18.15). + """ + # === Set rooftop PV capacity === + gens = etrago.network.generators + buses = etrago.find_interest_buses() + bus_list = buses.index.tolist() + + # Filter rooftop PV generators in interest area with extendable capacity + solar_gens = gens[ + (gens.bus.isin(bus_list)) & + (gens.carrier == "solar_rooftop") & + (gens.p_nom_extendable) + ] + + if not solar_gens.empty: + etrago.network.generators.loc[solar_gens.index, "p_nom_min"] = solar_p_nom + print(f"Set rooftop PV capacity to {solar_p_nom} MW for {len(solar_gens)} generator(s).") + else: + print("No extendable rooftop PV generators found in the interest area.") + + # === Set battery capacity === + storages = etrago.network.storage_units + + # Filter battery storage units in interest area + battery_units = storages[ + (storages.bus.isin(bus_list)) & + (storages.carrier == "battery") + ] + + if not battery_units.empty: + etrago.network.storage_units.loc[battery_units.index, "p_nom_min"] = battery_p_nom + etrago.network.storage_units.loc[battery_units.index, "p_nom"] = battery_p_set + print( + f"Set battery p_nom_min to {battery_p_nom} MW and p_nom to {battery_p_set} MW for {len(battery_units)} unit(s).") + else: + print("No battery storage units found in the interest area.") + + + def update_marginal_cost_of_CH4_generators(self, new_marginal_cost): gens = self.network.generators is_ch4 = gens.carrier == "CH4_NG" @@ -383,6 +490,33 @@ def run_solar_cost_sensitivity(): etrago.optimize() print(f"✅ Ergebnisse gespeichert unter: {export_dir}") + +def run_modified_base_model_with_biomass_and_solar(): + """ + Runs the base model with fixed biomass CHP, added rural biomass boiler, + and fixed rooftop PV and battery storage capacities. + """ + + print("\n🔧 Running base model with biomass CHP, biomass boiler, and fixed solar/battery capacities") + + # Initialize model + etrago = SensitivityEtrago(args=args) + + # === Apply scenario adjustments === + etrago.limit_solar_rooftop_potential(max_capacity_mw=655.916) + etrago.set_biomass_CHP_and_add_boiler() + etrago.set_solar_PV_and_batterie_capacities(solar_p_nom=655.916, battery_p_nom=109.32, battery_p_set=18.15) + + # === Define output path === + export_dir = "results/scenario_biomass_and_solar_fixed" + os.makedirs(export_dir, exist_ok=True) + etrago.args["csv_export"] = export_dir + + # === Run optimization === + etrago.optimize() + print(f"✅ Results successfully saved to: {export_dir}") + + def run_ch4_cost_sensitivity(): ch4_prices = [20, 30, 60, 100] # in €/MWh for price in ch4_prices: @@ -512,7 +646,8 @@ def run_central_heat_store_capital_cost_sensitivity(): if __name__ == "__main__": - run_solar_cost_sensitivity() + #run_solar_cost_sensitivity() + run_modified_base_model_with_biomass_and_solar() #run_ch4_cost_sensitivity() #run_co2_price_sensitivity() #run_rural_heat_pump_capital_cost_sensitivity() From 2bb593029dc98f488ad2d058ff0fd72e4aa4ade9 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Mon, 15 Sep 2025 10:46:37 +0200 Subject: [PATCH 103/109] Add method run_battery_cost_sensitivity in sensitivity_runner.py to change capital_cost parameter for batteries --- etrago/sensitivity_runner.py | 45 +++++++++-- etrago/tools/utilities.py | 147 +---------------------------------- 2 files changed, 41 insertions(+), 151 deletions(-) diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index c36ffa7be..5eed3afe4 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -200,6 +200,14 @@ def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost print(f"✅ capital_cost auf {new_capital_cost:.2f} €/MW/a für {len(solar_generators)} Solar-Generator(en) gesetzt.") + def update_capital_cost_of_batteries(self, new_capital_cost): + """ + Updates the capital_cost for all battery storage units. + """ + + # Update capital_cost + self.network.storage_units.capital_cost = new_capital_cost + print(f"✅ capital_cost set to {new_capital_cost:.2f} €/MW/a for battery storage unit(s).") def limit_solar_rooftop_potential(self, max_capacity_mw=655.916): """ @@ -237,7 +245,7 @@ def limit_solar_rooftop_potential(self, max_capacity_mw=655.916): def set_biomass_CHP_and_add_boiler(self): """ - Fixes capacity of central_biomass_solid_CHP in Ingolstadt to 3.45 MW, + Add capacity of central_biomass_solid_CHP in Ingolstadt to 3.45 MW, and adds an extendable rural_biomass_solid_boiler to the rural_heat bus. """ @@ -491,6 +499,33 @@ def run_solar_cost_sensitivity(): print(f"✅ Ergebnisse gespeichert unter: {export_dir}") +def run_battery_cost_sensitivity(): + """ + Runs a sensitivity analysis by varying the capital cost of battery storage units + in the interest area and saving the optimization results for each cost value. + """ + cost_values = [15813.72, 13539.00, 11508.15, 9477.30, 7446.45, 5415.60, 3384.75] # €/MW/a # 233.60 , 200, 170, 140, 110, 80, 50 + + for cost in cost_values: + print(f"🔄 Starting battery cost sensitivity with capital_cost = {cost:.2f} €/MW/a") + + # Initialize model + etrago = SensitivityEtrago(args=args) + + # Update battery capital costs + etrago.update_capital_cost_of_batteries(cost) + + # Prepare export directory + export_dir = f"results/sensitivity_battery_cost_{cost:.2f}".replace(".", "_") + os.makedirs(export_dir, exist_ok=True) + etrago.args["csv_export"] = export_dir + + # Run optimization + etrago.optimize() + + print(f"✅ Results saved to: {export_dir}") + + def run_modified_base_model_with_biomass_and_solar(): """ Runs the base model with fixed biomass CHP, added rural biomass boiler, @@ -540,7 +575,7 @@ def run_co2_price_sensitivity(): Ergebnisse werden in separate Ordner exportiert. """ - CO2_prices = [200] # in €/tCO2 + CO2_prices = [60] # in €/tCO2 for price in CO2_prices: print(f"🔄 Starte CO₂-Sensitivität mit CO2-Preis = {price:.2f} €/tCO2") @@ -644,12 +679,12 @@ def run_central_heat_store_capital_cost_sensitivity(): - if __name__ == "__main__": #run_solar_cost_sensitivity() - run_modified_base_model_with_biomass_and_solar() + #run_battery_cost_sensitivity() + #run_modified_base_model_with_biomass_and_solar() #run_ch4_cost_sensitivity() - #run_co2_price_sensitivity() + run_co2_price_sensitivity() #run_rural_heat_pump_capital_cost_sensitivity() #run_central_heat_pump_capital_cost_sensitivity() #run_central_heat_store_capital_cost_sensitivity() diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 4936828af..3010749cf 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4084,6 +4084,7 @@ def add_extendable_solar_generators_to_interest_area(self): print(f"Extendable solar_thermal_collector generator {new_thermal_index} added successfully on rural_heat bus {rural_heat_bus}.") + def replace_gas_links_with_extendable(self): """ Replaces selected gas-based links in the interest area with extendable variants. @@ -4186,8 +4187,6 @@ def replace_gas_links_with_extendable(self): print(f"Removed original link {index} ({carrier})") - - def reset_gas_CHP_capacities(self): """ reset_gas_CHP_capacities in interest area @@ -4208,148 +4207,6 @@ def reset_gas_CHP_capacities(self): self.network.links.at[index, "p_nom"] = 0 -def add_gas_CHP_extendable(self): - """ - Dupliziert gasbasierte Links und central_resistive_heater ohne Effizienz-Zeitreihe als investierbare Variante. - Setzt capital_cost, ggf. marginal_cost und setzt p_nom auf aktuelle Daten bei ausgewählten Technologien. - """ - # carriers of gas links - carriers = [ - #"central_gas_CHP", - #"central_gas_CHP_heat", - "central_gas_boiler", - "central_resistive_heater" - ] - - # Technologie-spezifische p_nom-Vorgaben [MW] - installed_p_nom = { - "central_gas_CHP": 22.86 - } - - # Technologie-spezifische p_nom_max-Vorgaben [MW] - p_nom_max_map = { - "central_gas_boiler": 1.83, - "central_resistive_heater":1.83 - } - - # capital_cost (annualised investment costs [€/MW/a]) - capital_cost_map = { - "central_gas_CHP": 41295.8840, - "central_gas_CHP_heat": 0, - "central_gas_boiler": 3754.1726, - "central_resistive_heater": 5094.8667 - } - - # VOM (€/MWh) - marginal_cost_map = { - "central_resistive_heater": 1.0582, - "central_gas_CHP_heat": 0, - "central_gas_boiler": 1.0582 - - } - - default_attrs = [ - 'efficiency', 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', - 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', - 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", - "marginal_cost_quadratic", "stand_by_cost" - ] - - # filter for interest gas_links - connected_links = find_links_connected_to_interest_buses(self) - gas_links = connected_links[connected_links.carrier.isin(carriers)] - - next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 - - for exist_index, row in gas_links.iterrows(): - - carrier = row.carrier - link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} - - # set capital_cost [€/MW/a] and marginal_cost [€/MWh] - link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) - link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, row.get("marginal_cost", 0)) - - # p_nom setzen, falls spezifiziert - if carrier in installed_p_nom: - link_attrs["p_nom"] = installed_p_nom[carrier] - link_attrs["p_nom_min"] = installed_p_nom[carrier] - - # p_nom_max setzen, falls spezifiziert - if carrier in p_nom_max_map: - link_attrs["p_nom_max"] = p_nom_max_map[carrier] - - new_index = str(next_link_id) - next_link_id += 1 - - self.network.add("Link", - name=new_index, - bus0=row.bus0, - bus1=row.bus1, - carrier=carrier, - p_nom_extendable=True, - **link_attrs) - - self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") - - -def add_gas_CHP_fixed(self): - """ - Adds fixed-capacity gas CHP links (electric and heat output). - Sets p_nom = p_nom_min and disables investment (p_nom_extendable=False). - """ - - # Relevant technologies and their fixed capacities - fixed_links = { - "central_gas_CHP": 22.83, # [MW electric] - "central_gas_CHP_heat": 35.76 # [MW thermal] - } - - # Capital cost map [€/MW/a] - capital_cost_map = { - "central_gas_CHP": 41295.8840, - "central_gas_CHP_heat": 0 - } - - # Link attributes to transfer from existing links - default_attrs = [ - 'efficiency', 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', - 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', - 'ramp_limit_start_up', 'ramp_limit_shut_down', "p_nom_mod", - "marginal_cost", "marginal_cost_quadratic", "stand_by_cost" - ] - - # Find matching links from area of interest - connected_links = find_links_connected_to_interest_buses(self) - gas_links = connected_links[connected_links.carrier.isin(fixed_links.keys())] - - next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 - - for exist_index, row in gas_links.iterrows(): - carrier = row.carrier - - # Copy default attributes from original link - link_attrs = {attr: row.get(attr, 0) for attr in default_attrs} - link_attrs["p_nom"] = fixed_links[carrier] - link_attrs["p_nom_min"] = fixed_links[carrier] - link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) - - new_index = str(next_link_id) - next_link_id += 1 - - self.network.add("Link", - name=new_index, - bus0=row.bus0, - bus1=row.bus1, - carrier=carrier, - p_nom_extendable=False, - **link_attrs) - - self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Fixer Link {new_index} ({carrier}) mit p_nom = {fixed_links[carrier]} MW hinzugefügt.") - - def get_matching_biogs_bus(self): """ get Bus_id of Bus only with CH4_biogas-generator @@ -4910,8 +4767,6 @@ def print_capital_costs(self): print(df) -# return cost_summary - def set_cyclic_constraints(self): """ Sets cyclic constraints for battery storage units and selected store carriers. From 0fa3dd655837aa114fce301a9819f7dab212f509 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 30 Sep 2025 20:35:32 +0200 Subject: [PATCH 104/109] Removes functions "add_gas_CHP_fixed" and "add_gas_CHP_extendable" which are unnecessary --- etrago/appl.py | 2 +- etrago/network.py | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index 4a97265bd..f52d01cc6 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -183,7 +183,7 @@ "n_clusters": 5, # number of periods - only relevant for 'typical_periods' "n_segments": 5, # number of segments - only relevant for segmentation }, - "skip_snapshots": 3, # False or number of snapshots to skip + "skip_snapshots": 20, # False or number of snapshots to skip "temporal_disaggregation": { "active": False, # choose if temporally full complex dispatch optimization should be conducted "no_slices": 8, # number of subproblems optimization is divided into diff --git a/etrago/network.py b/etrago/network.py index b18ff34ee..820e25496 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -121,15 +121,12 @@ set_battery_and_heat_store_parameters_interest_area, add_waste_CHP_ingolstadt, reset_gas_CHP_capacities, - add_gas_CHP_extendable, add_biogas_CHP_extendable, add_biomass_CHP_extendable, update_capital_cost_of_solar_ingolstadt, add_biomass_boiler_extendable, adjust_capital_costs, print_capital_costs, - add_gas_CHP_fixed, - add_gas_CHP_fixed, replace_gas_links_with_extendable, set_cyclic_constraints ) @@ -424,8 +421,6 @@ def __init__( reset_gas_CHP_capacities = reset_gas_CHP_capacities - add_gas_CHP_extendable = add_gas_CHP_extendable - add_biogas_CHP_extendable = add_biogas_CHP_extendable add_biomass_CHP_extendable = add_biomass_CHP_extendable @@ -436,8 +431,6 @@ def __init__( print_capital_costs = print_capital_costs - add_gas_CHP_fixed = add_gas_CHP_fixed - replace_gas_links_with_extendable = replace_gas_links_with_extendable set_cyclic_constraints = set_cyclic_constraints From 1dfba2dac87c227cb7debfb0541036e486e9f8bc Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 30 Sep 2025 23:29:42 +0200 Subject: [PATCH 105/109] Updates docstrings for new functions in utilities.py and sensitivities in sensitivity_runner.py to adapt network on municipality level --- etrago/appl.py | 10 +- etrago/sensitivity_runner.py | 441 ++++++++++++++++---- etrago/tools/utilities.py | 761 +++++++++++++++++++++++++---------- 3 files changed, 898 insertions(+), 314 deletions(-) diff --git a/etrago/appl.py b/etrago/appl.py index f52d01cc6..3629a7707 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -183,7 +183,7 @@ "n_clusters": 5, # number of periods - only relevant for 'typical_periods' "n_segments": 5, # number of segments - only relevant for segmentation }, - "skip_snapshots": 20, # False or number of snapshots to skip + "skip_snapshots": 3, # False or number of snapshots to skip "temporal_disaggregation": { "active": False, # choose if temporally full complex dispatch optimization should be conducted "no_slices": 8, # number of subproblems optimization is divided into @@ -769,10 +769,6 @@ def run_etrago(args, json_path): etrago.replace_gas_links_with_extendable() - #etrago.add_gas_CHP_extendable() - - #etrago.add_gas_CHP_fixed() - etrago.add_biogas_CHP_extendable() etrago.add_biomass_CHP_extendable() @@ -783,8 +779,8 @@ def run_etrago(args, json_path): #etrago.add_biomass_boiler_extendable() - etrago.set_battery_parameter_interest_area() - #etrago.set_battery_and_heat_store_parameters_interest_area() + #etrago.set_battery_parameter_interest_area() + etrago.set_battery_and_heat_store_parameters_interest_area() etrago.set_cyclic_constraints() diff --git a/etrago/sensitivity_runner.py b/etrago/sensitivity_runner.py index 5eed3afe4..e875271ab 100644 --- a/etrago/sensitivity_runner.py +++ b/etrago/sensitivity_runner.py @@ -172,11 +172,33 @@ class SensitivityEtrago(Etrago): def __init__(self, nc_path="base_network_Scenario_1f.nc", args=None): - # Initialisiere NICHT den vollen eTraGo-Workflow, sondern lade nur das gespeicherte Netzwerk + """ + Initialize a lightweight eTraGo wrapper for sensitivity analyses. + + Loads a pre-saved PyPSA network from a NetCDF file and skips the + full eTraGo build workflow. Sets minimal attributes required for + downstream compatibility. + + Parameters + ---------- + nc_path : str, optional + Path to the saved PyPSA network (.nc). Defaults to + ``"base_network_Scenario_1f.nc"``. + args : dict or None, optional + Optional dictionary with runtime arguments (e.g. paths for + result export). Defaults to ``None``. + + Returns + ------- + None + """ + # Load the network directly from NetCDF (no full build pipeline) self.network = pypsa.Network(nc_path) - self.args = args # Default-Pfad für Ergebnisexport - # 🔧 Wichtige Initialisierungen für Kompatibilität + # Store optional runtime arguments as provided by the caller + self.args = args + + # Minimal compatibility attributes used elsewhere in the project self.busmap = {} self.ch4_h2_mapping = {} self.tool_version = "manual_sensitivity" @@ -184,39 +206,88 @@ def __init__(self, nc_path="base_network_Scenario_1f.nc", args=None): def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): """ - Setzt den capital_cost für alle solar_rooftop Generatoren in der Interest Area Ingolstadt. + Update the capital cost of all solar rooftop generators located in the + interest area (e.g., Ingolstadt). + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``generators`` with columns ``bus``, ``carrier``, ``capital_cost``. + - ``find_interest_buses`` : callable + Helper to resolve buses in the interest area. + new_capital_cost : float + New capital cost value [EUR/MW/a] to assign to all rooftop PV generators + in the interest area. + + Returns + ------- + None """ - buses_ingolstadt = find_interest_buses(self) + # Resolve buses in the interest area + buses_ingolstadt = self.find_interest_buses() bus_list = buses_ingolstadt.index.to_list() + # Select rooftop PV generators connected to these buses gens = self.network.generators is_solar_in_ingolstadt = (gens.carrier == "solar_rooftop") & (gens.bus.isin(bus_list)) solar_generators = gens[is_solar_in_ingolstadt] if solar_generators.empty: - print("⚠️ Keine passenden Solar-Generatoren in Ingolstadt gefunden.") + print("No matching solar_rooftop generators found in the interest area.") return + # Apply new capital cost self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost - print(f"✅ capital_cost auf {new_capital_cost:.2f} €/MW/a für {len(solar_generators)} Solar-Generator(en) gesetzt.") + print( + f"Updated capital_cost to {new_capital_cost:.2f} €/MW/a " + f"for {len(solar_generators)} solar_rooftop generator(s) in the interest area." + ) + def update_capital_cost_of_batteries(self, new_capital_cost): """ - Updates the capital_cost for all battery storage units. + Update the capital cost of all battery storage units in the network. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``storage_units`` with columns ``carrier`` and ``capital_cost``. + new_capital_cost : float + New capital cost value [EUR/MW/a] to assign to all battery storage units. + + Returns + ------- + None """ - # Update capital_cost self.network.storage_units.capital_cost = new_capital_cost print(f"✅ capital_cost set to {new_capital_cost:.2f} €/MW/a for battery storage unit(s).") + def limit_solar_rooftop_potential(self, max_capacity_mw=655.916): """ - Limits the total extendable solar rooftop capacity in Ingolstadt to a given maximum. + Limit the maximum extendable rooftop PV capacity in the interest area. Parameters ---------- - max_capacity_mw : float - Maximum allowed capacity for solar_rooftop in MW. + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``generators`` with columns ``bus``, ``carrier``, + ``p_nom_extendable`` and ``p_nom_max``. + - ``find_interest_buses`` : callable + Helper to resolve buses in the interest area. + max_capacity_mw : float, optional + Maximum allowed extendable capacity for rooftop PV [MW]. + Default is 655.916. + + Returns + ------- + None """ # Get all buses in interest area (Ingolstadt) @@ -245,8 +316,29 @@ def limit_solar_rooftop_potential(self, max_capacity_mw=655.916): def set_biomass_CHP_and_add_boiler(self): """ - Add capacity of central_biomass_solid_CHP in Ingolstadt to 3.45 MW, - and adds an extendable rural_biomass_solid_boiler to the rural_heat bus. + Set a fixed minimum capacity for central biomass CHP and add an extendable biomass boiler. + + The function: + 1) Sets ``p_nom_min = 3.45`` MW and ``p_nom_extendable = True`` for all + links with carrier ``"central_biomass_solid_CHP"`` connected to buses + in the interest area. + 2) Adds an extendable generator of carrier ``"rural_biomass_solid_boiler"`` + on the ``rural_heat`` bus in the interest area with given techno-economic parameters. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``links`` and ``generators`` components. + - ``find_links_connected_to_interest_buses`` : callable + Helper to select links connected to interest-area buses. + - ``find_interest_buses`` : callable + Helper to resolve target buses in the interest area. + + Returns + ------- + None """ # === 1. Set fixed capacity for central biomass CHP === @@ -297,7 +389,6 @@ def set_biomass_CHP_and_add_boiler(self): print(f"Biomass boiler {gen_name} successfully added at bus {dH_bus_ing}.") - def set_solar_PV_and_batterie_capacities(etrago, solar_p_nom=655.916, battery_p_nom=109.32, battery_p_set=18.15): """ Sets fixed capacities for rooftop PV and battery storage in the interest area (e.g. Ingolstadt). @@ -312,6 +403,10 @@ def set_solar_PV_and_batterie_capacities(etrago, solar_p_nom=655.916, battery_p_ Minimum allowed capacity [MW] for battery storage (default: 109.32). battery_p_set : float, optional Fixed installed capacity [MW] to use for battery storage (default: 18.15). + + Returns + ------- + None """ # === Set rooftop PV capacity === gens = etrago.network.generators @@ -349,65 +444,97 @@ def set_solar_PV_and_batterie_capacities(etrago, solar_p_nom=655.916, battery_p_ print("No battery storage units found in the interest area.") - def update_marginal_cost_of_CH4_generators(self, new_marginal_cost): + """ + Update the marginal cost of all CH4-based generators in the network. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``generators`` with a ``carrier`` column. + new_marginal_cost : float + New marginal cost value [EUR/MWh] to assign to all generators + with carrier ``CH4_NG``. + + Returns + ------- + None + """ gens = self.network.generators is_ch4 = gens.carrier == "CH4_NG" + if is_ch4.sum() == 0: - print("⚠️ Keine CH4_NG-Generatoren gefunden.") + print("No CH4_NG generators found in the network.") return self.network.generators.loc[is_ch4, "marginal_cost"] = new_marginal_cost - print(f"✅ marginal_cost auf {new_marginal_cost:.2f} €/MWh gesetzt ({is_ch4.sum()} CH₄-Generatoren).") + print( + f"Updated marginal_cost to {new_marginal_cost:.2f} €/MWh " + f"for {is_ch4.sum()} CH4 generator(s)." + ) def update_marginal_cost_due_to_CO2_price(self, CO2_new): """ - Passt die marginal costs von CH4_NG- und waste-Generatoren an, - um den veränderten CO2-Preis zu berücksichtigen. + Adjust the marginal costs of CH4_NG and waste generators + to reflect a change in the CO₂ price. - Formel: + The adjustment follows: marginal_cost += (CO2_new - CO2_default) * emissions_factor - Args: - CO2_new (float): Neuer CO2-Preis in €/tCO2. + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``generators`` with columns ``carrier`` and ``marginal_cost``. + CO2_new : float + New CO₂ price [EUR/tCO₂]. + + Returns + ------- + None """ - gens = self.network.generators - # Fester Default-Preis - CO2_default = 76.5 # €/tCO2 + # Default CO₂ price (reference) + CO2_default = 76.5 # €/tCO₂ - # Delta CO2 + # Change in CO₂ price delta_CO2 = CO2_new - CO2_default - # Emissionsfaktoren in tCO2/MWh + # Emission factors [tCO₂/MWh] per carrier emissions_factors = { "CH4_NG": 0.201, "waste": 0.165 } - # Selektiere Generatoren mit Carrier CH4_NG oder waste + # Select relevant generators mask = gens.carrier.isin(emissions_factors.keys()) - if mask.sum() == 0: - print("⚠️ Keine relevanten Generatoren mit CO2-Emissionen gefunden.") + print("No CH4_NG or waste generators with emissions found in the network.") return - # Iteriere über die betroffenen Carrier + # Apply adjustments carrier by carrier for carrier, ef in emissions_factors.items(): carrier_mask = gens.carrier == carrier n = carrier_mask.sum() if n == 0: continue - # Berechne den Zuschlag + # Calculate marginal cost adjustment delta_marginal = delta_CO2 * ef - # Addiere zum bestehenden marginal_cost + # Update marginal_cost column self.network.generators.loc[carrier_mask, "marginal_cost"] += delta_marginal print( - f"✅ {n} {carrier}-Generator(en): marginal_cost um {delta_marginal:.2f} €/MWh angepasst (neuer CO2-Preis: {CO2_new} €/tCO2, alt: {CO2_default} €/tCO2).") + f"Updated {n} {carrier} generator(s): " + f"marginal_cost adjusted by {delta_marginal:.2f} €/MWh " + f"(CO₂ price new: {CO2_new} €/tCO₂, default: {CO2_default} €/tCO₂)." + ) + def update_capital_cost_rural_heat_pump(self, factor): """ @@ -418,6 +545,10 @@ def update_capital_cost_rural_heat_pump(self, factor): ---------- factor : float Multiplication factor for capital costs (e.g. 0.5 for halving). + + Returns + ------- + None """ # Get all links connected to interest area connected_links = self.find_links_connected_to_interest_buses() @@ -444,6 +575,10 @@ def update_capital_cost_central_heat_pump(self, factor): ---------- factor : float Multiplication factor for capital costs (e.g. 0.5 for halving). + + Returns + ------- + None """ # Get all links connected to interest area connected_links = self.find_links_connected_to_interest_buses() @@ -470,6 +605,10 @@ def update_capital_cost_central_heat_store(self, factor): ---------- factor : float Multiplication factor for capital costs (e.g. 0.5 for halving). + + Returns + ------- + None """ cH_stores = self.network.stores[self.network.stores.carrier == "central_heat_store"] # Apply cost adjustment @@ -482,29 +621,71 @@ def update_capital_cost_central_heat_store(self, factor): # === Sensitivitäten === def run_solar_cost_sensitivity(): - cost_values = [13403.9771, 13986.7587, 14569.5403, 15152.3219, 15735.1035, 16317.8851, 16900.6667, 17483.4483] # 230,240,250,260,270,280,290,300 + """ + Run a sensitivity analysis for rooftop PV capital costs in the interest area. + + For each predefined capital cost value, the function: + - Initializes a :class:`SensitivityEtrago` instance. + - Updates the capital cost of rooftop PV in the interest area. + - Limits the rooftop PV potential to a fixed maximum. + - Sets up a dedicated results export directory. + - Runs the optimization and saves results. + + Parameters + ---------- + None + + Returns + ------- + None + """ + + cost_values = [13403.9771, 13986.7587, 14569.5403, + 15152.3219, 15735.1035, 16317.8851, + 16900.6667, 17483.4483] # 230,240,250,260,270,280,290,300 + for cost in cost_values: - print(f" Starte Solar-Sensitivität mit capital_cost = {cost:.2f} €/MW/a") + print(f" Starting solar cost sensitivity with capital_cost = {cost:.2f} €/MW/a") + # Initialize network for sensitivity run etrago = SensitivityEtrago(args=args) + # Update rooftop PV parameters etrago.update_capital_cost_of_solar_ingolstadt(cost) etrago.limit_solar_rooftop_potential(max_capacity_mw=655.916) + # Prepare result directory for this cost value export_dir = f"results/sensitivity_solar_cost_{cost:.5f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir + # Run optimization and save results etrago.optimize() - print(f"✅ Ergebnisse gespeichert unter: {export_dir}") + print(f"✅ Results saved to: {export_dir}") def run_battery_cost_sensitivity(): """ - Runs a sensitivity analysis by varying the capital cost of battery storage units - in the interest area and saving the optimization results for each cost value. + Run a sensitivity analysis for battery storage capital costs. + + For each predefined capital cost value, the function: + - Initializes a :class:`SensitivityEtrago` instance. + - Updates the capital cost of all battery storage units. + - Creates a dedicated results export directory. + - Runs the optimization and stores results. + + Parameters + ---------- + None + + Returns + ------- + None """ - cost_values = [15813.72, 13539.00, 11508.15, 9477.30, 7446.45, 5415.60, 3384.75] # €/MW/a # 233.60 , 200, 170, 140, 110, 80, 50 + + # Battery capital cost values [EUR/MW/a] + cost_values = [15813.72, 13539.00, 11508.15, + 9477.30, 7446.45, 5415.60, 3384.75] # €/MW/a # 233.60 , 200, 170, 140, 110, 80, 50 for cost in cost_values: print(f"🔄 Starting battery cost sensitivity with capital_cost = {cost:.2f} €/MW/a") @@ -528,8 +709,20 @@ def run_battery_cost_sensitivity(): def run_modified_base_model_with_biomass_and_solar(): """ - Runs the base model with fixed biomass CHP, added rural biomass boiler, - and fixed rooftop PV and battery storage capacities. + Run the base model with predefined biomass and solar modifications. + + Scenario adjustments: + - Limit rooftop PV potential to a fixed maximum. + - Set fixed minimum capacity for central biomass CHP and add a rural biomass boiler. + - Fix rooftop PV and battery storage capacities to defined values. + + Parameters + ---------- + None + + Returns + ------- + None """ print("\n🔧 Running base model with biomass CHP, biomass boiler, and fixed solar/battery capacities") @@ -537,146 +730,216 @@ def run_modified_base_model_with_biomass_and_solar(): # Initialize model etrago = SensitivityEtrago(args=args) - # === Apply scenario adjustments === + # Apply scenario adjustments etrago.limit_solar_rooftop_potential(max_capacity_mw=655.916) etrago.set_biomass_CHP_and_add_boiler() etrago.set_solar_PV_and_batterie_capacities(solar_p_nom=655.916, battery_p_nom=109.32, battery_p_set=18.15) - # === Define output path === + # Define output path export_dir = "results/scenario_biomass_and_solar_fixed" os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir - # === Run optimization === + # Run optimization etrago.optimize() print(f"✅ Results successfully saved to: {export_dir}") def run_ch4_cost_sensitivity(): - ch4_prices = [20, 30, 60, 100] # in €/MWh + """ + Run a sensitivity analysis for natural gas (CH₄) prices. + + For each predefined CH₄ price, the function: + - Initializes a :class:`SensitivityEtrago` instance. + - Updates the marginal cost of all CH₄ generators. + - Creates a dedicated results export directory. + - Runs the optimization and stores the results. + + Parameters + ---------- + None + + Returns + ------- + None + """ + ch4_prices = [20, 30, 60, 100] # CH₄ fuel prices [EUR/MWh_th] + for price in ch4_prices: - print(f" Starte CH₄-Sensitivität mit marginal_cost = {price:.2f} €/MWh_th") + print(f"Starting CH₄ sensitivity with marginal_cost = {price:.2f} €/MWh_th") + # Initialize model for sensitivity run etrago = SensitivityEtrago(args=args) + # Update marginal costs of CH₄ generators etrago.update_marginal_cost_of_CH4_generators(price) + # Prepare export directory for this price value export_dir = f"results/sensitivity_CH4_price_{price:.2f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir + # Run optimization and save results etrago.optimize() - print(f"✅ Ergebnisse gespeichert unter: {export_dir}") + print(f"Results saved to: {export_dir}") + def run_co2_price_sensitivity(): """ - Führt eine Sensitivitätsanalyse über verschiedene CO2-Preise durch. - Für jeden Preis wird das Modell mit angepassten marginal_costs gerechnet. - Ergebnisse werden in separate Ordner exportiert. - """ + Run a sensitivity analysis for different CO₂ prices. + + For each CO₂ price, the function: + - Initializes a :class:`SensitivityEtrago` instance. + - Updates marginal costs of CH₄ and waste generators according to the new CO₂ price. + - Creates a dedicated results export directory. + - Runs the optimization and stores the results. - CO2_prices = [60] # in €/tCO2 + Parameters + ---------- + None + + Returns + ------- + None + """ + # CO₂ price values [EUR/tCO₂] + CO2_prices = [60] for price in CO2_prices: - print(f"🔄 Starte CO₂-Sensitivität mit CO2-Preis = {price:.2f} €/tCO2") + print(f"Starting CO₂ sensitivity with CO₂ price = {price:.2f} €/tCO₂") - # Neues Modell initialisieren + # Initialize model for sensitivity run etrago = SensitivityEtrago(args=args) - # CO2-bedingte Marginalkosten anpassen + # Adjust marginal costs according to CO₂ price etrago.update_marginal_cost_due_to_CO2_price(price) - # Export-Verzeichnis anlegen + # Prepare export directory for this CO₂ price export_dir = f"results/sensitivity_CO2_price_{price:.2f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir - # Optimierung starten + # Run optimization and save results etrago.optimize() + print(f"Results saved to: {export_dir}") - print(f"✅ Ergebnisse gespeichert unter: {export_dir}") def run_rural_heat_pump_capital_cost_sensitivity(): """ - Runs a sensitivity analysis over different capital cost factors - for rural heat pumps. For each factor, results are exported to - a separate folder. + Run a sensitivity analysis for rural heat pump capital costs. + + For each predefined factor, the function: + - Initializes a :class:`SensitivityEtrago` instance. + - Updates the capital cost of rural heat pumps by the given factor. + - Creates a dedicated results export directory. + - Runs the optimization and stores results. + + Parameters + ---------- + None + + Returns + ------- + None """ capital_cost_factors = [0.5, 0.9, 1.2, 1.5, 2.0] for factor in capital_cost_factors: - print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") + print(f"Running rural heat pump capital cost sensitivity with factor = {factor:.2f}") - # Neues Modell initialisieren + # Initialize model for sensitivity run etrago = SensitivityEtrago(args=args) - # Update capital cost of rural heat pumps + # Update rural heat pump capital cost etrago.update_capital_cost_rural_heat_pump(factor) - # Set export folder + # Prepare export directory for this factor export_dir = f"results/sensitivity_rural_HP_capital_cost_{factor:.2f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir - # Run optimization + # Run optimization and save results etrago.optimize() + print(f"Results saved to: {export_dir}") - print(f"✅ Results saved in: {export_dir}") def run_central_heat_pump_capital_cost_sensitivity(): """ - Runs a sensitivity analysis over different capital cost factors - for rural heat pumps. For each factor, results are exported to - a separate folder. + Run a sensitivity analysis for central heat pump capital costs. + + For each predefined factor, the function: + - Initializes a :class:`SensitivityEtrago` instance. + - Updates the capital cost of central heat pumps by the given factor. + - Creates a dedicated export folder for results. + - Runs the optimization and saves outputs. + + Parameters + ---------- + None + + Returns + ------- + None """ + # Define factors for scaling central heat pump capital costs capital_cost_factors = [0.5, 0.9, 1.2, 1.5] for factor in capital_cost_factors: - print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") + print(f"Running central heat pump capital cost sensitivity with factor = {factor:.2f}") - # Neues Modell initialisieren + # Initialize model for sensitivity run etrago = SensitivityEtrago(args=args) - # Update capital cost of rural heat pumps + # Apply capital cost adjustment etrago.update_capital_cost_central_heat_pump(factor) - # Set export folder + # Prepare export directory for this factor export_dir = f"results/sensitivity_central_HP_capital_cost_{factor:.2f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir - # Run optimization + # Run optimization and save results etrago.optimize() + print(f"Results saved to: {export_dir}") - print(f"✅ Results saved in: {export_dir}") def run_central_heat_store_capital_cost_sensitivity(): """ - Runs a sensitivity analysis over different capital cost factors - for rural heat pumps. For each factor, results are exported to - a separate folder. + Run a sensitivity analysis for central heat store capital costs. + + For each predefined factor, the function: + - Initializes a :class:`SensitivityEtrago` instance. + - Updates the capital cost of central heat stores by the given factor. + - Creates a dedicated export folder for results. + - Runs the optimization and saves outputs. + + Parameters + ---------- + None + + Returns + ------- + None """ capital_cost_factors = [0.5, 0.9, 1.2, 1.5] for factor in capital_cost_factors: - print(f"🔄 Running capital cost sensitivity with factor = {factor:.2f}") + print(f"Running central heat store capital cost sensitivity with factor = {factor:.2f}") - # Neues Modell initialisieren + # Initialize model for sensitivity run etrago = SensitivityEtrago(args=args) - # Update capital cost of rural heat pumps + # Apply capital cost adjustment etrago.update_capital_cost_central_heat_store(factor) - # Set export folder + # Prepare export directory for this factor export_dir = f"results/sensitivity_central_HS_capital_cost_{factor:.2f}".replace(".", "_") os.makedirs(export_dir, exist_ok=True) etrago.args["csv_export"] = export_dir - # Run optimization + # Run optimization and save results etrago.optimize() - - print(f"✅ Results saved in: {export_dir}") - + print(f"Results saved to: {export_dir}") if __name__ == "__main__": diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 3010749cf..92da1b14d 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -3900,55 +3900,94 @@ def filter_german_components(df, component, german_buses): def find_interest_buses(self): """ - Identifiziere alle Busse innerhalb von Regionen, deren Name - in args["interest_area"] als Teilstring vorkommt. + Identify all buses located within the regions defined by + name fragments in ``args["interest_area"]``. - args["interest_area"] ist eine Liste von Namensfragmenten. + Parameters + ---------- + self : :class:`Etrago + Class instance containing: + - ``self.args`` : dict + Dictionary of model arguments, must include: + * ``"interest_area"`` : list of str + List of name fragments for region selection. + * ``"nuts_3_map"`` : str + Path to GeoJSON file with NUTS-3 boundaries. + - ``self.network`` : pypsa.Network + PyPSA network object with bus coordinates. + + Returns + ------- + geopandas.GeoDataFrame + GeoDataFrame containing the subset of network buses + located within the selected interest area. Includes + all original bus attributes and geometry column. """ args = self.args - # GeoJSON einlesen + # Read NUTS-3 level map from GeoJSON nuts = gpd.read_file(args["nuts_3_map"]) nuts["NUTS_NAME"] = nuts["NUTS_NAME"].str.strip() - # Matchen über str.contains für alle Einträge in args["interest_area"] + # Match all entries from args["interest_area"] via substring search area_filter = args["interest_area"] - mask = nuts["NUTS_NAME"].apply(lambda name: any(area.lower() in name.lower() for area in area_filter)) + mask = nuts["NUTS_NAME"].apply( + lambda name: any(area.lower() in name.lower() for area in area_filter) + ) interest_area = nuts[mask] if interest_area.empty: - raise ValueError(f"Keine Region mit Teilstrings {area_filter} in GeoJSON gefunden.") + raise ValueError( + f"No region with substrings {area_filter} found in GeoJSON." + ) - # Busse zu GeoDataFrame + # Convert network buses to GeoDataFrame with coordinates buses = gpd.GeoDataFrame( self.network.buses.copy(), geometry=gpd.points_from_xy(self.network.buses.x, self.network.buses.y), crs="EPSG:4326" ) - # CRS-Anpassung - # buses = buses.to_crs(interest_area.crs) + # Ensure CRS is consistent between areas and buses if interest_area.crs != "EPSG:4326": interest_area = interest_area.to_crs("EPSG:4326") - # Leere Geometrien ausschließen - interest_area = interest_area[~interest_area.geometry.is_empty & interest_area.geometry.notnull()] + # Exclude empty or null geometries + interest_area = interest_area[ + ~interest_area.geometry.is_empty & interest_area.geometry.notnull() + ] - # Räumlicher Schnitt + # Perform spatial intersection to find buses within interest area buses_in_area = buses[buses.geometry.within(interest_area.unary_union)] - # buses_in_area = buses[buses.geometry.within(interest_area.buffer(0.005).unary_union)] - - #print(f"{len(buses_in_area)} Busse in {area_filter} gefunden.") - #print(buses_in_area.carrier) return buses_in_area + def find_links_connected_to_interest_buses(self): - # find buses in interst area + """ + Find all network links connected to buses within the defined interest area. + + Parameters + ---------- + self : :class:`Etrago` + Class instance containing: + - ``network`` : pypsa.Network + PyPSA network object with links and buses. + - ``args`` : dict + Dictionary of model arguments, required by + :func:`find_interest_buses` to identify the region. + + Returns + ------- + pandas.DataFrame + DataFrame containing all links for which either + ``bus0`` or ``bus1`` is located in the interest area. + """ + # Identify buses within the interest area gdf_buses_interest = find_interest_buses(self) buses_of_interest = gdf_buses_interest.index.tolist() - # Links where bus0 or bus1 is in the area of interest + # Select links connected to those buses links = self.network.links.copy() connected_links = links[ (links["bus0"].isin(buses_of_interest)) | @@ -3960,17 +3999,36 @@ def find_links_connected_to_interest_buses(self): def get_next_index(etrago, component="Generator", carrier="solar_rooftop"): """ - Gibt den nächsten freien Index im Format '{int} {carrier}' für das angegebene - PyPSA-Komponentenobjekt (z.B. 'Generator' oder 'Link') zurück. + Get the next available index for a PyPSA component of a given carrier. + + The index follows the format ``"{int} {carrier}"``, for example + ``"17 solar_rooftop"``. Existing indices are scanned, and the next + free integer is returned. + + Parameters + ---------- + etrago : :class:`Etrago` + Class instance containing the PyPSA network. + component : str, optional + Name of the PyPSA component (e.g. ``"Generator"`` or ``"Link"``). + Default is ``"Generator"``. + carrier : str, optional + Carrier name used to filter components (e.g. ``"solar_rooftop"``). + Default is ``"solar_rooftop"``. - Beispiel: "17 solar_rooftop" + Returns + ------- + str + Next available component index in the format + ``"{int} {carrier}"``. """ n = etrago.network.copy() comp_df = getattr(n, component.lower() + "s") - # Nur Komponenten mit dem angegebenen Carrier + # Select only components with the specified carrier filtered = comp_df[comp_df.carrier == carrier] + # Extract all used integer indices for this carrier used_ids = [] for idx in filtered.index: pattern = rf"(\d+)\s+{re.escape(carrier)}" @@ -3978,16 +4036,33 @@ def get_next_index(etrago, component="Generator", carrier="solar_rooftop"): if match: used_ids.append(int(match.group(1))) + # Determine the next free index next_id = max(used_ids) + 1 if used_ids else 0 + return f"{next_id} {carrier}" def add_extendable_solar_generators_to_interest_area(self): """ - Adds two extendable solar generators to the interest area: - 1. An extendable solar_rooftop generator on the AC bus (duplicate of existing, but p_nom=0 and new extendable gen). - 2. A solar_thermal_collector generator on the rural_heat bus. - """ + Add two extendable solar generators within the interest area. + + The function performs: + 1) Deactivate existing ``solar_rooftop`` generators in the area (set ``p_nom=0``), + then add a new extendable ``solar_rooftop`` generator on the AC bus and copy + the time series from an existing PV unit. + 2) Add an extendable ``solar_thermal_collector`` generator on the rural_heat bus, + copying default operational attributes from an existing solar-thermal unit. + + Parameters + ---------- + self : :class:`Etrago` + Model instance with ``network`` (pypsa.Network) and region utilities + (e.g., :func:`find_interest_buses`), as well as helper :func:`get_next_index`. + + Returns + ------- + None + """ # Define default attributes to be copied from existing generators default_attrs = [ @@ -4087,13 +4162,28 @@ def add_extendable_solar_generators_to_interest_area(self): def replace_gas_links_with_extendable(self): """ - Replaces selected gas-based links in the interest area with extendable variants. + Replace selected gas-based links in the interest area with extendable variants. - - Duplicates original gas-based links as investable links with defined costs and constraints. - - For 'central_gas_CHP' and 'central_gas_CHP_heat', the new carriers are suffixed with '_1'. - - Removes the original links from the network afterward. + The function: + - Duplicates original gas-based links as investable (extendable) links with + predefined costs and constraints. + - For ``central_gas_CHP`` and ``central_gas_CHP_heat``, applies a ``"_1"`` suffix + to the new carrier names (to support custom coupling constraints downstream). + - Removes the original links from the network afterwards. + - ``industrial_gas_CHP`` is intentionally not modified. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with ``links`` component to be modified. + - ``find_links_connected_to_interest_buses`` : callable + Helper to select links connected to buses in the interest area. - 'industrial_gas_CHP' is not affected. + Returns + ------- + None """ replace_carriers = [ @@ -4189,7 +4279,21 @@ def replace_gas_links_with_extendable(self): def reset_gas_CHP_capacities(self): """ - reset_gas_CHP_capacities in interest area + Reset installed capacities (``p_nom``) of selected gas-based links to zero + within the interest area. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with a ``links`` component to modify. + - ``find_links_connected_to_interest_buses`` : callable + Helper that returns links connected to buses in the interest area. + + Returns + ------- + None """ carriers = [ @@ -4209,7 +4313,24 @@ def reset_gas_CHP_capacities(self): def get_matching_biogs_bus(self): """ - get Bus_id of Bus only with CH4_biogas-generator + Identify the bus that hosts only a ``CH4_biogas`` generator (and the + associated ``load shedding`` unit). + + The function checks all buses and returns those where the generator + composition consists exclusively of these two carriers. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with a ``generators`` component. + + Returns + ------- + pandas.Index + Index of buses that contain exactly two generators: one with + carrier ``CH4_biogas`` and one with carrier ``load shedding``. """ bus_groups = self.network.generators.groupby("bus") @@ -4229,9 +4350,33 @@ def is_exact_match(group): def add_biogas_CHP_extendable(self): """ - add extendable biogas_CHP-Powerplant, located in Ingolstadt - """ + Add extendable biogas CHP links located in the interest area. + The function creates four investable links (power and heat, central and rural) + sourced from the bus that exclusively hosts the ``CH4_biogas`` generator: + - ``central_biogas_CHP`` -> AC bus (electric output) + - ``central_biogas_CHP_heat`` -> central_heat bus (thermal output) + - ``rural_biogas_CHP`` -> AC bus (electric output) + - ``rural_biogas_CHP_heat`` -> rural_heat bus (thermal output) + + Costs, efficiencies, and operational attributes are taken from maps below and + complemented by defaults copied from an existing gas CHP link. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with ``links`` and ``generators`` components. + - ``find_interest_buses`` : callable + Helper to select buses in the interest area (AC, central_heat, rural_heat). + - ``get_matching_biogs_bus`` : callable + Helper to find the source bus hosting the biogas generator only. + + Returns + ------- + None + """ carriers = [ "central_biogas_CHP", "central_biogas_CHP_heat", @@ -4239,16 +4384,19 @@ def add_biogas_CHP_extendable(self): "rural_biogas_CHP_heat" ] + # Capital costs per carrier (units consistent with project cost convention) capital_cost_map = { "central_biogas_CHP": 30467.1058, "rural_biogas_CHP": 30467.1058 } + # Variable costs per carrier marginal_cost_map = { "central_biogas_CHP": 3.2669, "rural_biogas_CHP": 3.2669 } + # Efficiencies per carrier (electrical and thermal) efficiency_map = { "central_biogas_CHP": 0.455, "central_biogas_CHP_heat": 0.484, @@ -4256,6 +4404,7 @@ def add_biogas_CHP_extendable(self): "rural_biogas_CHP_heat": 0.484 } + # Operational attributes copied from an existing link as defaults default_attrs = [ 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', @@ -4263,29 +4412,34 @@ def add_biogas_CHP_extendable(self): "marginal_cost_quadratic", "stand_by_cost" ] + # Determine next numeric link id (assumes existing numeric link names) next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 - # get default attributes from exitsting CHP-Link + # Use an existing gas CHP as template for default operational attributes existing_central_gas_CHP = self.network.links[self.network.links.carrier == "central_gas_CHP"].iloc[0] link_attrs = {attr: existing_central_gas_CHP.get(attr, 0) for attr in default_attrs} - # get AC-Bus and central_heat-Bus in Ingolstadt + # Resolve target buses in the interest area buses_ing = self.find_interest_buses() AC_bus_ing = buses_ing[buses_ing.carrier == "AC"].index[0] cH_bus_ing = buses_ing[buses_ing.carrier == "central_heat"].index[0] dH_bus_ing = buses_ing[buses_ing.carrier == "rural_heat"].index[0] + # Source bus that exclusively hosts CH4_biogas (and load shedding) biogas_bus_id = get_matching_biogs_bus(self) + # Add one link per carrier with extendable capacity for carrier in carriers: - + # Set carrier-specific cost and efficiency parameters link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, 0) link_attrs["efficiency"] = efficiency_map.get(carrier, 0) + # New unique link name new_index = str(next_link_id) next_link_id += 1 + # Map outputs to the corresponding bus if carrier in ("central_biogas_CHP", "rural_biogas_CHP"): bus1 = AC_bus_ing elif carrier == "central_biogas_CHP_heat": @@ -4293,24 +4447,44 @@ def add_biogas_CHP_extendable(self): elif carrier == "rural_biogas_CHP_heat": bus1 = dH_bus_ing - self.network.add("Link", - name=new_index, - bus0=biogas_bus_id, - bus1=bus1, - carrier=carrier, - p_nom=0, - p_nom_extendable=True, - **link_attrs) - + # Add extendable link with zero initial capacity + self.network.add( + "Link", + name=new_index, + bus0=biogas_bus_id, + bus1=bus1, + carrier=carrier, + p_nom=0, + p_nom_extendable=True, + **link_attrs + ) self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + print(f"New link {new_index} ({carrier}) added with installed p_nom={link_attrs.get('p_nom', 'n/a')}.") def add_biomass_CHP_extendable(self): """ - add extendable biomass_solid_CHP-Powerplant, located in Ingolstadt - """ + Add extendable biomass solid CHP links and a dedicated biomass bus in the interest area (e.g., Ingolstadt). + The function: + - Creates a new ``biomass_solid`` bus with geographic attributes. + - Adds a fixed-capacity ``biomass_solid`` generator (fuel supply proxy) on that bus. + - Creates four investable links (power/heat, central/rural) from the biomass bus to the + respective target buses in the interest area. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with ``buses``, ``generators`` and ``links`` components. + - ``find_interest_buses`` : callable + Helper to select AC, central_heat and rural_heat buses in the interest area. + + Returns + ------- + None + """ carriers = [ "central_biomass_solid_CHP", "central_biomass_solid_CHP_heat", @@ -4318,16 +4492,15 @@ def add_biomass_CHP_extendable(self): "rural_biomass_solid_CHP_heat" ] + # CAPEX, OPEX and efficiencies per carrier (consistent with project conventions) capital_cost_map = { "central_biomass_solid_CHP": 67513.5457, "rural_biomass_solid_CHP": 65684.0021 } - marginal_cost_map = { "central_biomass_solid_CHP": 1.3590, "rural_biomass_solid_CHP": 1.4445 } - efficiency_map = { "central_biomass_solid_CHP": 0.291, "central_biomass_solid_CHP_heat": 0.8309, @@ -4335,6 +4508,7 @@ def add_biomass_CHP_extendable(self): "rural_biomass_solid_CHP_heat": 0.9733 } + # Operational attributes copied from an existing link as defaults default_attrs = [ 'start_up_cost', 'shut_down_cost', 'min_up_time', 'min_down_time', 'up_time_before', 'down_time_before', 'ramp_limit_up', 'ramp_limit_down', @@ -4342,88 +4516,100 @@ def add_biomass_CHP_extendable(self): "marginal_cost_quadratic", "stand_by_cost" ] - # Add new bus with additional attributes + # --- Create a new biomass_solid bus (location set to Ingolstadt coordinates) --- new_bus = str(self.network.buses.index.astype(np.int64).max() + 1) - self.network.add("Bus", - new_bus, - carrier="biomass_solid", - v_nom=1.0, - x=11.342843544333306, - y=48.76756488279032 - ) + self.network.add( + "Bus", + new_bus, + carrier="biomass_solid", + v_nom=1.0, + x=11.342843544333306, + y=48.76756488279032 + ) self.network.buses.at[new_bus, "scn_name"] = "eGon2035" self.network.buses.at[new_bus, "country"] = "DE" - # Add new generator for biomass_carrier with additional attributes - - self.network.add("Generator", - name=f"{new_bus} biomass_solid", - bus=new_bus, - carrier="biomass_solid", - p_nom=10000, - p_nom_extendable=False, - marginal_cost=39.74, - capital_cost=0, - efficiency=1.0 - ) - + # --- Add a fixed-capacity biomass_solid generator as fuel supply proxy on the new bus --- + self.network.add( + "Generator", + name=f"{new_bus} biomass_solid", + bus=new_bus, + carrier="biomass_solid", + p_nom=10000, + p_nom_extendable=False, + marginal_cost=39.74, + capital_cost=0, + efficiency=1.0 + ) self.network.generators.at[f"{new_bus} biomass_solid", "e_nom_max"] = 11305.00 - self.network.generators.at[f"{new_bus} biomass_solid", "scn_name"] = "eGon2035" + print(f"New bus {new_bus} with carrier 'biomass_solid' added successfully.") - print(f"New Bus {new_bus} with carrier biomass_solid added successfully.") - - # Add Links biomass_solid - - # create new link_id + # --- Prepare link creation --- + # Next numeric link id (assumes numeric link indices exist) next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()]) + 1 - # get AC-Bus and central_heat-Bus in Ingolstadt + # Resolve target buses in interest area (AC, central heat, rural heat) buses_ing = self.find_interest_buses() AC_bus_ing = buses_ing[buses_ing.carrier == "AC"].index[0] cH_bus_ing = buses_ing[buses_ing.carrier == "central_heat"].index[0] dH_bus_ing = buses_ing[buses_ing.carrier == "rural_heat"].index[0] - # get AC-Bus_id in Ingolstadt - ac_buses = buses_ing[buses_ing.carrier == "AC"] - ac_bus_ing = ac_buses.index[0] - - # get default attributes from exitsting CHP-Link + # Use an existing gas CHP as template for default operational attributes existing_central_gas_CHP = self.network.links[self.network.links.carrier == "central_gas_CHP"].iloc[0] link_attrs = {attr: existing_central_gas_CHP.get(attr, 0) for attr in default_attrs} + # --- Add extendable biomass CHP links from biomass bus to target buses --- for carrier in carriers: - + # Apply carrier-specific costs and efficiencies link_attrs["capital_cost"] = capital_cost_map.get(carrier, 0) link_attrs["marginal_cost"] = marginal_cost_map.get(carrier, 0) link_attrs["efficiency"] = efficiency_map.get(carrier, 0) + # New unique link name new_index = str(next_link_id) next_link_id += 1 + # Map each carrier to its output bus if carrier in ("central_biomass_solid_CHP", "rural_biomass_solid_CHP"): - bus1 = AC_bus_ing + bus1 = AC_bus_ing # electric output elif carrier == "central_biomass_solid_CHP_heat": - bus1 = cH_bus_ing + bus1 = cH_bus_ing # central heat output elif carrier == "rural_biomass_solid_CHP_heat": - bus1 = dH_bus_ing - - self.network.add("Link", - name=new_index, - bus0=new_bus, - bus1=bus1, - carrier=carrier, - p_nom=0, - p_nom_extendable=True, - **link_attrs) + bus1 = dH_bus_ing # rural heat output + # Add extendable link with zero initial capacity + self.network.add( + "Link", + name=new_index, + bus0=new_bus, + bus1=bus1, + carrier=carrier, + p_nom=0, + p_nom_extendable=True, + **link_attrs + ) self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Neuer Link {new_index} ({carrier}) mit installed p_nom={link_attrs.get('p_nom', 'n/a')} hinzugefügt.") + print(f"New link {new_index} ({carrier}) added with installed p_nom={link_attrs.get('p_nom', 'n/a')}.") + def add_biomass_boiler_extendable(self): """ - Add extendable biomass_solid_boiler to rural_heat bus in the interest area. + Add an extendable biomass solid boiler on the rural heat bus in the interest area. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with ``generators`` and ``buses`` components. + - ``find_interest_buses`` : callable + Helper to resolve the ``rural_heat`` bus in the interest area. + + Returns + ------- + None """ # Carrier name for the generator @@ -4456,11 +4642,28 @@ def add_biomass_boiler_extendable(self): print(f"Biomass boiler {carrier} successfully added at bus {dH_bus_ing}.") + def add_extendable_heat_pumps_to_interest_area(self): """ - Dupliziert zentrale und ländliche Wärmepumpen in der Region. - Setzt investierbare Variante, kopiert 'efficiency'-Zeitreihe, - und übernimmt spezifische Kostenparameter. + Duplicate central and rural heat pumps in the interest area as investable links. + + For each matching heat pump link, the function: + - Sets the existing link's ``p_nom`` to zero (to avoid double counting). + - Adds a new extendable link with specified capital and marginal costs. + - Copies the time series for ``links_t.efficiency`` if available. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with ``links`` and ``links_t.efficiency``. + - ``find_links_connected_to_interest_buses`` : callable + Helper to select links connected to interest-area buses. + + Returns + ------- + None """ heat_pump_carriers = ["central_heat_pump", "rural_heat_pump"] @@ -4512,173 +4715,248 @@ def add_extendable_heat_pumps_to_interest_area(self): self.network.links_t.efficiency[new_index] = self.network.links_t.efficiency[old_index].copy() self.network.links.at[new_index, "scn_name"] = "eGon2035" - print(f"Wärmepumpe {new_index} ({carrier}) erfolgreich dupliziert mit Zeitreihe.") + print(f"Heat pump {new_index} ({carrier}) duplicated as extendable with time series.") def set_battery_parameter_interest_area(self): """ - Setzt p_nom_min = 0 für alle Batterien (storage_units) in der interest area. + Set minimum capacity and capital cost for all battery storage units in the interest area. + + Sets ``p_nom_min`` and ``capital_cost`` for rows in ``storage_units`` where + ``carrier == "battery"`` and the unit's bus lies within the interest area. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``storage_units`` with columns ``bus``, ``carrier``, + ``p_nom_min`` and ``capital_cost``. + - ``find_interest_buses`` : callable + Helper to resolve buses in the interest area. + + Returns + ------- + None """ - # Busse in der Region bestimmen - buses_ingolstadt = find_interest_buses(self) + # Resolve buses in the interest area + buses_ingolstadt = self.find_interest_buses() bus_list = buses_ingolstadt.index.to_list() - # Filtermaske: nur Batterien in der Region - mask = (self.network.storage_units.bus.isin(bus_list)) & (self.network.storage_units.carrier == "battery") + # Filter mask: battery storage units located in the interest area + mask = ( + self.network.storage_units.bus.isin(bus_list) + & (self.network.storage_units.carrier == "battery") + ) + # Target parameters p_nom_min = 18.15 capital_cost = 18744.86095 - # Setze p_nom_min = 0 direkt in der Original-Tabelle + # Apply parameters self.network.storage_units.loc[mask, "p_nom_min"] = p_nom_min - - # capital_cost setzen self.network.storage_units.loc[mask, "capital_cost"] = capital_cost print( - f"Für {mask.sum()} Batterien in der interest area wurde " - f"p_nom_min = {p_nom_min} und capital_cost = {capital_cost} gesetzt." + f"Updated {mask.sum()} battery storage units in the interest area: " + f"p_nom_min = {p_nom_min}, capital_cost = {capital_cost}." ) + def set_battery_and_heat_store_parameters_interest_area(self): """ - Setzt p_nom_min und capital_cost für Batterien (storage_units) sowie - capital_cost für Wärmespeicher (stores) in der interest area. - """ + Set minimum capacity and capital costs for batteries, and capital costs for heat stores, + within the interest area. + + Battery adjustments (``storage_units``): + - Set ``p_nom_min`` and ``capital_cost`` where ``carrier == "battery"`` and the unit's bus + lies within the interest area. + + Heat store adjustments (``stores``): + - Set ``capital_cost`` for carriers ``"rural_heat_store"`` and ``"central_heat_store"`` on buses + within the interest area. - # Busse der interest area bestimmen + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``storage_units`` with columns ``bus``, ``carrier``, ``p_nom_min``, ``capital_cost``, + and ``stores`` with columns ``bus``, ``carrier``, ``capital_cost``. + - ``find_interest_buses`` : callable + Resolves buses belonging to the interest area. + + Returns + ------- + None + """ + # Resolve buses in the interest area buses_ingolstadt = self.find_interest_buses() bus_list = buses_ingolstadt.index.to_list() - # Filtermaske für Batterien + # --- Battery parameters (storage_units) --- + # Filter mask: batteries located on interest-area buses mask_battery = ( - (self.network.storage_units.bus.isin(bus_list)) & - (self.network.storage_units.carrier == "battery") + self.network.storage_units.bus.isin(bus_list) + & (self.network.storage_units.carrier == "battery") ) - # Parameter für Batterien + # Target parameters for batteries p_nom_min_battery = 18.15 capital_cost_battery = 18744.86095 - # Setze Batterie-Parameter + # Apply battery parameters self.network.storage_units.loc[mask_battery, "p_nom_min"] = p_nom_min_battery self.network.storage_units.loc[mask_battery, "capital_cost"] = capital_cost_battery - # Filter für Wärmespeicher in der Region + # --- Heat store parameters (stores) --- + # Filter mask: stores located on interest-area buses mask_stores = self.network.stores.bus.isin(bus_list) - # Selektiere nur relevante Stores + # Select only relevant subset once for carrier filtering below stores_of_interest = self.network.stores.loc[mask_stores] - # Definition capital_cost nach carrier + # Capital costs per heat-store carrier capital_cost_store_values = { "rural_heat_store": 27312.6386, - "central_heat_store": 69.0976 + "central_heat_store": 69.0976, } - # Iteration über carrier und setzen der capital_cost + # Apply store capital costs for each target carrier for carrier, cap_cost in capital_cost_store_values.items(): mask_carrier = stores_of_interest.carrier == carrier - affected = mask_carrier.sum() - self.network.stores.loc[ - mask_stores & mask_carrier, - "capital_cost" - ] = cap_cost + affected = int(mask_carrier.sum()) + self.network.stores.loc[mask_stores & mask_carrier, "capital_cost"] = cap_cost print( - f"Für {affected} Wärmespeicher vom Typ '{carrier}' wurde capital_cost = {cap_cost} gesetzt." + f"Updated {affected} heat stores of type '{carrier}' in the interest area: capital_cost = {cap_cost}." ) - # Zusammenfassung für Batterien + # Summary for batteries print( - f"Für {mask_battery.sum()} Batterien in der interest area wurde " - f"p_nom_min = {p_nom_min_battery} und capital_cost = {capital_cost_battery} gesetzt." + f"Updated {int(mask_battery.sum())} battery storage units in the interest area: " + f"p_nom_min = {p_nom_min_battery}, capital_cost = {capital_cost_battery}." ) def add_waste_CHP_ingolstadt(self): """ - add waste_CHP-Powerplant, located in Ingolstadt + Add a waste-fueled CHP unit (power + heat) located in the interest area (e.g., Ingolstadt). + + The function: + - Creates a new ``waste`` fuel bus with coordinates in Ingolstadt. + - Adds a fixed-capacity ``waste`` generator (fuel supply proxy) on that bus. + - Adds two extendable links: + * ``central_waste_CHP`` -> electric output to the AC bus + * ``central_waste_CHP_heat`` -> thermal output to the central_heat bus + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with ``buses``, ``generators``, and ``links`` components. + - ``find_interest_buses`` : callable + Helper to resolve AC and central_heat buses in the interest area. + + Returns + ------- + None """ - # Add new bus with additional attributes + # --- Create a new waste fuel bus (geo-coordinates set to Ingolstadt) --- new_bus = str(self.network.buses.index.astype(np.int64).max() + 1) - self.network.add("Bus", - new_bus, - carrier="waste", - v_nom=1.0, - x=11.477725514411073, - y=48.76452002953957 - ) + self.network.add( + "Bus", + new_bus, + carrier="waste", + v_nom=1.0, + x=11.477725514411073, + y=48.76452002953957 + ) self.network.buses.at[new_bus, "scn_name"] = "eGon2035" self.network.buses.at[new_bus, "country"] = "DE" - - print(f"Neuer Bus {new_bus} mit carrier = waste hinzugefügt.") - - # Add new generator for waste_carrier with additional attributes - - self.network.add("Generator", - name=f"{new_bus} waste", - bus=new_bus, - carrier="waste", - p_nom=10000, - p_nom_extendable=False, - marginal_cost=12.6, - capital_cost=0, - efficiency=1.0 - ) - + print(f"New bus {new_bus} with carrier 'waste' added.") + + # --- Add a fixed-capacity waste generator as fuel supply proxy --- + self.network.add( + "Generator", + name=f"{new_bus} waste", + bus=new_bus, + carrier="waste", + p_nom=10000, + p_nom_extendable=False, + marginal_cost=12.6, + capital_cost=0, + efficiency=1.0 + ) self.network.generators.at[f"{new_bus} waste", "scn_name"] = "eGon2035" - bus_ingolstadt = find_interest_buses(self) - - # Add Link waste -> electricity - ac_buses = bus_ingolstadt[bus_ingolstadt.carrier == "AC"] - ac_bus = ac_buses.index[0] - # create new link_id - new_link_id = str(self.network.links.index.astype(int).max() + 1) - - self.network.add("Link", - name=new_link_id, - bus0=new_bus, - bus1=ac_bus, - carrier="central_waste_CHP", - p_nom=86.77, - p_nom_min=86.77, # elec_CHP_Power / n_elec - marginal_cost=5.8444, - capital_cost=123893.1786, - p_nom_extendable=True, - efficiency=0.2102 - ) - - self.network.links.at[new_link_id, "scn_name"] = "eGon2035" - - print(f"Neuer Link {new_link_id} mit carrier = central_waste_CHP hinzugefügt.") - - # Add Link waste -> central_heat - # create new link_id - new_link_id = str(self.network.links.index.astype(int).max() + 1) - central_heat_buses = bus_ingolstadt[bus_ingolstadt.carrier == "central_heat"] - central_heat_bus = central_heat_buses.index[0] - - self.network.add("Link", - name=new_link_id, - bus0=new_bus, - bus1=central_heat_bus, - carrier="central_waste_CHP_heat", - #p_nom=59.05, - #p_nom_min=59.05, - marginal_cost=0, - p_nom_extendable=True, - efficiency=0.762 - ) + # Resolve AC and central_heat buses in the interest area + buses_ing = self.find_interest_buses() + ac_bus = buses_ing[buses_ing.carrier == "AC"].index[0] + ch_bus = buses_ing[buses_ing.carrier == "central_heat"].index[0] - self.network.links.at[new_link_id, "scn_name"] = "eGon2035" + # Determine the next numeric link index (robust to non-numeric names) + next_link_id = max([int(i) for i in self.network.links.index if str(i).isdigit()] + [0]) + 1 - print(f"Neuer Link {new_link_id} mit carrier = central_waste_CHP_heat hinzugefügt.") + # --- Add Link: waste -> electricity (AC) --- + elec_link_id = str(next_link_id) + next_link_id += 1 + self.network.add( + "Link", + name=elec_link_id, + bus0=new_bus, + bus1=ac_bus, + carrier="central_waste_CHP", + p_nom=86.77, + p_nom_min=86.77, # elec_CHP_Power / n_elec + marginal_cost=5.8444, + capital_cost=123893.1786, + p_nom_extendable=True, + efficiency=0.2102 + ) + self.network.links.at[elec_link_id, "scn_name"] = "eGon2035" + print(f"New link {elec_link_id} with carrier 'central_waste_CHP' added.") + + # --- Add Link: waste -> central_heat --- + heat_link_id = str(next_link_id) + next_link_id += 1 + self.network.add( + "Link", + name=heat_link_id, + bus0=new_bus, + bus1=ch_bus, + carrier="central_waste_CHP_heat", + # p_nom and p_nom_min can be left free here; link is extendable + marginal_cost=0, + p_nom_extendable=True, + efficiency=0.762 + ) + self.network.links.at[heat_link_id, "scn_name"] = "eGon2035" + print(f"New link {heat_link_id} with carrier 'central_waste_CHP_heat' added.") def adjust_capital_costs(self): """ - Update the capital_cost values of selected PyPSA components - (links, storage_units, stores) globally based on specified carrier types. + Add a waste-fueled CHP unit (power + heat) located in the interest area. + + The function: + - Creates a new ``waste`` fuel bus with coordinates in Ingolstadt. + - Adds a fixed-capacity ``waste`` generator (fuel supply proxy) on that bus. + - Adds two extendable links: + * ``central_waste_CHP`` -> electric output to the AC bus + * ``central_waste_CHP_heat`` -> thermal output to the central_heat bus + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Network with ``buses``, ``generators``, and ``links`` components. + - ``find_interest_buses`` : callable + Helper to resolve AC and central_heat buses in the interest area. + + Returns + ------- + None """ # Define new capital costs for each component and carrier @@ -4718,17 +4996,27 @@ def adjust_capital_costs(self): def print_capital_costs(self): """ - Print a summary of capital_cost values for selected components - (links, storage_units, stores) grouped by carrier. + Print a summary of capital costs for selected network components. + + For each component type (``links``, ``storage_units``, ``stores``), + the function groups by carrier and prints the mean ``capital_cost``. + Returns a dictionary with one DataFrame per component. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``links``, ``storage_units`` and ``stores`` components. Returns ------- - dict of pd.DataFrame - Each key is a component name, each value is a DataFrame with capital_costs per carrier. + dict of pandas.DataFrame + Dictionary with keys ``"links"``, ``"storage_units"`` and ``"stores"`` + (if available in the network). Values are DataFrames with the mean + ``capital_cost`` per carrier, sorted by carrier. """ - import pandas as pd # just in case not yet imported - cost_summary = {} # Links: capital_cost by carrier @@ -4769,7 +5057,24 @@ def print_capital_costs(self): def set_cyclic_constraints(self): """ - Sets cyclic constraints for battery storage units and selected store carriers. + Set cyclic constraints for storage technologies in the interest area. + + The function ensures that: + - Battery storage units (``storage_units`` with carrier == "battery"``) + have ``cyclic_state_of_charge`` set to True. + - Selected store carriers have ``e_cyclic`` set to True. + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``storage_units`` with column ``cyclic_state_of_charge`` + and ``stores`` with column ``e_cyclic``. + + Returns + ------- + None """ # Set cyclic_state_of_charge = True for all batteries in storage_units battery_mask = self.network.storage_units.carrier == "battery" @@ -4785,7 +5090,24 @@ def set_cyclic_constraints(self): def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): """ - Setzt den capital_cost für alle solar_rooftop Generatoren in der Interest Area Ingolstadt. + Update the capital cost of all solar rooftop generators located in the + interest area (e.g., Ingolstadt). + + Parameters + ---------- + self : :class:`Etrago` + Model instance providing: + - ``network`` : pypsa.Network + Must contain ``generators`` with columns ``bus``, ``carrier``, ``capital_cost``. + - ``find_interest_buses`` : callable + Helper to resolve buses in the interest area. + new_capital_cost : float + New capital cost value [EUR/kW] to assign to all rooftop PV generators + in the interest area. + + Returns + ------- + None """ buses_ingolstadt = find_interest_buses(self) bus_list = buses_ingolstadt.index.to_list() @@ -4799,4 +5121,7 @@ def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): return self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost - print(f"✅ capital_cost auf {new_capital_cost:.2f} €/kW für {len(solar_generators)} Solar-Generator(en) gesetzt.") \ No newline at end of file + print( + f"Updated capital_cost to {new_capital_cost:.2f} €/kW " + f"for {len(solar_generators)} solar_rooftop generator(s) in the interest area." + ) \ No newline at end of file From 1d954cef3436329206fcda20e7feb66489fd0d6d Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 7 Oct 2025 17:55:57 +0200 Subject: [PATCH 106/109] Removes constraint "_add_resistive_heater_vollaststunden_constraint_linopy" because it's not needed anymore and remove "update_capital_cost_of_solar_ingolstadt" in utitilities.py --- etrago/appl.py | 4 +- etrago/network.py | 3 -- etrago/tools/constraints.py | 63 +--------------------------- etrago/tools/utilities.py | 46 ++------------------- requirements.txt | 82 +++++++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 109 deletions(-) create mode 100644 requirements.txt diff --git a/etrago/appl.py b/etrago/appl.py index 3629a7707..6cffdf5dc 100644 --- a/etrago/appl.py +++ b/etrago/appl.py @@ -779,8 +779,8 @@ def run_etrago(args, json_path): #etrago.add_biomass_boiler_extendable() - #etrago.set_battery_parameter_interest_area() - etrago.set_battery_and_heat_store_parameters_interest_area() + etrago.set_battery_parameter_interest_area() + #etrago.set_battery_and_heat_store_parameters_interest_area() etrago.set_cyclic_constraints() diff --git a/etrago/network.py b/etrago/network.py index 820e25496..1d2425032 100644 --- a/etrago/network.py +++ b/etrago/network.py @@ -123,7 +123,6 @@ reset_gas_CHP_capacities, add_biogas_CHP_extendable, add_biomass_CHP_extendable, - update_capital_cost_of_solar_ingolstadt, add_biomass_boiler_extendable, adjust_capital_costs, print_capital_costs, @@ -417,8 +416,6 @@ def __init__( add_waste_CHP_ingolstadt = add_waste_CHP_ingolstadt - update_capital_cost_of_solar_ingolstadt = update_capital_cost_of_solar_ingolstadt - reset_gas_CHP_capacities = reset_gas_CHP_capacities add_biogas_CHP_extendable = add_biogas_CHP_extendable diff --git a/etrago/tools/constraints.py b/etrago/tools/constraints.py index d1d524f24..523e7a085 100755 --- a/etrago/tools/constraints.py +++ b/etrago/tools/constraints.py @@ -3847,8 +3847,6 @@ def _add_chp_ratio_constraint_linopy(self, network, snapshots): ------- None """ - import logging - logger = logging.getLogger(__name__) logger.info("🚀 Start writing CHP ratio constraints for all supported CHP types.") chp_techs = { @@ -3906,63 +3904,4 @@ def _add_chp_ratio_constraint_linopy(self, network, snapshots): logger.error(f"❌ Error for {carrier_el}, bus {bus}, snapshot {snapshot}: {e}") continue - logger.info("✅ Finished writing CHP ratio constraints for all supported CHP types.") - - -def _add_resistive_heater_vollaststunden_constraint_linopy(network, snapshots): - """ - Limits the annual full-load hours of the regional electric boiler (resistive heater) - with bus0 = "16" to max. 500 hours. - - Parameters - ---------- - network : pypsa.Network - Network container. - snapshots : pandas.Index - Timesteps to optimize. - - Returns - ------- - None. - """ - - logger.info("✔️ add_resistive_heater_vollaststunden_constraint constraint activated") - - # Filtere den spezifischen Link am Bus 16 - heater_links = network.links[ - (network.links.carrier == "central_resistive_heater") - & (network.links.bus0 == "16") - ] - - if heater_links.empty: - print("Keine passenden Links für den Elektroboiler an Bus 16 gefunden!") - return - - # Summe der Nennleistung (p_nom) in MW - p_nom_sum = heater_links.p_nom.sum() - - # Dispatch-Summe über alle Zeitschritte in MWh - dispatch_sum = 0 - - for snapshot in snapshots: - # Zeitschrittgewichtung in Stunden - timestep_hours = network.snapshot_weightings["objective"].loc[snapshot] - - # Summiere alle Dispatch-Werte dieses Zeitpunkts (MW) - dispatch_sum += ( - get_var(network, "Link", "p").loc[snapshot, heater_links.index].sum() - * timestep_hours - ) - - # Maximal zulässige Energie = Volllaststunden * Nennleistung (MWh) - max_energy_mwh = 500 * p_nom_sum - - # Constraint definieren: Gesamterzeugung <= max. zulässige MWh - define_constraints( - network, - dispatch_sum, - "<=", - max_energy_mwh, - "Link", - f"vollaststunden_limit_bus16", - ) \ No newline at end of file + logger.info("✅ Finished writing CHP ratio constraints for all supported CHP types.") \ No newline at end of file diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 92da1b14d..1a2b2d9b4 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4675,7 +4675,7 @@ def add_extendable_heat_pumps_to_interest_area(self): marginal_cost_map = { "central_heat_pump": 2.4868 - # rural_heat_pump bleibt bei vorhandenem Wert + # rural_heat_pump remains at existing value } connected_links = find_links_connected_to_interest_buses(self) @@ -4717,6 +4717,7 @@ def add_extendable_heat_pumps_to_interest_area(self): self.network.links.at[new_index, "scn_name"] = "eGon2035" print(f"Heat pump {new_index} ({carrier}) duplicated as extendable with time series.") + def set_battery_parameter_interest_area(self): """ Set minimum capacity and capital cost for all battery storage units in the interest area. @@ -4817,7 +4818,7 @@ def set_battery_and_heat_store_parameters_interest_area(self): # Capital costs per heat-store carrier capital_cost_store_values = { - "rural_heat_store": 27312.6386, + "rural_heat_store": 19118.7469, "central_heat_store": 69.0976, } @@ -5085,43 +5086,4 @@ def set_cyclic_constraints(self): store_mask = self.network.stores.carrier.isin(stores_carrier) self.network.stores.loc[store_mask, "e_cyclic"] = True - print("Cyclic constraints set for battery storage_units and defined store carriers.") - - -def update_capital_cost_of_solar_ingolstadt(self, new_capital_cost): - """ - Update the capital cost of all solar rooftop generators located in the - interest area (e.g., Ingolstadt). - - Parameters - ---------- - self : :class:`Etrago` - Model instance providing: - - ``network`` : pypsa.Network - Must contain ``generators`` with columns ``bus``, ``carrier``, ``capital_cost``. - - ``find_interest_buses`` : callable - Helper to resolve buses in the interest area. - new_capital_cost : float - New capital cost value [EUR/kW] to assign to all rooftop PV generators - in the interest area. - - Returns - ------- - None - """ - buses_ingolstadt = find_interest_buses(self) - bus_list = buses_ingolstadt.index.to_list() - - gens = self.network.generators - is_solar_in_ingolstadt = (gens.carrier == "solar_rooftop") & (gens.bus.isin(bus_list)) - solar_generators = gens[is_solar_in_ingolstadt] - - if solar_generators.empty: - print("⚠️ Keine passenden Solar-Generatoren in Ingolstadt gefunden.") - return - - self.network.generators.loc[solar_generators.index, "capital_cost"] = new_capital_cost - print( - f"Updated capital_cost to {new_capital_cost:.2f} €/kW " - f"for {len(solar_generators)} solar_rooftop generator(s) in the interest area." - ) \ No newline at end of file + print("Cyclic constraints set for battery storage_units and defined store carriers.") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..e9a5a1363 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,82 @@ +backports.tarfile==1.2.0 +blosc2==2.7.1 +Bottleneck==1.4.2 +certifi==2025.1.31 +cffi==1.17.1 +cftime==1.6.4.post1 +charset-normalizer==3.4.1 +click==8.1.8 +cloudpickle==3.1.1 +contourpy==1.3.2 +cryptography==44.0.2 +cycler==0.12.1 +dask==2025.3.0 +deprecation==2.1.0 +-e git+ssh://git@github.com/openego/eTraGo.git@1dfba2dac87c227cb7debfb0541036e486e9f8bc#egg=eTraGo +fonttools==4.57.0 +fsspec==2025.3.2 +GeoAlchemy2==0.6.3 +geopandas==1.0.1 +greenlet==3.2.0 +gurobipy==10.0.0 +highspy==1.10.0 +idna==3.10 +importlib_metadata==8.6.1 +jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 +jeepney==0.9.0 +joblib==1.4.2 +keyring==25.6.0 +kiwisolver==1.4.8 +linopy==0.3.2 +locket==1.0.0 +loguru==0.7.3 +matplotlib==3.10.1 +more-itertools==10.6.0 +msgpack==1.1.0 +ndindex==1.9.2 +netCDF4==1.7.2 +networkx==3.4.2 +numexpr==2.10.2 +numpy==1.26.4 +oedialect==0.1.1 +packaging==24.2 +pandas==2.1.4 +partd==1.4.2 +pillow==11.2.1 +pkg_resources==0.0.0 +ply==3.11 +polars==1.27.1 +psycopg2-binary==2.9.10 +py-cpuinfo==9.0.0 +pycparser==2.22 +pyogrio==0.10.0 +Pyomo==6.5.0 +pyparsing==3.2.3 +pyproj==3.7.1 +pypsa==0.26.2 +python-dateutil==2.9.0.post0 +pytz==2025.2 +PyYAML==6.0.2 +requests==2.32.3 +rtree==1.4.0 +saio==0.2.1 +scikit-learn==1.6.1 +scipy==1.15.2 +SecretStorage==3.3.3 +shapely==2.1.0 +six==1.17.0 +SQLAlchemy==1.4.54 +tables==3.10.1 +threadpoolctl==3.6.0 +tilemapbase==0.4.5 +toolz==1.0.0 +tqdm==4.67.1 +tsam==2.3.6 +typing_extensions==4.13.2 +tzdata==2025.2 +urllib3==2.4.0 +validators==0.34.0 +xarray==2023.11.0 +zipp==3.21.0 From ee7823af7554512d5fc5281da812f945dfc6d902 Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 14 Oct 2025 13:11:00 +0200 Subject: [PATCH 107/109] Updates docstring of adjust_capital_costs - function in utilities.py --- etrago/tools/utilities.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/etrago/tools/utilities.py b/etrago/tools/utilities.py index 1a2b2d9b4..1524d3779 100755 --- a/etrago/tools/utilities.py +++ b/etrago/tools/utilities.py @@ -4937,23 +4937,20 @@ def add_waste_CHP_ingolstadt(self): def adjust_capital_costs(self): """ - Add a waste-fueled CHP unit (power + heat) located in the interest area. + Update the capital cost parameters for selected technologies in the network. - The function: - - Creates a new ``waste`` fuel bus with coordinates in Ingolstadt. - - Adds a fixed-capacity ``waste`` generator (fuel supply proxy) on that bus. - - Adds two extendable links: - * ``central_waste_CHP`` -> electric output to the AC bus - * ``central_waste_CHP_heat`` -> thermal output to the central_heat bus + The function overwrites the ``capital_cost`` values for: + - Links: power-to-gas and gas-to-power conversion technologies + (e.g. H₂ electrolysis, methanation, reforming) + - Storage units: battery storage systems + - Stores: central and rural heat storages Parameters ---------- self : :class:`Etrago` Model instance providing: - ``network`` : pypsa.Network - Network with ``buses``, ``generators``, and ``links`` components. - - ``find_interest_buses`` : callable - Helper to resolve AC and central_heat buses in the interest area. + Must contain ``links``, ``storage_units``, and ``stores`` components. Returns ------- From a997e19f9bf40046692954dfcaddaefaa07322da Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 14 Oct 2025 15:33:27 +0200 Subject: [PATCH 108/109] Rebases plot.py market_optimization.py and io.py to origin/features/#800-clustering-with-focus --- etrago/analyze/plot.py | 19 ----- etrago/execute/market_optimization.py | 2 +- etrago/tools/io.py | 112 ++++++++++---------------- 3 files changed, 45 insertions(+), 88 deletions(-) diff --git a/etrago/analyze/plot.py b/etrago/analyze/plot.py index d64e9e4eb..d8616acce 100644 --- a/etrago/analyze/plot.py +++ b/etrago/analyze/plot.py @@ -51,7 +51,6 @@ import tilemapbase from etrago.execute import import_gen_from_links - from etrago.tools.utilities import find_buses_area __copyright__ = ( "Flensburg University of Applied Sciences, " @@ -2477,8 +2476,6 @@ def plot_grid( * 'q_flow_max': maximal reactive flows * 'dlr': energy above nominal capacity * 'grey': plot all lines and DC links grey colored - * 'interest_area': plot all AC buses inside the interest area larger - than buses outside the interest area bus_sizes : float, optional Size of buses. The default is 0.001. @@ -2912,22 +2909,6 @@ def plot_grid( bus_sizes[bus_sizes != "AC"] = 0 bus_sizes[bus_sizes == "AC"] = 1 * bus_scaling bus_scaling = bus_sizes - elif bus_colors == "interest_area": - bus_scaling = bus_sizes - # only plot AC buses - bus_sizes = pd.Series( - data=network.buses.carrier, index=network.buses.index - ) - bus_sizes[bus_sizes != "AC"] = 0 - bus_sizes[bus_sizes == "AC"] = 1 * bus_scaling - # only plot buses inside interest area - buses_interest_area = find_buses_area(self, "AC") - buses_outside = [ - _ for _ in bus_sizes.index if _ not in buses_interest_area - ] - bus_sizes.loc[buses_outside] = bus_sizes.loc[buses_outside] * 0.3 - bus_scaling = bus_sizes - bus_colors = coloring()["AC"] else: logger.warning("bus_color {} undefined".format(bus_colors)) diff --git a/etrago/execute/market_optimization.py b/etrago/execute/market_optimization.py index 08efbfc2c..d9d9dfe22 100644 --- a/etrago/execute/market_optimization.py +++ b/etrago/execute/market_optimization.py @@ -319,7 +319,7 @@ def build_market_model(self, unit_commitment=False): """ # use existing preprocessing to get only the electricity system - net, _, _, busmap_foreign, _, _ = preprocessing( + net, weight, n_clusters, busmap_foreign = preprocessing( self, apply_on="market_model" ) diff --git a/etrago/tools/io.py b/etrago/tools/io.py index 7024af9e6..30054c1e5 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -48,6 +48,7 @@ __license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)" __author__ = "ulfmueller, mariusves, pieterhexen, ClaraBuettner" +from importlib import import_module import os import numpy as np @@ -111,14 +112,12 @@ def __init__( start_snapshot=1, end_snapshot=20, temp_id=1, - scenario_extension=False, **kwargs, ): self.scn_name = scn_name self.start_snapshot = start_snapshot self.end_snapshot = end_snapshot self.temp_id = temp_id - self.scenario_extension = scenario_extension super().__init__(engine, session, **kwargs) @@ -208,14 +207,6 @@ def fetch_by_relname(self, name): egon_etrago_transformer, ) - if self.scenario_extension: - from saio.grid import ( # noqa: F401, F811 - egon_etrago_extension_bus as egon_etrago_bus, - egon_etrago_extension_line as egon_etrago_line, - egon_etrago_extension_link as egon_etrago_link, - egon_etrago_extension_transformer as egon_etrago_transformer, - ) - index = f"{name.lower()}_id" if name == "Transformer": @@ -805,21 +796,31 @@ def extension(self, **kwargs): """ if self.args["scn_extension"] is not None: + if self.args["gridversion"] is None: + ormcls_prefix = "EgoGridPfHvExtension" + else: + ormcls_prefix = "EgoPfHvExtension" + for i in range(len(self.args["scn_extension"])): scn_extension = self.args["scn_extension"][i] # Adding overlay-network to existing network scenario = NetworkScenario( - self.engine, self.session, version=self.args["gridversion"], + prefix=ormcls_prefix, + method=kwargs.get("method", "lopf"), start_snapshot=self.args["start_snapshot"], end_snapshot=self.args["end_snapshot"], - scn_name=scn_extension, - scenario_extension=True, + scn_name="extension_" + scn_extension, ) self.network = scenario.build_network(self.network) + # Allow lossless links to conduct bidirectional + self.network.links.loc[ + self.network.links.efficiency == 1.0, "p_min_pu" + ] = -1 + def decommissioning(self, **kwargs): """ @@ -846,65 +847,40 @@ def decommissioning(self, **kwargs): Network container including decommissioning """ - if self.args["scn_extension"] is not None: - for i in range(len(self.args["scn_extension"])): - scn_decom = self.args["scn_extension"][i] - - df_decommisionning = pd.read_sql( - f""" - SELECT * FROM - grid.egon_etrago_extension_line - WHERE scn_name = 'decomissioining_{scn_decom}' - """, - self.session.bind, - ) - - self.network.mremove( - "Line", - df_decommisionning.line_id.astype(str).values, + if self.args["scn_decommissioning"] is not None: + if self.args["gridversion"] is None: + ormclass = getattr( + import_module("egoio.db_tables.model_draft"), + "EgoGridPfHvExtensionLine", ) - - # buses only between removed lines - candidates = pd.concat( - [df_decommisionning.bus0, df_decommisionning.bus1] + else: + ormclass = getattr( + import_module("egoio.db_tables.grid"), "EgoPfHvExtensionLine" ) - candidates.drop_duplicates(inplace=True, keep="first") - candidates = candidates.astype(str) - # Drop buses that are connecting other lines - candidates = candidates[~candidates.isin(self.network.lines.bus0)] - candidates = candidates[~candidates.isin(self.network.lines.bus1)] - - # Drop buses that are connection other DC-lines - candidates = candidates[~candidates.isin(self.dc_lines().bus0)] - candidates = candidates[~candidates.isin(self.dc_lines().bus1)] - - drop_buses = self.network.buses[ - (self.network.buses.index.isin(candidates.values)) - & (self.network.buses.country == "DE") - ] - - drop_links = self.network.links[ - (self.network.links.bus0.isin(drop_buses.index)) - | (self.network.links.bus1.isin(drop_buses.index)) - ].index - - drop_trafos = self.network.transformers[ - (self.network.transformers.bus0.isin(drop_buses.index)) - | (self.network.transformers.bus1.isin(drop_buses.index)) - ].index - - drop_su = self.network.storage_units[ - self.network.storage_units.bus.isin(candidates.values) - ].index - - self.network.mremove("StorageUnit", drop_su) - - self.network.mremove("Transformer", drop_trafos) - - self.network.mremove("Link", drop_links) + query = self.session.query(ormclass).filter( + ormclass.scn_name + == "decommissioning_" + self.args["scn_decommissioning"] + ) - self.network.mremove("Bus", drop_buses.index) + df_decommisionning = pd.read_sql( + query.statement, self.session.bind, index_col="line_id" + ) + df_decommisionning.index = df_decommisionning.index.astype(str) + + for idx, row in self.network.lines.iterrows(): + if (row["s_nom_min"] != 0) & ( + row["scn_name"] + == "extension_" + self.args["scn_decommissioning"] + ): + self.network.lines.s_nom_min[ + self.network.lines.index == idx + ] = self.network.lines.s_nom_min + + # Drop decommissioning-lines from existing network + self.network.lines = self.network.lines[ + ~self.network.lines.index.isin(df_decommisionning.index) + ] def distance(x0, x1, y0, y1): From ef7692d65f6c0b146f31b1163011e1aba05cddab Mon Sep 17 00:00:00 2001 From: Norman Zielke Date: Tue, 14 Oct 2025 15:35:26 +0200 Subject: [PATCH 109/109] Rebases plot.py market_optimization.py and io.py to origin/features/#800-clustering-with-focus 2 --- etrago/tools/io.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etrago/tools/io.py b/etrago/tools/io.py index 30054c1e5..579331aa9 100644 --- a/etrago/tools/io.py +++ b/etrago/tools/io.py @@ -870,12 +870,12 @@ def decommissioning(self, **kwargs): for idx, row in self.network.lines.iterrows(): if (row["s_nom_min"] != 0) & ( - row["scn_name"] - == "extension_" + self.args["scn_decommissioning"] + row["scn_name"] + == "extension_" + self.args["scn_decommissioning"] ): self.network.lines.s_nom_min[ self.network.lines.index == idx - ] = self.network.lines.s_nom_min + ] = self.network.lines.s_nom_min # Drop decommissioning-lines from existing network self.network.lines = self.network.lines[