mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 23:43:25 +03:00
start a tool to understand popular destinations. remove dot map,
superceded by live population map
This commit is contained in:
parent
9ce3061e17
commit
3145326207
@ -93,6 +93,10 @@ impl<T: Ord + PartialEq + Clone> Counter<T> {
|
||||
list.into_iter().map(|(t, _)| t).collect()
|
||||
}
|
||||
|
||||
pub fn max(&self) -> usize {
|
||||
*self.map.values().max().unwrap()
|
||||
}
|
||||
|
||||
pub fn compare(mut self, mut other: Counter<T>) -> Vec<(T, usize, usize)> {
|
||||
for key in self.map.keys() {
|
||||
other.map.entry(key.clone()).or_insert(0);
|
||||
@ -106,6 +110,9 @@ impl<T: Ord + PartialEq + Clone> Counter<T> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn borrow(&self) -> &BTreeMap<T, usize> {
|
||||
&self.map
|
||||
}
|
||||
pub fn consume(self) -> BTreeMap<T, usize> {
|
||||
self.map
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ first make sure your .osm has been clipped:
|
||||
[the instructions](dev.md#building-map-data). You'll need Rust, osmconvert,
|
||||
gdal, etc.
|
||||
|
||||
2. Use http://geojson.io/ to draw a polygon around the region you want to
|
||||
simulate.
|
||||
2. Use [geojson.io](http://geojson.io/) to draw a polygon around the region you
|
||||
want to simulate.
|
||||
|
||||
3. Create a new directory: `mkdir -p data/input/your_city/polygons`
|
||||
|
||||
|
@ -278,6 +278,7 @@ impl App {
|
||||
&ShowEverything::new(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@ -291,10 +292,11 @@ impl App {
|
||||
show_objs: &dyn ShowObject,
|
||||
debug_mode: bool,
|
||||
unzoomed_roads_and_intersections: bool,
|
||||
unzoomed_buildings: bool,
|
||||
) -> Option<ID> {
|
||||
// Unzoomed mode. Ignore when debugging areas and extra shapes.
|
||||
if ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail
|
||||
&& !(debug_mode || unzoomed_roads_and_intersections)
|
||||
&& !(debug_mode || unzoomed_roads_and_intersections || unzoomed_buildings)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@ -332,6 +334,11 @@ impl App {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ID::Building(_) => {
|
||||
if ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail && !unzoomed_buildings {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if ctx.canvas.cam_zoom < self.opts.min_zoom_for_detail {
|
||||
continue;
|
||||
|
@ -110,7 +110,7 @@ impl State for DebugMode {
|
||||
|
||||
if ctx.redo_mouseover() {
|
||||
app.primary.current_selection =
|
||||
app.calculate_current_selection(ctx, &app.primary.sim, self, true, false);
|
||||
app.calculate_current_selection(ctx, &app.primary.sim, self, true, false, false);
|
||||
}
|
||||
|
||||
match self.composite.event(ctx) {
|
||||
@ -179,8 +179,14 @@ impl State for DebugMode {
|
||||
}
|
||||
"unhide everything" => {
|
||||
self.hidden.clear();
|
||||
app.primary.current_selection =
|
||||
app.calculate_current_selection(ctx, &app.primary.sim, self, true, false);
|
||||
app.primary.current_selection = app.calculate_current_selection(
|
||||
ctx,
|
||||
&app.primary.sim,
|
||||
self,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
self.reset_info(ctx);
|
||||
}
|
||||
"search OSM metadata" => {
|
||||
|
@ -28,7 +28,7 @@ struct Block {
|
||||
}
|
||||
|
||||
impl BlockMap {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App, scenario: Scenario) -> BlockMap {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App, scenario: Scenario) -> Box<dyn State> {
|
||||
let mut bldg_to_block = HashMap::new();
|
||||
let mut blocks = Vec::new();
|
||||
|
||||
@ -64,7 +64,7 @@ impl BlockMap {
|
||||
all_blocks.push(Color::YELLOW.alpha(0.5), block.shape.clone());
|
||||
}
|
||||
|
||||
BlockMap {
|
||||
Box::new(BlockMap {
|
||||
bldg_to_block,
|
||||
blocks,
|
||||
scenario,
|
||||
@ -89,7 +89,7 @@ impl BlockMap {
|
||||
)
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn count_per_block(&self, base: &Block, from: bool, map: &Map) -> Vec<(&Block, usize)> {
|
||||
|
152
game/src/devtools/destinations.rs
Normal file
152
game/src/devtools/destinations.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use crate::app::{App, ShowEverything};
|
||||
use crate::common::{make_heatmap, HeatmapOptions};
|
||||
use crate::game::{State, Transition};
|
||||
use crate::helpers::ID;
|
||||
use abstutil::Counter;
|
||||
use ezgui::{
|
||||
hotkey, Btn, Checkbox, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
|
||||
HorizontalAlignment, Key, Line, Outcome, Text, VerticalAlignment, Widget,
|
||||
};
|
||||
use map_model::BuildingID;
|
||||
use sim::{DontDrawAgents, Scenario, TripEndpoint};
|
||||
|
||||
pub struct PopularDestinations {
|
||||
per_bldg: Counter<BuildingID>,
|
||||
composite: Composite,
|
||||
opts: Option<HeatmapOptions>,
|
||||
draw: Drawable,
|
||||
}
|
||||
|
||||
impl PopularDestinations {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App, scenario: &Scenario) -> Box<dyn State> {
|
||||
let mut per_bldg = Counter::new();
|
||||
for p in &scenario.people {
|
||||
for trip in &p.trips {
|
||||
if let TripEndpoint::Bldg(b) = trip.trip.end(&app.primary.map) {
|
||||
per_bldg.inc(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
PopularDestinations::make(ctx, app, per_bldg, None)
|
||||
}
|
||||
|
||||
fn make(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
per_bldg: Counter<BuildingID>,
|
||||
opts: Option<HeatmapOptions>,
|
||||
) -> Box<dyn State> {
|
||||
let map = &app.primary.map;
|
||||
let mut batch = GeomBatch::new();
|
||||
let controls = if let Some(ref o) = opts {
|
||||
let mut pts = Vec::new();
|
||||
for (b, cnt) in per_bldg.borrow() {
|
||||
let pt = map.get_b(*b).label_center;
|
||||
for _ in 0..*cnt {
|
||||
pts.push(pt);
|
||||
}
|
||||
}
|
||||
// TODO Er, the heatmap actually looks terrible.
|
||||
Widget::col(o.to_controls(ctx, make_heatmap(&mut batch, map.get_bounds(), pts, o)))
|
||||
} else {
|
||||
let max = per_bldg.max();
|
||||
let gradient = colorous::REDS;
|
||||
for (b, cnt) in per_bldg.borrow() {
|
||||
let c = gradient.eval_rational(*cnt, max);
|
||||
batch.push(
|
||||
Color::rgb(c.r as usize, c.g as usize, c.b as usize),
|
||||
map.get_b(*b).polygon.clone(),
|
||||
);
|
||||
}
|
||||
Widget::nothing()
|
||||
};
|
||||
|
||||
Box::new(PopularDestinations {
|
||||
per_bldg,
|
||||
draw: ctx.upload(batch),
|
||||
composite: Composite::new(
|
||||
Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line("Most popular destinations")
|
||||
.small_heading()
|
||||
.draw(ctx)
|
||||
.margin_right(10),
|
||||
Btn::text_fg("X")
|
||||
.build_def(ctx, hotkey(Key::Escape))
|
||||
.align_right(),
|
||||
]),
|
||||
Checkbox::text(ctx, "Show heatmap", None, opts.is_some()),
|
||||
controls,
|
||||
])
|
||||
.padding(10)
|
||||
.bg(app.cs.panel_bg),
|
||||
)
|
||||
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
opts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State for PopularDestinations {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
if ctx.redo_mouseover() {
|
||||
app.primary.current_selection = app.calculate_current_selection(
|
||||
ctx,
|
||||
&DontDrawAgents {},
|
||||
&ShowEverything::new(),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
if let Some(ID::Building(_)) = app.primary.current_selection {
|
||||
} else {
|
||||
app.primary.current_selection = None;
|
||||
}
|
||||
}
|
||||
|
||||
match self.composite.event(ctx) {
|
||||
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||
"X" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
let opts = if self.composite.is_checked("Show heatmap") {
|
||||
Some(HeatmapOptions::from_controls(&self.composite))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if self.opts != opts {
|
||||
return Transition::Replace(PopularDestinations::make(
|
||||
ctx,
|
||||
app,
|
||||
self.per_bldg.clone(),
|
||||
opts,
|
||||
));
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
g.redraw(&self.draw);
|
||||
self.composite.draw(g);
|
||||
|
||||
if let Some(ID::Building(b)) = app.primary.current_selection {
|
||||
let mut txt = Text::new();
|
||||
txt.add(Line(format!(
|
||||
"{} trips to here",
|
||||
abstutil::prettyprint_usize(self.per_bldg.get(b))
|
||||
)));
|
||||
for (name, amenity) in &app.primary.map.get_b(b).amenities {
|
||||
txt.add(Line(format!("- {} ({})", name, amenity)));
|
||||
}
|
||||
g.draw_mouse_tooltip(txt);
|
||||
}
|
||||
}
|
||||
}
|
@ -210,6 +210,7 @@ impl State for ParkingMapper {
|
||||
&ShowEverything::new(),
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
) {
|
||||
Some(ID::Road(r)) => Some(r),
|
||||
Some(ID::Lane(l)) => Some(map.get_l(l).parent),
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod blocks;
|
||||
mod destinations;
|
||||
pub mod mapping;
|
||||
mod polygon;
|
||||
mod scenario;
|
||||
|
@ -1,15 +1,16 @@
|
||||
use crate::app::App;
|
||||
use crate::common::{tool_panel, Colorer, CommonState, ContextualActions, Warping};
|
||||
use crate::devtools::blocks::BlockMap;
|
||||
use crate::devtools::destinations::PopularDestinations;
|
||||
use crate::game::{State, Transition, WizardState};
|
||||
use crate::helpers::ID;
|
||||
use crate::managed::{WrappedComposite, WrappedOutcome};
|
||||
use abstutil::{prettyprint_usize, Counter, MultiMap};
|
||||
use ezgui::{
|
||||
hotkey, lctrl, Btn, Choice, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
|
||||
HorizontalAlignment, Key, Line, Outcome, Slider, Text, VerticalAlignment, Widget,
|
||||
hotkey, lctrl, Choice, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line,
|
||||
Outcome, Text,
|
||||
};
|
||||
use geom::{Distance, Line, PolyLine, Polygon};
|
||||
use geom::{Distance, PolyLine};
|
||||
use map_model::{BuildingID, IntersectionID, Map};
|
||||
use sim::{
|
||||
DrivingGoal, IndividTrip, PersonSpec, Scenario, SidewalkPOI, SidewalkSpot, SpawnTrip,
|
||||
@ -104,7 +105,7 @@ impl ScenarioManager {
|
||||
],
|
||||
vec![
|
||||
(hotkey(Key::B), "block map"),
|
||||
(hotkey(Key::D), "dot map"),
|
||||
(hotkey(Key::D), "popular destinations"),
|
||||
(lctrl(Key::P), "stop showing paths"),
|
||||
],
|
||||
),
|
||||
@ -128,15 +129,11 @@ impl State for ScenarioManager {
|
||||
"X" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
"dot map" => {
|
||||
return Transition::Push(Box::new(DotMap::new(ctx, app, &self.scenario)));
|
||||
}
|
||||
"block map" => {
|
||||
return Transition::Push(Box::new(BlockMap::new(
|
||||
ctx,
|
||||
app,
|
||||
self.scenario.clone(),
|
||||
)));
|
||||
return Transition::Push(BlockMap::new(ctx, app, self.scenario.clone()));
|
||||
}
|
||||
"popular destinations" => {
|
||||
return Transition::Push(PopularDestinations::new(ctx, app, &self.scenario));
|
||||
}
|
||||
// TODO Inactivate this sometimes
|
||||
"stop showing paths" => {
|
||||
@ -465,109 +462,6 @@ fn show_demand(
|
||||
batch.upload(ctx)
|
||||
}
|
||||
|
||||
struct DotMap {
|
||||
composite: Composite,
|
||||
|
||||
lines: Vec<Line>,
|
||||
draw: Option<(f64, Drawable)>,
|
||||
}
|
||||
|
||||
impl DotMap {
|
||||
fn new(ctx: &mut EventCtx, app: &App, scenario: &Scenario) -> DotMap {
|
||||
let map = &app.primary.map;
|
||||
let lines = scenario
|
||||
.people
|
||||
.iter()
|
||||
.flat_map(|p| {
|
||||
p.trips.iter().filter_map(|trip| {
|
||||
let (start, end) = match &trip.trip {
|
||||
SpawnTrip::VehicleAppearing { start, goal, .. } => {
|
||||
(start.pt(map), goal.pt(map))
|
||||
}
|
||||
SpawnTrip::FromBorder { dr, goal, .. } => {
|
||||
(map.get_i(dr.src_i(map)).polygon.center(), goal.pt(map))
|
||||
}
|
||||
SpawnTrip::UsingParkedCar(b, goal) => {
|
||||
(map.get_b(*b).polygon.center(), goal.pt(map))
|
||||
}
|
||||
SpawnTrip::UsingBike(start, goal) => {
|
||||
(start.sidewalk_pos.pt(map), goal.pt(map))
|
||||
}
|
||||
SpawnTrip::JustWalking(start, goal) => {
|
||||
(start.sidewalk_pos.pt(map), goal.sidewalk_pos.pt(map))
|
||||
}
|
||||
SpawnTrip::UsingTransit(start, goal, _, _, _) => {
|
||||
(start.sidewalk_pos.pt(map), goal.sidewalk_pos.pt(map))
|
||||
}
|
||||
SpawnTrip::Remote { .. } => unimplemented!(),
|
||||
};
|
||||
Line::maybe_new(start, end)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
DotMap {
|
||||
composite: Composite::new(
|
||||
Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line("Dot map of all trips").small_heading().draw(ctx),
|
||||
Btn::text_fg("X")
|
||||
.build_def(ctx, hotkey(Key::Escape))
|
||||
.align_right(),
|
||||
]),
|
||||
Slider::horizontal(ctx, 150.0, 25.0, 0.0).named("time slider"),
|
||||
])
|
||||
.padding(10)
|
||||
.bg(app.cs.panel_bg),
|
||||
)
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
|
||||
lines,
|
||||
draw: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State for DotMap {
|
||||
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
|
||||
match self.composite.event(ctx) {
|
||||
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||
"X" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
let pct = self.composite.slider("time slider").get_percent();
|
||||
|
||||
if self.draw.as_ref().map(|(p, _)| pct != *p).unwrap_or(true) {
|
||||
let mut batch = GeomBatch::new();
|
||||
let radius = Distance::meters(5.0);
|
||||
for l in &self.lines {
|
||||
// Circles are too expensive. :P
|
||||
batch.push(
|
||||
Color::RED,
|
||||
Polygon::rectangle_centered(l.percent_along(pct), radius, radius),
|
||||
);
|
||||
}
|
||||
self.draw = Some((pct, batch.upload(ctx)));
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
if let Some((_, ref d)) = self.draw {
|
||||
g.redraw(d);
|
||||
}
|
||||
self.composite.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
struct Actions<'a> {
|
||||
demand: &'a mut Option<Drawable>,
|
||||
scenario: &'a Scenario,
|
||||
|
@ -58,6 +58,7 @@ impl State for BulkSelect {
|
||||
&ShowEverything::new(),
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
if let Some(ID::Intersection(_)) = app.primary.current_selection {
|
||||
} else {
|
||||
@ -296,6 +297,7 @@ impl State for PaintSelect {
|
||||
&ShowEverything::new(),
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
if let Some(ID::Road(_)) = app.primary.current_selection {
|
||||
} else {
|
||||
|
@ -100,6 +100,7 @@ impl State for EditMode {
|
||||
&ShowEverything::new(),
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
if let Some(ID::Lane(l)) = app.primary.current_selection {
|
||||
if !can_edit_lane(&self.mode, l, app) {
|
||||
|
@ -47,7 +47,7 @@ pub fn info(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BuildingID
|
||||
txt.add(Line(format!("{} amenities:", b.amenities.len())));
|
||||
}
|
||||
for (name, amenity) in &b.amenities {
|
||||
txt.add(Line(format!("- {} (a {})", name, amenity)));
|
||||
txt.add(Line(format!("- {} ({})", name, amenity)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,11 +132,7 @@ pub fn main_menu(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
|
||||
txt
|
||||
})
|
||||
.build_def(ctx, hotkey(Key::M)),
|
||||
if app.opts.dev {
|
||||
Btn::text_bg2("Internal Dev Tools").build_def(ctx, hotkey(Key::D))
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
Btn::text_bg2("Internal Dev Tools").build_def(ctx, hotkey(Key::D)),
|
||||
])
|
||||
.centered(),
|
||||
Widget::col(vec![
|
||||
@ -146,7 +142,7 @@ pub fn main_menu(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
|
||||
.centered(),
|
||||
];
|
||||
|
||||
let mut c = WrappedComposite::new(
|
||||
let c = WrappedComposite::new(
|
||||
Composite::new(Widget::col(col).evenly_spaced())
|
||||
.exact_size_percent(90, 85)
|
||||
.build(ctx),
|
||||
@ -219,13 +215,11 @@ pub fn main_menu(ctx: &mut EventCtx, app: &App) -> Box<dyn State> {
|
||||
crate::devtools::mapping::ParkingMapper::new(ctx, app, true, BTreeMap::new()),
|
||||
))
|
||||
}),
|
||||
);
|
||||
if app.opts.dev {
|
||||
c = c.cb(
|
||||
)
|
||||
.cb(
|
||||
"Internal Dev Tools",
|
||||
Box::new(|ctx, app| Some(Transition::Push(DevToolsMode::new(ctx, app)))),
|
||||
);
|
||||
}
|
||||
ManagedGUIState::fullscreen(c)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user