Skip to main content

oxigdal_algorithms/vector/network/
graph.rs

1//! Graph data structures for network analysis
2//!
3//! This module provides efficient graph representations optimized for
4//! geospatial network analysis, supporting both directed and undirected graphs
5//! with multi-criteria edge weights.
6
7use crate::error::{AlgorithmError, Result};
8use oxigdal_core::vector::{Coordinate, LineString};
9use std::collections::HashMap;
10
11/// Unique identifier for a node in the graph
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct NodeId(pub usize);
14
15/// Unique identifier for an edge in the graph
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub struct EdgeId(pub usize);
18
19/// Specifies whether the graph is directed or undirected
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum GraphType {
22    /// Directed graph: edges have a specific direction
23    Directed,
24    /// Undirected graph: edges can be traversed in both directions
25    Undirected,
26}
27
28/// Multi-criteria edge weight supporting distance, time, and custom costs
29#[derive(Debug, Clone)]
30pub struct EdgeWeight {
31    /// Distance cost (e.g., meters)
32    pub distance: f64,
33    /// Time cost (e.g., seconds)
34    pub time: f64,
35    /// Monetary cost (e.g., toll fees)
36    pub monetary: f64,
37    /// Custom weight dimensions (named)
38    pub custom: HashMap<String, f64>,
39}
40
41impl EdgeWeight {
42    /// Create a simple weight with only distance
43    pub fn from_distance(distance: f64) -> Self {
44        Self {
45            distance,
46            time: 0.0,
47            monetary: 0.0,
48            custom: HashMap::new(),
49        }
50    }
51
52    /// Create a weight with distance and time
53    pub fn from_distance_time(distance: f64, time: f64) -> Self {
54        Self {
55            distance,
56            time,
57            monetary: 0.0,
58            custom: HashMap::new(),
59        }
60    }
61
62    /// Create a weight with all three primary dimensions
63    pub fn new(distance: f64, time: f64, monetary: f64) -> Self {
64        Self {
65            distance,
66            time,
67            monetary,
68            custom: HashMap::new(),
69        }
70    }
71
72    /// Add a custom weight dimension
73    pub fn with_custom(mut self, name: String, value: f64) -> Self {
74        self.custom.insert(name, value);
75        self
76    }
77
78    /// Compute a weighted combination of all costs
79    ///
80    /// `criteria` maps weight names to their multiplier coefficients.
81    /// Built-in names: "distance", "time", "monetary".
82    pub fn weighted_cost(&self, criteria: &HashMap<String, f64>) -> f64 {
83        let mut total = 0.0;
84        if let Some(&w) = criteria.get("distance") {
85            total += self.distance * w;
86        }
87        if let Some(&w) = criteria.get("time") {
88            total += self.time * w;
89        }
90        if let Some(&w) = criteria.get("monetary") {
91            total += self.monetary * w;
92        }
93        for (name, &value) in &self.custom {
94            if let Some(&w) = criteria.get(name.as_str()) {
95                total += value * w;
96            }
97        }
98        total
99    }
100
101    /// Get the primary weight (distance by default)
102    pub fn primary(&self) -> f64 {
103        self.distance
104    }
105}
106
107impl Default for EdgeWeight {
108    fn default() -> Self {
109        Self {
110            distance: 0.0,
111            time: 0.0,
112            monetary: 0.0,
113            custom: HashMap::new(),
114        }
115    }
116}
117
118/// A node in the network graph
119#[derive(Debug, Clone)]
120pub struct Node {
121    /// Unique identifier
122    pub id: NodeId,
123    /// Geographic coordinate
124    pub coordinate: Coordinate,
125    /// Incident edges (incoming and outgoing)
126    pub edges: Vec<EdgeId>,
127    /// Custom attributes
128    pub attributes: HashMap<String, String>,
129}
130
131impl Node {
132    /// Create a new node
133    pub fn new(id: NodeId, coordinate: Coordinate) -> Self {
134        Self {
135            id,
136            coordinate,
137            edges: Vec::new(),
138            attributes: HashMap::new(),
139        }
140    }
141
142    /// Add an attribute to the node
143    pub fn add_attribute(&mut self, key: String, value: String) {
144        self.attributes.insert(key, value);
145    }
146
147    /// Get an attribute value
148    pub fn get_attribute(&self, key: &str) -> Option<&String> {
149        self.attributes.get(key)
150    }
151
152    /// Get the degree of this node (number of incident edges)
153    pub fn degree(&self) -> usize {
154        self.edges.len()
155    }
156}
157
158/// Road classification for routing
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
160pub enum RoadClass {
161    /// Motorway / highway
162    Motorway,
163    /// Trunk road
164    Trunk,
165    /// Primary road
166    Primary,
167    /// Secondary road
168    Secondary,
169    /// Tertiary road
170    Tertiary,
171    /// Residential road
172    Residential,
173    /// Service road
174    Service,
175    /// Footpath / cycleway
176    Path,
177    /// Unclassified
178    Unclassified,
179}
180
181/// An edge in the network graph
182#[derive(Debug, Clone)]
183pub struct Edge {
184    /// Unique identifier
185    pub id: EdgeId,
186    /// Source node
187    pub source: NodeId,
188    /// Target node
189    pub target: NodeId,
190    /// Edge weight (simple scalar cost -- legacy compatibility)
191    pub weight: f64,
192    /// Multi-criteria edge weight
193    pub multi_weight: EdgeWeight,
194    /// Geographic geometry (optional)
195    pub geometry: Option<LineString>,
196    /// Whether the edge is bidirectional
197    pub bidirectional: bool,
198    /// Speed limit (optional, in km/h)
199    pub speed_limit: Option<f64>,
200    /// Road class (for routing priority)
201    pub road_class: Option<RoadClass>,
202    /// Custom attributes
203    pub attributes: HashMap<String, String>,
204}
205
206impl Edge {
207    /// Create a new edge
208    pub fn new(id: EdgeId, source: NodeId, target: NodeId, weight: f64) -> Self {
209        Self {
210            id,
211            source,
212            target,
213            weight,
214            multi_weight: EdgeWeight::from_distance(weight),
215            geometry: None,
216            bidirectional: false,
217            speed_limit: None,
218            road_class: None,
219            attributes: HashMap::new(),
220        }
221    }
222
223    /// Create a new edge with multi-criteria weight
224    pub fn with_multi_weight(
225        id: EdgeId,
226        source: NodeId,
227        target: NodeId,
228        multi_weight: EdgeWeight,
229    ) -> Self {
230        let weight = multi_weight.primary();
231        Self {
232            id,
233            source,
234            target,
235            weight,
236            multi_weight,
237            geometry: None,
238            bidirectional: false,
239            speed_limit: None,
240            road_class: None,
241            attributes: HashMap::new(),
242        }
243    }
244
245    /// Set the geometry for this edge
246    pub fn with_geometry(mut self, geometry: LineString) -> Self {
247        self.geometry = Some(geometry);
248        self
249    }
250
251    /// Mark the edge as bidirectional
252    pub fn bidirectional(mut self) -> Self {
253        self.bidirectional = true;
254        self
255    }
256
257    /// Set the speed limit
258    pub fn with_speed_limit(mut self, speed_limit: f64) -> Self {
259        self.speed_limit = Some(speed_limit);
260        self
261    }
262
263    /// Set the road class
264    pub fn with_road_class(mut self, road_class: RoadClass) -> Self {
265        self.road_class = Some(road_class);
266        self
267    }
268
269    /// Add an attribute to the edge
270    pub fn add_attribute(&mut self, key: String, value: String) {
271        self.attributes.insert(key, value);
272    }
273
274    /// Get an attribute value
275    pub fn get_attribute(&self, key: &str) -> Option<&String> {
276        self.attributes.get(key)
277    }
278
279    /// Calculate travel time based on edge length and speed limit
280    pub fn travel_time(&self) -> Option<f64> {
281        if let (Some(geom), Some(speed)) = (&self.geometry, self.speed_limit) {
282            let length = compute_linestring_length(geom);
283            Some((length / 1000.0) / speed) // Convert to hours
284        } else {
285            None
286        }
287    }
288
289    /// Returns the other endpoint of this edge given one endpoint
290    pub fn other_node(&self, node: NodeId) -> Option<NodeId> {
291        if node == self.source {
292            Some(self.target)
293        } else if node == self.target {
294            Some(self.source)
295        } else {
296            None
297        }
298    }
299
300    /// Check if this is a self-loop
301    pub fn is_self_loop(&self) -> bool {
302        self.source == self.target
303    }
304}
305
306/// Compute the length of a linestring in meters (Euclidean)
307fn compute_linestring_length(linestring: &LineString) -> f64 {
308    let coords = &linestring.coords;
309    let mut length = 0.0;
310
311    for i in 0..coords.len().saturating_sub(1) {
312        let dx = coords[i + 1].x - coords[i].x;
313        let dy = coords[i + 1].y - coords[i].y;
314        length += (dx * dx + dy * dy).sqrt();
315    }
316
317    length
318}
319
320/// Graph metrics for analysis
321#[derive(Debug, Clone)]
322pub struct GraphMetrics {
323    /// Number of nodes
324    pub num_nodes: usize,
325    /// Number of edges
326    pub num_edges: usize,
327    /// Average node degree
328    pub avg_degree: f64,
329    /// Maximum node degree
330    pub max_degree: usize,
331    /// Minimum node degree
332    pub min_degree: usize,
333    /// Graph density (0.0 to 1.0)
334    pub density: f64,
335    /// Number of connected components
336    pub num_components: usize,
337    /// Total weight of all edges
338    pub total_weight: f64,
339    /// Average edge weight
340    pub avg_weight: f64,
341    /// Minimum edge weight
342    pub min_weight: f64,
343    /// Maximum edge weight
344    pub max_weight: f64,
345    /// Graph type
346    pub graph_type: GraphType,
347}
348
349/// A network graph for spatial analysis
350#[derive(Debug, Clone)]
351pub struct Graph {
352    /// All nodes in the graph
353    nodes: HashMap<NodeId, Node>,
354    /// All edges in the graph
355    edges: HashMap<EdgeId, Edge>,
356    /// Adjacency list (node -> outgoing edges)
357    adjacency: HashMap<NodeId, Vec<EdgeId>>,
358    /// Reverse adjacency list (node -> incoming edges)
359    reverse_adjacency: HashMap<NodeId, Vec<EdgeId>>,
360    /// Next node ID
361    next_node_id: usize,
362    /// Next edge ID
363    next_edge_id: usize,
364    /// Graph type (directed or undirected)
365    graph_type: GraphType,
366}
367
368impl Graph {
369    /// Create a new empty directed graph
370    pub fn new() -> Self {
371        Self {
372            nodes: HashMap::new(),
373            edges: HashMap::new(),
374            adjacency: HashMap::new(),
375            reverse_adjacency: HashMap::new(),
376            next_node_id: 0,
377            next_edge_id: 0,
378            graph_type: GraphType::Directed,
379        }
380    }
381
382    /// Create a new graph with specified type
383    pub fn with_type(graph_type: GraphType) -> Self {
384        Self {
385            nodes: HashMap::new(),
386            edges: HashMap::new(),
387            adjacency: HashMap::new(),
388            reverse_adjacency: HashMap::new(),
389            next_node_id: 0,
390            next_edge_id: 0,
391            graph_type,
392        }
393    }
394
395    /// Get the graph type
396    pub fn graph_type(&self) -> GraphType {
397        self.graph_type
398    }
399
400    /// Add a node to the graph
401    pub fn add_node(&mut self, coordinate: Coordinate) -> NodeId {
402        let id = NodeId(self.next_node_id);
403        self.next_node_id = self.next_node_id.wrapping_add(1);
404
405        let node = Node::new(id, coordinate);
406        self.nodes.insert(id, node);
407        self.adjacency.insert(id, Vec::new());
408        self.reverse_adjacency.insert(id, Vec::new());
409
410        id
411    }
412
413    /// Add a node with a specific ID (useful for graph reconstruction)
414    pub fn add_node_with_id(&mut self, id: NodeId, coordinate: Coordinate) -> Result<NodeId> {
415        if self.nodes.contains_key(&id) {
416            return Err(AlgorithmError::InvalidGeometry(format!(
417                "Node {:?} already exists",
418                id
419            )));
420        }
421
422        let node = Node::new(id, coordinate);
423        self.nodes.insert(id, node);
424        self.adjacency.insert(id, Vec::new());
425        self.reverse_adjacency.insert(id, Vec::new());
426
427        if id.0 >= self.next_node_id {
428            self.next_node_id = id.0.wrapping_add(1);
429        }
430
431        Ok(id)
432    }
433
434    /// Add an edge to the graph
435    pub fn add_edge(&mut self, source: NodeId, target: NodeId, weight: f64) -> Result<EdgeId> {
436        if !self.nodes.contains_key(&source) {
437            return Err(AlgorithmError::InvalidGeometry(format!(
438                "Source node {:?} not found",
439                source
440            )));
441        }
442
443        if !self.nodes.contains_key(&target) {
444            return Err(AlgorithmError::InvalidGeometry(format!(
445                "Target node {:?} not found",
446                target
447            )));
448        }
449
450        let id = EdgeId(self.next_edge_id);
451        self.next_edge_id = self.next_edge_id.wrapping_add(1);
452
453        let edge = Edge::new(id, source, target, weight);
454        self.edges.insert(id, edge);
455
456        self.adjacency
457            .get_mut(&source)
458            .ok_or_else(|| AlgorithmError::InvalidGeometry("Source node not found".to_string()))?
459            .push(id);
460
461        self.reverse_adjacency
462            .get_mut(&target)
463            .ok_or_else(|| AlgorithmError::InvalidGeometry("Target node not found".to_string()))?
464            .push(id);
465
466        // For undirected graphs, also add the reverse direction
467        if self.graph_type == GraphType::Undirected && source != target {
468            self.adjacency
469                .get_mut(&target)
470                .ok_or_else(|| {
471                    AlgorithmError::InvalidGeometry("Target node not found".to_string())
472                })?
473                .push(id);
474
475            self.reverse_adjacency
476                .get_mut(&source)
477                .ok_or_else(|| {
478                    AlgorithmError::InvalidGeometry("Source node not found".to_string())
479                })?
480                .push(id);
481        }
482
483        if let Some(node) = self.nodes.get_mut(&source) {
484            node.edges.push(id);
485        }
486
487        if source != target {
488            if let Some(node) = self.nodes.get_mut(&target) {
489                node.edges.push(id);
490            }
491        }
492
493        Ok(id)
494    }
495
496    /// Add an edge with multi-criteria weight
497    pub fn add_weighted_edge(
498        &mut self,
499        source: NodeId,
500        target: NodeId,
501        multi_weight: EdgeWeight,
502    ) -> Result<EdgeId> {
503        if !self.nodes.contains_key(&source) {
504            return Err(AlgorithmError::InvalidGeometry(format!(
505                "Source node {:?} not found",
506                source
507            )));
508        }
509
510        if !self.nodes.contains_key(&target) {
511            return Err(AlgorithmError::InvalidGeometry(format!(
512                "Target node {:?} not found",
513                target
514            )));
515        }
516
517        let id = EdgeId(self.next_edge_id);
518        self.next_edge_id = self.next_edge_id.wrapping_add(1);
519
520        let edge = Edge::with_multi_weight(id, source, target, multi_weight);
521        self.edges.insert(id, edge);
522
523        self.adjacency
524            .get_mut(&source)
525            .ok_or_else(|| AlgorithmError::InvalidGeometry("Source node not found".to_string()))?
526            .push(id);
527
528        self.reverse_adjacency
529            .get_mut(&target)
530            .ok_or_else(|| AlgorithmError::InvalidGeometry("Target node not found".to_string()))?
531            .push(id);
532
533        if self.graph_type == GraphType::Undirected && source != target {
534            self.adjacency
535                .get_mut(&target)
536                .ok_or_else(|| {
537                    AlgorithmError::InvalidGeometry("Target node not found".to_string())
538                })?
539                .push(id);
540
541            self.reverse_adjacency
542                .get_mut(&source)
543                .ok_or_else(|| {
544                    AlgorithmError::InvalidGeometry("Source node not found".to_string())
545                })?
546                .push(id);
547        }
548
549        if let Some(node) = self.nodes.get_mut(&source) {
550            node.edges.push(id);
551        }
552
553        if source != target {
554            if let Some(node) = self.nodes.get_mut(&target) {
555                node.edges.push(id);
556            }
557        }
558
559        Ok(id)
560    }
561
562    /// Remove an edge from the graph
563    pub fn remove_edge(&mut self, edge_id: EdgeId) -> Result<Edge> {
564        let edge = self.edges.remove(&edge_id).ok_or_else(|| {
565            AlgorithmError::InvalidGeometry(format!("Edge {:?} not found", edge_id))
566        })?;
567
568        if let Some(adj) = self.adjacency.get_mut(&edge.source) {
569            adj.retain(|&e| e != edge_id);
570        }
571        if let Some(radj) = self.reverse_adjacency.get_mut(&edge.target) {
572            radj.retain(|&e| e != edge_id);
573        }
574
575        if self.graph_type == GraphType::Undirected && edge.source != edge.target {
576            if let Some(adj) = self.adjacency.get_mut(&edge.target) {
577                adj.retain(|&e| e != edge_id);
578            }
579            if let Some(radj) = self.reverse_adjacency.get_mut(&edge.source) {
580                radj.retain(|&e| e != edge_id);
581            }
582        }
583
584        if let Some(node) = self.nodes.get_mut(&edge.source) {
585            node.edges.retain(|&e| e != edge_id);
586        }
587        if let Some(node) = self.nodes.get_mut(&edge.target) {
588            node.edges.retain(|&e| e != edge_id);
589        }
590
591        Ok(edge)
592    }
593
594    /// Remove a node and all its incident edges from the graph
595    pub fn remove_node(&mut self, node_id: NodeId) -> Result<Node> {
596        let node = self.nodes.get(&node_id).ok_or_else(|| {
597            AlgorithmError::InvalidGeometry(format!("Node {:?} not found", node_id))
598        })?;
599
600        let incident_edges: Vec<EdgeId> = node.edges.clone();
601
602        for edge_id in incident_edges {
603            let _ = self.remove_edge(edge_id);
604        }
605
606        self.adjacency.remove(&node_id);
607        self.reverse_adjacency.remove(&node_id);
608
609        let node = self.nodes.remove(&node_id).ok_or_else(|| {
610            AlgorithmError::InvalidGeometry(format!("Node {:?} not found", node_id))
611        })?;
612
613        Ok(node)
614    }
615
616    /// Remove a node without error checking (for internal use in topology cleaning)
617    pub(crate) fn remove_node_unchecked(&mut self, node_id: NodeId) {
618        self.nodes.remove(&node_id);
619        self.adjacency.remove(&node_id);
620        self.reverse_adjacency.remove(&node_id);
621    }
622
623    /// Get a node by ID
624    pub fn get_node(&self, id: NodeId) -> Option<&Node> {
625        self.nodes.get(&id)
626    }
627
628    /// Get a mutable reference to a node by ID
629    pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
630        self.nodes.get_mut(&id)
631    }
632
633    /// Get an edge by ID
634    pub fn get_edge(&self, id: EdgeId) -> Option<&Edge> {
635        self.edges.get(&id)
636    }
637
638    /// Get a mutable reference to an edge by ID
639    pub fn get_edge_mut(&mut self, id: EdgeId) -> Option<&mut Edge> {
640        self.edges.get_mut(&id)
641    }
642
643    /// Get all outgoing edges from a node
644    pub fn outgoing_edges(&self, node: NodeId) -> &[EdgeId] {
645        self.adjacency
646            .get(&node)
647            .map(|v| v.as_slice())
648            .unwrap_or(&[])
649    }
650
651    /// Get all incoming edges to a node
652    pub fn incoming_edges(&self, node: NodeId) -> &[EdgeId] {
653        self.reverse_adjacency
654            .get(&node)
655            .map(|v| v.as_slice())
656            .unwrap_or(&[])
657    }
658
659    /// Get all neighbors of a node (targets of outgoing edges)
660    pub fn neighbors(&self, node: NodeId) -> Vec<NodeId> {
661        self.outgoing_edges(node)
662            .iter()
663            .filter_map(|edge_id| {
664                self.edges.get(edge_id).and_then(|e| {
665                    if self.graph_type == GraphType::Undirected {
666                        e.other_node(node)
667                    } else {
668                        Some(e.target)
669                    }
670                })
671            })
672            .collect()
673    }
674
675    /// Get all neighbors with the connecting edge information
676    pub fn neighbors_with_edges(&self, node: NodeId) -> Vec<(NodeId, EdgeId, f64)> {
677        self.outgoing_edges(node)
678            .iter()
679            .filter_map(|&edge_id| {
680                self.edges.get(&edge_id).and_then(|e| {
681                    let neighbor = if self.graph_type == GraphType::Undirected {
682                        e.other_node(node)
683                    } else {
684                        Some(e.target)
685                    };
686                    neighbor.map(|n| (n, edge_id, e.weight))
687                })
688            })
689            .collect()
690    }
691
692    /// Number of nodes in the graph
693    pub fn num_nodes(&self) -> usize {
694        self.nodes.len()
695    }
696
697    /// Number of edges in the graph
698    pub fn num_edges(&self) -> usize {
699        self.edges.len()
700    }
701
702    /// Check if the graph is empty
703    pub fn is_empty(&self) -> bool {
704        self.nodes.is_empty()
705    }
706
707    /// Get all node IDs
708    pub fn node_ids(&self) -> Vec<NodeId> {
709        self.nodes.keys().copied().collect()
710    }
711
712    /// Get all edge IDs
713    pub fn edge_ids(&self) -> Vec<EdgeId> {
714        self.edges.keys().copied().collect()
715    }
716
717    /// Check if a node exists
718    pub fn has_node(&self, id: NodeId) -> bool {
719        self.nodes.contains_key(&id)
720    }
721
722    /// Check if an edge exists
723    pub fn has_edge(&self, id: EdgeId) -> bool {
724        self.edges.contains_key(&id)
725    }
726
727    /// Find an edge between two specific nodes
728    pub fn find_edge(&self, source: NodeId, target: NodeId) -> Option<EdgeId> {
729        self.outgoing_edges(source).iter().find_map(|&edge_id| {
730            self.edges.get(&edge_id).and_then(|e| {
731                if e.target == target
732                    || (self.graph_type == GraphType::Undirected && e.source == target)
733                {
734                    Some(edge_id)
735                } else {
736                    None
737                }
738            })
739        })
740    }
741
742    /// Find all edges between two specific nodes (multi-graph support)
743    pub fn find_edges(&self, source: NodeId, target: NodeId) -> Vec<EdgeId> {
744        self.outgoing_edges(source)
745            .iter()
746            .filter_map(|&edge_id| {
747                self.edges.get(&edge_id).and_then(|e| {
748                    if e.target == target
749                        || (self.graph_type == GraphType::Undirected && e.source == target)
750                    {
751                        Some(edge_id)
752                    } else {
753                        None
754                    }
755                })
756            })
757            .collect()
758    }
759
760    /// Find the nearest node to a given coordinate
761    pub fn nearest_node(&self, coord: &Coordinate) -> Option<NodeId> {
762        self.nodes
763            .iter()
764            .min_by(|(_, a), (_, b)| {
765                let dist_a = Self::euclidean_distance(&a.coordinate, coord);
766                let dist_b = Self::euclidean_distance(&b.coordinate, coord);
767                dist_a
768                    .partial_cmp(&dist_b)
769                    .unwrap_or(std::cmp::Ordering::Equal)
770            })
771            .map(|(id, _)| *id)
772    }
773
774    /// Find the k nearest nodes to a given coordinate
775    pub fn k_nearest_nodes(&self, coord: &Coordinate, k: usize) -> Vec<(NodeId, f64)> {
776        let mut node_dists: Vec<(NodeId, f64)> = self
777            .nodes
778            .iter()
779            .map(|(id, node)| (*id, Self::euclidean_distance(&node.coordinate, coord)))
780            .collect();
781
782        node_dists.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
783        node_dists.truncate(k);
784        node_dists
785    }
786
787    /// Calculate Euclidean distance between two coordinates
788    fn euclidean_distance(a: &Coordinate, b: &Coordinate) -> f64 {
789        let dx = a.x - b.x;
790        let dy = a.y - b.y;
791        (dx * dx + dy * dy).sqrt()
792    }
793
794    /// Compute the in-degree of a node
795    pub fn in_degree(&self, node: NodeId) -> usize {
796        self.incoming_edges(node).len()
797    }
798
799    /// Compute the out-degree of a node
800    pub fn out_degree(&self, node: NodeId) -> usize {
801        self.outgoing_edges(node).len()
802    }
803
804    /// Compute the total degree of a node
805    pub fn degree(&self, node: NodeId) -> usize {
806        if self.graph_type == GraphType::Undirected {
807            self.outgoing_edges(node).len()
808        } else {
809            self.in_degree(node) + self.out_degree(node)
810        }
811    }
812
813    /// Calculate graph metrics
814    pub fn metrics(&self) -> GraphMetrics {
815        let mut total_degree = 0;
816        let mut max_degree = 0;
817        let mut min_degree = usize::MAX;
818        let mut total_weight = 0.0;
819        let mut min_weight = f64::INFINITY;
820        let mut max_weight = f64::NEG_INFINITY;
821
822        for node_id in self.nodes.keys() {
823            let deg = self.degree(*node_id);
824            total_degree += deg;
825            max_degree = max_degree.max(deg);
826            min_degree = min_degree.min(deg);
827        }
828
829        for edge in self.edges.values() {
830            total_weight += edge.weight;
831            min_weight = min_weight.min(edge.weight);
832            max_weight = max_weight.max(edge.weight);
833        }
834
835        if self.nodes.is_empty() {
836            min_degree = 0;
837        }
838        if self.edges.is_empty() {
839            min_weight = 0.0;
840            max_weight = 0.0;
841        }
842
843        let avg_degree = if self.num_nodes() > 0 {
844            total_degree as f64 / self.num_nodes() as f64
845        } else {
846            0.0
847        };
848
849        let avg_weight = if self.num_edges() > 0 {
850            total_weight / self.num_edges() as f64
851        } else {
852            0.0
853        };
854
855        let components = self.connected_components();
856        let density = if self.num_nodes() > 1 {
857            let max_edges = if self.graph_type == GraphType::Directed {
858                self.num_nodes() * (self.num_nodes() - 1)
859            } else {
860                self.num_nodes() * (self.num_nodes() - 1) / 2
861            };
862            if max_edges > 0 {
863                self.num_edges() as f64 / max_edges as f64
864            } else {
865                0.0
866            }
867        } else {
868            0.0
869        };
870
871        GraphMetrics {
872            num_nodes: self.num_nodes(),
873            num_edges: self.num_edges(),
874            avg_degree,
875            max_degree,
876            min_degree,
877            density,
878            num_components: components.len(),
879            total_weight,
880            avg_weight,
881            min_weight,
882            max_weight,
883            graph_type: self.graph_type,
884        }
885    }
886
887    /// Create a subgraph containing only the specified nodes and edges between them
888    pub fn subgraph(&self, node_ids: &[NodeId]) -> Result<Graph> {
889        let node_set: std::collections::HashSet<NodeId> = node_ids.iter().copied().collect();
890        let mut sub = Graph::with_type(self.graph_type);
891
892        for &node_id in &node_set {
893            let node = self.nodes.get(&node_id).ok_or_else(|| {
894                AlgorithmError::InvalidGeometry(format!("Node {:?} not found", node_id))
895            })?;
896            sub.add_node_with_id(node_id, node.coordinate)?;
897        }
898
899        for edge in self.edges.values() {
900            if node_set.contains(&edge.source) && node_set.contains(&edge.target) {
901                let _ = sub.add_edge(edge.source, edge.target, edge.weight);
902            }
903        }
904
905        Ok(sub)
906    }
907
908    /// Reverse all edge directions (only meaningful for directed graphs)
909    pub fn reverse(&self) -> Graph {
910        let mut reversed = Graph::with_type(self.graph_type);
911
912        for (id, node) in &self.nodes {
913            let _ = reversed.add_node_with_id(*id, node.coordinate);
914        }
915
916        for edge in self.edges.values() {
917            let _ = reversed.add_edge(edge.target, edge.source, edge.weight);
918        }
919
920        reversed
921    }
922
923    /// Access the nodes map directly (for iteration)
924    pub fn nodes_iter(&self) -> impl Iterator<Item = (&NodeId, &Node)> {
925        self.nodes.iter()
926    }
927
928    /// Access the edges map directly (for iteration)
929    pub fn edges_iter(&self) -> impl Iterator<Item = (&EdgeId, &Edge)> {
930        self.edges.iter()
931    }
932
933    /// Get sorted node IDs for deterministic iteration
934    pub fn sorted_node_ids(&self) -> Vec<NodeId> {
935        let mut ids: Vec<NodeId> = self.nodes.keys().copied().collect();
936        ids.sort();
937        ids
938    }
939}
940
941impl Default for Graph {
942    fn default() -> Self {
943        Self::new()
944    }
945}
946
947/// Builder for constructing graphs from geospatial data
948pub struct GraphBuilder {
949    graph: Graph,
950    tolerance: f64,
951    /// Cached node lookup for fast coordinate snapping
952    node_index: Vec<(Coordinate, NodeId)>,
953}
954
955impl GraphBuilder {
956    /// Create a new graph builder (directed)
957    pub fn new() -> Self {
958        Self {
959            graph: Graph::new(),
960            tolerance: 1e-6,
961            node_index: Vec::new(),
962        }
963    }
964
965    /// Create a new graph builder with specified type
966    pub fn with_graph_type(graph_type: GraphType) -> Self {
967        Self {
968            graph: Graph::with_type(graph_type),
969            tolerance: 1e-6,
970            node_index: Vec::new(),
971        }
972    }
973
974    /// Set the tolerance for coordinate snapping
975    pub fn with_tolerance(mut self, tolerance: f64) -> Self {
976        self.tolerance = tolerance;
977        self
978    }
979
980    /// Add a linestring as edges in the graph
981    pub fn add_linestring(
982        &mut self,
983        linestring: &LineString,
984        weight_fn: impl Fn(f64) -> f64,
985    ) -> Result<Vec<EdgeId>> {
986        let coords = &linestring.coords;
987        if coords.len() < 2 {
988            return Err(AlgorithmError::InvalidGeometry(
989                "Linestring must have at least 2 coordinates".to_string(),
990            ));
991        }
992
993        let mut edge_ids = Vec::new();
994        let mut prev_node = self.find_or_create_node(coords[0]);
995
996        for i in 1..coords.len() {
997            let curr_node = self.find_or_create_node(coords[i]);
998            let dx = coords[i].x - coords[i - 1].x;
999            let dy = coords[i].y - coords[i - 1].y;
1000            let length = (dx * dx + dy * dy).sqrt();
1001            let weight = weight_fn(length);
1002            let edge_id = self.graph.add_edge(prev_node, curr_node, weight)?;
1003            edge_ids.push(edge_id);
1004            prev_node = curr_node;
1005        }
1006
1007        Ok(edge_ids)
1008    }
1009
1010    /// Add a linestring with multi-criteria weights
1011    pub fn add_linestring_weighted(
1012        &mut self,
1013        linestring: &LineString,
1014        weight_fn: impl Fn(f64) -> EdgeWeight,
1015    ) -> Result<Vec<EdgeId>> {
1016        let coords = &linestring.coords;
1017        if coords.len() < 2 {
1018            return Err(AlgorithmError::InvalidGeometry(
1019                "Linestring must have at least 2 coordinates".to_string(),
1020            ));
1021        }
1022
1023        let mut edge_ids = Vec::new();
1024        let mut prev_node = self.find_or_create_node(coords[0]);
1025
1026        for i in 1..coords.len() {
1027            let curr_node = self.find_or_create_node(coords[i]);
1028            let dx = coords[i].x - coords[i - 1].x;
1029            let dy = coords[i].y - coords[i - 1].y;
1030            let length = (dx * dx + dy * dy).sqrt();
1031            let multi_weight = weight_fn(length);
1032            let edge_id = self
1033                .graph
1034                .add_weighted_edge(prev_node, curr_node, multi_weight)?;
1035            edge_ids.push(edge_id);
1036            prev_node = curr_node;
1037        }
1038
1039        Ok(edge_ids)
1040    }
1041
1042    /// Find an existing node or create a new one
1043    fn find_or_create_node(&mut self, coord: Coordinate) -> NodeId {
1044        for (existing_coord, node_id) in &self.node_index {
1045            let dx = existing_coord.x - coord.x;
1046            let dy = existing_coord.y - coord.y;
1047            if (dx * dx + dy * dy).sqrt() < self.tolerance {
1048                return *node_id;
1049            }
1050        }
1051        let node_id = self.graph.add_node(coord);
1052        self.node_index.push((coord, node_id));
1053        node_id
1054    }
1055
1056    /// Build and return the graph
1057    pub fn build(self) -> Graph {
1058        self.graph
1059    }
1060
1061    /// Build and validate the graph
1062    pub fn build_validated(self) -> Result<Graph> {
1063        let graph = self.graph;
1064        graph.validate()?;
1065        Ok(graph)
1066    }
1067}
1068
1069impl Default for GraphBuilder {
1070    fn default() -> Self {
1071        Self::new()
1072    }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077    use super::*;
1078
1079    #[test]
1080    fn test_create_graph() {
1081        let mut graph = Graph::new();
1082        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1083        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1084        assert_eq!(graph.num_nodes(), 2);
1085        let edge = graph.add_edge(n1, n2, 1.0);
1086        assert!(edge.is_ok());
1087        assert_eq!(graph.num_edges(), 1);
1088    }
1089
1090    #[test]
1091    fn test_undirected_graph() {
1092        let mut graph = Graph::with_type(GraphType::Undirected);
1093        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1094        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1095        let _ = graph.add_edge(n1, n2, 1.0);
1096        assert!(graph.neighbors(n1).contains(&n2));
1097        assert!(graph.neighbors(n2).contains(&n1));
1098    }
1099
1100    #[test]
1101    fn test_directed_graph_one_way() {
1102        let mut graph = Graph::with_type(GraphType::Directed);
1103        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1104        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1105        let _ = graph.add_edge(n1, n2, 1.0);
1106        assert!(graph.neighbors(n1).contains(&n2));
1107        assert!(graph.neighbors(n2).is_empty());
1108    }
1109
1110    #[test]
1111    fn test_adjacency() {
1112        let mut graph = Graph::new();
1113        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1114        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1115        let n3 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1116        let _ = graph.add_edge(n1, n2, 1.0).expect("add edge");
1117        let _ = graph.add_edge(n1, n3, 2.0).expect("add edge");
1118        let neighbors = graph.neighbors(n1);
1119        assert_eq!(neighbors.len(), 2);
1120    }
1121
1122    #[test]
1123    fn test_nearest_node() {
1124        let mut graph = Graph::new();
1125        let _n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1126        let n2 = graph.add_node(Coordinate::new_2d(5.0, 0.0));
1127        let _n3 = graph.add_node(Coordinate::new_2d(10.0, 0.0));
1128        assert_eq!(graph.nearest_node(&Coordinate::new_2d(4.5, 0.0)), Some(n2));
1129    }
1130
1131    #[test]
1132    fn test_k_nearest_nodes() {
1133        let mut graph = Graph::new();
1134        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1135        let n2 = graph.add_node(Coordinate::new_2d(5.0, 0.0));
1136        let _n3 = graph.add_node(Coordinate::new_2d(10.0, 0.0));
1137        let nearest = graph.k_nearest_nodes(&Coordinate::new_2d(1.0, 0.0), 2);
1138        assert_eq!(nearest.len(), 2);
1139        assert_eq!(nearest[0].0, n1);
1140        assert_eq!(nearest[1].0, n2);
1141    }
1142
1143    #[test]
1144    fn test_graph_builder() {
1145        let coords = vec![
1146            Coordinate::new_2d(0.0, 0.0),
1147            Coordinate::new_2d(1.0, 0.0),
1148            Coordinate::new_2d(2.0, 0.0),
1149        ];
1150        let linestring = LineString::new(coords).expect("linestring");
1151        let mut builder = GraphBuilder::new();
1152        let edges = builder.add_linestring(&linestring, |length| length);
1153        assert!(edges.is_ok());
1154        let graph = builder.build();
1155        assert_eq!(graph.num_nodes(), 3);
1156        assert_eq!(graph.num_edges(), 2);
1157    }
1158
1159    #[test]
1160    fn test_graph_metrics() {
1161        let mut graph = Graph::new();
1162        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1163        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1164        let n3 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1165        let _ = graph.add_edge(n1, n2, 1.0);
1166        let _ = graph.add_edge(n2, n3, 1.0);
1167        let metrics = graph.metrics();
1168        assert_eq!(metrics.num_nodes, 3);
1169        assert_eq!(metrics.num_edges, 2);
1170        assert_eq!(metrics.total_weight, 2.0);
1171    }
1172
1173    #[test]
1174    fn test_edge_travel_time() {
1175        let coords = vec![
1176            Coordinate::new_2d(0.0, 0.0),
1177            Coordinate::new_2d(1000.0, 0.0),
1178        ];
1179        let linestring = LineString::new(coords).expect("linestring");
1180        let edge = Edge::new(EdgeId(0), NodeId(0), NodeId(1), 1.0)
1181            .with_geometry(linestring)
1182            .with_speed_limit(60.0);
1183        let time = edge.travel_time().expect("travel time");
1184        assert!((time - 1.0 / 60.0).abs() < 1e-6);
1185    }
1186
1187    #[test]
1188    fn test_remove_edge() {
1189        let mut graph = Graph::new();
1190        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1191        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1192        let e1 = graph.add_edge(n1, n2, 1.0).expect("add edge");
1193        assert!(graph.remove_edge(e1).is_ok());
1194        assert_eq!(graph.num_edges(), 0);
1195    }
1196
1197    #[test]
1198    fn test_remove_node() {
1199        let mut graph = Graph::new();
1200        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1201        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1202        let n3 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1203        let _ = graph.add_edge(n1, n2, 1.0);
1204        let _ = graph.add_edge(n2, n3, 1.0);
1205        assert!(graph.remove_node(n2).is_ok());
1206        assert_eq!(graph.num_nodes(), 2);
1207        assert_eq!(graph.num_edges(), 0);
1208    }
1209
1210    #[test]
1211    fn test_edge_weight_multi_criteria() {
1212        let weight = EdgeWeight::new(100.0, 60.0, 5.0).with_custom("elevation".to_string(), 10.0);
1213        let mut criteria = HashMap::new();
1214        criteria.insert("distance".to_string(), 1.0);
1215        criteria.insert("time".to_string(), 2.0);
1216        criteria.insert("monetary".to_string(), 0.5);
1217        criteria.insert("elevation".to_string(), 0.3);
1218        let cost = weight.weighted_cost(&criteria);
1219        assert!((cost - 225.5).abs() < 1e-10);
1220    }
1221
1222    #[test]
1223    fn test_subgraph() {
1224        let mut graph = Graph::new();
1225        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1226        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1227        let n3 = graph.add_node(Coordinate::new_2d(2.0, 0.0));
1228        let _ = graph.add_edge(n1, n2, 1.0);
1229        let _ = graph.add_edge(n2, n3, 1.0);
1230        let sub = graph.subgraph(&[n1, n2]).expect("subgraph");
1231        assert_eq!(sub.num_nodes(), 2);
1232        assert_eq!(sub.num_edges(), 1);
1233    }
1234
1235    #[test]
1236    fn test_graph_reverse() {
1237        let mut graph = Graph::with_type(GraphType::Directed);
1238        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1239        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1240        let _ = graph.add_edge(n1, n2, 1.0);
1241        let reversed = graph.reverse();
1242        assert!(reversed.neighbors(n2).contains(&n1));
1243        assert!(reversed.neighbors(n1).is_empty());
1244    }
1245
1246    #[test]
1247    fn test_find_edge() {
1248        let mut graph = Graph::new();
1249        let n1 = graph.add_node(Coordinate::new_2d(0.0, 0.0));
1250        let n2 = graph.add_node(Coordinate::new_2d(1.0, 0.0));
1251        let e1 = graph.add_edge(n1, n2, 1.0).expect("add edge");
1252        assert_eq!(graph.find_edge(n1, n2), Some(e1));
1253        assert_eq!(graph.find_edge(n2, n1), None);
1254    }
1255
1256    #[test]
1257    fn test_builder_weighted() {
1258        let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(1.0, 0.0)];
1259        let linestring = LineString::new(coords).expect("linestring");
1260        let mut builder = GraphBuilder::new();
1261        let edges = builder.add_linestring_weighted(&linestring, |len| {
1262            EdgeWeight::from_distance_time(len, len / 50.0)
1263        });
1264        assert!(edges.is_ok());
1265        assert_eq!(builder.build().num_edges(), 1);
1266    }
1267}