Skip to content

Commit

Permalink
Per step geometry
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkirk committed May 18, 2024
1 parent 7120b33 commit 2f975f2
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 6 deletions.
104 changes: 104 additions & 0 deletions services/travelmux/src/api/v5/haversine_segmenter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use geo::{
algorithm::{HaversineDistance, HaversineIntermediate},
geometry::{Coord, LineString, Point},
};

pub struct HaversineSegmenter {
geometry: LineString,
next_index: usize,
}

impl HaversineSegmenter {
pub fn new(geometry: LineString) -> Self {
Self {
geometry,
next_index: 0,
}
}
pub fn next_segment(&mut self, distance_meters: f64) -> Option<LineString> {
// REVIEW: Handle case with linestring of 1 point?
if self.next_index == self.geometry.0.len() - 1 {
return None;
}
let mut distance_remaining = distance_meters;
let mut start = self.geometry.0[self.next_index];
let mut output = vec![start];
while self.next_index < self.geometry.0.len() - 1 {
let end = self.geometry.0[self.next_index + 1];
let segment_length = Point::from(start).haversine_distance(&Point::from(end));
if segment_length > distance_remaining {
// take whatever portion of the segment we can fit
let ratio = distance_remaining / segment_length;
let intermediate =
Point::from(start).haversine_intermediate(&Point::from(end), ratio);
output.push(Coord::from(intermediate));
if self.geometry.0[self.next_index] == Coord::from(intermediate) {
debug_assert!(
false,
"intermediate point is the same as the start point - inifinite loop?"
);
// skip a point rather than risk infinite loop
self.next_index += 1;
}
// overwrite the last point with the intermediate value
self.geometry.0[self.next_index] = Coord::from(intermediate);
break;
}

output.push(end);
distance_remaining -= segment_length;
start = end;
self.next_index += 1;
}
Some(LineString::new(output))
}
}

#[cfg(test)]
mod test {
use super::*;
use approx::assert_relative_eq;
use geo::{point, wkt, HaversineDestination};

#[test]
fn test_segmenter() {
// paris to berlin (878km) to prague
let paris = point!(x: 2.3514, y: 48.8575);
let berlin = point!(x: 13.4050, y: 52.5200);
let prague = point!(x: 14.4378, y: 50.0755);

let paris_to_berlin_distance = LineString::new(vec![paris.0, berlin.0]).haversine_length();
assert_relative_eq!(paris_to_berlin_distance, 877461.0, epsilon = 1.0);

let line_string = LineString::new(vec![paris.0, berlin.0, prague.0]);
let total_distance = line_string.haversine_length();
assert_relative_eq!(total_distance, 1_158_595.0, epsilon = 1.0);

let mut segmenter = HaversineSegmenter::new(line_string);

let east_of_paris = point!(x: 2.467660089582291, y: 48.90485360250366);
let segment_1 = segmenter.next_segment(10_000.0).unwrap();
assert_relative_eq!(segment_1.haversine_length(), 10_000.0, epsilon = 1e-9);
assert_relative_eq!(segment_1, LineString::new(vec![paris.0, east_of_paris.0]));

// next one should pick up where the last one left off
let segment_2 = segmenter.next_segment(10_000.0).unwrap();
assert_eq!(segment_1.0.last(), segment_2.0.first());

let east_of_berlin = point!(x: 13.482210264987538, y: 52.34640526357316);
let segment_3 = segmenter.next_segment(paris_to_berlin_distance).unwrap();
let expected = LineString::new(vec![
*segment_2.0.last().unwrap(),
berlin.0,
east_of_berlin.0,
]);
assert_relative_eq!(segment_3, expected);

// overshoot it
let next = segmenter.next_segment(total_distance).unwrap();
assert_relative_eq!(next, LineString::new(vec![east_of_berlin.0, prague.0]));

let next = segmenter.next_segment(4.0);
assert!(next.is_none());
}
}
1 change: 1 addition & 0 deletions services/travelmux/src/api/v5/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod directions;
mod error;
mod haversine_segmenter;
mod osrm_api;
pub mod plan;
mod travel_modes;
Expand Down
2 changes: 1 addition & 1 deletion services/travelmux/src/api/v5/osrm_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ impl BannerInstruction {
ManeuverType::Merge => {}
*/
ManeuverType::RoundaboutEnter => (RoundaboutEnter, None), // Enter/Exit?
ManeuverType::RoundaboutExit => (RoundaboutExit, None), // Enter/Exit?
ManeuverType::RoundaboutExit => (RoundaboutExit, None), // Enter/Exit?
/*
ManeuverType::FerryEnter => {}
ManeuverType::FerryExit => {}
Expand Down
21 changes: 16 additions & 5 deletions services/travelmux/src/api/v5/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::time::{Duration, SystemTime};
use super::error::{PlanResponseErr, PlanResponseOk};
use super::TravelModes;

use crate::api::v5::haversine_segmenter::HaversineSegmenter;
use crate::api::AppState;
use crate::error::ErrorType;
use crate::otp::otp_api;
Expand Down Expand Up @@ -367,7 +368,12 @@ impl Maneuver {
}
}

fn from_otp(otp: otp_api::Step, leg: &otp_api::Leg, distance_unit: DistanceUnit) -> Self {
fn from_otp(
otp: otp_api::Step,
geometry: LineString,
leg: &otp_api::Leg,
distance_unit: DistanceUnit,
) -> Self {
let instruction = build_instruction(
leg.mode,
otp.relative_direction,
Expand All @@ -385,9 +391,6 @@ impl Maneuver {
};

let duration_seconds = otp.distance / leg.distance * leg.duration_seconds();

log::error!("TODO: synthesize geometry for OTP steps");
let geometry = LineString::new(vec![]);
Self {
instruction,
r#type: otp.relative_direction.into(),
Expand Down Expand Up @@ -494,6 +497,8 @@ impl Leg {
let from_place: Place = (&otp.from).into();
let to_place: Place = (&otp.to).into();

let mut distance_so_far = 0.0;
let mut segmenter = HaversineSegmenter::new(geometry.clone());
let mode_leg = match otp.mode {
otp_api::TransitMode::Walk
| otp_api::TransitMode::Bicycle
Expand All @@ -502,7 +507,13 @@ impl Leg {
.steps
.iter()
.cloned()
.map(|otp_step| Maneuver::from_otp(otp_step, otp, distance_unit))
.map(|otp_step| {
// compute step geometry by distance along leg geometry
let step_geometry =
segmenter.next_segment(otp_step.distance).expect("TODO");
distance_so_far += otp_step.distance;
Maneuver::from_otp(otp_step, step_geometry, otp, distance_unit)
})
.collect();

// OTP doesn't include an arrival step like valhalla, so we synthesize one
Expand Down

0 comments on commit 2f975f2

Please sign in to comment.