use crate::geo::spatial::coord_distance;
use crate::geo::types::{Coordinate, Way as GeoWay};
use crate::optimizer::types::{Node, Way};
use petgraph::graph::{DiGraph, NodeIndex};
use std::collections::HashMap;
pub struct GraphBuilder {
pub graph: DiGraph<Node, f64>,
pub node_index: HashMap<String, NodeIndex>,
pub spatial_registry: HashMap<String, Coordinate>,
pub weight_multipliers: HashMap<(String, String), f64>,
}
impl GraphBuilder {
#[must_use]
pub fn new() -> Self {
Self {
graph: DiGraph::new(),
node_index: HashMap::new(),
spatial_registry: HashMap::new(),
weight_multipliers: HashMap::new(),
}
}
#[allow(dead_code)]
#[must_use]
pub fn with_multipliers(mut self, multipliers: HashMap<(String, String), f64>) -> Self {
self.weight_multipliers = multipliers;
self
}
#[must_use]
pub fn build_from_ways(mut self, ways: &[Way], registry: HashMap<String, Coordinate>) -> DiGraph<Node, f64> {
self.spatial_registry = registry;
for way in ways {
let is_oneway = way.is_oneway();
let mut prev_idx: Option<NodeIndex> = None;
for node_id in &way.nodes {
let idx = *self.node_index.entry(node_id.clone())
.or_insert_with(|| {
let coord = self.spatial_registry.get(node_id);
let (lat, lon) = coord.map_or((0.0, 0.0), |c| (c.lat, c.lon));
self.graph.add_node(Node::new(node_id.clone(), lat, lon))
});
if let Some(prev) = prev_idx {
let prev_id = self.graph[prev].id.clone();
let distance = self.haversine_between(&prev_id, node_id);
let multiplier = self.weight_multipliers.get(&(prev_id.clone(), node_id.clone())).copied().unwrap_or(1.0);
let weight = distance * multiplier;
self.graph.add_edge(prev, idx, weight);
if !is_oneway {
let rev_multiplier = self.weight_multipliers.get(&(node_id.clone(), prev_id.clone())).copied().unwrap_or(1.0);
self.graph.add_edge(idx, prev, distance * rev_multiplier);
}
}
prev_idx = Some(idx);
}
}
self.graph
}
#[allow(dead_code)]
#[must_use]
pub fn build_from_geo_ways(mut self, ways: &[GeoWay]) -> DiGraph<Node, f64> {
for way in ways {
for (node_id, coord) in way.node_ids.iter().zip(way.geometry.coordinates.iter()) {
self.spatial_registry.insert(node_id.clone(), *coord);
}
}
for way in ways {
let is_oneway = way.is_oneway();
let mut prev_idx: Option<NodeIndex> = None;
for node_id in &way.node_ids {
let idx = *self.node_index.entry(node_id.clone())
.or_insert_with(|| {
let coord = self.spatial_registry.get(node_id);
let (lat, lon) = coord.map_or((0.0, 0.0), |c| (c.lat, c.lon));
self.graph.add_node(Node::new(node_id.clone(), lat, lon))
});
if let Some(prev) = prev_idx {
let prev_id = self.graph[prev].id.clone();
let distance = self.haversine_between(&prev_id, node_id);
let multiplier = self.weight_multipliers.get(&(prev_id.clone(), node_id.clone())).copied().unwrap_or(1.0);
let weight = distance * multiplier;
self.graph.add_edge(prev, idx, weight);
if !is_oneway {
let rev_multiplier = self.weight_multipliers.get(&(node_id.clone(), prev_id.clone())).copied().unwrap_or(1.0);
self.graph.add_edge(idx, prev, distance * rev_multiplier);
}
}
prev_idx = Some(idx);
}
}
self.graph
}
#[allow(dead_code)]
#[must_use]
pub fn spatial_registry(&self) -> &HashMap<String, Coordinate> {
&self.spatial_registry
}
#[allow(dead_code)]
#[must_use]
pub fn into_spatial_registry(self) -> HashMap<String, Coordinate> {
self.spatial_registry
}
fn haversine_between(&self, id_a: &str, id_b: &str) -> f64 {
match (self.spatial_registry.get(id_a), self.spatial_registry.get(id_b)) {
(Some(c1), Some(c2)) => coord_distance(c1, c2),
_ => 0.0,
}
}
}
impl Default for GraphBuilder {
fn default() -> Self {
Self::new()
}
}