From d4b61ff5f73eba8c0f2542b6890f5c6ad5190b91 Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Wed, 5 Feb 2025 11:11:12 +0100 Subject: [PATCH 1/5] Add new data import functionality for electromobility and enhance data processing --- edisgo/edisgo.py | 13 +++- edisgo/io/electromobility_import.py | 92 ++++++++++++++++++++++++++++- edisgo/io/powermodels_io.py | 52 ++++++++-------- edisgo/network/components.py | 5 +- 4 files changed, 133 insertions(+), 29 deletions(-) diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 4fda72aa2..4e39f2fa2 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -38,6 +38,7 @@ import_electromobility_from_dir, import_electromobility_from_oedb, integrate_charging_parks, + import_electromobility_from_new_data, ) from edisgo.io.heat_pump_import import oedb as import_heat_pumps_oedb from edisgo.io.storage_import import home_batteries_oedb @@ -1922,8 +1923,10 @@ def import_electromobility( engine: Engine = None, charging_processes_dir: PurePath | str = None, potential_charging_points_dir: PurePath | str = None, + data_dir: PurePath | str = None, import_electromobility_data_kwds=None, allocate_charging_demand_kwds=None, + **kwargs, ): """ Imports electromobility data and integrates charging points into grid. @@ -2048,6 +2051,11 @@ def import_electromobility( potential_charging_points_dir, **import_electromobility_data_kwds, ) + elif data_source == "new_data": + import_electromobility_from_new_data( + self, + data_dir, + **import_electromobility_data_kwds) else: raise ValueError( "Invalid input for parameter 'data_source'. Possible options are " @@ -2057,8 +2065,9 @@ def import_electromobility( if allocate_charging_demand_kwds is None: allocate_charging_demand_kwds = {} - distribute_charging_demand(self, **allocate_charging_demand_kwds) - + if data_source != "new_data": + distribute_charging_demand(self, **allocate_charging_demand_kwds) + integrate_charging_parks(self) def apply_charging_strategy(self, strategy="dumb", **kwargs): diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py index 83dc68af4..94496a075 100644 --- a/edisgo/io/electromobility_import.py +++ b/edisgo/io/electromobility_import.py @@ -3,13 +3,15 @@ import json import logging import os +import io from collections import Counter from pathlib import Path, PurePath from typing import TYPE_CHECKING - +import requests import numpy as np import pandas as pd +import zipfile from numpy.random import default_rng from sklearn import preprocessing @@ -101,6 +103,7 @@ PRIVATE_DESTINATIONS = { "0_work": "work", "6_home": "home", + "home_apartment": "home", } @@ -476,7 +479,7 @@ def assure_minimum_potential_charging_parks( potential_charging_parks_gdf = potential_charging_parks_gdf.sort_values( by=["use_case", "ags", "user_centric_weight"], ascending=[True, True, False] ).reset_index(drop=True) - + potential_charging_parks_gdf['ags'] = potential_charging_parks_gdf['ags'].fillna(0) # in case of polygons use the centroid as potential charging parks point # and set crs to match edisgo object return ( @@ -1145,6 +1148,90 @@ def integrate_charging_parks(edisgo_obj): index=charging_park_ids, ) +def get_ags_from_geometry(gdf): + """ + Get the AGS from a given geometry. + + Parameters + ---------- + geometry : :shapely:`shapely.geometry` + Geometry object + + Returns + ------- + :obj:`int` + AGS + + """ + url = "https://daten.gdz.bkg.bund.de/produkte/vg/vg250_ebenen_0101/aktuell/vg250_01-01.utm32s.gpkg.ebenen.zip" + r = requests.get(url) + z = zipfile.ZipFile(io.BytesIO(r.content)) + z.extractall("/tmp/vg250_data") + vg250 = gpd.read_file("/tmp/vg250_data/vg250_01-01.utm32s.gpkg.ebenen/vg250_ebenen_0101/DE_VG250.gpkg", layer="vg250_gem") + vg250 = vg250.to_crs(gdf.crs) + gdf = gdf.to_crs(vg250.crs) + gdf = gpd.sjoin(gdf, vg250[['geometry', 'AGS']], how="left", predicate="intersects") + return gdf + + + + +def import_electromobility_from_new_data( + edisgo_obj: EDisGo, + data_dir: str, + **kwargs, +): + """ + """ + csv_files = [f for f in os.listdir(data_dir) if f.endswith('.csv')] + gpkg_files = [f for f in os.listdir(data_dir) if f.endswith('.gpkg')] + + # Load CSV files into a dictionary + # csv_data = {f: pd.read_csv(os.path.join(data_dir, f)) for f in csv_files} + + # Load GPKG files into a dictionary + gpkg_data = {f: gpd.read_file(os.path.join(data_dir, f)) for f in gpkg_files} + charging_processes_df = [gdf for key, gdf in gpkg_data.items() if 'charging-events' in key] + charging_processes_gdf = gpd.GeoDataFrame(pd.concat(charging_processes_df, ignore_index=True)) + charging_processes_gdf = get_ags_from_geometry(charging_processes_gdf) + column_mapping = { + 'energy': 'chargingdemand_kWh', + 'use_case': 'use_case', + 'id': 'car_id', + 'station_charging_capacity': 'nominal_charging_capacity_kW', + 'assigned_location': 'ags', + 'charging_use_case': 'destination', + 'assigned_location': 'charging_park_id', + } + + + charging_processes_gdf = charging_processes_gdf.rename(columns=column_mapping) + charging_processes_gdf['charging_point_id'] = charging_processes_gdf.charging_park_id + edisgo_obj.electromobility.charging_processes_df = charging_processes_gdf + + charging_parks_gdfs = [] + for key, gdf in gpkg_data.items(): + if 'charging-locations' in key: + # Extrahiere den use_case aus dem Dateinamen + use_case = key.split('_')[1] + # Füge die Spalte 'use_case' hinzu + gdf['use_case'] = use_case + charging_parks_gdfs.append(gdf) + charging_parks_gdf = gpd.GeoDataFrame(pd.concat(charging_parks_gdfs, ignore_index=True)) + charging_parks_gdf.geometry = charging_parks_gdf.geometry.centroid + mapping_charging_parks = { + 'probability': 'user_centric_weight', + 'required_points': 'ags', + 'use_case': 'use_case', + 'charging_park_id': 'id', + } + charging_parks_gdf = charging_parks_gdf.rename(columns=mapping_charging_parks) + edisgo_obj.electromobility.potential_charging_parks_gdf = assure_minimum_potential_charging_parks( + edisgo_obj=edisgo_obj, + potential_charging_parks_gdf=charging_parks_gdf, + ) + # edisgo_obj.electromobility.potential_charging_parks = edisgo_obj.electromobility.potential_charging_parks_gdf + def import_electromobility_from_oedb( edisgo_obj: EDisGo, @@ -1235,6 +1322,7 @@ def simbev_config_from_oedb( def potential_charging_parks_from_oedb( edisgo_obj: EDisGo, engine: Engine, + **kwargs, ): """ Gets :attr:`~.network.electromobility.Electromobility.potential_charging_parks_gdf` diff --git a/edisgo/io/powermodels_io.py b/edisgo/io/powermodels_io.py index 541e01ffc..e8d384253 100644 --- a/edisgo/io/powermodels_io.py +++ b/edisgo/io/powermodels_io.py @@ -568,6 +568,9 @@ def _build_bus(psa_net, edisgo_obj, pm, flexible_storage_units): v_max = [min(val, 1.1) for val in psa_net.buses["v_mag_pu_max"].values] v_min = [max(val, 0.9) for val in psa_net.buses["v_mag_pu_min"].values] for bus_i in np.arange(len(psa_net.buses.index)): + control_value = psa_net.buses["control"].iloc[bus_i] # updated + v_max_value = psa_net.buses["v_mag_pu_max"].iloc[bus_i] # updated + v_min_value = psa_net.buses["v_mag_pu_min"].iloc[bus_i] # updated pm["bus"][str(bus_i + 1)] = { "index": bus_i + 1, "bus_i": bus_i + 1, @@ -579,8 +582,8 @@ def _build_bus(psa_net, edisgo_obj, pm, flexible_storage_units): "vm": 1, "storage": False, "name": psa_net.buses.index[bus_i], - "base_kv": psa_net.buses.v_nom[bus_i], - "grid_level": grid_level[psa_net.buses.v_nom[bus_i]], + "base_kv": psa_net.buses.v_nom.iloc[bus_i], + "grid_level": grid_level[psa_net.buses.v_nom.iloc[bus_i]], } # add virtual busses for storage units for stor_i in np.arange(len(flexible_storage_units)): @@ -664,19 +667,19 @@ def _build_gen(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): idx_bus = _mapping( psa_net, edisgo_obj, - gen.bus[gen_i], + gen.bus.iloc[gen_i], flexible_storage_units=flexible_storage_units, ) pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "generator") q = [ - sign * np.tan(np.arccos(pf)) * gen.p_nom[gen_i], - sign * np.tan(np.arccos(pf)) * gen.p_nom_min[gen_i], + sign * np.tan(np.arccos(pf)) * gen.p_nom.iloc[gen_i], + sign * np.tan(np.arccos(pf)) * gen.p_nom_min.iloc[gen_i], ] pm[text][str(gen_i + 1)] = { "pg": psa_net.generators_t.p_set[gen.index[gen_i]][0] / s_base, "qg": psa_net.generators_t.q_set[gen.index[gen_i]][0] / s_base, - "pmax": gen.p_nom[gen_i].round(20) / s_base, - "pmin": gen.p_nom_min[gen_i].round(20) / s_base, + "pmax": gen.p_nom.iloc[gen_i].round(20) / s_base, + "pmin": gen.p_nom_min.iloc[gen_i].round(20) / s_base, "qmax": max(q).round(20) / s_base, "qmin": min(q).round(20) / s_base, "P": 0, @@ -684,7 +687,7 @@ def _build_gen(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): "vg": 1, "pf": pf, "sign": sign, - "mbase": gen.p_nom[gen_i] / s_base, + "mbase": gen.p_nom.iloc[gen_i] / s_base, "gen_bus": idx_bus, "gen_status": 1, "name": gen.index[gen_i], @@ -793,13 +796,13 @@ def _build_branch(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): idx_f_bus = _mapping( psa_net, edisgo_obj, - branches.bus0[branch_i], + branches.bus0.iloc[branch_i], flexible_storage_units=flexible_storage_units, ) idx_t_bus = _mapping( psa_net, edisgo_obj, - branches.bus1[branch_i], + branches.bus1.iloc[branch_i], flexible_storage_units=flexible_storage_units, ) pm["branch"][str(branch_i + 1)] = { @@ -820,11 +823,11 @@ def _build_branch(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): "rate_c": 250 / s_base, "angmin": -np.pi / 6, "angmax": np.pi / 6, - "transformer": bool(transformer[branch_i]), + "transformer": bool(transformer.iloc[branch_i]), "storage": False, - "tap": tap[branch_i], - "length": branches.length.fillna(1)[branch_i].round(20), - "cost": branches.capital_cost[branch_i].round(20), + "tap": tap.iloc[branch_i], + "length": branches.length.fillna(1).iloc[branch_i].round(20), + "cost": branches.capital_cost.iloc[branch_i].round(20), "storage_pf": 0, "index": branch_i + 1, } @@ -912,7 +915,7 @@ def _build_load( idx_bus = _mapping( psa_net, edisgo_obj, - loads_df.bus[load_i], + loads_df.bus.iloc[load_i], flexible_storage_units=flexible_storage_units, ) if ( @@ -935,11 +938,14 @@ def _build_load( "be set for conventional load.".format(loads_df.index[load_i]) ) pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "conventional_load") - p_d = psa_net.loads_t.p_set[loads_df.index[load_i]] + try: + p_d = psa_net.loads_t.p_set[loads_df.index[load_i]] + except Exception as e: + print(e) q_d = psa_net.loads_t.q_set[loads_df.index[load_i]] pm["load"][str(load_i + 1)] = { - "pd": p_d[0].round(20) / s_base, - "qd": q_d[0].round(20) / s_base, + "pd": p_d.iloc[0].round(20) / s_base, + "qd": q_d.iloc[0].round(20) / s_base, "load_bus": idx_bus, "status": True, "pf": pf, @@ -1145,7 +1151,7 @@ def _build_electromobility(edisgo_obj, psa_net, pm, s_base, flexible_cps): ] emob_df = psa_net.loads.loc[flexible_cps] for cp_i in np.arange(len(emob_df.index)): - idx_bus = _mapping(psa_net, edisgo_obj, emob_df.bus[cp_i]) + idx_bus = _mapping(psa_net, edisgo_obj, emob_df.bus.iloc[cp_i]) # retrieve power factor and sign from config try: eta = edisgo_obj.electromobility.simbev_config_df.eta_cp.values[0] @@ -1216,7 +1222,7 @@ def _build_heatpump(psa_net, pm, edisgo_obj, s_base, flexible_hps): ) ) for hp_i in np.arange(len(heat_df.index)): - idx_bus = _mapping(psa_net, edisgo_obj, heat_df.bus[hp_i]) + idx_bus = _mapping(psa_net, edisgo_obj, heat_df.bus.iloc[hp_i]) # retrieve power factor and sign from config pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "heat_pump") q = sign * np.tan(np.arccos(pf)) * heat_df.p_set[hp_i] @@ -1325,7 +1331,7 @@ def _build_heat_storage(psa_net, pm, edisgo_obj, s_base, flexible_hps, opf_versi heat_storage_df = heat_storage_df.loc[flexible_hps] for stor_i in np.arange(len(flexible_hps)): idx_bus = _mapping( - psa_net, edisgo_obj, psa_net.loads.loc[flexible_hps].bus[stor_i] + psa_net, edisgo_obj, psa_net.loads.loc[flexible_hps].bus.iloc[stor_i] ) if ( edisgo_obj.topology.loads_df.loc[heat_storage_df.index[stor_i]].sector @@ -1444,7 +1450,7 @@ def _build_dsm(edisgo_obj, psa_net, pm, s_base, flexible_loads): ] dsm_df = psa_net.loads.loc[flexible_loads] for dsm_i in np.arange(len(dsm_df.index)): - idx_bus = _mapping(psa_net, edisgo_obj, dsm_df.bus[dsm_i]) + idx_bus = _mapping(psa_net, edisgo_obj, dsm_df.bus.iloc[dsm_i]) # retrieve power factor and sign from config pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "conventional_load") p_max = edisgo_obj.dsm.p_max[dsm_df.index[dsm_i]] @@ -1701,7 +1707,7 @@ def _build_component_timeseries( Flexibilities that should be considered in the optimization. hv_flex_dict : dict Dictionary containing time series of HV requirement for each flexibility - retrieved from overlying grid component of edisgo object. + retrieved from overlying_grid component of edisgo object. """ pm_comp = dict() solar_gens = edisgo_obj.topology.generators_df.index[ diff --git a/edisgo/network/components.py b/edisgo/network/components.py index d0666bf2d..9587bd9ea 100644 --- a/edisgo/network/components.py +++ b/edisgo/network/components.py @@ -850,13 +850,14 @@ def designated_charging_point_capacity(self): Total gross designated charging park capacity """ + df_without_geometry = self.charging_processes_df[["charging_point_id", "nominal_charging_capacity_kW"]] return ( - self.charging_processes_df.groupby("charging_point_id") + df_without_geometry.groupby("charging_point_id") .max() .nominal_charging_capacity_kW.sum() / self._edisgo_obj.electromobility.eta_charging_points ) - + @property def user_centric_weight(self): """ From e8a13a9e4c8b76f3191b56c37a78edd5d95b8f15 Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Wed, 5 Feb 2025 11:11:12 +0100 Subject: [PATCH 2/5] Add new data import functionality for electromobility and enhance data processing --- edisgo/edisgo.py | 13 +++- edisgo/io/electromobility_import.py | 92 ++++++++++++++++++++++++++++- edisgo/io/powermodels_io.py | 52 ++++++++-------- edisgo/network/components.py | 5 +- 4 files changed, 133 insertions(+), 29 deletions(-) diff --git a/edisgo/edisgo.py b/edisgo/edisgo.py index 4fda72aa2..4e39f2fa2 100755 --- a/edisgo/edisgo.py +++ b/edisgo/edisgo.py @@ -38,6 +38,7 @@ import_electromobility_from_dir, import_electromobility_from_oedb, integrate_charging_parks, + import_electromobility_from_new_data, ) from edisgo.io.heat_pump_import import oedb as import_heat_pumps_oedb from edisgo.io.storage_import import home_batteries_oedb @@ -1922,8 +1923,10 @@ def import_electromobility( engine: Engine = None, charging_processes_dir: PurePath | str = None, potential_charging_points_dir: PurePath | str = None, + data_dir: PurePath | str = None, import_electromobility_data_kwds=None, allocate_charging_demand_kwds=None, + **kwargs, ): """ Imports electromobility data and integrates charging points into grid. @@ -2048,6 +2051,11 @@ def import_electromobility( potential_charging_points_dir, **import_electromobility_data_kwds, ) + elif data_source == "new_data": + import_electromobility_from_new_data( + self, + data_dir, + **import_electromobility_data_kwds) else: raise ValueError( "Invalid input for parameter 'data_source'. Possible options are " @@ -2057,8 +2065,9 @@ def import_electromobility( if allocate_charging_demand_kwds is None: allocate_charging_demand_kwds = {} - distribute_charging_demand(self, **allocate_charging_demand_kwds) - + if data_source != "new_data": + distribute_charging_demand(self, **allocate_charging_demand_kwds) + integrate_charging_parks(self) def apply_charging_strategy(self, strategy="dumb", **kwargs): diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py index 83dc68af4..94496a075 100644 --- a/edisgo/io/electromobility_import.py +++ b/edisgo/io/electromobility_import.py @@ -3,13 +3,15 @@ import json import logging import os +import io from collections import Counter from pathlib import Path, PurePath from typing import TYPE_CHECKING - +import requests import numpy as np import pandas as pd +import zipfile from numpy.random import default_rng from sklearn import preprocessing @@ -101,6 +103,7 @@ PRIVATE_DESTINATIONS = { "0_work": "work", "6_home": "home", + "home_apartment": "home", } @@ -476,7 +479,7 @@ def assure_minimum_potential_charging_parks( potential_charging_parks_gdf = potential_charging_parks_gdf.sort_values( by=["use_case", "ags", "user_centric_weight"], ascending=[True, True, False] ).reset_index(drop=True) - + potential_charging_parks_gdf['ags'] = potential_charging_parks_gdf['ags'].fillna(0) # in case of polygons use the centroid as potential charging parks point # and set crs to match edisgo object return ( @@ -1145,6 +1148,90 @@ def integrate_charging_parks(edisgo_obj): index=charging_park_ids, ) +def get_ags_from_geometry(gdf): + """ + Get the AGS from a given geometry. + + Parameters + ---------- + geometry : :shapely:`shapely.geometry` + Geometry object + + Returns + ------- + :obj:`int` + AGS + + """ + url = "https://daten.gdz.bkg.bund.de/produkte/vg/vg250_ebenen_0101/aktuell/vg250_01-01.utm32s.gpkg.ebenen.zip" + r = requests.get(url) + z = zipfile.ZipFile(io.BytesIO(r.content)) + z.extractall("/tmp/vg250_data") + vg250 = gpd.read_file("/tmp/vg250_data/vg250_01-01.utm32s.gpkg.ebenen/vg250_ebenen_0101/DE_VG250.gpkg", layer="vg250_gem") + vg250 = vg250.to_crs(gdf.crs) + gdf = gdf.to_crs(vg250.crs) + gdf = gpd.sjoin(gdf, vg250[['geometry', 'AGS']], how="left", predicate="intersects") + return gdf + + + + +def import_electromobility_from_new_data( + edisgo_obj: EDisGo, + data_dir: str, + **kwargs, +): + """ + """ + csv_files = [f for f in os.listdir(data_dir) if f.endswith('.csv')] + gpkg_files = [f for f in os.listdir(data_dir) if f.endswith('.gpkg')] + + # Load CSV files into a dictionary + # csv_data = {f: pd.read_csv(os.path.join(data_dir, f)) for f in csv_files} + + # Load GPKG files into a dictionary + gpkg_data = {f: gpd.read_file(os.path.join(data_dir, f)) for f in gpkg_files} + charging_processes_df = [gdf for key, gdf in gpkg_data.items() if 'charging-events' in key] + charging_processes_gdf = gpd.GeoDataFrame(pd.concat(charging_processes_df, ignore_index=True)) + charging_processes_gdf = get_ags_from_geometry(charging_processes_gdf) + column_mapping = { + 'energy': 'chargingdemand_kWh', + 'use_case': 'use_case', + 'id': 'car_id', + 'station_charging_capacity': 'nominal_charging_capacity_kW', + 'assigned_location': 'ags', + 'charging_use_case': 'destination', + 'assigned_location': 'charging_park_id', + } + + + charging_processes_gdf = charging_processes_gdf.rename(columns=column_mapping) + charging_processes_gdf['charging_point_id'] = charging_processes_gdf.charging_park_id + edisgo_obj.electromobility.charging_processes_df = charging_processes_gdf + + charging_parks_gdfs = [] + for key, gdf in gpkg_data.items(): + if 'charging-locations' in key: + # Extrahiere den use_case aus dem Dateinamen + use_case = key.split('_')[1] + # Füge die Spalte 'use_case' hinzu + gdf['use_case'] = use_case + charging_parks_gdfs.append(gdf) + charging_parks_gdf = gpd.GeoDataFrame(pd.concat(charging_parks_gdfs, ignore_index=True)) + charging_parks_gdf.geometry = charging_parks_gdf.geometry.centroid + mapping_charging_parks = { + 'probability': 'user_centric_weight', + 'required_points': 'ags', + 'use_case': 'use_case', + 'charging_park_id': 'id', + } + charging_parks_gdf = charging_parks_gdf.rename(columns=mapping_charging_parks) + edisgo_obj.electromobility.potential_charging_parks_gdf = assure_minimum_potential_charging_parks( + edisgo_obj=edisgo_obj, + potential_charging_parks_gdf=charging_parks_gdf, + ) + # edisgo_obj.electromobility.potential_charging_parks = edisgo_obj.electromobility.potential_charging_parks_gdf + def import_electromobility_from_oedb( edisgo_obj: EDisGo, @@ -1235,6 +1322,7 @@ def simbev_config_from_oedb( def potential_charging_parks_from_oedb( edisgo_obj: EDisGo, engine: Engine, + **kwargs, ): """ Gets :attr:`~.network.electromobility.Electromobility.potential_charging_parks_gdf` diff --git a/edisgo/io/powermodels_io.py b/edisgo/io/powermodels_io.py index 541e01ffc..e8d384253 100644 --- a/edisgo/io/powermodels_io.py +++ b/edisgo/io/powermodels_io.py @@ -568,6 +568,9 @@ def _build_bus(psa_net, edisgo_obj, pm, flexible_storage_units): v_max = [min(val, 1.1) for val in psa_net.buses["v_mag_pu_max"].values] v_min = [max(val, 0.9) for val in psa_net.buses["v_mag_pu_min"].values] for bus_i in np.arange(len(psa_net.buses.index)): + control_value = psa_net.buses["control"].iloc[bus_i] # updated + v_max_value = psa_net.buses["v_mag_pu_max"].iloc[bus_i] # updated + v_min_value = psa_net.buses["v_mag_pu_min"].iloc[bus_i] # updated pm["bus"][str(bus_i + 1)] = { "index": bus_i + 1, "bus_i": bus_i + 1, @@ -579,8 +582,8 @@ def _build_bus(psa_net, edisgo_obj, pm, flexible_storage_units): "vm": 1, "storage": False, "name": psa_net.buses.index[bus_i], - "base_kv": psa_net.buses.v_nom[bus_i], - "grid_level": grid_level[psa_net.buses.v_nom[bus_i]], + "base_kv": psa_net.buses.v_nom.iloc[bus_i], + "grid_level": grid_level[psa_net.buses.v_nom.iloc[bus_i]], } # add virtual busses for storage units for stor_i in np.arange(len(flexible_storage_units)): @@ -664,19 +667,19 @@ def _build_gen(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): idx_bus = _mapping( psa_net, edisgo_obj, - gen.bus[gen_i], + gen.bus.iloc[gen_i], flexible_storage_units=flexible_storage_units, ) pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "generator") q = [ - sign * np.tan(np.arccos(pf)) * gen.p_nom[gen_i], - sign * np.tan(np.arccos(pf)) * gen.p_nom_min[gen_i], + sign * np.tan(np.arccos(pf)) * gen.p_nom.iloc[gen_i], + sign * np.tan(np.arccos(pf)) * gen.p_nom_min.iloc[gen_i], ] pm[text][str(gen_i + 1)] = { "pg": psa_net.generators_t.p_set[gen.index[gen_i]][0] / s_base, "qg": psa_net.generators_t.q_set[gen.index[gen_i]][0] / s_base, - "pmax": gen.p_nom[gen_i].round(20) / s_base, - "pmin": gen.p_nom_min[gen_i].round(20) / s_base, + "pmax": gen.p_nom.iloc[gen_i].round(20) / s_base, + "pmin": gen.p_nom_min.iloc[gen_i].round(20) / s_base, "qmax": max(q).round(20) / s_base, "qmin": min(q).round(20) / s_base, "P": 0, @@ -684,7 +687,7 @@ def _build_gen(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): "vg": 1, "pf": pf, "sign": sign, - "mbase": gen.p_nom[gen_i] / s_base, + "mbase": gen.p_nom.iloc[gen_i] / s_base, "gen_bus": idx_bus, "gen_status": 1, "name": gen.index[gen_i], @@ -793,13 +796,13 @@ def _build_branch(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): idx_f_bus = _mapping( psa_net, edisgo_obj, - branches.bus0[branch_i], + branches.bus0.iloc[branch_i], flexible_storage_units=flexible_storage_units, ) idx_t_bus = _mapping( psa_net, edisgo_obj, - branches.bus1[branch_i], + branches.bus1.iloc[branch_i], flexible_storage_units=flexible_storage_units, ) pm["branch"][str(branch_i + 1)] = { @@ -820,11 +823,11 @@ def _build_branch(edisgo_obj, psa_net, pm, flexible_storage_units, s_base): "rate_c": 250 / s_base, "angmin": -np.pi / 6, "angmax": np.pi / 6, - "transformer": bool(transformer[branch_i]), + "transformer": bool(transformer.iloc[branch_i]), "storage": False, - "tap": tap[branch_i], - "length": branches.length.fillna(1)[branch_i].round(20), - "cost": branches.capital_cost[branch_i].round(20), + "tap": tap.iloc[branch_i], + "length": branches.length.fillna(1).iloc[branch_i].round(20), + "cost": branches.capital_cost.iloc[branch_i].round(20), "storage_pf": 0, "index": branch_i + 1, } @@ -912,7 +915,7 @@ def _build_load( idx_bus = _mapping( psa_net, edisgo_obj, - loads_df.bus[load_i], + loads_df.bus.iloc[load_i], flexible_storage_units=flexible_storage_units, ) if ( @@ -935,11 +938,14 @@ def _build_load( "be set for conventional load.".format(loads_df.index[load_i]) ) pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "conventional_load") - p_d = psa_net.loads_t.p_set[loads_df.index[load_i]] + try: + p_d = psa_net.loads_t.p_set[loads_df.index[load_i]] + except Exception as e: + print(e) q_d = psa_net.loads_t.q_set[loads_df.index[load_i]] pm["load"][str(load_i + 1)] = { - "pd": p_d[0].round(20) / s_base, - "qd": q_d[0].round(20) / s_base, + "pd": p_d.iloc[0].round(20) / s_base, + "qd": q_d.iloc[0].round(20) / s_base, "load_bus": idx_bus, "status": True, "pf": pf, @@ -1145,7 +1151,7 @@ def _build_electromobility(edisgo_obj, psa_net, pm, s_base, flexible_cps): ] emob_df = psa_net.loads.loc[flexible_cps] for cp_i in np.arange(len(emob_df.index)): - idx_bus = _mapping(psa_net, edisgo_obj, emob_df.bus[cp_i]) + idx_bus = _mapping(psa_net, edisgo_obj, emob_df.bus.iloc[cp_i]) # retrieve power factor and sign from config try: eta = edisgo_obj.electromobility.simbev_config_df.eta_cp.values[0] @@ -1216,7 +1222,7 @@ def _build_heatpump(psa_net, pm, edisgo_obj, s_base, flexible_hps): ) ) for hp_i in np.arange(len(heat_df.index)): - idx_bus = _mapping(psa_net, edisgo_obj, heat_df.bus[hp_i]) + idx_bus = _mapping(psa_net, edisgo_obj, heat_df.bus.iloc[hp_i]) # retrieve power factor and sign from config pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "heat_pump") q = sign * np.tan(np.arccos(pf)) * heat_df.p_set[hp_i] @@ -1325,7 +1331,7 @@ def _build_heat_storage(psa_net, pm, edisgo_obj, s_base, flexible_hps, opf_versi heat_storage_df = heat_storage_df.loc[flexible_hps] for stor_i in np.arange(len(flexible_hps)): idx_bus = _mapping( - psa_net, edisgo_obj, psa_net.loads.loc[flexible_hps].bus[stor_i] + psa_net, edisgo_obj, psa_net.loads.loc[flexible_hps].bus.iloc[stor_i] ) if ( edisgo_obj.topology.loads_df.loc[heat_storage_df.index[stor_i]].sector @@ -1444,7 +1450,7 @@ def _build_dsm(edisgo_obj, psa_net, pm, s_base, flexible_loads): ] dsm_df = psa_net.loads.loc[flexible_loads] for dsm_i in np.arange(len(dsm_df.index)): - idx_bus = _mapping(psa_net, edisgo_obj, dsm_df.bus[dsm_i]) + idx_bus = _mapping(psa_net, edisgo_obj, dsm_df.bus.iloc[dsm_i]) # retrieve power factor and sign from config pf, sign = _get_pf(edisgo_obj, pm, idx_bus, "conventional_load") p_max = edisgo_obj.dsm.p_max[dsm_df.index[dsm_i]] @@ -1701,7 +1707,7 @@ def _build_component_timeseries( Flexibilities that should be considered in the optimization. hv_flex_dict : dict Dictionary containing time series of HV requirement for each flexibility - retrieved from overlying grid component of edisgo object. + retrieved from overlying_grid component of edisgo object. """ pm_comp = dict() solar_gens = edisgo_obj.topology.generators_df.index[ diff --git a/edisgo/network/components.py b/edisgo/network/components.py index d0666bf2d..9587bd9ea 100644 --- a/edisgo/network/components.py +++ b/edisgo/network/components.py @@ -850,13 +850,14 @@ def designated_charging_point_capacity(self): Total gross designated charging park capacity """ + df_without_geometry = self.charging_processes_df[["charging_point_id", "nominal_charging_capacity_kW"]] return ( - self.charging_processes_df.groupby("charging_point_id") + df_without_geometry.groupby("charging_point_id") .max() .nominal_charging_capacity_kW.sum() / self._edisgo_obj.electromobility.eta_charging_points ) - + @property def user_centric_weight(self): """ From eaa7ee4cc4d956b9b91f440caf7f12164013832d Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Wed, 5 Feb 2025 17:11:59 +0100 Subject: [PATCH 3/5] Update parameter documentation in get_ags_from_geometry function to clarify input type --- edisgo/io/electromobility_import.py | 100 +++++++++++++++------------- 1 file changed, 54 insertions(+), 46 deletions(-) diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py index 94496a075..85ab8e6fb 100644 --- a/edisgo/io/electromobility_import.py +++ b/edisgo/io/electromobility_import.py @@ -1,17 +1,18 @@ from __future__ import annotations +import io import json import logging import os -import io +import zipfile from collections import Counter from pathlib import Path, PurePath from typing import TYPE_CHECKING -import requests + import numpy as np import pandas as pd -import zipfile +import requests from numpy.random import default_rng from sklearn import preprocessing @@ -479,7 +480,7 @@ def assure_minimum_potential_charging_parks( potential_charging_parks_gdf = potential_charging_parks_gdf.sort_values( by=["use_case", "ags", "user_centric_weight"], ascending=[True, True, False] ).reset_index(drop=True) - potential_charging_parks_gdf['ags'] = potential_charging_parks_gdf['ags'].fillna(0) + potential_charging_parks_gdf["ags"] = potential_charging_parks_gdf["ags"].fillna(0) # in case of polygons use the centroid as potential charging parks point # and set crs to match edisgo object return ( @@ -1080,11 +1081,11 @@ def distribute_public_charging_demand(edisgo_obj, **kwargs): idx, "charging_point_id" ] = charging_point_id - available_charging_points_df.loc[ - charging_point_id - ] = edisgo_obj.electromobility.charging_processes_df.loc[ - idx, available_charging_points_df.columns - ].tolist() + available_charging_points_df.loc[charging_point_id] = ( + edisgo_obj.electromobility.charging_processes_df.loc[ + idx, available_charging_points_df.columns + ].tolist() + ) designated_charging_point_capacity_df.at[ charging_park_id, "designated_charging_point_capacity" @@ -1148,89 +1149,96 @@ def integrate_charging_parks(edisgo_obj): index=charging_park_ids, ) -def get_ags_from_geometry(gdf): + +def get_ags_from_geometry(gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame: """ Get the AGS from a given geometry. Parameters ---------- - geometry : :shapely:`shapely.geometry` - Geometry object + gdf : :geopandas:`geopandas.GeoDataFrame` + GeoDataFrame with geometry Returns ------- - :obj:`int` - AGS + :geopandas:`geopandas.GeoDataFrame` + GeoDataFrame with geometry and AGS """ - url = "https://daten.gdz.bkg.bund.de/produkte/vg/vg250_ebenen_0101/aktuell/vg250_01-01.utm32s.gpkg.ebenen.zip" + url = "https://daten.gdz.bkg.bund.de/produkte/vg/vg250_ebenen_0101/aktuell/vg250_01-01.utm32s.gpkg.ebenen.zip" # noqa r = requests.get(url) z = zipfile.ZipFile(io.BytesIO(r.content)) z.extractall("/tmp/vg250_data") - vg250 = gpd.read_file("/tmp/vg250_data/vg250_01-01.utm32s.gpkg.ebenen/vg250_ebenen_0101/DE_VG250.gpkg", layer="vg250_gem") + vg250 = gpd.read_file( + "/tmp/vg250_data/vg250_01-01.utm32s.gpkg.ebenen/vg250_ebenen_0101/DE_VG250.gpkg", # noqa + layer="vg250_gem", + ) vg250 = vg250.to_crs(gdf.crs) gdf = gdf.to_crs(vg250.crs) - gdf = gpd.sjoin(gdf, vg250[['geometry', 'AGS']], how="left", predicate="intersects") + gdf = gpd.sjoin(gdf, vg250[["geometry", "AGS"]], how="left", predicate="intersects") return gdf - - def import_electromobility_from_new_data( edisgo_obj: EDisGo, data_dir: str, **kwargs, ): - """ - """ - csv_files = [f for f in os.listdir(data_dir) if f.endswith('.csv')] - gpkg_files = [f for f in os.listdir(data_dir) if f.endswith('.gpkg')] + """ """ + gpkg_files = [f for f in os.listdir(data_dir) if f.endswith(".gpkg")] # Load CSV files into a dictionary # csv_data = {f: pd.read_csv(os.path.join(data_dir, f)) for f in csv_files} # Load GPKG files into a dictionary gpkg_data = {f: gpd.read_file(os.path.join(data_dir, f)) for f in gpkg_files} - charging_processes_df = [gdf for key, gdf in gpkg_data.items() if 'charging-events' in key] - charging_processes_gdf = gpd.GeoDataFrame(pd.concat(charging_processes_df, ignore_index=True)) + charging_processes_df = [ + gdf for key, gdf in gpkg_data.items() if "charging-events" in key + ] + charging_processes_gdf = gpd.GeoDataFrame( + pd.concat(charging_processes_df, ignore_index=True) + ) charging_processes_gdf = get_ags_from_geometry(charging_processes_gdf) column_mapping = { - 'energy': 'chargingdemand_kWh', - 'use_case': 'use_case', - 'id': 'car_id', - 'station_charging_capacity': 'nominal_charging_capacity_kW', - 'assigned_location': 'ags', - 'charging_use_case': 'destination', - 'assigned_location': 'charging_park_id', + "energy": "chargingdemand_kWh", + "use_case": "use_case", + "id": "car_id", + "station_charging_capacity": "nominal_charging_capacity_kW", + "charging_use_case": "destination", + "assigned_location": "charging_park_id", } - charging_processes_gdf = charging_processes_gdf.rename(columns=column_mapping) - charging_processes_gdf['charging_point_id'] = charging_processes_gdf.charging_park_id + charging_processes_gdf["charging_point_id"] = ( + charging_processes_gdf.charging_park_id + ) edisgo_obj.electromobility.charging_processes_df = charging_processes_gdf charging_parks_gdfs = [] for key, gdf in gpkg_data.items(): - if 'charging-locations' in key: + if "charging-locations" in key: # Extrahiere den use_case aus dem Dateinamen - use_case = key.split('_')[1] + use_case = key.split("_")[1] # Füge die Spalte 'use_case' hinzu - gdf['use_case'] = use_case + gdf["use_case"] = use_case charging_parks_gdfs.append(gdf) - charging_parks_gdf = gpd.GeoDataFrame(pd.concat(charging_parks_gdfs, ignore_index=True)) + charging_parks_gdf = gpd.GeoDataFrame( + pd.concat(charging_parks_gdfs, ignore_index=True) + ) charging_parks_gdf.geometry = charging_parks_gdf.geometry.centroid mapping_charging_parks = { - 'probability': 'user_centric_weight', - 'required_points': 'ags', - 'use_case': 'use_case', - 'charging_park_id': 'id', + "probability": "user_centric_weight", + "required_points": "ags", + "use_case": "use_case", + "charging_park_id": "id", } charging_parks_gdf = charging_parks_gdf.rename(columns=mapping_charging_parks) - edisgo_obj.electromobility.potential_charging_parks_gdf = assure_minimum_potential_charging_parks( - edisgo_obj=edisgo_obj, - potential_charging_parks_gdf=charging_parks_gdf, + edisgo_obj.electromobility.potential_charging_parks_gdf = ( + assure_minimum_potential_charging_parks( + edisgo_obj=edisgo_obj, + potential_charging_parks_gdf=charging_parks_gdf, ) - # edisgo_obj.electromobility.potential_charging_parks = edisgo_obj.electromobility.potential_charging_parks_gdf + ) def import_electromobility_from_oedb( From 9f7507fb6a36ead21a0b7a96dbd2ef6d163004ba Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Wed, 5 Feb 2025 18:10:20 +0100 Subject: [PATCH 4/5] Update parameter documentation in get_ags_from_geometry function to clarify input type --- edisgo/io/electromobility_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edisgo/io/electromobility_import.py b/edisgo/io/electromobility_import.py index 85ab8e6fb..d042083ba 100644 --- a/edisgo/io/electromobility_import.py +++ b/edisgo/io/electromobility_import.py @@ -1156,12 +1156,12 @@ def get_ags_from_geometry(gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame: Parameters ---------- - gdf : :geopandas:`geopandas.GeoDataFrame` + gdf : :geopandas:`geopandas.GeoDataFrame GeoDataFrame with geometry Returns ------- - :geopandas:`geopandas.GeoDataFrame` + :geopandas:`geopandas.GeoDataFrame GeoDataFrame with geometry and AGS """ From 73fecfba4643585f5964bc0942d7cd30bfc2a40a Mon Sep 17 00:00:00 2001 From: Jonas Danke Date: Thu, 6 Feb 2025 11:21:48 +0100 Subject: [PATCH 5/5] update setup.py --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index b8445c69f..7d857ccd5 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ """Setup""" + import os import sys @@ -63,6 +64,7 @@ def read(fname): "workalendar", ] + dev_requirements = [ "black", "flake8",