1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
//! Generic "tile rendering" methods.
use std::thread;
use color_eyre::Result;
use crossbeam_channel::Sender;
use super::gpx::Coordinates;
pub mod heatmap;
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<u8>,
}
/// 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 {
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(zoom: u32, tracks: &[Vec<Coordinates>], tick: Sender<()>) -> Result<Self::Prepared>;
/// 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(prepared: Self::Prepared, saver: Sender<RenderedTile>) -> Result<()>;
/// Returns the tile count of the prepared data.
///
/// This is used for the user interface, to scale progress bars appropriately.
fn tile_count(prepared: &Self::Prepared) -> Result<u64>;
}
/// 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<R: Renderer, F: FnMut() -> Result<()>>(
zoom: u32,
tracks: &[Vec<Coordinates>],
mut tick: F,
) -> Result<R::Prepared> {
thread::scope(|s| {
let (sender, receiver) = crossbeam_channel::bounded(CHANNEL_SIZE);
let preparer = s.spawn(|| R::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<R: Renderer, F: FnMut(RenderedTile) -> Result<()>>(
prepared: R::Prepared,
mut saver: F,
) -> Result<()> {
thread::scope(|s| {
let (sender, receiver) = crossbeam_channel::bounded(CHANNEL_SIZE);
let colorizer = s.spawn(|| R::colorize(prepared, sender));
for tile in receiver {
saver(tile)?;
}
colorizer.join().unwrap()
})
}
|