Skip to main content

utiles_core/
gdal.rs

1//! GDAL related functions/utils
2
3/// A geotransform is an affine transformation from the image coordinate space
4/// (row, column), also known as (pixel, line) to the georeferenced coordinate
5/// space (projected or geographic coordinates).
6///
7/// A geotransform consists in a set of 6 coefficients:
8///
9/// GT(0) x-coordinate of the upper-left corner of the upper-left pixel.
10/// GT(1) w-e pixel resolution / pixel width.
11/// GT(2) row rotation (typically zero).
12/// GT(3) y-coordinate of the upper-left corner of the upper-left pixel.
13/// GT(4) column rotation (typically zero).
14/// GT(5) n-s pixel resolution / pixel height (negative value for a north-up image).
15pub struct GeoTransform {
16    /// x-coordinate of the upper-left corner of the upper-left pixel.
17    pub gt0: f64,
18    /// w-e pixel resolution / pixel width.
19    pub gt1: f64,
20    /// row rotation (typically zero).
21    pub gt2: f64,
22    /// y-coordinate of the upper-left corner of the upper-left pixel.
23    pub gt3: f64,
24    /// column rotation (typically zero).
25    pub gt4: f64,
26    /// n-s pixel resolution / pixel height (negative value for a north-up image).
27    pub gt5: f64,
28}
29
30impl GeoTransform {
31    /// Create a new `GeoTransform` with given coefficients.
32    #[must_use]
33    pub const fn new(
34        gt0: f64,
35        gt1: f64,
36        gt2: f64,
37        gt3: f64,
38        gt4: f64,
39        gt5: f64,
40    ) -> Self {
41        Self {
42            gt0,
43            gt1,
44            gt2,
45            gt3,
46            gt4,
47            gt5,
48        }
49    }
50
51    /// Compute the optimal zoom level for a geo-transform.
52    #[expect(clippy::cast_sign_loss)]
53    #[expect(clippy::cast_possible_truncation)]
54    #[must_use]
55    pub fn optzoom(&self) -> u8 {
56        let equator = 2.0 * std::f64::consts::PI * 6_378_137.0; // 2 * pi * radius of earth in meters
57        let resolution = self.gt1 * (equator / 360.0);
58        let zoom_level = (equator / 256.0) / resolution; // Assuming pixel_size is 256 as in the previous example
59        (zoom_level.log2().min(20.0).floor() + 0.5) as u8
60    }
61}
62
63impl From<(f64, f64, f64, f64, f64, f64)> for GeoTransform {
64    fn from(gt: (f64, f64, f64, f64, f64, f64)) -> Self {
65        Self::new(gt.0, gt.1, gt.2, gt.3, gt.4, gt.5)
66    }
67}
68
69/// Compute the optimal zoom level for a geo-transform.
70#[must_use]
71pub fn geotransform2optzoom(geotransform: (f64, f64, f64, f64, f64, f64)) -> u8 {
72    let gt = GeoTransform::from(geotransform);
73    gt.optzoom()
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_geotransform2optzoom() {
82        let optz = geotransform2optzoom((
83            -77.000_138,
84            0.000_278,
85            0.0,
86            26.000_138_9,
87            0.0,
88            -0.000_278,
89        ));
90        assert_eq!(optz, 12);
91    }
92}