mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-23 17:07:12 +03:00
Add a layer showing pedestrian density.
This commit is contained in:
parent
16e539a77c
commit
a229bf9385
@ -109,6 +109,7 @@ impl PickLayer {
|
||||
btn("throughput", Key::T),
|
||||
btn("traffic jams", Key::J),
|
||||
btn("cycling activity", Key::B),
|
||||
btn("pedestrian crowding", Key::C),
|
||||
]),
|
||||
Widget::col(vec![
|
||||
"Map".text_widget(ctx),
|
||||
@ -178,6 +179,9 @@ impl State<App> for PickLayer {
|
||||
"delay" => {
|
||||
app.primary.layer = Some(Box::new(traffic::Delay::new(ctx, app)));
|
||||
}
|
||||
"pedestrian crowding" => {
|
||||
app.primary.layer = Some(Box::new(traffic::PedestrianCrowding::new(ctx, app)));
|
||||
}
|
||||
"steep streets" => {
|
||||
app.primary.layer = Some(Box::new(elevation::SteepStreets::new(ctx, app)));
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use maplit::btreeset;
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use geom::{Circle, Distance, Duration, Percent, Polygon, Pt2D, Time};
|
||||
use map_gui::render::unzoomed_agent_radius;
|
||||
use map_gui::tools::{ColorLegend, ColorNetwork, DivergingScale};
|
||||
use map_gui::tools::{ColorDiscrete, ColorLegend, ColorNetwork, DivergingScale};
|
||||
use map_gui::ID;
|
||||
use map_model::{IntersectionID, Map, Traversable};
|
||||
use sim::{AgentType, VehicleType};
|
||||
@ -622,6 +622,86 @@ impl Delay {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PedestrianCrowding {
|
||||
time: Time,
|
||||
draw: ToggleZoomed,
|
||||
panel: Panel,
|
||||
}
|
||||
|
||||
impl Layer for PedestrianCrowding {
|
||||
fn name(&self) -> Option<&'static str> {
|
||||
Some("pedestrian crowding")
|
||||
}
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Option<LayerOutcome> {
|
||||
if app.primary.sim.time() != self.time {
|
||||
*self = Self::new(ctx, app);
|
||||
}
|
||||
|
||||
if let Outcome::Clicked(x) = self.panel.event(ctx) {
|
||||
match x.as_ref() {
|
||||
"close" => {
|
||||
return Some(LayerOutcome::Close);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.draw.draw(g);
|
||||
}
|
||||
fn draw_minimap(&self, g: &mut GfxCtx) {
|
||||
g.redraw(&self.draw.unzoomed);
|
||||
}
|
||||
}
|
||||
|
||||
impl PedestrianCrowding {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App) -> Self {
|
||||
let categories = vec![
|
||||
("< 1", app.cs.good_to_bad_red.eval(0.0)),
|
||||
("1 - 1.5", app.cs.good_to_bad_red.eval(0.3)),
|
||||
("1.5 - 2.0", app.cs.good_to_bad_red.eval(0.6)),
|
||||
("> 2", app.cs.good_to_bad_red.eval(1.0)),
|
||||
];
|
||||
let mut colorer = ColorDiscrete::new(app, categories);
|
||||
|
||||
fn bucket(x: f64) -> &'static str {
|
||||
if x < 1.0 {
|
||||
"< 1"
|
||||
} else if x <= 1.5 {
|
||||
"1 - 1.5"
|
||||
} else if x <= 2.0 {
|
||||
"1.5 - 2.0"
|
||||
} else {
|
||||
"> 2"
|
||||
}
|
||||
}
|
||||
|
||||
let (roads, intersections) = app.primary.sim.get_pedestrian_density(&app.primary.map);
|
||||
for (r, density) in roads {
|
||||
colorer.add_r(r, bucket(density));
|
||||
}
|
||||
for (i, density) in intersections {
|
||||
colorer.add_i(i, bucket(density));
|
||||
}
|
||||
|
||||
let (draw, legend) = colorer.build(ctx);
|
||||
|
||||
Self {
|
||||
time: app.primary.sim.time(),
|
||||
draw,
|
||||
panel: Panel::new_builder(Widget::col(vec![
|
||||
header(ctx, "Pedestrian crowding"),
|
||||
"(people / m^2)".text_widget(ctx),
|
||||
legend,
|
||||
]))
|
||||
.aligned_pair(PANEL_PLACEMENT)
|
||||
.build(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn export_throughput(app: &App) -> Result<(String, String)> {
|
||||
let path1 = format!(
|
||||
"road_throughput_{}_{}.csv",
|
||||
|
@ -8,6 +8,7 @@ use widgetry::{Color, EventCtx, Fill, GeomBatch, Line, LinearGradient, Text, Wid
|
||||
|
||||
use crate::AppLike;
|
||||
|
||||
// TODO Tooltips would almost be nice, for cases like pedestrian crowding
|
||||
pub struct ColorDiscrete<'a> {
|
||||
map: &'a Map,
|
||||
// pub so callers can add stuff in before building
|
||||
|
@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize};
|
||||
use abstutil::{deserialize_multimap, serialize_multimap, FixedMap, IndexableKey, MultiMap};
|
||||
use geom::{Distance, Duration, Line, PolyLine, Speed, Time};
|
||||
use map_model::{
|
||||
BuildingID, DrivingSide, Map, ParkingLotID, Path, PathConstraints, PathStep, TransitRouteID,
|
||||
Traversable,
|
||||
BuildingID, DrivingSide, IntersectionID, Map, ParkingLotID, Path, PathConstraints, PathStep,
|
||||
RoadID, TransitRouteID, Traversable,
|
||||
};
|
||||
|
||||
use crate::sim::Ctx;
|
||||
@ -625,6 +625,35 @@ impl WalkingSimState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pedestrian_density(
|
||||
&self,
|
||||
map: &Map,
|
||||
) -> (BTreeMap<RoadID, f64>, BTreeMap<IntersectionID, f64>) {
|
||||
let mut roads = BTreeMap::new();
|
||||
let mut intersections = BTreeMap::new();
|
||||
for (traversable, peds) in self.peds_per_traversable.borrow() {
|
||||
if peds.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let density = (peds.len() as f64) / area(map, *traversable);
|
||||
match traversable {
|
||||
Traversable::Lane(l) => {
|
||||
let entry = roads.entry(l.road).or_insert(0.0);
|
||||
if *entry < density {
|
||||
*entry = density;
|
||||
}
|
||||
}
|
||||
Traversable::Turn(t) => {
|
||||
let entry = intersections.entry(t.parent).or_insert(0.0);
|
||||
if *entry < density {
|
||||
*entry = density;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(roads, intersections)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@ -995,20 +1024,9 @@ fn crowdedness_penalty(
|
||||
traversable: Traversable,
|
||||
peds_per_traversable: &MultiMap<Traversable, PedestrianID>,
|
||||
) -> f64 {
|
||||
// 1) How many people are in the space
|
||||
let num_people = peds_per_traversable.get(traversable).len();
|
||||
// 2) The length of the sidewalk or crosswalk
|
||||
let len = traversable.get_polyline(map).length();
|
||||
// 3) The width of the space
|
||||
let width = match traversable {
|
||||
// Sidewalk
|
||||
Traversable::Lane(l) => map.get_l(l).width,
|
||||
// For crosswalks, the thinner of the two sidewalks being connected
|
||||
Traversable::Turn(t) => map.get_l(t.src).width.min(map.get_l(t.dst).width),
|
||||
};
|
||||
|
||||
// Person per area, assuming everyone's equally spread out
|
||||
let people_per_sq_m = (num_people as f64) / (width.inner_meters() * len.inner_meters());
|
||||
// Assume everyone's equally spread out
|
||||
let people_per_sq_m = (num_people as f64) / area(map, traversable);
|
||||
// Based on eyeballing images from
|
||||
// https://www.gkstill.com/Support/crowd-density/CrowdDensity-1.html, let's use a fixed
|
||||
// threshold of 1.5 people per square meter as "crowded" and slow them down by half.
|
||||
@ -1018,3 +1036,17 @@ fn crowdedness_penalty(
|
||||
}
|
||||
0.5
|
||||
}
|
||||
|
||||
// In m^2
|
||||
fn area(map: &Map, traversable: Traversable) -> f64 {
|
||||
// The length of the sidewalk or crosswalk
|
||||
let len = traversable.get_polyline(map).length();
|
||||
// The width of the space
|
||||
let width = match traversable {
|
||||
// Sidewalk
|
||||
Traversable::Lane(l) => map.get_l(l).width,
|
||||
// For crosswalks, the thinner of the two sidewalks being connected
|
||||
Traversable::Turn(t) => map.get_l(t.src).width.min(map.get_l(t.dst).width),
|
||||
};
|
||||
width.inner_meters() * len.inner_meters()
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use abstutil::Counter;
|
||||
use geom::{Distance, Duration, PolyLine, Pt2D, Time};
|
||||
use map_model::{
|
||||
BuildingID, IntersectionID, Lane, LaneID, Map, Path, Position, TransitRouteID, TransitStopID,
|
||||
Traversable, TurnID,
|
||||
BuildingID, IntersectionID, Lane, LaneID, Map, Path, Position, RoadID, TransitRouteID,
|
||||
TransitStopID, Traversable, TurnID,
|
||||
};
|
||||
use synthpop::{OrigPersonID, Scenario, TripMode};
|
||||
|
||||
@ -452,6 +452,15 @@ impl Sim {
|
||||
pub fn get_highlighted_people(&self) -> &Option<BTreeSet<PersonID>> {
|
||||
&self.highlighted_people
|
||||
}
|
||||
|
||||
/// Returns people / m^2. Roads have up to two sidewalks and intersections have many crossings
|
||||
/// -- take the max density along any one.
|
||||
pub fn get_pedestrian_density(
|
||||
&self,
|
||||
map: &Map,
|
||||
) -> (BTreeMap<RoadID, f64>, BTreeMap<IntersectionID, f64>) {
|
||||
self.walking.get_pedestrian_density(map)
|
||||
}
|
||||
}
|
||||
|
||||
// Drawing
|
||||
|
Loading…
Reference in New Issue
Block a user