zerometry/
zoint.rs

1use core::fmt;
2use std::io::{self, Write};
3
4use geo_types::Point;
5
6use crate::{
7    Coord, InputRelation, OutputRelation, RelationBetweenShapes, Zerometry, Zollection, Zolygon,
8    ZultiPoints, ZultiPolygons, zine::Zine, zulti_lines::ZultiLines,
9};
10
11#[derive(Clone, Copy)]
12pub struct Zoint<'a> {
13    coord: &'a Coord,
14}
15
16impl<'a> Zoint<'a> {
17    pub fn new(coord: &'a Coord) -> Self {
18        Self { coord }
19    }
20
21    pub fn from_bytes(data: &'a [u8]) -> Self {
22        let coord = Coord::from_bytes(data);
23        Self::new(coord)
24    }
25
26    pub fn write_from_geometry(
27        writer: &mut impl Write,
28        geometry: &Point<f64>,
29    ) -> Result<(), io::Error> {
30        writer.write_all(&geometry.x().to_ne_bytes())?;
31        writer.write_all(&geometry.y().to_ne_bytes())?;
32        Ok(())
33    }
34
35    pub fn coord(&self) -> &'a Coord {
36        self.coord
37    }
38
39    pub fn lat(&self) -> f64 {
40        self.coord.lat()
41    }
42    pub fn lng(&self) -> f64 {
43        self.coord.lng()
44    }
45
46    pub fn x(&self) -> f64 {
47        self.coord.lng()
48    }
49    pub fn y(&self) -> f64 {
50        self.coord.lat()
51    }
52
53    pub fn to_geo(&self) -> geo_types::Point<f64> {
54        geo_types::Point::new(self.coord.lng(), self.coord.lat())
55    }
56}
57
58impl<'a> fmt::Debug for Zoint<'a> {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        f.debug_struct("Zoint")
61            .field("lng", &self.coord.lng())
62            .field("lat", &self.coord.lat())
63            .finish()
64    }
65}
66
67impl PartialEq<geo_types::Point<f64>> for Zoint<'_> {
68    fn eq(&self, other: &geo_types::Point<f64>) -> bool {
69        self.coord.lng() == other.x() && self.coord.lat() == other.y()
70    }
71}
72
73// A point cannot contains or intersect with another point
74impl<'a> RelationBetweenShapes<Zoint<'a>> for Zoint<'a> {
75    fn relation(&self, _other: &Zoint<'a>, relation: InputRelation) -> OutputRelation {
76        relation.to_false().make_disjoint_if_set()
77    }
78}
79
80// A point cannot contains or intersect with a multi point
81impl<'a> RelationBetweenShapes<ZultiPoints<'a>> for Zoint<'a> {
82    fn relation(&self, _other: &ZultiPoints<'a>, relation: InputRelation) -> OutputRelation {
83        relation.to_false().make_disjoint_if_set()
84    }
85}
86
87// A point cannot contains or intersect with a line
88impl<'a> RelationBetweenShapes<Zine<'a>> for Zoint<'a> {
89    fn relation(&self, _other: &Zine<'a>, relation: InputRelation) -> OutputRelation {
90        relation.to_false().make_disjoint_if_set()
91    }
92}
93
94// A point cannot contains or intersect with a line
95impl<'a> RelationBetweenShapes<ZultiLines<'a>> for Zoint<'a> {
96    fn relation(&self, _other: &ZultiLines<'a>, relation: InputRelation) -> OutputRelation {
97        relation.to_false().make_disjoint_if_set()
98    }
99}
100
101impl<'a> RelationBetweenShapes<Zolygon<'a>> for Zoint<'a> {
102    fn relation(&self, other: &Zolygon<'a>, relation: InputRelation) -> OutputRelation {
103        if other.strict_contains(self) {
104            relation.to_false().make_strict_contained_if_set()
105        } else {
106            relation.to_false().make_disjoint_if_set()
107        }
108    }
109}
110
111impl<'a> RelationBetweenShapes<ZultiPolygons<'a>> for Zoint<'a> {
112    fn relation(&self, other: &ZultiPolygons<'a>, relation: InputRelation) -> OutputRelation {
113        other
114            .relation(self, relation.swap_contains_relation())
115            .swap_contains_relation()
116    }
117}
118
119impl<'a> RelationBetweenShapes<Zollection<'a>> for Zoint<'a> {
120    fn relation(&self, other: &Zollection<'a>, relation: InputRelation) -> OutputRelation {
121        other
122            .relation(self, relation.swap_contains_relation())
123            .swap_contains_relation()
124    }
125}
126
127impl<'a> RelationBetweenShapes<Zerometry<'a>> for Zoint<'a> {
128    fn relation(&self, other: &Zerometry<'a>, relation: InputRelation) -> OutputRelation {
129        other
130            .relation(self, relation.swap_contains_relation())
131            .swap_contains_relation()
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use bytemuck::cast_slice;
138    use insta::assert_compact_debug_snapshot;
139
140    use super::*;
141
142    #[test]
143    fn test_zoint_binary_format() {
144        let mut buffer = Vec::new();
145        Zoint::write_from_geometry(&mut buffer, &Point::new(1.0, 2.0)).unwrap();
146        let input: &[f64] = cast_slice(&buffer);
147        assert_compact_debug_snapshot!(input, @"[1.0, 2.0]");
148        let zoint = Zoint::from_bytes(&buffer);
149        assert_compact_debug_snapshot!(zoint.coord(), @"Coord { x: 1.0, y: 2.0 }");
150    }
151
152    // Prop test ensuring we can round trip from a point to a zoint and back to a point
153    proptest::proptest! {
154        #[test]
155        fn test_zoint_round_trip(lng: f64, lat: f64) {
156            let point = Point::new(lng, lat);
157            let mut buffer = Vec::new();
158            Zoint::write_from_geometry(&mut buffer, &point).unwrap();
159            let zoint = Zoint::from_bytes(&buffer);
160            assert_eq!(zoint, point);
161        }
162    }
163}