1use crate::geo::Point;
2use serde::{Deserialize, Serialize};
3use std::time::SystemTime;
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct Point3d {
26 pub point: Point,
28 pub z: f64,
30}
31
32impl Point3d {
33 #[inline]
49 #[must_use]
50 pub fn new(x: f64, y: f64, z: f64) -> Self {
51 Self {
52 point: Point::new(x, y),
53 z,
54 }
55 }
56
57 #[inline]
59 #[must_use]
60 pub fn from_point_and_altitude(point: Point, z: f64) -> Self {
61 Self { point, z }
62 }
63
64 #[inline]
66 pub fn x(&self) -> f64 {
67 self.point.x()
68 }
69
70 #[inline]
72 pub fn y(&self) -> f64 {
73 self.point.y()
74 }
75
76 #[inline]
78 pub fn z(&self) -> f64 {
79 self.z
80 }
81
82 #[inline]
84 pub fn altitude(&self) -> f64 {
85 self.z
86 }
87
88 #[inline]
90 pub fn point_2d(&self) -> &Point {
91 &self.point
92 }
93
94 #[inline]
96 #[must_use]
97 pub fn to_2d(&self) -> Point {
98 self.point
99 }
100
101 #[inline]
122 #[must_use]
123 pub fn distance_3d(&self, other: &Point3d) -> f64 {
124 let dx = self.x() - other.x();
125 let dy = self.y() - other.y();
126 let dz = self.z - other.z;
127 (dx * dx + dy * dy + dz * dz).sqrt()
128 }
129
130 pub fn haversine_distances(&self, other: &Point3d) -> (f64, f64, f64) {
150 let horizontal_distance = self.point.haversine_distance(&other.point);
151 let altitude_diff = (self.z - other.z).abs();
152 let distance_3d = (horizontal_distance.powi(2) + altitude_diff.powi(2)).sqrt();
153
154 (horizontal_distance, altitude_diff, distance_3d)
155 }
156
157 #[inline]
176 pub fn haversine_3d(&self, other: &Point3d) -> f64 {
177 let (_, _, dist_3d) = self.haversine_distances(other);
178 dist_3d
179 }
180
181 #[inline]
187 pub fn haversine_2d(&self, other: &Point3d) -> f64 {
188 self.point.haversine_distance(&other.point)
189 }
190
191 #[inline]
193 pub fn altitude_difference(&self, other: &Point3d) -> f64 {
194 (self.z - other.z).abs()
195 }
196
197 #[cfg(feature = "geojson")]
212 pub fn to_geojson(&self) -> Result<String, crate::geo::GeoJsonError> {
213 use geojson::{Geometry, Value};
214
215 let geom = Geometry::new(Value::Point(vec![self.x(), self.y(), self.z()]));
216 serde_json::to_string(&geom).map_err(|e| {
217 crate::geo::GeoJsonError::Serialization(format!("Failed to serialize 3D point: {}", e))
218 })
219 }
220
221 #[cfg(feature = "geojson")]
237 pub fn from_geojson(geojson: &str) -> Result<Self, crate::geo::GeoJsonError> {
238 use geojson::{Geometry, Value};
239
240 let geom: Geometry = serde_json::from_str(geojson).map_err(|e| {
241 crate::geo::GeoJsonError::Deserialization(format!("Failed to parse GeoJSON: {}", e))
242 })?;
243
244 match geom.value {
245 Value::Point(coords) => {
246 if coords.len() < 2 {
247 return Err(crate::geo::GeoJsonError::InvalidCoordinates(
248 "Point must have at least 2 coordinates".to_string(),
249 ));
250 }
251 let z = coords.get(2).copied().unwrap_or(0.0);
252 if !(coords[0].is_finite() && coords[1].is_finite() && z.is_finite()) {
253 return Err(crate::geo::GeoJsonError::InvalidCoordinates(
254 "Point coordinates must be finite".to_string(),
255 ));
256 }
257 Ok(Point3d::new(coords[0], coords[1], z))
258 }
259 _ => Err(crate::geo::GeoJsonError::InvalidGeometry(
260 "GeoJSON geometry is not a Point".to_string(),
261 )),
262 }
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub struct TemporalPoint {
269 pub point: Point,
270 pub timestamp: SystemTime,
271}
272
273impl TemporalPoint {
274 pub fn new(point: Point, timestamp: SystemTime) -> Self {
275 Self { point, timestamp }
276 }
277
278 pub fn point(&self) -> &Point {
279 &self.point
280 }
281
282 pub fn timestamp(&self) -> &SystemTime {
283 &self.timestamp
284 }
285}
286
287#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub struct TemporalPoint3D {
290 pub point: Point,
291 pub altitude: f64,
292 pub timestamp: SystemTime,
293}
294
295impl TemporalPoint3D {
296 pub fn new(point: Point, altitude: f64, timestamp: SystemTime) -> Self {
297 Self {
298 point,
299 altitude,
300 timestamp,
301 }
302 }
303
304 pub fn point(&self) -> &Point {
305 &self.point
306 }
307
308 pub fn altitude(&self) -> f64 {
309 self.altitude
310 }
311
312 pub fn timestamp(&self) -> &SystemTime {
313 &self.timestamp
314 }
315
316 pub fn to_point_3d(&self) -> Point3d {
318 Point3d::from_point_and_altitude(self.point, self.altitude)
319 }
320
321 pub fn distance_to(&self, other: &TemporalPoint3D) -> f64 {
323 self.to_point_3d().haversine_3d(&other.to_point_3d())
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn test_point3d_creation() {
333 let p = Point3d::new(-74.0, 40.7, 100.0);
334 assert_eq!(p.x(), -74.0);
335 assert_eq!(p.y(), 40.7);
336 assert_eq!(p.z(), 100.0);
337 assert_eq!(p.altitude(), 100.0);
338 }
339
340 #[test]
341 fn test_point3d_distance_3d() {
342 let p1 = Point3d::new(0.0, 0.0, 0.0);
343 let p2 = Point3d::new(3.0, 4.0, 12.0);
344 let distance = p1.distance_3d(&p2);
345 assert_eq!(distance, 13.0);
346 }
347
348 #[test]
349 fn test_point3d_altitude_difference() {
350 let p1 = Point3d::new(-74.0, 40.7, 50.0);
351 let p2 = Point3d::new(-74.0, 40.7, 150.0);
352 assert_eq!(p1.altitude_difference(&p2), 100.0);
353 }
354
355 #[test]
356 fn test_point3d_to_2d() {
357 let p = Point3d::new(-74.0, 40.7, 100.0);
358 let p2d = p.to_2d();
359 assert_eq!(p2d.x(), -74.0);
360 assert_eq!(p2d.y(), 40.7);
361 }
362
363 #[test]
364 fn test_haversine_3d() {
365 let p1 = Point3d::new(-74.0, 40.7, 0.0);
367 let p2 = Point3d::new(-74.0, 40.7, 100.0);
368 let distance = p1.haversine_3d(&p2);
369 assert!((distance - 100.0).abs() < 0.1);
371 }
372
373 #[test]
374 fn test_haversine_distances() {
375 let p1 = Point3d::new(-74.0060, 40.7128, 0.0);
376 let p2 = Point3d::new(-74.0070, 40.7138, 100.0);
377 let (h_dist, alt_diff, dist_3d) = p1.haversine_distances(&p2);
378
379 assert_eq!(alt_diff, 100.0);
381
382 assert!((dist_3d - (h_dist * h_dist + alt_diff * alt_diff).sqrt()).abs() < 0.1);
384
385 assert!((h_dist - p1.haversine_2d(&p2)).abs() < 0.1);
387 assert!((dist_3d - p1.haversine_3d(&p2)).abs() < 0.1);
388 }
389
390 #[test]
391 fn test_temporal_point3d_to_point3d() {
392 let temporal = TemporalPoint3D::new(Point::new(-74.0, 40.7), 100.0, SystemTime::now());
393 let p3d = temporal.to_point_3d();
394 assert_eq!(p3d.x(), -74.0);
395 assert_eq!(p3d.y(), 40.7);
396 assert_eq!(p3d.altitude(), 100.0);
397 }
398
399 #[cfg(feature = "geojson")]
400 #[test]
401 fn test_point3d_geojson_roundtrip() {
402 let original = Point3d::new(-74.0060, 40.7128, 100.0);
403 let json = original.to_geojson().unwrap();
404 let parsed = Point3d::from_geojson(&json).unwrap();
405
406 assert!((original.x() - parsed.x()).abs() < 1e-10);
407 assert!((original.y() - parsed.y()).abs() < 1e-10);
408 assert!((original.z() - parsed.z()).abs() < 1e-10);
409 }
410
411 #[cfg(feature = "geojson")]
412 #[test]
413 fn test_point3d_from_geojson_defaults_z() {
414 let json = r#"{"type":"Point","coordinates":[-74.006,40.7128]}"#;
415 let point = Point3d::from_geojson(json).unwrap();
416 assert_eq!(point.z(), 0.0);
417 }
418}