Skip to main content

use_map_scale/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum MapScaleError {
9    ZeroScaleRatio,
10    ResolutionNotFinite,
11    ResolutionNotPositive,
12}
13
14impl fmt::Display for MapScaleError {
15    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
16        match self {
17            Self::ZeroScaleRatio => formatter.write_str("scale ratio must be greater than zero"),
18            Self::ResolutionNotFinite => formatter.write_str("map resolution must be finite"),
19            Self::ResolutionNotPositive => {
20                formatter.write_str("map resolution must be greater than zero")
21            },
22        }
23    }
24}
25
26impl Error for MapScaleError {}
27
28#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub struct ScaleRatio(u32);
30
31impl ScaleRatio {
32    /// Creates a scale ratio from a positive denominator.
33    ///
34    /// # Errors
35    ///
36    /// Returns [`MapScaleError::ZeroScaleRatio`] when `denominator` is zero.
37    pub const fn new(denominator: u32) -> Result<Self, MapScaleError> {
38        if denominator == 0 {
39            return Err(MapScaleError::ZeroScaleRatio);
40        }
41
42        Ok(Self(denominator))
43    }
44
45    #[must_use]
46    pub const fn denominator(self) -> u32 {
47        self.0
48    }
49}
50
51impl fmt::Display for ScaleRatio {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(formatter, "1:{}", self.denominator())
54    }
55}
56
57#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub struct MapScale {
59    ratio: ScaleRatio,
60}
61
62impl MapScale {
63    #[must_use]
64    pub const fn new(ratio: ScaleRatio) -> Self {
65        Self { ratio }
66    }
67
68    #[must_use]
69    pub const fn ratio(self) -> ScaleRatio {
70        self.ratio
71    }
72}
73
74impl fmt::Display for MapScale {
75    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76        self.ratio.fmt(formatter)
77    }
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
81pub struct MapResolution(f64);
82
83impl MapResolution {
84    /// Creates a positive map resolution.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`MapScaleError::ResolutionNotFinite`] when the value is not finite.
89    /// Returns [`MapScaleError::ResolutionNotPositive`] when the value is zero or negative.
90    pub fn new(units_per_pixel: f64) -> Result<Self, MapScaleError> {
91        if !units_per_pixel.is_finite() {
92            return Err(MapScaleError::ResolutionNotFinite);
93        }
94
95        if units_per_pixel <= 0.0 {
96            return Err(MapScaleError::ResolutionNotPositive);
97        }
98
99        Ok(Self(units_per_pixel))
100    }
101
102    #[must_use]
103    pub const fn units_per_pixel(self) -> f64 {
104        self.0
105    }
106}
107
108impl fmt::Display for MapResolution {
109    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110        write!(formatter, "{} units-per-pixel", self.units_per_pixel())
111    }
112}
113
114#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
115pub struct ZoomLevel(u8);
116
117impl ZoomLevel {
118    #[must_use]
119    pub const fn new(level: u8) -> Self {
120        Self(level)
121    }
122
123    #[must_use]
124    pub const fn level(self) -> u8 {
125        self.0
126    }
127}
128
129impl fmt::Display for ZoomLevel {
130    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
131        self.level().fmt(formatter)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::{MapResolution, MapScale, MapScaleError, ScaleRatio, ZoomLevel};
138
139    #[test]
140    fn valid_scale_ratio() -> Result<(), MapScaleError> {
141        let ratio = ScaleRatio::new(50_000)?;
142
143        assert_eq!(ratio.denominator(), 50_000);
144        Ok(())
145    }
146
147    #[test]
148    fn zero_scale_ratio_rejected() {
149        assert_eq!(ScaleRatio::new(0), Err(MapScaleError::ZeroScaleRatio));
150    }
151
152    #[test]
153    fn map_resolution_construction() -> Result<(), MapScaleError> {
154        let resolution = MapResolution::new(4.0)?;
155
156        assert!((resolution.units_per_pixel() - 4.0).abs() < f64::EPSILON);
157        Ok(())
158    }
159
160    #[test]
161    fn zoom_level_construction() {
162        let zoom = ZoomLevel::new(12);
163
164        assert_eq!(zoom.level(), 12);
165    }
166
167    #[test]
168    fn display_behavior() -> Result<(), MapScaleError> {
169        let scale = MapScale::new(ScaleRatio::new(25_000)?);
170
171        assert_eq!(scale.to_string(), "1:25000");
172        assert_eq!(ZoomLevel::new(8).to_string(), "8");
173        Ok(())
174    }
175}