mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 17:37:22 +03:00
Flesh out the LTN impact tool -- calculate impact after, add tooltips
This commit is contained in:
parent
fc705318a9
commit
c6e26e5e20
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use abstutil::{prettyprint_usize, Counter, Timer};
|
||||
use geom::{Duration, Polygon};
|
||||
use map_gui::colors::ColorSchemeChoice;
|
||||
use map_gui::tools::ColorNetwork;
|
||||
use map_gui::tools::{cmp_count, ColorNetwork};
|
||||
use map_gui::{AppLike, ID};
|
||||
use map_model::{
|
||||
DirectedRoadID, Direction, PathRequest, RoadID, RoutingParams, Traversable,
|
||||
@ -450,7 +450,7 @@ impl State<App> for AllRoutesExplorer {
|
||||
let baseline = self.baseline_counts.get(r);
|
||||
let current = self.current_counts.get(r);
|
||||
let mut txt = Text::new();
|
||||
txt.append_all(cmp_count(current, baseline));
|
||||
cmp_count(&mut txt, baseline, current);
|
||||
txt.add_line(format!("{} baseline", prettyprint_usize(baseline)));
|
||||
txt.add_line(format!("{} now", prettyprint_usize(current)));
|
||||
self.tooltip = Some(txt);
|
||||
@ -490,26 +490,6 @@ fn calculate_demand(app: &App, requests: &[PathRequest], timer: &mut Timer) -> C
|
||||
counter
|
||||
}
|
||||
|
||||
fn cmp_count(after: usize, before: usize) -> Vec<TextSpan> {
|
||||
match after.cmp(&before) {
|
||||
std::cmp::Ordering::Equal => {
|
||||
vec![Line("same")]
|
||||
}
|
||||
std::cmp::Ordering::Less => {
|
||||
vec![
|
||||
Line(prettyprint_usize(before - after)).fg(Color::GREEN),
|
||||
Line(" less"),
|
||||
]
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
vec![
|
||||
Line(prettyprint_usize(after - before)).fg(Color::RED),
|
||||
Line(" more"),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate why an alternative path wasn't chosen, by showing the cost to reach every road from
|
||||
/// one start.
|
||||
pub struct PathCostDebugger {
|
||||
|
@ -125,6 +125,10 @@ impl State<App> for BrowseNeighborhoods {
|
||||
"Calculate" => {
|
||||
return Transition::Push(super::impact::ShowResults::new_state(ctx, app));
|
||||
}
|
||||
"Force recalculation" => {
|
||||
app.session.impact = None;
|
||||
return Transition::Push(super::impact::ShowResults::new_state(ctx, app));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Outcome::Changed(_) => {
|
||||
@ -270,9 +274,16 @@ fn impact_widget(ctx: &EventCtx, app: &App) -> Widget {
|
||||
Line(format!("We need to load a {} file", size)),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Calculate")
|
||||
.build_def(ctx),
|
||||
Widget::row(vec![
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Calculate")
|
||||
.build_def(ctx),
|
||||
// TODO Bad UI! Detect edits and do this. I'm being lazy.
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Force recalculation")
|
||||
.build_def(ctx),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
@ -1,21 +1,23 @@
|
||||
use abstio::MapName;
|
||||
use abstutil::{Counter, Timer};
|
||||
use abstutil::{prettyprint_usize, Counter, Timer};
|
||||
use map_gui::load::FileLoader;
|
||||
use map_gui::tools::ColorNetwork;
|
||||
use map_gui::tools::{cmp_count, ColorNetwork};
|
||||
use map_gui::ID;
|
||||
use map_model::{PathRequest, PathStepV2, RoadID};
|
||||
use sim::{Scenario, TripEndpoint, TripMode};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{
|
||||
EventCtx, GfxCtx, HorizontalAlignment, Panel, SimpleState, State, TextExt, VerticalAlignment,
|
||||
Widget,
|
||||
Choice, EventCtx, GfxCtx, HorizontalAlignment, Line, Panel, SimpleState, State, Text, TextExt,
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::{App, Transition};
|
||||
|
||||
// TODO Tooltips
|
||||
// TODO Intersections
|
||||
// TODO Toggle before/after / compare directly
|
||||
// TODO Configurable main road penalty, like in the pathfinding tool
|
||||
// TODO Don't allow crossing filters at all -- don't just disincentivize
|
||||
// TODO Share structure or pieces with Ungap's predict mode
|
||||
// ... can't we just produce data of a certain shape, and have a UI pretty tuned for that?
|
||||
|
||||
pub struct Results {
|
||||
map: MapName,
|
||||
@ -66,12 +68,15 @@ impl Results {
|
||||
fn recalculate_impact(&mut self, ctx: &mut EventCtx, app: &App, timer: &mut Timer) {
|
||||
self.before_counts = Counter::new();
|
||||
self.after_counts = Counter::new();
|
||||
|
||||
let map = &app.map;
|
||||
|
||||
// Before the filters
|
||||
for path in timer
|
||||
.parallelize("calculate routes", self.all_driving_trips.clone(), |req| {
|
||||
map.pathfind_v2(req)
|
||||
})
|
||||
.parallelize(
|
||||
"calculate routes before filters",
|
||||
self.all_driving_trips.clone(),
|
||||
|req| map.pathfind_v2(req),
|
||||
)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
@ -82,14 +87,46 @@ impl Results {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut colorer = ColorNetwork::no_fading(app);
|
||||
colorer.ranked_roads(self.before_counts.clone(), &app.cs.good_to_bad_red);
|
||||
self.before_draw_heatmap = colorer.build(ctx);
|
||||
|
||||
// After the filters
|
||||
let mut params = map.routing_params().clone();
|
||||
app.session.modal_filters.update_routing_params(&mut params);
|
||||
let cache_custom = true;
|
||||
for path in timer
|
||||
.parallelize(
|
||||
"calculate routes after filters",
|
||||
self.all_driving_trips.clone(),
|
||||
|req| map.pathfind_v2_with_params(req, ¶ms, cache_custom),
|
||||
)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
for step in path.get_steps() {
|
||||
// No Contraflow steps for driving paths
|
||||
if let PathStepV2::Along(dr) = step {
|
||||
self.after_counts.inc(dr.road);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut colorer = ColorNetwork::no_fading(app);
|
||||
colorer.ranked_roads(self.after_counts.clone(), &app.cs.good_to_bad_red);
|
||||
self.after_draw_heatmap = colorer.build(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShowResults;
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Layer {
|
||||
Before,
|
||||
After,
|
||||
}
|
||||
|
||||
pub struct ShowResults {
|
||||
layer: Layer,
|
||||
tooltip: Option<Text>,
|
||||
}
|
||||
|
||||
impl ShowResults {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
|
||||
@ -115,35 +152,89 @@ impl ShowResults {
|
||||
);
|
||||
}
|
||||
|
||||
let layer = Layer::Before;
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
map_gui::tools::app_header(ctx, app, "Low traffic neighborhoods"),
|
||||
Widget::row(vec![
|
||||
"Impact prediction".text_widget(ctx),
|
||||
ctx.style().btn_close_widget(ctx),
|
||||
]),
|
||||
"This shows how many driving trips cross each road".text_widget(ctx),
|
||||
Widget::row(vec![
|
||||
"Show what?".text_widget(ctx).centered_vert(),
|
||||
Widget::dropdown(
|
||||
ctx,
|
||||
"layer",
|
||||
layer,
|
||||
vec![
|
||||
Choice::new("before", Layer::Before),
|
||||
Choice::new("after", Layer::After),
|
||||
],
|
||||
),
|
||||
]),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
<dyn SimpleState<_>>::new_state(panel, Box::new(ShowResults))
|
||||
<dyn SimpleState<_>>::new_state(
|
||||
panel,
|
||||
Box::new(ShowResults {
|
||||
layer,
|
||||
tooltip: None,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleState<App> for ShowResults {
|
||||
fn on_click(&mut self, ctx: &mut EventCtx, app: &mut App, x: &str, _: &Panel) -> Transition {
|
||||
fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, _: &Panel) -> Transition {
|
||||
if x == "close" {
|
||||
return Transition::Pop;
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
// TODO Or on_mouseover?
|
||||
fn other_event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
|
||||
fn on_mouseover(&mut self, ctx: &mut EventCtx, app: &mut App) {
|
||||
ctx.canvas_movement();
|
||||
Transition::Keep
|
||||
self.tooltip = None;
|
||||
if let Some(r) = match app.mouseover_unzoomed_roads_and_intersections(ctx) {
|
||||
Some(ID::Road(r)) => Some(r),
|
||||
Some(ID::Lane(l)) => Some(l.road),
|
||||
_ => None,
|
||||
} {
|
||||
let impact = app.session.impact.as_ref().unwrap();
|
||||
let before = impact.before_counts.get(r);
|
||||
let after = impact.after_counts.get(r);
|
||||
let mut txt = Text::from_multiline(vec![
|
||||
Line(format!("Before: {}", prettyprint_usize(before))),
|
||||
Line(format!("After: {}", prettyprint_usize(after))),
|
||||
]);
|
||||
cmp_count(&mut txt, before, after);
|
||||
self.tooltip = Some(txt);
|
||||
}
|
||||
}
|
||||
|
||||
fn panel_changed(
|
||||
&mut self,
|
||||
_: &mut EventCtx,
|
||||
_: &mut App,
|
||||
panel: &mut Panel,
|
||||
) -> Option<Transition> {
|
||||
self.layer = panel.dropdown_value("layer");
|
||||
None
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
let impact = app.session.impact.as_ref().unwrap();
|
||||
impact.before_draw_heatmap.draw(g);
|
||||
match self.layer {
|
||||
Layer::Before => {
|
||||
impact.before_draw_heatmap.draw(g);
|
||||
}
|
||||
Layer::After => {
|
||||
impact.after_draw_heatmap.draw(g);
|
||||
}
|
||||
}
|
||||
if let Some(ref txt) = self.tooltip {
|
||||
g.draw_mouse_tooltip(txt.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,8 @@ impl RoutePlanner {
|
||||
let mut draw = ToggleZoomed::builder();
|
||||
|
||||
// First the route respecting the filters
|
||||
// TODO Like in rat_runs, we need to actually enforce this, not just penalize... though,
|
||||
// should it matter except for when a cell is disconnected?
|
||||
let (total_time_after, total_dist_after) = {
|
||||
let mut params = map.routing_params().clone();
|
||||
app.session.modal_filters.update_routing_params(&mut params);
|
||||
|
@ -19,7 +19,8 @@ pub use self::title_screen::{Executable, TitleScreen};
|
||||
pub use self::trip_files::{TripManagement, TripManagementState};
|
||||
pub use self::turn_explorer::TurnExplorer;
|
||||
pub use self::ui::{
|
||||
cmp_dist, cmp_duration, percentage_bar, ChooseSomething, FilePicker, PopupMsg, PromptInput,
|
||||
cmp_count, cmp_dist, cmp_duration, percentage_bar, ChooseSomething, FilePicker, PopupMsg,
|
||||
PromptInput,
|
||||
};
|
||||
pub use self::url::URLManager;
|
||||
pub use self::waypoints::{InputWaypoints, WaypointID};
|
||||
|
@ -4,6 +4,7 @@ use std::cmp::Ordering;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::{Distance, Duration, Polygon};
|
||||
use widgetry::{
|
||||
hotkeys, Choice, Color, DrawBaselayer, EventCtx, GeomBatch, GfxCtx, Key, Line, Menu, Outcome,
|
||||
@ -316,3 +317,24 @@ pub fn cmp_duration(
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Less is better
|
||||
pub fn cmp_count(txt: &mut Text, before: usize, after: usize) {
|
||||
match after.cmp(&before) {
|
||||
std::cmp::Ordering::Equal => {
|
||||
txt.add_line(Line("same"));
|
||||
}
|
||||
std::cmp::Ordering::Less => {
|
||||
txt.add_appended(vec![
|
||||
Line(prettyprint_usize(before - after)).fg(Color::GREEN),
|
||||
Line(" less"),
|
||||
]);
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
txt.add_appended(vec![
|
||||
Line(prettyprint_usize(after - before)).fg(Color::RED),
|
||||
Line(" more"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user