Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/to geopandas #415

Merged
merged 8 commits into from
Aug 7, 2024
16 changes: 4 additions & 12 deletions edisgo/network/grids.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,19 @@ def graph(self):
@property
def geopandas(self):
"""
Returns components as :geopandas:`GeoDataFrame`\\ s
Returns components as :geopandas:`GeoDataFrame`\\ s.

Returns container with :geopandas:`GeoDataFrame`\\ s containing all
georeferenced components within the grid.

Returns
-------
:class:`~.tools.geopandas_helper.GeoPandasGridContainer` or \
list(:class:`~.tools.geopandas_helper.GeoPandasGridContainer`)
:class:`~.tools.geopandas_helper.GeoPandasGridContainer`
Data container with GeoDataFrames containing all georeferenced components
within the grid(s).
within the grid.

"""
return to_geopandas(self)
return to_geopandas(self, srid=self.edisgo_obj.topology.grid_district["srid"])

@property
def station(self):
Expand Down Expand Up @@ -650,10 +649,3 @@ def draw(
else:
plt.savefig(filename, dpi=150, bbox_inches="tight", pad_inches=0.1)
plt.close()

@property
def geopandas(self):
"""
TODO: Remove this as soon as LVGrids are georeferenced
"""
raise NotImplementedError("LV Grids are not georeferenced yet.")
34 changes: 23 additions & 11 deletions edisgo/network/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import random
import warnings

from typing import TYPE_CHECKING
from zipfile import ZipFile

import networkx as nx
Expand All @@ -15,7 +16,7 @@

from edisgo.network.components import Switch
from edisgo.network.grids import LVGrid, MVGrid
from edisgo.tools import geo, networkx_helper
from edisgo.tools import geo, geopandas_helper, networkx_helper
from edisgo.tools.tools import (
calculate_apparent_power,
calculate_line_reactance,
Expand All @@ -30,6 +31,9 @@
from shapely.ops import transform
from shapely.wkt import loads as wkt_loads

if TYPE_CHECKING:
from edisgo.tools.geopandas_helper import GeoPandasGridContainer

logger = logging.getLogger(__name__)

COLUMNS = {
Expand Down Expand Up @@ -2756,7 +2760,9 @@ def to_graph(self):
self.transformers_df,
)

def to_geopandas(self, mode: str = "mv"):
def to_geopandas(
self, mode: str | None = None, lv_grid_id: int | None = None
) -> GeoPandasGridContainer:
"""
Returns components as :geopandas:`GeoDataFrame`\\ s.

Expand All @@ -2766,23 +2772,29 @@ def to_geopandas(self, mode: str = "mv"):
Parameters
----------
mode : str
Return mode. If mode is "mv" the mv components are returned. If mode is "lv"
a generator with a container per lv grid is returned. Default: "mv"
If `mode` is None, GeoDataFrames for the MV grid and underlying LV grids is
returned. If `mode` is "mv", GeoDataFrames for only the MV grid are
returned. If `mode` is "lv", GeoDataFrames for the LV grid specified through
`lv_grid_id` are returned.
Default: None.
lv_grid_id : int
Only needs to be provided in case `mode` is "lv". In that case `lv_grid_id`
gives the LV grid ID as integer of the LV grid for which to return the
geodataframes.

Returns
-------
:class:`~.tools.geopandas_helper.GeoPandasGridContainer` or \
list(:class:`~.tools.geopandas_helper.GeoPandasGridContainer`)
:class:`~.tools.geopandas_helper.GeoPandasGridContainer`
Data container with GeoDataFrames containing all georeferenced components
within the grid(s).
within the grid.

"""
if mode == "mv":
if mode is None:
return geopandas_helper.to_geopandas(self, srid=self.grid_district["srid"])
elif mode == "mv":
return self.mv_grid.geopandas
elif mode == "lv":
raise NotImplementedError("LV Grids are not georeferenced yet.")
# for lv_grid in self.mv_grid.lv_grids:
# yield lv_grid.geopandas
return self.get_lv_grid(name=lv_grid_id).geopandas
else:
raise ValueError(f"{mode} is not valid. See docstring for more info.")

Expand Down
47 changes: 25 additions & 22 deletions edisgo/tools/geopandas_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

if TYPE_CHECKING:
from edisgo.network.grids import Grid
from edisgo.network.topology import Topology

COMPONENTS: list[str] = [
"generators_df",
Expand Down Expand Up @@ -162,14 +163,17 @@ def plot(self):
raise NotImplementedError


def to_geopandas(grid_obj: Grid):
def to_geopandas(grid_obj: Grid | Topology, srid: int) -> GeoPandasGridContainer:
"""
Translates all DataFrames with geolocations within a Grid class to GeoDataFrames.
Translates all DataFrames with geolocations within a grid topology to GeoDataFrames.

Parameters
----------
grid_obj : :class:`~.network.grids.Grid`
Grid object to transform.
grid_obj : :class:`~.network.grids.Grid` or :class:`~.network.topology.Topology`
Grid or Topology object to transform.
srid : int
SRID (spatial reference ID) of x and y coordinates of buses. Usually given in
Topology.grid_district["srid"].

Returns
-------
Expand All @@ -178,9 +182,6 @@ def to_geopandas(grid_obj: Grid):
their geolocation.

"""
# get srid id
srid = grid_obj._edisgo_obj.topology.grid_district["srid"]

# convert buses_df
buses_df = grid_obj.buses_df
buses_df = buses_df.assign(
Expand All @@ -204,25 +205,27 @@ def to_geopandas(grid_obj: Grid):
crs=f"EPSG:{srid}",
)
if components_dict[component.replace("_df", "_gdf")].empty:
components_dict[component.replace("_df", "_gdf")].index = components_dict[
component.replace("_df", "_gdf")
].index.astype(object)
components_dict[component.replace("_df", "_gdf")].index = attr.index

# convert lines_df
lines_df = grid_obj.lines_df

geom_0 = lines_df.merge(
buses_gdf[["geometry"]], left_on="bus0", right_index=True
).geometry
geom_1 = lines_df.merge(
buses_gdf[["geometry"]], left_on="bus1", right_index=True
).geometry

geometry = [
LineString([point_0, point_1]) for point_0, point_1 in list(zip(geom_0, geom_1))
]

lines_gdf = gpd.GeoDataFrame(lines_df.assign(geometry=geometry), crs=f"EPSG:{srid}")
lines_gdf = lines_df.merge(
buses_gdf[["geometry", "v_nom"]].rename(columns={"geometry": "geom_0"}),
left_on="bus0",
right_index=True,
)
lines_gdf = lines_gdf.merge(
buses_gdf[["geometry"]].rename(columns={"geometry": "geom_1"}),
left_on="bus1",
right_index=True,
)
lines_gdf["geometry"] = lines_gdf.apply(
lambda _: LineString([_["geom_0"], _["geom_1"]]), axis=1
)
lines_gdf = gpd.GeoDataFrame(
lines_gdf.drop(columns=["geom_0", "geom_1"]), crs=f"EPSG:{srid}"
)

return GeoPandasGridContainer(
crs=f"EPSG:{srid}",
Expand Down
41 changes: 30 additions & 11 deletions tests/network/test_topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,9 +951,17 @@ def setup_class(self):
self.edisgo.set_time_series_worst_case_analysis()

def test_to_geopandas(self):
geopandas_container = self.edisgo.topology.to_geopandas()
# further tests of to_geopandas are conducted in test_geopandas_helper.py

assert isinstance(geopandas_container, GeoPandasGridContainer)
# set up edisgo object with georeferenced LV
edisgo_geo = EDisGo(
ding0_grid=pytest.ding0_test_network_3_path, legacy_ding0_grids=False
)
test_suits = {
"mv": {"edisgo_obj": self.edisgo, "mode": "mv", "lv_grid_id": None},
"lv": {"edisgo_obj": edisgo_geo, "mode": "lv", "lv_grid_id": 1164120002},
"mv+lv": {"edisgo_obj": edisgo_geo, "mode": None, "lv_grid_id": None},
}

attrs = [
"buses_gdf",
Expand All @@ -964,19 +972,30 @@ def test_to_geopandas(self):
"transformers_gdf",
]

for attr_str in attrs:
attr = getattr(geopandas_container, attr_str)
grid_attr = getattr(
self.edisgo.topology.mv_grid, attr_str.replace("_gdf", "_df")
for test_suit, params in test_suits.items():
# call to_geopandas() function with different settings
geopandas_container = params["edisgo_obj"].topology.to_geopandas(
mode=params["mode"], lv_grid_id=params["lv_grid_id"]
)

assert isinstance(attr, GeoDataFrame)
assert isinstance(geopandas_container, GeoPandasGridContainer)

common_cols = list(set(attr.columns).intersection(grid_attr.columns))
# check that content of geodataframes is the same as content of original
# dataframes
for attr_str in attrs:
grid = getattr(geopandas_container, "grid")
attr = getattr(geopandas_container, attr_str)
grid_attr = getattr(grid, attr_str.replace("_gdf", "_df"))

assert_frame_equal(
attr[common_cols], grid_attr[common_cols], check_names=False
)
assert isinstance(attr, GeoDataFrame)

common_cols = list(set(attr.columns).intersection(grid_attr.columns))

assert_frame_equal(
attr[common_cols].sort_index(),
grid_attr[common_cols].sort_index(),
check_names=False,
)

def test_from_csv(self):
"""
Expand Down
65 changes: 65 additions & 0 deletions tests/tools/test_geopandas_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import pytest

from edisgo import EDisGo
from edisgo.tools import geopandas_helper


class TestGeopandasHelper:
@classmethod
def setup_class(self):
self.edisgo = EDisGo(ding0_grid=pytest.ding0_test_network_path)

def test_to_geopandas(self):
# further tests of this function are conducted in test_topology.py
# test MV grid
data = geopandas_helper.to_geopandas(self.edisgo.topology.mv_grid, 4326)
assert data.buses_gdf.shape[0] == self.edisgo.topology.mv_grid.buses_df.shape[0]
assert (
data.buses_gdf.shape[1]
== self.edisgo.topology.mv_grid.buses_df.shape[1] + 1 - 2
)
assert "geometry" in data.buses_gdf.columns

assert data.lines_gdf.shape[0] == self.edisgo.topology.mv_grid.lines_df.shape[0]
assert (
data.lines_gdf.shape[1]
== self.edisgo.topology.mv_grid.lines_df.shape[1] + 2
)
assert "geometry" in data.lines_gdf.columns

assert data.loads_gdf.shape[0] == self.edisgo.topology.mv_grid.loads_df.shape[0]
assert (
data.loads_gdf.shape[1]
== self.edisgo.topology.mv_grid.loads_df.shape[1] + 2
)
assert "geometry" in data.loads_gdf.columns

assert (
data.generators_gdf.shape[0]
== self.edisgo.topology.mv_grid.generators_df.shape[0]
)
assert (
data.generators_gdf.shape[1]
== self.edisgo.topology.mv_grid.generators_df.shape[1] + 2
)
assert "geometry" in data.generators_gdf.columns

assert (
data.storage_units_gdf.shape[0]
== self.edisgo.topology.mv_grid.storage_units_df.shape[0]
)
assert (
data.storage_units_gdf.shape[1]
== self.edisgo.topology.mv_grid.storage_units_df.shape[1] + 2
)
assert "geometry" in data.storage_units_gdf.columns

assert (
data.transformers_gdf.shape[0]
== self.edisgo.topology.mv_grid.transformers_df.shape[0]
)
assert (
data.transformers_gdf.shape[1]
== self.edisgo.topology.mv_grid.transformers_df.shape[1] + 2
)
assert "geometry" in data.transformers_gdf.columns
Loading