1use nodedb_types::geometry::Geometry;
8use nodedb_types::{BoundingBox, geometry_bbox};
9
10pub fn st_buffer(geom: &Geometry, distance_meters: f64, segments: usize) -> Geometry {
20 let segments = segments.max(4);
21
22 match geom {
23 Geometry::Point { coordinates } => {
24 buffer_point(coordinates[0], coordinates[1], distance_meters, segments)
25 }
26 Geometry::LineString { coordinates } => {
27 buffer_linestring(coordinates, distance_meters, segments)
28 }
29 Geometry::Polygon { coordinates } => buffer_polygon(coordinates, distance_meters, segments),
30 Geometry::MultiPoint { coordinates } => {
32 let polys: Vec<Vec<Vec<[f64; 2]>>> = coordinates
33 .iter()
34 .map(|pt| {
35 if let Geometry::Polygon { coordinates: rings } =
36 buffer_point(pt[0], pt[1], distance_meters, segments)
37 {
38 rings
39 } else {
40 vec![]
41 }
42 })
43 .collect();
44 Geometry::MultiPolygon { coordinates: polys }
45 }
46 _ => {
47 let bb = geometry_bbox(geom).expand_meters(distance_meters);
49 bbox_to_polygon(&bb)
50 }
51 }
52}
53
54pub fn st_envelope(geom: &Geometry) -> Geometry {
56 let bb = geometry_bbox(geom);
57 bbox_to_polygon(&bb)
58}
59
60pub fn st_union(a: &Geometry, b: &Geometry) -> Geometry {
66 match (a, b) {
67 (Geometry::Point { coordinates: ca }, Geometry::Point { coordinates: cb }) => {
69 Geometry::MultiPoint {
70 coordinates: vec![*ca, *cb],
71 }
72 }
73 (Geometry::LineString { coordinates: la }, Geometry::LineString { coordinates: lb }) => {
75 Geometry::MultiLineString {
76 coordinates: vec![la.clone(), lb.clone()],
77 }
78 }
79 (Geometry::Polygon { coordinates: ra }, Geometry::Polygon { coordinates: rb }) => {
81 Geometry::MultiPolygon {
82 coordinates: vec![ra.clone(), rb.clone()],
83 }
84 }
85 (Geometry::MultiPoint { coordinates: pts }, Geometry::Point { coordinates: pt }) => {
87 let mut coords = pts.clone();
88 coords.push(*pt);
89 Geometry::MultiPoint {
90 coordinates: coords,
91 }
92 }
93 (Geometry::Point { coordinates: pt }, Geometry::MultiPoint { coordinates: pts }) => {
94 let mut coords = vec![*pt];
95 coords.extend_from_slice(pts);
96 Geometry::MultiPoint {
97 coordinates: coords,
98 }
99 }
100 _ => Geometry::GeometryCollection {
102 geometries: vec![a.clone(), b.clone()],
103 },
104 }
105}
106
107fn buffer_point(lng: f64, lat: f64, meters: f64, segments: usize) -> Geometry {
111 let dlat = meters / 110_540.0;
112 let dlng = meters / (111_320.0 * lat.to_radians().cos().max(0.001));
113
114 let mut ring = Vec::with_capacity(segments + 1);
116 for i in 0..segments {
117 let angle = 2.0 * std::f64::consts::PI * (i as f64) / (segments as f64);
118 ring.push([lng + dlng * angle.cos(), lat + dlat * angle.sin()]);
119 }
120 ring.push(ring[0]);
122
123 Geometry::Polygon {
124 coordinates: vec![ring],
125 }
126}
127
128fn buffer_linestring(line: &[[f64; 2]], meters: f64, segments: usize) -> Geometry {
130 if line.is_empty() {
131 return Geometry::Polygon {
132 coordinates: vec![vec![]],
133 };
134 }
135 if line.len() == 1 {
136 return buffer_point(line[0][0], line[0][1], meters, segments);
137 }
138
139 let mut left_side = Vec::new();
142 let mut right_side = Vec::new();
143
144 for i in 0..line.len() - 1 {
145 let (nl, nr) = offset_segment(line[i], line[i + 1], meters);
146 left_side.push(nl.0);
147 left_side.push(nl.1);
148 right_side.push(nr.0);
149 right_side.push(nr.1);
150 }
151
152 let mut ring = Vec::new();
154 ring.extend_from_slice(&left_side);
155
156 let last = line[line.len() - 1];
158 let cap_segments = segments / 2;
159 let end_dir = direction(line[line.len() - 2], last);
160 for i in 0..=cap_segments {
161 let angle = end_dir - std::f64::consts::FRAC_PI_2
162 + std::f64::consts::PI * (i as f64) / (cap_segments as f64);
163 let dlat = meters / 110_540.0;
164 let dlng = meters / (111_320.0 * last[1].to_radians().cos().max(0.001));
165 ring.push([last[0] + dlng * angle.cos(), last[1] + dlat * angle.sin()]);
166 }
167
168 for pt in right_side.iter().rev() {
170 ring.push(*pt);
171 }
172
173 let first = line[0];
175 let start_dir = direction(first, line[1]);
176 for i in 0..=cap_segments {
177 let angle = start_dir
178 + std::f64::consts::FRAC_PI_2
179 + std::f64::consts::PI * (i as f64) / (cap_segments as f64);
180 let dlat = meters / 110_540.0;
181 let dlng = meters / (111_320.0 * first[1].to_radians().cos().max(0.001));
182 ring.push([first[0] + dlng * angle.cos(), first[1] + dlat * angle.sin()]);
183 }
184
185 if let Some(&first_pt) = ring.first() {
187 ring.push(first_pt);
188 }
189
190 Geometry::Polygon {
191 coordinates: vec![ring],
192 }
193}
194
195fn buffer_polygon(rings: &[Vec<[f64; 2]>], meters: f64, _segments: usize) -> Geometry {
197 let mut new_rings = Vec::with_capacity(rings.len());
199
200 for (ring_idx, ring) in rings.iter().enumerate() {
201 if ring.len() < 3 {
202 new_rings.push(ring.clone());
203 continue;
204 }
205 let is_exterior = ring_idx == 0;
206 let sign = if is_exterior { 1.0 } else { -1.0 };
208 let offset_m = meters * sign;
209
210 let mut new_ring = Vec::with_capacity(ring.len());
212 let n = if ring.first() == ring.last() {
213 ring.len() - 1
214 } else {
215 ring.len()
216 };
217
218 for i in 0..n {
219 let prev = ring[(i + n - 1) % n];
220 let curr = ring[i];
221 let next = ring[(i + 1) % n];
222
223 let n1 = edge_outward_normal(prev, curr);
225 let n2 = edge_outward_normal(curr, next);
226 let bisect = [(n1[0] + n2[0]) / 2.0, (n1[1] + n2[1]) / 2.0];
227 let len = (bisect[0] * bisect[0] + bisect[1] * bisect[1])
228 .sqrt()
229 .max(1e-12);
230 let unit = [bisect[0] / len, bisect[1] / len];
231
232 let dlat = offset_m / 110_540.0;
233 let dlng = offset_m / (111_320.0 * curr[1].to_radians().cos().max(0.001));
234
235 new_ring.push([curr[0] + unit[0] * dlng, curr[1] + unit[1] * dlat]);
236 }
237
238 if let Some(&first_pt) = new_ring.first() {
240 new_ring.push(first_pt);
241 }
242 new_rings.push(new_ring);
243 }
244
245 Geometry::Polygon {
246 coordinates: new_rings,
247 }
248}
249
250type OffsetPair = (([f64; 2], [f64; 2]), ([f64; 2], [f64; 2]));
253
254fn offset_segment(a: [f64; 2], b: [f64; 2], meters: f64) -> OffsetPair {
255 let normal = edge_outward_normal(a, b);
256 let dlat = meters / 110_540.0;
257 let mid_lat = ((a[1] + b[1]) / 2.0).to_radians().cos().max(0.001);
258 let dlng = meters / (111_320.0 * mid_lat);
259
260 let left = (
261 [a[0] + normal[0] * dlng, a[1] + normal[1] * dlat],
262 [b[0] + normal[0] * dlng, b[1] + normal[1] * dlat],
263 );
264 let right = (
265 [a[0] - normal[0] * dlng, a[1] - normal[1] * dlat],
266 [b[0] - normal[0] * dlng, b[1] - normal[1] * dlat],
267 );
268 (left, right)
269}
270
271fn edge_outward_normal(a: [f64; 2], b: [f64; 2]) -> [f64; 2] {
277 let dx = b[0] - a[0];
278 let dy = b[1] - a[1];
279 let len = (dx * dx + dy * dy).sqrt().max(1e-12);
280 [dy / len, -dx / len]
282}
283
284fn direction(a: [f64; 2], b: [f64; 2]) -> f64 {
286 (b[1] - a[1]).atan2(b[0] - a[0])
287}
288
289fn bbox_to_polygon(bb: &BoundingBox) -> Geometry {
291 Geometry::Polygon {
292 coordinates: vec![vec![
293 [bb.min_lng, bb.min_lat],
294 [bb.max_lng, bb.min_lat],
295 [bb.max_lng, bb.max_lat],
296 [bb.min_lng, bb.max_lat],
297 [bb.min_lng, bb.min_lat],
298 ]],
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn buffer_point_circle() {
308 let circle = st_buffer(&Geometry::point(0.0, 0.0), 1000.0, 32);
309 if let Geometry::Polygon { coordinates } = &circle {
310 assert_eq!(coordinates.len(), 1);
311 assert_eq!(coordinates[0].len(), 33);
313 for pt in &coordinates[0][..32] {
315 let d = nodedb_types::geometry::haversine_distance(0.0, 0.0, pt[0], pt[1]);
316 assert!((d - 1000.0).abs() < 50.0, "d={d}");
317 }
318 } else {
319 panic!("expected Polygon");
320 }
321 }
322
323 #[test]
324 fn buffer_point_4_segments() {
325 let circle = st_buffer(&Geometry::point(10.0, 50.0), 500.0, 4);
326 if let Geometry::Polygon { coordinates } = &circle {
327 assert_eq!(coordinates[0].len(), 5); } else {
329 panic!("expected Polygon");
330 }
331 }
332
333 #[test]
334 fn envelope_polygon() {
335 let poly = Geometry::polygon(vec![vec![[1.0, 2.0], [5.0, 2.0], [3.0, 8.0], [1.0, 2.0]]]);
336 let env = st_envelope(&poly);
337 if let Geometry::Polygon { coordinates } = &env {
338 let ring = &coordinates[0];
339 assert_eq!(ring[0], [1.0, 2.0]); assert_eq!(ring[2], [5.0, 8.0]); } else {
342 panic!("expected Polygon");
343 }
344 }
345
346 #[test]
347 fn envelope_point() {
348 let env = st_envelope(&Geometry::point(5.0, 10.0));
349 if let Geometry::Polygon { coordinates } = &env {
350 assert_eq!(coordinates[0][0], [5.0, 10.0]);
352 assert_eq!(coordinates[0][2], [5.0, 10.0]);
353 } else {
354 panic!("expected Polygon");
355 }
356 }
357
358 #[test]
359 fn union_points() {
360 let a = Geometry::point(1.0, 2.0);
361 let b = Geometry::point(3.0, 4.0);
362 let u = st_union(&a, &b);
363 if let Geometry::MultiPoint { coordinates } = &u {
364 assert_eq!(coordinates.len(), 2);
365 } else {
366 panic!("expected MultiPoint, got {:?}", u.geometry_type());
367 }
368 }
369
370 #[test]
371 fn union_polygons() {
372 let a = Geometry::polygon(vec![vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]]);
373 let b = Geometry::polygon(vec![vec![[2.0, 2.0], [3.0, 2.0], [3.0, 3.0], [2.0, 2.0]]]);
374 let u = st_union(&a, &b);
375 if let Geometry::MultiPolygon { coordinates } = &u {
376 assert_eq!(coordinates.len(), 2);
377 } else {
378 panic!("expected MultiPolygon");
379 }
380 }
381
382 #[test]
383 fn union_mixed_types() {
384 let a = Geometry::point(1.0, 2.0);
385 let b = Geometry::polygon(vec![vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 0.0]]]);
386 let u = st_union(&a, &b);
387 if let Geometry::GeometryCollection { geometries } = &u {
388 assert_eq!(geometries.len(), 2);
389 } else {
390 panic!("expected GeometryCollection");
391 }
392 }
393
394 #[test]
395 fn buffer_linestring_corridor() {
396 let line = Geometry::line_string(vec![[0.0, 0.0], [0.1, 0.0]]);
397 let buf = st_buffer(&line, 1000.0, 16);
398 if let Geometry::Polygon { coordinates } = &buf {
399 assert!(!coordinates[0].is_empty());
400 assert!(coordinates[0].len() > 10);
402 } else {
403 panic!("expected Polygon");
404 }
405 }
406
407 #[test]
408 fn buffer_polygon_expands() {
409 let poly = Geometry::polygon(vec![vec![
410 [0.0, 0.0],
411 [1.0, 0.0],
412 [1.0, 1.0],
413 [0.0, 1.0],
414 [0.0, 0.0],
415 ]]);
416 let buf = st_buffer(&poly, 1000.0, 32);
417 if let Geometry::Polygon { .. } = &buf {
418 let orig_bb = geometry_bbox(&poly);
419 let buf_bb = geometry_bbox(&buf);
420 assert!(buf_bb.min_lng < orig_bb.min_lng);
422 assert!(buf_bb.max_lng > orig_bb.max_lng);
423 assert!(buf_bb.min_lat < orig_bb.min_lat);
424 assert!(buf_bb.max_lat > orig_bb.max_lat);
425 } else {
426 panic!("expected Polygon");
427 }
428 }
429}