diff options
author | Daniel Schadt <kingdread@gmx.de> | 2023-03-13 22:08:48 +0100 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2023-03-13 22:08:48 +0100 |
commit | 3e694d68a685b6e22d6ab59f34090e4681849ebc (patch) | |
tree | 0e9d817d38d8d3524b459c5e8ebe4c55876e322b /src/renderer/tilehunt.rs | |
parent | 3bec9ff1bcb7fb8b93693c0c93b8d42797f95e1c (diff) | |
download | hittekaart-3e694d68a685b6e22d6ab59f34090e4681849ebc.tar.gz hittekaart-3e694d68a685b6e22d6ab59f34090e4681849ebc.tar.bz2 hittekaart-3e694d68a685b6e22d6ab59f34090e4681849ebc.zip |
implement "proper" tile hunter mode
Now with fixed zoom level for the hunting.
Diffstat (limited to 'src/renderer/tilehunt.rs')
-rw-r--r-- | src/renderer/tilehunt.rs | 137 |
1 files changed, 115 insertions, 22 deletions
diff --git a/src/renderer/tilehunt.rs b/src/renderer/tilehunt.rs index 55b30af..9081523 100644 --- a/src/renderer/tilehunt.rs +++ b/src/renderer/tilehunt.rs @@ -4,30 +4,76 @@ //! //! Note that is version of "tile hunt" is a bit silly, as the tile size changes with the zoom //! level. For a better version, the "tile hunt size" should be fixed to a given zoom. +use std::cmp::Ordering; + use color_eyre::eyre::Result; use crossbeam_channel::Sender; -use fnv::FnvHashSet; +use fnv::{FnvHashMap, FnvHashSet}; +use image::RgbaImage; +use imageproc::{drawing::draw_filled_rect_mut, rect::Rect}; +use rayon::iter::{IntoParallelIterator, ParallelIterator}; use super::{ super::{ gpx::Coordinates, - layer::{TILE_HEIGHT, TILE_WIDTH}, + layer::{self, TILE_HEIGHT, TILE_WIDTH}, }, RenderedTile, }; +fn render_squares(grid: u32, inner: Vec<(u8, u8)>) -> Result<Vec<u8>> { + // We re-use the tiny PNG if possible + 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; +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 = FnvHashSet<(u64, u64)>; + type Prepared = (u32, FnvHashMap<(u64, u64), Vec<(u8, u8)>>); - fn prepare(zoom: u32, tracks: &[Vec<Coordinates>], tick: Sender<()>) -> Result<Self::Prepared> { + 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(zoom); + 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)); @@ -36,25 +82,72 @@ impl super::Renderer for Renderer { tick.send(()).unwrap(); } - Ok(marked) - } + 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(); - fn colorize(layer: Self::Prepared, tx: Sender<RenderedTile>) -> Result<()> { - // The tile is hand-crafted to be very small. See - // <https://www.mjt.me.uk/posts/smallest-png/> for a reference, and of course the actual - // PNG specification <http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html>. - static IMAGE_DATA: &[u8] = include_bytes!("tile-marked.png"); - for (tile_x, tile_y) in layer { - tx.send(RenderedTile { - x: tile_x, - y: tile_y, - data: IMAGE_DATA.to_vec(), - })?; + for (tile_x, tile_y) in marked { + match scale.cmp(&0) { + Ordering::Equal => + // The current zoom level is the same as the hunter level, so the tiles have a 1:1 + // mapping + { + result.entry((tile_x, tile_y)).or_default().push((0u8, 0u8)) + } + Ordering::Less => + // In this case we are "zoomed out" further than the hunter level, so a marked tile + // has to be scaled down and we need to figure out where in the "big tile" our + // marked tile is + { + 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 => { + // In this case, we are zoomed in more than the hunter level. Each marked tile + // expands to multiple tiles. + 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(()) + + 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(layer: &Self::Prepared) -> Result<u64> { - Ok(layer.len().try_into().unwrap()) + fn tile_count(&self, layer: &Self::Prepared) -> Result<u64> { + Ok(layer.1.len().try_into().unwrap()) } } |