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 /// Return the x ordinate.
153 ///
154 /// Mirrors `model::d2::point_xy::x() const` from
155 /// `geometries/point_xy.hpp:67-68`.
156 #[inline]
157 #[must_use]
158 pub const fn x(&self) -> T {
159 self.coords[0]
160 }
161
162 /// Return the y ordinate.
163 ///
164 /// Mirrors `model::d2::point_xy::y() const` from
165 /// `geometries/point_xy.hpp:71-72`.
166 #[inline]
167 #[must_use]
168 pub const fn y(&self) -> T {
169 self.coords[1]
170 }
171
172 /// Set the x ordinate.
173 ///
174 /// Mirrors the setter overload `model::d2::point_xy::x(v)` from
175 /// `geometries/point_xy.hpp:75-76`. Rust uses a distinct method name
176 /// because it has no inherent-method overloading.
177 #[inline]
178 pub fn set_x(&mut self, value: T) {
179 self.coords[0] = value;
180 }
181
182 /// Set the y ordinate.
183 ///
184 /// Mirrors the setter overload `model::d2::point_xy::y(v)` from
185 /// `geometries/point_xy.hpp:79-80`.
186 #[inline]
187 pub fn set_y(&mut self, value: T) {
188 self.coords[1] = value;
189 }
190}
191
192impl<T: CoordinateScalar, Cs: CoordinateSystem> Point<T, 3, Cs> {
193 /// Construct a 3-D point.
194 ///
195 /// Mirrors the three-argument `point(v0, v1, v2)` constructor at
196 /// `boost/geometry/geometries/point.hpp:169-185`.
197 #[must_use]
198 pub const fn new(v0: T, v1: T, v2: T) -> Self {
199 Self {
200 coords: [v0, v1, v2],
201 _cs: PhantomData,
202 }
203 }
204
205 /// Return the x ordinate.
206 ///
207 /// Mirrors `model::d3::point_xyz::x() const` from
208 /// `geometries/point_xyz.hpp:55-56`.
209 #[inline]
210 #[must_use]
211 pub const fn x(&self) -> T {
212 self.coords[0]
213 }
214
215 /// Return the y ordinate.
216 ///
217 /// Mirrors `model::d3::point_xyz::y() const` from
218 /// `geometries/point_xyz.hpp:59-60`.
219 #[inline]
220 #[must_use]
221 pub const fn y(&self) -> T {
222 self.coords[1]
223 }
224
225 /// Return the z ordinate.
226 ///
227 /// Mirrors `model::d3::point_xyz::z() const` from
228 /// `geometries/point_xyz.hpp:63-64`.
229 #[inline]
230 #[must_use]
231 pub const fn z(&self) -> T {
232 self.coords[2]
233 }
234
235 /// Set the x ordinate.
236 ///
237 /// Mirrors the setter overload `model::d3::point_xyz::x(v)` from
238 /// `geometries/point_xyz.hpp:67-68`. Rust uses a distinct name because
239 /// it has no inherent-method overloading.
240 #[inline]
241 pub fn set_x(&mut self, value: T) {
242 self.coords[0] = value;
243 }
244
245 /// Set the y ordinate.
246 ///
247 /// Mirrors the setter overload `model::d3::point_xyz::y(v)` from
248 /// `geometries/point_xyz.hpp:71-72`.
249 #[inline]
250 pub fn set_y(&mut self, value: T) {
251 self.coords[1] = value;
252 }
253
254 /// Set the z ordinate.
255 ///
256 /// Mirrors the setter overload `model::d3::point_xyz::z(v)` from
257 /// `geometries/point_xyz.hpp:75-76`.
258 #[inline]
259 pub fn set_z(&mut self, value: T) {
260 self.coords[2] = value;
261 }
262}
263
264// ---- Default ----------------------------------------------------------
265//
266// `#[derive(Default)]` would require `[T; D]: Default`, whose
267// const-generic blanket impl is still unstable. We provide an explicit
268// impl using `CoordinateScalar::ZERO` — the Rust analogue of Boost's
269// zero-init `m_values{}` at `point.hpp:113-119`.
270
271impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Default for Point<T, D, Cs> {
272 #[inline]
273 fn default() -> Self {
274 Self {
275 coords: [T::ZERO; D],
276 _cs: PhantomData,
277 }
278 }
279}
280
281// ---- Trait impls ------------------------------------------------------
282
283/// Tag this concrete type as a Point. Mirrors the
284/// `traits::tag<model::point<...>>` specialisation at
285/// `boost/geometry/geometries/point.hpp:241-244`.
286impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> Geometry for Point<T, D, Cs> {
287 type Kind = PointTag;
288 type Point = Self;
289}
290
291/// Wire the Point concept up. Collapses the four trait specialisations
292/// at `boost/geometry/geometries/point.hpp:246-299`
293/// (`coordinate_type`, `coordinate_system`, `dimension`, `access`)
294/// into one Rust impl.
295impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointTrait for Point<T, D, Cs> {
296 type Scalar = T;
297 type Cs = Cs;
298 const DIM: usize = D;
299
300 #[inline]
301 fn get<const DIDX: usize>(&self) -> T {
302 self.coords[DIDX]
303 }
304}
305
306impl<T: CoordinateScalar, const D: usize, Cs: CoordinateSystem> PointMut for Point<T, D, Cs> {
307 #[inline]
308 fn set<const DIDX: usize>(&mut self, value: T) {
309 self.coords[DIDX] = value;
310 }
311}