geometry_model/point.rs
1//! The default `model::Point<T, D, Cs>`.
2//!
3//! Mirrors `boost::geometry::model::point<CoordinateType, DimensionCount,
4//! CoordinateSystem>` declared in `boost/geometry/geometries/point.hpp`,
5//! together with the matching trait specialisations (`tag`,
6//! `coordinate_type`, `coordinate_system`, `dimension`, `access`) the
7//! same header provides under `namespace traits`. The Rust port collapses
8//! those five specialisations to one `Point` impl, plus the
9//! [`Geometry`] super-bound for the tag.
10//!
11//! The two convenience aliases [`Point2D`] and [`Point3D`] play the role
12//! of `boost/geometry/geometries/point_xy.hpp` and
13//! `boost/geometry/geometries/point_xyz.hpp` — they fix the dimension
14//! parameter but reuse the same const-generic body.
15
16use core::marker::PhantomData;
17
18use geometry_coords::CoordinateScalar;
19use geometry_cs::{Cartesian, CoordinateSystem};
20use geometry_tag::PointTag;
21use geometry_trait::{Geometry, Point as PointTrait, PointMut};
22
23/// Default Point — a `[T; D]` array plus a phantom CS tag.
24///
25/// Mirrors `boost::geometry::model::point<CoordinateType, DimensionCount,
26/// CoordinateSystem>` from `boost/geometry/geometries/point.hpp`. The
27/// `#[repr(transparent)]` annotation guarantees the in-memory layout
28/// matches the underlying `[T; D]`, so callers can safely treat the
29/// point as an array of coordinates when interfacing with FFI or SIMD
30/// code — the same invariant Boost relies on for its
31/// `m_values[DimensionCount]` member.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[repr(transparent)]
34pub struct Point<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem = Cartesian> {
35 coords: [T; D],
36 _cs: PhantomData<Cs>,
37}
38
39// Serde support (behind the `serde` feature). `serde` cannot derive
40// over the const-generic `[T; D]` field, so `Point` gets a hand-written
41// impl that (de)serialises the coordinates as a length-`D` sequence and
42// reconstructs the phantom CS tag. The other model types are plain
43// derives (see their own modules).
44#[cfg(feature = "serde")]
45impl<T, const D: usize, Cs> serde::Serialize for Point<T, D, Cs>
46where
47 T: CoordinateScalar + serde::Serialize,
48 Cs: CoordinateSystem,
49{
50 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
51 use serde::ser::SerializeTuple;
52 let mut tup = serializer.serialize_tuple(D)?;
53 for coord in &self.coords {
54 tup.serialize_element(coord)?;
55 }
56 tup.end()
57 }
58}
59
60#[cfg(feature = "serde")]
61impl<'de, T, const D: usize, Cs> serde::Deserialize<'de> for Point<T, D, Cs>
62where
63 T: CoordinateScalar + serde::Deserialize<'de>,
64 Cs: CoordinateSystem,
65{
66 fn deserialize<De: serde::Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error> {
67 struct CoordsVisitor<T, const D: usize>(PhantomData<T>);
68
69 impl<'de, T, const D: usize> serde::de::Visitor<'de> for CoordsVisitor<T, D>
70 where
71 T: CoordinateScalar + serde::Deserialize<'de>,
72 {
73 type Value = [T; D];
74
75 fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
76 write!(f, "a sequence of {D} coordinates")
77 }
78
79 fn visit_seq<A: serde::de::SeqAccess<'de>>(
80 self,
81 mut seq: A,
82 ) -> Result<[T; D], A::Error> {
83 // Build the array element by element; `[T; D]` has no
84 // `Default`, so collect into a temporary and convert.
85 let mut coords: [T; D] = [T::ZERO; D];
86 for (i, slot) in coords.iter_mut().enumerate() {
87 *slot = seq
88 .next_element::<T>()?
89 .ok_or_else(|| serde::de::Error::invalid_length(i, &self))?;
90 }
91 Ok(coords)
92 }
93 }
94
95 let coords = deserializer.deserialize_tuple(D, CoordsVisitor::<T, D>(PhantomData))?;
96 Ok(Point {
97 coords,
98 _cs: PhantomData,
99 })
100 }
101}
102
103/// 2D point alias.
104///
105/// Mirrors `boost::geometry::model::d2::point_xy<CoordinateType,
106/// CoordinateSystem>` from `boost/geometry/geometries/point_xy.hpp`.
107pub type Point2D<T, Cs = Cartesian> = Point<T, 2, Cs>;
108
109/// 3D point alias.
110///
111/// Mirrors `boost::geometry::model::d3::point_xyz<CoordinateType,
112/// CoordinateSystem>` from `boost/geometry/geometries/point_xyz.hpp`.
113pub type Point3D<T, Cs = Cartesian> = Point<T, 3, Cs>;
114
115// ---- Constructors -----------------------------------------------------
116//
117// Boost exposes three overloads of `point(...)` gated on the arity
118// matching `DimensionCount` (`point.hpp:132-185`). Rust has no SFINAE,
119// so we split into three inherent impls keyed on a specific const `D`.
120// Calling `Point2D::new(v0, v1, v2)` then fails to type-check because
121// there is no `new(_, _, _)` on `Point<T, 2, Cs>` — the mirror of the
122// C++ `enable_if_t<is_coordinates_number_leq<C, 3, DimensionCount>>`
123// guard.
124
125impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 1, Cs> {
126 /// Construct a 1-D point.
127 ///
128 /// Mirrors the single-argument `point(v0)` constructor at
129 /// `boost/geometry/geometries/point.hpp:133-149`.
130 #[must_use]
131 pub const fn new(v0: T) -> Self {
132 Self {
133 coords: [v0],
134 _cs: PhantomData,
135 }
136 }
137}
138
139impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 2, Cs> {
140 /// Construct a 2-D point.
141 ///
142 /// Mirrors the two-argument `point(v0, v1)` constructor at
143 /// `boost/geometry/geometries/point.hpp:151-167`.
144 #[must_use]
145 pub const fn new(v0: T, v1: T) -> Self {
146 Self {
147 coords: [v0, v1],
148 _cs: PhantomData,
149 }
150 }
151}
152
153impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 3, Cs> {
154 /// Construct a 3-D point.
155 ///
156 /// Mirrors the three-argument `point(v0, v1, v2)` constructor at
157 /// `boost/geometry/geometries/point.hpp:169-185`.
158 #[must_use]
159 pub const fn new(v0: T, v1: T, v2: T) -> Self {
160 Self {
161 coords: [v0, v1, v2],
162 _cs: PhantomData,
163 }
164 }
165}
166
167// ---- Default ----------------------------------------------------------
168//
169// `#[derive(Default)]` would require `[T; D]: Default`, whose
170// const-generic blanket impl is still unstable. We provide an explicit
171// impl using `CoordinateScalar::ZERO` — the Rust analogue of Boost's
172// zero-init `m_values{}` at `point.hpp:113-119`.
173
174impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Default for Point<T, D, Cs> {
175 #[inline]
176 fn default() -> Self {
177 Self {
178 coords: [T::ZERO; D],
179 _cs: PhantomData,
180 }
181 }
182}
183
184// ---- Trait impls ------------------------------------------------------
185
186/// Tag this concrete type as a Point. Mirrors the
187/// `traits::tag<model::point<...>>` specialisation at
188/// `boost/geometry/geometries/point.hpp:241-244`.
189impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Geometry for Point<T, D, Cs> {
190 type Kind = PointTag;
191 type Point = Self;
192}
193
194/// Wire the Point concept up. Collapses the four trait specialisations
195/// at `boost/geometry/geometries/point.hpp:246-299`
196/// (`coordinate_type`, `coordinate_system`, `dimension`, `access`)
197/// into one Rust impl.
198impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointTrait for Point<T, D, Cs> {
199 type Scalar = T;
200 type Cs = Cs;
201 const DIM: usize = D;
202
203 #[inline]
204 fn get<const DIDX: usize>(&self) -> T {
205 self.coords[DIDX]
206 }
207}
208
209impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointMut for Point<T, D, Cs> {
210 #[inline]
211 fn set<const DIDX: usize>(&mut self, value: T) {
212 self.coords[DIDX] = value;
213 }
214}