1use crate::Point;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum CoordError {
24 OutOfRange,
26 NotAnInteger,
28 NotFinite,
30}
31
32impl core::fmt::Display for CoordError {
33 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
34 match self {
35 CoordError::OutOfRange => write!(f, "coordinate is outside the i16 range"),
36 CoordError::NotAnInteger => write!(f, "coordinate is not an integer"),
37 CoordError::NotFinite => write!(f, "coordinate is not finite"),
38 }
39 }
40}
41
42#[cfg(feature = "std")]
43impl std::error::Error for CoordError {}
44
45fn lattice(v: f64) -> Result<i16, CoordError> {
48 if !v.is_finite() {
49 return Err(CoordError::NotFinite);
50 }
51 let t = v as i64;
53 if t as f64 != v {
54 return Err(CoordError::NotAnInteger);
55 }
56 if t < i16::MIN as i64 || t > i16::MAX as i64 {
57 return Err(CoordError::OutOfRange);
58 }
59 Ok(t as i16)
60}
61
62#[cfg(feature = "geo-types")]
65#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
66mod geo_types_impl {
67 use super::*;
68
69 impl From<Point> for geo_types::Coord<i16> {
70 #[inline]
71 fn from(p: Point) -> Self {
72 geo_types::Coord { x: p.x, y: p.y }
73 }
74 }
75
76 impl From<geo_types::Coord<i16>> for Point {
77 #[inline]
78 fn from(c: geo_types::Coord<i16>) -> Self {
79 Point::new(c.x, c.y)
80 }
81 }
82
83 impl From<Point> for geo_types::Point<i16> {
84 #[inline]
85 fn from(p: Point) -> Self {
86 geo_types::Point::new(p.x, p.y)
87 }
88 }
89
90 impl From<geo_types::Point<i16>> for Point {
91 #[inline]
92 fn from(p: geo_types::Point<i16>) -> Self {
93 Point::new(p.x(), p.y())
94 }
95 }
96
97 impl From<Point> for geo_types::Coord<f64> {
100 #[inline]
101 fn from(p: Point) -> Self {
102 geo_types::Coord {
103 x: p.x as f64,
104 y: p.y as f64,
105 }
106 }
107 }
108
109 impl TryFrom<geo_types::Coord<f64>> for Point {
110 type Error = CoordError;
111 #[inline]
112 fn try_from(c: geo_types::Coord<f64>) -> Result<Self, CoordError> {
113 Ok(Point::new(lattice(c.x)?, lattice(c.y)?))
114 }
115 }
116}
117
118#[cfg(feature = "glam")]
121#[cfg_attr(docsrs, doc(cfg(feature = "glam")))]
122mod glam_impl {
123 use super::*;
124
125 impl From<Point> for glam::I16Vec2 {
126 #[inline]
127 fn from(p: Point) -> Self {
128 glam::I16Vec2::new(p.x, p.y)
129 }
130 }
131
132 impl From<glam::I16Vec2> for Point {
133 #[inline]
134 fn from(v: glam::I16Vec2) -> Self {
135 Point::new(v.x, v.y)
136 }
137 }
138
139 impl From<Point> for glam::IVec2 {
140 #[inline]
141 fn from(p: Point) -> Self {
142 glam::IVec2::new(p.x as i32, p.y as i32)
143 }
144 }
145
146 impl TryFrom<glam::IVec2> for Point {
147 type Error = CoordError;
148 #[inline]
149 fn try_from(v: glam::IVec2) -> Result<Self, CoordError> {
150 let x = i16::try_from(v.x).map_err(|_| CoordError::OutOfRange)?;
151 let y = i16::try_from(v.y).map_err(|_| CoordError::OutOfRange)?;
152 Ok(Point::new(x, y))
153 }
154 }
155
156 impl From<Point> for glam::Vec2 {
158 #[inline]
159 fn from(p: Point) -> Self {
160 glam::Vec2::new(p.x as f32, p.y as f32)
161 }
162 }
163
164 impl TryFrom<glam::Vec2> for Point {
165 type Error = CoordError;
166 #[inline]
167 fn try_from(v: glam::Vec2) -> Result<Self, CoordError> {
168 Ok(Point::new(lattice(v.x as f64)?, lattice(v.y as f64)?))
169 }
170 }
171}
172
173#[cfg(feature = "mint")]
176#[cfg_attr(docsrs, doc(cfg(feature = "mint")))]
177mod mint_impl {
178 use super::*;
179
180 impl From<Point> for mint::Point2<i16> {
181 #[inline]
182 fn from(p: Point) -> Self {
183 mint::Point2 { x: p.x, y: p.y }
184 }
185 }
186
187 impl From<mint::Point2<i16>> for Point {
188 #[inline]
189 fn from(p: mint::Point2<i16>) -> Self {
190 Point::new(p.x, p.y)
191 }
192 }
193
194 impl From<Point> for mint::Vector2<i16> {
195 #[inline]
196 fn from(p: Point) -> Self {
197 mint::Vector2 { x: p.x, y: p.y }
198 }
199 }
200
201 impl From<mint::Vector2<i16>> for Point {
202 #[inline]
203 fn from(v: mint::Vector2<i16>) -> Self {
204 Point::new(v.x, v.y)
205 }
206 }
207
208 impl From<Point> for mint::Point2<f32> {
209 #[inline]
210 fn from(p: Point) -> Self {
211 mint::Point2 {
212 x: p.x as f32,
213 y: p.y as f32,
214 }
215 }
216 }
217
218 impl TryFrom<mint::Point2<f32>> for Point {
219 type Error = CoordError;
220 #[inline]
221 fn try_from(p: mint::Point2<f32>) -> Result<Self, CoordError> {
222 Ok(Point::new(lattice(p.x as f64)?, lattice(p.y as f64)?))
223 }
224 }
225}
226
227#[cfg(feature = "num-traits")]
230#[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))]
231mod num_traits_impl {
232 use super::*;
233 use num_traits::{NumCast, ToPrimitive};
234
235 impl Point {
236 pub fn cast<T: NumCast>(self) -> Option<(T, T)> {
252 Some((T::from(self.x)?, T::from(self.y)?))
253 }
254
255 pub fn try_cast<T: ToPrimitive>(x: T, y: T) -> Option<Point> {
273 Some(Point::new(
278 lattice(x.to_f64()?).ok()?,
279 lattice(y.to_f64()?).ok()?,
280 ))
281 }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn lattice_accepts_exact_integers() {
291 assert_eq!(lattice(0.0), Ok(0));
292 assert_eq!(lattice(-32768.0), Ok(i16::MIN));
293 assert_eq!(lattice(32767.0), Ok(i16::MAX));
294 assert_eq!(lattice(-0.0), Ok(0));
295 }
296
297 #[test]
298 fn lattice_rejects_rather_than_rounds() {
299 assert_eq!(lattice(0.5), Err(CoordError::NotAnInteger));
300 assert_eq!(lattice(-1.25), Err(CoordError::NotAnInteger));
301 }
302
303 #[test]
304 fn lattice_rejects_rather_than_wraps() {
305 assert_eq!(lattice(32768.0), Err(CoordError::OutOfRange));
306 assert_eq!(lattice(-32769.0), Err(CoordError::OutOfRange));
307 assert_eq!(lattice(1e18), Err(CoordError::OutOfRange));
308 }
309
310 #[test]
311 fn lattice_rejects_nonfinite() {
312 assert_eq!(lattice(f64::NAN), Err(CoordError::NotFinite));
313 assert_eq!(lattice(f64::INFINITY), Err(CoordError::NotFinite));
314 assert_eq!(lattice(f64::NEG_INFINITY), Err(CoordError::NotFinite));
315 }
316
317 #[cfg(feature = "geo-types")]
318 #[test]
319 fn geo_types_round_trips() {
320 let p = Point::new(-7, 12);
321 let c: geo_types::Coord<i16> = p.into();
322 assert_eq!(Point::from(c), p);
323
324 let gp: geo_types::Point<i16> = p.into();
325 assert_eq!(Point::from(gp), p);
326
327 let f: geo_types::Coord<f64> = p.into();
328 assert_eq!(Point::try_from(f), Ok(p));
329
330 assert_eq!(
331 Point::try_from(geo_types::Coord { x: 0.5f64, y: 0.0 }),
332 Err(CoordError::NotAnInteger)
333 );
334 }
335
336 #[cfg(feature = "glam")]
337 #[test]
338 fn glam_round_trips() {
339 let p = Point::new(-7, 12);
340 assert_eq!(Point::from(glam::I16Vec2::from(p)), p);
341 assert_eq!(Point::try_from(glam::IVec2::from(p)), Ok(p));
342 assert_eq!(Point::try_from(glam::Vec2::from(p)), Ok(p));
343
344 assert_eq!(
345 Point::try_from(glam::IVec2::new(70_000, 0)),
346 Err(CoordError::OutOfRange)
347 );
348 assert_eq!(
349 Point::try_from(glam::Vec2::new(0.5, 0.0)),
350 Err(CoordError::NotAnInteger)
351 );
352 }
353
354 #[cfg(feature = "mint")]
355 #[test]
356 fn mint_round_trips() {
357 let p = Point::new(-7, 12);
358 assert_eq!(Point::from(mint::Point2::from(p)), p);
359 assert_eq!(Point::from(mint::Vector2::from(p)), p);
360 let f: mint::Point2<f32> = p.into();
361 assert_eq!(Point::try_from(f), Ok(p));
362 }
363
364 #[cfg(feature = "num-traits")]
365 #[test]
366 fn num_traits_casts() {
367 let p = Point::new(3, -4);
368 assert_eq!(p.cast::<f64>(), Some((3.0, -4.0)));
369 assert_eq!(p.cast::<i32>(), Some((3, -4)));
370 assert_eq!(p.cast::<u16>(), None, "-4 is not representable");
371 assert_eq!(Point::try_cast(3i32, -4i32), Some(p));
372 assert_eq!(Point::try_cast(99_999i32, 0i32), None);
373 assert_eq!(Point::try_cast(0.5f64, 0.0f64), None);
375 assert_eq!(Point::try_cast(-0.5f32, 0.0f32), None);
376 assert_eq!(Point::try_cast(f64::NAN, 0.0), None);
377 }
378}