mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-27 00:12:55 +03:00
interpolate along route for trip viz
This commit is contained in:
parent
416d0b0d7d
commit
f674527ef2
@ -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);
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user