use crate::config::Config;
use crate::optimizer::RouteOptimizer;
use anyhow::{Context, Result};
use clap::Args as ClapArgs;
use std::fs::File;
use std::io::BufWriter;
use std::path::PathBuf;
#[derive(Debug, ClapArgs)]
pub struct Args {
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long, default_value = "true")]
zero_copy: bool,
#[arg(long)]
stats: bool,
}
pub async fn run(args: Args) -> Result<()> {
let config = Config::load().unwrap_or_default();
config.init_logging();
tracing::info!("Compiling map: {}", args.input.display());
let geojson_str = std::fs::read_to_string(&args.input)
.context("Failed to read GeoJSON file")?;
let feature_collection: geojson::FeatureCollection = geojson_str.parse()
.context("Failed to parse GeoJSON")?;
tracing::info!("Loaded {} features", feature_collection.features.len());
let mut optimizer = RouteOptimizer::new();
optimizer.build_graph_from_features(&feature_collection.features)?;
let output_path = args.output.unwrap_or_else(|| {
let mut path = args.input.clone();
path.set_extension("rmp");
path
});
tracing::info!("Serializing graph to: {}", output_path.display());
if args.zero_copy {
serialize_rkyv(&optimizer, &output_path)?;
} else {
serialize_bincode(&optimizer, &output_path)?;
}
if args.stats {
print_graph_stats(&optimizer);
}
tracing::info!("Map compilation complete: {}", output_path.display());
Ok(())
}
fn serialize_rkyv(optimizer: &RouteOptimizer, path: &PathBuf) -> Result<()> {
use crate::optimizer::OptimizerData;
let data: OptimizerData = optimizer.into();
let bytes = rkyv::to_bytes::<_, 4096>(&data)
.map_err(|e| anyhow::anyhow!("Failed to serialize with rkyv: {}", e))?;
std::fs::write(path, &bytes)
.context("Failed to write rkyv binary file")?;
tracing::info!("Serialized {} bytes with rkyv (zero-copy)", bytes.len());
Ok(())
}
fn serialize_bincode(optimizer: &RouteOptimizer, path: &PathBuf) -> Result<()> {
let serialized = bincode::serialize(optimizer)
.context("Failed to serialize graph with bincode")?;
std::fs::write(path, serialized)
.context("Failed to write binary file")?;
Ok(())
}
fn print_graph_stats(optimizer: &RouteOptimizer) {
let stats = optimizer.get_stats();
println!("Graph Statistics:");
println!(" Nodes: {}", stats.node_count);
println!(" Edges: {}", stats.edge_count);
println!(" Components: {}", stats.component_count);
println!(" Avg degree: {:.2}", stats.avg_degree);
println!(" Max degree: {}", stats.max_degree);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_map_args() {
let args = Args {
input: PathBuf::from("test.geojson"),
output: None,
zero_copy: true,
stats: false,
};
assert_eq!(args.input, PathBuf::from("test.geojson"));
assert!(args.zero_copy);
assert!(!args.stats);
}
#[test]
fn test_rkyv_roundtrip() {
use crate::optimizer::OptimizerData;
use rkyv::Deserialize;
let mut optimizer = RouteOptimizer::new();
optimizer.nodes.push(crate::optimizer::Node::new("n1", 45.5, -73.6));
optimizer.nodes.push(crate::optimizer::Node::new("n2", 45.6, -73.7));
optimizer.ways.push(crate::optimizer::Way::new("w1", vec!["n1".into(), "n2".into()]));
let data: OptimizerData = (&optimizer).into();
let bytes = rkyv::to_bytes::<_, 256>(&data).unwrap();
let archived = unsafe { rkyv::archived_root::<OptimizerData>(&bytes) };
let restored: OptimizerData = archived.deserialize(&mut rkyv::Infallible).unwrap();
assert_eq!(restored.nodes.len(), 2);
assert_eq!(restored.ways.len(), 1);
assert_eq!(restored.nodes[0].id, "n1");
assert_eq!(restored.nodes[1].lat, 45.6);
}
}