use crate::config::RoutingCfg;
use crate::ds::{ds_from_config, RouterDs};
use crate::error::{self, Result};
use fast_paths::{FastGraph, ShortestPath};
use log::info;
use rstar::primitives::GeomWithData;
use rstar::RTree;
use serde_json::json;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
#[derive(Clone)]
pub struct NodeIndex {
tree: RTree<Node>,
search_dist: f64,
nodes: NodeLookup,
next_node_id: usize,
}
type NodeLookup = HashMap<usize, (f64, f64)>;
type Node = GeomWithData<[f64; 2], usize>;
impl NodeIndex {
pub fn new(search_dist: f64) -> Self {
NodeIndex {
tree: RTree::new(),
search_dist,
nodes: Default::default(),
next_node_id: 0,
}
}
fn bulk_load(nodes: NodeLookup, search_dist: f64) -> Self {
let rtree_nodes = nodes
.iter()
.map(|(id, (x, y))| Node::new([*x, *y], *id))
.collect::<Vec<_>>();
let tree = RTree::bulk_load(rtree_nodes);
let next_node_id = nodes.keys().max().unwrap_or(&0) + 1;
NodeIndex {
tree,
search_dist,
nodes,
next_node_id,
}
}
pub fn get_coord(&self, id: usize) -> Option<&(f64, f64)> {
self.nodes.get(&id)
}
pub fn entry(&mut self, x: f64, y: f64) -> usize {
let coord = [x, y];
if let Some(node) = self.tree.locate_at_point(&coord) {
node.data
} else {
let id = self.next_node_id;
self.tree.insert(Node::new(coord, id));
self.nodes.insert(id, (x, y));
self.next_node_id += 1;
id
}
}
pub fn insert(&mut self, x: f64, y: f64, id: usize) -> bool {
#[allow(clippy::map_entry)]
if self.nodes.contains_key(&id) {
false
} else {
let coord = [x, y];
let node = Node::new(coord, id);
self.tree.insert(node);
self.nodes.insert(id, (x, y));
true
}
}
fn find(&self, x: f64, y: f64) -> Option<usize> {
let max = self.search_dist;
self.tree
.nearest_neighbor_iter_with_distance_2(&[x, y])
.next()
.filter(|(_node, dist)| *dist < max)
.map(|(node, _dist)| node.data)
}
}
pub const DEFAULT_SEARCH_DISTANCE: f64 = 0.01;
#[derive(Clone)]
pub struct Router {
index: NodeIndex,
graph: FastGraph,
}
impl Router {
pub async fn from_config(config: &RoutingCfg) -> Result<Self> {
let ds = ds_from_config(config).await?;
let dist = config.search_dist.unwrap_or(DEFAULT_SEARCH_DISTANCE);
let cache_name = ds.cache_name().to_string();
let router = if Router::cache_exists(&cache_name) {
Router::from_disk(&cache_name, dist)?
} else {
let router = Router::from_ds(ds).await?;
router.save_to_disk(&cache_name).unwrap();
router
};
info!("Routing graph ready");
Ok(router)
}
fn cache_exists(base_name: &str) -> bool {
Path::new(&format!("{base_name}.nodes.bin")).exists()
}
fn from_disk(base_name: &str, search_dist: f64) -> Result<Self> {
let fname = format!("{base_name}.nodes.bin");
info!("Reading routing graph from {fname}");
let reader = BufReader::new(File::open(fname)?);
let nodes: NodeLookup = bincode::deserialize_from(reader).unwrap();
let index = NodeIndex::bulk_load(nodes, search_dist);
let fname = format!("{base_name}.graph.bin");
let reader = BufReader::new(File::open(fname)?);
let graph: FastGraph = bincode::deserialize_from(reader).unwrap();
Ok(Router { index, graph })
}
fn save_to_disk(&self, base_name: &str) -> Result<()> {
let fname = format!("{base_name}.graph.bin");
info!("Saving routing graph to {fname}");
let writer = BufWriter::new(File::create(fname)?);
bincode::serialize_into(writer, &self.graph)?;
let fname = format!("{base_name}.nodes.bin");
let writer = BufWriter::new(File::create(fname)?);
bincode::serialize_into(writer, &self.index.nodes)?;
Ok(())
}
pub async fn from_ds(ds: Box<dyn RouterDs>) -> Result<Self> {
let load = ds.load();
let (mut input_graph, index) = load.await?;
info!("Peparing routing graph");
input_graph.freeze();
let graph = fast_paths::prepare(&input_graph);
info!("Routing graph preparation finished");
Ok(Router { index, graph })
}
pub fn calc_path(&self, source: (f64, f64), target: (f64, f64)) -> Result<ShortestPath> {
let src_id = self
.index
.find(source.0, source.1)
.ok_or(error::Error::NodeNotFound)?;
let dst_id = self
.index
.find(target.0, target.1)
.ok_or(error::Error::NodeNotFound)?;
fast_paths::calc_path(&self.graph, src_id, dst_id).ok_or(error::Error::NoRouteFound)
}
pub fn path_to_geojson(&self, paths: Vec<ShortestPath>) -> serde_json::Value {
let features = paths.iter().map(|p| {
let coords = p.get_nodes().iter().map(|node_id| {
let (x, y) = self.index.get_coord(*node_id).unwrap();
json!([x, y])
}).collect::<Vec<_>>();
json!({"type": "Feature", "geometry": {"type": "LineString", "coordinates": coords}})
}).collect::<Vec<_>>();
json!({
"type": "FeatureCollection",
"features": features
})
}
pub fn path_to_valhalla_json(&self, paths: Vec<ShortestPath>) -> serde_json::Value {
let coords = paths.iter().flat_map(|p| {
p.get_nodes().iter().map(|node_id| {
let (x, y) = *self.index.get_coord(*node_id).unwrap();
geo_types::Coord { x, y }
})
});
let polyline = polyline::encode_coordinates(coords, 6).unwrap();
json!({
"trip": {
"legs": [
{
"summary": {
"time": 1.0,
"length": 1.0
},
"shape": polyline
}
],
}
})
}
}
#[cfg(test)]
pub mod tests {
use super::*;
pub async fn router(gpkg: &str, table: &str, geom: &str) -> Router {
let cfg = RoutingCfg {
gpkg: gpkg.to_string(),
table: table.to_string(),
geom: geom.to_string(),
..Default::default()
};
let ds = ds_from_config(&cfg).await.unwrap();
Router::from_ds(ds).await.unwrap()
}
#[tokio::test]
async fn chgraph() {
let router = router("../assets/railway-test.gpkg", "flows", "geom").await;
let shortest_path = router.calc_path(
(9.352133533333333, 47.09350116666666),
(9.3422712, 47.1011887),
);
match shortest_path {
Ok(p) => {
let weight = p.get_weight();
let nodes = p.get_nodes();
dbg!(&weight, &nodes);
assert_eq!(nodes.len(), 3);
}
Err(e) => {
panic!("{e}");
}
}
}
}