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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Create orthographic projection images in Rust

/// An orthographic image
/// 
/// Images are square, with the globe in the middle. Create one with a size of 500x500, where
/// all pixels are set to `0`, centred on Rome:
///
/// # Examples
///
///```
///use orthoproj::OrthoProj;
///let mut image = OrthoProj::new(500, 41.89889, 12.47337, 0);
///```
///
/// We can then set pixels on this image, based on their lat/lon
///
///```
///# use orthoproj::OrthoProj;
///# let mut image = OrthoProj::new(500, 41.89889, 12.47337, 0);
///image.set(51.50791, -0.12786, 1);
///```
///
/// You can then loop over all the pixels, getting the current value.
///
pub struct OrthoProj<T: Clone> {
    _data: Vec<T>,
    _lat: f32,
    _lon: f32,
    _size: u32,
}

impl<T: Clone> OrthoProj<T> {
    /// Create a new orthographic projection with width & height of `size`, centred on `lat` and
    /// `lon`. `default` is the default value
    pub fn new(size: u32, lat: f32, lon: f32, default: T) -> Self {
        let s = size as usize;
        OrthoProj{ _size: size,  _data: vec![default; s*s], _lat: lat.to_radians(), _lon: lon.to_radians() }
    }

    /// Create a new OrthoProj, `size` and `lon`/`lat`, but the background (non-sphere) is `bg`,
    /// and `surface` is used for values on the sphere.
    pub fn new_with_bg(size: u32, lat: f32, lon: f32, bg: T, surface: T) -> Self {
        let mut o = Self::new(size, lat, lon, bg);

        // draw a circle
        let r2 = (size/2)*(size/2);
        // presume centre is at (size/2, size/2)
        let cx = size/2;
        let cy = size/2;

        for x in 0..size {
            for y in 0..size {
                let dist2 = (x-cx)*(x-cx) + (y-cy)*(y-cy);
                if dist2 <= r2 {
                    o.set_pixel(x, y, surface.clone());
                }
            }
        }

        o
    }

    /// For this projection, what would be the pixel x/y values for this point. `None` if the
    /// lat/lon lies outside the visible area.
    pub fn xy_for_pos(&self, lat: f32, lon: f32) -> Option<(u32, u32)> {
        // lat = phi
        // lon = lambda
        //
        // x = r cos(lat)sin(lon - lon0)
        // y = r ( cos(lat0)sin(lat) - sin(lat0)cos(lat)cos(lon-lon0) )
        //
        // cos c = sin(lat0)sin(lat) + cos(lat0)cos(lat)cos(lon-lon0)
        // is it the far side of the globe
        let lat = lat.to_radians();
        let lon = lon.to_radians();
        let cos_c = self._lat.sin() * lat.sin() + self._lat.cos()*lat.cos()*(lon - self._lon).cos();
        if cos_c < 0. {
            return None;
        }

        let r = (self._size / 2) as f32;

        let x = r * lat.cos() * (lon - self._lon).sin();
        let y = r * ( self._lat.cos()*lat.sin() - self._lat.sin()*lat.cos()*(lon - self._lon).cos() );

        // FIXME Weird hack? Why is this required?
        let y = y * -1.;

        let x = x + r;
        let y = y + r;

        let x = x.trunc() as u32;
        let y = y.trunc() as u32;

        Some((x, y))
    }

    /// Set the value of `lat`, `lon` to `value`
    pub fn set(&mut self, lat: f32, lon: f32, value: T) {
        match self.xy_for_pos(lat, lon) {
            None => {},
            Some((x, y)) => {
                self.set_pixel(x, y, value);
            },
        };
    }

    /// For `lat`/`lon` what is the currently stored value?
    pub fn get(&self, lat: f32, lon: f32) -> &T {
        let r = (self._size / 2) as f32;
        let x = r * lat.to_radians().cos() * (lon - self._lon).to_radians().sin();
        let y = r * ( self._lon.to_radians().cos()*lat.to_radians().sin() - self._lon.to_radians().sin()*(lon - self._lat).to_radians().cos() );
        // FIXME clipping
        let i = x.trunc() as usize * self._size as usize + y.trunc() as usize;

        &self._data[i]
    }

    /// What is the current value of pixel `x`, `y`
    pub fn get_pixel(&self, x: u32, y: u32) -> &T {
        &self._data[(x*self._size+y) as usize]
    }

    /// Shortcut to set the value of pixel (`x`, `y`) to `value`.
    fn set_pixel(&mut self, x: u32, y: u32, value: T) {
        let i = x as usize * self._size as usize + y as usize;
        self._data[i] = value;
    }
}


#[cfg(test)]
mod tests {
    #[test]
    fn test_simple() {
        use super::OrthoProj;
        let o = OrthoProj::new(3, 0., 0., 0u8);
        assert_eq!(o.get_pixel(0, 0), &0u8);

    }
}