aboutsummaryrefslogtreecommitdiff
path: root/src/renderer/mod.rs
blob: f109872bc2eed43145dfa2fbebba03d28a3ff658 (plain)
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()
    })
}