mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-27 15:03:20 +03:00
Make the LTN tool handle roads that already ban cars.
- Don't allow filtering them - Don't cross them when calculating cell connectivity - Create special green cells for them, and don't count towards the disconnected warning
This commit is contained in:
parent
332e88f235
commit
7d6f133f1c
@ -60,7 +60,7 @@ impl Viewer {
|
||||
.neighborhood
|
||||
.cells
|
||||
.iter()
|
||||
.filter(|c| c.borders.is_empty())
|
||||
.filter(|c| c.borders.is_empty() && !c.car_free)
|
||||
.count();
|
||||
// TODO Also add a red outline to them or something
|
||||
let warning = if disconnected_cells == 0 {
|
||||
|
@ -10,11 +10,12 @@ use super::Neighborhood;
|
||||
const COLORS: [Color; 6] = [
|
||||
Color::BLUE,
|
||||
Color::YELLOW,
|
||||
Color::GREEN,
|
||||
Color::RED,
|
||||
Color::PURPLE,
|
||||
Color::PINK,
|
||||
Color::ORANGE,
|
||||
];
|
||||
const CAR_FREE_COLOR: Color = Color::GREEN;
|
||||
|
||||
/// Partition a neighborhood's boundary polygon based on the cells. Currently this discretizes
|
||||
/// space into a grid, so the results don't look perfect, but it's fast. Also returns the color for
|
||||
@ -74,7 +75,14 @@ pub fn draw_cells(map: &Map, neighborhood: &Neighborhood) -> (GeomBatch, Vec<Col
|
||||
}
|
||||
|
||||
let adjacencies = diffusion(&mut grid, boundary_marker);
|
||||
let cell_colors = color_cells(neighborhood.cells.len(), adjacencies);
|
||||
let mut cell_colors = color_cells(neighborhood.cells.len(), adjacencies);
|
||||
|
||||
// Color car-free cells in a special way
|
||||
for (idx, cell) in neighborhood.cells.iter().enumerate() {
|
||||
if cell.car_free {
|
||||
cell_colors[idx] = CAR_FREE_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
// Just draw rectangles based on the grid
|
||||
// TODO We should be able to generate actual polygons per cell using the contours crate
|
||||
|
@ -4,7 +4,7 @@ use maplit::btreeset;
|
||||
|
||||
use geom::{Circle, Distance, Line, Polygon};
|
||||
use map_gui::tools::DrawRoadLabels;
|
||||
use map_model::{IntersectionID, Map, Perimeter, RoadID, RoutingParams, TurnID};
|
||||
use map_model::{IntersectionID, Map, PathConstraints, Perimeter, RoadID, RoutingParams, TurnID};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, Drawable, EventCtx, GeomBatch};
|
||||
|
||||
@ -88,6 +88,8 @@ pub struct Cell {
|
||||
pub roads: BTreeMap<RoadID, DistanceInterval>,
|
||||
/// Intersections where this cell touches the boundary of the neighborhood.
|
||||
pub borders: BTreeSet<IntersectionID>,
|
||||
/// This cell only contains roads that ban cars.
|
||||
pub car_free: bool,
|
||||
}
|
||||
|
||||
/// An interval along a road's length, with start < end.
|
||||
@ -245,11 +247,17 @@ fn find_cells(
|
||||
let mut cells = Vec::new();
|
||||
let mut visited = BTreeSet::new();
|
||||
|
||||
let mut no_car_roads = Vec::new();
|
||||
for start in &perimeter.interior {
|
||||
if visited.contains(start) || modal_filters.roads.contains_key(start) {
|
||||
continue;
|
||||
}
|
||||
let cell = floodfill(map, *start, perimeter, borders, &modal_filters);
|
||||
let start = *start;
|
||||
if !PathConstraints::Car.can_use_road(map.get_r(start), map) {
|
||||
no_car_roads.push(start);
|
||||
continue;
|
||||
}
|
||||
let cell = floodfill(map, start, perimeter, borders, &modal_filters);
|
||||
visited.extend(cell.roads.keys().cloned());
|
||||
cells.push(cell);
|
||||
}
|
||||
@ -261,6 +269,7 @@ fn find_cells(
|
||||
let mut cell = Cell {
|
||||
roads: BTreeMap::new(),
|
||||
borders: btreeset! { road.src_i },
|
||||
car_free: false,
|
||||
};
|
||||
cell.roads.insert(
|
||||
road.id,
|
||||
@ -275,6 +284,7 @@ fn find_cells(
|
||||
let mut cell = Cell {
|
||||
roads: BTreeMap::new(),
|
||||
borders: btreeset! { road.dst_i },
|
||||
car_free: false,
|
||||
};
|
||||
cell.roads.insert(
|
||||
road.id,
|
||||
@ -287,6 +297,34 @@ fn find_cells(
|
||||
}
|
||||
}
|
||||
|
||||
// Roads already banning cars should still contribute a cell, so the cell coloring can still
|
||||
// account for them
|
||||
//
|
||||
// TODO Should we attempt to merge adjacent cells like this? If we have lots of tiny pieces of
|
||||
// bike-only roads, they'll each get their own cell
|
||||
for r in no_car_roads {
|
||||
let mut cell = Cell {
|
||||
roads: BTreeMap::new(),
|
||||
borders: BTreeSet::new(),
|
||||
car_free: true,
|
||||
};
|
||||
let road = map.get_r(r);
|
||||
if borders.contains(&road.src_i) {
|
||||
cell.borders.insert(road.src_i);
|
||||
}
|
||||
if borders.contains(&road.dst_i) {
|
||||
cell.borders.insert(road.dst_i);
|
||||
}
|
||||
cell.roads.insert(
|
||||
road.id,
|
||||
DistanceInterval {
|
||||
start: Distance::ZERO,
|
||||
end: road.length(),
|
||||
},
|
||||
);
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
cells
|
||||
}
|
||||
|
||||
@ -304,6 +342,7 @@ fn floodfill(
|
||||
|
||||
// The caller should handle this case
|
||||
assert!(!modal_filters.roads.contains_key(&start));
|
||||
assert!(PathConstraints::Car.can_use_road(map.get_r(start), map));
|
||||
|
||||
while !queue.is_empty() {
|
||||
let current = map.get_r(queue.pop().unwrap());
|
||||
@ -319,6 +358,7 @@ fn floodfill(
|
||||
);
|
||||
for i in [current.src_i, current.dst_i] {
|
||||
for next in &map.get_i(i).roads {
|
||||
let next_road = map.get_r(*next);
|
||||
if !perimeter.interior.contains(next) {
|
||||
if neighborhood_borders.contains(&i) {
|
||||
cell_borders.insert(i);
|
||||
@ -331,7 +371,6 @@ fn floodfill(
|
||||
}
|
||||
}
|
||||
if let Some(filter_dist) = modal_filters.roads.get(next) {
|
||||
let next_road = map.get_r(*next);
|
||||
// Which ends of the filtered road have we reached?
|
||||
let mut visited_start = next_road.src_i == i;
|
||||
let mut visited_end = next_road.dst_i == i;
|
||||
@ -359,9 +398,15 @@ fn floodfill(
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
queue.push(*next);
|
||||
continue;
|
||||
}
|
||||
|
||||
if !PathConstraints::Car.can_use_road(next_road, map) {
|
||||
// The road is only for bikes/pedestrians to start with
|
||||
continue;
|
||||
}
|
||||
|
||||
queue.push(*next);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -369,6 +414,7 @@ fn floodfill(
|
||||
Cell {
|
||||
roads: visited_roads,
|
||||
borders: cell_borders,
|
||||
car_free: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use geom::Distance;
|
||||
use map_gui::tools::CityPicker;
|
||||
use map_model::{IntersectionID, RoadID};
|
||||
use map_model::{IntersectionID, PathConstraints, RoadID};
|
||||
use widgetry::mapspace::{ObjectID, World, WorldOutcome};
|
||||
use widgetry::{
|
||||
Color, EventCtx, HorizontalAlignment, Key, Panel, PanelBuilder, State, TextExt,
|
||||
@ -177,11 +177,17 @@ pub fn handle_world_outcome(
|
||||
app: &mut App,
|
||||
outcome: WorldOutcome<FilterableObj>,
|
||||
) -> bool {
|
||||
let map = &app.primary.map;
|
||||
match outcome {
|
||||
WorldOutcome::ClickedObject(FilterableObj::InteriorRoad(r)) => {
|
||||
let road = map.get_r(r);
|
||||
// Filtering on a road that's already marked bike-only doesn't make sense
|
||||
if !PathConstraints::Car.can_use_road(road, map) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if app.session.modal_filters.roads.remove(&r).is_none() {
|
||||
// Place the filter on the part of the road that was clicked
|
||||
let road = app.primary.map.get_r(r);
|
||||
// These calls shouldn't fail -- since we clicked a road, the cursor must be in
|
||||
// map-space. And project_pt returns a point that's guaranteed to be on the
|
||||
// polyline.
|
||||
@ -194,7 +200,7 @@ pub fn handle_world_outcome(
|
||||
true
|
||||
}
|
||||
WorldOutcome::ClickedObject(FilterableObj::InteriorIntersection(i)) => {
|
||||
if app.primary.map.get_i(i).roads.len() != 4 {
|
||||
if map.get_i(i).roads.len() != 4 {
|
||||
// Misleading. Nothing changes, but we'll "fall through" to other cases without
|
||||
// this
|
||||
return true;
|
||||
|
@ -13,7 +13,7 @@ pub use self::v1::{Path, PathRequest, PathStep};
|
||||
pub use self::v2::{PathStepV2, PathV2};
|
||||
pub use self::vehicles::vehicle_cost;
|
||||
pub use self::walking::WalkingNode;
|
||||
use crate::{osm, Lane, LaneID, LaneType, Map, MovementID, RoadID, TurnType};
|
||||
use crate::{osm, Lane, LaneID, LaneType, Map, MovementID, Road, RoadID, TurnType};
|
||||
|
||||
mod engine;
|
||||
mod node_map;
|
||||
@ -115,6 +115,12 @@ impl PathConstraints {
|
||||
false
|
||||
}
|
||||
|
||||
/// Can an agent use a road in either direction? There are some subtle exceptions with using
|
||||
/// bus-only lanes for turns.
|
||||
pub fn can_use_road(self, road: &Road, map: &Map) -> bool {
|
||||
road.lanes.iter().any(|lane| self.can_use(lane, map))
|
||||
}
|
||||
|
||||
/// Strict for bikes. If there are bike lanes, not allowed to use other lanes.
|
||||
pub(crate) fn filter_lanes(self, mut choices: Vec<LaneID>, map: &Map) -> Vec<LaneID> {
|
||||
choices.retain(|l| self.can_use(map.get_l(*l), map));
|
||||
|
@ -68,7 +68,7 @@ impl Texture {
|
||||
}
|
||||
|
||||
impl Color {
|
||||
// TODO Won't this confuse the shader? :P
|
||||
// TODO Won't this confuse the shader?
|
||||
pub const CLEAR: Color = Color::rgba_f(1.0, 0.0, 0.0, 0.0);
|
||||
pub const BLACK: Color = Color::rgb_f(0.0, 0.0, 0.0);
|
||||
pub const WHITE: Color = Color::rgb_f(1.0, 1.0, 1.0);
|
||||
@ -110,6 +110,7 @@ impl Color {
|
||||
Color::rgb_f(f, f, f)
|
||||
}
|
||||
|
||||
/// Note this is incorrect for `Color::CLEAR`. Can't fix in a const fn.
|
||||
pub const fn alpha(&self, a: f32) -> Color {
|
||||
Color::rgba_f(self.r, self.g, self.b, a)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user