interpolate along route for trip viz

This commit is contained in:
Dustin Carlino 2019-05-24 11:52:29 -07:00
parent 416d0b0d7d
commit f674527ef2
3 changed files with 102 additions and 35 deletions

View File

@ -1,9 +1,11 @@
use crate::common::CommonState; use crate::common::CommonState;
use crate::mission::{clip_trips, Trip}; use crate::mission::{clip_trips, Trip};
use crate::ui::{ShowEverything, UI}; use crate::ui::{ShowEverything, UI};
use abstutil::{prettyprint_usize, Timer}; use abstutil::prettyprint_usize;
use ezgui::{Color, EventCtx, GeomBatch, GfxCtx, Key, ModalMenu, Text}; use ezgui::{Color, EventCtx, GeomBatch, GfxCtx, Key, ModalMenu, Text};
use geom::{Circle, Distance, Duration}; use geom::{Circle, Distance, Duration};
use map_model::LANE_THICKNESS;
use popdat::psrc::Mode;
use popdat::PopDat; use popdat::PopDat;
pub struct TripsVisualizer { pub struct TripsVisualizer {
@ -16,28 +18,40 @@ pub struct TripsVisualizer {
impl TripsVisualizer { impl TripsVisualizer {
pub fn new(ctx: &mut EventCtx, ui: &UI) -> TripsVisualizer { pub fn new(ctx: &mut EventCtx, ui: &UI) -> TripsVisualizer {
let mut timer = Timer::new("initialize popdat"); let trips = ctx.loading_screen(|_, mut timer| {
let popdat: PopDat = abstutil::read_binary("../data/shapes/popdat", &mut timer) let popdat: PopDat = abstutil::read_binary("../data/shapes/popdat", &mut timer)
.expect("Couldn't load popdat"); .expect("Couldn't load popdat");
let mut trips = clip_trips(&popdat, ui, &mut timer); let mut all_trips = clip_trips(&popdat, ui, &mut timer);
timer.start_iter("calculate routes", trips.len()); timer.start_iter("calculate routes", all_trips.len());
for trip in trips.iter_mut() { let mut final_trips = Vec::new();
timer.next(); for mut trip in all_trips.drain(..) {
let req = trip.path_req(&ui.primary.map); timer.next();
trip.route = ui let req = trip.path_req(&ui.primary.map);
.primary if let Some(route) = ui
.map .primary
.pathfind(req.clone()) .map
.and_then(|path| path.trace(&ui.primary.map, req.start.dist_along(), None)); .pathfind(req.clone())
} .and_then(|path| path.trace(&ui.primary.map, req.start.dist_along(), None))
{
trip.route = Some(route);
final_trips.push(trip);
} else {
timer.warn(format!("Couldn't satisfy {}", req));
}
}
final_trips
});
// TODO It'd be awesome to use the generic timer controls for this
TripsVisualizer { TripsVisualizer {
menu: ModalMenu::new( menu: ModalMenu::new(
"Trips Visualizer", "Trips Visualizer",
vec![ vec![
(Some(Key::Escape), "quit"), (Some(Key::Escape), "quit"),
(Some(Key::Dot), "forwards 1 minute"), (Some(Key::Dot), "forwards 10 seconds"),
(Some(Key::Comma), "backwards 1 minute"), (Some(Key::RightArrow), "forwards 30 minutes"),
(Some(Key::Comma), "backwards 10 seconds"),
(Some(Key::LeftArrow), "backwards 30 minutes"),
(Some(Key::F), "goto start of day"), (Some(Key::F), "goto start of day"),
(Some(Key::L), "goto end of day"), (Some(Key::L), "goto end of day"),
], ],
@ -63,14 +77,22 @@ impl TripsVisualizer {
ui.primary.current_selection = ui.primary.current_selection =
ui.handle_mouseover(ctx, &ui.primary.sim, &ShowEverything::new(), false); ui.handle_mouseover(ctx, &ui.primary.sim, &ShowEverything::new(), false);
let last_time = Duration::parse("23:59:00.0").unwrap(); let last_time = Duration::parse("23:59:50.0").unwrap();
let ten_secs = Duration::seconds(10.0);
let thirty_mins = Duration::minutes(30);
if self.menu.action("quit") { if self.menu.action("quit") {
return true; return true;
} else if self.time != last_time && self.menu.action("forwards 1 minute") { } else if self.time != last_time && self.menu.action("forwards 10 seconds") {
self.time += Duration::minutes(1); self.time += ten_secs;
} else if self.time != Duration::ZERO && self.menu.action("backwards 1 minute") { } else if self.time + thirty_mins <= last_time && self.menu.action("forwards 30 minutes") {
self.time -= Duration::minutes(1); self.time += thirty_mins;
} else if self.time != Duration::ZERO && self.menu.action("backwards 10 seconds") {
self.time -= ten_secs;
} else if self.time - thirty_mins >= Duration::ZERO
&& self.menu.action("backwards 30 minutes")
{
self.time -= thirty_mins;
} else if self.time != Duration::ZERO && self.menu.action("goto start of day") { } else if self.time != Duration::ZERO && self.menu.action("goto start of day") {
self.time = Duration::ZERO; self.time = Duration::ZERO;
} else if self.time != last_time && self.menu.action("goto end of day") { } else if self.time != last_time && self.menu.action("goto end of day") {
@ -95,18 +117,41 @@ impl TripsVisualizer {
let mut batch = GeomBatch::new(); let mut batch = GeomBatch::new();
for idx in &self.active_trips { for idx in &self.active_trips {
let trip = &self.trips[*idx]; let trip = &self.trips[*idx];
let from = ui.primary.map.get_b(trip.from); let percent = (self.time - trip.depart_at) / trip.trip_time;
let to = ui.primary.map.get_b(trip.to);
let percent = ((self.time - trip.depart_at) / trip.trip_time) as f32; if true {
batch.push( let pl = trip.route.as_ref().unwrap();
Color::RED.alpha(1.0 - percent), let color = match trip.mode {
Circle::new(from.polygon.center(), Distance::meters(100.0)).to_polygon(), Mode::Drive => Color::RED,
); Mode::Walk => Color::GREEN,
batch.push( Mode::Bike => Color::BLUE,
Color::BLUE.alpha(percent), }
Circle::new(to.polygon.center(), Distance::meters(100.0)).to_polygon(), .alpha(0.5);
); batch.push(
color,
Circle::new(
pl.dist_along(percent * pl.length()).0,
// Draw bigger circles when zoomed out, but don't go smaller than the lane
// once fully zoomed in.
(Distance::meters(10.0) / g.canvas.cam_zoom).max(LANE_THICKNESS),
)
.to_polygon(),
);
// TODO Draw the entire route, once sharp angled polylines are fixed
} else {
// Draw the start and end, gradually fading the color.
let from = ui.primary.map.get_b(trip.from);
let to = ui.primary.map.get_b(trip.to);
batch.push(
Color::RED.alpha(1.0 - (percent as f32)),
Circle::new(from.polygon.center(), Distance::meters(100.0)).to_polygon(),
);
batch.push(
Color::BLUE.alpha(percent as f32),
Circle::new(to.polygon.center(), Distance::meters(100.0)).to_polygon(),
);
}
} }
batch.draw(g); batch.draw(g);

View File

@ -8,6 +8,7 @@ use crate::{BusRouteID, BusStopID, LaneID, LaneType, Map, Position, Traversable,
use geom::{Distance, PolyLine}; use geom::{Distance, PolyLine};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeSet, VecDeque}; use std::collections::{BTreeSet, VecDeque};
use std::fmt;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PathStep { pub enum PathStep {
@ -233,7 +234,7 @@ impl Path {
} }
} }
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct PathRequest { pub struct PathRequest {
pub start: Position, pub start: Position,
pub end: Position, pub end: Position,
@ -241,6 +242,27 @@ pub struct PathRequest {
pub can_use_bus_lanes: bool, pub can_use_bus_lanes: bool,
} }
impl fmt::Display for PathRequest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"PathRequest({} along {}... to {} along {}",
self.start.dist_along(),
self.start.lane(),
self.end.dist_along(),
self.end.lane()
)?;
// TODO can_use_bike_lanes and can_use_bus_lanes are mutex, encode that directly.
if self.can_use_bike_lanes {
write!(f, ", bike lanes)")
} else if self.can_use_bus_lanes {
write!(f, ", bus lanes)")
} else {
write!(f, ")")
}
}
}
fn validate(map: &Map, steps: &Vec<PathStep>) { fn validate(map: &Map, steps: &Vec<PathStep>) {
if steps.is_empty() { if steps.is_empty() {
panic!("Empty Path"); panic!("Empty Path");

View File

@ -167,7 +167,7 @@ impl TripSpawner {
self.trips.drain(..).zip(paths) self.trips.drain(..).zip(paths)
{ {
if maybe_path.is_none() { if maybe_path.is_none() {
timer.warn(format!("{:?} couldn't find the first path {:?}", spec, req)); timer.warn(format!("{:?} couldn't find the first path {}", spec, req));
continue; continue;
} }
let path = maybe_path.unwrap(); let path = maybe_path.unwrap();