Skip to content

Commit

Permalink
Feat/136 simple graph segmentation for damages (#539)
Browse files Browse the repository at this point in the history
* chore: renaming representative_damage_percentile

* feat: segmentation added to network wrappers missing it

* feat: segmentation modifications and config updates for the examples

* doc: minor

* chore: crs exception covered

* chore: segment_graph function created at the protocol level

* chore: segment_graph function created at the protocol level and refined

* chore: commented lines removed

* chore: update

* chore: segment_graph enhanced

* chore: set_crs updated

* chore: unused imports are removed

* chore: time of the link is updated

* doc: segmentation length added
  • Loading branch information
sahand-asgarpour authored Aug 9, 2024
1 parent c8fbfcb commit 1a812db
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 52 deletions.
2 changes: 1 addition & 1 deletion examples/data/damages_analysis_OSdamage/network.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ hazard_crs = EPSG:32736

[cleanup]
snapping_threshold = None
segmentation_length = None
segmentation_length = 100
merge_lines = True
merge_on_id = False
cut_at_intersections = False
2 changes: 1 addition & 1 deletion examples/data/damages_analysis_huizinga/network.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ hazard_crs = EPSG:32736

[cleanup]
snapping_threshold = None
segmentation_length = None
segmentation_length = 100
merge_lines = True
merge_on_id = False
cut_at_intersections = False
2 changes: 1 addition & 1 deletion examples/data/damages_analysis_manual/network.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ hazard_crs = EPSG:32736

[cleanup]
snapping_threshold = None
segmentation_length = None
segmentation_length = 100
merge_lines = True
merge_on_id = False
cut_at_intersections = False
16 changes: 8 additions & 8 deletions examples/example_damages_OSdamage.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
"name = beira<br>\n",
"<br>\n",
"[network]<br>\n",
"**directed = False<br>\n",
"directed = False<br>\n",
"source = OSM download<br>\n",
"primary_file = None<br>\n",
"diversion_file = None<br>\n",
Expand All @@ -123,28 +123,28 @@
"save_gpkg = True\n",
"<br>\n",
"[origins_destinations]<br>\n",
"**origins = None<br>\n",
"origins = None<br>\n",
"destinations = None<br>\n",
"origins_names = None<br>\n",
"destinations_names = None<br>\n",
"id_name_origin_destination = None<br>\n",
"origin_count = None<br>\n",
"origin_out_fraction = None<br>\n",
"category = category**<br>\n",
"category = categorybr>\n",
"<br>\n",
"[hazard]<br>\n",
"**hazard_map = max_flood_depth.tif<br>\n",
"hazard_map = max_flood_depth.tif<br>\n",
"hazard_id = None<br>\n",
"hazard_field_name = waterdepth<br>\n",
"aggregate_wl = max<br>\n",
"hazard_crs = EPSG:32736**<br>\n",
"hazard_crs = EPSG:32736br>\n",
"<br>\n",
"*[cleanup]<br>\n",
"[cleanup]<br>\n",
"snapping_threshold = None<br>\n",
"segmentation_length = None<br>\n",
"segmentation_length = 100<br>\n",
"merge_lines = True<br>\n",
"merge_on_id = False<br>\n",
"cut_at_intersections = False<br>*"
"cut_at_intersections = False<br>"
]
},
{
Expand Down
16 changes: 8 additions & 8 deletions examples/example_damages_huizinga.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
"name = beira<br>\n",
"<br>\n",
"[network]<br>\n",
"**directed = False<br>\n",
"directed = False<br>\n",
"source = OSM download<br>\n",
"primary_file = None<br>\n",
"diversion_file = None<br>\n",
Expand All @@ -143,28 +143,28 @@
"save_gpkg = True\n",
"<br>\n",
"[origins_destinations]<br>\n",
"**origins = None<br>\n",
"origins = None<br>\n",
"destinations = None<br>\n",
"origins_names = None<br>\n",
"destinations_names = None<br>\n",
"id_name_origin_destination = None<br>\n",
"origin_count = None<br>\n",
"origin_out_fraction = None<br>\n",
"category = category**<br>\n",
"category = categorybr>\n",
"<br>\n",
"[hazard]<br>\n",
"**hazard_map = max_flood_depth.tif<br>\n",
"hazard_map = max_flood_depth.tif<br>\n",
"hazard_id = None<br>\n",
"hazard_field_name = waterdepth<br>\n",
"aggregate_wl = max<br>\n",
"hazard_crs = EPSG:32736**<br>\n",
"hazard_crs = EPSG:32736br>\n",
"<br>\n",
"*[cleanup]<br>\n",
"[cleanup]<br>\n",
"snapping_threshold = None<br>\n",
"segmentation_length = None<br>\n",
"segmentation_length = 100<br>\n",
"merge_lines = True<br>\n",
"merge_on_id = False<br>\n",
"cut_at_intersections = False<br>*"
"cut_at_intersections = False<br>"
]
},
{
Expand Down
12 changes: 6 additions & 6 deletions examples/example_damages_manual.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
"name = beira<br>\n",
"<br>\n",
"[network]<br>\n",
"**directed = False<br>\n",
"directed = False<br>\n",
"source = OSM download<br>\n",
"primary_file = None<br>\n",
"diversion_file = None<br>\n",
Expand All @@ -123,25 +123,25 @@
"save_gpkg = True\n",
"<br>\n",
"[origins_destinations]<br>\n",
"**origins = None<br>\n",
"origins = None<br>\n",
"destinations = None<br>\n",
"origins_names = None<br>\n",
"destinations_names = None<br>\n",
"id_name_origin_destination = None<br>\n",
"origin_count = None<br>\n",
"origin_out_fraction = None<br>\n",
"category = category**<br>\n",
"category = category<br>\n",
"<br>\n",
"[hazard]<br>\n",
"**hazard_map = max_flood_depth.tif<br>\n",
"hazard_map = max_flood_depth.tif<br>\n",
"hazard_id = None<br>\n",
"hazard_field_name = waterdepth<br>\n",
"aggregate_wl = max<br>\n",
"hazard_crs = EPSG:32736**<br>\n",
"hazard_crs = EPSG:32736<br>\n",
"<br>\n",
"*[cleanup]<br>\n",
"snapping_threshold = None<br>\n",
"segmentation_length = None<br>\n",
"segmentation_length = 100<br>\n",
"merge_lines = True<br>\n",
"merge_on_id = False<br>\n",
"cut_at_intersections = False<br>*"
Expand Down
43 changes: 42 additions & 1 deletion ra2ce/network/network_wrappers/network_wrapper_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,55 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from typing import Protocol, runtime_checkable
import logging
import math
from typing import Protocol, runtime_checkable, Union, Optional

from geopandas import GeoDataFrame
from networkx import MultiGraph

from ra2ce.network.segmentation import Segmentation


@runtime_checkable
class NetworkWrapperProtocol(Protocol):
def segment_graph(
self,
edges: GeoDataFrame,
export_link_table: bool,
link_tables: Optional[tuple] = None,
) -> Union[GeoDataFrame, tuple[GeoDataFrame, tuple]]:
"""
Segments a complex graph based on the given segmentation length.
Args:
- segmentation_length (Optional[float]): The length to segment the graph edges. If None, no segmentation is applied.
- edges_complex (gpd.GeoDataFrame): The GeoDataFrame containing the complex graph edges.
- crs (str): The coordinate reference system to apply if the CRS is missing after segmentation.
Returns:
- gpd.GeoDataFrame: The segmented edges_complex GeoDataFrame.
"""
if not math.isnan(self.segmentation_length):
segmentation = Segmentation(edges, self.segmentation_length)
segmented_edges = segmentation.apply_segmentation()
if segmented_edges.crs is None: # The CRS might have disappeared.
segmented_edges.crs = self.crs # set the right CRS

if export_link_table:
updated_link_tables = segmentation.generate_link_tables()
segmented_edges.drop(columns=["rfid_c"], inplace=True)
segmented_edges.rename(columns={"splt_id": "rfid_c"}, inplace=True)
return segmented_edges, updated_link_tables
return segmented_edges
elif export_link_table and link_tables:
return edges, link_tables
elif export_link_table and not link_tables:
logging.warning("empty link_tables is passed")
return edges, tuple()
else:
return edges

def get_network(self) -> tuple[MultiGraph, GeoDataFrame]:
"""
Gets a network built within this wrapper instance. No arguments are accepted, the `__init__` method is meant to assign all required attributes for a wrapper.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,17 @@ def __init__(self, config_data: NetworkConfigData) -> None:
config_data.network.attributes_to_exclude_in_simplification
)
self.output_graph_dir = config_data.output_graph_dir
self.graph_crs = config_data.crs
self.crs = config_data.crs

# Network
self.network_type = config_data.network.network_type
self.road_types = config_data.network.road_types
self.polygon_graph = self._get_clean_graph_from_osm(config_data.network.polygon)
self.is_directed = config_data.network.directed

# Cleanup
self.segmentation_length = config_data.cleanup.segmentation_length

@classmethod
def with_polygon(
cls, config_data: NetworkConfigData, polygon: BaseGeometry
Expand Down Expand Up @@ -153,6 +156,11 @@ def get_network(self) -> tuple[MultiGraph, GeoDataFrame]:
edges_complex, _ = nut.graph_to_gdf(graph_complex)
logging.info("Finished converting the graph to a geodataframe")

# Segment the complex graph
edges_complex, link_tables = self.segment_graph(
edges_complex, export_link_table=True, link_tables=link_tables
)

# Save the link tables linking complex and simple IDs
self._export_linking_tables(link_tables)

Expand Down Expand Up @@ -250,7 +258,7 @@ def _download_clean_graph_from_osm(
)
)
if "crs" not in _complex_graph.graph.keys():
_complex_graph.graph["crs"] = self.graph_crs
_complex_graph.graph["crs"] = self.crs
self.get_clean_graph(_complex_graph)
return _complex_graph

Expand Down
9 changes: 2 additions & 7 deletions ra2ce/network/network_wrappers/shp_network_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"""

import logging
import math

import geopandas as gpd
import networkx as nx
Expand All @@ -32,7 +31,6 @@
from ra2ce.network.network_wrappers.network_wrapper_protocol import (
NetworkWrapperProtocol,
)
from ra2ce.network.segmentation import Segmentation


class ShpNetworkWrapper(NetworkWrapperProtocol):
Expand Down Expand Up @@ -190,9 +188,6 @@ def get_network(

graph_complex, edges_complex = self._get_complex_graph_and_edges(edges, id_name)

if not math.isnan(self.segmentation_length):
edges_complex = Segmentation(edges_complex, self.segmentation_length)
edges_complex = edges_complex.apply_segmentation()
if edges_complex.crs is None: # The CRS might have dissapeared.
edges_complex.crs = self.crs # set the right CRS
edges_complex = self.segment_graph(edges_complex, export_link_table=False)

return graph_complex, edges_complex
11 changes: 2 additions & 9 deletions ra2ce/network/network_wrappers/trails_network_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
NetworkWrapperProtocol,
)
from ra2ce.network.networks_utils import graph_from_gdf
from ra2ce.network.segmentation import Segmentation


class TrailsNetworkWrapper(NetworkWrapperProtocol):
Expand Down Expand Up @@ -90,14 +89,8 @@ def get_network(self) -> tuple[MultiGraph, GeoDataFrame]:
"RA2CE will not clean-up your graph, assuming that it is already done in TRAILS"
)

edges_complex = edges
if self.segmentation_length:
logging.info("TRAILS importer: start segmentating graph")
to_segment = Segmentation(edges, self.segmentation_length)
edges_simple_segmented = to_segment.apply_segmentation()
if edges_simple_segmented.crs is None: # The CRS might have disappeared.
edges_simple_segmented.crs = edges.crs # set the right CRS
edges_complex = edges_simple_segmented
# Segment the complex graph
edges_complex = self.segment_graph(edges, export_link_table=False)

graph_complex = graph_simple # NOTE THAT DIFFERENCE
# BETWEEN SIMPLE AND COMPLEX DOES NOT EXIST WHEN IMPORTING WITH TRAILS
Expand Down
6 changes: 6 additions & 0 deletions ra2ce/network/network_wrappers/vector_network_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(
self.output_graph_dir = config_data.output_graph_dir

# Cleanup
self.segmentation_length = config_data.cleanup.segmentation_length
self.delete_duplicate_nodes = config_data.cleanup.delete_duplicate_nodes

def get_network(
Expand Down Expand Up @@ -111,6 +112,11 @@ def get_network(
edges_complex, _ = nut.graph_to_gdf(graph_complex)
logging.info("Finished converting the graph to a geodataframe")

# Segment the complex graph
edges_complex, link_tables = self.segment_graph(
edges_complex, export_link_table=True, link_tables=link_tables
)

# Save the link tables linking complex and simple IDs
self._export_linking_tables(link_tables)

Expand Down
Loading

0 comments on commit 1a812db

Please sign in to comment.