Skip to content

Commit

Permalink
Merge pull request #207 from Deltares/fix/192-fix-traffic-analysis-fo…
Browse files Browse the repository at this point in the history
…r-nodes-attached-to-multiple-nodes

Fix/192 fix traffic analysis for nodes attached to multiple nodes
  • Loading branch information
Carsopre authored Nov 8, 2023
2 parents c510377 + 20df818 commit a457c46
Show file tree
Hide file tree
Showing 8 changed files with 504 additions and 491 deletions.
43 changes: 39 additions & 4 deletions ra2ce/analyses/indirect/analyses_indirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,31 @@ def multi_link_losses(self, gdf, analysis):
aggregated_results = pd.concat(results, ignore_index=True)
return aggregated_results

def _get_origin_destination_pairs(self, graph):
@staticmethod
def extract_od_nodes_from_graph(
graph: nx.classes.MultiGraph,
) -> list[tuple[str, str]]:
"""
Extracts all Origin - Destination nodes from the graph, prevents from entries
with list of nodes for a node.
Args:
graph (nx.classes.MultiGraph): Graph containing origin-destination nodes.
Returns:
list[tuple[str, str]]]: List containing tuples of origin - destination node combinations.
"""
_od_nodes = []
for n, v in graph.nodes(data=True):
if "od_id" not in v:
continue
_o_node_list = list(map(lambda x: (n, x), v["od_id"].split(",")))
_od_nodes.extend(_o_node_list)
return _od_nodes

def _get_origin_destination_pairs(
self, graph: nx.classes.MultiGraph
) -> list[tuple[int, str], tuple[int, str]]:
od_path = (
self.config["static"] / "output_graph" / "origin_destination_table.feather"
)
Expand All @@ -521,7 +545,7 @@ def _get_origin_destination_pairs(self, graph):
for a in od.loc[od["o_id"].notnull(), "o_id"]
for b in od.loc[od["d_id"].notnull(), "d_id"]
]
all_nodes = [(n, v["od_id"]) for n, v in graph.nodes(data=True) if "od_id" in v]
all_nodes = self.extract_od_nodes_from_graph(graph)
od_nodes = []
for aa, bb in od_pairs:
# it is possible that there are multiple origins/destinations at the same 'entry-point' in the road
Expand All @@ -541,7 +565,9 @@ def _get_origin_destination_pairs(self, graph):
)
return od_nodes

def optimal_route_origin_destination(self, graph, analysis):
def optimal_route_origin_destination(
self, graph: nx.classes.MultiGraph, analysis: dict
) -> gpd.GeoDataFrame:
# create list of origin-destination pairs
od_nodes = self._get_origin_destination_pairs(graph)
pref_routes = find_route_ods(graph, od_nodes, analysis["weighing"])
Expand Down Expand Up @@ -1300,7 +1326,11 @@ def save_gdf(gdf: gpd.GeoDataFrame, save_path: Path):
logging.info("Results saved to: {}".format(save_path))


def find_route_ods(graph, od_nodes, weighing):
def find_route_ods(
graph: nx.classes.MultiGraph,
od_nodes: list[tuple[tuple[int, str], tuple[int, str]]],
weighing: str,
) -> gpd.GeoDataFrame:
# create the routes between all OD pairs
(
o_node_list,
Expand Down Expand Up @@ -1371,6 +1401,11 @@ def find_route_ods(graph, od_nodes, weighing):
geometry="geometry",
crs="epsg:4326",
)
# Remove potential duplicates (o, d node) with a different Origin name.
_duplicate_columns = ["o_node", "d_node", "destination", "length", "geometry"]
pref_routes = pref_routes.drop_duplicates(
subset=_duplicate_columns, keep="first"
).reset_index(drop=True)
return pref_routes


Expand Down
56 changes: 8 additions & 48 deletions ra2ce/analyses/indirect/traffic_analysis/traffic_analysis_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import ast
import itertools
import logging
import operator
from abc import ABC, abstractmethod
from typing import Any
Expand Down Expand Up @@ -60,9 +61,15 @@ def optimal_route_od_link(
opt_path = self._get_opt_path_values(o_node, d_node)
for u_node, v_node in itertools.pairwise(opt_path):
_nodes_key_name = self._get_node_key(u_node, v_node)
_calculated_traffic = self._calculate_origin_node_traffic(
if "," in o_node:
logging.error(
"List of nodes as 'origin node' is not accepted and will be skipped."
)
continue
_calculated_traffic = self._get_accumulated_traffic_from_node(
o_node, count_destination_nodes
)

if "," in d_node:
_calculated_traffic *= len(d_node.split(","))

Expand Down Expand Up @@ -102,53 +109,6 @@ def _get_recorded_traffic_in_node(
/ count_destination_nodes
)

def _get_accumulated_traffic_from_node_list(
self,
nodes_list: list[str],
count_destination_nodes: int,
) -> AccumulatedTraffic:
# TODO: This algorithm is not entirely clear (increase decrease of variable _intermediate_nodes)
# When do we want to 'multiply' the accumulated values?
# When do we want to 'add' the accumulated values?
_accumulated_traffic = AccumulatedTraffic(
utilitarian=1, egalitarian=1, prioritarian=1
)
_intermediate_nodes = 0
for _node in nodes_list:
if self.destinations_names in _node:
_intermediate_nodes -= 1
continue
_node_traffic = self._get_accumulated_traffic_from_node(
_node, count_destination_nodes
)
# Multiplication ( 'operator.mul' or *) or Addition ( 'operator.add' or +) operations to acummulate traffic.
# This will trigger the overloaded methods in `AccumulatedTraffic`.
_acummulated_operator = (
operator.mul if _intermediate_nodes == 0 else operator.add
)
_accumulated_traffic = _acummulated_operator(
_accumulated_traffic, _node_traffic
)
_intermediate_nodes += 1

# Set the remainig values
_accumulated_traffic.egalitarian = len(
list(filter(lambda x: self.destinations_names not in x, nodes_list))
)
return _accumulated_traffic

def _calculate_origin_node_traffic(
self,
origin_node: str,
total_d_nodes: int,
) -> AccumulatedTraffic:
if "," in origin_node:
return self._get_accumulated_traffic_from_node_list(
origin_node.split(","), total_d_nodes
)

return self._get_accumulated_traffic_from_node(origin_node, total_d_nodes)

@abstractmethod
def _get_accumulated_traffic_from_node(
self, target_node: str, total_d_nodes: int
Expand Down
21 changes: 21 additions & 0 deletions ra2ce/graph/hazard/hazard_common_functions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
"""
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Risk Assessment and Adaptation for Critical Infrastructure (RA2CE).
Copyright (C) 2023 Stichting Deltares
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import logging
from pathlib import Path
from osgeo import gdal
Expand Down
9 changes: 4 additions & 5 deletions ra2ce/graph/hazard/hazard_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import logging
from pathlib import Path
from typing import Any, List, Tuple, Union

import geopandas as gpd
import networkx as nx
Expand Down Expand Up @@ -198,7 +197,7 @@ def overlay_hazard_raster_graph(

def od_hazard_intersect(
self, graph: nx.classes.graph.Graph, ods: gpd.GeoDataFrame
) -> Tuple[nx.classes.graph.Graph, gpd.GeoDataFrame]:
) -> tuple[nx.classes.graph.Graph, gpd.GeoDataFrame]:
"""Overlays the origin and destination locations and edges with the hazard maps
Args:
Expand Down Expand Up @@ -433,8 +432,8 @@ def get_filtered_files(*suffix) -> list[Path]:
return _hazard_files

def hazard_intersect(
self, to_overlay: Union[gpd.GeoDataFrame, nx.classes.graph.Graph]
) -> Union[gpd.GeoDataFrame, nx.classes.graph.Graph]:
self, to_overlay: gpd.GeoDataFrame | nx.classes.graph.Graph
) -> gpd.GeoDataFrame | nx.classes.graph.Graph:
"""Handler function that chooses the right function for overlaying the network with the hazard data."""
# To improve performance we need to initialize the variables
if self.hazard_files["tif"]:
Expand Down Expand Up @@ -487,7 +486,7 @@ def get_original_geoms_graph(self, graph_original, graph_new):
nx.set_edge_attributes(_graph_new, original_geometries, "geometry")
return _graph_new.copy()

def _export_network_files(self, graph_name: str, types_to_export: List[str]):
def _export_network_files(self, graph_name: str, types_to_export: list[str]):
_exporter = NetworkExporterFactory()
_exporter.export(
network=self.graphs[graph_name],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_equity_analysis_with_valid_data(
},
)
assert isinstance(_expected_result, pd.DataFrame)
assert len(_expected_result.values) == 359
assert len(_expected_result.values) == 358

# 2. Run test.
_result = valid_equity_analysis.optimal_route_od_link()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_equity_analysis_with_valid_data(
},
)
assert isinstance(_expected_result, pd.DataFrame)
assert len(_expected_result.values) == 359
assert len(_expected_result.values) == 358

# 2. Run test.
_result = valid_traffic_analysis.optimal_route_od_link()
Expand Down
Loading

0 comments on commit a457c46

Please sign in to comment.