Skip to content

Commit

Permalink
Merge pull request #415 from openego/bugfix/to_geopandas
Browse files Browse the repository at this point in the history
Bugfix/to geopandas
  • Loading branch information
birgits authored Aug 7, 2024
2 parents 460f90b + 5178018 commit 5d9f4c2
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 56 deletions.
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

0 comments on commit 5d9f4c2

Please sign in to comment.