Show two LTN routes, to show effects of the new filters

This commit is contained in:
Dustin Carlino 2021-11-11 14:42:31 -08:00
parent 08223efed5
commit 29fbf74c2d
3 changed files with 176 additions and 81 deletions

View File

@ -1,6 +1,7 @@
use std::cmp::Ordering;
use std::collections::BTreeSet;
use geom::{Duration, Polygon, Time};
use geom::{Distance, Duration, Polygon, Time};
use map_gui::ID;
use map_model::{IntersectionID, Map, RoadID};
use sim::{AgentType, TripMode, TripPhaseType};
@ -369,6 +370,55 @@ pub fn cmp_duration_shorter(app: &App, after: Duration, before: Duration) -> Vec
}
}
/// Shorter is better
pub fn cmp_dist(txt: &mut Text, app: &App, dist: Distance, shorter: &str, longer: &str) {
match dist.cmp(&Distance::ZERO) {
Ordering::Less => {
txt.add_line(
Line(format!(
"{} {}",
(-dist).to_string(&app.opts.units),
shorter
))
.fg(Color::GREEN),
);
}
Ordering::Greater => {
txt.add_line(
Line(format!("{} {}", dist.to_string(&app.opts.units), longer)).fg(Color::RED),
);
}
Ordering::Equal => {}
}
}
/// Shorter is better
pub fn cmp_duration(txt: &mut Text, app: &App, duration: Duration, shorter: &str, longer: &str) {
match duration.cmp(&Duration::ZERO) {
Ordering::Less => {
txt.add_line(
Line(format!(
"{} {}",
(-duration).to_string(&app.opts.units),
shorter
))
.fg(Color::GREEN),
);
}
Ordering::Greater => {
txt.add_line(
Line(format!(
"{} {}",
duration.to_string(&app.opts.units),
longer
))
.fg(Color::RED),
);
}
Ordering::Equal => {}
}
}
pub fn color_for_mode(app: &App, m: TripMode) -> Color {
match m {
TripMode::Walk => app.cs.unzoomed_pedestrian,

View File

@ -1,15 +1,15 @@
use geom::{Distance, Polygon};
use geom::{Distance, Duration, Polygon};
use map_model::NORMAL_LANE_THICKNESS;
use sim::{TripEndpoint, TripMode};
use widgetry::mapspace::{ObjectID, ToggleZoomed, World};
use widgetry::{
Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Outcome, Panel, State, VerticalAlignment,
Widget,
Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
VerticalAlignment, Widget,
};
use super::Neighborhood;
use crate::app::{App, Transition};
use crate::common::{InputWaypoints, WaypointID};
use crate::common::{cmp_dist, cmp_duration, InputWaypoints, WaypointID};
pub struct RoutePlanner {
panel: Panel,
@ -21,7 +21,8 @@ pub struct RoutePlanner {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum ID {
MainRoute,
RouteAfterFilters,
RouteBeforeFilters,
Waypoint(WaypointID),
}
impl ObjectID for ID {}
@ -49,6 +50,9 @@ impl RoutePlanner {
.text("Back to editing modal filters")
.hotkey(Key::Escape)
.build_def(ctx),
Line("Warning: Time estimates assume freeflow conditions (no traffic)")
.fg(Color::RED)
.into_widget(ctx),
self.waypoints.get_panel_widget(ctx),
]))
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
@ -58,7 +62,7 @@ impl RoutePlanner {
let mut world = self.calculate_paths(ctx, app);
self.waypoints
.rebuild_world(ctx, &mut world, ID::Waypoint, 1);
.rebuild_world(ctx, &mut world, ID::Waypoint, 2);
world.initialize_hover(ctx);
world.rebuilt_during_drag(&self.world);
self.world = world;
@ -66,37 +70,124 @@ impl RoutePlanner {
fn calculate_paths(&self, ctx: &mut EventCtx, app: &App) -> World<ID> {
let map = &app.primary.map;
let mut world = World::bounded(map.get_bounds());
let mut params = map.routing_params().clone();
params
.avoid_roads
.extend(app.session.modal_filters.roads.keys().cloned());
let cache_custom = true;
// First the route respecting the filters
let (total_time_after, total_dist_after) = {
let mut params = map.routing_params().clone();
params
.avoid_roads
.extend(app.session.modal_filters.roads.keys().cloned());
let cache_custom = true;
let mut draw_route = ToggleZoomed::builder();
let mut hitbox_pieces = Vec::new();
for pair in self.waypoints.get_waypoints().windows(2) {
if let Some(pl) = TripEndpoint::path_req(pair[0], pair[1], TripMode::Drive, map)
.and_then(|req| map.pathfind_with_params(req, &params, cache_custom).ok())
.and_then(|path| path.trace(map))
{
let shape = pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS);
draw_route
.unzoomed
.push(Color::RED.alpha(0.8), shape.clone());
draw_route.zoomed.push(Color::RED.alpha(0.5), shape.clone());
hitbox_pieces.push(shape);
let mut draw_route = ToggleZoomed::builder();
let mut hitbox_pieces = Vec::new();
let mut total_time = Duration::ZERO;
let mut total_dist = Distance::ZERO;
for pair in self.waypoints.get_waypoints().windows(2) {
if let Some((path, pl)) =
TripEndpoint::path_req(pair[0], pair[1], TripMode::Drive, map)
.and_then(|req| map.pathfind_with_params(req, &params, cache_custom).ok())
.and_then(|path| path.trace(map).map(|pl| (path, pl)))
{
let shape = pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS);
draw_route
.unzoomed
.push(Color::RED.alpha(0.8), shape.clone());
draw_route.zoomed.push(Color::RED.alpha(0.5), shape.clone());
hitbox_pieces.push(shape);
// Use estimate_duration and not the original cost from pathfinding, since that
// includes huge penalties when the route is forced to cross a filter
total_time += path.estimate_duration(map, None);
total_dist += path.total_length();
}
}
if !hitbox_pieces.is_empty() {
let mut txt = Text::new();
txt.add_line(Line("Route respecting the new modal filters"));
txt.add_line(Line(format!("Time: {}", total_time)));
txt.add_line(Line(format!("Distance: {}", total_dist)));
world
.add(ID::RouteAfterFilters)
.hitbox(Polygon::union_all(hitbox_pieces))
.zorder(0)
.draw(draw_route)
.hover_outline(Color::BLACK, Distance::meters(2.0))
.tooltip(txt)
.build(ctx);
}
(total_time, total_dist)
};
// Then the one ignoring filters
{
let mut draw_route = ToggleZoomed::builder();
let mut hitbox_pieces = Vec::new();
let mut total_time = Duration::ZERO;
let mut total_dist = Distance::ZERO;
for pair in self.waypoints.get_waypoints().windows(2) {
if let Some((path, pl)) =
TripEndpoint::path_req(pair[0], pair[1], TripMode::Drive, map)
.and_then(|req| map.pathfind(req).ok())
.and_then(|path| path.trace(map).map(|pl| (path, pl)))
{
let shape = pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS);
draw_route
.unzoomed
.push(Color::BLUE.alpha(0.8), shape.clone());
draw_route
.zoomed
.push(Color::BLUE.alpha(0.5), shape.clone());
hitbox_pieces.push(shape);
total_time += path.estimate_duration(map, None);
total_dist += path.total_length();
}
}
if !hitbox_pieces.is_empty() {
let mut txt = Text::new();
// If these two stats are the same, assume the two paths are equivalent
if total_time == total_time_after && total_dist == total_dist_after {
world.delete(ID::RouteAfterFilters);
txt.add_line(Line(
"The route is the same before/after the new modal filters",
));
txt.add_line(Line(format!("Time: {}", total_time)));
txt.add_line(Line(format!("Distance: {}", total_dist)));
} else {
txt.add_line(Line("Route before the new modal filters"));
txt.add_line(Line(format!("Time: {}", total_time)));
txt.add_line(Line(format!("Distance: {}", total_dist)));
cmp_duration(
&mut txt,
app,
total_time - total_time_after,
"shorter",
"longer",
);
cmp_dist(
&mut txt,
app,
total_dist - total_dist_after,
"shorter",
"longer",
);
}
world
.add(ID::RouteBeforeFilters)
.hitbox(Polygon::union_all(hitbox_pieces))
// If the two routes partly overlap, put the "before" on top, since it has
// the comparison stats.
.zorder(1)
.draw(draw_route)
.hover_outline(Color::BLACK, Distance::meters(2.0))
.tooltip(txt)
.build(ctx);
}
}
if !hitbox_pieces.is_empty() {
world
.add(ID::MainRoute)
.hitbox(Polygon::union_all(hitbox_pieces))
.draw(draw_route)
.hover_outline(Color::BLACK, Distance::meters(2.0))
.build(ctx);
}
world

View File

@ -12,6 +12,7 @@ use widgetry::{
use super::{before_after_button, RoutingPreferences};
use crate::app::{App, Transition};
use crate::common::{cmp_dist, cmp_duration};
/// A temporary structure that the caller should unpack and use as needed.
pub struct BuiltRoute {
@ -507,50 +508,3 @@ fn compare_routes(
txt
}
fn cmp_dist(txt: &mut Text, app: &App, dist: Distance, shorter: &str, longer: &str) {
match dist.cmp(&Distance::ZERO) {
Ordering::Less => {
txt.add_line(
Line(format!(
"{} {}",
(-dist).to_string(&app.opts.units),
shorter
))
.fg(Color::GREEN),
);
}
Ordering::Greater => {
txt.add_line(
Line(format!("{} {}", dist.to_string(&app.opts.units), longer)).fg(Color::RED),
);
}
Ordering::Equal => {}
}
}
fn cmp_duration(txt: &mut Text, app: &App, duration: Duration, shorter: &str, longer: &str) {
match duration.cmp(&Duration::ZERO) {
Ordering::Less => {
txt.add_line(
Line(format!(
"{} {}",
(-duration).to_string(&app.opts.units),
shorter
))
.fg(Color::GREEN),
);
}
Ordering::Greater => {
txt.add_line(
Line(format!(
"{} {}",
duration.to_string(&app.opts.units),
longer
))
.fg(Color::RED),
);
}
Ordering::Equal => {}
}
}