use chrono::{DateTime, Utc};
use geo::Point;
use routers_network::{Edge, Entry, Network};
use routers_shard::{Geohash, GeohashStrategy, ShardingStrategy};
use routers_transition::candidate::CollapsedPath;
use routers_transition::matcher::{Continuation, Origin, Trip};
use serde::{Deserialize, Serialize};
use buffa::Message;
use schema::proto::routers::realtime::v1 as proto;
use crate::bus::{Wire, postcard_wire};
use crate::store::Storable;
macro_rules! wire_id {
($(#[$doc:meta])* $name:ident) => {
$(#[$doc])*
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct $name(pub u64);
impl From<u64> for $name {
fn from(value: u64) -> Self {
Self(value)
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
};
}
wire_id! {
VehicleId
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(bound(serialize = "E: Serialize", deserialize = "E: Deserialize<'de>"))]
pub struct MatchContext<E: Entry> {
pub continuation: Continuation<E>,
pub vehicle_id: VehicleId,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(bound(serialize = "E: Serialize", deserialize = "E: Deserialize<'de>"))]
pub enum MatchReply<E: Entry> {
Solved { diff: MatchedDiff<E>, trip: Trip<E> },
NoMatch,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(bound(serialize = "E: Serialize", deserialize = "E: Deserialize<'de>"))]
pub struct MatchedEvent<E: Entry> {
pub vehicle_id: VehicleId,
pub diff: MatchedDiff<E>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound(serialize = "E: Serialize", deserialize = "E: Deserialize<'de>"))]
pub struct MatchedLayer<E: Entry> {
pub timestamp: i64,
pub edge: Edge<E>,
pub position: Point,
pub path: Vec<Point>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound(serialize = "E: Serialize", deserialize = "E: Deserialize<'de>"))]
pub struct MatchedDiff<E: Entry> {
pub revision: u64,
pub downgraded: bool,
pub layers: Vec<MatchedLayer<E>>,
}
impl<E: Entry> MatchedDiff<E> {
pub fn new<N: Network<Entry = E>>(
solution: &CollapsedPath<'_, E>,
origins: &[Origin],
map: &N,
revision: u64,
) -> Self {
let layers = solution
.route
.iter()
.zip(origins)
.enumerate()
.filter_map(|(index, (chosen, origin))| {
let candidate = solution.candidates.candidate(chosen)?;
let path = match index.checked_sub(1) {
Some(hop) => solution.hop_geometry(hop, map),
None => Vec::new(),
};
Some(MatchedLayer {
timestamp: origin.timestamp,
edge: candidate.edge,
position: candidate.position,
path,
})
})
.collect();
Self {
revision,
downgraded: false,
layers,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Payload {
pub vehicle_id: VehicleId,
#[serde(with = "chrono::serde::ts_microseconds")]
pub timestamp: DateTime<Utc>,
pub point: Point,
}
postcard_wire!(MatchContext<E: Entry>);
postcard_wire!(MatchReply<E: Entry>);
postcard_wire!(MatchedEvent<E: Entry>);
impl Wire for Payload {
fn encode(&self) -> anyhow::Result<Vec<u8>> {
Ok(proto::Payload::from(self).encode_to_vec())
}
fn decode(bytes: &[u8]) -> anyhow::Result<Self> {
Ok(Self::from(proto::Payload::decode_from_slice(bytes)?))
}
}
impl From<&Payload> for proto::Payload {
fn from(payload: &Payload) -> Self {
proto::Payload {
vehicle_id: payload.vehicle_id.0,
timestamp: buffa::MessageField::some(
buffa_types::google::protobuf::Timestamp::from_unix(
payload.timestamp.timestamp(),
payload.timestamp.timestamp_subsec_nanos() as i32,
),
),
point: buffa::MessageField::some(schema::proto::routers::model::v1::Coordinate {
longitude: payload.point.x(),
latitude: payload.point.y(),
..Default::default()
}),
..Default::default()
}
}
}
impl From<proto::Payload> for Payload {
fn from(payload: proto::Payload) -> Self {
let point = payload.point.into_option().unwrap_or_default();
let timestamp = payload.timestamp.into_option().unwrap_or_default();
Payload {
vehicle_id: VehicleId(payload.vehicle_id),
timestamp: DateTime::from_timestamp(timestamp.seconds, timestamp.nanos as u32)
.unwrap_or_default(),
point: Point::new(point.longitude, point.latitude),
}
}
}
impl Payload {
pub fn as_event(&self) -> RawEvent {
RawEvent {
vehicle_id: self.vehicle_id,
point: self.point,
timestamp: self.timestamp,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawEvent {
pub vehicle_id: VehicleId,
pub point: Point,
#[serde(with = "chrono::serde::ts_microseconds")]
pub timestamp: DateTime<Utc>,
}
pub const SHARD_PRECISION: u8 = 4;
pub fn shard_of(point: Point) -> Geohash {
GeohashStrategy::with_precision(SHARD_PRECISION).locate(point)
}
impl Storable for RawEvent {
type ShardId = Geohash;
type Key = VehicleId;
fn shard_id(&self) -> Self::ShardId {
shard_of(self.point)
}
fn key(&self) -> Self::Key {
self.vehicle_id
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn payload_round_trips_over_the_wire() {
let payload = Payload {
vehicle_id: VehicleId(0xdead_beef_cafe_f00d),
timestamp: DateTime::from_timestamp_micros(1_775_000_000_123_456).unwrap(),
point: Point::new(150.871294, -33.938879),
};
let bytes = payload.encode().expect("payload must encode");
let decoded = Payload::decode(&bytes).expect("payload must decode");
assert_eq!(decoded.vehicle_id, payload.vehicle_id);
assert_eq!(decoded.timestamp, payload.timestamp);
assert_eq!(decoded.point, payload.point);
}
}