use std::cmp::Ordering;
use crossbeam_channel::Sender;
use fnv::{FnvHashMap, FnvHashSet};
use image::RgbaImage;
use imageproc::{drawing::draw_filled_rect_mut, rect::Rect};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use super::{
super::{
error::Result,
gpx::Coordinates,
layer::{self, TILE_HEIGHT, TILE_WIDTH},
},
RenderedTile,
};
fn render_squares(grid: u32, inner: Vec<(u8, u8)>) -> Result<Vec<u8>> {
static FULL_TILE: &[u8] = include_bytes!("tile-marked.png");
if grid == 1 && !inner.is_empty() {
return Ok(FULL_TILE.to_vec());
}
let mut base =
RgbaImage::from_pixel(TILE_WIDTH as u32, TILE_HEIGHT as u32, [0, 0, 0, 0].into());
let patch_size = TILE_WIDTH as u32 / grid;
for (patch_x, patch_y) in inner {
draw_filled_rect_mut(
&mut base,
Rect::at(
patch_x as i32 * patch_size as i32,
patch_y as i32 * patch_size as i32,
)
.of_size(patch_size, patch_size),
[0, 255, 0, 128].into(),
);
}
layer::compress_png_as_bytes(&base)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Renderer(u32);
impl Renderer {
pub fn new(hunter_zoom: u32) -> Self {
Renderer(hunter_zoom)
}
#[inline]
pub fn hunter_zoom(&self) -> u32 {
self.0
}
}
impl super::Renderer for Renderer {
type Prepared = (u32, FnvHashMap<(u64, u64), Vec<(u8, u8)>>);
fn prepare(
&self,
zoom: u32,
tracks: &[Vec<Coordinates>],
tick: Sender<()>,
) -> Result<Self::Prepared> {
let mut marked = FnvHashSet::default();
for track in tracks {
for point in track {
let merc = point.web_mercator(self.hunter_zoom());
let tile_x = merc.0 / TILE_WIDTH;
let tile_y = merc.1 / TILE_HEIGHT;
marked.insert((tile_x, tile_y));
}
tick.send(()).unwrap();
}
let scale = i32::try_from(zoom).unwrap() - i32::try_from(self.hunter_zoom()).unwrap();
let grid = if scale >= 0 {
1
} else {
2u64.pow(scale.abs().min(8) as u32)
};
let mut result = FnvHashMap::<(u64, u64), Vec<(u8, u8)>>::default();
for (tile_x, tile_y) in marked {
match scale.cmp(&0) {
Ordering::Equal =>
{
result.entry((tile_x, tile_y)).or_default().push((0u8, 0u8))
}
Ordering::Less =>
{
result
.entry((tile_x / grid, tile_y / grid))
.or_default()
.push((
(tile_x % grid).try_into().unwrap(),
(tile_y % grid).try_into().unwrap(),
))
}
Ordering::Greater => {
let multiplier = 2u64.pow(scale as u32);
for dx in 0..multiplier {
for dy in 0..multiplier {
result
.entry((tile_x * multiplier + dx, tile_y * multiplier + dy))
.or_default()
.push((0u8, 0u8));
}
}
}
}
}
Ok((grid.try_into().unwrap(), result))
}
fn colorize(&self, layer: Self::Prepared, tx: Sender<RenderedTile>) -> Result<()> {
let grid = layer.0;
layer
.1
.into_par_iter()
.try_for_each_with(tx, |tx, ((tile_x, tile_y), inner)| {
let data = render_squares(grid, inner)?;
tx.send(RenderedTile {
x: tile_x,
y: tile_y,
data,
})?;
Ok(())
})
}
fn tile_count(&self, layer: &Self::Prepared) -> Result<u64> {
Ok(layer.1.len().try_into().unwrap())
}
}