1use 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<f64>,
28 pub z: f64,
30}
31
32impl Point3d {
33 pub fn new(x: f64, y: f64, z: f64) -> Self {
49 Self {
50 point: Point::new(x, y),
51 z,
52 }
53 }
54
55 pub fn from_point_and_altitude(point: Point<f64>, z: f64) -> Self {
57 Self { point, z }
58 }
59
60 pub fn x(&self) -> f64 {
62 self.point.x()
63 }
64
65 pub fn y(&self) -> f64 {
67 self.point.y()
68 }
69
70 pub fn z(&self) -> f64 {
72 self.z
73 }
74
75 pub fn altitude(&self) -> f64 {
77 self.z
78 }
79
80 pub fn point_2d(&self) -> &Point<f64> {
82 &self.point
83 }
84
85 pub fn to_2d(&self) -> Point<f64> {
87 self.point
88 }
89
90 pub fn distance_3d(&self, other: &Point3d) -> f64 {
107 let dx = self.x() - other.x();
108 let dy = self.y() - other.y();
109 let dz = self.z - other.z;
110 (dx * dx + dy * dy + dz * dz).sqrt()
111 }
112
113 pub fn haversine_distances(&self, other: &Point3d) -> (f64, f64, f64) {
132 const EARTH_RADIUS_METERS: f64 = 6_371_000.0;
133
134 let lat1 = self.y().to_radians();
135 let lat2 = other.y().to_radians();
136 let delta_lat = (other.y() - self.y()).to_radians();
137 let delta_lon = (other.x() - self.x()).to_radians();
138
139 let a = (delta_lat / 2.0).sin().powi(2)
140 + lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
141 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
142
143 let horizontal_distance = EARTH_RADIUS_METERS * c;
144 let altitude_diff = (self.z - other.z).abs();
145 let distance_3d =
146 (horizontal_distance * horizontal_distance + altitude_diff * altitude_diff).sqrt();
147
148 (horizontal_distance, altitude_diff, distance_3d)
149 }
150
151 #[inline]
170 pub fn haversine_3d(&self, other: &Point3d) -> f64 {
171 let (_, _, dist_3d) = self.haversine_distances(other);
172 dist_3d
173 }
174
175 #[inline]
181 pub fn haversine_2d(&self, other: &Point3d) -> f64 {
182 const EARTH_RADIUS_METERS: f64 = 6_371_000.0;
183
184 let lat1 = self.y().to_radians();
185 let lat2 = other.y().to_radians();
186 let delta_lat = (other.y() - self.y()).to_radians();
187 let delta_lon = (other.x() - self.x()).to_radians();
188
189 let a = (delta_lat / 2.0).sin().powi(2)
190 + lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
191 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
192
193 EARTH_RADIUS_METERS * c
194 }
195
196 #[inline]
198 pub fn altitude_difference(&self, other: &Point3d) -> f64 {
199 (self.z - other.z).abs()
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
205pub struct TemporalPoint {
206 pub point: Point<f64>,
207 pub timestamp: SystemTime,
208}
209
210impl TemporalPoint {
211 pub fn new(point: Point<f64>, timestamp: SystemTime) -> Self {
212 Self { point, timestamp }
213 }
214
215 pub fn point(&self) -> &Point<f64> {
216 &self.point
217 }
218
219 pub fn timestamp(&self) -> &SystemTime {
220 &self.timestamp
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
226pub struct TemporalPoint3D {
227 pub point: Point<f64>,
228 pub altitude: f64,
229 pub timestamp: SystemTime,
230}
231
232impl TemporalPoint3D {
233 pub fn new(point: Point<f64>, altitude: f64, timestamp: SystemTime) -> Self {
234 Self {
235 point,
236 altitude,
237 timestamp,
238 }
239 }
240
241 pub fn point(&self) -> &Point<f64> {
242 &self.point
243 }
244
245 pub fn altitude(&self) -> f64 {
246 self.altitude
247 }
248
249 pub fn timestamp(&self) -> &SystemTime {
250 &self.timestamp
251 }
252
253 pub fn to_point_3d(&self) -> Point3d {
255 Point3d::from_point_and_altitude(self.point, self.altitude)
256 }
257
258 pub fn distance_to(&self, other: &TemporalPoint3D) -> f64 {
260 self.to_point_3d().haversine_3d(&other.to_point_3d())
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_point3d_creation() {
270 let p = Point3d::new(-74.0, 40.7, 100.0);
271 assert_eq!(p.x(), -74.0);
272 assert_eq!(p.y(), 40.7);
273 assert_eq!(p.z(), 100.0);
274 assert_eq!(p.altitude(), 100.0);
275 }
276
277 #[test]
278 fn test_point3d_distance_3d() {
279 let p1 = Point3d::new(0.0, 0.0, 0.0);
280 let p2 = Point3d::new(3.0, 4.0, 12.0);
281 let distance = p1.distance_3d(&p2);
282 assert_eq!(distance, 13.0);
283 }
284
285 #[test]
286 fn test_point3d_altitude_difference() {
287 let p1 = Point3d::new(-74.0, 40.7, 50.0);
288 let p2 = Point3d::new(-74.0, 40.7, 150.0);
289 assert_eq!(p1.altitude_difference(&p2), 100.0);
290 }
291
292 #[test]
293 fn test_point3d_to_2d() {
294 let p = Point3d::new(-74.0, 40.7, 100.0);
295 let p2d = p.to_2d();
296 assert_eq!(p2d.x(), -74.0);
297 assert_eq!(p2d.y(), 40.7);
298 }
299
300 #[test]
301 fn test_haversine_3d() {
302 let p1 = Point3d::new(-74.0, 40.7, 0.0);
304 let p2 = Point3d::new(-74.0, 40.7, 100.0);
305 let distance = p1.haversine_3d(&p2);
306 assert!((distance - 100.0).abs() < 0.1);
308 }
309
310 #[test]
311 fn test_haversine_distances() {
312 let p1 = Point3d::new(-74.0060, 40.7128, 0.0);
313 let p2 = Point3d::new(-74.0070, 40.7138, 100.0);
314 let (h_dist, alt_diff, dist_3d) = p1.haversine_distances(&p2);
315
316 assert_eq!(alt_diff, 100.0);
318
319 assert!((dist_3d - (h_dist * h_dist + alt_diff * alt_diff).sqrt()).abs() < 0.1);
321
322 assert!((h_dist - p1.haversine_2d(&p2)).abs() < 0.1);
324 assert!((dist_3d - p1.haversine_3d(&p2)).abs() < 0.1);
325 }
326
327 #[test]
328 fn test_temporal_point3d_to_point3d() {
329 let temporal = TemporalPoint3D::new(Point::new(-74.0, 40.7), 100.0, SystemTime::now());
330 let p3d = temporal.to_point_3d();
331 assert_eq!(p3d.x(), -74.0);
332 assert_eq!(p3d.y(), 40.7);
333 assert_eq!(p3d.altitude(), 100.0);
334 }
335}