mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-18 03:41:52 +03:00
Generalized connectivity to use Spot and added BorderIsochrone (#668)
This commit is contained in:
parent
972f5b0187
commit
4e150dcab0
@ -6,7 +6,7 @@ use widgetry::{
|
|||||||
SimpleState, State, TextExt, Transition, VerticalAlignment, Widget,
|
SimpleState, State, TextExt, Transition, VerticalAlignment, Widget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::isochrone::{Isochrone, Options};
|
use crate::isochrone::{draw_isochrone, BorderIsochrone, Isochrone, Options};
|
||||||
use crate::viewer::{draw_star, HoverKey, HoverOnBuilding};
|
use crate::viewer::{draw_star, HoverKey, HoverOnBuilding};
|
||||||
use crate::App;
|
use crate::App;
|
||||||
|
|
||||||
@ -23,8 +23,15 @@ impl FindAmenity {
|
|||||||
.map(|at| Choice::new(at.to_string(), at))
|
.map(|at| Choice::new(at.to_string(), at))
|
||||||
.collect(),
|
.collect(),
|
||||||
Box::new(move |at, ctx, app| {
|
Box::new(move |at, ctx, app| {
|
||||||
let multi_isochrone = create_multi_isochrone(ctx, app, at, options);
|
let multi_isochrone = create_multi_isochrone(ctx, app, at, options.clone());
|
||||||
return Transition::Replace(Results::new_state(ctx, app, multi_isochrone, at));
|
let border_isochrone = create_border_isochrone(ctx, app, options);
|
||||||
|
return Transition::Replace(Results::new_state(
|
||||||
|
ctx,
|
||||||
|
app,
|
||||||
|
multi_isochrone,
|
||||||
|
border_isochrone,
|
||||||
|
at,
|
||||||
|
));
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -48,6 +55,17 @@ fn create_multi_isochrone(
|
|||||||
Isochrone::new(ctx, app, stores, options)
|
Isochrone::new(ctx, app, stores, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw an isochrone from every intersection border
|
||||||
|
fn create_border_isochrone(ctx: &mut EventCtx, app: &App, options: Options) -> BorderIsochrone {
|
||||||
|
let mut all_intersections = Vec::new();
|
||||||
|
for i in app.map.all_intersections() {
|
||||||
|
if i.is_border() {
|
||||||
|
all_intersections.push(i.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BorderIsochrone::new(ctx, app, all_intersections, options)
|
||||||
|
}
|
||||||
|
|
||||||
struct Results {
|
struct Results {
|
||||||
draw: Drawable,
|
draw: Drawable,
|
||||||
isochrone: Isochrone,
|
isochrone: Isochrone,
|
||||||
@ -59,6 +77,7 @@ impl Results {
|
|||||||
ctx: &mut EventCtx,
|
ctx: &mut EventCtx,
|
||||||
app: &App,
|
app: &App,
|
||||||
isochrone: Isochrone,
|
isochrone: Isochrone,
|
||||||
|
border_isochrone: BorderIsochrone,
|
||||||
category: AmenityType,
|
category: AmenityType,
|
||||||
) -> Box<dyn State<App>> {
|
) -> Box<dyn State<App>> {
|
||||||
let panel = Panel::new_builder(Widget::col(vec![
|
let panel = Panel::new_builder(Widget::col(vec![
|
||||||
@ -77,11 +96,27 @@ impl Results {
|
|||||||
(Color::RED, "15 mins"),
|
(Color::RED, "15 mins"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
ColorLegend::row(
|
||||||
|
ctx,
|
||||||
|
Color::rgb(0, 0, 0).alpha(0.3),
|
||||||
|
"< 15 mins from border (amenity could exist off map)",
|
||||||
|
),
|
||||||
]))
|
]))
|
||||||
.aligned(HorizontalAlignment::RightInset, VerticalAlignment::TopInset)
|
.aligned(HorizontalAlignment::RightInset, VerticalAlignment::TopInset)
|
||||||
.build(ctx);
|
.build(ctx);
|
||||||
|
|
||||||
let mut batch = isochrone.draw_isochrone(app);
|
let mut batch = draw_isochrone(
|
||||||
|
app,
|
||||||
|
&border_isochrone.time_to_reach_building,
|
||||||
|
&border_isochrone.thresholds,
|
||||||
|
&border_isochrone.colors,
|
||||||
|
);
|
||||||
|
batch.append(draw_isochrone(
|
||||||
|
app,
|
||||||
|
&isochrone.time_to_reach_building,
|
||||||
|
&isochrone.thresholds,
|
||||||
|
&isochrone.colors,
|
||||||
|
));
|
||||||
for &start in &isochrone.start {
|
for &start in &isochrone.start {
|
||||||
batch.append(draw_star(ctx, app.map.get_b(start)));
|
batch.append(draw_star(ctx, app.map.get_b(start)));
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::App;
|
||||||
use abstutil::{prettyprint_usize, Counter, Timer};
|
use abstutil::{prettyprint_usize, Counter, Timer};
|
||||||
use geom::Percent;
|
use geom::Percent;
|
||||||
use map_gui::tools::PopupMsg;
|
use map_gui::tools::PopupMsg;
|
||||||
|
use map_model::connectivity::Spot;
|
||||||
use map_model::{AmenityType, BuildingID};
|
use map_model::{AmenityType, BuildingID};
|
||||||
use widgetry::{
|
use widgetry::{
|
||||||
Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Panel,
|
Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Panel,
|
||||||
@ -10,7 +12,6 @@ use widgetry::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::isochrone::Options;
|
use crate::isochrone::Options;
|
||||||
use crate::App;
|
|
||||||
|
|
||||||
/// Ask what types of amenities are necessary to be within a walkshed, then rank every house with
|
/// Ask what types of amenities are necessary to be within a walkshed, then rank every house with
|
||||||
/// how many of those needs are satisfied.
|
/// how many of those needs are satisfied.
|
||||||
@ -98,12 +99,10 @@ fn score_houses(
|
|||||||
let mut stores = Vec::new();
|
let mut stores = Vec::new();
|
||||||
for b in map.all_buildings() {
|
for b in map.all_buildings() {
|
||||||
if b.has_amenity(category) {
|
if b.has_amenity(category) {
|
||||||
stores.push(b.id);
|
stores.push(Spot::Building(b.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
options.clone().times_from(map, stores)
|
||||||
// Then find all buildings reachable from any of those starting points
|
|
||||||
options.clone().times_from_buildings(map, stores)
|
|
||||||
}) {
|
}) {
|
||||||
for (b, _) in times {
|
for (b, _) in times {
|
||||||
satisfied_per_bldg.inc(b);
|
satisfied_per_bldg.inc(b);
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use abstutil::MultiMap;
|
use abstutil::MultiMap;
|
||||||
|
use connectivity::Spot;
|
||||||
use geom::{Duration, Polygon};
|
use geom::{Duration, Polygon};
|
||||||
use map_gui::tools::Grid;
|
use map_gui::tools::Grid;
|
||||||
use map_model::{
|
use map_model::{
|
||||||
connectivity, AmenityType, BuildingID, BuildingType, LaneType, Map, Path, PathConstraints,
|
connectivity, AmenityType, BuildingID, BuildingType, IntersectionID, LaneType, Map, Path,
|
||||||
PathRequest,
|
PathConstraints, PathRequest,
|
||||||
};
|
};
|
||||||
use widgetry::{Color, Drawable, EventCtx, GeomBatch};
|
use widgetry::{Color, Drawable, EventCtx, GeomBatch};
|
||||||
|
|
||||||
@ -19,6 +20,10 @@ pub struct Isochrone {
|
|||||||
pub options: Options,
|
pub options: Options,
|
||||||
/// Colored polygon contours, uploaded to the GPU and ready for drawing
|
/// Colored polygon contours, uploaded to the GPU and ready for drawing
|
||||||
pub draw: Drawable,
|
pub draw: Drawable,
|
||||||
|
/// Thresholds used to draw the isochrone
|
||||||
|
pub thresholds: Vec<f64>,
|
||||||
|
/// Colors used to draw the isochrone
|
||||||
|
pub colors: Vec<Color>,
|
||||||
/// How far away is each building from the start?
|
/// How far away is each building from the start?
|
||||||
pub time_to_reach_building: HashMap<BuildingID, Duration>,
|
pub time_to_reach_building: HashMap<BuildingID, Duration>,
|
||||||
/// Per category of amenity, what buildings have that?
|
/// Per category of amenity, what buildings have that?
|
||||||
@ -40,11 +45,7 @@ pub enum Options {
|
|||||||
impl Options {
|
impl Options {
|
||||||
/// Calculate the quickest time to reach buildings across the map from any of the starting
|
/// Calculate the quickest time to reach buildings across the map from any of the starting
|
||||||
/// points, subject to the walking/biking settings configured in these Options.
|
/// points, subject to the walking/biking settings configured in these Options.
|
||||||
pub fn times_from_buildings(
|
pub fn times_from(self, map: &Map, starts: Vec<Spot>) -> HashMap<BuildingID, Duration> {
|
||||||
self,
|
|
||||||
map: &Map,
|
|
||||||
starts: Vec<BuildingID>,
|
|
||||||
) -> HashMap<BuildingID, Duration> {
|
|
||||||
match self {
|
match self {
|
||||||
Options::Walking(opts) => {
|
Options::Walking(opts) => {
|
||||||
connectivity::all_walking_costs_from(map, starts, Duration::minutes(15), opts)
|
connectivity::all_walking_costs_from(map, starts, Duration::minutes(15), opts)
|
||||||
@ -66,9 +67,8 @@ impl Isochrone {
|
|||||||
start: Vec<BuildingID>,
|
start: Vec<BuildingID>,
|
||||||
options: Options,
|
options: Options,
|
||||||
) -> Isochrone {
|
) -> Isochrone {
|
||||||
let time_to_reach_building = options
|
let spot_starts = start.iter().map(|b_id| Spot::Building(*b_id)).collect();
|
||||||
.clone()
|
let time_to_reach_building = options.clone().times_from(&app.map, spot_starts);
|
||||||
.times_from_buildings(&app.map, start.clone());
|
|
||||||
|
|
||||||
let mut amenities_reachable = MultiMap::new();
|
let mut amenities_reachable = MultiMap::new();
|
||||||
let mut population = 0;
|
let mut population = 0;
|
||||||
@ -101,16 +101,36 @@ impl Isochrone {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate polygons covering the contour line where the cost in the grid crosses these
|
||||||
|
// threshold values.
|
||||||
|
let thresholds = vec![
|
||||||
|
0.1,
|
||||||
|
Duration::minutes(5).inner_seconds(),
|
||||||
|
Duration::minutes(10).inner_seconds(),
|
||||||
|
Duration::minutes(15).inner_seconds(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// And color the polygon for each threshold
|
||||||
|
let colors = vec![
|
||||||
|
Color::GREEN.alpha(0.5),
|
||||||
|
Color::ORANGE.alpha(0.5),
|
||||||
|
Color::RED.alpha(0.5),
|
||||||
|
];
|
||||||
|
|
||||||
let mut i = Isochrone {
|
let mut i = Isochrone {
|
||||||
start,
|
start,
|
||||||
options,
|
options,
|
||||||
draw: Drawable::empty(ctx),
|
draw: Drawable::empty(ctx),
|
||||||
|
thresholds,
|
||||||
|
colors,
|
||||||
time_to_reach_building,
|
time_to_reach_building,
|
||||||
amenities_reachable,
|
amenities_reachable,
|
||||||
population,
|
population,
|
||||||
onstreet_parking_spots,
|
onstreet_parking_spots,
|
||||||
};
|
};
|
||||||
i.draw = i.draw_isochrone(app).upload(ctx);
|
|
||||||
|
i.draw =
|
||||||
|
draw_isochrone(app, &i.time_to_reach_building, &i.thresholds, &i.colors).upload(ctx);
|
||||||
i
|
i
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,73 +153,111 @@ impl Isochrone {
|
|||||||
|
|
||||||
all_paths.min_by_key(|path| path.total_length())
|
all_paths.min_by_key(|path| path.total_length())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_isochrone(&self, app: &App) -> GeomBatch {
|
pub fn draw_isochrone(
|
||||||
// To generate the polygons covering areas between 0-5 mins, 5-10 mins, etc, we have to feed
|
app: &App,
|
||||||
// in a 2D grid of costs. Use a 100x100 meter resolution.
|
time_to_reach_building: &HashMap<BuildingID, Duration>,
|
||||||
let bounds = app.map.get_bounds();
|
thresholds: &[f64],
|
||||||
let resolution_m = 100.0;
|
colors: &[Color],
|
||||||
// The costs we're storing are currenly durations, but the contour crate needs f64, so
|
) -> GeomBatch {
|
||||||
// just store the number of seconds.
|
// To generate the polygons covering areas between 0-5 mins, 5-10 mins, etc, we have to feed
|
||||||
let mut grid: Grid<f64> = Grid::new(
|
// in a 2D grid of costs. Use a 100x100 meter resolution.
|
||||||
(bounds.width() / resolution_m).ceil() as usize,
|
let bounds = app.map.get_bounds();
|
||||||
(bounds.height() / resolution_m).ceil() as usize,
|
let resolution_m = 100.0;
|
||||||
0.0,
|
// The costs we're storing are currenly durations, but the contour crate needs f64, so
|
||||||
|
// just store the number of seconds.
|
||||||
|
let mut grid: Grid<f64> = Grid::new(
|
||||||
|
(bounds.width() / resolution_m).ceil() as usize,
|
||||||
|
(bounds.height() / resolution_m).ceil() as usize,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate the cost from the start building to every other building in the map
|
||||||
|
for (b, cost) in time_to_reach_building {
|
||||||
|
// What grid cell does the building belong to?
|
||||||
|
let pt = app.map.get_b(*b).polygon.center();
|
||||||
|
let idx = grid.idx(
|
||||||
|
((pt.x() - bounds.min_x) / resolution_m) as usize,
|
||||||
|
((pt.y() - bounds.min_y) / resolution_m) as usize,
|
||||||
);
|
);
|
||||||
|
// Don't add! If two buildings map to the same cell, we should pick a finer resolution.
|
||||||
|
grid.data[idx] = cost.inner_seconds();
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate the cost from the start building to every other building in the map
|
let smooth = false;
|
||||||
for (b, cost) in &self.time_to_reach_building {
|
let c = contour::ContourBuilder::new(grid.width as u32, grid.height as u32, smooth);
|
||||||
// What grid cell does the building belong to?
|
let mut batch = GeomBatch::new();
|
||||||
let pt = app.map.get_b(*b).polygon.center();
|
// The last feature returned will be larger than the last threshold value. We don't want to
|
||||||
let idx = grid.idx(
|
// display that at all. zip() will omit this last pair, since colors.len() ==
|
||||||
((pt.x() - bounds.min_x) / resolution_m) as usize,
|
// thresholds.len() - 1.
|
||||||
((pt.y() - bounds.min_y) / resolution_m) as usize,
|
//
|
||||||
);
|
// TODO Actually, this still isn't working. I think each polygon is everything > the
|
||||||
// Don't add! If two buildings map to the same cell, we should pick a finer resolution.
|
// threshold, not everything between two thresholds?
|
||||||
grid.data[idx] = cost.inner_seconds();
|
for (feature, color) in c
|
||||||
}
|
.contours(&grid.data, &thresholds)
|
||||||
|
.unwrap()
|
||||||
// Generate polygons covering the contour line where the cost in the grid crosses these
|
.into_iter()
|
||||||
// threshold values.
|
.zip(colors)
|
||||||
let thresholds = vec![
|
{
|
||||||
0.1,
|
match feature.geometry.unwrap().value {
|
||||||
Duration::minutes(5).inner_seconds(),
|
geojson::Value::MultiPolygon(polygons) => {
|
||||||
Duration::minutes(10).inner_seconds(),
|
for p in polygons {
|
||||||
Duration::minutes(15).inner_seconds(),
|
if let Ok(poly) = Polygon::from_geojson(&p) {
|
||||||
];
|
batch.push(*color, poly.scale(resolution_m));
|
||||||
// And color the polygon for each threshold
|
|
||||||
let colors = vec![
|
|
||||||
Color::GREEN.alpha(0.5),
|
|
||||||
Color::ORANGE.alpha(0.5),
|
|
||||||
Color::RED.alpha(0.5),
|
|
||||||
];
|
|
||||||
let smooth = false;
|
|
||||||
let c = contour::ContourBuilder::new(grid.width as u32, grid.height as u32, smooth);
|
|
||||||
let mut batch = GeomBatch::new();
|
|
||||||
// The last feature returned will be larger than the last threshold value. We don't want to
|
|
||||||
// display that at all. zip() will omit this last pair, since colors.len() ==
|
|
||||||
// thresholds.len() - 1.
|
|
||||||
//
|
|
||||||
// TODO Actually, this still isn't working. I think each polygon is everything > the
|
|
||||||
// threshold, not everything between two thresholds?
|
|
||||||
for (feature, color) in c
|
|
||||||
.contours(&grid.data, &thresholds)
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.zip(colors)
|
|
||||||
{
|
|
||||||
match feature.geometry.unwrap().value {
|
|
||||||
geojson::Value::MultiPolygon(polygons) => {
|
|
||||||
for p in polygons {
|
|
||||||
if let Ok(poly) = Polygon::from_geojson(&p) {
|
|
||||||
batch.push(color, poly.scale(resolution_m));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
batch
|
batch
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the area reachable from all intersections on the map border
|
||||||
|
pub struct BorderIsochrone {
|
||||||
|
/// The center of the isochrone (can be multiple points)
|
||||||
|
pub start: Vec<IntersectionID>,
|
||||||
|
/// The options used to generate this isochrone
|
||||||
|
pub options: Options,
|
||||||
|
/// Colored polygon contours, uploaded to the GPU and ready for drawing
|
||||||
|
pub draw: Drawable,
|
||||||
|
/// Thresholds used to draw the isochrone
|
||||||
|
pub thresholds: Vec<f64>,
|
||||||
|
/// Colors used to draw the isochrone
|
||||||
|
pub colors: Vec<Color>,
|
||||||
|
/// How far away is each building from the start?
|
||||||
|
pub time_to_reach_building: HashMap<BuildingID, Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BorderIsochrone {
|
||||||
|
pub fn new(
|
||||||
|
ctx: &mut EventCtx,
|
||||||
|
app: &App,
|
||||||
|
start: Vec<IntersectionID>,
|
||||||
|
options: Options,
|
||||||
|
) -> BorderIsochrone {
|
||||||
|
let spot_starts = start.iter().map(|i_id| Spot::Border(*i_id)).collect();
|
||||||
|
let time_to_reach_building = options.clone().times_from(&app.map, spot_starts);
|
||||||
|
|
||||||
|
// Generate a single polygon showing 15 minutes from the border
|
||||||
|
let thresholds = vec![0.1, Duration::minutes(15).inner_seconds()];
|
||||||
|
|
||||||
|
// Use one color for the entire polygon
|
||||||
|
let colors = vec![Color::rgb(0, 0, 0).alpha(0.3)];
|
||||||
|
|
||||||
|
let mut i = BorderIsochrone {
|
||||||
|
start,
|
||||||
|
options,
|
||||||
|
draw: Drawable::empty(ctx),
|
||||||
|
thresholds,
|
||||||
|
colors,
|
||||||
|
time_to_reach_building,
|
||||||
|
};
|
||||||
|
|
||||||
|
i.draw =
|
||||||
|
draw_isochrone(app, &i.time_to_reach_building, &i.thresholds, &i.colors).upload(ctx);
|
||||||
|
i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ use widgetry::{
|
|||||||
|
|
||||||
use crate::find_amenities::FindAmenity;
|
use crate::find_amenities::FindAmenity;
|
||||||
use crate::find_home::FindHome;
|
use crate::find_home::FindHome;
|
||||||
use crate::isochrone::{Isochrone, Options};
|
use crate::isochrone::{draw_isochrone, Isochrone, Options};
|
||||||
use crate::App;
|
use crate::App;
|
||||||
|
|
||||||
/// This is the UI state for exploring the isochrone/walkshed from a single building.
|
/// This is the UI state for exploring the isochrone/walkshed from a single building.
|
||||||
@ -450,7 +450,12 @@ impl ExploreAmenities {
|
|||||||
isochrone: &Isochrone,
|
isochrone: &Isochrone,
|
||||||
category: AmenityType,
|
category: AmenityType,
|
||||||
) -> Box<dyn State<App>> {
|
) -> Box<dyn State<App>> {
|
||||||
let mut batch = isochrone.draw_isochrone(app);
|
let mut batch = draw_isochrone(
|
||||||
|
app,
|
||||||
|
&isochrone.time_to_reach_building,
|
||||||
|
&isochrone.thresholds,
|
||||||
|
&isochrone.colors,
|
||||||
|
);
|
||||||
batch.append(draw_star(ctx, app.map.get_b(isochrone.start[0])));
|
batch.append(draw_star(ctx, app.map.get_b(isochrone.start[0])));
|
||||||
|
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// TODO Possibly these should be methods on Map.
|
// TODO Possibly these should be methods on Map.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::{BinaryHeap, HashMap, HashSet};
|
use std::collections::{BinaryHeap, HashMap, HashSet};
|
||||||
|
|
||||||
@ -10,10 +11,18 @@ use geom::Duration;
|
|||||||
pub use self::walking::{all_walking_costs_from, WalkingOptions};
|
pub use self::walking::{all_walking_costs_from, WalkingOptions};
|
||||||
use crate::pathfind::{build_graph_for_vehicles, zone_cost};
|
use crate::pathfind::{build_graph_for_vehicles, zone_cost};
|
||||||
pub use crate::pathfind::{vehicle_cost, WalkingNode};
|
pub use crate::pathfind::{vehicle_cost, WalkingNode};
|
||||||
use crate::{BuildingID, DirectedRoadID, LaneID, Map, PathConstraints, PathRequest};
|
use crate::{
|
||||||
|
BuildingID, DirectedRoadID, IntersectionID, LaneID, Map, PathConstraints, PathRequest,
|
||||||
|
};
|
||||||
|
|
||||||
mod walking;
|
mod walking;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub enum Spot {
|
||||||
|
Building(BuildingID),
|
||||||
|
Border(IntersectionID),
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate the strongly connected components (SCC) of the part of the map accessible by
|
/// Calculate the strongly connected components (SCC) of the part of the map accessible by
|
||||||
/// constraints (ie, the graph of sidewalks or driving+bike lanes). The largest component is the
|
/// constraints (ie, the graph of sidewalks or driving+bike lanes). The largest component is the
|
||||||
/// "main" graph; the rest is disconnected. Returns (lanes in the largest "main" component, all
|
/// "main" graph; the rest is disconnected. Returns (lanes in the largest "main" component, all
|
||||||
@ -51,12 +60,12 @@ pub fn find_scc(map: &Map, constraints: PathConstraints) -> (HashSet<LaneID>, Ha
|
|||||||
(largest_group, disconnected)
|
(largest_group, disconnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starting from some initial buildings, calculate the cost to all others. If a destination isn't
|
/// Starting from some initial spot, calculate the cost to all buildings. If a destination isn't
|
||||||
/// reachable, it won't be included in the results. Ignore results greater than the time_limit
|
/// reachable, it won't be included in the results. Ignore results greater than the time_limit
|
||||||
/// away.
|
/// away.
|
||||||
pub fn all_vehicle_costs_from(
|
pub fn all_vehicle_costs_from(
|
||||||
map: &Map,
|
map: &Map,
|
||||||
starts: Vec<BuildingID>,
|
starts: Vec<Spot>,
|
||||||
time_limit: Duration,
|
time_limit: Duration,
|
||||||
constraints: PathConstraints,
|
constraints: PathConstraints,
|
||||||
) -> HashMap<BuildingID, Duration> {
|
) -> HashMap<BuildingID, Duration> {
|
||||||
@ -64,6 +73,7 @@ pub fn all_vehicle_costs_from(
|
|||||||
// TODO We have a graph of DirectedRoadIDs, but mapping a building to one isn't
|
// TODO We have a graph of DirectedRoadIDs, but mapping a building to one isn't
|
||||||
// straightforward. In the common case it'll be fine, but some buildings are isolated from the
|
// straightforward. In the common case it'll be fine, but some buildings are isolated from the
|
||||||
// graph by some sidewalks.
|
// graph by some sidewalks.
|
||||||
|
|
||||||
let mut bldg_to_road = HashMap::new();
|
let mut bldg_to_road = HashMap::new();
|
||||||
for b in map.all_buildings() {
|
for b in map.all_buildings() {
|
||||||
if constraints == PathConstraints::Car {
|
if constraints == PathConstraints::Car {
|
||||||
@ -78,12 +88,32 @@ pub fn all_vehicle_costs_from(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
||||||
for b in starts {
|
|
||||||
if let Some(start_road) = bldg_to_road.get(&b).cloned() {
|
for spot in starts {
|
||||||
queue.push(Item {
|
match spot {
|
||||||
cost: Duration::ZERO,
|
Spot::Building(b_id) => {
|
||||||
node: start_road,
|
if let Some(start_road) = bldg_to_road.get(&b_id).cloned() {
|
||||||
});
|
queue.push(Item {
|
||||||
|
cost: Duration::ZERO,
|
||||||
|
node: start_road,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spot::Border(i_id) => {
|
||||||
|
let intersection = map.get_i(i_id);
|
||||||
|
|
||||||
|
let incoming_lanes = intersection.get_incoming_lanes(map, constraints);
|
||||||
|
let mut outgoing_lanes = intersection.get_outgoing_lanes(map, constraints);
|
||||||
|
let mut all_lanes = incoming_lanes;
|
||||||
|
all_lanes.append(&mut outgoing_lanes);
|
||||||
|
|
||||||
|
for l_id in all_lanes {
|
||||||
|
queue.push(Item {
|
||||||
|
cost: Duration::ZERO,
|
||||||
|
node: map.get_l(l_id).get_directed_parent(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@ use std::collections::{BinaryHeap, HashMap};
|
|||||||
|
|
||||||
use geom::{Duration, Speed};
|
use geom::{Duration, Speed};
|
||||||
|
|
||||||
|
use crate::connectivity::Spot;
|
||||||
use crate::pathfind::{zone_cost, WalkingNode};
|
use crate::pathfind::{zone_cost, WalkingNode};
|
||||||
use crate::{BuildingID, LaneType, Map, PathConstraints, Traversable};
|
use crate::{BuildingID, Lane, LaneType, Map, PathConstraints, Traversable};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WalkingOptions {
|
pub struct WalkingOptions {
|
||||||
@ -64,24 +65,57 @@ impl Ord for Item {
|
|||||||
/// the results will always be empty.
|
/// the results will always be empty.
|
||||||
pub fn all_walking_costs_from(
|
pub fn all_walking_costs_from(
|
||||||
map: &Map,
|
map: &Map,
|
||||||
starts: Vec<BuildingID>,
|
starts: Vec<Spot>,
|
||||||
time_limit: Duration,
|
time_limit: Duration,
|
||||||
opts: WalkingOptions,
|
opts: WalkingOptions,
|
||||||
) -> HashMap<BuildingID, Duration> {
|
) -> HashMap<BuildingID, Duration> {
|
||||||
if !opts.allow_shoulders
|
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
||||||
&& starts
|
|
||||||
.iter()
|
for spot in starts {
|
||||||
.all(|b| map.get_l(map.get_b(*b).sidewalk()).lane_type == LaneType::Shoulder)
|
match spot {
|
||||||
{
|
Spot::Building(b_id) => {
|
||||||
return HashMap::new();
|
queue.push(Item {
|
||||||
|
cost: Duration::ZERO,
|
||||||
|
node: WalkingNode::closest(map.get_b(b_id).sidewalk_pos, map),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Spot::Border(i_id) => {
|
||||||
|
let intersection = map.get_i(i_id);
|
||||||
|
let incoming_lanes = intersection.incoming_lanes.clone();
|
||||||
|
let mut outgoing_lanes = intersection.outgoing_lanes.clone();
|
||||||
|
let mut all_lanes = incoming_lanes;
|
||||||
|
all_lanes.append(&mut outgoing_lanes);
|
||||||
|
let walkable_lanes: Vec<&Lane> = all_lanes
|
||||||
|
.into_iter()
|
||||||
|
.map(|l_id| map.get_l(l_id))
|
||||||
|
.filter(|l| l.is_walkable())
|
||||||
|
.collect();
|
||||||
|
for lane in walkable_lanes {
|
||||||
|
queue.push(Item {
|
||||||
|
cost: Duration::ZERO,
|
||||||
|
node: WalkingNode::SidewalkEndpoint(
|
||||||
|
lane.get_directed_parent(),
|
||||||
|
lane.src_i == i_id,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
if !opts.allow_shoulders {
|
||||||
for b in starts {
|
let mut shoulder_endpoint = Vec::new();
|
||||||
queue.push(Item {
|
for q in &queue {
|
||||||
cost: Duration::ZERO,
|
if let WalkingNode::SidewalkEndpoint(dir_r, _) = q.node {
|
||||||
node: WalkingNode::closest(map.get_b(b).sidewalk_pos, map),
|
let lanes = &map.get_r(dir_r.id).lanes_ltr;
|
||||||
});
|
for (_, _, lane_type) in lanes {
|
||||||
|
shoulder_endpoint.push(lane_type == &LaneType::Shoulder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shoulder_endpoint.into_iter().all(|x| x) {
|
||||||
|
return HashMap::new();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cost_per_node: HashMap<WalkingNode, Duration> = HashMap::new();
|
let mut cost_per_node: HashMap<WalkingNode, Duration> = HashMap::new();
|
||||||
|
Loading…
Reference in New Issue
Block a user