1use crate::errors::ProjectionError;
4use crate::models::Netelement;
5use geo::Point;
6use rstar::{PointDistance, RTree, RTreeObject, AABB};
7
8#[derive(Debug, Clone)]
10struct NetelementIndexEntry {
11 index: usize,
13 bbox: AABB<[f64; 2]>,
15 segments: Vec<([f64; 2], [f64; 2])>,
21}
22
23impl RTreeObject for NetelementIndexEntry {
24 type Envelope = AABB<[f64; 2]>;
25
26 fn envelope(&self) -> Self::Envelope {
27 self.bbox
28 }
29}
30
31#[inline]
35fn point_to_segment_dist_2(p: &[f64; 2], a: &[f64; 2], b: &[f64; 2]) -> f64 {
36 let dx = b[0] - a[0];
37 let dy = b[1] - a[1];
38 let len_sq = dx * dx + dy * dy;
39 let (px, py) = if len_sq == 0.0 {
40 (a[0], a[1])
42 } else {
43 let t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len_sq;
44 let t = t.clamp(0.0, 1.0);
45 (a[0] + t * dx, a[1] + t * dy)
46 };
47 let ex = p[0] - px;
48 let ey = p[1] - py;
49 ex * ex + ey * ey
50}
51
52impl PointDistance for NetelementIndexEntry {
53 fn distance_2(&self, point: &[f64; 2]) -> f64 {
54 self.segments
59 .iter()
60 .map(|(a, b)| point_to_segment_dist_2(point, a, b))
61 .fold(f64::MAX, f64::min)
62 }
63}
64
65#[derive(Clone)]
67pub struct NetworkIndex {
68 tree: RTree<NetelementIndexEntry>,
69 netelements: Vec<Netelement>,
70}
71
72impl NetworkIndex {
73 pub fn new(netelements: Vec<Netelement>) -> Result<Self, ProjectionError> {
75 if netelements.is_empty() {
76 return Err(ProjectionError::EmptyNetwork);
77 }
78
79 let mut entries = Vec::new();
81
82 for (index, netelement) in netelements.iter().enumerate() {
84 let coords = &netelement.geometry.0;
85 if coords.is_empty() {
86 continue; }
88
89 let mut min_x = f64::MAX;
91 let mut max_x = f64::MIN;
92 let mut min_y = f64::MAX;
93 let mut max_y = f64::MIN;
94
95 for coord in coords {
96 min_x = min_x.min(coord.x);
97 max_x = max_x.max(coord.x);
98 min_y = min_y.min(coord.y);
99 max_y = max_y.max(coord.y);
100 }
101
102 let bbox = AABB::from_corners([min_x, min_y], [max_x, max_y]);
103
104 let segments: Vec<([f64; 2], [f64; 2])> = coords
106 .windows(2)
107 .map(|w| ([w[0].x, w[0].y], [w[1].x, w[1].y]))
108 .collect();
109
110 entries.push(NetelementIndexEntry {
111 index,
112 bbox,
113 segments,
114 });
115 }
116
117 let tree = RTree::bulk_load(entries);
118
119 Ok(Self { tree, netelements })
120 }
121
122 pub fn netelements(&self) -> &[Netelement] {
124 &self.netelements
125 }
126}
127
128pub fn find_nearest_netelement(
130 point: &Point<f64>,
131 index: &NetworkIndex,
132) -> Result<usize, ProjectionError> {
133 if index.netelements.is_empty() {
134 return Err(ProjectionError::EmptyNetwork);
135 }
136
137 let query_point = [point.x(), point.y()];
138
139 let nearest_entry = index.tree.nearest_neighbor(&query_point).ok_or_else(|| {
143 ProjectionError::InvalidGeometry("Could not find nearest netelement".to_string())
144 })?;
145
146 Ok(nearest_entry.index)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use geo::{Coord, LineString};
153
154 #[test]
155 fn test_network_index_empty() {
156 let result = NetworkIndex::new(vec![]);
157 assert!(result.is_err());
158 if let Err(ProjectionError::EmptyNetwork) = result {
159 } else {
161 panic!("Expected EmptyNetwork error");
162 }
163 }
164
165 #[test]
166 fn test_network_index_single_netelement() {
167 let linestring =
168 LineString::from(vec![Coord { x: 4.0, y: 50.0 }, Coord { x: 5.0, y: 50.0 }]);
169 let netelement =
170 Netelement::new("NE1".to_string(), linestring, "EPSG:4326".to_string()).unwrap();
171
172 let index = NetworkIndex::new(vec![netelement]);
173 assert!(index.is_ok());
174 let index = index.unwrap();
175 assert_eq!(index.netelements().len(), 1);
176 }
177
178 #[test]
179 fn test_find_nearest_netelement() {
180 let linestring1 =
182 LineString::from(vec![Coord { x: 4.0, y: 50.0 }, Coord { x: 5.0, y: 50.0 }]);
183 let netelement1 =
184 Netelement::new("NE1".to_string(), linestring1, "EPSG:4326".to_string()).unwrap();
185
186 let linestring2 =
187 LineString::from(vec![Coord { x: 6.0, y: 51.0 }, Coord { x: 7.0, y: 51.0 }]);
188 let netelement2 =
189 Netelement::new("NE2".to_string(), linestring2, "EPSG:4326".to_string()).unwrap();
190
191 let index = NetworkIndex::new(vec![netelement1, netelement2]).unwrap();
192
193 let point1 = Point::new(4.5, 50.0);
195 let nearest1 = find_nearest_netelement(&point1, &index).unwrap();
196 assert_eq!(nearest1, 0, "Point should be nearest to first netelement");
197
198 let point2 = Point::new(6.5, 51.0);
200 let nearest2 = find_nearest_netelement(&point2, &index).unwrap();
201 assert_eq!(nearest2, 1, "Point should be nearest to second netelement");
202 }
203
204 #[test]
221 fn test_find_nearest_with_overlapping_bboxes() {
222 let ls_a = LineString::from(vec![Coord { x: 4.0, y: 51.0 }, Coord { x: 6.0, y: 51.0 }]);
225 let ne_a = Netelement::new("A".to_string(), ls_a, "EPSG:4326".to_string()).unwrap();
226
227 let ls_b = LineString::from(vec![Coord { x: 5.5, y: 50.8 }, Coord { x: 5.5, y: 51.2 }]);
230 let ne_b = Netelement::new("B".to_string(), ls_b, "EPSG:4326".to_string()).unwrap();
231
232 let index = NetworkIndex::new(vec![ne_a, ne_b]).unwrap();
233
234 let query = Point::new(5.0, 50.99);
239 let nearest = find_nearest_netelement(&query, &index).unwrap();
240 assert_eq!(
241 nearest, 0,
242 "Point at (5.0, 50.99) should be nearest to netelement A (idx 0), not B (idx 1)"
243 );
244 }
245
246 #[test]
247 fn test_point_to_segment_dist_2_midpoint() {
248 let p = [0.0, 1.0];
250 let a = [0.0, 0.0];
251 let b = [2.0, 0.0];
252 let d = point_to_segment_dist_2(&p, &a, &b);
254 assert!((d - 1.0).abs() < 1e-12, "expected 1.0, got {}", d);
255 }
256
257 #[test]
258 fn test_point_to_segment_dist_2_beyond_endpoint() {
259 let p = [3.0, 0.0];
261 let a = [0.0, 0.0];
262 let b = [2.0, 0.0];
263 let d = point_to_segment_dist_2(&p, &a, &b);
265 assert!((d - 1.0).abs() < 1e-12, "expected 1.0, got {}", d);
266 }
267}