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::mission::{clip_trips, Trip};
use crate::ui::{ShowEverything, UI};
use abstutil::{prettyprint_usize, Timer};
use abstutil::prettyprint_usize;
use ezgui::{Color, EventCtx, GeomBatch, GfxCtx, Key, ModalMenu, Text};
use geom::{Circle, Distance, Duration};
use map_model::LANE_THICKNESS;
use popdat::psrc::Mode;
use popdat::PopDat;
pub struct TripsVisualizer {
@ -16,28 +18,40 @@ pub struct TripsVisualizer {
impl TripsVisualizer {
pub fn new(ctx: &mut EventCtx, ui: &UI) -> TripsVisualizer {
let mut timer = Timer::new("initialize popdat");
let popdat: PopDat = abstutil::read_binary("../data/shapes/popdat", &mut timer)
.expect("Couldn't load popdat");
let mut trips = clip_trips(&popdat, ui, &mut timer);
timer.start_iter("calculate routes", trips.len());
for trip in trips.iter_mut() {
timer.next();
let req = trip.path_req(&ui.primary.map);
trip.route = ui
.primary
.map
.pathfind(req.clone())
.and_then(|path| path.trace(&ui.primary.map, req.start.dist_along(), None));
}
let trips = ctx.loading_screen(|_, mut timer| {
let popdat: PopDat = abstutil::read_binary("../data/shapes/popdat", &mut timer)
.expect("Couldn't load popdat");
let mut all_trips = clip_trips(&popdat, ui, &mut timer);
timer.start_iter("calculate routes", all_trips.len());
let mut final_trips = Vec::new();
for mut trip in all_trips.drain(..) {
timer.next();
let req = trip.path_req(&ui.primary.map);
if let Some(route) = ui
.primary
.map
.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 {
menu: ModalMenu::new(
"Trips Visualizer",
vec![
(Some(Key::Escape), "quit"),
(Some(Key::Dot), "forwards 1 minute"),
(Some(Key::Comma), "backwards 1 minute"),
(Some(Key::Dot), "forwards 10 seconds"),
(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::L), "goto end of day"),
],
@ -63,14 +77,22 @@ impl TripsVisualizer {
ui.primary.current_selection =
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") {
return true;
} else if self.time != last_time && self.menu.action("forwards 1 minute") {
self.time += Duration::minutes(1);
} else if self.time != Duration::ZERO && self.menu.action("backwards 1 minute") {
self.time -= Duration::minutes(1);
} else if self.time != last_time && self.menu.action("forwards 10 seconds") {
self.time += ten_secs;
} else if self.time + thirty_mins <= last_time && self.menu.action("forwards 30 minutes") {
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") {
self.time = Duration::ZERO;
} else if self.time != last_time && self.menu.action("goto end of day") {
@ -95,18 +117,41 @@ impl TripsVisualizer {
let mut batch = GeomBatch::new();
for idx in &self.active_trips {
let trip = &self.trips[*idx];
let from = ui.primary.map.get_b(trip.from);
let to = ui.primary.map.get_b(trip.to);
let percent = (self.time - trip.depart_at) / trip.trip_time;
let percent = ((self.time - trip.depart_at) / trip.trip_time) as f32;
batch.push(
Color::RED.alpha(1.0 - percent),
Circle::new(from.polygon.center(), Distance::meters(100.0)).to_polygon(),
);
batch.push(
Color::BLUE.alpha(percent),
Circle::new(to.polygon.center(), Distance::meters(100.0)).to_polygon(),
);
if true {
let pl = trip.route.as_ref().unwrap();
let color = match trip.mode {
Mode::Drive => Color::RED,
Mode::Walk => Color::GREEN,
Mode::Bike => Color::BLUE,
}
.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);

View File

@ -8,6 +8,7 @@ use crate::{BusRouteID, BusStopID, LaneID, LaneType, Map, Position, Traversable,
use geom::{Distance, PolyLine};
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeSet, VecDeque};
use std::fmt;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PathStep {
@ -233,7 +234,7 @@ impl Path {
}
}
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct PathRequest {
pub start: Position,
pub end: Position,
@ -241,6 +242,27 @@ pub struct PathRequest {
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>) {
if steps.is_empty() {
panic!("Empty Path");

View File

@ -167,7 +167,7 @@ impl TripSpawner {
self.trips.drain(..).zip(paths)
{
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;
}
let path = maybe_path.unwrap();