Skip to main content

sklears_utils/
spatial.rs

1//! Spatial data structures for efficient spatial queries and indexing
2//!
3//! This module provides spatial data structures commonly used in machine learning
4//! for nearest neighbor search, range queries, and spatial indexing.
5
6/// A point in N-dimensional space
7#[derive(Debug, Clone, PartialEq)]
8pub struct Point {
9    pub coordinates: Vec<f64>,
10}
11
12impl Point {
13    pub fn new(coordinates: Vec<f64>) -> Self {
14        Self { coordinates }
15    }
16
17    pub fn dimension(&self) -> usize {
18        self.coordinates.len()
19    }
20
21    pub fn distance_squared(&self, other: &Point) -> f64 {
22        self.coordinates
23            .iter()
24            .zip(other.coordinates.iter())
25            .map(|(a, b)| (a - b).powi(2))
26            .sum()
27    }
28
29    pub fn distance(&self, other: &Point) -> f64 {
30        self.distance_squared(other).sqrt()
31    }
32}
33
34/// Rectangle/Bounding box for spatial queries
35#[derive(Debug, Clone, PartialEq)]
36pub struct Rectangle {
37    pub min_bounds: Vec<f64>,
38    pub max_bounds: Vec<f64>,
39}
40
41impl Rectangle {
42    pub fn new(min_bounds: Vec<f64>, max_bounds: Vec<f64>) -> Self {
43        assert_eq!(min_bounds.len(), max_bounds.len());
44        Self {
45            min_bounds,
46            max_bounds,
47        }
48    }
49
50    pub fn dimension(&self) -> usize {
51        self.min_bounds.len()
52    }
53
54    pub fn contains(&self, point: &Point) -> bool {
55        point
56            .coordinates
57            .iter()
58            .enumerate()
59            .all(|(i, &coord)| coord >= self.min_bounds[i] && coord <= self.max_bounds[i])
60    }
61
62    pub fn intersects(&self, other: &Rectangle) -> bool {
63        self.min_bounds.iter().enumerate().all(|(i, &min)| {
64            min <= other.max_bounds[i] && self.max_bounds[i] >= other.min_bounds[i]
65        })
66    }
67
68    pub fn area(&self) -> f64 {
69        self.min_bounds
70            .iter()
71            .zip(self.max_bounds.iter())
72            .map(|(min, max)| max - min)
73            .product()
74    }
75
76    pub fn expand_to_include(&mut self, point: &Point) {
77        for (i, &coord) in point.coordinates.iter().enumerate() {
78            if coord < self.min_bounds[i] {
79                self.min_bounds[i] = coord;
80            }
81            if coord > self.max_bounds[i] {
82                self.max_bounds[i] = coord;
83            }
84        }
85    }
86
87    pub fn union(&self, other: &Rectangle) -> Rectangle {
88        let min_bounds = self
89            .min_bounds
90            .iter()
91            .zip(other.min_bounds.iter())
92            .map(|(a, b)| a.min(*b))
93            .collect();
94
95        let max_bounds = self
96            .max_bounds
97            .iter()
98            .zip(other.max_bounds.iter())
99            .map(|(a, b)| a.max(*b))
100            .collect();
101
102        Rectangle::new(min_bounds, max_bounds)
103    }
104}
105
106/// K-d tree for efficient nearest neighbor search
107pub struct KdTree {
108    root: Option<Box<KdNode>>,
109    dimension: usize,
110}
111
112#[derive(Debug)]
113struct KdNode {
114    point: Point,
115    data: usize, // Index or ID of associated data
116    split_dimension: usize,
117    left: Option<Box<KdNode>>,
118    right: Option<Box<KdNode>>,
119}
120
121impl KdTree {
122    pub fn new(dimension: usize) -> Self {
123        Self {
124            root: None,
125            dimension,
126        }
127    }
128
129    pub fn from_points(points: Vec<(Point, usize)>) -> Self {
130        if points.is_empty() {
131            return Self::new(0);
132        }
133
134        let dimension = points[0].0.dimension();
135        let root = Self::build_tree(points, 0, dimension);
136
137        Self {
138            root: Some(root),
139            dimension,
140        }
141    }
142
143    fn build_tree(mut points: Vec<(Point, usize)>, depth: usize, dimension: usize) -> Box<KdNode> {
144        let split_dim = depth % dimension;
145
146        // Sort points by the splitting dimension
147        points.sort_by(|a, b| {
148            a.0.coordinates[split_dim]
149                .partial_cmp(&b.0.coordinates[split_dim])
150                .expect("operation should succeed")
151        });
152
153        let median = points.len() / 2;
154        let (point, data) = points.remove(median);
155
156        let left = if median > 0 {
157            Some(Self::build_tree(
158                points[..median].to_vec(),
159                depth + 1,
160                dimension,
161            ))
162        } else {
163            None
164        };
165
166        let right = if median < points.len() {
167            Some(Self::build_tree(
168                points[median..].to_vec(),
169                depth + 1,
170                dimension,
171            ))
172        } else {
173            None
174        };
175
176        Box::new(KdNode {
177            point,
178            data,
179            split_dimension: split_dim,
180            left,
181            right,
182        })
183    }
184
185    pub fn insert(&mut self, point: Point, data: usize) {
186        if self.root.is_none() {
187            self.dimension = point.dimension();
188        }
189
190        self.root = Some(Self::insert_recursive(
191            self.root.take(),
192            point,
193            data,
194            0,
195            self.dimension,
196        ));
197    }
198
199    fn insert_recursive(
200        node: Option<Box<KdNode>>,
201        point: Point,
202        data: usize,
203        depth: usize,
204        dimension: usize,
205    ) -> Box<KdNode> {
206        if let Some(mut existing) = node {
207            let split_dim = depth % dimension;
208
209            if point.coordinates[split_dim] <= existing.point.coordinates[split_dim] {
210                existing.left = Some(Self::insert_recursive(
211                    existing.left.take(),
212                    point,
213                    data,
214                    depth + 1,
215                    dimension,
216                ));
217            } else {
218                existing.right = Some(Self::insert_recursive(
219                    existing.right.take(),
220                    point,
221                    data,
222                    depth + 1,
223                    dimension,
224                ));
225            }
226
227            existing
228        } else {
229            Box::new(KdNode {
230                point,
231                data,
232                split_dimension: depth % dimension,
233                left: None,
234                right: None,
235            })
236        }
237    }
238
239    pub fn nearest_neighbor(&self, query: &Point) -> Option<(Point, usize, f64)> {
240        self.root.as_ref().map(|root| {
241            let mut best = (
242                root.point.clone(),
243                root.data,
244                query.distance_squared(&root.point),
245            );
246            Self::nearest_recursive(root, query, &mut best, 0);
247            (best.0, best.1, best.2.sqrt())
248        })
249    }
250
251    fn nearest_recursive(
252        node: &KdNode,
253        query: &Point,
254        best: &mut (Point, usize, f64),
255        _depth: usize,
256    ) {
257        let distance_sq = query.distance_squared(&node.point);
258        if distance_sq < best.2 {
259            *best = (node.point.clone(), node.data, distance_sq);
260        }
261
262        let split_dim = node.split_dimension;
263        let diff = query.coordinates[split_dim] - node.point.coordinates[split_dim];
264
265        let (primary, secondary) = if diff <= 0.0 {
266            (&node.left, &node.right)
267        } else {
268            (&node.right, &node.left)
269        };
270
271        if let Some(child) = primary {
272            Self::nearest_recursive(child, query, best, _depth + 1);
273        }
274
275        // Check if we need to explore the other side
276        if diff * diff < best.2 {
277            if let Some(child) = secondary {
278                Self::nearest_recursive(child, query, best, _depth + 1);
279            }
280        }
281    }
282
283    pub fn range_query(&self, range: &Rectangle) -> Vec<(Point, usize)> {
284        let mut results = Vec::new();
285        if let Some(root) = &self.root {
286            Self::range_recursive(root, range, &mut results);
287        }
288        results
289    }
290
291    fn range_recursive(node: &KdNode, range: &Rectangle, results: &mut Vec<(Point, usize)>) {
292        if range.contains(&node.point) {
293            results.push((node.point.clone(), node.data));
294        }
295
296        let split_dim = node.split_dimension;
297
298        if range.min_bounds[split_dim] <= node.point.coordinates[split_dim] {
299            if let Some(left) = &node.left {
300                Self::range_recursive(left, range, results);
301            }
302        }
303
304        if range.max_bounds[split_dim] >= node.point.coordinates[split_dim] {
305            if let Some(right) = &node.right {
306                Self::range_recursive(right, range, results);
307            }
308        }
309    }
310}
311
312/// R-tree for efficient spatial indexing of rectangles
313pub struct RTree {
314    root: Option<Box<RNode>>,
315    max_entries: usize,
316    #[allow(dead_code)]
317    min_entries: usize,
318}
319
320#[derive(Debug)]
321struct RNode {
322    bounds: Rectangle,
323    entries: Vec<REntry>,
324    is_leaf: bool,
325}
326
327#[derive(Debug)]
328enum REntry {
329    Leaf {
330        bounds: Rectangle,
331        data: usize,
332    },
333    #[allow(dead_code)]
334    Internal {
335        bounds: Rectangle,
336        child: Box<RNode>,
337    },
338}
339
340impl RTree {
341    pub fn new(max_entries: usize) -> Self {
342        let min_entries = max_entries / 2;
343        Self {
344            root: None,
345            max_entries,
346            min_entries,
347        }
348    }
349
350    pub fn insert(&mut self, bounds: Rectangle, data: usize) {
351        let entry = REntry::Leaf {
352            bounds: bounds.clone(),
353            data,
354        };
355
356        if self.root.is_none() {
357            self.root = Some(Box::new(RNode {
358                bounds,
359                entries: vec![entry],
360                is_leaf: true,
361            }));
362        } else {
363            self.insert_recursive(entry);
364        }
365    }
366
367    fn insert_recursive(&mut self, entry: REntry) {
368        // Simplified insertion - in a full implementation, this would handle splits and tree growth
369        if let Some(root) = &mut self.root {
370            if root.is_leaf && root.entries.len() < self.max_entries {
371                root.bounds = root.bounds.union(entry.bounds());
372                root.entries.push(entry);
373            }
374        }
375    }
376
377    pub fn query(&self, query_bounds: &Rectangle) -> Vec<usize> {
378        let mut results = Vec::new();
379        if let Some(root) = &self.root {
380            Self::query_recursive(root, query_bounds, &mut results);
381        }
382        results
383    }
384
385    fn query_recursive(node: &RNode, query_bounds: &Rectangle, results: &mut Vec<usize>) {
386        if !node.bounds.intersects(query_bounds) {
387            return;
388        }
389
390        for entry in &node.entries {
391            match entry {
392                REntry::Leaf { bounds, data } => {
393                    if bounds.intersects(query_bounds) {
394                        results.push(*data);
395                    }
396                }
397                REntry::Internal { bounds, child } => {
398                    if bounds.intersects(query_bounds) {
399                        Self::query_recursive(child, query_bounds, results);
400                    }
401                }
402            }
403        }
404    }
405}
406
407impl REntry {
408    fn bounds(&self) -> &Rectangle {
409        match self {
410            REntry::Leaf { bounds, .. } => bounds,
411            REntry::Internal { bounds, .. } => bounds,
412        }
413    }
414}
415
416/// Quadtree for 2D spatial indexing
417pub struct QuadTree {
418    root: QuadNode,
419    max_points: usize,
420    max_depth: usize,
421}
422
423#[derive(Debug)]
424struct QuadNode {
425    bounds: Rectangle,
426    points: Vec<(Point, usize)>,
427    children: Option<[Box<QuadNode>; 4]>,
428    depth: usize,
429}
430
431impl QuadTree {
432    pub fn new(bounds: Rectangle, max_points: usize, max_depth: usize) -> Self {
433        assert_eq!(bounds.dimension(), 2, "QuadTree only supports 2D");
434
435        Self {
436            root: QuadNode {
437                bounds,
438                points: Vec::new(),
439                children: None,
440                depth: 0,
441            },
442            max_points,
443            max_depth,
444        }
445    }
446
447    pub fn insert(&mut self, point: Point, data: usize) {
448        assert_eq!(point.dimension(), 2, "QuadTree only supports 2D points");
449        self.root
450            .insert(point, data, self.max_points, self.max_depth);
451    }
452
453    pub fn query(&self, query_bounds: &Rectangle) -> Vec<(Point, usize)> {
454        let mut results = Vec::new();
455        self.root.query(query_bounds, &mut results);
456        results
457    }
458
459    pub fn nearest_neighbor(
460        &self,
461        query: &Point,
462        max_distance: Option<f64>,
463    ) -> Option<(Point, usize, f64)> {
464        self.root.nearest_neighbor(query, max_distance)
465    }
466}
467
468impl QuadNode {
469    fn insert(&mut self, point: Point, data: usize, max_points: usize, max_depth: usize) {
470        if !self.bounds.contains(&point) {
471            return;
472        }
473
474        if self.children.is_none() {
475            self.points.push((point, data));
476
477            if self.points.len() > max_points && self.depth < max_depth {
478                self.subdivide();
479
480                // Redistribute points to children
481                let points = std::mem::take(&mut self.points);
482                for (p, d) in points {
483                    self.insert_into_children(p, d, max_points, max_depth);
484                }
485            }
486        } else {
487            self.insert_into_children(point, data, max_points, max_depth);
488        }
489    }
490
491    fn subdivide(&mut self) {
492        let mid_x = (self.bounds.min_bounds[0] + self.bounds.max_bounds[0]) / 2.0;
493        let mid_y = (self.bounds.min_bounds[1] + self.bounds.max_bounds[1]) / 2.0;
494
495        let nw = Rectangle::new(
496            vec![self.bounds.min_bounds[0], mid_y],
497            vec![mid_x, self.bounds.max_bounds[1]],
498        );
499        let ne = Rectangle::new(
500            vec![mid_x, mid_y],
501            vec![self.bounds.max_bounds[0], self.bounds.max_bounds[1]],
502        );
503        let sw = Rectangle::new(
504            vec![self.bounds.min_bounds[0], self.bounds.min_bounds[1]],
505            vec![mid_x, mid_y],
506        );
507        let se = Rectangle::new(
508            vec![mid_x, self.bounds.min_bounds[1]],
509            vec![self.bounds.max_bounds[0], mid_y],
510        );
511
512        self.children = Some([
513            Box::new(QuadNode {
514                bounds: nw,
515                points: Vec::new(),
516                children: None,
517                depth: self.depth + 1,
518            }),
519            Box::new(QuadNode {
520                bounds: ne,
521                points: Vec::new(),
522                children: None,
523                depth: self.depth + 1,
524            }),
525            Box::new(QuadNode {
526                bounds: sw,
527                points: Vec::new(),
528                children: None,
529                depth: self.depth + 1,
530            }),
531            Box::new(QuadNode {
532                bounds: se,
533                points: Vec::new(),
534                children: None,
535                depth: self.depth + 1,
536            }),
537        ]);
538    }
539
540    fn insert_into_children(
541        &mut self,
542        point: Point,
543        data: usize,
544        max_points: usize,
545        max_depth: usize,
546    ) {
547        if let Some(children) = &mut self.children {
548            for child in children.iter_mut() {
549                child.insert(point.clone(), data, max_points, max_depth);
550            }
551        }
552    }
553
554    fn query(&self, query_bounds: &Rectangle, results: &mut Vec<(Point, usize)>) {
555        if !self.bounds.intersects(query_bounds) {
556            return;
557        }
558
559        for (point, data) in &self.points {
560            if query_bounds.contains(point) {
561                results.push((point.clone(), *data));
562            }
563        }
564
565        if let Some(children) = &self.children {
566            for child in children.iter() {
567                child.query(query_bounds, results);
568            }
569        }
570    }
571
572    fn nearest_neighbor(
573        &self,
574        query: &Point,
575        max_distance: Option<f64>,
576    ) -> Option<(Point, usize, f64)> {
577        let mut best: Option<(Point, usize, f64)> = None;
578        let max_dist_sq = max_distance.map(|d| d * d);
579
580        // Check points in this node
581        for (point, data) in &self.points {
582            let dist_sq = query.distance_squared(point);
583
584            if let Some(max_sq) = max_dist_sq {
585                if dist_sq > max_sq {
586                    continue;
587                }
588            }
589
590            if best.is_none() || dist_sq < best.as_ref().expect("operation should succeed").2 {
591                best = Some((point.clone(), *data, dist_sq));
592            }
593        }
594
595        // Check children if they exist
596        if let Some(children) = &self.children {
597            for child in children.iter() {
598                if let Some(child_best) = child.nearest_neighbor(query, max_distance) {
599                    if best.is_none()
600                        || child_best.2 * child_best.2
601                            < best.as_ref().expect("operation should succeed").2
602                    {
603                        best = Some((child_best.0, child_best.1, child_best.2 * child_best.2));
604                    }
605                }
606            }
607        }
608
609        best.map(|(p, d, dist_sq)| (p, d, dist_sq.sqrt()))
610    }
611}
612
613/// Octree for 3D spatial indexing
614pub struct OctTree {
615    root: OctNode,
616    max_points: usize,
617    max_depth: usize,
618}
619
620#[derive(Debug)]
621struct OctNode {
622    bounds: Rectangle,
623    points: Vec<(Point, usize)>,
624    children: Option<[Box<OctNode>; 8]>,
625    depth: usize,
626}
627
628impl OctTree {
629    pub fn new(bounds: Rectangle, max_points: usize, max_depth: usize) -> Self {
630        assert_eq!(bounds.dimension(), 3, "OctTree only supports 3D");
631
632        Self {
633            root: OctNode {
634                bounds,
635                points: Vec::new(),
636                children: None,
637                depth: 0,
638            },
639            max_points,
640            max_depth,
641        }
642    }
643
644    pub fn insert(&mut self, point: Point, data: usize) {
645        assert_eq!(point.dimension(), 3, "OctTree only supports 3D points");
646        self.root
647            .insert(point, data, self.max_points, self.max_depth);
648    }
649
650    pub fn query(&self, query_bounds: &Rectangle) -> Vec<(Point, usize)> {
651        let mut results = Vec::new();
652        self.root.query(query_bounds, &mut results);
653        results
654    }
655}
656
657impl OctNode {
658    fn insert(&mut self, point: Point, data: usize, max_points: usize, max_depth: usize) {
659        if !self.bounds.contains(&point) {
660            return;
661        }
662
663        if self.children.is_none() {
664            self.points.push((point, data));
665
666            if self.points.len() > max_points && self.depth < max_depth {
667                self.subdivide();
668
669                // Redistribute points to children
670                let points = std::mem::take(&mut self.points);
671                for (p, d) in points {
672                    self.insert_into_children(p, d, max_points, max_depth);
673                }
674            }
675        } else {
676            self.insert_into_children(point, data, max_points, max_depth);
677        }
678    }
679
680    fn subdivide(&mut self) {
681        let mid_x = (self.bounds.min_bounds[0] + self.bounds.max_bounds[0]) / 2.0;
682        let mid_y = (self.bounds.min_bounds[1] + self.bounds.max_bounds[1]) / 2.0;
683        let mid_z = (self.bounds.min_bounds[2] + self.bounds.max_bounds[2]) / 2.0;
684
685        // Create 8 octants
686        let mut children = Vec::with_capacity(8);
687
688        for &z_low in &[true, false] {
689            for &y_low in &[true, false] {
690                for &x_low in &[true, false] {
691                    let min_bounds = vec![
692                        if x_low {
693                            self.bounds.min_bounds[0]
694                        } else {
695                            mid_x
696                        },
697                        if y_low {
698                            self.bounds.min_bounds[1]
699                        } else {
700                            mid_y
701                        },
702                        if z_low {
703                            self.bounds.min_bounds[2]
704                        } else {
705                            mid_z
706                        },
707                    ];
708                    let max_bounds = vec![
709                        if x_low {
710                            mid_x
711                        } else {
712                            self.bounds.max_bounds[0]
713                        },
714                        if y_low {
715                            mid_y
716                        } else {
717                            self.bounds.max_bounds[1]
718                        },
719                        if z_low {
720                            mid_z
721                        } else {
722                            self.bounds.max_bounds[2]
723                        },
724                    ];
725
726                    children.push(Box::new(OctNode {
727                        bounds: Rectangle::new(min_bounds, max_bounds),
728                        points: Vec::new(),
729                        children: None,
730                        depth: self.depth + 1,
731                    }));
732                }
733            }
734        }
735
736        self.children = Some(children.try_into().expect("operation should succeed"));
737    }
738
739    fn insert_into_children(
740        &mut self,
741        point: Point,
742        data: usize,
743        max_points: usize,
744        max_depth: usize,
745    ) {
746        if let Some(children) = &mut self.children {
747            for child in children.iter_mut() {
748                child.insert(point.clone(), data, max_points, max_depth);
749            }
750        }
751    }
752
753    fn query(&self, query_bounds: &Rectangle, results: &mut Vec<(Point, usize)>) {
754        if !self.bounds.intersects(query_bounds) {
755            return;
756        }
757
758        for (point, data) in &self.points {
759            if query_bounds.contains(point) {
760                results.push((point.clone(), *data));
761            }
762        }
763
764        if let Some(children) = &self.children {
765            for child in children.iter() {
766                child.query(query_bounds, results);
767            }
768        }
769    }
770}
771
772/// Spatial hash for efficient spatial queries with grid-based indexing
773pub struct SpatialHash {
774    grid: std::collections::HashMap<(i32, i32), Vec<(Point, usize)>>,
775    cell_size: f64,
776    bounds: Rectangle,
777}
778
779impl SpatialHash {
780    pub fn new(bounds: Rectangle, cell_size: f64) -> Self {
781        assert_eq!(bounds.dimension(), 2, "SpatialHash only supports 2D");
782
783        Self {
784            grid: std::collections::HashMap::new(),
785            cell_size,
786            bounds,
787        }
788    }
789
790    fn hash_point(&self, point: &Point) -> (i32, i32) {
791        let x =
792            ((point.coordinates[0] - self.bounds.min_bounds[0]) / self.cell_size).floor() as i32;
793        let y =
794            ((point.coordinates[1] - self.bounds.min_bounds[1]) / self.cell_size).floor() as i32;
795        (x, y)
796    }
797
798    pub fn insert(&mut self, point: Point, data: usize) {
799        let hash = self.hash_point(&point);
800        self.grid.entry(hash).or_default().push((point, data));
801    }
802
803    pub fn query_radius(&self, center: &Point, radius: f64) -> Vec<(Point, usize)> {
804        let mut results = Vec::new();
805        let radius_sq = radius * radius;
806
807        // Calculate grid cells that might contain points within radius
808        let cells_to_check = ((radius / self.cell_size).ceil() as i32) + 1;
809        let center_hash = self.hash_point(center);
810
811        for dx in -cells_to_check..=cells_to_check {
812            for dy in -cells_to_check..=cells_to_check {
813                let hash = (center_hash.0 + dx, center_hash.1 + dy);
814
815                if let Some(points) = self.grid.get(&hash) {
816                    for (point, data) in points {
817                        if center.distance_squared(point) <= radius_sq {
818                            results.push((point.clone(), *data));
819                        }
820                    }
821                }
822            }
823        }
824
825        results
826    }
827
828    pub fn clear(&mut self) {
829        self.grid.clear();
830    }
831
832    pub fn stats(&self) -> SpatialHashStats {
833        let total_points: usize = self.grid.values().map(|v| v.len()).sum();
834        let occupied_cells = self.grid.len();
835        let max_points_per_cell = self.grid.values().map(|v| v.len()).max().unwrap_or(0);
836        let avg_points_per_cell = if occupied_cells > 0 {
837            total_points as f64 / occupied_cells as f64
838        } else {
839            0.0
840        };
841
842        SpatialHashStats {
843            total_points,
844            occupied_cells,
845            max_points_per_cell,
846            avg_points_per_cell,
847            cell_size: self.cell_size,
848        }
849    }
850}
851
852#[derive(Debug, Clone)]
853pub struct SpatialHashStats {
854    pub total_points: usize,
855    pub occupied_cells: usize,
856    pub max_points_per_cell: usize,
857    pub avg_points_per_cell: f64,
858    pub cell_size: f64,
859}
860
861/// Geographic utilities for working with spatial coordinates and geographic data
862pub mod geographic {
863    use super::{Point, Rectangle};
864    use std::f64::consts::PI;
865
866    /// Coordinate system types
867    #[derive(Debug, Clone, PartialEq)]
868    pub enum CoordinateSystem {
869        /// Latitude/Longitude in degrees (WGS84)
870        LatLon,
871        /// Universal Transverse Mercator
872        UTM { zone: u8, hemisphere: Hemisphere },
873        /// Web Mercator (used by web mapping services)
874        WebMercator,
875        /// Custom coordinate system with EPSG code
876        EPSG(u32),
877    }
878
879    #[derive(Debug, Clone, PartialEq)]
880    pub enum Hemisphere {
881        North,
882        South,
883    }
884
885    /// Geographic point with latitude and longitude
886    #[derive(Debug, Clone, PartialEq)]
887    pub struct GeoPoint {
888        pub latitude: f64,
889        pub longitude: f64,
890        pub altitude: Option<f64>,
891        pub coordinate_system: CoordinateSystem,
892    }
893
894    impl GeoPoint {
895        /// Create a new geographic point
896        pub fn new(latitude: f64, longitude: f64) -> Self {
897            Self {
898                latitude,
899                longitude,
900                altitude: None,
901                coordinate_system: CoordinateSystem::LatLon,
902            }
903        }
904
905        /// Create a geographic point with altitude
906        pub fn with_altitude(latitude: f64, longitude: f64, altitude: f64) -> Self {
907            Self {
908                latitude,
909                longitude,
910                altitude: Some(altitude),
911                coordinate_system: CoordinateSystem::LatLon,
912            }
913        }
914
915        /// Set coordinate system
916        pub fn with_coordinate_system(mut self, coord_sys: CoordinateSystem) -> Self {
917            self.coordinate_system = coord_sys;
918            self
919        }
920
921        /// Calculate Haversine distance between two geographic points in meters
922        pub fn haversine_distance(&self, other: &GeoPoint) -> f64 {
923            const EARTH_RADIUS_M: f64 = 6_371_000.0;
924
925            let lat1_rad = self.latitude.to_radians();
926            let lat2_rad = other.latitude.to_radians();
927            let dlat_rad = (other.latitude - self.latitude).to_radians();
928            let dlon_rad = (other.longitude - self.longitude).to_radians();
929
930            let a = (dlat_rad / 2.0).sin().powi(2)
931                + lat1_rad.cos() * lat2_rad.cos() * (dlon_rad / 2.0).sin().powi(2);
932
933            let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
934            EARTH_RADIUS_M * c
935        }
936
937        /// Calculate bearing (initial heading) from this point to another in degrees
938        pub fn bearing_to(&self, other: &GeoPoint) -> f64 {
939            let lat1_rad = self.latitude.to_radians();
940            let lat2_rad = other.latitude.to_radians();
941            let dlon_rad = (other.longitude - self.longitude).to_radians();
942
943            let y = dlon_rad.sin() * lat2_rad.cos();
944            let x =
945                lat1_rad.cos() * lat2_rad.sin() - lat1_rad.sin() * lat2_rad.cos() * dlon_rad.cos();
946
947            let bearing_rad = y.atan2(x);
948            (bearing_rad.to_degrees() + 360.0) % 360.0
949        }
950
951        /// Calculate destination point given distance and bearing
952        pub fn destination_point(&self, distance_m: f64, bearing_deg: f64) -> GeoPoint {
953            const EARTH_RADIUS_M: f64 = 6_371_000.0;
954
955            let lat1_rad = self.latitude.to_radians();
956            let lon1_rad = self.longitude.to_radians();
957            let bearing_rad = bearing_deg.to_radians();
958            let angular_distance = distance_m / EARTH_RADIUS_M;
959
960            let lat2_rad = (lat1_rad.sin() * angular_distance.cos()
961                + lat1_rad.cos() * angular_distance.sin() * bearing_rad.cos())
962            .asin();
963
964            let lon2_rad = lon1_rad
965                + (bearing_rad.sin() * angular_distance.sin() * lat1_rad.cos())
966                    .atan2(angular_distance.cos() - lat1_rad.sin() * lat2_rad.sin());
967
968            GeoPoint::new(lat2_rad.to_degrees(), lon2_rad.to_degrees())
969        }
970
971        /// Convert to Point for use with spatial data structures
972        pub fn to_point(&self) -> Point {
973            if let Some(alt) = self.altitude {
974                Point::new(vec![self.longitude, self.latitude, alt])
975            } else {
976                Point::new(vec![self.longitude, self.latitude])
977            }
978        }
979
980        /// Check if point is within geographic bounds
981        pub fn is_within_bounds(&self, bounds: &GeoBounds) -> bool {
982            self.latitude >= bounds.south
983                && self.latitude <= bounds.north
984                && self.longitude >= bounds.west
985                && self.longitude <= bounds.east
986        }
987
988        /// Convert to Web Mercator projection (EPSG:3857)
989        pub fn to_web_mercator(&self) -> Point {
990            const EARTH_RADIUS: f64 = 6_378_137.0;
991
992            let x = self.longitude.to_radians() * EARTH_RADIUS;
993            let y = ((PI / 4.0 + self.latitude.to_radians() / 2.0).tan().ln()) * EARTH_RADIUS;
994
995            Point::new(vec![x, y])
996        }
997    }
998
999    /// Geographic bounding box
1000    #[derive(Debug, Clone, PartialEq)]
1001    pub struct GeoBounds {
1002        pub north: f64,
1003        pub south: f64,
1004        pub east: f64,
1005        pub west: f64,
1006    }
1007
1008    impl GeoBounds {
1009        /// Create new geographic bounds
1010        pub fn new(north: f64, south: f64, east: f64, west: f64) -> Self {
1011            Self {
1012                north,
1013                south,
1014                east,
1015                west,
1016            }
1017        }
1018
1019        /// Create bounds from center point and radius in meters
1020        pub fn from_center_radius(center: &GeoPoint, radius_m: f64) -> Self {
1021            const EARTH_RADIUS_M: f64 = 6_371_000.0;
1022
1023            let lat_offset = (radius_m / EARTH_RADIUS_M).to_degrees();
1024            let lon_offset =
1025                (radius_m / (EARTH_RADIUS_M * center.latitude.to_radians().cos())).to_degrees();
1026
1027            Self {
1028                north: center.latitude + lat_offset,
1029                south: center.latitude - lat_offset,
1030                east: center.longitude + lon_offset,
1031                west: center.longitude - lon_offset,
1032            }
1033        }
1034
1035        /// Check if bounds contains a point
1036        pub fn contains(&self, point: &GeoPoint) -> bool {
1037            point.is_within_bounds(self)
1038        }
1039
1040        /// Check if bounds intersect with another bounds
1041        pub fn intersects(&self, other: &GeoBounds) -> bool {
1042            !(self.east < other.west
1043                || self.west > other.east
1044                || self.north < other.south
1045                || self.south > other.north)
1046        }
1047
1048        /// Calculate area in square meters (approximate)
1049        pub fn area_square_meters(&self) -> f64 {
1050            const EARTH_RADIUS_M: f64 = 6_371_000.0;
1051
1052            let lat_range_rad = (self.north - self.south).to_radians();
1053            let lon_range_rad = (self.east - self.west).to_radians();
1054            let avg_lat_rad = ((self.north + self.south) / 2.0).to_radians();
1055
1056            EARTH_RADIUS_M.powi(2) * lat_range_rad * lon_range_rad * avg_lat_rad.cos()
1057        }
1058
1059        /// Convert to Rectangle for use with spatial data structures
1060        pub fn to_rectangle(&self) -> Rectangle {
1061            Rectangle::new(vec![self.west, self.south], vec![self.east, self.north])
1062        }
1063    }
1064
1065    /// Geographic utilities for common geographic operations
1066    pub struct GeoUtils;
1067
1068    impl GeoUtils {
1069        /// Convert degrees to radians
1070        pub fn deg_to_rad(degrees: f64) -> f64 {
1071            degrees * PI / 180.0
1072        }
1073
1074        /// Convert radians to degrees
1075        pub fn rad_to_deg(radians: f64) -> f64 {
1076            radians * 180.0 / PI
1077        }
1078
1079        /// Normalize longitude to [-180, 180] range
1080        pub fn normalize_longitude(lon: f64) -> f64 {
1081            let mut normalized = lon % 360.0;
1082            if normalized > 180.0 {
1083                normalized -= 360.0;
1084            } else if normalized < -180.0 {
1085                normalized += 360.0;
1086            }
1087            normalized
1088        }
1089
1090        /// Normalize latitude to [-90, 90] range
1091        pub fn normalize_latitude(lat: f64) -> f64 {
1092            lat.clamp(-90.0, 90.0)
1093        }
1094
1095        /// Calculate the center point of multiple geographic points
1096        pub fn centroid(points: &[GeoPoint]) -> Option<GeoPoint> {
1097            if points.is_empty() {
1098                return None;
1099            }
1100
1101            let mut x_sum = 0.0;
1102            let mut y_sum = 0.0;
1103            let mut z_sum = 0.0;
1104
1105            for point in points {
1106                let lat_rad = point.latitude.to_radians();
1107                let lon_rad = point.longitude.to_radians();
1108
1109                x_sum += lat_rad.cos() * lon_rad.cos();
1110                y_sum += lat_rad.cos() * lon_rad.sin();
1111                z_sum += lat_rad.sin();
1112            }
1113
1114            let count = points.len() as f64;
1115            let x_avg = x_sum / count;
1116            let y_avg = y_sum / count;
1117            let z_avg = z_sum / count;
1118
1119            let lon_rad = y_avg.atan2(x_avg);
1120            let hyp = (x_avg * x_avg + y_avg * y_avg).sqrt();
1121            let lat_rad = z_avg.atan2(hyp);
1122
1123            Some(GeoPoint::new(lat_rad.to_degrees(), lon_rad.to_degrees()))
1124        }
1125
1126        /// Calculate polygon area using the spherical excess method (in square meters)
1127        pub fn polygon_area(vertices: &[GeoPoint]) -> f64 {
1128            if vertices.len() < 3 {
1129                return 0.0;
1130            }
1131
1132            const EARTH_RADIUS_M: f64 = 6_371_000.0;
1133            let mut area = 0.0;
1134
1135            for i in 0..vertices.len() {
1136                let j = (i + 1) % vertices.len();
1137                let lat1 = vertices[i].latitude.to_radians();
1138                let lat2 = vertices[j].latitude.to_radians();
1139                let lon_diff = (vertices[j].longitude - vertices[i].longitude).to_radians();
1140
1141                area += lon_diff * (2.0 + lat1.sin() + lat2.sin());
1142            }
1143
1144            (area.abs() / 2.0) * EARTH_RADIUS_M.powi(2)
1145        }
1146
1147        /// Check if a point is inside a polygon using ray casting algorithm
1148        pub fn point_in_polygon(point: &GeoPoint, polygon: &[GeoPoint]) -> bool {
1149            if polygon.len() < 3 {
1150                return false;
1151            }
1152
1153            let mut inside = false;
1154            let mut j = polygon.len() - 1;
1155
1156            for i in 0..polygon.len() {
1157                if ((polygon[i].latitude > point.latitude)
1158                    != (polygon[j].latitude > point.latitude))
1159                    && (point.longitude
1160                        < (polygon[j].longitude - polygon[i].longitude)
1161                            * (point.latitude - polygon[i].latitude)
1162                            / (polygon[j].latitude - polygon[i].latitude)
1163                            + polygon[i].longitude)
1164                {
1165                    inside = !inside;
1166                }
1167                j = i;
1168            }
1169
1170            inside
1171        }
1172
1173        /// Find the closest point on a line segment to a given point
1174        pub fn closest_point_on_line(
1175            point: &GeoPoint,
1176            line_start: &GeoPoint,
1177            line_end: &GeoPoint,
1178        ) -> GeoPoint {
1179            let lat_start = line_start.latitude.to_radians();
1180            let lon_start = line_start.longitude.to_radians();
1181            let lat_end = line_end.latitude.to_radians();
1182            let lon_end = line_end.longitude.to_radians();
1183            let lat_point = point.latitude.to_radians();
1184            let lon_point = point.longitude.to_radians();
1185
1186            // Convert to Cartesian coordinates
1187            let x1 = lat_start.cos() * lon_start.cos();
1188            let y1 = lat_start.cos() * lon_start.sin();
1189            let z1 = lat_start.sin();
1190
1191            let x2 = lat_end.cos() * lon_end.cos();
1192            let y2 = lat_end.cos() * lon_end.sin();
1193            let z2 = lat_end.sin();
1194
1195            let x0 = lat_point.cos() * lon_point.cos();
1196            let y0 = lat_point.cos() * lon_point.sin();
1197            let z0 = lat_point.sin();
1198
1199            // Project point onto line
1200            let dot_product = x0 * (x2 - x1) + y0 * (y2 - y1) + z0 * (z2 - z1);
1201            let line_length_sq = (x2 - x1).powi(2) + (y2 - y1).powi(2) + (z2 - z1).powi(2);
1202
1203            let t = if line_length_sq == 0.0 {
1204                0.0
1205            } else {
1206                (dot_product / line_length_sq).clamp(0.0, 1.0)
1207            };
1208
1209            let closest_x = x1 + t * (x2 - x1);
1210            let closest_y = y1 + t * (y2 - y1);
1211            let closest_z = z1 + t * (z2 - z1);
1212
1213            // Convert back to lat/lon
1214            let closest_lon = closest_y.atan2(closest_x).to_degrees();
1215            let closest_lat = closest_z
1216                .atan2((closest_x.powi(2) + closest_y.powi(2)).sqrt())
1217                .to_degrees();
1218
1219            GeoPoint::new(closest_lat, closest_lon)
1220        }
1221
1222        /// Calculate great circle distance between two points (same as Haversine)
1223        pub fn great_circle_distance(point1: &GeoPoint, point2: &GeoPoint) -> f64 {
1224            point1.haversine_distance(point2)
1225        }
1226
1227        /// Convert decimal degrees to degrees, minutes, seconds format
1228        pub fn decimal_to_dms(decimal: f64) -> (i32, i32, f64) {
1229            let degrees = decimal.trunc() as i32;
1230            let minutes_float = (decimal.abs() - degrees.abs() as f64) * 60.0;
1231            let minutes = minutes_float.trunc() as i32;
1232            let seconds = (minutes_float - minutes as f64) * 60.0;
1233            (degrees, minutes, seconds)
1234        }
1235
1236        /// Convert degrees, minutes, seconds to decimal degrees
1237        pub fn dms_to_decimal(degrees: i32, minutes: i32, seconds: f64) -> f64 {
1238            let sign = if degrees < 0 { -1.0 } else { 1.0 };
1239            degrees.abs() as f64 + minutes as f64 / 60.0 + seconds / 3600.0 * sign
1240        }
1241    }
1242
1243    #[allow(non_snake_case)]
1244    #[cfg(test)]
1245    mod geo_tests {
1246        use super::*;
1247
1248        #[test]
1249        fn test_geopoint_haversine_distance() {
1250            let london = GeoPoint::new(51.5074, -0.1278);
1251            let paris = GeoPoint::new(48.8566, 2.3522);
1252
1253            let distance = london.haversine_distance(&paris);
1254            // Distance between London and Paris is approximately 344 km
1255            assert!((distance - 344_000.0).abs() < 10_000.0);
1256        }
1257
1258        #[test]
1259        fn test_geopoint_bearing() {
1260            let start = GeoPoint::new(51.0, 0.0);
1261            let end = GeoPoint::new(52.0, 1.0);
1262
1263            let bearing = start.bearing_to(&end);
1264            assert!((0.0..360.0).contains(&bearing));
1265        }
1266
1267        #[test]
1268        fn test_geopoint_destination() {
1269            let start = GeoPoint::new(51.0, 0.0);
1270            let destination = start.destination_point(100_000.0, 90.0); // 100km east
1271
1272            // Should be roughly at the same latitude, but further east
1273            assert!((destination.latitude - start.latitude).abs() < 0.1);
1274            assert!(destination.longitude > start.longitude);
1275        }
1276
1277        #[test]
1278        fn test_geobounds() {
1279            let bounds = GeoBounds::new(52.0, 50.0, 2.0, 0.0);
1280            let point_inside = GeoPoint::new(51.0, 1.0);
1281            let point_outside = GeoPoint::new(53.0, 3.0);
1282
1283            assert!(bounds.contains(&point_inside));
1284            assert!(!bounds.contains(&point_outside));
1285        }
1286
1287        #[test]
1288        fn test_geoutils_centroid() {
1289            let points = vec![
1290                GeoPoint::new(0.0, 0.0),
1291                GeoPoint::new(1.0, 0.0),
1292                GeoPoint::new(0.0, 1.0),
1293                GeoPoint::new(1.0, 1.0),
1294            ];
1295
1296            let centroid = GeoUtils::centroid(&points).expect("operation should succeed");
1297            assert!((centroid.latitude - 0.5).abs() < 0.1);
1298            assert!((centroid.longitude - 0.5).abs() < 0.1);
1299        }
1300
1301        #[test]
1302        fn test_point_in_polygon() {
1303            let polygon = vec![
1304                GeoPoint::new(0.0, 0.0),
1305                GeoPoint::new(2.0, 0.0),
1306                GeoPoint::new(2.0, 2.0),
1307                GeoPoint::new(0.0, 2.0),
1308            ];
1309
1310            let inside_point = GeoPoint::new(1.0, 1.0);
1311            let outside_point = GeoPoint::new(3.0, 3.0);
1312
1313            assert!(GeoUtils::point_in_polygon(&inside_point, &polygon));
1314            assert!(!GeoUtils::point_in_polygon(&outside_point, &polygon));
1315        }
1316
1317        #[test]
1318        fn test_normalize_coordinates() {
1319            assert_eq!(GeoUtils::normalize_longitude(380.0), 20.0);
1320            assert_eq!(GeoUtils::normalize_longitude(-200.0), 160.0);
1321            assert_eq!(GeoUtils::normalize_latitude(100.0), 90.0);
1322            assert_eq!(GeoUtils::normalize_latitude(-100.0), -90.0);
1323        }
1324
1325        #[test]
1326        fn test_dms_conversion() {
1327            let decimal = 51.5074;
1328            let (degrees, minutes, seconds) = GeoUtils::decimal_to_dms(decimal);
1329            let converted_back = GeoUtils::dms_to_decimal(degrees, minutes, seconds);
1330
1331            assert!((decimal - converted_back).abs() < 0.0001);
1332        }
1333    }
1334}
1335
1336#[allow(non_snake_case)]
1337#[cfg(test)]
1338mod tests {
1339    use super::*;
1340
1341    #[test]
1342    fn test_point_operations() {
1343        let p1 = Point::new(vec![1.0, 2.0, 3.0]);
1344        let p2 = Point::new(vec![4.0, 5.0, 6.0]);
1345
1346        assert_eq!(p1.dimension(), 3);
1347        assert_eq!(p1.distance_squared(&p2), 27.0); // (4-1)² + (5-2)² + (6-3)² = 9+9+9 = 27
1348        assert_eq!(p1.distance(&p2), 27.0_f64.sqrt());
1349    }
1350
1351    #[test]
1352    fn test_rectangle_operations() {
1353        let rect = Rectangle::new(vec![0.0, 0.0], vec![10.0, 10.0]);
1354        let point_inside = Point::new(vec![5.0, 5.0]);
1355        let point_outside = Point::new(vec![15.0, 15.0]);
1356
1357        assert!(rect.contains(&point_inside));
1358        assert!(!rect.contains(&point_outside));
1359        assert_eq!(rect.area(), 100.0);
1360
1361        let other_rect = Rectangle::new(vec![5.0, 5.0], vec![15.0, 15.0]);
1362        assert!(rect.intersects(&other_rect));
1363
1364        let union = rect.union(&other_rect);
1365        assert_eq!(union.min_bounds, vec![0.0, 0.0]);
1366        assert_eq!(union.max_bounds, vec![15.0, 15.0]);
1367    }
1368
1369    #[test]
1370    fn test_kdtree() {
1371        let points = vec![
1372            (Point::new(vec![2.0, 3.0]), 0),
1373            (Point::new(vec![5.0, 4.0]), 1),
1374            (Point::new(vec![9.0, 6.0]), 2),
1375            (Point::new(vec![4.0, 7.0]), 3),
1376            (Point::new(vec![8.0, 1.0]), 4),
1377            (Point::new(vec![7.0, 2.0]), 5),
1378        ];
1379
1380        let kd_tree = KdTree::from_points(points);
1381
1382        let query = Point::new(vec![5.0, 5.0]);
1383        let result = kd_tree.nearest_neighbor(&query);
1384
1385        assert!(result.is_some());
1386        let (_, data, _) = result.expect("operation should succeed");
1387        // Should find one of the nearby points
1388        assert!(data <= 5);
1389
1390        // Test range query
1391        let range = Rectangle::new(vec![0.0, 0.0], vec![6.0, 6.0]);
1392        let range_results = kd_tree.range_query(&range);
1393        assert!(!range_results.is_empty());
1394    }
1395
1396    #[test]
1397    fn test_quadtree() {
1398        let bounds = Rectangle::new(vec![0.0, 0.0], vec![100.0, 100.0]);
1399        let mut quad_tree = QuadTree::new(bounds, 4, 6);
1400
1401        // Insert some points
1402        quad_tree.insert(Point::new(vec![10.0, 10.0]), 0);
1403        quad_tree.insert(Point::new(vec![20.0, 20.0]), 1);
1404        quad_tree.insert(Point::new(vec![80.0, 80.0]), 2);
1405        quad_tree.insert(Point::new(vec![90.0, 90.0]), 3);
1406
1407        // Query a region
1408        let query_bounds = Rectangle::new(vec![5.0, 5.0], vec![25.0, 25.0]);
1409        let results = quad_tree.query(&query_bounds);
1410
1411        assert_eq!(results.len(), 2); // Should find points 0 and 1
1412
1413        // Test nearest neighbor
1414        let query_point = Point::new(vec![15.0, 15.0]);
1415        let nearest = quad_tree.nearest_neighbor(&query_point, None);
1416        assert!(nearest.is_some());
1417    }
1418
1419    #[test]
1420    fn test_spatial_hash() {
1421        let bounds = Rectangle::new(vec![0.0, 0.0], vec![100.0, 100.0]);
1422        let mut spatial_hash = SpatialHash::new(bounds, 10.0);
1423
1424        // Insert some points
1425        spatial_hash.insert(Point::new(vec![15.0, 15.0]), 0);
1426        spatial_hash.insert(Point::new(vec![25.0, 25.0]), 1);
1427        spatial_hash.insert(Point::new(vec![85.0, 85.0]), 2);
1428
1429        // Query radius
1430        let center = Point::new(vec![20.0, 20.0]);
1431        let results = spatial_hash.query_radius(&center, 10.0);
1432
1433        assert!(!results.is_empty());
1434
1435        let stats = spatial_hash.stats();
1436        assert_eq!(stats.total_points, 3);
1437    }
1438
1439    #[test]
1440    fn test_octree() {
1441        let bounds = Rectangle::new(vec![0.0, 0.0, 0.0], vec![100.0, 100.0, 100.0]);
1442        let mut oct_tree = OctTree::new(bounds, 4, 6);
1443
1444        // Insert some 3D points
1445        oct_tree.insert(Point::new(vec![10.0, 10.0, 10.0]), 0);
1446        oct_tree.insert(Point::new(vec![20.0, 20.0, 20.0]), 1);
1447        oct_tree.insert(Point::new(vec![80.0, 80.0, 80.0]), 2);
1448
1449        // Query a 3D region
1450        let query_bounds = Rectangle::new(vec![5.0, 5.0, 5.0], vec![25.0, 25.0, 25.0]);
1451        let results = oct_tree.query(&query_bounds);
1452
1453        assert_eq!(results.len(), 2); // Should find points 0 and 1
1454    }
1455}