mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 06:55:40 +03:00
Prototype a UI to view aggregated collisions. #87
This commit is contained in:
parent
ad7a25f7f2
commit
31fcf1b863
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -921,6 +921,7 @@ dependencies = [
|
||||
"abstutil",
|
||||
"built",
|
||||
"chrono",
|
||||
"collisions",
|
||||
"colorous",
|
||||
"console_log",
|
||||
"contour",
|
||||
|
@ -23,6 +23,7 @@ aabb-quadtree = "0.1.0"
|
||||
abstutil = { path = "../abstutil" }
|
||||
built = { version = "0.4.3", optional = true, features=["chrono"] }
|
||||
chrono = "0.4.15"
|
||||
collisions = { path = "../collisions" }
|
||||
colorous = "1.0.3"
|
||||
console_log = { version = "0.1", optional = true }
|
||||
contour = "0.2.0"
|
||||
|
115
game/src/devtools/collisions.rs
Normal file
115
game/src/devtools/collisions.rs
Normal file
@ -0,0 +1,115 @@
|
||||
use abstutil::{prettyprint_usize, Counter};
|
||||
use collisions::CollisionDataset;
|
||||
use geom::{Distance, FindClosest, Pt2D};
|
||||
use widgetry::{
|
||||
Btn, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Line, Outcome, Panel, State,
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::common::ColorNetwork;
|
||||
use crate::game::Transition;
|
||||
use crate::helpers::ID;
|
||||
|
||||
pub struct CollisionsViewer {
|
||||
panel: Panel,
|
||||
unzoomed: Drawable,
|
||||
zoomed: Drawable,
|
||||
}
|
||||
|
||||
impl CollisionsViewer {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
|
||||
let map = &app.primary.map;
|
||||
let dataset: CollisionDataset =
|
||||
ctx.loading_screen("load collision data", |_, mut timer| {
|
||||
abstutil::read_binary(
|
||||
abstutil::path(format!("input/{}/collisions.bin", map.get_city_name())),
|
||||
&mut timer,
|
||||
)
|
||||
});
|
||||
|
||||
// Match each collision to the nearest road and intersection
|
||||
let mut closest: FindClosest<ID> = FindClosest::new(map.get_bounds());
|
||||
for i in map.all_intersections() {
|
||||
closest.add(ID::Intersection(i.id), i.polygon.points());
|
||||
}
|
||||
for r in map.all_roads() {
|
||||
closest.add(ID::Road(r.id), r.center_pts.points());
|
||||
}
|
||||
|
||||
// How many collisions occurred at each road and intersection?
|
||||
let mut per_road = Counter::new();
|
||||
let mut per_intersection = Counter::new();
|
||||
let mut unsnapped = 0;
|
||||
for collision in dataset.collisions {
|
||||
// Search up to 10m away
|
||||
if let Some((id, _)) = closest.closest_pt(
|
||||
Pt2D::from_gps(collision.location, map.get_gps_bounds()),
|
||||
Distance::meters(10.0),
|
||||
) {
|
||||
match id {
|
||||
ID::Road(r) => {
|
||||
per_road.inc(r);
|
||||
}
|
||||
ID::Intersection(i) => {
|
||||
per_intersection.inc(i);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
unsnapped += 1;
|
||||
}
|
||||
}
|
||||
if unsnapped > 0 {
|
||||
warn!(
|
||||
"{} collisions weren't close enough to a road or intersection",
|
||||
prettyprint_usize(unsnapped)
|
||||
);
|
||||
}
|
||||
|
||||
// Color roads and intersections using the counts
|
||||
let mut colorer = ColorNetwork::new(app);
|
||||
// TODO We should use some scale for both!
|
||||
colorer.pct_roads(per_road, &app.cs.good_to_bad_red);
|
||||
colorer.pct_intersections(per_intersection, &app.cs.good_to_bad_red);
|
||||
let (unzoomed, zoomed) = colorer.build(ctx);
|
||||
|
||||
Box::new(CollisionsViewer {
|
||||
unzoomed,
|
||||
zoomed,
|
||||
panel: Panel::new(Widget::col(vec![Widget::row(vec![
|
||||
Line("Collisions viewer").small_heading().draw(ctx),
|
||||
Btn::close(ctx),
|
||||
])]))
|
||||
.aligned(HorizontalAlignment::Right, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State<App> for CollisionsViewer {
|
||||
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
|
||||
match self.panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"close" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
if g.canvas.cam_zoom < app.opts.min_zoom_for_detail {
|
||||
g.redraw(&self.unzoomed);
|
||||
} else {
|
||||
g.redraw(&self.zoomed);
|
||||
}
|
||||
self.panel.draw(g);
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ use crate::common::CityPicker;
|
||||
use crate::game::{ChooseSomething, Transition};
|
||||
use crate::helpers::nice_map_name;
|
||||
|
||||
mod collisions;
|
||||
mod destinations;
|
||||
mod kml;
|
||||
pub mod mapping;
|
||||
@ -44,6 +45,14 @@ impl DevToolsMode {
|
||||
Btn::text_fg("load scenario").build_def(ctx, Key::W),
|
||||
Btn::text_fg("view KML").build_def(ctx, Key::K),
|
||||
Btn::text_fg("story maps").build_def(ctx, Key::S),
|
||||
if abstutil::file_exists(abstutil::path(format!(
|
||||
"input/{}/collisions.bin",
|
||||
app.primary.map.get_city_name()
|
||||
))) {
|
||||
Btn::text_fg("collisions").build_def(ctx, Key::C)
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
])
|
||||
.flex_wrap(ctx, Percent::int(60)),
|
||||
]))
|
||||
@ -113,6 +122,9 @@ impl State<App> for DevToolsMode {
|
||||
"story maps" => {
|
||||
return Transition::Push(story::StoryMapEditor::new(ctx));
|
||||
}
|
||||
"collisions" => {
|
||||
return Transition::Push(collisions::CollisionsViewer::new(ctx, app));
|
||||
}
|
||||
"change map" => {
|
||||
return Transition::Push(CityPicker::new(
|
||||
ctx,
|
||||
|
Loading…
Reference in New Issue
Block a user