tp_lib_core/projection/
spatial.rs1use crate::models::Netelement;
4use crate::errors::ProjectionError;
5use geo::Point;
6use rstar::{RTree, RTreeObject, AABB};
7
8#[derive(Debug, Clone)]
10struct NetelementIndexEntry {
11 index: usize,
13 bbox: AABB<[f64; 2]>,
15}
16
17impl RTreeObject for NetelementIndexEntry {
18 type Envelope = AABB<[f64; 2]>;
19
20 fn envelope(&self) -> Self::Envelope {
21 self.bbox
22 }
23}
24
25pub struct NetworkIndex {
27 tree: RTree<NetelementIndexEntry>,
28 netelements: Vec<Netelement>,
29}
30
31impl NetworkIndex {
32 pub fn new(netelements: Vec<Netelement>) -> Result<Self, ProjectionError> {
34 if netelements.is_empty() {
35 return Err(ProjectionError::EmptyNetwork);
36 }
37
38 let mut entries = Vec::new();
40 for (index, netelement) in netelements.iter().enumerate() {
41 let coords = &netelement.geometry.0;
42 if coords.is_empty() {
43 continue; }
45
46 let mut min_x = f64::MAX;
48 let mut max_x = f64::MIN;
49 let mut min_y = f64::MAX;
50 let mut max_y = f64::MIN;
51
52 for coord in coords {
53 min_x = min_x.min(coord.x);
54 max_x = max_x.max(coord.x);
55 min_y = min_y.min(coord.y);
56 max_y = max_y.max(coord.y);
57 }
58
59 let bbox = AABB::from_corners([min_x, min_y], [max_x, max_y]);
60 entries.push(NetelementIndexEntry { index, bbox });
61 }
62
63 let tree = RTree::bulk_load(entries);
64
65 Ok(Self {
66 tree,
67 netelements,
68 })
69 }
70
71 pub fn netelements(&self) -> &[Netelement] {
73 &self.netelements
74 }
75}
76
77pub fn find_nearest_netelement(
79 point: &Point<f64>,
80 index: &NetworkIndex,
81) -> Result<usize, ProjectionError> {
82 if index.netelements.is_empty() {
83 return Err(ProjectionError::EmptyNetwork);
84 }
85
86 let query_point = [point.x(), point.y()];
89
90 let mut best_index = 0;
93 let mut best_distance = f64::MAX;
94
95 let epsilon = 0.01; let search_envelope = AABB::from_corners(
98 [query_point[0] - epsilon, query_point[1] - epsilon],
99 [query_point[0] + epsilon, query_point[1] + epsilon],
100 );
101
102 for entry in index.tree.locate_in_envelope_intersecting(&search_envelope) {
104 let bbox_center_x = (entry.bbox.lower()[0] + entry.bbox.upper()[0]) / 2.0;
106 let bbox_center_y = (entry.bbox.lower()[1] + entry.bbox.upper()[1]) / 2.0;
107
108 let dx = bbox_center_x - query_point[0];
109 let dy = bbox_center_y - query_point[1];
110 let distance = (dx * dx + dy * dy).sqrt();
111
112 if distance < best_distance {
113 best_distance = distance;
114 best_index = entry.index;
115 }
116 }
117
118 if best_distance == f64::MAX {
120 best_index = 0;
121 }
122
123 Ok(best_index)
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use geo::{LineString, Coord};
130
131 #[test]
132 fn test_network_index_empty() {
133 let result = NetworkIndex::new(vec![]);
134 assert!(result.is_err());
135 if let Err(ProjectionError::EmptyNetwork) = result {
136 } else {
138 panic!("Expected EmptyNetwork error");
139 }
140 }
141
142 #[test]
143 fn test_network_index_single_netelement() {
144 let linestring = LineString::from(vec![
145 Coord { x: 4.0, y: 50.0 },
146 Coord { x: 5.0, y: 50.0 },
147 ]);
148 let netelement = Netelement::new(
149 "NE1".to_string(),
150 linestring,
151 "EPSG:4326".to_string(),
152 ).unwrap();
153
154 let index = NetworkIndex::new(vec![netelement]);
155 assert!(index.is_ok());
156 let index = index.unwrap();
157 assert_eq!(index.netelements().len(), 1);
158 }
159
160 #[test]
161 fn test_find_nearest_netelement() {
162 let linestring1 = LineString::from(vec![
164 Coord { x: 4.0, y: 50.0 },
165 Coord { x: 5.0, y: 50.0 },
166 ]);
167 let netelement1 = Netelement::new(
168 "NE1".to_string(),
169 linestring1,
170 "EPSG:4326".to_string(),
171 ).unwrap();
172
173 let linestring2 = LineString::from(vec![
174 Coord { x: 6.0, y: 51.0 },
175 Coord { x: 7.0, y: 51.0 },
176 ]);
177 let netelement2 = Netelement::new(
178 "NE2".to_string(),
179 linestring2,
180 "EPSG:4326".to_string(),
181 ).unwrap();
182
183 let index = NetworkIndex::new(vec![netelement1, netelement2]).unwrap();
184
185 let point1 = Point::new(4.5, 50.0);
187 let nearest1 = find_nearest_netelement(&point1, &index).unwrap();
188 assert_eq!(nearest1, 0, "Point should be nearest to first netelement");
189
190 let point2 = Point::new(6.5, 51.0);
192 let nearest2 = find_nearest_netelement(&point2, &index).unwrap();
193 assert_eq!(nearest2, 1, "Point should be nearest to second netelement");
194 }
195}