use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use rkyv::{Archive, Serialize as RkyvSerialize, Deserialize as RkyvDeserialize};
#[derive(Debug, Clone, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)]
pub struct Node {
pub id: String,
pub lat: f64,
pub lon: f64,
pub z: Option<f64>,
}
impl Node {
pub fn new(id: impl Into<String>, lat: f64, lon: f64) -> Self {
Self {
id: id.into(),
lat,
lon,
z: None,
}
}
pub fn with_elevation(id: impl Into<String>, lat: f64, lon: f64, z: f64) -> Self {
Self {
id: id.into(),
lat,
lon,
z: Some(z),
}
}
pub fn bearing_to(&self, other: &Node) -> f64 {
let lat1 = self.lat.to_radians();
let lat2 = other.lat.to_radians();
let lon_diff = (other.lon - self.lon).to_radians();
let x = (lon_diff).sin() * lat2.cos();
let y = lat1.cos() * lat2.sin() - lat1.sin() * lat2.cos() * lon_diff.cos();
(x.atan2(y).to_degrees() + 360.0) % 360.0
}
pub fn distance_to(&self, other: &Node) -> f64 {
const R: f64 = 6371_000.0;
let lat1 = self.lat.to_radians();
let lat2 = other.lat.to_radians();
let dlat = lat2 - lat1;
let dlon = (other.lon - self.lon).to_radians();
let a = (dlat / 2.0).sin() * (dlat / 2.0).sin()
+ lat1.cos() * lat2.cos() * (dlon / 2.0).sin() * (dlon / 2.0).sin();
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
R * c
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)]
pub struct Way {
pub id: String,
pub nodes: Vec<String>,
pub tags: HashMap<String, String>,
}
impl Way {
pub fn new(id: impl Into<String>, nodes: Vec<String>) -> Self {
Self {
id: id.into(),
nodes,
tags: HashMap::new(),
}
}
pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.tags.insert(key.into(), value.into());
self
}
pub fn is_oneway(&self) -> bool {
self.tags
.get("oneway")
.map(|v| v == "yes" || v == "true" || v == "1")
.unwrap_or(false)
}
pub fn highway_type(&self) -> Option<&String> {
self.tags.get("highway")
}
pub fn max_speed(&self) -> Option<f64> {
self.tags
.get("maxspeed")
.and_then(|v| v.parse::<f64>().ok())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationResult {
pub route: Vec<RoutePoint>,
pub total_distance: f64,
pub message: String,
pub stats: Option<RouteStats>,
}
impl OptimizationResult {
pub fn new(route: Vec<RoutePoint>, total_distance: f64) -> Self {
Self {
route,
total_distance,
message: "Optimization complete".to_string(),
stats: None,
}
}
pub fn calculate_stats(&mut self) {
let stats = RouteStats {
total_points: self.route.len(),
total_distance_km: self.total_distance,
average_segment_length: if self.route.len() > 1 {
let total = self.route.windows(2)
.map(|w| w[0].distance_to(&w[1]))
.sum::<f64>();
total / (self.route.len() - 1) as f64
} else {
0.0
},
};
self.stats = Some(stats);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoutePoint {
pub latitude: f64,
pub longitude: f64,
pub node_id: Option<String>,
}
impl RoutePoint {
pub fn new(lat: f64, lon: f64) -> Self {
Self {
latitude: lat,
longitude: lon,
node_id: None,
}
}
pub fn with_node_id(lat: f64, lon: f64, node_id: impl Into<String>) -> Self {
Self {
latitude: lat,
longitude: lon,
node_id: Some(node_id.into()),
}
}
pub fn distance_to(&self, other: &RoutePoint) -> f64 {
let node1 = Node::new("", self.latitude, self.longitude);
let node2 = Node::new("", other.latitude, other.longitude);
node1.distance_to(&node2)
}
}
impl From<Node> for RoutePoint {
fn from(node: Node) -> Self {
Self {
latitude: node.lat,
longitude: node.lon,
node_id: Some(node.id),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouteStats {
pub total_points: usize,
pub total_distance_km: f64,
pub average_segment_length: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_creation() {
let node = Node::new("node1", 45.5, -73.6);
assert_eq!(node.id, "node1");
assert_eq!(node.lat, 45.5);
assert_eq!(node.lon, -73.6);
assert!(node.z.is_none());
}
#[test]
fn test_node_with_elevation() {
let node = Node::with_elevation("node1", 45.5, -73.6, 100.0);
assert_eq!(node.id, "node1");
assert_eq!(node.z, Some(100.0));
}
#[test]
fn test_node_distance() {
let node1 = Node::new("", 45.5, -73.6);
let node2 = Node::new("", 45.6, -73.7);
let distance = node1.distance_to(&node2);
assert!(distance > 10_000.0 && distance < 15_000.0);
}
#[test]
fn test_way_creation() {
let way = Way::new("way1", vec!["node1".to_string(), "node2".to_string()]);
assert_eq!(way.id, "way1");
assert_eq!(way.nodes.len(), 2);
assert!(way.tags.is_empty());
}
#[test]
fn test_way_with_tags() {
let way = Way::new("way1", vec!["node1".to_string()])
.with_tag("highway", "primary")
.with_tag("maxspeed", "60");
assert_eq!(way.highway_type(), Some(&"primary".to_string()));
assert_eq!(way.max_speed(), Some(60.0));
assert!(!way.is_oneway());
let oneway_way = way.with_tag("oneway", "yes");
assert!(oneway_way.is_oneway());
}
#[test]
fn test_route_point_creation() {
let point = RoutePoint::new(45.5, -73.6);
assert_eq!(point.latitude, 45.5);
assert_eq!(point.longitude, -73.6);
assert!(point.node_id.is_none());
let with_id = RoutePoint::with_node_id(45.5, -73.6, "node1");
assert_eq!(with_id.node_id, Some("node1".to_string()));
}
#[test]
fn test_optimization_result() {
let route = vec![
RoutePoint::new(45.5, -73.6),
RoutePoint::new(45.51, -73.61),
];
let mut result = OptimizationResult::new(route, 12.5);
assert_eq!(result.total_distance, 12.5);
assert_eq!(result.route.len(), 2);
assert!(result.stats.is_none());
result.calculate_stats();
assert!(result.stats.is_some());
let stats = result.stats.as_ref().unwrap();
assert_eq!(stats.total_points, 2);
assert_eq!(stats.total_distance_km, 12.5);
}
}