//! Generic "tile rendering" methods. use std::thread; use color_eyre::Result; use crossbeam_channel::Sender; use super::gpx::Coordinates; pub mod heatmap; pub mod marktile; pub mod tilehunt; const CHANNEL_SIZE: usize = 30; /// Represents a fully rendered tile. #[derive(Debug, Clone)] pub struct RenderedTile { /// The `x` coordinate of the tile. pub x: u64, /// The `y` coordinate of the tile. pub y: u64, /// The encoded (PNG) image data, ready to be saved to disk. pub data: Vec, } /// An object that is responsible for turning raw GPX tracks into a representation. /// /// This is done in two steps, preparation and actual rendering. This allows different feedback for /// the user. pub trait Renderer: Send + Sync { type Prepared: Send; /// Prepare the rendered data. /// /// The `tick` channel is used to provide user-feedback, for every finished track a tick should /// be sent. fn prepare( &self, zoom: u32, tracks: &[Vec], tick: Sender<()>, ) -> Result; /// Actually produce the colored tiles, using the previously prepared data. /// /// The `saver` channel is used to send the finished tiles to a thread that is responsible for /// saving them. fn colorize(&self, prepared: Self::Prepared, saver: Sender) -> Result<()>; /// Returns the tile count of the prepared data. /// /// This is used for the user interface, to scale progress bars appropriately. fn tile_count(&self, prepared: &Self::Prepared) -> Result; } /// A convenience wrapper to call [`Renderer::prepare`]. /// /// This function takes the same arguments, but provides the ability to use a callback closure /// instead of having to set up a channel. The callback is always called on the same thread. pub fn prepare Result<()>>( renderer: &R, zoom: u32, tracks: &[Vec], mut tick: F, ) -> Result { thread::scope(|s| { let (sender, receiver) = crossbeam_channel::bounded(CHANNEL_SIZE); let preparer = s.spawn(|| renderer.prepare(zoom, tracks, sender)); for _ in receiver { tick()?; } preparer.join().unwrap() }) } /// A convenience wrapper to call [`Renderer::colorize`]. /// /// This function takes the same arguments, but provides the ability to use a callback closure /// instead of having to set up a channel. The callback is always called on the same thread. pub fn colorize Result<()>>( renderer: &R, prepared: R::Prepared, mut saver: F, ) -> Result<()> { thread::scope(|s| { let (sender, receiver) = crossbeam_channel::bounded(CHANNEL_SIZE); let colorizer = s.spawn(|| renderer.colorize(prepared, sender)); for tile in receiver { saver(tile)?; } colorizer.join().unwrap() }) }