diff --git a/game/src/devtools/fifteen_min/isochrone.rs b/game/src/devtools/fifteen_min/isochrone.rs index 8e56c2819b..bc4c0c205e 100644 --- a/game/src/devtools/fifteen_min/isochrone.rs +++ b/game/src/devtools/fifteen_min/isochrone.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use abstutil::MultiMap; use geom::{Duration, Polygon}; -use map_model::{connectivity, BuildingID}; +use map_model::{connectivity, BuildingID, PathConstraints}; use widgetry::{Color, Drawable, EventCtx, GeomBatch}; use crate::app::App; @@ -11,6 +11,10 @@ use crate::helpers::amenity_type; /// Represents the area reachable from a single building. pub struct Isochrone { + /// The center of the isochrone + pub start: BuildingID, + /// What mode of travel we're using + pub constraints: PathConstraints, /// Colored polygon contours, uploaded to the GPU and ready for drawing pub draw: Drawable, /// How far away is each building from the start? @@ -20,9 +24,18 @@ pub struct Isochrone { } impl Isochrone { - pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Isochrone { - let time_to_reach_building = - connectivity::all_costs_from(&app.primary.map, start, Duration::minutes(15)); + pub fn new( + ctx: &mut EventCtx, + app: &App, + start: BuildingID, + constraints: PathConstraints, + ) -> Isochrone { + let time_to_reach_building = connectivity::all_costs_from( + &app.primary.map, + start, + Duration::minutes(15), + constraints, + ); let draw = draw_isochrone(app, &time_to_reach_building).upload(ctx); let mut amenities_reachable = MultiMap::new(); @@ -36,6 +49,8 @@ impl Isochrone { } Isochrone { + start, + constraints, draw, time_to_reach_building, amenities_reachable, diff --git a/game/src/devtools/fifteen_min/mod.rs b/game/src/devtools/fifteen_min/mod.rs index 2d37c7a6ee..79dfc5dc27 100644 --- a/game/src/devtools/fifteen_min/mod.rs +++ b/game/src/devtools/fifteen_min/mod.rs @@ -7,10 +7,10 @@ use rand::seq::SliceRandom; use geom::Pt2D; -use map_model::{Building, BuildingID}; +use map_model::{Building, BuildingID, PathConstraints}; use widgetry::{ - Btn, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, Outcome, Panel, - RewriteColor, State, Text, VerticalAlignment, Widget, + Btn, Checkbox, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, + Outcome, Panel, RewriteColor, State, Text, VerticalAlignment, Widget, }; use self::isochrone::Isochrone; @@ -44,9 +44,9 @@ impl Viewer { } pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Box> { + let constraints = PathConstraints::Pedestrian; let start = app.primary.map.get_b(start); - let isochrone = Isochrone::new(ctx, app, start.id); - + let isochrone = Isochrone::new(ctx, app, start.id, constraints); let highlight_start = draw_star(ctx, start.polygon.center()); let panel = build_panel(ctx, start, &isochrone); @@ -86,7 +86,7 @@ impl State for Viewer { if ctx.input.left_mouse_button_pressed() { if let Some(ref hover) = self.hovering_on_bldg { let start = app.primary.map.get_b(hover.id); - self.isochrone = Isochrone::new(ctx, app, start.id); + self.isochrone = Isochrone::new(ctx, app, start.id, self.isochrone.constraints); self.highlight_start = draw_star(ctx, start.polygon.center()); self.panel = build_panel(ctx, start, &self.isochrone); } @@ -102,10 +102,14 @@ impl State for Viewer { ctx, "15 minute neighborhoods", vec![ - "What if you could access most of your daily needs with a 15-minute walk or bike ride from your house?", - "Wouldn't it be nice to not rely on a climate unfriendly motor vehicle and get stuck in traffic for these simple errands?", - "Different cities around the world are talking about what design and policy changes could lead to 15 minute neighborhoods.", - "This tool lets you see what commercial amenities are near you right now, using data from OpenStreetMap.", + "What if you could access most of your daily needs with a 15-minute \ + walk or bike ride from your house?", + "Wouldn't it be nice to not rely on a climate unfriendly motor \ + vehicle and get stuck in traffic for these simple errands?", + "Different cities around the world are talking about what design and \ + policy changes could lead to 15 minute neighborhoods.", + "This tool lets you see what commercial amenities are near you right \ + now, using data from OpenStreetMap.", ], )); } @@ -129,6 +133,19 @@ impl State for Viewer { return Transition::Push(PopupMsg::new(ctx, category, details)); } }, + Outcome::Changed => { + let constraints = if self.panel.is_checked("walking / biking") { + PathConstraints::Bike + } else { + PathConstraints::Pedestrian + }; + self.isochrone = Isochrone::new(ctx, app, self.isochrone.start, constraints); + self.panel = build_panel( + ctx, + app.primary.map.get_b(self.isochrone.start), + &self.isochrone, + ); + } _ => {} } @@ -156,7 +173,7 @@ fn draw_star(ctx: &mut EventCtx, center: Pt2D) -> Drawable { fn build_panel(ctx: &mut EventCtx, start: &Building, isochrone: &Isochrone) -> Panel { let mut rows = Vec::new(); - + rows.push(Widget::row(vec![ Line("15-minute neighborhood explorer") .small_heading() @@ -180,6 +197,14 @@ fn build_panel(ctx: &mut EventCtx, start: &Building, isochrone: &Isochrone) -> P // Start of toolbar rows.push(Widget::horiz_separator(ctx, 0.3).margin_above(10)); + rows.push(Checkbox::toggle( + ctx, + "walking / biking", + "walking", + "biking", + None, + isochrone.constraints == PathConstraints::Bike, + )); rows.push(Btn::plaintext("About").build_def(ctx, None)); Panel::new(Widget::col(rows)) diff --git a/map_model/src/connectivity.rs b/map_model/src/connectivity.rs index 00d526c145..896b1001f8 100644 --- a/map_model/src/connectivity.rs +++ b/map_model/src/connectivity.rs @@ -52,23 +52,29 @@ pub fn all_costs_from( map: &Map, start: BuildingID, time_limit: Duration, + constraints: PathConstraints, ) -> HashMap { - // TODO This is hardcoded to walking; take a PathConstraints. - let graph = build_graph_for_pedestrians(map); - let start = WalkingNode::closest(map.get_b(start).sidewalk_pos, map); - let cost_per_node = petgraph::algo::dijkstra(&graph, start, None, |(_, _, cost)| *cost); - - // Assign every building a cost based on which end of the sidewalk it's closest to - // TODO We could try to get a little more accurate by accounting for the distance from that - // end of the sidewalk to the building let mut results = HashMap::new(); - for b in map.all_buildings() { - if let Some(seconds) = cost_per_node.get(&WalkingNode::closest(b.sidewalk_pos, map)) { - let duration = Duration::seconds(*seconds as f64); - if duration <= time_limit { - results.insert(b.id, duration); + + if constraints == PathConstraints::Pedestrian { + let graph = build_graph_for_pedestrians(map); + let start = WalkingNode::closest(map.get_b(start).sidewalk_pos, map); + let cost_per_node = petgraph::algo::dijkstra(&graph, start, None, |(_, _, cost)| *cost); + + // Assign every building a cost based on which end of the sidewalk it's closest to + // TODO We could try to get a little more accurate by accounting for the distance from that + // end of the sidewalk to the building + for b in map.all_buildings() { + if let Some(seconds) = cost_per_node.get(&WalkingNode::closest(b.sidewalk_pos, map)) { + let duration = Duration::seconds(*seconds as f64); + if duration <= time_limit { + results.insert(b.id, duration); + } } } + } else { + // TODO } + results }