mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
implement a super basic heatmap (many many problems) for the dot map
This commit is contained in:
parent
d7c855c556
commit
4ad75f99a1
111
game/src/common/heatmap.rs
Normal file
111
game/src/common/heatmap.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use ezgui::{Color, GeomBatch};
|
||||
use geom::{Bounds, Polygon, Pt2D};
|
||||
|
||||
pub fn make_heatmap(batch: &mut GeomBatch, bounds: &Bounds, pts: Vec<Pt2D>) {
|
||||
// Meters
|
||||
let resolution = 10.0;
|
||||
// u8 is not quite enough -- one building could totally have more than 256 people.
|
||||
let mut counts: Grid<u16> = Grid::new(
|
||||
(bounds.width() / resolution).ceil() as usize,
|
||||
(bounds.height() / resolution).ceil() as usize,
|
||||
0,
|
||||
);
|
||||
|
||||
for pt in pts {
|
||||
// TODO more careful rounding
|
||||
let idx = counts.idx(
|
||||
((pt.x() - bounds.min_x) / resolution) as usize,
|
||||
((pt.y() - bounds.min_y) / resolution) as usize,
|
||||
);
|
||||
counts.data[idx] += 1;
|
||||
}
|
||||
|
||||
// Diffusion
|
||||
let num_passes = 5;
|
||||
for _ in 0..num_passes {
|
||||
// Have to hot-swap! Urgh
|
||||
let mut copy = counts.data.clone();
|
||||
for y in 0..counts.height {
|
||||
for x in 0..counts.width {
|
||||
let idx = counts.idx(x, y);
|
||||
if counts.data[idx] > 0 {
|
||||
copy[idx] += 1;
|
||||
for idx in counts.neighbors_8(x, y) {
|
||||
copy[idx] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
counts.data = copy;
|
||||
}
|
||||
|
||||
// Now draw rectangles
|
||||
let max = *counts.data.iter().max().unwrap();
|
||||
// TODO Full spectral progression isn't recommended anymore!
|
||||
// This is in order from low density to high.
|
||||
let colors = vec![
|
||||
Color::hex("#0b2c7a"),
|
||||
Color::hex("#1e9094"),
|
||||
Color::hex("#0ec441"),
|
||||
Color::hex("#7bed00"),
|
||||
Color::hex("#f7d707"),
|
||||
Color::hex("#e68e1c"),
|
||||
Color::hex("#c2523c"),
|
||||
];
|
||||
// TODO Off by 1?
|
||||
let range = max / ((colors.len() - 1) as u16);
|
||||
if range == 0 {
|
||||
// Max is too low, use less colors?
|
||||
return;
|
||||
}
|
||||
let square = Polygon::rectangle(resolution, resolution);
|
||||
for y in 0..counts.height {
|
||||
for x in 0..counts.width {
|
||||
let idx = counts.idx(x, y);
|
||||
let cnt = counts.data[idx];
|
||||
if cnt > 0 {
|
||||
// TODO Urgh, uneven buckets
|
||||
let color = colors[((cnt / range) as usize).min(colors.len() - 1)];
|
||||
batch.push(
|
||||
color,
|
||||
square.translate((x as f64) * resolution, (y as f64) * resolution),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Grid<T> {
|
||||
data: Vec<T>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl<T: Copy> Grid<T> {
|
||||
fn new(width: usize, height: usize, default: T) -> Grid<T> {
|
||||
Grid {
|
||||
data: std::iter::repeat(default).take(width * height).collect(),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn idx(&self, x: usize, y: usize) -> usize {
|
||||
// Row-major
|
||||
y * self.width + x
|
||||
}
|
||||
|
||||
fn neighbors_8(&self, x: usize, y: usize) -> Vec<usize> {
|
||||
let mut indices = Vec::new();
|
||||
let x1 = if x == 0 { 0 } else { x - 1 };
|
||||
let x2 = if x == self.width - 1 { x } else { x + 1 };
|
||||
let y1 = if y == 0 { 0 } else { y - 1 };
|
||||
let y2 = if y == self.height - 1 { y } else { y + 1 };
|
||||
for x in x1..=x2 {
|
||||
for y in y1..=y2 {
|
||||
indices.push(self.idx(x, y));
|
||||
}
|
||||
}
|
||||
indices
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
mod bus_explorer;
|
||||
mod colors;
|
||||
mod heatmap;
|
||||
mod info;
|
||||
mod minimap;
|
||||
mod navigate;
|
||||
@ -11,6 +12,7 @@ mod warp;
|
||||
|
||||
pub use self::bus_explorer::ShowBusRoute;
|
||||
pub use self::colors::{ColorLegend, Colorer};
|
||||
pub use self::heatmap::make_heatmap;
|
||||
pub use self::minimap::Minimap;
|
||||
pub use self::overlays::Overlays;
|
||||
pub use self::panels::tool_panel;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::app::App;
|
||||
use crate::colors;
|
||||
use crate::common::{ColorLegend, Colorer, ShowBusRoute, Warping};
|
||||
use crate::common::{make_heatmap, ColorLegend, Colorer, ShowBusRoute, Warping};
|
||||
use crate::game::Transition;
|
||||
use crate::helpers::rotating_color_map;
|
||||
use crate::helpers::ID;
|
||||
@ -267,16 +267,16 @@ impl Overlays {
|
||||
let mut choices = vec![
|
||||
Btn::text_fg("None").build_def(ctx, hotkey(Key::N)),
|
||||
Btn::text_fg("map edits").build_def(ctx, hotkey(Key::E)),
|
||||
Btn::text_fg("worst traffic jams").build_def(ctx, hotkey(Key::G)),
|
||||
Btn::text_fg("worst traffic jams").build_def(ctx, hotkey(Key::J)),
|
||||
Btn::text_fg("elevation").build_def(ctx, hotkey(Key::S)),
|
||||
Btn::text_fg("parking availability").build_def(ctx, hotkey(Key::P)),
|
||||
Btn::text_fg("delay").build_def(ctx, hotkey(Key::I)),
|
||||
Btn::text_fg("delay").build_def(ctx, hotkey(Key::D)),
|
||||
Btn::text_fg("throughput").build_def(ctx, hotkey(Key::T)),
|
||||
Btn::text_fg("bike network").build_def(ctx, hotkey(Key::B)),
|
||||
Btn::text_fg("bus network").build_def(ctx, hotkey(Key::U)),
|
||||
];
|
||||
if app.opts.dev {
|
||||
choices.push(Btn::text_fg("dot map of people").build_def(ctx, None));
|
||||
choices.push(Btn::text_fg("dot map of people").build_def(ctx, hotkey(Key::X)));
|
||||
}
|
||||
// TODO Grey out the inactive SVGs, and add the green checkmark
|
||||
if let Some(name) = match app.overlay {
|
||||
@ -1007,8 +1007,12 @@ impl Overlays {
|
||||
// It's quite silly to produce triangles for the same circle over and over again. ;)
|
||||
let circle = Circle::new(Pt2D::new(0.0, 0.0), Distance::meters(10.0)).to_polygon();
|
||||
let mut batch = GeomBatch::new();
|
||||
for pt in pts {
|
||||
batch.push(Color::RED.alpha(0.8), circle.translate(pt.x(), pt.y()));
|
||||
if true {
|
||||
make_heatmap(&mut batch, app.primary.map.get_bounds(), pts);
|
||||
} else {
|
||||
for pt in pts {
|
||||
batch.push(Color::RED.alpha(0.8), circle.translate(pt.x(), pt.y()));
|
||||
}
|
||||
}
|
||||
Overlays::PersonDotMap(app.primary.sim.time(), ctx.upload(batch))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user