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                .unwrap()
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().unwrap().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() || child_best.2 * child_best.2 < best.as_ref().unwrap().2 {
600                        best = Some((child_best.0, child_best.1, child_best.2 * child_best.2));
601                    }
602                }
603            }
604        }
605
606        best.map(|(p, d, dist_sq)| (p, d, dist_sq.sqrt()))
607    }
608}
609
610/// Octree for 3D spatial indexing
611pub struct OctTree {
612    root: OctNode,
613    max_points: usize,
614    max_depth: usize,
615}
616
617#[derive(Debug)]
618struct OctNode {
619    bounds: Rectangle,
620    points: Vec<(Point, usize)>,
621    children: Option<[Box<OctNode>; 8]>,
622    depth: usize,
623}
624
625impl OctTree {
626    pub fn new(bounds: Rectangle, max_points: usize, max_depth: usize) -> Self {
627        assert_eq!(bounds.dimension(), 3, "OctTree only supports 3D");
628
629        Self {
630            root: OctNode {
631                bounds,
632                points: Vec::new(),
633                children: None,
634                depth: 0,
635            },
636            max_points,
637            max_depth,
638        }
639    }
640
641    pub fn insert(&mut self, point: Point, data: usize) {
642        assert_eq!(point.dimension(), 3, "OctTree only supports 3D points");
643        self.root
644            .insert(point, data, self.max_points, self.max_depth);
645    }
646
647    pub fn query(&self, query_bounds: &Rectangle) -> Vec<(Point, usize)> {
648        let mut results = Vec::new();
649        self.root.query(query_bounds, &mut results);
650        results
651    }
652}
653
654impl OctNode {
655    fn insert(&mut self, point: Point, data: usize, max_points: usize, max_depth: usize) {
656        if !self.bounds.contains(&point) {
657            return;
658        }
659
660        if self.children.is_none() {
661            self.points.push((point, data));
662
663            if self.points.len() > max_points && self.depth < max_depth {
664                self.subdivide();
665
666                // Redistribute points to children
667                let points = std::mem::take(&mut self.points);
668                for (p, d) in points {
669                    self.insert_into_children(p, d, max_points, max_depth);
670                }
671            }
672        } else {
673            self.insert_into_children(point, data, max_points, max_depth);
674        }
675    }
676
677    fn subdivide(&mut self) {
678        let mid_x = (self.bounds.min_bounds[0] + self.bounds.max_bounds[0]) / 2.0;
679        let mid_y = (self.bounds.min_bounds[1] + self.bounds.max_bounds[1]) / 2.0;
680        let mid_z = (self.bounds.min_bounds[2] + self.bounds.max_bounds[2]) / 2.0;
681
682        // Create 8 octants
683        let mut children = Vec::with_capacity(8);
684
685        for &z_low in &[true, false] {
686            for &y_low in &[true, false] {
687                for &x_low in &[true, false] {
688                    let min_bounds = vec![
689                        if x_low {
690                            self.bounds.min_bounds[0]
691                        } else {
692                            mid_x
693                        },
694                        if y_low {
695                            self.bounds.min_bounds[1]
696                        } else {
697                            mid_y
698                        },
699                        if z_low {
700                            self.bounds.min_bounds[2]
701                        } else {
702                            mid_z
703                        },
704                    ];
705                    let max_bounds = vec![
706                        if x_low {
707                            mid_x
708                        } else {
709                            self.bounds.max_bounds[0]
710                        },
711                        if y_low {
712                            mid_y
713                        } else {
714                            self.bounds.max_bounds[1]
715                        },
716                        if z_low {
717                            mid_z
718                        } else {
719                            self.bounds.max_bounds[2]
720                        },
721                    ];
722
723                    children.push(Box::new(OctNode {
724                        bounds: Rectangle::new(min_bounds, max_bounds),
725                        points: Vec::new(),
726                        children: None,
727                        depth: self.depth + 1,
728                    }));
729                }
730            }
731        }
732
733        self.children = Some(children.try_into().unwrap());
734    }
735
736    fn insert_into_children(
737        &mut self,
738        point: Point,
739        data: usize,
740        max_points: usize,
741        max_depth: usize,
742    ) {
743        if let Some(children) = &mut self.children {
744            for child in children.iter_mut() {
745                child.insert(point.clone(), data, max_points, max_depth);
746            }
747        }
748    }
749
750    fn query(&self, query_bounds: &Rectangle, results: &mut Vec<(Point, usize)>) {
751        if !self.bounds.intersects(query_bounds) {
752            return;
753        }
754
755        for (point, data) in &self.points {
756            if query_bounds.contains(point) {
757                results.push((point.clone(), *data));
758            }
759        }
760
761        if let Some(children) = &self.children {
762            for child in children.iter() {
763                child.query(query_bounds, results);
764            }
765        }
766    }
767}
768
769/// Spatial hash for efficient spatial queries with grid-based indexing
770pub struct SpatialHash {
771    grid: std::collections::HashMap<(i32, i32), Vec<(Point, usize)>>,
772    cell_size: f64,
773    bounds: Rectangle,
774}
775
776impl SpatialHash {
777    pub fn new(bounds: Rectangle, cell_size: f64) -> Self {
778        assert_eq!(bounds.dimension(), 2, "SpatialHash only supports 2D");
779
780        Self {
781            grid: std::collections::HashMap::new(),
782            cell_size,
783            bounds,
784        }
785    }
786
787    fn hash_point(&self, point: &Point) -> (i32, i32) {
788        let x =
789            ((point.coordinates[0] - self.bounds.min_bounds[0]) / self.cell_size).floor() as i32;
790        let y =
791            ((point.coordinates[1] - self.bounds.min_bounds[1]) / self.cell_size).floor() as i32;
792        (x, y)
793    }
794
795    pub fn insert(&mut self, point: Point, data: usize) {
796        let hash = self.hash_point(&point);
797        self.grid.entry(hash).or_default().push((point, data));
798    }
799
800    pub fn query_radius(&self, center: &Point, radius: f64) -> Vec<(Point, usize)> {
801        let mut results = Vec::new();
802        let radius_sq = radius * radius;
803
804        // Calculate grid cells that might contain points within radius
805        let cells_to_check = ((radius / self.cell_size).ceil() as i32) + 1;
806        let center_hash = self.hash_point(center);
807
808        for dx in -cells_to_check..=cells_to_check {
809            for dy in -cells_to_check..=cells_to_check {
810                let hash = (center_hash.0 + dx, center_hash.1 + dy);
811
812                if let Some(points) = self.grid.get(&hash) {
813                    for (point, data) in points {
814                        if center.distance_squared(point) <= radius_sq {
815                            results.push((point.clone(), *data));
816                        }
817                    }
818                }
819            }
820        }
821
822        results
823    }
824
825    pub fn clear(&mut self) {
826        self.grid.clear();
827    }
828
829    pub fn stats(&self) -> SpatialHashStats {
830        let total_points: usize = self.grid.values().map(|v| v.len()).sum();
831        let occupied_cells = self.grid.len();
832        let max_points_per_cell = self.grid.values().map(|v| v.len()).max().unwrap_or(0);
833        let avg_points_per_cell = if occupied_cells > 0 {
834            total_points as f64 / occupied_cells as f64
835        } else {
836            0.0
837        };
838
839        SpatialHashStats {
840            total_points,
841            occupied_cells,
842            max_points_per_cell,
843            avg_points_per_cell,
844            cell_size: self.cell_size,
845        }
846    }
847}
848
849#[derive(Debug, Clone)]
850pub struct SpatialHashStats {
851    pub total_points: usize,
852    pub occupied_cells: usize,
853    pub max_points_per_cell: usize,
854    pub avg_points_per_cell: f64,
855    pub cell_size: f64,
856}
857
858/// Geographic utilities for working with spatial coordinates and geographic data
859pub mod geographic {
860    use super::{Point, Rectangle};
861    use std::f64::consts::PI;
862
863    /// Coordinate system types
864    #[derive(Debug, Clone, PartialEq)]
865    pub enum CoordinateSystem {
866        /// Latitude/Longitude in degrees (WGS84)
867        LatLon,
868        /// Universal Transverse Mercator
869        UTM { zone: u8, hemisphere: Hemisphere },
870        /// Web Mercator (used by web mapping services)
871        WebMercator,
872        /// Custom coordinate system with EPSG code
873        EPSG(u32),
874    }
875
876    #[derive(Debug, Clone, PartialEq)]
877    pub enum Hemisphere {
878        North,
879        South,
880    }
881
882    /// Geographic point with latitude and longitude
883    #[derive(Debug, Clone, PartialEq)]
884    pub struct GeoPoint {
885        pub latitude: f64,
886        pub longitude: f64,
887        pub altitude: Option<f64>,
888        pub coordinate_system: CoordinateSystem,
889    }
890
891    impl GeoPoint {
892        /// Create a new geographic point
893        pub fn new(latitude: f64, longitude: f64) -> Self {
894            Self {
895                latitude,
896                longitude,
897                altitude: None,
898                coordinate_system: CoordinateSystem::LatLon,
899            }
900        }
901
902        /// Create a geographic point with altitude
903        pub fn with_altitude(latitude: f64, longitude: f64, altitude: f64) -> Self {
904            Self {
905                latitude,
906                longitude,
907                altitude: Some(altitude),
908                coordinate_system: CoordinateSystem::LatLon,
909            }
910        }
911
912        /// Set coordinate system
913        pub fn with_coordinate_system(mut self, coord_sys: CoordinateSystem) -> Self {
914            self.coordinate_system = coord_sys;
915            self
916        }
917
918        /// Calculate Haversine distance between two geographic points in meters
919        pub fn haversine_distance(&self, other: &GeoPoint) -> f64 {
920            const EARTH_RADIUS_M: f64 = 6_371_000.0;
921
922            let lat1_rad = self.latitude.to_radians();
923            let lat2_rad = other.latitude.to_radians();
924            let dlat_rad = (other.latitude - self.latitude).to_radians();
925            let dlon_rad = (other.longitude - self.longitude).to_radians();
926
927            let a = (dlat_rad / 2.0).sin().powi(2)
928                + lat1_rad.cos() * lat2_rad.cos() * (dlon_rad / 2.0).sin().powi(2);
929
930            let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
931            EARTH_RADIUS_M * c
932        }
933
934        /// Calculate bearing (initial heading) from this point to another in degrees
935        pub fn bearing_to(&self, other: &GeoPoint) -> f64 {
936            let lat1_rad = self.latitude.to_radians();
937            let lat2_rad = other.latitude.to_radians();
938            let dlon_rad = (other.longitude - self.longitude).to_radians();
939
940            let y = dlon_rad.sin() * lat2_rad.cos();
941            let x =
942                lat1_rad.cos() * lat2_rad.sin() - lat1_rad.sin() * lat2_rad.cos() * dlon_rad.cos();
943
944            let bearing_rad = y.atan2(x);
945            (bearing_rad.to_degrees() + 360.0) % 360.0
946        }
947
948        /// Calculate destination point given distance and bearing
949        pub fn destination_point(&self, distance_m: f64, bearing_deg: f64) -> GeoPoint {
950            const EARTH_RADIUS_M: f64 = 6_371_000.0;
951
952            let lat1_rad = self.latitude.to_radians();
953            let lon1_rad = self.longitude.to_radians();
954            let bearing_rad = bearing_deg.to_radians();
955            let angular_distance = distance_m / EARTH_RADIUS_M;
956
957            let lat2_rad = (lat1_rad.sin() * angular_distance.cos()
958                + lat1_rad.cos() * angular_distance.sin() * bearing_rad.cos())
959            .asin();
960
961            let lon2_rad = lon1_rad
962                + (bearing_rad.sin() * angular_distance.sin() * lat1_rad.cos())
963                    .atan2(angular_distance.cos() - lat1_rad.sin() * lat2_rad.sin());
964
965            GeoPoint::new(lat2_rad.to_degrees(), lon2_rad.to_degrees())
966        }
967
968        /// Convert to Point for use with spatial data structures
969        pub fn to_point(&self) -> Point {
970            if let Some(alt) = self.altitude {
971                Point::new(vec![self.longitude, self.latitude, alt])
972            } else {
973                Point::new(vec![self.longitude, self.latitude])
974            }
975        }
976
977        /// Check if point is within geographic bounds
978        pub fn is_within_bounds(&self, bounds: &GeoBounds) -> bool {
979            self.latitude >= bounds.south
980                && self.latitude <= bounds.north
981                && self.longitude >= bounds.west
982                && self.longitude <= bounds.east
983        }
984
985        /// Convert to Web Mercator projection (EPSG:3857)
986        pub fn to_web_mercator(&self) -> Point {
987            const EARTH_RADIUS: f64 = 6_378_137.0;
988
989            let x = self.longitude.to_radians() * EARTH_RADIUS;
990            let y = ((PI / 4.0 + self.latitude.to_radians() / 2.0).tan().ln()) * EARTH_RADIUS;
991
992            Point::new(vec![x, y])
993        }
994    }
995
996    /// Geographic bounding box
997    #[derive(Debug, Clone, PartialEq)]
998    pub struct GeoBounds {
999        pub north: f64,
1000        pub south: f64,
1001        pub east: f64,
1002        pub west: f64,
1003    }
1004
1005    impl GeoBounds {
1006        /// Create new geographic bounds
1007        pub fn new(north: f64, south: f64, east: f64, west: f64) -> Self {
1008            Self {
1009                north,
1010                south,
1011                east,
1012                west,
1013            }
1014        }
1015
1016        /// Create bounds from center point and radius in meters
1017        pub fn from_center_radius(center: &GeoPoint, radius_m: f64) -> Self {
1018            const EARTH_RADIUS_M: f64 = 6_371_000.0;
1019
1020            let lat_offset = (radius_m / EARTH_RADIUS_M).to_degrees();
1021            let lon_offset =
1022                (radius_m / (EARTH_RADIUS_M * center.latitude.to_radians().cos())).to_degrees();
1023
1024            Self {
1025                north: center.latitude + lat_offset,
1026                south: center.latitude - lat_offset,
1027                east: center.longitude + lon_offset,
1028                west: center.longitude - lon_offset,
1029            }
1030        }
1031
1032        /// Check if bounds contains a point
1033        pub fn contains(&self, point: &GeoPoint) -> bool {
1034            point.is_within_bounds(self)
1035        }
1036
1037        /// Check if bounds intersect with another bounds
1038        pub fn intersects(&self, other: &GeoBounds) -> bool {
1039            !(self.east < other.west
1040                || self.west > other.east
1041                || self.north < other.south
1042                || self.south > other.north)
1043        }
1044
1045        /// Calculate area in square meters (approximate)
1046        pub fn area_square_meters(&self) -> f64 {
1047            const EARTH_RADIUS_M: f64 = 6_371_000.0;
1048
1049            let lat_range_rad = (self.north - self.south).to_radians();
1050            let lon_range_rad = (self.east - self.west).to_radians();
1051            let avg_lat_rad = ((self.north + self.south) / 2.0).to_radians();
1052
1053            EARTH_RADIUS_M.powi(2) * lat_range_rad * lon_range_rad * avg_lat_rad.cos()
1054        }
1055
1056        /// Convert to Rectangle for use with spatial data structures
1057        pub fn to_rectangle(&self) -> Rectangle {
1058            Rectangle::new(vec![self.west, self.south], vec![self.east, self.north])
1059        }
1060    }
1061
1062    /// Geographic utilities for common geographic operations
1063    pub struct GeoUtils;
1064
1065    impl GeoUtils {
1066        /// Convert degrees to radians
1067        pub fn deg_to_rad(degrees: f64) -> f64 {
1068            degrees * PI / 180.0
1069        }
1070
1071        /// Convert radians to degrees
1072        pub fn rad_to_deg(radians: f64) -> f64 {
1073            radians * 180.0 / PI
1074        }
1075
1076        /// Normalize longitude to [-180, 180] range
1077        pub fn normalize_longitude(lon: f64) -> f64 {
1078            let mut normalized = lon % 360.0;
1079            if normalized > 180.0 {
1080                normalized -= 360.0;
1081            } else if normalized < -180.0 {
1082                normalized += 360.0;
1083            }
1084            normalized
1085        }
1086
1087        /// Normalize latitude to [-90, 90] range
1088        pub fn normalize_latitude(lat: f64) -> f64 {
1089            lat.clamp(-90.0, 90.0)
1090        }
1091
1092        /// Calculate the center point of multiple geographic points
1093        pub fn centroid(points: &[GeoPoint]) -> Option<GeoPoint> {
1094            if points.is_empty() {
1095                return None;
1096            }
1097
1098            let mut x_sum = 0.0;
1099            let mut y_sum = 0.0;
1100            let mut z_sum = 0.0;
1101
1102            for point in points {
1103                let lat_rad = point.latitude.to_radians();
1104                let lon_rad = point.longitude.to_radians();
1105
1106                x_sum += lat_rad.cos() * lon_rad.cos();
1107                y_sum += lat_rad.cos() * lon_rad.sin();
1108                z_sum += lat_rad.sin();
1109            }
1110
1111            let count = points.len() as f64;
1112            let x_avg = x_sum / count;
1113            let y_avg = y_sum / count;
1114            let z_avg = z_sum / count;
1115
1116            let lon_rad = y_avg.atan2(x_avg);
1117            let hyp = (x_avg * x_avg + y_avg * y_avg).sqrt();
1118            let lat_rad = z_avg.atan2(hyp);
1119
1120            Some(GeoPoint::new(lat_rad.to_degrees(), lon_rad.to_degrees()))
1121        }
1122
1123        /// Calculate polygon area using the spherical excess method (in square meters)
1124        pub fn polygon_area(vertices: &[GeoPoint]) -> f64 {
1125            if vertices.len() < 3 {
1126                return 0.0;
1127            }
1128
1129            const EARTH_RADIUS_M: f64 = 6_371_000.0;
1130            let mut area = 0.0;
1131
1132            for i in 0..vertices.len() {
1133                let j = (i + 1) % vertices.len();
1134                let lat1 = vertices[i].latitude.to_radians();
1135                let lat2 = vertices[j].latitude.to_radians();
1136                let lon_diff = (vertices[j].longitude - vertices[i].longitude).to_radians();
1137
1138                area += lon_diff * (2.0 + lat1.sin() + lat2.sin());
1139            }
1140
1141            (area.abs() / 2.0) * EARTH_RADIUS_M.powi(2)
1142        }
1143
1144        /// Check if a point is inside a polygon using ray casting algorithm
1145        pub fn point_in_polygon(point: &GeoPoint, polygon: &[GeoPoint]) -> bool {
1146            if polygon.len() < 3 {
1147                return false;
1148            }
1149
1150            let mut inside = false;
1151            let mut j = polygon.len() - 1;
1152
1153            for i in 0..polygon.len() {
1154                if ((polygon[i].latitude > point.latitude)
1155                    != (polygon[j].latitude > point.latitude))
1156                    && (point.longitude
1157                        < (polygon[j].longitude - polygon[i].longitude)
1158                            * (point.latitude - polygon[i].latitude)
1159                            / (polygon[j].latitude - polygon[i].latitude)
1160                            + polygon[i].longitude)
1161                {
1162                    inside = !inside;
1163                }
1164                j = i;
1165            }
1166
1167            inside
1168        }
1169
1170        /// Find the closest point on a line segment to a given point
1171        pub fn closest_point_on_line(
1172            point: &GeoPoint,
1173            line_start: &GeoPoint,
1174            line_end: &GeoPoint,
1175        ) -> GeoPoint {
1176            let lat_start = line_start.latitude.to_radians();
1177            let lon_start = line_start.longitude.to_radians();
1178            let lat_end = line_end.latitude.to_radians();
1179            let lon_end = line_end.longitude.to_radians();
1180            let lat_point = point.latitude.to_radians();
1181            let lon_point = point.longitude.to_radians();
1182
1183            // Convert to Cartesian coordinates
1184            let x1 = lat_start.cos() * lon_start.cos();
1185            let y1 = lat_start.cos() * lon_start.sin();
1186            let z1 = lat_start.sin();
1187
1188            let x2 = lat_end.cos() * lon_end.cos();
1189            let y2 = lat_end.cos() * lon_end.sin();
1190            let z2 = lat_end.sin();
1191
1192            let x0 = lat_point.cos() * lon_point.cos();
1193            let y0 = lat_point.cos() * lon_point.sin();
1194            let z0 = lat_point.sin();
1195
1196            // Project point onto line
1197            let dot_product = x0 * (x2 - x1) + y0 * (y2 - y1) + z0 * (z2 - z1);
1198            let line_length_sq = (x2 - x1).powi(2) + (y2 - y1).powi(2) + (z2 - z1).powi(2);
1199
1200            let t = if line_length_sq == 0.0 {
1201                0.0
1202            } else {
1203                (dot_product / line_length_sq).clamp(0.0, 1.0)
1204            };
1205
1206            let closest_x = x1 + t * (x2 - x1);
1207            let closest_y = y1 + t * (y2 - y1);
1208            let closest_z = z1 + t * (z2 - z1);
1209
1210            // Convert back to lat/lon
1211            let closest_lon = closest_y.atan2(closest_x).to_degrees();
1212            let closest_lat = closest_z
1213                .atan2((closest_x.powi(2) + closest_y.powi(2)).sqrt())
1214                .to_degrees();
1215
1216            GeoPoint::new(closest_lat, closest_lon)
1217        }
1218
1219        /// Calculate great circle distance between two points (same as Haversine)
1220        pub fn great_circle_distance(point1: &GeoPoint, point2: &GeoPoint) -> f64 {
1221            point1.haversine_distance(point2)
1222        }
1223
1224        /// Convert decimal degrees to degrees, minutes, seconds format
1225        pub fn decimal_to_dms(decimal: f64) -> (i32, i32, f64) {
1226            let degrees = decimal.trunc() as i32;
1227            let minutes_float = (decimal.abs() - degrees.abs() as f64) * 60.0;
1228            let minutes = minutes_float.trunc() as i32;
1229            let seconds = (minutes_float - minutes as f64) * 60.0;
1230            (degrees, minutes, seconds)
1231        }
1232
1233        /// Convert degrees, minutes, seconds to decimal degrees
1234        pub fn dms_to_decimal(degrees: i32, minutes: i32, seconds: f64) -> f64 {
1235            let sign = if degrees < 0 { -1.0 } else { 1.0 };
1236            degrees.abs() as f64 + minutes as f64 / 60.0 + seconds / 3600.0 * sign
1237        }
1238    }
1239
1240    #[allow(non_snake_case)]
1241    #[cfg(test)]
1242    mod geo_tests {
1243        use super::*;
1244
1245        #[test]
1246        fn test_geopoint_haversine_distance() {
1247            let london = GeoPoint::new(51.5074, -0.1278);
1248            let paris = GeoPoint::new(48.8566, 2.3522);
1249
1250            let distance = london.haversine_distance(&paris);
1251            // Distance between London and Paris is approximately 344 km
1252            assert!((distance - 344_000.0).abs() < 10_000.0);
1253        }
1254
1255        #[test]
1256        fn test_geopoint_bearing() {
1257            let start = GeoPoint::new(51.0, 0.0);
1258            let end = GeoPoint::new(52.0, 1.0);
1259
1260            let bearing = start.bearing_to(&end);
1261            assert!(bearing >= 0.0 && bearing < 360.0);
1262        }
1263
1264        #[test]
1265        fn test_geopoint_destination() {
1266            let start = GeoPoint::new(51.0, 0.0);
1267            let destination = start.destination_point(100_000.0, 90.0); // 100km east
1268
1269            // Should be roughly at the same latitude, but further east
1270            assert!((destination.latitude - start.latitude).abs() < 0.1);
1271            assert!(destination.longitude > start.longitude);
1272        }
1273
1274        #[test]
1275        fn test_geobounds() {
1276            let bounds = GeoBounds::new(52.0, 50.0, 2.0, 0.0);
1277            let point_inside = GeoPoint::new(51.0, 1.0);
1278            let point_outside = GeoPoint::new(53.0, 3.0);
1279
1280            assert!(bounds.contains(&point_inside));
1281            assert!(!bounds.contains(&point_outside));
1282        }
1283
1284        #[test]
1285        fn test_geoutils_centroid() {
1286            let points = vec![
1287                GeoPoint::new(0.0, 0.0),
1288                GeoPoint::new(1.0, 0.0),
1289                GeoPoint::new(0.0, 1.0),
1290                GeoPoint::new(1.0, 1.0),
1291            ];
1292
1293            let centroid = GeoUtils::centroid(&points).unwrap();
1294            assert!((centroid.latitude - 0.5).abs() < 0.1);
1295            assert!((centroid.longitude - 0.5).abs() < 0.1);
1296        }
1297
1298        #[test]
1299        fn test_point_in_polygon() {
1300            let polygon = vec![
1301                GeoPoint::new(0.0, 0.0),
1302                GeoPoint::new(2.0, 0.0),
1303                GeoPoint::new(2.0, 2.0),
1304                GeoPoint::new(0.0, 2.0),
1305            ];
1306
1307            let inside_point = GeoPoint::new(1.0, 1.0);
1308            let outside_point = GeoPoint::new(3.0, 3.0);
1309
1310            assert!(GeoUtils::point_in_polygon(&inside_point, &polygon));
1311            assert!(!GeoUtils::point_in_polygon(&outside_point, &polygon));
1312        }
1313
1314        #[test]
1315        fn test_normalize_coordinates() {
1316            assert_eq!(GeoUtils::normalize_longitude(380.0), 20.0);
1317            assert_eq!(GeoUtils::normalize_longitude(-200.0), 160.0);
1318            assert_eq!(GeoUtils::normalize_latitude(100.0), 90.0);
1319            assert_eq!(GeoUtils::normalize_latitude(-100.0), -90.0);
1320        }
1321
1322        #[test]
1323        fn test_dms_conversion() {
1324            let decimal = 51.5074;
1325            let (degrees, minutes, seconds) = GeoUtils::decimal_to_dms(decimal);
1326            let converted_back = GeoUtils::dms_to_decimal(degrees, minutes, seconds);
1327
1328            assert!((decimal - converted_back).abs() < 0.0001);
1329        }
1330    }
1331}
1332
1333#[allow(non_snake_case)]
1334#[cfg(test)]
1335mod tests {
1336    use super::*;
1337
1338    #[test]
1339    fn test_point_operations() {
1340        let p1 = Point::new(vec![1.0, 2.0, 3.0]);
1341        let p2 = Point::new(vec![4.0, 5.0, 6.0]);
1342
1343        assert_eq!(p1.dimension(), 3);
1344        assert_eq!(p1.distance_squared(&p2), 27.0); // (4-1)² + (5-2)² + (6-3)² = 9+9+9 = 27
1345        assert_eq!(p1.distance(&p2), 27.0_f64.sqrt());
1346    }
1347
1348    #[test]
1349    fn test_rectangle_operations() {
1350        let rect = Rectangle::new(vec![0.0, 0.0], vec![10.0, 10.0]);
1351        let point_inside = Point::new(vec![5.0, 5.0]);
1352        let point_outside = Point::new(vec![15.0, 15.0]);
1353
1354        assert!(rect.contains(&point_inside));
1355        assert!(!rect.contains(&point_outside));
1356        assert_eq!(rect.area(), 100.0);
1357
1358        let other_rect = Rectangle::new(vec![5.0, 5.0], vec![15.0, 15.0]);
1359        assert!(rect.intersects(&other_rect));
1360
1361        let union = rect.union(&other_rect);
1362        assert_eq!(union.min_bounds, vec![0.0, 0.0]);
1363        assert_eq!(union.max_bounds, vec![15.0, 15.0]);
1364    }
1365
1366    #[test]
1367    fn test_kdtree() {
1368        let points = vec![
1369            (Point::new(vec![2.0, 3.0]), 0),
1370            (Point::new(vec![5.0, 4.0]), 1),
1371            (Point::new(vec![9.0, 6.0]), 2),
1372            (Point::new(vec![4.0, 7.0]), 3),
1373            (Point::new(vec![8.0, 1.0]), 4),
1374            (Point::new(vec![7.0, 2.0]), 5),
1375        ];
1376
1377        let kd_tree = KdTree::from_points(points);
1378
1379        let query = Point::new(vec![5.0, 5.0]);
1380        let result = kd_tree.nearest_neighbor(&query);
1381
1382        assert!(result.is_some());
1383        let (_, data, _) = result.unwrap();
1384        // Should find one of the nearby points
1385        assert!(data <= 5);
1386
1387        // Test range query
1388        let range = Rectangle::new(vec![0.0, 0.0], vec![6.0, 6.0]);
1389        let range_results = kd_tree.range_query(&range);
1390        assert!(!range_results.is_empty());
1391    }
1392
1393    #[test]
1394    fn test_quadtree() {
1395        let bounds = Rectangle::new(vec![0.0, 0.0], vec![100.0, 100.0]);
1396        let mut quad_tree = QuadTree::new(bounds, 4, 6);
1397
1398        // Insert some points
1399        quad_tree.insert(Point::new(vec![10.0, 10.0]), 0);
1400        quad_tree.insert(Point::new(vec![20.0, 20.0]), 1);
1401        quad_tree.insert(Point::new(vec![80.0, 80.0]), 2);
1402        quad_tree.insert(Point::new(vec![90.0, 90.0]), 3);
1403
1404        // Query a region
1405        let query_bounds = Rectangle::new(vec![5.0, 5.0], vec![25.0, 25.0]);
1406        let results = quad_tree.query(&query_bounds);
1407
1408        assert_eq!(results.len(), 2); // Should find points 0 and 1
1409
1410        // Test nearest neighbor
1411        let query_point = Point::new(vec![15.0, 15.0]);
1412        let nearest = quad_tree.nearest_neighbor(&query_point, None);
1413        assert!(nearest.is_some());
1414    }
1415
1416    #[test]
1417    fn test_spatial_hash() {
1418        let bounds = Rectangle::new(vec![0.0, 0.0], vec![100.0, 100.0]);
1419        let mut spatial_hash = SpatialHash::new(bounds, 10.0);
1420
1421        // Insert some points
1422        spatial_hash.insert(Point::new(vec![15.0, 15.0]), 0);
1423        spatial_hash.insert(Point::new(vec![25.0, 25.0]), 1);
1424        spatial_hash.insert(Point::new(vec![85.0, 85.0]), 2);
1425
1426        // Query radius
1427        let center = Point::new(vec![20.0, 20.0]);
1428        let results = spatial_hash.query_radius(&center, 10.0);
1429
1430        assert!(!results.is_empty());
1431
1432        let stats = spatial_hash.stats();
1433        assert_eq!(stats.total_points, 3);
1434    }
1435
1436    #[test]
1437    fn test_octree() {
1438        let bounds = Rectangle::new(vec![0.0, 0.0, 0.0], vec![100.0, 100.0, 100.0]);
1439        let mut oct_tree = OctTree::new(bounds, 4, 6);
1440
1441        // Insert some 3D points
1442        oct_tree.insert(Point::new(vec![10.0, 10.0, 10.0]), 0);
1443        oct_tree.insert(Point::new(vec![20.0, 20.0, 20.0]), 1);
1444        oct_tree.insert(Point::new(vec![80.0, 80.0, 80.0]), 2);
1445
1446        // Query a 3D region
1447        let query_bounds = Rectangle::new(vec![5.0, 5.0, 5.0], vec![25.0, 25.0, 25.0]);
1448        let results = oct_tree.query(&query_bounds);
1449
1450        assert_eq!(results.len(), 2); // Should find points 0 and 1
1451    }
1452}