|
35 | 35 | from edisgo.io.ding0_import import import_ding0_grid |
36 | 36 | from edisgo.io.electromobility_import import ( |
37 | 37 | distribute_charging_demand, |
| 38 | + distribute_charging_demand_14a, #engine |
38 | 39 | import_electromobility_from_dir, |
39 | 40 | import_electromobility_from_oedb, |
| 41 | + import_electromobility_from_oedb_14a, |
40 | 42 | integrate_charging_parks, |
| 43 | + integrate_charging_parks_14a, |
41 | 44 | ) |
42 | 45 | from edisgo.io.heat_pump_import import oedb as import_heat_pumps_oedb |
43 | 46 | from edisgo.io.storage_import import home_batteries_oedb |
|
53 | 56 | from edisgo.opf.results.opf_result_class import OPFResults |
54 | 57 | from edisgo.tools import plots, tools |
55 | 58 | from edisgo.tools.config import Config |
56 | | -from edisgo.tools.geo import find_nearest_bus |
| 59 | +from edisgo.tools.geo import find_nearest_bus, find_nearest_bus_14a |
57 | 60 | from edisgo.tools.spatial_complexity_reduction import spatial_complexity_reduction |
58 | 61 | from edisgo.tools.tools import determine_grid_integration_voltage_level |
59 | 62 |
|
@@ -1800,6 +1803,91 @@ def integrate_component_based_on_geolocation( |
1800 | 1803 | ) |
1801 | 1804 |
|
1802 | 1805 | return comp_name |
| 1806 | + |
| 1807 | + def integrate_component_based_on_geolocation_14a( |
| 1808 | + self, |
| 1809 | + comp_type, |
| 1810 | + geolocation, |
| 1811 | + voltage_level=None, |
| 1812 | + add_ts=True, |
| 1813 | + ts_active_power=None, |
| 1814 | + ts_reactive_power=None, |
| 1815 | + **kwargs, |
| 1816 | + ): |
| 1817 | + """ |
| 1818 | + 14a variant of integrate_component_based_on_geolocation. |
| 1819 | + |
| 1820 | + Uses explicit _14a helper functions where custom behavior was introduced, |
| 1821 | + while leaving the original integrate_component_based_on_geolocation |
| 1822 | + unchanged for standard users. |
| 1823 | + """ |
| 1824 | + supported_voltage_levels = {4, 5, 6, 7} |
| 1825 | + p_nom = kwargs.get("p_nom", None) |
| 1826 | + p_set = kwargs.get("p_set", None) |
| 1827 | + |
| 1828 | + p = p_nom if p_set is None else p_set |
| 1829 | + kwargs["p"] = p |
| 1830 | + |
| 1831 | + if voltage_level not in supported_voltage_levels: |
| 1832 | + if p is None: |
| 1833 | + raise ValueError( |
| 1834 | + "Neither appropriate voltage level nor nominal power were supplied." |
| 1835 | + ) |
| 1836 | + voltage_level = determine_grid_integration_voltage_level(self, p) |
| 1837 | + |
| 1838 | + # convert geolocation to shapely Point if needed |
| 1839 | + if type(geolocation) is not Point: |
| 1840 | + geolocation = Point(geolocation) |
| 1841 | + |
| 1842 | + kwargs["geom"] = geolocation |
| 1843 | + kwargs["voltage_level"] = voltage_level |
| 1844 | + |
| 1845 | + # ------------------------- |
| 1846 | + # Connect in MV |
| 1847 | + # ------------------------- |
| 1848 | + if voltage_level in [4, 5]: |
| 1849 | + comp_name = self.topology.connect_to_mv_14a(self, kwargs, comp_type) |
| 1850 | + |
| 1851 | + # ------------------------- |
| 1852 | + # Connect in LV |
| 1853 | + # ------------------------- |
| 1854 | + else: |
| 1855 | + lv_buses = self.topology.buses_df.drop(self.topology.mv_grid.buses_df.index) |
| 1856 | + lv_buses_dropna = lv_buses.dropna(axis=0, subset=["x", "y"]) |
| 1857 | + |
| 1858 | + # fallback path for non-georeferenced LV grids |
| 1859 | + if len(lv_buses_dropna) < len(lv_buses): |
| 1860 | + if kwargs.get("mvlv_subst_id", None) is None: |
| 1861 | + substations = self.topology.buses_df.loc[ |
| 1862 | + self.topology.transformers_df.bus1.unique() |
| 1863 | + ] |
| 1864 | + nearest_substation, _ = find_nearest_bus_14a(geolocation, substations) |
| 1865 | + kwargs["mvlv_subst_id"] = int(nearest_substation.split("_")[-2]) |
| 1866 | + |
| 1867 | + comp_name = self.topology.connect_to_lv_14a(self, kwargs, comp_type) |
| 1868 | + |
| 1869 | + else: |
| 1870 | + max_distance_from_target_bus = kwargs.pop( |
| 1871 | + "max_distance_from_target_bus", 0.3 #CHANGED #14a |
| 1872 | + ) |
| 1873 | + comp_name = self.topology.connect_to_lv_based_on_geolocation_14a( |
| 1874 | + self, kwargs, comp_type, max_distance_from_target_bus |
| 1875 | + ) |
| 1876 | + |
| 1877 | + if add_ts: |
| 1878 | + if comp_type == "generator": |
| 1879 | + self.set_time_series_manual( |
| 1880 | + generators_p=pd.DataFrame({comp_name: ts_active_power}), |
| 1881 | + generators_q=pd.DataFrame({comp_name: ts_reactive_power}), |
| 1882 | + ) |
| 1883 | + else: |
| 1884 | + self.set_time_series_manual( |
| 1885 | + loads_p=pd.DataFrame({comp_name: ts_active_power}), |
| 1886 | + loads_q=pd.DataFrame({comp_name: ts_reactive_power}), |
| 1887 | + ) |
| 1888 | + |
| 1889 | + return comp_name |
| 1890 | + |
1803 | 1891 |
|
1804 | 1892 | def remove_component(self, comp_type, comp_name, drop_ts=True): |
1805 | 1893 | """ |
@@ -2107,6 +2195,58 @@ def import_electromobility( |
2107 | 2195 |
|
2108 | 2196 | integrate_charging_parks(self) |
2109 | 2197 |
|
| 2198 | + def import_electromobility_14a( |
| 2199 | + self, |
| 2200 | + data_source: str = "oedb", |
| 2201 | + scenario: str = None, |
| 2202 | + import_electromobility_data_kwds=None, |
| 2203 | + allocate_charging_demand_kwds=None, |
| 2204 | + ): |
| 2205 | + """ |
| 2206 | + 14a variant of import_electromobility. |
| 2207 | + |
| 2208 | + This method uses the specific 14a EV integration path. |
| 2209 | + """ |
| 2210 | + if data_source != "oedb": |
| 2211 | + raise ValueError( |
| 2212 | + "Invalid input for parameter 'data_source'. Currently only 'oedb' is supported." |
| 2213 | + ) |
| 2214 | + |
| 2215 | + valid_scenarios = {"eGon2035", "eGon100RE"} |
| 2216 | + if scenario not in valid_scenarios: |
| 2217 | + raise ValueError( |
| 2218 | + f"Invalid scenario '{scenario}'. Possible options are {sorted(valid_scenarios)}." |
| 2219 | + ) |
| 2220 | + |
| 2221 | + if self.engine is None: |
| 2222 | + raise ValueError( |
| 2223 | + "No database engine available. Please set 'self.engine' before calling " |
| 2224 | + "import_electromobility_14a()." |
| 2225 | + ) |
| 2226 | + |
| 2227 | + if import_electromobility_data_kwds is None: |
| 2228 | + import_electromobility_data_kwds = {} |
| 2229 | + |
| 2230 | + if "shapefile_path" not in import_electromobility_data_kwds: |
| 2231 | + raise ValueError( |
| 2232 | + "For import_electromobility_14a with data_source='oedb', " |
| 2233 | + "'shapefile_path' must be provided in import_electromobility_data_kwds." |
| 2234 | + ) |
| 2235 | + |
| 2236 | + if allocate_charging_demand_kwds is None: |
| 2237 | + allocate_charging_demand_kwds = {} |
| 2238 | + |
| 2239 | + import_electromobility_from_oedb_14a( |
| 2240 | + self, |
| 2241 | + scenario=scenario, |
| 2242 | + engine=self.engine, |
| 2243 | + **import_electromobility_data_kwds, |
| 2244 | + ) |
| 2245 | + |
| 2246 | + distribute_charging_demand_14a(self, **allocate_charging_demand_kwds) |
| 2247 | + |
| 2248 | + integrate_charging_parks_14a(self) |
| 2249 | + |
2110 | 2250 | def apply_charging_strategy(self, strategy="dumb", **kwargs): |
2111 | 2251 | """ |
2112 | 2252 | Applies charging strategy to set EV charging time series at charging parks. |
|
0 commit comments