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