Skip to content

Commit

Permalink
[travelmux] add start/end to each trip leg
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkirk committed Apr 10, 2024
1 parent 5fa2f3d commit 359b8df
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 36 deletions.
169 changes: 145 additions & 24 deletions services/travelmux/src/api/v4/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ use polyline::decode_polyline;
use reqwest::header::{HeaderName, HeaderValue};
use serde::{de, de::IntoDeserializer, de::Visitor, Deserialize, Deserializer, Serialize};
use std::fmt;
use std::time::{Duration, SystemTime};

use super::error::{PlanResponseErr, PlanResponseOk};
use crate::api::AppState;
use crate::error::ErrorType;
use crate::otp::otp_api;
use crate::otp::otp_api::RelativeDirection;
use crate::util::serialize_rect_to_lng_lat;
use crate::util::{deserialize_point_from_lat_lon, extend_bounds};
use crate::util::{
deserialize_point_from_lat_lon, extend_bounds, serialize_rect_to_lng_lat,
serialize_system_time_as_millis, system_time_from_millis,
};
use crate::valhalla::valhalla_api;
use crate::valhalla::valhalla_api::ManeuverType;
use crate::valhalla::valhalla_api::{LonLat, ManeuverType};
use crate::{DistanceUnit, Error, TravelMode};

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
Expand Down Expand Up @@ -48,9 +51,11 @@ pub struct Itinerary {
/// seconds
duration: f64,
/// unix millis, UTC
start_time: u64,
#[serde(serialize_with = "serialize_system_time_as_millis")]
start_time: SystemTime,
/// unix millis, UTC
end_time: u64,
#[serde(serialize_with = "serialize_system_time_as_millis")]
end_time: SystemTime,
distance: f64,
distance_units: DistanceUnit,
#[serde(serialize_with = "serialize_rect_to_lng_lat")]
Expand All @@ -65,16 +70,34 @@ impl Itinerary {
geo::coord!(x: valhalla.summary.max_lon, y: valhalla.summary.max_lat),
);

use std::time::Duration;
fn time_since_epoch() -> Duration {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time is after unix epoch")
}
let start_time = SystemTime::now();
let end_time = start_time + Duration::from_millis((valhalla.summary.time * 1000.0) as u64);
debug_assert!(
valhalla.locations.len() == valhalla.legs.len() + 1,
"assuming each leg has a start and end location"
);

let mut start_time = SystemTime::now();
let legs = valhalla
.legs
.iter()
.zip(valhalla.locations.windows(2))
.map(|(v_leg, locations)| {
let leg_start_time = start_time;
let leg_end_time =
start_time + Duration::from_millis((v_leg.summary.time * 1000.0) as u64);
start_time = leg_end_time;
Leg::from_valhalla(
v_leg,
mode,
leg_start_time,
leg_end_time,
locations[0],
locations[1],
)
})
.collect();

let start_time = time_since_epoch().as_millis() as u64;
let end_time = start_time + (valhalla.summary.time * 1000.0) as u64;
Self {
mode,
start_time,
Expand All @@ -83,11 +106,7 @@ impl Itinerary {
distance: valhalla.summary.length,
bounds,
distance_units: valhalla.units,
legs: valhalla
.legs
.iter()
.map(|v_leg| Leg::from_valhalla(v_leg, mode))
.collect(),
legs,
}
}

Expand All @@ -113,10 +132,11 @@ impl Itinerary {
};
extend_bounds(&mut itinerary_bounds, &leg_bounds);
}

Ok(Self {
duration: itinerary.duration as f64,
start_time: itinerary.start_time,
end_time: itinerary.end_time,
start_time: system_time_from_millis(itinerary.start_time),
end_time: system_time_from_millis(itinerary.end_time),
mode,
distance: distance_meters / 1000.0,
distance_units: DistanceUnit::Kilometers,
Expand All @@ -126,6 +146,32 @@ impl Itinerary {
}
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Place {
#[serde(flatten)]
location: LonLat,
name: Option<String>
}

impl From<&otp_api::Place> for Place {
fn from(value: &otp_api::Place) -> Self {
Self {
location: value.location.into(),
name: value.name.clone(),
}
}
}

impl From<valhalla_api::LonLat> for Place {
fn from(value: LonLat) -> Self {
Self {
location: value,
name: None,
}
}
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
struct Leg {
Expand All @@ -137,6 +183,24 @@ struct Leg {

#[serde(flatten)]
mode_leg: ModeLeg,

/// Beginning of the leg
from_place: Place,

/// End of the Leg
to_place: Place,

// This is mostly OTP specific. We can synthesize a value from the valhalla response, but we
// don't currently use it.
/// Start time of the leg
#[serde(serialize_with = "serialize_system_time_as_millis")]
start_time: SystemTime,

// This is mostly OTP specific. We can synthesize a value from the valhalla response, but we
// don't currently use it.
/// Start time of the leg
#[serde(serialize_with = "serialize_system_time_as_millis")]
end_time: SystemTime,
}

// Should we just pass the entire OTP leg?
Expand All @@ -147,7 +211,7 @@ type TransitLeg = otp_api::Leg;
enum ModeLeg {
// REVIEW: rename? There is a boolean field for OTP called TransitLeg
#[serde(rename = "transitLeg")]
Transit(TransitLeg),
Transit(Box<TransitLeg>),

#[serde(rename = "maneuvers")]
NonTransit(Vec<Maneuver>),
Expand Down Expand Up @@ -231,19 +295,36 @@ impl Leg {
}
_ => {
// assume everything else is transit
ModeLeg::Transit(otp.clone())
ModeLeg::Transit(Box::new(otp.clone()))
}
};

let from_place = (&otp.from).into();
let to_place = (&otp.to).into();
Ok(Self {
from_place,
to_place,
start_time: system_time_from_millis(otp.start_time),
end_time: system_time_from_millis(otp.end_time),
geometry,
mode: otp.mode.into(),
mode_leg,
})
}

fn from_valhalla(valhalla: &valhalla_api::Leg, travel_mode: TravelMode) -> Self {
fn from_valhalla(
valhalla: &valhalla_api::Leg,
travel_mode: TravelMode,
start_time: SystemTime,
end_time: SystemTime,
from_place: LonLat,
to_place: LonLat,
) -> Self {
Self {
start_time,
end_time,
from_place: from_place.into(),
to_place: to_place.into(),
geometry: valhalla.shape.clone(),
mode: travel_mode,
mode_leg: ModeLeg::NonTransit(
Expand Down Expand Up @@ -462,6 +543,18 @@ mod tests {
epsilon = 1e-4
);

assert_relative_eq!(
geo::Point::from(first_leg.from_place.location),
geo::point!(x: -122.339414, y: 47.575837)
);
assert_relative_eq!(
geo::Point::from(first_leg.to_place.location),
geo::point!(x:-122.347234, y: 47.651048)
);
assert!(
first_leg.to_place.name.is_none()
);

let ModeLeg::NonTransit(maneuvers) = &first_leg.mode_leg else {
panic!("unexpected transit leg")
};
Expand Down Expand Up @@ -497,6 +590,19 @@ mod tests {
epsilon = 1e-4
);

assert_relative_eq!(
geo::Point::from(first_leg.from_place.location),
geo::point!(x: -122.339414, y: 47.575837)
);
assert_relative_eq!(
geo::Point::from(first_leg.to_place.location),
geo::point!(x: -122.334106, y: 47.575924)
);
assert_eq!(
first_leg.to_place.name.as_ref().unwrap(),
"1st Ave S & S Hanford St"
);

assert_eq!(first_leg.mode, TravelMode::Walk);
let ModeLeg::NonTransit(maneuvers) = &first_leg.mode_leg else {
panic!("expected non-transit leg")
Expand Down Expand Up @@ -541,6 +647,21 @@ mod tests {
let first_leg = legs.get(0).unwrap().as_object().unwrap();
let mode = first_leg.get("mode").unwrap().as_str().unwrap();
assert_eq!(mode, "WALK");

let mode = first_leg
.get("startTime")
.expect("field missing")
.as_u64()
.expect("unexpected type. expected u64");
assert_eq!(mode, 1708728373000);

let mode = first_leg
.get("endTime")
.expect("field missing")
.as_u64()
.expect("unexpected type. expected u64");
assert_eq!(mode, 1708728745000);

assert!(first_leg.get("transitLeg").is_none());
let maneuvers = first_leg.get("maneuvers").unwrap().as_array().unwrap();
let first_maneuver = maneuvers.get(0).unwrap();
Expand Down
37 changes: 35 additions & 2 deletions services/travelmux/src/otp/otp_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ pub struct Leg {
pub route_color: Option<String>,
// Present, but empty, for transit legs. Non-empty for non-transit legs.
pub steps: Vec<Step>,

pub from: Place,
pub to: Place,

/// What time the leg starts, in millis since Unix epoch (UTC)
pub start_time: u64,

/// What time the leg starts, in millis since Unix epoch (UTC)
pub end_time: u64,

#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
Expand Down Expand Up @@ -160,13 +170,36 @@ pub struct LegGeometry {
pub points: String,
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
#[serde(rename_all = "camelCase")]
pub struct Location {
pub struct LonLat {
pub lat: f64,
pub lon: f64,
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Place {
#[serde(flatten)]
pub location: LonLat,

/// millis since Unix epoch
/// I think it's None iff it's the trip Origin
pub arrival: Option<u64>,

/// millis since Unix epoch
/// I think it's None iff it's the trip Destination
pub departure: Option<u64>,

/// "Civic Center / Grand Park Station"
/// Transit stops often have names. But this is often blank when
/// the place is some random lat/lon (e.g. the users destination)
pub name: Option<String>,

#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
24 changes: 22 additions & 2 deletions services/travelmux/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use geo::{Point, Rect};
use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serializer};
use std::time::Duration;
use serde::{
ser::{Error, SerializeStruct},
Deserialize, Deserializer, Serializer,
};
use std::time::{Duration, SystemTime};

pub fn deserialize_point_from_lat_lon<'de, D>(deserializer: D) -> Result<Point, D::Error>
where
Expand Down Expand Up @@ -70,3 +73,20 @@ pub fn serialize_rect_to_lng_lat<S: Serializer>(
struct_serializer.serialize_field("max", &[rect.max().x, rect.max().y])?;
struct_serializer.end()
}

pub fn serialize_system_time_as_millis<S>(
time: &SystemTime,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let since_epoch = time
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_e| S::Error::custom("time is before epoch"))?;
serializer.serialize_u64(since_epoch.as_millis() as u64)
}

pub fn system_time_from_millis(millis: u64) -> SystemTime {
std::time::UNIX_EPOCH + Duration::from_millis(millis)
}
Loading

0 comments on commit 359b8df

Please sign in to comment.