Skip to content

Commit

Permalink
UI glue for switching between walking/biking. #393
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Nov 21, 2020
1 parent 657a24d commit 78eedff
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 28 deletions.
23 changes: 19 additions & 4 deletions game/src/devtools/fifteen_min/isochrone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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?
Expand All @@ -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();
Expand All @@ -36,6 +49,8 @@ impl Isochrone {
}

Isochrone {
start,
constraints,
draw,
time_to_reach_building,
amenities_reachable,
Expand Down
47 changes: 36 additions & 11 deletions game/src/devtools/fifteen_min/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,9 +44,9 @@ impl Viewer {
}

pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID) -> Box<dyn State<App>> {
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);

Expand Down Expand Up @@ -86,7 +86,7 @@ impl State<App> 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);
}
Expand All @@ -102,10 +102,14 @@ impl State<App> 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.",
],
));
}
Expand All @@ -129,6 +133,19 @@ impl State<App> 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,
);
}
_ => {}
}

Expand Down Expand Up @@ -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()
Expand All @@ -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))
Expand Down
32 changes: 19 additions & 13 deletions map_model/src/connectivity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,29 @@ pub fn all_costs_from(
map: &Map,
start: BuildingID,
time_limit: Duration,
constraints: PathConstraints,
) -> HashMap<BuildingID, Duration> {
// 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
}

0 comments on commit 78eedff

Please sign in to comment.