rmpca 0.1.1

Enterprise-grade route optimization engine — Chinese Postman Problem solver with Eulerian circuit detection, Lean 4 FFI boundary, and property-based testing
Documentation
//! Core geographic types used across the routing engine.
//!
//! // Aligns with `Lean4`: structure `Coordinate`, structure `GeoWay`

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// A geographic coordinate (latitude, longitude).
///
/// // Aligns with `Lean4`: def `Coordinate` := `Prod Float Float`
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Coordinate {
    pub lat: f64,
    pub lon: f64,
}

impl Coordinate {
    /// Create a new coordinate.
    #[allow(dead_code)]
    #[must_use]
    pub fn new(lat: f64, lon: f64) -> Self {
        Self { lat, lon }
    }
}

/// A geographic way (road segment) with node IDs and geometry.
///
/// Used for Overture/OSM data that carries full geometry per way,
/// as opposed to the simpler `optimizer::Way` which only stores node ID references.
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Way {
    /// Unique identifier for this way
    pub id: String,

    /// Ordered list of node IDs forming this way
    pub node_ids: Vec<String>,

    /// Geometry coordinates corresponding to `node_ids`
    pub geometry: WayGeometry,

    /// OSM tags describing this road segment
    pub tags: HashMap<String, String>,
}

/// Geometry for a way — an ordered sequence of coordinates.
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WayGeometry {
    pub coordinates: Vec<Coordinate>,
}

impl WayGeometry {
    /// Create geometry from a coordinate vector.
    #[allow(dead_code)]
    #[must_use]
    pub fn new(coordinates: Vec<Coordinate>) -> Self {
        Self { coordinates }
    }

    /// Create empty geometry.
    #[allow(dead_code)]
    #[must_use]
    pub fn empty() -> Self {
        Self { coordinates: Vec::new() }
    }
}

impl Way {
    /// Create a new way with the given ID, node IDs, and geometry.
    #[allow(dead_code)]
    #[must_use]
    pub fn new(id: impl Into<String>, node_ids: Vec<String>, geometry: WayGeometry) -> Self {
        Self {
            id: id.into(),
            node_ids,
            geometry,
            tags: HashMap::new(),
        }
    }

    /// Add a tag to this way.
    #[allow(dead_code)]
    #[must_use]
    pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.tags.insert(key.into(), value.into());
        self
    }

    /// Check if this way is one-way based on OSM tags.
    #[must_use]
    pub fn is_oneway(&self) -> bool {
        self.tags
            .get("oneway")
            .is_some_and(|v| v == "yes" || v == "true" || v == "1")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_coordinate_creation() {
        let coord = Coordinate::new(45.5, -73.6);
        assert_eq!(coord.lat, 45.5);
        assert_eq!(coord.lon, -73.6);
    }

    #[test]
    fn test_way_creation() {
        let way = Way::new(
            "way1",
            vec!["n1".to_string(), "n2".to_string()],
            WayGeometry::new(vec![Coordinate::new(45.5, -73.6), Coordinate::new(45.6, -73.7)]),
        );
        assert_eq!(way.id, "way1");
        assert_eq!(way.node_ids.len(), 2);
        assert!(!way.is_oneway());
    }

    #[test]
    fn test_way_oneway() {
        let way = Way::new("w1", vec![], WayGeometry::empty())
            .with_tag("oneway", "yes");
        assert!(way.is_oneway());
    }
}