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 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 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}