1use crate::error::{AlgorithmError, Result};
29use crate::vector::offset::{JoinStyle, OffsetOptions, offset_polygon_rings};
30use crate::vector::pool::{PoolGuard, get_pooled_polygon};
31use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
32
33#[cfg(not(feature = "std"))]
34use core::f64::consts::PI;
35#[cfg(feature = "std")]
36use std::f64::consts::PI;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum BufferCapStyle {
41 #[default]
43 Round,
44 Flat,
46 Square,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum BufferJoinStyle {
53 #[default]
55 Round,
56 Miter,
58 Bevel,
60}
61
62#[derive(Debug, Clone)]
64pub struct BufferOptions {
65 pub quadrant_segments: usize,
67 pub cap_style: BufferCapStyle,
69 pub join_style: BufferJoinStyle,
71 pub miter_limit: f64,
73 pub simplify_tolerance: f64,
75}
76
77impl Default for BufferOptions {
78 fn default() -> Self {
79 Self {
80 quadrant_segments: 8,
81 cap_style: BufferCapStyle::Round,
82 join_style: BufferJoinStyle::Round,
83 miter_limit: 5.0,
84 simplify_tolerance: 0.0,
85 }
86 }
87}
88
89pub fn buffer_point(center: &Point, radius: f64, options: &BufferOptions) -> Result<Polygon> {
101 if radius < 0.0 {
102 return Err(AlgorithmError::InvalidParameter {
103 parameter: "radius",
104 message: "radius must be non-negative".to_string(),
105 });
106 }
107
108 if !radius.is_finite() {
109 return Err(AlgorithmError::InvalidParameter {
110 parameter: "radius",
111 message: "radius must be finite".to_string(),
112 });
113 }
114
115 if radius == 0.0 {
116 return create_degenerate_polygon(¢er.coord);
118 }
119
120 let segments = options.quadrant_segments * 4;
121 let mut coords = Vec::with_capacity(segments + 1);
122
123 for i in 0..segments {
124 let angle = 2.0 * PI * (i as f64) / (segments as f64);
125 let x = center.coord.x + radius * angle.cos();
126 let y = center.coord.y + radius * angle.sin();
127 coords.push(Coordinate::new_2d(x, y));
128 }
129
130 coords.push(coords[0]);
132
133 let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
134 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
135}
136
137pub fn buffer_linestring(
152 line: &LineString,
153 distance: f64,
154 options: &BufferOptions,
155) -> Result<Polygon> {
156 if line.coords.len() < 2 {
157 return Err(AlgorithmError::InsufficientData {
158 operation: "buffer_linestring",
159 message: "linestring must have at least 2 coordinates".to_string(),
160 });
161 }
162
163 if !distance.is_finite() {
164 return Err(AlgorithmError::InvalidParameter {
165 parameter: "distance",
166 message: "distance must be finite".to_string(),
167 });
168 }
169
170 if distance == 0.0 {
171 return create_degenerate_linestring_polygon(line);
173 }
174
175 let abs_distance = distance.abs();
176 let mut left_coords = Vec::new();
177 let mut right_coords = Vec::new();
178
179 for i in 0..(line.coords.len() - 1) {
181 let p1 = &line.coords[i];
182 let p2 = &line.coords[i + 1];
183
184 let (left, right) = offset_segment(p1, p2, abs_distance)?;
185
186 if i == 0 {
187 add_start_cap(&mut left_coords, p1, &left, abs_distance, options);
189 }
190
191 left_coords.push(left);
192
193 if i == line.coords.len() - 2 {
194 let (left2, right2) = offset_segment(p1, p2, abs_distance)?;
196 left_coords.push(left2);
197
198 add_end_cap(&mut left_coords, p2, &left2, abs_distance, options);
200
201 right_coords.insert(0, right2);
203 right_coords.insert(0, right);
204 } else {
205 let p3 = &line.coords[i + 2];
212 let (left3, _) = offset_segment(p2, p3, abs_distance)?;
213 let off1_at_vertex = Coordinate::new_2d(p2.x + (left.x - p1.x), p2.y + (left.y - p1.y));
214
215 add_join(
216 &mut left_coords,
217 &off1_at_vertex,
218 &left3,
219 p2,
220 abs_distance,
221 options,
222 )?;
223
224 right_coords.insert(0, right);
225 }
226 }
227
228 left_coords.extend(right_coords);
230 left_coords.push(left_coords[0]); let exterior = LineString::new(left_coords).map_err(AlgorithmError::Core)?;
233 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
234}
235
236pub fn buffer_polygon(
251 polygon: &Polygon,
252 distance: f64,
253 options: &BufferOptions,
254) -> Result<Polygon> {
255 if !distance.is_finite() {
256 return Err(AlgorithmError::InvalidParameter {
257 parameter: "distance",
258 message: "distance must be finite".to_string(),
259 });
260 }
261
262 if distance == 0.0 {
263 return Ok(polygon.clone());
265 }
266
267 let exterior_buffer = buffer_ring(&polygon.exterior, distance, options, false)?;
270
271 let mut interior_buffers = Vec::new();
273 for interior in &polygon.interiors {
274 let hole_buffer = buffer_ring(interior, -distance, options, true)?;
276 interior_buffers.push(hole_buffer);
277 }
278
279 Polygon::new(exterior_buffer, interior_buffers).map_err(AlgorithmError::Core)
280}
281
282fn create_degenerate_polygon(coord: &Coordinate) -> Result<Polygon> {
288 let coords = vec![*coord, *coord, *coord, *coord];
289 let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
290 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
291}
292
293fn create_degenerate_linestring_polygon(line: &LineString) -> Result<Polygon> {
295 let mut coords = line.coords.clone();
296 coords.reverse();
297 coords.extend_from_slice(&line.coords);
298 coords.push(coords[0]);
299
300 let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
301 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
302}
303
304fn offset_segment(
308 p1: &Coordinate,
309 p2: &Coordinate,
310 distance: f64,
311) -> Result<(Coordinate, Coordinate)> {
312 let dx = p2.x - p1.x;
313 let dy = p2.y - p1.y;
314 let length = (dx * dx + dy * dy).sqrt();
315
316 if length < f64::EPSILON {
317 return Err(AlgorithmError::GeometryError {
318 message: "degenerate segment (zero length)".to_string(),
319 });
320 }
321
322 let perp_x = -dy / length;
324 let perp_y = dx / length;
325
326 let left = Coordinate::new_2d(p1.x + perp_x * distance, p1.y + perp_y * distance);
327
328 let right = Coordinate::new_2d(p1.x - perp_x * distance, p1.y - perp_y * distance);
329
330 Ok((left, right))
331}
332
333fn add_start_cap(
335 coords: &mut Vec<Coordinate>,
336 point: &Coordinate,
337 offset: &Coordinate,
338 distance: f64,
339 options: &BufferOptions,
340) {
341 match options.cap_style {
342 BufferCapStyle::Round => {
343 add_round_cap(coords, point, offset, distance, options, true);
344 }
345 BufferCapStyle::Flat => {
346 coords.push(*offset);
347 }
348 BufferCapStyle::Square => {
349 let dx = offset.x - point.x;
351 let dy = offset.y - point.y;
352 let len = (dx * dx + dy * dy).sqrt();
353 if len > f64::EPSILON {
354 let nx = -dy / len;
355 let ny = dx / len;
356 let extended =
357 Coordinate::new_2d(offset.x + nx * distance, offset.y + ny * distance);
358 coords.push(extended);
359 }
360 coords.push(*offset);
361 }
362 }
363}
364
365fn add_end_cap(
367 coords: &mut Vec<Coordinate>,
368 point: &Coordinate,
369 offset: &Coordinate,
370 distance: f64,
371 options: &BufferOptions,
372) {
373 match options.cap_style {
374 BufferCapStyle::Round => {
375 add_round_cap(coords, point, offset, distance, options, false);
376 }
377 BufferCapStyle::Flat => {
378 coords.push(*offset);
379 }
380 BufferCapStyle::Square => {
381 let dx = offset.x - point.x;
382 let dy = offset.y - point.y;
383 let len = (dx * dx + dy * dy).sqrt();
384 if len > f64::EPSILON {
385 let nx = dy / len;
386 let ny = -dx / len;
387 let extended =
388 Coordinate::new_2d(offset.x + nx * distance, offset.y + ny * distance);
389 coords.push(*offset);
390 coords.push(extended);
391 }
392 }
393 }
394}
395
396fn add_round_cap(
398 coords: &mut Vec<Coordinate>,
399 center: &Coordinate,
400 start_offset: &Coordinate,
401 radius: f64,
402 options: &BufferOptions,
403 is_start: bool,
404) {
405 let segments = options.quadrant_segments * 2; let start_angle = (start_offset.y - center.y).atan2(start_offset.x - center.x);
407
408 for i in 0..=segments {
409 let t = if is_start {
410 (i as f64) / (segments as f64)
411 } else {
412 (i as f64) / (segments as f64)
413 };
414 let angle = start_angle + t * PI * if is_start { 1.0 } else { -1.0 };
415 let x = center.x + radius * angle.cos();
416 let y = center.y + radius * angle.sin();
417 coords.push(Coordinate::new_2d(x, y));
418 }
419}
420
421fn add_join(
423 coords: &mut Vec<Coordinate>,
424 offset1: &Coordinate,
425 offset2: &Coordinate,
426 vertex: &Coordinate,
427 distance: f64,
428 options: &BufferOptions,
429) -> Result<()> {
430 match options.join_style {
431 BufferJoinStyle::Round => {
432 add_round_join(coords, offset1, offset2, vertex, distance, options);
433 }
434 BufferJoinStyle::Miter => {
435 add_miter_join(coords, offset1, offset2, vertex, distance, options)?;
436 }
437 BufferJoinStyle::Bevel => {
438 coords.push(*offset1);
439 coords.push(*offset2);
440 }
441 }
442 Ok(())
443}
444
445fn add_round_join(
447 coords: &mut Vec<Coordinate>,
448 offset1: &Coordinate,
449 offset2: &Coordinate,
450 center: &Coordinate,
451 radius: f64,
452 options: &BufferOptions,
453) {
454 coords.push(*offset1);
455
456 let angle1 = (offset1.y - center.y).atan2(offset1.x - center.x);
457 let angle2 = (offset2.y - center.y).atan2(offset2.x - center.x);
458
459 let mut angle_diff = angle2 - angle1;
460 while angle_diff > PI {
462 angle_diff -= 2.0 * PI;
463 }
464 while angle_diff < -PI {
465 angle_diff += 2.0 * PI;
466 }
467
468 let segments = ((angle_diff.abs() / (PI / 2.0)) * (options.quadrant_segments as f64)) as usize;
469
470 for i in 1..segments {
471 let t = (i as f64) / (segments as f64);
472 let angle = angle1 + t * angle_diff;
473 let x = center.x + radius * angle.cos();
474 let y = center.y + radius * angle.sin();
475 coords.push(Coordinate::new_2d(x, y));
476 }
477}
478
479fn add_miter_join(
481 coords: &mut Vec<Coordinate>,
482 offset1: &Coordinate,
483 offset2: &Coordinate,
484 vertex: &Coordinate,
485 distance: f64,
486 options: &BufferOptions,
487) -> Result<()> {
488 coords.push(*offset1);
489
490 let miter_result = compute_miter_point(offset1, offset2, vertex, distance, options.miter_limit);
493
494 if let Some(miter) = miter_result {
495 coords.push(miter);
496 }
497
498 coords.push(*offset2);
499 Ok(())
500}
501
502fn compute_miter_point(
519 offset1: &Coordinate,
520 offset2: &Coordinate,
521 vertex: &Coordinate,
522 distance: f64,
523 miter_limit: f64,
524) -> Option<Coordinate> {
525 if distance.abs() < f64::EPSILON {
526 return None;
527 }
528
529 let ax = offset1.x - vertex.x;
531 let ay = offset1.y - vertex.y;
532 let bx = offset2.x - vertex.x;
533 let by = offset2.y - vertex.y;
534
535 let sx = ax + bx;
537 let sy = ay + by;
538 let blen = (sx * sx + sy * sy).sqrt();
539
540 if blen < f64::EPSILON {
542 return None;
543 }
544
545 let bux = sx / blen;
546 let buy = sy / blen;
547
548 let cos_half = (bux * ax + buy * ay) / distance;
550 if cos_half.abs() < f64::EPSILON {
551 return None;
553 }
554
555 let miter_length = distance / cos_half;
557 let ratio = (miter_length / distance).abs();
558
559 if ratio > miter_limit.abs() || !miter_length.is_finite() {
560 None
562 } else {
563 Some(Coordinate::new_2d(
564 vertex.x + miter_length * bux,
565 vertex.y + miter_length * buy,
566 ))
567 }
568}
569
570fn buffer_ring(
584 ring: &LineString,
585 distance: f64,
586 options: &BufferOptions,
587 _is_hole: bool,
588) -> Result<LineString> {
589 if ring.coords.len() < 4 {
590 return Err(AlgorithmError::InsufficientData {
591 operation: "buffer_ring",
592 message: "ring must have at least 4 coordinates".to_string(),
593 });
594 }
595
596 let ring_tuples: Vec<(f64, f64)> = ring.coords.iter().map(|c| (c.x, c.y)).collect();
598
599 let offset_options = OffsetOptions {
602 miter_limit: options.miter_limit,
603 join_style: match options.join_style {
604 BufferJoinStyle::Round => JoinStyle::Round,
605 BufferJoinStyle::Miter => JoinStyle::Miter,
606 BufferJoinStyle::Bevel => JoinStyle::Bevel,
607 },
608 simplify_tolerance: if options.simplify_tolerance > 0.0 {
609 Some(options.simplify_tolerance)
610 } else {
611 None
612 },
613 };
614
615 let mut offset_rings = offset_polygon_rings(&[ring_tuples], distance, &offset_options)?;
616
617 let out_ring = offset_rings
618 .pop()
619 .ok_or_else(|| AlgorithmError::GeometryError {
620 message: "offset_polygon_rings returned no ring".to_string(),
621 })?;
622
623 let offset_coords: Vec<Coordinate> = out_ring
624 .iter()
625 .map(|&(x, y)| Coordinate::new_2d(x, y))
626 .collect();
627
628 LineString::new(offset_coords).map_err(AlgorithmError::Core)
629}
630
631pub fn buffer_point_pooled(
674 center: &Point,
675 radius: f64,
676 options: &BufferOptions,
677) -> Result<PoolGuard<'static, Polygon>> {
678 if radius < 0.0 {
679 return Err(AlgorithmError::InvalidParameter {
680 parameter: "radius",
681 message: "radius must be non-negative".to_string(),
682 });
683 }
684
685 if !radius.is_finite() {
686 return Err(AlgorithmError::InvalidParameter {
687 parameter: "radius",
688 message: "radius must be finite".to_string(),
689 });
690 }
691
692 let mut poly = get_pooled_polygon();
693
694 if radius == 0.0 {
695 let degenerate = create_degenerate_polygon(¢er.coord)?;
697 poly.exterior = degenerate.exterior;
698 poly.interiors = degenerate.interiors;
699 return Ok(poly);
700 }
701
702 let segments = options.quadrant_segments * 4;
703 poly.exterior.coords.clear();
704 poly.exterior.coords.reserve(segments + 1);
705
706 for i in 0..segments {
707 let angle = 2.0 * PI * (i as f64) / (segments as f64);
708 let x = center.coord.x + radius * angle.cos();
709 let y = center.coord.y + radius * angle.sin();
710 poly.exterior.coords.push(Coordinate::new_2d(x, y));
711 }
712
713 if let Some(&first) = poly.exterior.coords.first() {
715 poly.exterior.coords.push(first);
716 }
717
718 Ok(poly)
719}
720
721pub fn buffer_linestring_pooled(
753 line: &LineString,
754 distance: f64,
755 options: &BufferOptions,
756) -> Result<PoolGuard<'static, Polygon>> {
757 let result = buffer_linestring(line, distance, options)?;
759
760 let mut poly = get_pooled_polygon();
762 poly.exterior = result.exterior;
763 poly.interiors = result.interiors;
764
765 Ok(poly)
766}
767
768pub fn buffer_polygon_pooled(
806 polygon: &Polygon,
807 distance: f64,
808 options: &BufferOptions,
809) -> Result<PoolGuard<'static, Polygon>> {
810 let result = buffer_polygon(polygon, distance, options)?;
812
813 let mut poly = get_pooled_polygon();
815 poly.exterior = result.exterior;
816 poly.interiors = result.interiors;
817
818 Ok(poly)
819}
820
821#[cfg(test)]
822mod tests {
823 use super::*;
824 use approx::assert_relative_eq;
825
826 #[test]
827 fn test_buffer_point_basic() {
828 let point = Point::new(0.0, 0.0);
829 let options = BufferOptions::default();
830 let result = buffer_point(&point, 10.0, &options);
831 assert!(result.is_ok());
832
833 let polygon = result.ok();
834 assert!(polygon.is_some());
835 if let Some(poly) = polygon {
836 for coord in &poly.exterior.coords {
838 let dist = (coord.x * coord.x + coord.y * coord.y).sqrt();
839 assert_relative_eq!(dist, 10.0, epsilon = 1e-10);
840 }
841 }
842 }
843
844 #[test]
845 fn test_buffer_point_zero_radius() {
846 let point = Point::new(5.0, 5.0);
847 let options = BufferOptions::default();
848 let result = buffer_point(&point, 0.0, &options);
849 assert!(result.is_ok());
850 }
851
852 #[test]
853 fn test_buffer_point_negative_radius() {
854 let point = Point::new(0.0, 0.0);
855 let options = BufferOptions::default();
856 let result = buffer_point(&point, -10.0, &options);
857 assert!(result.is_err());
858 }
859
860 #[test]
861 fn test_buffer_linestring_basic() {
862 let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(10.0, 0.0)];
863 let line = LineString::new(coords);
864 assert!(line.is_ok());
865
866 if let Ok(ls) = line {
867 let options = BufferOptions::default();
868 let result = buffer_linestring(&ls, 5.0, &options);
869 assert!(result.is_ok());
870
871 if let Ok(poly) = result {
872 assert!(poly.exterior.coords.len() > 4);
874 }
875 }
876 }
877
878 #[test]
879 fn test_buffer_linestring_empty() {
880 let coords = vec![Coordinate::new_2d(0.0, 0.0)];
881 let line = LineString::new(coords);
882 assert!(line.is_err()); }
884
885 #[test]
886 fn test_buffer_polygon_basic() {
887 let exterior_coords = vec![
888 Coordinate::new_2d(0.0, 0.0),
889 Coordinate::new_2d(10.0, 0.0),
890 Coordinate::new_2d(10.0, 10.0),
891 Coordinate::new_2d(0.0, 10.0),
892 Coordinate::new_2d(0.0, 0.0),
893 ];
894 let exterior = LineString::new(exterior_coords);
895 assert!(exterior.is_ok());
896
897 if let Ok(ext) = exterior {
898 let polygon = Polygon::new(ext, vec![]);
899 assert!(polygon.is_ok());
900
901 if let Ok(poly) = polygon {
902 let options = BufferOptions::default();
903 let result = buffer_polygon(&poly, 2.0, &options);
904 assert!(result.is_ok());
905
906 if let Ok(buffered) = result {
911 let mut min_x = f64::INFINITY;
912 let mut min_y = f64::INFINITY;
913 let mut max_x = f64::NEG_INFINITY;
914 let mut max_y = f64::NEG_INFINITY;
915 for c in &buffered.exterior.coords {
916 min_x = min_x.min(c.x);
917 min_y = min_y.min(c.y);
918 max_x = max_x.max(c.x);
919 max_y = max_y.max(c.y);
920 }
921 assert!(min_x < 0.0, "min_x should extend below 0, got {min_x}");
923 assert!(min_y < 0.0, "min_y should extend below 0, got {min_y}");
924 assert!(max_x > 10.0, "max_x should extend beyond 10, got {max_x}");
925 assert!(max_y > 10.0, "max_y should extend beyond 10, got {max_y}");
926 assert!((-2.5..=-1.5).contains(&min_x), "min_x ~ -2, got {min_x}");
928 assert!((11.5..=12.5).contains(&max_x), "max_x ~ 12, got {max_x}");
929 }
930 }
931 }
932 }
933
934 #[test]
935 fn test_buffer_polygon_honors_join_style() {
936 let exterior_coords = vec![
939 Coordinate::new_2d(0.0, 0.0),
940 Coordinate::new_2d(10.0, 0.0),
941 Coordinate::new_2d(10.0, 10.0),
942 Coordinate::new_2d(0.0, 10.0),
943 Coordinate::new_2d(0.0, 0.0),
944 ];
945 let exterior = LineString::new(exterior_coords).expect("valid ring");
946 let poly = Polygon::new(exterior, vec![]).expect("valid polygon");
947
948 let mut round_pts = 0usize;
949 let mut bevel_pts = 0usize;
950 let mut miter_pts = 0usize;
951
952 for (style, out) in [
953 (BufferJoinStyle::Round, &mut round_pts),
954 (BufferJoinStyle::Bevel, &mut bevel_pts),
955 (BufferJoinStyle::Miter, &mut miter_pts),
956 ] {
957 let options = BufferOptions {
958 join_style: style,
959 ..BufferOptions::default()
960 };
961 let buffered = buffer_polygon(&poly, 2.0, &options).expect("buffer ok");
962 for c in &buffered.exterior.coords {
964 assert!(c.x.is_finite() && c.y.is_finite());
965 }
966 *out = buffered.exterior.coords.len();
967 }
968
969 assert!(
972 round_pts > miter_pts,
973 "round joins ({round_pts}) should add more points than miter ({miter_pts})"
974 );
975 assert!(
978 bevel_pts >= miter_pts,
979 "bevel ({bevel_pts}) should have >= miter ({miter_pts}) points"
980 );
981 }
982
983 #[test]
984 fn test_compute_miter_point_extends_beyond_offsets() {
985 let vertex = Coordinate::new_2d(10.0, 0.0);
990 let offset1 = Coordinate::new_2d(10.0, 1.0); let offset2 = Coordinate::new_2d(9.0, 0.0); let miter =
993 compute_miter_point(&offset1, &offset2, &vertex, 1.0, 5.0).expect("within miter limit");
994 assert_relative_eq!(miter.x, 9.0, epsilon = 1e-9);
995 assert_relative_eq!(miter.y, 1.0, epsilon = 1e-9);
996
997 let midpoint_x = (offset1.x + offset2.x) / 2.0;
999 let midpoint_y = (offset1.y + offset2.y) / 2.0;
1000 assert!(
1001 (miter.x - midpoint_x).abs() > 0.1 || (miter.y - midpoint_y).abs() > 0.1,
1002 "miter point must differ from the offset midpoint"
1003 );
1004 }
1005
1006 #[test]
1007 fn test_compute_miter_point_exceeds_limit_falls_back() {
1008 let vertex = Coordinate::new_2d(0.0, 0.0);
1010 let offset1 = Coordinate::new_2d(0.0, 1.0);
1011 let offset2 = Coordinate::new_2d(0.01, -0.99995);
1013 let miter = compute_miter_point(&offset1, &offset2, &vertex, 1.0, 5.0);
1014 assert!(
1015 miter.is_none(),
1016 "sharp corner must fall back to bevel (None), got {miter:?}"
1017 );
1018 }
1019
1020 #[test]
1021 fn test_offset_segment() {
1022 let p1 = Coordinate::new_2d(0.0, 0.0);
1023 let p2 = Coordinate::new_2d(10.0, 0.0);
1024 let result = offset_segment(&p1, &p2, 5.0);
1025
1026 assert!(result.is_ok());
1027 if let Ok((left, right)) = result {
1028 assert_relative_eq!(left.x, 0.0, epsilon = 1e-10);
1029 assert_relative_eq!(left.y, 5.0, epsilon = 1e-10);
1030 assert_relative_eq!(right.x, 0.0, epsilon = 1e-10);
1031 assert_relative_eq!(right.y, -5.0, epsilon = 1e-10);
1032 }
1033 }
1034
1035 #[test]
1036 fn test_buffer_cap_styles() {
1037 let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(10.0, 0.0)];
1038 let line = LineString::new(coords);
1039 assert!(line.is_ok());
1040
1041 if let Ok(ls) = line {
1042 let mut options = BufferOptions::default();
1044 options.cap_style = BufferCapStyle::Round;
1045 let result = buffer_linestring(&ls, 5.0, &options);
1046 assert!(result.is_ok());
1047
1048 options.cap_style = BufferCapStyle::Flat;
1050 let result = buffer_linestring(&ls, 5.0, &options);
1051 assert!(result.is_ok());
1052
1053 options.cap_style = BufferCapStyle::Square;
1055 let result = buffer_linestring(&ls, 5.0, &options);
1056 assert!(result.is_ok());
1057 }
1058 }
1059
1060 #[test]
1061 fn test_buffer_join_styles() {
1062 let coords = vec![
1063 Coordinate::new_2d(0.0, 0.0),
1064 Coordinate::new_2d(10.0, 0.0),
1065 Coordinate::new_2d(10.0, 10.0),
1066 ];
1067 let line = LineString::new(coords);
1068 assert!(line.is_ok());
1069
1070 if let Ok(ls) = line {
1071 let mut options = BufferOptions::default();
1073 options.join_style = BufferJoinStyle::Round;
1074 let result = buffer_linestring(&ls, 5.0, &options);
1075 assert!(result.is_ok());
1076
1077 options.join_style = BufferJoinStyle::Miter;
1079 let result = buffer_linestring(&ls, 5.0, &options);
1080 assert!(result.is_ok());
1081
1082 options.join_style = BufferJoinStyle::Bevel;
1084 let result = buffer_linestring(&ls, 5.0, &options);
1085 assert!(result.is_ok());
1086 }
1087 }
1088}