mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 01:15:12 +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,
|
||||
};
|
||||
|
||||
use crate::isochrone::{Isochrone, Options};
|
||||
use crate::isochrone::{draw_isochrone, BorderIsochrone, Isochrone, Options};
|
||||
use crate::viewer::{draw_star, HoverKey, HoverOnBuilding};
|
||||
use crate::App;
|
||||
|
||||
@ -23,8 +23,15 @@ impl FindAmenity {
|
||||
.map(|at| Choice::new(at.to_string(), at))
|
||||
.collect(),
|
||||
Box::new(move |at, ctx, app| {
|
||||
let multi_isochrone = create_multi_isochrone(ctx, app, at, options);
|
||||
return Transition::Replace(Results::new_state(ctx, app, multi_isochrone, at));
|
||||
let multi_isochrone = create_multi_isochrone(ctx, app, at, options.clone());
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
draw: Drawable,
|
||||
isochrone: Isochrone,
|
||||
@ -59,6 +77,7 @@ impl Results {
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
isochrone: Isochrone,
|
||||
border_isochrone: BorderIsochrone,
|
||||
category: AmenityType,
|
||||
) -> Box<dyn State<App>> {
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
@ -77,11 +96,27 @@ impl Results {
|
||||
(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)
|
||||
.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 {
|
||||
batch.append(draw_star(ctx, app.map.get_b(start)));
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::App;
|
||||
use abstutil::{prettyprint_usize, Counter, Timer};
|
||||
use geom::Percent;
|
||||
use map_gui::tools::PopupMsg;
|
||||
use map_model::connectivity::Spot;
|
||||
use map_model::{AmenityType, BuildingID};
|
||||
use widgetry::{
|
||||
Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Panel,
|
||||
@ -10,7 +12,6 @@ use widgetry::{
|
||||
};
|
||||
|
||||
use crate::isochrone::Options;
|
||||
use crate::App;
|
||||
|
||||
/// Ask what types of amenities are necessary to be within a walkshed, then rank every house with
|
||||
/// how many of those needs are satisfied.
|
||||
@ -98,12 +99,10 @@ fn score_houses(
|
||||
let mut stores = Vec::new();
|
||||
for b in map.all_buildings() {
|
||||
if b.has_amenity(category) {
|
||||
stores.push(b.id);
|
||||
stores.push(Spot::Building(b.id));
|
||||
}
|
||||
}
|
||||
|
||||
// Then find all buildings reachable from any of those starting points
|
||||
options.clone().times_from_buildings(map, stores)
|
||||
options.clone().times_from(map, stores)
|
||||
}) {
|
||||
for (b, _) in times {
|
||||
satisfied_per_bldg.inc(b);
|
||||
|
@ -1,11 +1,12 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use abstutil::MultiMap;
|
||||
use connectivity::Spot;
|
||||
use geom::{Duration, Polygon};
|
||||
use map_gui::tools::Grid;
|
||||
use map_model::{
|
||||
connectivity, AmenityType, BuildingID, BuildingType, LaneType, Map, Path, PathConstraints,
|
||||
PathRequest,
|
||||
connectivity, AmenityType, BuildingID, BuildingType, IntersectionID, LaneType, Map, Path,
|
||||
PathConstraints, PathRequest,
|
||||
};
|
||||
use widgetry::{Color, Drawable, EventCtx, GeomBatch};
|
||||
|
||||
@ -19,6 +20,10 @@ pub struct 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>,
|
||||
/// Per category of amenity, what buildings have that?
|
||||
@ -40,11 +45,7 @@ pub enum Options {
|
||||
impl Options {
|
||||
/// 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.
|
||||
pub fn times_from_buildings(
|
||||
self,
|
||||
map: &Map,
|
||||
starts: Vec<BuildingID>,
|
||||
) -> HashMap<BuildingID, Duration> {
|
||||
pub fn times_from(self, map: &Map, starts: Vec<Spot>) -> HashMap<BuildingID, Duration> {
|
||||
match self {
|
||||
Options::Walking(opts) => {
|
||||
connectivity::all_walking_costs_from(map, starts, Duration::minutes(15), opts)
|
||||
@ -66,9 +67,8 @@ impl Isochrone {
|
||||
start: Vec<BuildingID>,
|
||||
options: Options,
|
||||
) -> Isochrone {
|
||||
let time_to_reach_building = options
|
||||
.clone()
|
||||
.times_from_buildings(&app.map, start.clone());
|
||||
let spot_starts = start.iter().map(|b_id| Spot::Building(*b_id)).collect();
|
||||
let time_to_reach_building = options.clone().times_from(&app.map, spot_starts);
|
||||
|
||||
let mut amenities_reachable = MultiMap::new();
|
||||
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 {
|
||||
start,
|
||||
options,
|
||||
draw: Drawable::empty(ctx),
|
||||
thresholds,
|
||||
colors,
|
||||
time_to_reach_building,
|
||||
amenities_reachable,
|
||||
population,
|
||||
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
|
||||
}
|
||||
|
||||
@ -133,73 +153,111 @@ impl Isochrone {
|
||||
|
||||
all_paths.min_by_key(|path| path.total_length())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_isochrone(&self, app: &App) -> GeomBatch {
|
||||
// To generate the polygons covering areas between 0-5 mins, 5-10 mins, etc, we have to feed
|
||||
// in a 2D grid of costs. Use a 100x100 meter resolution.
|
||||
let bounds = app.map.get_bounds();
|
||||
let resolution_m = 100.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,
|
||||
pub fn draw_isochrone(
|
||||
app: &App,
|
||||
time_to_reach_building: &HashMap<BuildingID, Duration>,
|
||||
thresholds: &[f64],
|
||||
colors: &[Color],
|
||||
) -> GeomBatch {
|
||||
// To generate the polygons covering areas between 0-5 mins, 5-10 mins, etc, we have to feed
|
||||
// in a 2D grid of costs. Use a 100x100 meter resolution.
|
||||
let bounds = app.map.get_bounds();
|
||||
let resolution_m = 100.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
|
||||
for (b, cost) in &self.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();
|
||||
}
|
||||
|
||||
// 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 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));
|
||||
}
|
||||
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_home::FindHome;
|
||||
use crate::isochrone::{Isochrone, Options};
|
||||
use crate::isochrone::{draw_isochrone, Isochrone, Options};
|
||||
use crate::App;
|
||||
|
||||
/// This is the UI state for exploring the isochrone/walkshed from a single building.
|
||||
@ -450,7 +450,12 @@ impl ExploreAmenities {
|
||||
isochrone: &Isochrone,
|
||||
category: AmenityType,
|
||||
) -> 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])));
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
@ -1,5 +1,6 @@
|
||||
// TODO Possibly these should be methods on Map.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BinaryHeap, HashMap, HashSet};
|
||||
|
||||
@ -10,10 +11,18 @@ use geom::Duration;
|
||||
pub use self::walking::{all_walking_costs_from, WalkingOptions};
|
||||
use crate::pathfind::{build_graph_for_vehicles, zone_cost};
|
||||
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;
|
||||
|
||||
#[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
|
||||
/// 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
|
||||
@ -51,12 +60,12 @@ pub fn find_scc(map: &Map, constraints: PathConstraints) -> (HashSet<LaneID>, Ha
|
||||
(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
|
||||
/// away.
|
||||
pub fn all_vehicle_costs_from(
|
||||
map: &Map,
|
||||
starts: Vec<BuildingID>,
|
||||
starts: Vec<Spot>,
|
||||
time_limit: Duration,
|
||||
constraints: PathConstraints,
|
||||
) -> 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
|
||||
// straightforward. In the common case it'll be fine, but some buildings are isolated from the
|
||||
// graph by some sidewalks.
|
||||
|
||||
let mut bldg_to_road = HashMap::new();
|
||||
for b in map.all_buildings() {
|
||||
if constraints == PathConstraints::Car {
|
||||
@ -78,12 +88,32 @@ pub fn all_vehicle_costs_from(
|
||||
}
|
||||
|
||||
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
||||
for b in starts {
|
||||
if let Some(start_road) = bldg_to_road.get(&b).cloned() {
|
||||
queue.push(Item {
|
||||
cost: Duration::ZERO,
|
||||
node: start_road,
|
||||
});
|
||||
|
||||
for spot in starts {
|
||||
match spot {
|
||||
Spot::Building(b_id) => {
|
||||
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 crate::connectivity::Spot;
|
||||
use crate::pathfind::{zone_cost, WalkingNode};
|
||||
use crate::{BuildingID, LaneType, Map, PathConstraints, Traversable};
|
||||
use crate::{BuildingID, Lane, LaneType, Map, PathConstraints, Traversable};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WalkingOptions {
|
||||
@ -64,24 +65,57 @@ impl Ord for Item {
|
||||
/// the results will always be empty.
|
||||
pub fn all_walking_costs_from(
|
||||
map: &Map,
|
||||
starts: Vec<BuildingID>,
|
||||
starts: Vec<Spot>,
|
||||
time_limit: Duration,
|
||||
opts: WalkingOptions,
|
||||
) -> HashMap<BuildingID, Duration> {
|
||||
if !opts.allow_shoulders
|
||||
&& starts
|
||||
.iter()
|
||||
.all(|b| map.get_l(map.get_b(*b).sidewalk()).lane_type == LaneType::Shoulder)
|
||||
{
|
||||
return HashMap::new();
|
||||
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
||||
|
||||
for spot in starts {
|
||||
match spot {
|
||||
Spot::Building(b_id) => {
|
||||
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();
|
||||
for b in starts {
|
||||
queue.push(Item {
|
||||
cost: Duration::ZERO,
|
||||
node: WalkingNode::closest(map.get_b(b).sidewalk_pos, map),
|
||||
});
|
||||
if !opts.allow_shoulders {
|
||||
let mut shoulder_endpoint = Vec::new();
|
||||
for q in &queue {
|
||||
if let WalkingNode::SidewalkEndpoint(dir_r, _) = q.node {
|
||||
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();
|
||||
|
Loading…
Reference in New Issue
Block a user