Skip to content

Commit

Permalink
[travelmux] compute bearing between steps
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkirk committed May 31, 2024
1 parent aac689a commit c7d977d
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 21 deletions.
80 changes: 59 additions & 21 deletions services/travelmux/src/api/v6/osrm_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use super::plan::{Itinerary, Leg, Maneuver, ModeLeg};
use crate::util::serde_util::{
serialize_line_string_as_polyline6, serialize_point_as_lon_lat_pair,
};
use crate::util::{bearing_at_end, bearing_at_start};
use crate::valhalla::valhalla_api::ManeuverType;
use crate::{DistanceUnit, TravelMode};
use geo::{LineString, Point};
Expand Down Expand Up @@ -75,17 +76,45 @@ impl RouteLeg {
}
ModeLeg::NonTransit(non_transit_leg) => {
let summary = non_transit_leg.substantial_street_names.join(", ");
let mut steps: Vec<_> = non_transit_leg
.maneuvers
.windows(2)
.map(|this_and_next| {
let maneuver = this_and_next[0].clone();
let next_maneuver = this_and_next.get(1);
RouteStep::from_maneuver(maneuver, next_maneuver, value.mode, distance_unit)
})
.collect();

debug_assert!(non_transit_leg.maneuvers.len() >= 2);
let mut steps = Vec::with_capacity(non_transit_leg.maneuvers.len());
if let Some(first_maneuver) = non_transit_leg.maneuvers.first() {
let first_step = RouteStep::from_maneuver(
None,
first_maneuver.clone(),
non_transit_leg.maneuvers.get(1),
value.mode,
distance_unit,
);
steps.push(first_step);
}

let middle_steps = non_transit_leg.maneuvers.windows(3).map(|maneuvers| {
let prev_maneuver = &maneuvers[0];
let maneuver = maneuvers[1].clone();
let next_maneuver = &maneuvers[2];

RouteStep::from_maneuver(
Some(prev_maneuver),
maneuver,
Some(next_maneuver),
value.mode,
distance_unit,
)
});
steps.extend(middle_steps);

if let Some(final_maneuver) = non_transit_leg.maneuvers.last() {
let prev_maneuver = if non_transit_leg.maneuvers.len() < 2 {
None
} else {
non_transit_leg
.maneuvers
.get(non_transit_leg.maneuvers.len() - 2)
};
let final_step = RouteStep::from_maneuver(
prev_maneuver,
final_maneuver.clone(),
None,
value.mode,
Expand Down Expand Up @@ -134,7 +163,7 @@ pub struct RouteStep {
pub mode: TravelMode,

/// A `StepManeuver` object representing the maneuver.
pub maneuver: StepManeuver,
pub maneuver: RouteStepManeuver,

/// A list of `BannerInstruction` objects that represent all signs on the step.
pub banner_instructions: Option<Vec<VisualInstructionBanner>>,
Expand All @@ -145,13 +174,20 @@ pub struct RouteStep {

impl RouteStep {
fn from_maneuver(
prev_maneuver: Option<&Maneuver>,
maneuver: Maneuver,
next_maneuver: Option<&Maneuver>,
mode: TravelMode,
from_distance_unit: DistanceUnit,
) -> Self {
let banner_instructions =
VisualInstructionBanner::from_maneuver(&maneuver, next_maneuver, from_distance_unit);

let bearing_after = bearing_at_start(&maneuver.geometry).unwrap_or(0);
let bearing_before = prev_maneuver
.and_then(|prev_maneuver| bearing_at_end(&prev_maneuver.geometry))
.unwrap_or(bearing_after);

RouteStep {
distance: maneuver.distance_meters(from_distance_unit),
duration: maneuver.duration_seconds,
Expand All @@ -164,8 +200,10 @@ impl RouteStep {
pronunciation: None, // TODO
destinations: None, // TODO
mode,
maneuver: StepManeuver {
maneuver: RouteStepManeuver {
location: maneuver.start_point.into(),
bearing_before,
bearing_after,
},
intersections: None, // TODO
banner_instructions,
Expand Down Expand Up @@ -461,23 +499,23 @@ pub struct VisualInstructionComponent {

#[derive(Debug, Serialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StepManeuver {
pub struct RouteStepManeuver {
/// The location of the maneuver
#[serde(serialize_with = "serialize_point_as_lon_lat_pair")]
pub location: Point,
// /// The type of maneuver. new identifiers might be introduced without changing the API, so best practice is to gracefully handle any new values
// r#type: String,
// r#type: ManeuverType,
// /// The modifier of the maneuver. new identifiers might be introduced without changing the API, so best practice is to gracefully handle any new values
// modifier: String,
// modifier: ManeuverDirection,
// /// A human-readable instruction of how to execute the returned maneuver
// instruction: String,
// /// The bearing before the turn, in degrees
// OSRM expects `bearing_before` to be snake cased.
// #[serde(rename_all = "snake_case")]
// bearing_before: f64,
// OSRM expects `bearing_after` to be snake cased.
// /// The bearing after the turn, in degrees
// bearing_after: f64,
/// The bearing before the turn, in degrees
#[serde(rename = "bearing_before")] // OSRM expects `bearing_before` to be snake cased.
bearing_before: u16,

/// The bearing after the turn, in degrees
#[serde(rename = "bearing_after")] // OSRM expects `bearing_after` to be snake cased.
bearing_after: u16,
}

#[derive(Debug, Serialize, PartialEq, Clone)]
Expand Down
14 changes: 14 additions & 0 deletions services/travelmux/src/api/v6/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,9 @@ async fn otp_plan(
#[cfg(test)]
mod tests {
use super::*;
use crate::util::{bearing_at_end, bearing_at_start};
use approx::assert_relative_eq;
use geo::wkt;
use serde_json::{json, Value};
use std::fs::File;
use std::io::BufReader;
Expand Down Expand Up @@ -1095,4 +1097,16 @@ mod tests {
assert_eq!(plan_error.error.status_code, 400);
assert_eq!(plan_error.error.error_code, 2154);
}

#[test]
fn maneuver_bearing() {
let a = wkt!(LINESTRING(0. 0.,1. 0.,1. 1.));
let b = wkt!(LINESTRING(1. 1., 0. 1., 0. 0.));

assert_eq!(90, bearing_at_start(&a).unwrap());
assert_eq!(0, bearing_at_end(&a).unwrap());

assert_eq!(270, bearing_at_start(&b).unwrap());
assert_eq!(180, bearing_at_end(&b).unwrap());
}
}
24 changes: 24 additions & 0 deletions services/travelmux/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod haversine_segmenter;
pub(crate) mod serde_util;

use crate::DistanceUnit;
use geo::{Coord, LineString, Point};
use std::time::{Duration, SystemTime};

pub fn extend_bounds(bounds: &mut geo::Rect, extension: &geo::Rect) {
Expand Down Expand Up @@ -38,3 +39,26 @@ pub fn convert_to_meters(distance: f64, input_units: DistanceUnit) -> f64 {
DistanceUnit::Miles => distance * METERS_PER_MILE,
}
}

/// Integers from 0 to 359 representing the bearing from the last point of the current LineString
/// North is 0°, East is 90°, South is 180°, West is 270°
pub(crate) fn bearing_between(start: Option<Coord>, end: Option<Coord>) -> Option<u16> {
let start = Point(start?);
let end = Point(end?);
use geo::HaversineBearing;
let bearing = start.haversine_bearing(end);
debug_assert!(bearing >= -180.0);
Some((bearing.round() + 360.0) as u16 % 360)
}

pub(crate) fn bearing_at_start(line_string: &LineString) -> Option<u16> {
let first = line_string.0.first();
let second = line_string.0.get(1);
bearing_between(first.copied(), second.copied())
}

pub(crate) fn bearing_at_end(line_string: &LineString) -> Option<u16> {
let last = line_string.0.last();
let second_to_last = line_string.0.get(line_string.0.len() - 2);
bearing_between(second_to_last.copied(), last.copied())
}

0 comments on commit c7d977d

Please sign in to comment.