diff options
author | Daniel Schadt <kingdread@gmx.de> | 2023-01-17 23:06:54 +0100 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2023-01-17 23:06:54 +0100 |
commit | 160aba6258e1979ef85d66b2c27c33f6f28a7e38 (patch) | |
tree | 265f061f4b256a9dffac3abeb8bf283e1bc75c55 /src | |
parent | f824bef0e78e51c985c2333c400c6741782db2d0 (diff) | |
download | hittekaart-160aba6258e1979ef85d66b2c27c33f6f28a7e38.tar.gz hittekaart-160aba6258e1979ef85d66b2c27c33f6f28a7e38.tar.bz2 hittekaart-160aba6258e1979ef85d66b2c27c33f6f28a7e38.zip |
factor out saving logic
Since we want to support SQLite at some point, it makes sense to have
the exact storage method abstracted away.
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 40 | ||||
-rw-r--r-- | src/renderer.rs | 42 | ||||
-rw-r--r-- | src/storage.rs | 67 |
4 files changed, 99 insertions, 51 deletions
@@ -2,3 +2,4 @@ pub mod gpx; pub mod layer; pub mod renderer; +pub mod storage; diff --git a/src/main.rs b/src/main.rs index 53a1301..7230dce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,14 @@ -use std::{ - fs, - io::{self, ErrorKind}, - path::{Path, PathBuf}, -}; +use std::{io, path::PathBuf}; use clap::Parser; use color_eyre::{ - eyre::{bail, eyre, Context, Result}, + eyre::{bail, eyre, Result}, Report, }; use hittekaart::{ gpx::{self, Compression}, renderer, + storage::{Folder, Storage}, }; use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle}; use is_terminal::IsTerminal; @@ -88,7 +85,8 @@ fn main() -> Result<()> { let tracks = tracks.into_iter().collect::<Result<Vec<_>>>()?; bar.finish(); - ensure_output_directory(&args.output_directory)?; + let mut storage = Folder::new(args.output_directory.clone()); + storage.prepare()?; let multibar = MultiProgress::new(); if !use_progress_bars { @@ -107,37 +105,23 @@ fn main() -> Result<()> { bar.finish(); multibar.remove(&bar); - let target = [&args.output_directory, &zoom.to_string().into()] - .iter() - .collect::<PathBuf>(); - fs::create_dir(&target)?; + storage.prepare_zoom(zoom)?; let bar = make_bar(counter.tile_count().try_into().unwrap()).with_style(progress_style.clone()); multibar.insert_from_back(1, bar.clone()); bar.set_prefix("Saving heat tiles"); - renderer::lazy_colorization(counter, &target, |x| bar.inc(x.try_into().unwrap()))?; + renderer::lazy_colorization(counter, |rendered_tile| { + storage.store(zoom, rendered_tile.x, rendered_tile.y, &rendered_tile.data)?; + bar.inc(1); + Ok(()) + })?; bar.finish(); multibar.remove(&bar); zoom_bar.inc(1); } + storage.finish()?; zoom_bar.finish(); Ok(()) } - -fn ensure_output_directory<P: AsRef<Path>>(path: P) -> Result<()> { - let path = path.as_ref(); - let metadata = fs::metadata(path); - match metadata { - Err(e) if e.kind() == ErrorKind::NotFound => { - let parent = path.parent().unwrap_or_else(|| Path::new("/")); - fs::create_dir(path) - .context(format!("Could not create output directory at {parent:?}"))? - } - Err(e) => Err(e).context("Error while checking output directory")?, - Ok(m) if m.is_dir() => (), - Ok(_) => bail!("Output directory is not a directory"), - } - Ok(()) -} diff --git a/src/renderer.rs b/src/renderer.rs index bb33ddc..4840e73 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,9 +1,6 @@ -use std::{fs, path::Path, thread}; +use std::thread; -use color_eyre::{ - eyre::{bail, Result}, - Report, -}; +use color_eyre::{eyre::Result, Report}; use image::{ImageBuffer, Luma, Pixel, RgbaImage}; use nalgebra::{vector, Vector2}; use rayon::iter::ParallelIterator; @@ -13,6 +10,13 @@ use super::{ layer::{self, TileLayer}, }; +#[derive(Debug, Clone)] +pub struct RenderedTile { + pub x: u64, + pub y: u64, + pub data: Vec<u8>, +} + pub type HeatCounter = TileLayer<Luma<u8>>; fn render_circle<P: Pixel>(layer: &mut TileLayer<P>, center: (u64, u64), radius: u64, pixel: P) { @@ -135,32 +139,21 @@ fn colorize_tile(tile: &ImageBuffer<Luma<u8>, Vec<u8>>, max: u32) -> RgbaImage { /// rendering the next one. /// /// This has a way lower memory usage than [`colorize_heatcounter`]. -pub fn lazy_colorization<P: AsRef<Path>, F: Fn(usize) + Send + Sync>( +pub fn lazy_colorization<F: FnMut(RenderedTile) -> Result<()> + Send + Sync>( layer: HeatCounter, - base_dir: P, - progress_callback: F, + mut save_callback: F, ) -> Result<()> { - let base_dir = base_dir.as_ref(); let max = layer.pixels().map(|l| l.0[0]).max().unwrap_or_default(); if max == 0 { return Ok(()); } - type Job = (u64, u64, Vec<u8>); - let (tx, rx) = crossbeam_channel::bounded::<Job>(30); + let (tx, rx) = crossbeam_channel::bounded::<RenderedTile>(30); thread::scope(|s| { let saver = s.spawn(move || loop { - let Ok((tile_x, tile_y, data)) = rx.recv() else { return Ok(()) }; - let folder = base_dir.join(tile_x.to_string()); - let metadata = folder.metadata(); - match metadata { - Err(_) => fs::create_dir(&folder)?, - Ok(m) if !m.is_dir() => bail!("Output path is not a directory"), - _ => {} - } - let file = folder.join(format!("{tile_y}.png")); - fs::write(file, data)?; + let Ok(tile) = rx.recv() else { return Ok::<_, Report>(()) }; + save_callback(tile)?; }); layer @@ -168,8 +161,11 @@ pub fn lazy_colorization<P: AsRef<Path>, F: Fn(usize) + Send + Sync>( .try_for_each_with(tx, |tx, (tile_x, tile_y, tile)| { let colorized = colorize_tile(&tile, max.into()); let data = layer::compress_png_as_bytes(&colorized)?; - tx.send((tile_x, tile_y, data))?; - progress_callback(1); + tx.send(RenderedTile { + x: tile_x, + y: tile_y, + data, + })?; Ok::<(), Report>(()) })?; diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 0000000..fef8194 --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,67 @@ +use color_eyre::{eyre::{bail, WrapErr}, Result}; +use std::{ + fs, + io::ErrorKind, + path::{Path, PathBuf}, +}; + +pub trait Storage { + fn prepare(&mut self) -> Result<()>; + fn prepare_zoom(&mut self, zoom: u32) -> Result<()>; + fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()>; + fn finish(&mut self) -> Result<()>; +} + +#[derive(Debug)] +pub struct Folder { + base_dir: PathBuf, +} + +impl Folder { + pub fn new(base_dir: PathBuf) -> Self { + Folder { base_dir } + } +} + +impl Storage for Folder { + fn prepare(&mut self) -> Result<()> { + let path = &self.base_dir; + let metadata = fs::metadata(path); + match metadata { + Err(e) if e.kind() == ErrorKind::NotFound => { + let parent = path.parent().unwrap_or_else(|| Path::new("/")); + fs::create_dir(path) + .context(format!("Could not create output directory at {parent:?}"))? + } + Err(e) => Err(e).context("Error while checking output directory")?, + Ok(m) if m.is_dir() => (), + Ok(_) => bail!("Output directory is not a directory"), + } + Ok(()) + } + + fn prepare_zoom(&mut self, zoom: u32) -> Result<()> { + let target = [&self.base_dir, &zoom.to_string().into()] + .iter() + .collect::<PathBuf>(); + fs::create_dir(target)?; + Ok(()) + } + + fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()> { + let folder = self.base_dir.join(zoom.to_string()).join(x.to_string()); + let metadata = folder.metadata(); + match metadata { + Err(_) => fs::create_dir(&folder)?, + Ok(m) if !m.is_dir() => bail!("Output path is not a directory"), + _ => {} + } + let file = folder.join(format!("{y}.png")); + fs::write(file, data)?; + Ok(()) + } + + fn finish(&mut self) -> Result<()> { + Ok(()) + } +} |