Try tracing paths more precisely along road edges, even with changing widths. Use in the experimental bus tool. It needs work, but solid start.

This commit is contained in:
Dustin Carlino 2022-04-04 19:12:00 +01:00
parent 6aa71539a5
commit 1c05a99969
4 changed files with 60 additions and 11 deletions

View File

@ -2,12 +2,11 @@ use abstutil::prettyprint_usize;
use geom::Duration; use geom::Duration;
use map_gui::tools::{InputWaypoints, WaypointID}; use map_gui::tools::{InputWaypoints, WaypointID};
use map_model::connectivity::WalkingOptions; use map_model::connectivity::WalkingOptions;
use map_model::NORMAL_LANE_THICKNESS;
use synthpop::{TripEndpoint, TripMode}; use synthpop::{TripEndpoint, TripMode};
use widgetry::mapspace::{ObjectID, World, WorldOutcome}; use widgetry::mapspace::{ObjectID, World, WorldOutcome};
use widgetry::{ use widgetry::{
Color, EventCtx, GfxCtx, HorizontalAlignment, Line, Panel, State, Text, Transition, Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, Text,
VerticalAlignment, Widget, Transition, VerticalAlignment, Widget,
}; };
use crate::isochrone::{Isochrone, MovementOptions, Options}; use crate::isochrone::{Isochrone, MovementOptions, Options};
@ -50,10 +49,10 @@ impl BusExperiment {
.and_then(|req| map.pathfind(req).ok()) .and_then(|req| map.pathfind(req).ok())
{ {
let duration = path.estimate_duration(map, None); let duration = path.estimate_duration(map, None);
if let Some(pl) = path.trace(map) { if let Ok(hitbox) = path.trace_v2(map) {
world world
.add(ID::BusRoute(idx)) .add(ID::BusRoute(idx))
.hitbox(pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS)) .hitbox(hitbox)
.zorder(0) .zorder(0)
.draw_color(self.waypoints.get_waypoint_color(idx)) .draw_color(self.waypoints.get_waypoint_color(idx))
.hover_alpha(0.8) .hover_alpha(0.8)
@ -78,12 +77,13 @@ impl BusExperiment {
stops, stops,
Options { Options {
movement: MovementOptions::Walking(WalkingOptions::default()), movement: MovementOptions::Walking(WalkingOptions::default()),
thresholds: vec![(Duration::minutes(15), Color::grey(0.3).alpha(0.5))],
// TODO The inner colors overlap the outer; this doesn't look right yet // TODO The inner colors overlap the outer; this doesn't look right yet
thresholds: vec![ /*thresholds: vec![
(Duration::minutes(5), Color::grey(0.3).alpha(0.5)), (Duration::minutes(5), Color::grey(0.3).alpha(0.5)),
(Duration::minutes(10), Color::grey(0.3).alpha(0.3)), (Duration::minutes(10), Color::grey(0.3).alpha(0.3)),
(Duration::minutes(15), Color::grey(0.3).alpha(0.2)), (Duration::minutes(15), Color::grey(0.3).alpha(0.2)),
], ],*/
}, },
); );
world.draw_master_batch_built(isochrone.draw); world.draw_master_batch_built(isochrone.draw);
@ -94,6 +94,10 @@ impl BusExperiment {
self.panel = Panel::new_builder(Widget::col(vec![ self.panel = Panel::new_builder(Widget::col(vec![
map_gui::tools::app_header(ctx, app, "Bus planner"), map_gui::tools::app_header(ctx, app, "Bus planner"),
ctx.style()
.btn_back("15-minute neighborhoods")
.hotkey(Key::Escape)
.build_def(ctx),
Text::from_multiline(vec![ Text::from_multiline(vec![
Line("Within a 15 min walk of all stops:"), Line("Within a 15 min walk of all stops:"),
Line(format!( Line(format!(
@ -124,6 +128,12 @@ impl BusExperiment {
impl State<App> for BusExperiment { impl State<App> for BusExperiment {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> { fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition<App> {
let panel_outcome = self.panel.event(ctx); let panel_outcome = self.panel.event(ctx);
if let Outcome::Clicked(ref x) = panel_outcome {
if x == "15-minute neighborhoods" {
return Transition::Pop;
}
}
let world_outcome = self.world.event(ctx); let world_outcome = self.world.event(ctx);
let world_outcome_for_waypoints = world_outcome let world_outcome_for_waypoints = world_outcome
.maybe_map_id(|id| match id { .maybe_map_id(|id| match id {

View File

@ -126,8 +126,8 @@ impl State<App> for Viewer {
match self.panel.event(ctx) { match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() { Outcome::Clicked(x) => match x.as_ref() {
"Bus sketch" => { "Sketch bus route (experimental)" => {
return Transition::Replace(crate::bus::BusExperiment::new_state(ctx, app)); return Transition::Push(crate::bus::BusExperiment::new_state(ctx, app));
} }
"Home" => { "Home" => {
return Transition::Pop; return Transition::Pop;
@ -291,7 +291,7 @@ fn build_panel(ctx: &mut EventCtx, app: &App, start: &Building, isochrone: &Isoc
map_gui::tools::app_header(ctx, app, "15-minute neighborhood explorer"), map_gui::tools::app_header(ctx, app, "15-minute neighborhood explorer"),
ctx.style() ctx.style()
.btn_outline .btn_outline
.text("Bus sketch") .text("Sketch bus route (experimental)")
.hotkey(Key::B) .hotkey(Key::B)
.build_def(ctx), .build_def(ctx),
Text::from_all(vec![ Text::from_all(vec![

View File

@ -39,10 +39,17 @@ impl Ring {
Ok(result) Ok(result)
} }
pub fn must_new(pts: Vec<Pt2D>) -> Ring { pub fn must_new(pts: Vec<Pt2D>) -> Ring {
Ring::new(pts).unwrap() Ring::new(pts).unwrap()
} }
/// First dedupes adjacent points
pub fn deduping_new(mut pts: Vec<Pt2D>) -> Result<Self> {
pts.dedup();
Self::new(pts)
}
/// Draws the ring with some thickness, with half of it straddling the interor of the ring, and /// Draws the ring with some thickness, with half of it straddling the interor of the ring, and
/// half on the outside. /// half on the outside.
pub fn to_outline(&self, thickness: Distance) -> Polygon { pub fn to_outline(&self, thickness: Distance) -> Polygon {

View File

@ -5,7 +5,7 @@ use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use abstutil::prettyprint_usize; use abstutil::prettyprint_usize;
use geom::{Distance, Duration, PolyLine, Speed, EPSILON_DIST}; use geom::{Distance, Duration, PolyLine, Polygon, Ring, Speed, EPSILON_DIST};
use crate::{ use crate::{
BuildingID, DirectedRoadID, LaneID, Map, PathConstraints, Position, Traversable, TurnID, BuildingID, DirectedRoadID, LaneID, Map, PathConstraints, Position, Traversable, TurnID,
@ -468,6 +468,38 @@ impl Path {
Some(pts_so_far.unwrap()) Some(pts_so_far.unwrap())
} }
/// Draws the thickened path, matching entire roads. Ignores the path's exact starting and
/// ending distance.
pub fn trace_v2(&self, map: &Map) -> Result<Polygon> {
let mut left_pts = Vec::new();
let mut right_pts = Vec::new();
for step in &self.steps {
match step {
PathStep::Lane(l) => {
let road = map.get_parent(*l);
let width = road.get_half_width();
if map.get_l(*l).dst_i == road.dst_i {
left_pts.extend(road.center_pts.shift_left(width)?.into_points());
right_pts.extend(road.center_pts.shift_right(width)?.into_points());
} else {
left_pts
.extend(road.center_pts.shift_right(width)?.reversed().into_points());
right_pts
.extend(road.center_pts.shift_left(width)?.reversed().into_points());
}
}
PathStep::ContraflowLane(_) => todo!(),
// Just make a straight line across the intersection. It'd be fancier to try and
// trace along.
PathStep::Turn(_) | PathStep::ContraflowTurn(_) => {}
}
}
right_pts.reverse();
left_pts.extend(right_pts);
left_pts.push(left_pts[0]);
Ok(Ring::deduping_new(left_pts)?.into_polygon())
}
pub fn get_steps(&self) -> &VecDeque<PathStep> { pub fn get_steps(&self) -> &VecDeque<PathStep> {
&self.steps &self.steps
} }