use core::fmt::Debug;
use core::hash::BuildHasherDefault;
use geo::{Point, Rect};
use log::debug;
use petgraph::prelude::DiGraphMap;
use rustc_hash::{FxHashMap, FxHasher};
use serde::{Deserialize, Serialize};
use web_time::Instant;
use routers_network::edge::Weight;
use routers_network::network::GraphEdge;
use routers_network::{
DirectionAwareEdgeId, Discovery, Edge, Node, Route, RowIndex, Scan, envelope_of,
};
use crate::overture::element::{Connector, Segment};
use crate::overture::id::OvertureEntryId;
use crate::overture::meta::OvertureEdgeMetadata;
#[cfg(not(target_arch = "wasm32"))]
use std::io::Write;
#[cfg(not(target_arch = "wasm32"))]
use std::path::{Path, PathBuf};
#[cfg(all(feature = "overture", not(target_arch = "wasm32")))]
use crate::overture::error::OvertureError;
pub type GraphStructure<E> =
DiGraphMap<E, (Weight, DirectionAwareEdgeId<E>), BuildHasherDefault<FxHasher>>;
const SAVE_MAGIC: &[u8; 4] = b"OVMN";
include!(concat!(env!("OUT_DIR"), "/format_hash.rs"));
const SAVE_VERSION: u64 = FORMAT_HASH;
#[derive(Serialize, Deserialize)]
pub struct OvertureNetwork {
pub graph: GraphStructure<OvertureEntryId>,
pub hash: FxHashMap<OvertureEntryId, Node<OvertureEntryId>>,
pub meta: FxHashMap<OvertureEntryId, OvertureEdgeMetadata>,
#[serde(skip)]
pub index: RowIndex<OvertureEntryId>,
#[serde(skip)]
pub index_edge: RowIndex<Edge<Node<OvertureEntryId>>>,
}
impl OvertureNetwork {
pub fn from_elements(connectors: Vec<Connector>, segments: Vec<Segment>) -> Self {
crate::overture::builder::build(connectors, segments)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
const HEADER_LEN: usize = SAVE_MAGIC.len() + 8;
if bytes.len() < HEADER_LEN || &bytes[..SAVE_MAGIC.len()] != SAVE_MAGIC {
return Err("Header bytes are missing, try rebuilding the cache.".to_string());
}
let version = u64::from_le_bytes(
bytes[SAVE_MAGIC.len()..HEADER_LEN]
.try_into()
.expect("8 bytes"),
);
if version != SAVE_VERSION {
return Err(format!(
"Header expects {SAVE_VERSION:016x}, got format hash {version:016x}, rebuild the cache."
));
}
let deserialise_start = Instant::now();
let mut net: Self =
postcard::from_bytes(&bytes[HEADER_LEN..]).map_err(|v| v.to_string())?;
let deserialise = deserialise_start.elapsed();
let rebuild_start = Instant::now();
net.rebuild_indices();
debug!(
"OvertureNetwork::from_bytes: {} bytes, deserialised in {:?}, rebuilt indices in {:?}",
bytes.len(),
deserialise,
rebuild_start.elapsed()
);
Ok(net)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
let payload: Vec<u8> =
postcard::to_allocvec(self).map_err(|e| format!("failed to serialise value: {e}"))?;
let mut out = Vec::with_capacity(SAVE_MAGIC.len() + 8 + payload.len());
out.extend_from_slice(SAVE_MAGIC);
out.extend_from_slice(&SAVE_VERSION.to_le_bytes());
out.extend_from_slice(&payload);
Ok(out)
}
#[cfg(all(feature = "overture", not(target_arch = "wasm32")))]
pub fn from_geoparquet(path: &Path) -> Result<Self, OvertureError> {
let start = Instant::now();
let transportation = crate::overture::reader::read_transportation(path)?;
debug!(
"OvertureNetwork::from_geoparquet read {} connectors, {} segments in {:?}",
transportation.connectors.len(),
transportation.segments.len(),
start.elapsed()
);
Ok(Self::from_elements(
transportation.connectors,
transportation.segments,
))
}
#[cfg(all(feature = "overture", not(target_arch = "wasm32")))]
pub fn from_geoparquet_and_save(source: &Path, saved_path: &PathBuf) -> Result<Self, String> {
if saved_path.exists() {
match OvertureNetwork::from_saved(saved_path) {
Ok(g) => return Ok(g),
Err(e) => log::warn!(
"OvertureNetwork cache at `{}` is unusable ({e}); rebuilding from GeoParquet",
saved_path.display()
),
}
}
let graph = OvertureNetwork::from_geoparquet(source).map_err(|e| e.to_string())?;
graph.save_to_file(saved_path)?;
Ok(graph)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn from_saved(filename: &PathBuf) -> Result<Self, String> {
let bytes = std::fs::read(filename).map_err(|v| v.to_string())?;
Self::from_bytes(&bytes).map_err(|e| format!("cache file `{}`: {e}", filename.display()))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_to_file(&self, path: &Path) -> Result<(), String> {
let bytes = self.to_bytes()?;
let mut file = std::fs::File::create(path).map_err(|e| e.to_string())?;
file.write_all(&bytes).map_err(|e| e.to_string())?;
debug!(
"OvertureNetwork::save_to_file wrote {} bytes (incl. 12-byte header, format {:016x}) to {}",
bytes.len(),
SAVE_VERSION,
path.display()
);
Ok(())
}
pub fn rebuild_indices(&mut self) {
let nodes: Vec<OvertureEntryId> = self.hash.keys().copied().collect();
let edges: Vec<Edge<Node<OvertureEntryId>>> = self
.graph
.all_edges()
.filter_map(|(s, t, &(weight, id))| {
let source = *self.hash.get(&s)?;
let target = *self.hash.get(&t)?;
Some(Edge {
source,
target,
id: DirectionAwareEdgeId::new(Node::new(Point::new(0., 0.), id.index()))
.with_direction(id.direction()),
weight,
})
})
.collect();
let hash = &self.hash;
let (node_index, edge_index) = rayon::join(
|| {
RowIndex::build(nodes, |id| {
let p = hash[id].position;
(p, p)
})
},
|| RowIndex::build(edges, |e| envelope_of(e.source.position, e.target.position)),
);
self.index = node_index;
self.index_edge = edge_index;
}
pub fn num_nodes(&self) -> usize {
self.graph.node_count()
}
}
impl Default for OvertureNetwork {
fn default() -> Self {
Self {
graph: GraphStructure::new(),
hash: FxHashMap::default(),
meta: FxHashMap::default(),
index: RowIndex::default(),
index_edge: RowIndex::default(),
}
}
}
impl Discovery for OvertureNetwork {
fn edges_in_box<'a>(
&'a self,
bounds: Rect<f64>,
) -> Box<dyn Iterator<Item = Edge<Node<OvertureEntryId>>> + Send + 'a> {
Box::new(self.index_edge.search(bounds).copied())
}
fn nodes_in_box<'a>(
&'a self,
bounds: Rect<f64>,
) -> Box<dyn Iterator<Item = &'a Node<OvertureEntryId>> + Send + 'a> {
Box::new(self.index.search(bounds).filter_map(|id| self.hash.get(id)))
}
fn node(&self, id: &OvertureEntryId) -> Option<&Node<OvertureEntryId>> {
self.hash.get(id)
}
fn edge(
&self,
&source: &OvertureEntryId,
&target: &OvertureEntryId,
) -> Option<Edge<OvertureEntryId>> {
self.graph
.edge_weight(source, target)
.map(|&(weight, id)| Edge {
source,
target,
weight,
id,
})
}
}
impl Scan for OvertureNetwork {
fn nearest_node<'a>(&'a self, point: &Point) -> Option<&'a Node<OvertureEntryId>> {
self.index.nearest(point).and_then(|id| self.hash.get(id))
}
}
impl Route for OvertureNetwork {
fn route_nodes(
&self,
start_node: OvertureEntryId,
finish_node: OvertureEntryId,
) -> Option<(Weight, Vec<Node<OvertureEntryId>>)> {
let (score, path) = petgraph::algo::astar(
&self.graph,
start_node,
|finish| finish == finish_node,
|(_, _, w)| w.0,
|_| 0 as Weight,
)?;
let route = path
.iter()
.filter_map(|v| self.hash.get(v).copied())
.collect();
Some((score, route))
}
}
impl Debug for OvertureNetwork {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("overture maps : network")
}
}
impl routers_network::DataPlane for OvertureNetwork {
type Entry = OvertureEntryId;
type Runtime = <OvertureEdgeMetadata as routers_network::Metadata>::Runtime;
type Meta = OvertureEdgeMetadata;
fn metadata(&self, id: &OvertureEntryId) -> Option<&OvertureEdgeMetadata> {
self.meta.get(id)
}
fn point(&self, id: &OvertureEntryId) -> Option<Point> {
self.hash.get(id).map(|v| v.position)
}
fn edges_into<'a>(
&'a self,
id: OvertureEntryId,
) -> Box<dyn Iterator<Item = GraphEdge<OvertureEntryId>> + 'a> {
Box::new(
self.graph
.edges_directed(id, petgraph::Direction::Incoming)
.map(|(src, dst, &data)| (src, dst, data)),
)
}
fn edges_outof<'a>(
&'a self,
id: OvertureEntryId,
) -> Box<dyn Iterator<Item = GraphEdge<OvertureEntryId>> + 'a> {
Box::new(
self.graph
.edges_directed(id, petgraph::Direction::Outgoing)
.map(|(src, dst, &data)| (src, dst, data)),
)
}
fn fatten(
&self,
Edge {
source,
target,
weight,
id,
}: &Edge<OvertureEntryId>,
) -> Option<Edge<Node<OvertureEntryId>>> {
Some(Edge {
source: *self.hash.get(source)?,
target: *self.hash.get(target)?,
id: DirectionAwareEdgeId::new(Node::new(Point::new(0., 0.), id.index())),
weight: *weight,
})
}
}