1use crate::error::{AlgorithmError, Result};
29use crate::vector::pool::{PoolGuard, get_pooled_polygon};
30use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
31
32#[cfg(not(feature = "std"))]
33use core::f64::consts::PI;
34#[cfg(feature = "std")]
35use std::f64::consts::PI;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum BufferCapStyle {
40 #[default]
42 Round,
43 Flat,
45 Square,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum BufferJoinStyle {
52 #[default]
54 Round,
55 Miter,
57 Bevel,
59}
60
61#[derive(Debug, Clone)]
63pub struct BufferOptions {
64 pub quadrant_segments: usize,
66 pub cap_style: BufferCapStyle,
68 pub join_style: BufferJoinStyle,
70 pub miter_limit: f64,
72 pub simplify_tolerance: f64,
74}
75
76impl Default for BufferOptions {
77 fn default() -> Self {
78 Self {
79 quadrant_segments: 8,
80 cap_style: BufferCapStyle::Round,
81 join_style: BufferJoinStyle::Round,
82 miter_limit: 5.0,
83 simplify_tolerance: 0.0,
84 }
85 }
86}
87
88pub fn buffer_point(center: &Point, radius: f64, options: &BufferOptions) -> Result<Polygon> {
100 if radius < 0.0 {
101 return Err(AlgorithmError::InvalidParameter {
102 parameter: "radius",
103 message: "radius must be non-negative".to_string(),
104 });
105 }
106
107 if !radius.is_finite() {
108 return Err(AlgorithmError::InvalidParameter {
109 parameter: "radius",
110 message: "radius must be finite".to_string(),
111 });
112 }
113
114 if radius == 0.0 {
115 return create_degenerate_polygon(¢er.coord);
117 }
118
119 let segments = options.quadrant_segments * 4;
120 let mut coords = Vec::with_capacity(segments + 1);
121
122 for i in 0..segments {
123 let angle = 2.0 * PI * (i as f64) / (segments as f64);
124 let x = center.coord.x + radius * angle.cos();
125 let y = center.coord.y + radius * angle.sin();
126 coords.push(Coordinate::new_2d(x, y));
127 }
128
129 coords.push(coords[0]);
131
132 let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
133 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
134}
135
136pub fn buffer_linestring(
151 line: &LineString,
152 distance: f64,
153 options: &BufferOptions,
154) -> Result<Polygon> {
155 if line.coords.len() < 2 {
156 return Err(AlgorithmError::InsufficientData {
157 operation: "buffer_linestring",
158 message: "linestring must have at least 2 coordinates".to_string(),
159 });
160 }
161
162 if !distance.is_finite() {
163 return Err(AlgorithmError::InvalidParameter {
164 parameter: "distance",
165 message: "distance must be finite".to_string(),
166 });
167 }
168
169 if distance == 0.0 {
170 return create_degenerate_linestring_polygon(line);
172 }
173
174 let abs_distance = distance.abs();
175 let mut left_coords = Vec::new();
176 let mut right_coords = Vec::new();
177
178 for i in 0..(line.coords.len() - 1) {
180 let p1 = &line.coords[i];
181 let p2 = &line.coords[i + 1];
182
183 let (left, right) = offset_segment(p1, p2, abs_distance)?;
184
185 if i == 0 {
186 add_start_cap(&mut left_coords, p1, &left, abs_distance, options);
188 }
189
190 left_coords.push(left);
191
192 if i == line.coords.len() - 2 {
193 let (left2, right2) = offset_segment(p1, p2, abs_distance)?;
195 left_coords.push(left2);
196
197 add_end_cap(&mut left_coords, p2, &left2, abs_distance, options);
199
200 right_coords.insert(0, right2);
202 right_coords.insert(0, right);
203 } else {
204 let p3 = &line.coords[i + 2];
206 let (left3, _) = offset_segment(p2, p3, abs_distance)?;
207
208 add_join(&mut left_coords, &left, &left3, p2, abs_distance, options)?;
209
210 right_coords.insert(0, right);
211 }
212 }
213
214 left_coords.extend(right_coords);
216 left_coords.push(left_coords[0]); let exterior = LineString::new(left_coords).map_err(AlgorithmError::Core)?;
219 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
220}
221
222pub fn buffer_polygon(
237 polygon: &Polygon,
238 distance: f64,
239 options: &BufferOptions,
240) -> Result<Polygon> {
241 if !distance.is_finite() {
242 return Err(AlgorithmError::InvalidParameter {
243 parameter: "distance",
244 message: "distance must be finite".to_string(),
245 });
246 }
247
248 if distance == 0.0 {
249 return Ok(polygon.clone());
251 }
252
253 let exterior_buffer = buffer_ring(&polygon.exterior, distance, options, false)?;
256
257 let mut interior_buffers = Vec::new();
259 for interior in &polygon.interiors {
260 let hole_buffer = buffer_ring(interior, -distance, options, true)?;
262 interior_buffers.push(hole_buffer);
263 }
264
265 Polygon::new(exterior_buffer, interior_buffers).map_err(AlgorithmError::Core)
266}
267
268fn create_degenerate_polygon(coord: &Coordinate) -> Result<Polygon> {
274 let coords = vec![*coord, *coord, *coord, *coord];
275 let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
276 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
277}
278
279fn create_degenerate_linestring_polygon(line: &LineString) -> Result<Polygon> {
281 let mut coords = line.coords.clone();
282 coords.reverse();
283 coords.extend_from_slice(&line.coords);
284 coords.push(coords[0]);
285
286 let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
287 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
288}
289
290fn offset_segment(
294 p1: &Coordinate,
295 p2: &Coordinate,
296 distance: f64,
297) -> Result<(Coordinate, Coordinate)> {
298 let dx = p2.x - p1.x;
299 let dy = p2.y - p1.y;
300 let length = (dx * dx + dy * dy).sqrt();
301
302 if length < f64::EPSILON {
303 return Err(AlgorithmError::GeometryError {
304 message: "degenerate segment (zero length)".to_string(),
305 });
306 }
307
308 let perp_x = -dy / length;
310 let perp_y = dx / length;
311
312 let left = Coordinate::new_2d(p1.x + perp_x * distance, p1.y + perp_y * distance);
313
314 let right = Coordinate::new_2d(p1.x - perp_x * distance, p1.y - perp_y * distance);
315
316 Ok((left, right))
317}
318
319fn add_start_cap(
321 coords: &mut Vec<Coordinate>,
322 point: &Coordinate,
323 offset: &Coordinate,
324 distance: f64,
325 options: &BufferOptions,
326) {
327 match options.cap_style {
328 BufferCapStyle::Round => {
329 add_round_cap(coords, point, offset, distance, options, true);
330 }
331 BufferCapStyle::Flat => {
332 coords.push(*offset);
333 }
334 BufferCapStyle::Square => {
335 let dx = offset.x - point.x;
337 let dy = offset.y - point.y;
338 let len = (dx * dx + dy * dy).sqrt();
339 if len > f64::EPSILON {
340 let nx = -dy / len;
341 let ny = dx / len;
342 let extended =
343 Coordinate::new_2d(offset.x + nx * distance, offset.y + ny * distance);
344 coords.push(extended);
345 }
346 coords.push(*offset);
347 }
348 }
349}
350
351fn add_end_cap(
353 coords: &mut Vec<Coordinate>,
354 point: &Coordinate,
355 offset: &Coordinate,
356 distance: f64,
357 options: &BufferOptions,
358) {
359 match options.cap_style {
360 BufferCapStyle::Round => {
361 add_round_cap(coords, point, offset, distance, options, false);
362 }
363 BufferCapStyle::Flat => {
364 coords.push(*offset);
365 }
366 BufferCapStyle::Square => {
367 let dx = offset.x - point.x;
368 let dy = offset.y - point.y;
369 let len = (dx * dx + dy * dy).sqrt();
370 if len > f64::EPSILON {
371 let nx = dy / len;
372 let ny = -dx / len;
373 let extended =
374 Coordinate::new_2d(offset.x + nx * distance, offset.y + ny * distance);
375 coords.push(*offset);
376 coords.push(extended);
377 }
378 }
379 }
380}
381
382fn add_round_cap(
384 coords: &mut Vec<Coordinate>,
385 center: &Coordinate,
386 start_offset: &Coordinate,
387 radius: f64,
388 options: &BufferOptions,
389 is_start: bool,
390) {
391 let segments = options.quadrant_segments * 2; let start_angle = (start_offset.y - center.y).atan2(start_offset.x - center.x);
393
394 for i in 0..=segments {
395 let t = if is_start {
396 (i as f64) / (segments as f64)
397 } else {
398 (i as f64) / (segments as f64)
399 };
400 let angle = start_angle + t * PI * if is_start { 1.0 } else { -1.0 };
401 let x = center.x + radius * angle.cos();
402 let y = center.y + radius * angle.sin();
403 coords.push(Coordinate::new_2d(x, y));
404 }
405}
406
407fn add_join(
409 coords: &mut Vec<Coordinate>,
410 offset1: &Coordinate,
411 offset2: &Coordinate,
412 vertex: &Coordinate,
413 distance: f64,
414 options: &BufferOptions,
415) -> Result<()> {
416 match options.join_style {
417 BufferJoinStyle::Round => {
418 add_round_join(coords, offset1, offset2, vertex, distance, options);
419 }
420 BufferJoinStyle::Miter => {
421 add_miter_join(coords, offset1, offset2, vertex, distance, options)?;
422 }
423 BufferJoinStyle::Bevel => {
424 coords.push(*offset1);
425 coords.push(*offset2);
426 }
427 }
428 Ok(())
429}
430
431fn add_round_join(
433 coords: &mut Vec<Coordinate>,
434 offset1: &Coordinate,
435 offset2: &Coordinate,
436 center: &Coordinate,
437 radius: f64,
438 options: &BufferOptions,
439) {
440 coords.push(*offset1);
441
442 let angle1 = (offset1.y - center.y).atan2(offset1.x - center.x);
443 let angle2 = (offset2.y - center.y).atan2(offset2.x - center.x);
444
445 let mut angle_diff = angle2 - angle1;
446 while angle_diff > PI {
448 angle_diff -= 2.0 * PI;
449 }
450 while angle_diff < -PI {
451 angle_diff += 2.0 * PI;
452 }
453
454 let segments = ((angle_diff.abs() / (PI / 2.0)) * (options.quadrant_segments as f64)) as usize;
455
456 for i in 1..segments {
457 let t = (i as f64) / (segments as f64);
458 let angle = angle1 + t * angle_diff;
459 let x = center.x + radius * angle.cos();
460 let y = center.y + radius * angle.sin();
461 coords.push(Coordinate::new_2d(x, y));
462 }
463}
464
465fn add_miter_join(
467 coords: &mut Vec<Coordinate>,
468 offset1: &Coordinate,
469 offset2: &Coordinate,
470 vertex: &Coordinate,
471 distance: f64,
472 options: &BufferOptions,
473) -> Result<()> {
474 coords.push(*offset1);
475
476 let miter_result = compute_miter_point(offset1, offset2, vertex, distance, options.miter_limit);
479
480 if let Some(miter) = miter_result {
481 coords.push(miter);
482 }
483
484 coords.push(*offset2);
485 Ok(())
486}
487
488fn compute_miter_point(
490 offset1: &Coordinate,
491 offset2: &Coordinate,
492 _vertex: &Coordinate,
493 distance: f64,
494 miter_limit: f64,
495) -> Option<Coordinate> {
496 let dx = offset2.x - offset1.x;
498 let dy = offset2.y - offset1.y;
499 let miter_distance = (dx * dx + dy * dy).sqrt();
500
501 if miter_distance > distance * miter_limit {
502 None
504 } else {
505 Some(Coordinate::new_2d(
507 (offset1.x + offset2.x) / 2.0,
508 (offset1.y + offset2.y) / 2.0,
509 ))
510 }
511}
512
513fn buffer_ring(
515 ring: &LineString,
516 distance: f64,
517 _options: &BufferOptions,
518 _is_hole: bool,
519) -> Result<LineString> {
520 if ring.coords.len() < 4 {
521 return Err(AlgorithmError::InsufficientData {
522 operation: "buffer_ring",
523 message: "ring must have at least 4 coordinates".to_string(),
524 });
525 }
526
527 let abs_distance = distance.abs();
528 let mut offset_coords = Vec::new();
529
530 for i in 0..(ring.coords.len() - 1) {
532 let p1 = &ring.coords[i];
533 let p2 = &ring.coords[i + 1];
534
535 let (left, _right) = offset_segment(p1, p2, abs_distance)?;
536
537 if distance > 0.0 {
538 offset_coords.push(left);
539 } else {
540 let (_left, right) = offset_segment(p1, p2, abs_distance)?;
542 offset_coords.push(right);
543 }
544 }
545
546 if let Some(first) = offset_coords.first() {
548 offset_coords.push(*first);
549 }
550
551 LineString::new(offset_coords).map_err(AlgorithmError::Core)
552}
553
554pub fn buffer_point_pooled(
597 center: &Point,
598 radius: f64,
599 options: &BufferOptions,
600) -> Result<PoolGuard<'static, Polygon>> {
601 if radius < 0.0 {
602 return Err(AlgorithmError::InvalidParameter {
603 parameter: "radius",
604 message: "radius must be non-negative".to_string(),
605 });
606 }
607
608 if !radius.is_finite() {
609 return Err(AlgorithmError::InvalidParameter {
610 parameter: "radius",
611 message: "radius must be finite".to_string(),
612 });
613 }
614
615 let mut poly = get_pooled_polygon();
616
617 if radius == 0.0 {
618 let degenerate = create_degenerate_polygon(¢er.coord)?;
620 poly.exterior = degenerate.exterior;
621 poly.interiors = degenerate.interiors;
622 return Ok(poly);
623 }
624
625 let segments = options.quadrant_segments * 4;
626 poly.exterior.coords.clear();
627 poly.exterior.coords.reserve(segments + 1);
628
629 for i in 0..segments {
630 let angle = 2.0 * PI * (i as f64) / (segments as f64);
631 let x = center.coord.x + radius * angle.cos();
632 let y = center.coord.y + radius * angle.sin();
633 poly.exterior.coords.push(Coordinate::new_2d(x, y));
634 }
635
636 if let Some(&first) = poly.exterior.coords.first() {
638 poly.exterior.coords.push(first);
639 }
640
641 Ok(poly)
642}
643
644pub fn buffer_linestring_pooled(
676 line: &LineString,
677 distance: f64,
678 options: &BufferOptions,
679) -> Result<PoolGuard<'static, Polygon>> {
680 let result = buffer_linestring(line, distance, options)?;
682
683 let mut poly = get_pooled_polygon();
685 poly.exterior = result.exterior;
686 poly.interiors = result.interiors;
687
688 Ok(poly)
689}
690
691pub fn buffer_polygon_pooled(
729 polygon: &Polygon,
730 distance: f64,
731 options: &BufferOptions,
732) -> Result<PoolGuard<'static, Polygon>> {
733 let result = buffer_polygon(polygon, distance, options)?;
735
736 let mut poly = get_pooled_polygon();
738 poly.exterior = result.exterior;
739 poly.interiors = result.interiors;
740
741 Ok(poly)
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747 use approx::assert_relative_eq;
748
749 #[test]
750 fn test_buffer_point_basic() {
751 let point = Point::new(0.0, 0.0);
752 let options = BufferOptions::default();
753 let result = buffer_point(&point, 10.0, &options);
754 assert!(result.is_ok());
755
756 let polygon = result.ok();
757 assert!(polygon.is_some());
758 if let Some(poly) = polygon {
759 for coord in &poly.exterior.coords {
761 let dist = (coord.x * coord.x + coord.y * coord.y).sqrt();
762 assert_relative_eq!(dist, 10.0, epsilon = 1e-10);
763 }
764 }
765 }
766
767 #[test]
768 fn test_buffer_point_zero_radius() {
769 let point = Point::new(5.0, 5.0);
770 let options = BufferOptions::default();
771 let result = buffer_point(&point, 0.0, &options);
772 assert!(result.is_ok());
773 }
774
775 #[test]
776 fn test_buffer_point_negative_radius() {
777 let point = Point::new(0.0, 0.0);
778 let options = BufferOptions::default();
779 let result = buffer_point(&point, -10.0, &options);
780 assert!(result.is_err());
781 }
782
783 #[test]
784 fn test_buffer_linestring_basic() {
785 let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(10.0, 0.0)];
786 let line = LineString::new(coords);
787 assert!(line.is_ok());
788
789 if let Ok(ls) = line {
790 let options = BufferOptions::default();
791 let result = buffer_linestring(&ls, 5.0, &options);
792 assert!(result.is_ok());
793
794 if let Ok(poly) = result {
795 assert!(poly.exterior.coords.len() > 4);
797 }
798 }
799 }
800
801 #[test]
802 fn test_buffer_linestring_empty() {
803 let coords = vec![Coordinate::new_2d(0.0, 0.0)];
804 let line = LineString::new(coords);
805 assert!(line.is_err()); }
807
808 #[test]
809 fn test_buffer_polygon_basic() {
810 let exterior_coords = vec![
811 Coordinate::new_2d(0.0, 0.0),
812 Coordinate::new_2d(10.0, 0.0),
813 Coordinate::new_2d(10.0, 10.0),
814 Coordinate::new_2d(0.0, 10.0),
815 Coordinate::new_2d(0.0, 0.0),
816 ];
817 let exterior = LineString::new(exterior_coords);
818 assert!(exterior.is_ok());
819
820 if let Ok(ext) = exterior {
821 let polygon = Polygon::new(ext, vec![]);
822 assert!(polygon.is_ok());
823
824 if let Ok(poly) = polygon {
825 let options = BufferOptions::default();
826 let result = buffer_polygon(&poly, 2.0, &options);
827 assert!(result.is_ok());
828 }
829 }
830 }
831
832 #[test]
833 fn test_offset_segment() {
834 let p1 = Coordinate::new_2d(0.0, 0.0);
835 let p2 = Coordinate::new_2d(10.0, 0.0);
836 let result = offset_segment(&p1, &p2, 5.0);
837
838 assert!(result.is_ok());
839 if let Ok((left, right)) = result {
840 assert_relative_eq!(left.x, 0.0, epsilon = 1e-10);
841 assert_relative_eq!(left.y, 5.0, epsilon = 1e-10);
842 assert_relative_eq!(right.x, 0.0, epsilon = 1e-10);
843 assert_relative_eq!(right.y, -5.0, epsilon = 1e-10);
844 }
845 }
846
847 #[test]
848 fn test_buffer_cap_styles() {
849 let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(10.0, 0.0)];
850 let line = LineString::new(coords);
851 assert!(line.is_ok());
852
853 if let Ok(ls) = line {
854 let mut options = BufferOptions::default();
856 options.cap_style = BufferCapStyle::Round;
857 let result = buffer_linestring(&ls, 5.0, &options);
858 assert!(result.is_ok());
859
860 options.cap_style = BufferCapStyle::Flat;
862 let result = buffer_linestring(&ls, 5.0, &options);
863 assert!(result.is_ok());
864
865 options.cap_style = BufferCapStyle::Square;
867 let result = buffer_linestring(&ls, 5.0, &options);
868 assert!(result.is_ok());
869 }
870 }
871
872 #[test]
873 fn test_buffer_join_styles() {
874 let coords = vec![
875 Coordinate::new_2d(0.0, 0.0),
876 Coordinate::new_2d(10.0, 0.0),
877 Coordinate::new_2d(10.0, 10.0),
878 ];
879 let line = LineString::new(coords);
880 assert!(line.is_ok());
881
882 if let Ok(ls) = line {
883 let mut options = BufferOptions::default();
885 options.join_style = BufferJoinStyle::Round;
886 let result = buffer_linestring(&ls, 5.0, &options);
887 assert!(result.is_ok());
888
889 options.join_style = BufferJoinStyle::Miter;
891 let result = buffer_linestring(&ls, 5.0, &options);
892 assert!(result.is_ok());
893
894 options.join_style = BufferJoinStyle::Bevel;
896 let result = buffer_linestring(&ls, 5.0, &options);
897 assert!(result.is_ok());
898 }
899 }
900}