implement a super basic heatmap (many many problems) for the dot map

This commit is contained in:
Dustin Carlino 2020-03-21 14:46:23 -07:00
parent d7c855c556
commit 4ad75f99a1
3 changed files with 123 additions and 6 deletions

111
game/src/common/heatmap.rs Normal file
View 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
}
}

View File

@ -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;

View File

@ -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))
}