1#![allow(
28 clippy::cast_precision_loss,
29 clippy::cast_possible_truncation,
30 clippy::cast_sign_loss,
31 reason = "angular vertex-count arithmetic; values are small and non-negative"
32)]
33#![allow(clippy::float_cmp, reason = "exact degenerate-case guards")]
36
37use alloc::vec::Vec;
38
39use geometry_coords::{
40 CoordinateScalar,
41 math::{atan2, ceil, cos, hypot, mul_add, sin, sqrt},
42};
43use geometry_cs::{
44 AngleUnit, Cartesian, CartesianFamily, CoordinateSystem, FromF64, Geographic, GeographicFamily,
45 Spherical, SphericalFamily,
46};
47use geometry_model::{
48 Box as ModelBox, Linestring, MultiLinestring, MultiPoint, MultiPolygon, Point2D, Polygon, Ring,
49 Segment,
50};
51use geometry_strategy::buffer::{
52 BufferDistanceStrategy, BufferEndStrategy, BufferJoinStrategy, BufferPointStrategy,
53 BufferSettings, CartesianBuffer, DefaultBuffer, DefaultBufferStrategy, GeographicBuffer,
54 SphericalBuffer,
55};
56use geometry_tag::{
57 BoxTag, LinestringTag, MultiLinestringTag, MultiPointTag, MultiPolygonTag, PointTag,
58 PolygonTag, RingTag, SameAs, SegmentTag,
59};
60use geometry_trait::{
61 Box as BoxTrait, Geometry, Linestring as LinestringTrait,
62 MultiLinestring as MultiLinestringTrait, MultiPoint as MultiPointTrait,
63 MultiPolygon as MultiPolygonTrait, Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
64 Segment as SegmentTrait, box_max, box_min, segment_end, segment_start,
65};
66
67use crate::operation::{OverlayError, difference_multi, union_multi};
68use crate::predicate::segment_intersection::{SegmentIntersection, segment_intersection};
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum JoinStrategy {
76 Round {
79 points_per_circle: usize,
82 },
83 Miter,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum PointStrategy {
100 Circle {
103 points_per_circle: usize,
105 },
106 Square,
109}
110
111#[doc(hidden)]
117pub trait BufferStrategy<G: Geometry, CoordinateStrategy> {
118 fn apply(
119 &self,
120 geometry: &G,
121 settings: BufferSettings,
122 coordinate_strategy: &CoordinateStrategy,
123 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>;
124}
125
126#[doc(hidden)]
132pub trait BufferStrategyForKind {
133 type S: Default;
134}
135
136#[doc(hidden)]
141#[derive(Debug, Default, Clone, Copy)]
142pub struct PointBuffer;
143
144#[doc(hidden)]
149#[derive(Debug, Default, Clone, Copy)]
150pub struct PolygonBuffer;
151
152#[doc(hidden)]
154#[derive(Debug, Default, Clone, Copy)]
155pub struct LinestringBuffer;
156
157#[doc(hidden)]
159#[derive(Debug, Default, Clone, Copy)]
160pub struct SegmentBuffer;
161
162#[doc(hidden)]
164#[derive(Debug, Default, Clone, Copy)]
165pub struct RingBuffer;
166
167#[doc(hidden)]
169#[derive(Debug, Default, Clone, Copy)]
170pub struct BoxBuffer;
171
172#[doc(hidden)]
174#[derive(Debug, Default, Clone, Copy)]
175pub struct MultiPointBuffer;
176
177#[doc(hidden)]
179#[derive(Debug, Default, Clone, Copy)]
180pub struct MultiLinestringBuffer;
181
182#[doc(hidden)]
184#[derive(Debug, Default, Clone, Copy)]
185pub struct MultiPolygonBuffer;
186
187impl BufferStrategyForKind for PointTag {
190 type S = PointBuffer;
191}
192
193impl BufferStrategyForKind for PolygonTag {
196 type S = PolygonBuffer;
197}
198
199impl BufferStrategyForKind for LinestringTag {
200 type S = LinestringBuffer;
201}
202
203impl BufferStrategyForKind for SegmentTag {
204 type S = SegmentBuffer;
205}
206
207impl BufferStrategyForKind for RingTag {
208 type S = RingBuffer;
209}
210
211impl BufferStrategyForKind for BoxTag {
212 type S = BoxBuffer;
213}
214
215impl BufferStrategyForKind for MultiPointTag {
216 type S = MultiPointBuffer;
217}
218
219impl BufferStrategyForKind for MultiLinestringTag {
220 type S = MultiLinestringBuffer;
221}
222
223impl BufferStrategyForKind for MultiPolygonTag {
224 type S = MultiPolygonBuffer;
225}
226
227#[inline]
241#[must_use = "buffering can fail and the generated geometry should be used"]
242pub fn buffer<G>(
243 geometry: &G,
244 distance: f64,
245 join: JoinStrategy,
246 point: PointStrategy,
247) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
248where
249 G: Geometry,
250 G::Kind: BufferStrategyForKind,
251 <<G::Point as Point>::Cs as CoordinateSystem>::Family:
252 DefaultBuffer<<<G::Point as Point>::Cs as CoordinateSystem>::Family>,
253 <G::Kind as BufferStrategyForKind>::S: BufferStrategy<G, DefaultBufferStrategy<G>>,
254{
255 let settings = BufferSettings {
256 distance: BufferDistanceStrategy::Symmetric(distance),
257 side: geometry_strategy::buffer::BufferSideStrategy::Straight,
258 join: match join {
259 JoinStrategy::Round { points_per_circle } => {
260 BufferJoinStrategy::Round { points_per_circle }
261 }
262 JoinStrategy::Miter => BufferJoinStrategy::Miter { limit: 5.0 },
263 },
264 end: BufferEndStrategy::Round {
265 points_per_circle: 36,
266 },
267 point: match point {
268 PointStrategy::Circle { points_per_circle } => {
269 BufferPointStrategy::Circle { points_per_circle }
270 }
271 PointStrategy::Square => BufferPointStrategy::Square,
272 },
273 };
274 buffer_with(geometry, settings)
275}
276
277#[inline]
288#[must_use = "buffering can fail and the generated geometry should be used"]
289pub fn buffer_with<G>(
290 geometry: &G,
291 settings: BufferSettings,
292) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
293where
294 G: Geometry,
295 G::Kind: BufferStrategyForKind,
296 <<G::Point as Point>::Cs as CoordinateSystem>::Family:
297 DefaultBuffer<<<G::Point as Point>::Cs as CoordinateSystem>::Family>,
298 <G::Kind as BufferStrategyForKind>::S: BufferStrategy<G, DefaultBufferStrategy<G>>,
299{
300 buffer_with_strategy(geometry, settings, DefaultBufferStrategy::<G>::default())
301}
302
303#[inline]
321#[must_use = "buffering can fail and the generated geometry should be used"]
322#[allow(
323 clippy::needless_pass_by_value,
324 reason = "Boost buffer coordinate strategies are small value objects passed explicitly"
325)]
326pub fn buffer_with_strategy<G, CoordinateStrategy>(
327 geometry: &G,
328 settings: BufferSettings,
329 coordinate_strategy: CoordinateStrategy,
330) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
331where
332 G: Geometry,
333 G::Kind: BufferStrategyForKind,
334 <G::Kind as BufferStrategyForKind>::S: BufferStrategy<G, CoordinateStrategy>,
335{
336 <<G::Kind as BufferStrategyForKind>::S as Default>::default().apply(
337 geometry,
338 settings,
339 &coordinate_strategy,
340 )
341}
342
343fn zero_width_polygon_buffer<G>(
356 polygon: &G,
357) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
358where
359 G: PolygonTrait,
360 G::Point: PointMut + Default + Copy,
361 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
362{
363 use crate::piece_collection::{ZeroWidthOutcome, zero_width_outcome, zero_width_rings};
364
365 let rings = zero_width_rings(polygon);
366 match zero_width_outcome(&rings) {
367 ZeroWidthOutcome::RingsStand => Ok(MultiPolygon(
368 rings.into_iter().map(Polygon::new).collect::<Vec<_>>(),
369 )),
370 ZeroWidthOutcome::NeedsTraversal => Err(OverlayError::Unsupported),
371 }
372}
373
374impl<G> BufferStrategy<G, CartesianBuffer> for PointBuffer
377where
378 G: Point + PointMut + Default + Copy,
379 G::Scalar: CoordinateScalar + Into<f64> + FromF64,
380 <G::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
381{
382 fn apply(
383 &self,
384 point_geometry: &G,
385 settings: BufferSettings,
386 _coordinate_strategy: &CartesianBuffer,
387 ) -> Result<MultiPolygon<Polygon<G>>, OverlayError> {
388 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
389 return Err(OverlayError::Unsupported);
390 };
391 if !distance.is_finite() {
392 return Err(OverlayError::Unsupported);
393 }
394 if distance <= 0.0 {
395 return Ok(MultiPolygon(alloc::vec![]));
396 }
397 let point = match settings.point {
398 BufferPointStrategy::Circle { points_per_circle } => {
399 PointStrategy::Circle { points_per_circle }
400 }
401 BufferPointStrategy::Square => PointStrategy::Square,
402 };
403 let ring = buffer_point(point_geometry, distance, point);
404 Ok(MultiPolygon(alloc::vec![Polygon::new(ring)]))
405 }
406}
407
408impl<G> BufferStrategy<G, CartesianBuffer> for PolygonBuffer
411where
412 G: PolygonTrait,
413 G::Point: PointMut + Default + Copy,
414 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
415 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
416{
417 fn apply(
418 &self,
419 polygon: &G,
420 settings: BufferSettings,
421 _coordinate_strategy: &CartesianBuffer,
422 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
423 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
424 return Err(OverlayError::Unsupported);
425 };
426 if !distance.is_finite() {
427 return Err(OverlayError::Unsupported);
428 }
429 if distance == 0.0 {
430 return zero_width_polygon_buffer(polygon);
435 }
436 let outer = offset_ring(polygon.exterior(), distance, settings.join, true);
437 let inners = polygon
438 .interiors()
439 .map(|ring| offset_ring(ring, -distance, settings.join, false))
440 .collect::<Vec<_>>();
441 if offset_rings_need_dissolving(outer.as_ref(), &inners, distance) {
442 return dissolve_offset(polygon, distance, settings.join);
443 }
444 let Some(outer) = outer else {
445 return Ok(MultiPolygon(alloc::vec![]));
446 };
447 let inners = inners.into_iter().flatten().collect::<Vec<_>>();
448 let outer_vertices = distinct_vertices(&outer);
449 if inners.iter().any(|inner| {
450 let inner_vertices = distinct_vertices(inner);
451 outer_vertices
452 .iter()
453 .all(|point| point_in_or_on_ring(*point, &inner_vertices))
454 }) {
455 return Ok(MultiPolygon::new());
456 }
457
458 Ok(MultiPolygon(alloc::vec![Polygon::with_inners(
459 outer, inners,
460 )]))
461 }
462}
463
464impl<G> BufferStrategy<G, CartesianBuffer> for LinestringBuffer
465where
466 G: LinestringTrait,
467 G::Point: PointMut + Default + Copy,
468 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
469 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
470{
471 fn apply(
472 &self,
473 line: &G,
474 settings: BufferSettings,
475 _coordinate_strategy: &CartesianBuffer,
476 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
477 let (left, right) = match settings.distance {
478 BufferDistanceStrategy::Symmetric(distance) => (distance, distance),
479 BufferDistanceStrategy::Asymmetric { left, right } => (left, right),
480 };
481 if !left.is_finite() || !right.is_finite() || left < 0.0 || right < 0.0 {
482 return Err(OverlayError::Unsupported);
483 }
484 if left == 0.0 && right == 0.0 {
485 return Ok(MultiPolygon(alloc::vec![]));
486 }
487 let polygon = buffer_linestring(line, left, right, settings.join, settings.end)?;
488 Ok(MultiPolygon(alloc::vec![polygon]))
489 }
490}
491
492impl<G> BufferStrategy<G, CartesianBuffer> for SegmentBuffer
493where
494 G: SegmentTrait,
495 G::Point: PointMut + Default + Copy,
496 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
497 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
498{
499 fn apply(
500 &self,
501 segment: &G,
502 settings: BufferSettings,
503 coordinate_strategy: &CartesianBuffer,
504 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
505 let line: Linestring<G::Point> =
506 Linestring::from_vec(alloc::vec![segment_start(segment), segment_end(segment)]);
507 LinestringBuffer.apply(&line, settings, coordinate_strategy)
508 }
509}
510
511impl<G> BufferStrategy<G, CartesianBuffer> for RingBuffer
512where
513 G: RingTrait,
514 G::Point: PointMut + Default + Copy,
515 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
516 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
517{
518 fn apply(
519 &self,
520 ring: &G,
521 settings: BufferSettings,
522 coordinate_strategy: &CartesianBuffer,
523 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
524 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
525 return Err(OverlayError::Unsupported);
526 };
527 if !distance.is_finite() || distance == 0.0 {
528 return Err(OverlayError::Unsupported);
529 }
530 let polygon: Polygon<G::Point> =
534 Polygon::new(Ring::from_vec(ring.points().copied().collect()));
535 PolygonBuffer.apply(&polygon, settings, coordinate_strategy)
536 }
537}
538
539impl<G> BufferStrategy<G, CartesianBuffer> for BoxBuffer
540where
541 G: BoxTrait,
542 G::Point: PointMut + Default + Copy,
543 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
544 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
545{
546 fn apply(
547 &self,
548 bounds: &G,
549 settings: BufferSettings,
550 coordinate_strategy: &CartesianBuffer,
551 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
552 let minimum = box_min(bounds);
553 let maximum = box_max(bounds);
554 let min_x = minimum.get::<0>().into();
555 let min_y = minimum.get::<1>().into();
556 let max_x = maximum.get::<0>().into();
557 let max_y = maximum.get::<1>().into();
558 let ring: Ring<G::Point> = Ring::from_vec(alloc::vec![
559 make_point(min_x, min_y),
560 make_point(min_x, max_y),
561 make_point(max_x, max_y),
562 make_point(max_x, min_y),
563 make_point(min_x, min_y),
564 ]);
565 RingBuffer.apply(&ring, settings, coordinate_strategy)
566 }
567}
568
569impl<G> BufferStrategy<G, CartesianBuffer> for MultiPointBuffer
570where
571 G: MultiPointTrait<ItemPoint = <G as Geometry>::Point>,
572 G::Point: PointMut + Default + Copy,
573 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
574 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
575{
576 fn apply(
577 &self,
578 points: &G,
579 settings: BufferSettings,
580 coordinate_strategy: &CartesianBuffer,
581 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
582 let mut output = MultiPolygon::new();
583 for point in points.points() {
584 output
585 .0
586 .extend(PointBuffer.apply(point, settings, coordinate_strategy)?.0);
587 }
588 crate::merge::merge_polygons(output.0)
589 }
590}
591
592impl<G> BufferStrategy<G, CartesianBuffer> for MultiLinestringBuffer
593where
594 G: MultiLinestringTrait,
595 G::Point: PointMut + Default + Copy,
596 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
597 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
598{
599 fn apply(
600 &self,
601 lines: &G,
602 settings: BufferSettings,
603 coordinate_strategy: &CartesianBuffer,
604 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
605 let mut output = MultiPolygon::new();
606 for line in lines.linestrings() {
607 output.0.extend(
608 LinestringBuffer
609 .apply(line, settings, coordinate_strategy)?
610 .0,
611 );
612 }
613 crate::merge::merge_polygons(output.0)
614 }
615}
616
617impl<G> BufferStrategy<G, CartesianBuffer> for MultiPolygonBuffer
618where
619 G: MultiPolygonTrait,
620 G::Point: PointMut + Default + Copy,
621 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
622 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
623{
624 fn apply(
625 &self,
626 polygons: &G,
627 settings: BufferSettings,
628 coordinate_strategy: &CartesianBuffer,
629 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
630 let mut output = MultiPolygon::new();
631 for polygon in polygons.polygons() {
632 output.0.extend(
633 PolygonBuffer
634 .apply(polygon, settings, coordinate_strategy)?
635 .0,
636 );
637 }
638 crate::merge::merge_polygons(output.0)
639 }
640}
641
642trait AngularCoordinateSystem {
643 type Units: AngleUnit;
644}
645
646impl<Units: AngleUnit> AngularCoordinateSystem for Spherical<Units> {
647 type Units = Units;
648}
649
650impl<Units: AngleUnit> AngularCoordinateSystem for Geographic<Units> {
651 type Units = Units;
652}
653
654#[derive(Debug, Clone, Copy)]
655struct LocalProjection {
656 longitude: f64,
657 latitude: f64,
658 east_scale: f64,
659 north_scale: f64,
660}
661
662impl LocalProjection {
663 fn project(self, longitude: f64, latitude: f64) -> (f64, f64) {
664 let mut delta_longitude = longitude - self.longitude;
665 if delta_longitude > core::f64::consts::PI {
666 delta_longitude -= 2.0 * core::f64::consts::PI;
667 } else if delta_longitude < -core::f64::consts::PI {
668 delta_longitude += 2.0 * core::f64::consts::PI;
669 }
670 (
671 delta_longitude * self.east_scale,
672 (latitude - self.latitude) * self.north_scale,
673 )
674 }
675
676 fn unproject(self, x: f64, y: f64) -> (f64, f64) {
677 let mut longitude = self.longitude + x / self.east_scale;
678 if longitude > core::f64::consts::PI {
679 longitude -= 2.0 * core::f64::consts::PI;
680 } else if longitude < -core::f64::consts::PI {
681 longitude += 2.0 * core::f64::consts::PI;
682 }
683 (longitude, self.latitude + y / self.north_scale)
684 }
685}
686
687trait AngularBufferProjection {
688 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError>;
689}
690
691impl AngularBufferProjection for SphericalBuffer {
692 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError> {
693 if !self.radius.is_finite() || self.radius <= 0.0 {
694 return Err(OverlayError::Unsupported);
695 }
696 let longitude_scale = cos(latitude);
697 if longitude_scale.abs() <= f64::EPSILON {
698 return Err(OverlayError::Unsupported);
699 }
700 let east_scale = self.radius * longitude_scale;
701 Ok(LocalProjection {
702 longitude,
703 latitude,
704 east_scale,
705 north_scale: self.radius,
706 })
707 }
708}
709
710impl AngularBufferProjection for GeographicBuffer {
711 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError> {
712 let spheroid = self.spheroid;
713 if !spheroid.equatorial_radius.is_finite()
714 || spheroid.equatorial_radius <= 0.0
715 || !spheroid.flattening.is_finite()
716 || !(0.0..1.0).contains(&spheroid.flattening)
717 {
718 return Err(OverlayError::Unsupported);
719 }
720
721 let eccentricity_squared = spheroid.eccentricity_squared();
722 let sin_latitude = sin(latitude);
723 let denominator = sqrt(1.0 - eccentricity_squared * sin_latitude * sin_latitude);
724 let prime_vertical = spheroid.equatorial_radius / denominator;
725 let meridional = spheroid.equatorial_radius * (1.0 - eccentricity_squared)
726 / (denominator * denominator * denominator);
727 let longitude_scale = cos(latitude);
728 if longitude_scale.abs() <= f64::EPSILON {
729 return Err(OverlayError::Unsupported);
730 }
731 let east_scale = prime_vertical * longitude_scale;
732 Ok(LocalProjection {
733 longitude,
734 latitude,
735 east_scale,
736 north_scale: meridional,
737 })
738 }
739}
740
741fn angular_coordinates<P>(point: &P) -> (f64, f64)
742where
743 P: Point,
744 P::Scalar: Into<f64>,
745 P::Cs: AngularCoordinateSystem,
746{
747 let longitude = <P::Cs as AngularCoordinateSystem>::Units::to_radians(point.get::<0>().into());
748 let latitude = <P::Cs as AngularCoordinateSystem>::Units::to_radians(point.get::<1>().into());
749 (longitude, latitude)
750}
751
752fn angular_point<P>(longitude: f64, latitude: f64) -> P
753where
754 P: PointMut + Default,
755 P::Scalar: FromF64,
756 P::Cs: AngularCoordinateSystem,
757{
758 let mut point = P::default();
759 let longitude = <P::Cs as AngularCoordinateSystem>::Units::from_radians(longitude);
760 let latitude = <P::Cs as AngularCoordinateSystem>::Units::from_radians(latitude);
761 point.set::<0>(P::Scalar::from_f64(longitude));
762 point.set::<1>(P::Scalar::from_f64(latitude));
763 point
764}
765
766fn projection_center(coordinates: &[(f64, f64)]) -> Result<(f64, f64), OverlayError> {
767 if coordinates.is_empty() {
768 return Err(OverlayError::Unsupported);
769 }
770 let mut longitude_sine = 0.0;
771 let mut longitude_cosine = 0.0;
772 let mut latitude = 0.0;
773 for &(longitude, point_latitude) in coordinates {
774 longitude_sine += sin(longitude);
775 longitude_cosine += cos(longitude);
776 latitude += point_latitude;
777 }
778 let count = coordinates.len() as f64;
779 Ok((atan2(longitude_sine, longitude_cosine), latitude / count))
780}
781
782type ProjectedPoint = Point2D<f64, Cartesian>;
783
784fn projected_point<P>(point: &P, projection: LocalProjection) -> ProjectedPoint
785where
786 P: Point,
787 P::Scalar: Into<f64>,
788 P::Cs: AngularCoordinateSystem,
789{
790 let (longitude, latitude) = angular_coordinates(point);
791 let (x, y) = projection.project(longitude, latitude);
792 ProjectedPoint::new(x, y)
793}
794
795fn projected_ring<R>(ring: &R, projection: LocalProjection) -> Ring<ProjectedPoint>
796where
797 R: RingTrait,
798 R::Point: Point,
799 <R::Point as Point>::Scalar: Into<f64>,
800 <R::Point as Point>::Cs: AngularCoordinateSystem,
801{
802 Ring::from_vec(
803 ring.points()
804 .map(|point| projected_point(point, projection))
805 .collect(),
806 )
807}
808
809fn projected_polygon<G>(polygon: &G, projection: LocalProjection) -> Polygon<ProjectedPoint>
810where
811 G: PolygonTrait,
812 G::Point: Point,
813 <G::Point as Point>::Scalar: Into<f64>,
814 <G::Point as Point>::Cs: AngularCoordinateSystem,
815{
816 Polygon::with_inners(
817 projected_ring(polygon.exterior(), projection),
818 polygon
819 .interiors()
820 .map(|ring| projected_ring(ring, projection))
821 .collect(),
822 )
823}
824
825fn unprojected_buffer<P>(
826 polygons: MultiPolygon<Polygon<ProjectedPoint>>,
827 projection: LocalProjection,
828) -> MultiPolygon<Polygon<P>>
829where
830 P: PointMut + Default,
831 P::Scalar: FromF64,
832 P::Cs: AngularCoordinateSystem,
833{
834 MultiPolygon::from_vec(
835 polygons
836 .0
837 .into_iter()
838 .map(|polygon| {
839 let outer = Ring::from_vec(
840 polygon
841 .outer
842 .0
843 .into_iter()
844 .map(|point| {
845 let (longitude, latitude) = projection.unproject(point.x(), point.y());
846 angular_point(longitude, latitude)
847 })
848 .collect(),
849 );
850 let inners = polygon
851 .inners
852 .into_iter()
853 .map(|ring| {
854 Ring::from_vec(
855 ring.0
856 .into_iter()
857 .map(|point| {
858 let (longitude, latitude) =
859 projection.unproject(point.x(), point.y());
860 angular_point(longitude, latitude)
861 })
862 .collect(),
863 )
864 })
865 .collect();
866 Polygon::with_inners(outer, inners)
867 })
868 .collect(),
869 )
870}
871
872fn projection_for_points<'a, P>(
873 points: impl IntoIterator<Item = &'a P>,
874 strategy: &impl AngularBufferProjection,
875) -> Result<LocalProjection, OverlayError>
876where
877 P: Point + 'a,
878 P::Scalar: Into<f64>,
879 P::Cs: AngularCoordinateSystem,
880{
881 let coordinates: Vec<_> = points.into_iter().map(angular_coordinates).collect();
882 let (longitude, latitude) = projection_center(&coordinates)?;
883 strategy.projection(longitude, latitude)
884}
885
886fn projected_point_apply<P>(
887 point: &P,
888 settings: BufferSettings,
889 strategy: &impl AngularBufferProjection,
890) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
891where
892 P: Point + PointMut + Default + Copy,
893 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
894 P::Cs: AngularCoordinateSystem,
895{
896 let projection = projection_for_points(core::iter::once(point), strategy)?;
897 let point = projected_point(point, projection);
898 let output = PointBuffer.apply(&point, settings, &CartesianBuffer)?;
899 Ok(unprojected_buffer(output, projection))
900}
901
902fn projected_linestring_apply<L>(
903 line: &L,
904 settings: BufferSettings,
905 strategy: &impl AngularBufferProjection,
906) -> Result<MultiPolygon<Polygon<L::Point>>, OverlayError>
907where
908 L: LinestringTrait,
909 L::Point: PointMut + Default + Copy,
910 <L::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
911 <L::Point as Point>::Cs: AngularCoordinateSystem,
912{
913 let projection = projection_for_points(line.points(), strategy)?;
914 let projected = Linestring::from_vec(
915 line.points()
916 .map(|point| projected_point(point, projection))
917 .collect(),
918 );
919 let output = LinestringBuffer.apply(&projected, settings, &CartesianBuffer)?;
920 Ok(unprojected_buffer(output, projection))
921}
922
923fn projected_ring_apply<R>(
924 ring: &R,
925 settings: BufferSettings,
926 strategy: &impl AngularBufferProjection,
927) -> Result<MultiPolygon<Polygon<R::Point>>, OverlayError>
928where
929 R: RingTrait,
930 R::Point: PointMut + Default + Copy,
931 <R::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
932 <R::Point as Point>::Cs: AngularCoordinateSystem,
933{
934 let projection = projection_for_points(ring.points(), strategy)?;
935 let output = RingBuffer.apply(
936 &projected_ring(ring, projection),
937 settings,
938 &CartesianBuffer,
939 )?;
940 Ok(unprojected_buffer(output, projection))
941}
942
943fn projected_polygon_apply<G>(
944 polygon: &G,
945 settings: BufferSettings,
946 strategy: &impl AngularBufferProjection,
947) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
948where
949 G: PolygonTrait,
950 G::Point: PointMut + Default + Copy,
951 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
952 <G::Point as Point>::Cs: AngularCoordinateSystem,
953{
954 let mut coordinates = polygon
955 .exterior()
956 .points()
957 .map(angular_coordinates)
958 .collect::<Vec<_>>();
959 for ring in polygon.interiors() {
960 coordinates.extend(ring.points().map(angular_coordinates));
961 }
962 let (longitude, latitude) = projection_center(&coordinates)?;
963 let projection = strategy.projection(longitude, latitude)?;
964 let output = PolygonBuffer.apply(
965 &projected_polygon(polygon, projection),
966 settings,
967 &CartesianBuffer,
968 )?;
969 Ok(unprojected_buffer(output, projection))
970}
971
972macro_rules! impl_angular_buffer_strategy {
973 ($strategy:ty, $family:ty) => {
974 impl<G> BufferStrategy<G, $strategy> for PointBuffer
975 where
976 G: Point + PointMut + Default + Copy,
977 G::Scalar: CoordinateScalar + Into<f64> + FromF64,
978 G::Cs: AngularCoordinateSystem,
979 <G::Cs as CoordinateSystem>::Family: SameAs<$family>,
980 {
981 fn apply(
982 &self,
983 geometry: &G,
984 settings: BufferSettings,
985 coordinate_strategy: &$strategy,
986 ) -> Result<MultiPolygon<Polygon<G>>, OverlayError> {
987 projected_point_apply(geometry, settings, coordinate_strategy)
988 }
989 }
990
991 impl<G> BufferStrategy<G, $strategy> for LinestringBuffer
992 where
993 G: LinestringTrait,
994 G::Point: PointMut + Default + Copy,
995 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
996 <G::Point as Point>::Cs: AngularCoordinateSystem,
997 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
998 {
999 fn apply(
1000 &self,
1001 geometry: &G,
1002 settings: BufferSettings,
1003 coordinate_strategy: &$strategy,
1004 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1005 projected_linestring_apply(geometry, settings, coordinate_strategy)
1006 }
1007 }
1008
1009 impl<G> BufferStrategy<G, $strategy> for SegmentBuffer
1010 where
1011 G: SegmentTrait,
1012 G::Point: PointMut + Default + Copy,
1013 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1014 <G::Point as Point>::Cs: AngularCoordinateSystem,
1015 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1016 {
1017 fn apply(
1018 &self,
1019 geometry: &G,
1020 settings: BufferSettings,
1021 coordinate_strategy: &$strategy,
1022 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1023 let line = Linestring::from_vec(alloc::vec![
1024 segment_start(geometry),
1025 segment_end(geometry),
1026 ]);
1027 projected_linestring_apply(&line, settings, coordinate_strategy)
1028 }
1029 }
1030
1031 impl<G> BufferStrategy<G, $strategy> for RingBuffer
1032 where
1033 G: RingTrait,
1034 G::Point: PointMut + Default + Copy,
1035 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1036 <G::Point as Point>::Cs: AngularCoordinateSystem,
1037 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1038 {
1039 fn apply(
1040 &self,
1041 geometry: &G,
1042 settings: BufferSettings,
1043 coordinate_strategy: &$strategy,
1044 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1045 projected_ring_apply(geometry, settings, coordinate_strategy)
1046 }
1047 }
1048
1049 impl<G> BufferStrategy<G, $strategy> for PolygonBuffer
1050 where
1051 G: PolygonTrait,
1052 G::Point: PointMut + Default + Copy,
1053 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1054 <G::Point as Point>::Cs: AngularCoordinateSystem,
1055 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1056 {
1057 fn apply(
1058 &self,
1059 geometry: &G,
1060 settings: BufferSettings,
1061 coordinate_strategy: &$strategy,
1062 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1063 projected_polygon_apply(geometry, settings, coordinate_strategy)
1064 }
1065 }
1066
1067 impl<G> BufferStrategy<G, $strategy> for BoxBuffer
1068 where
1069 G: BoxTrait,
1070 G::Point: PointMut + Default + Copy,
1071 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1072 <G::Point as Point>::Cs: AngularCoordinateSystem,
1073 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1074 {
1075 fn apply(
1076 &self,
1077 geometry: &G,
1078 settings: BufferSettings,
1079 coordinate_strategy: &$strategy,
1080 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1081 let minimum = box_min(geometry);
1082 let maximum = box_max(geometry);
1083 let projection = projection_for_points([&minimum, &maximum], coordinate_strategy)?;
1084 let projected = ModelBox::from_corners(
1085 projected_point(&minimum, projection),
1086 projected_point(&maximum, projection),
1087 );
1088 let output = BoxBuffer.apply(&projected, settings, &CartesianBuffer)?;
1089 Ok(unprojected_buffer(output, projection))
1090 }
1091 }
1092
1093 impl<G> BufferStrategy<G, $strategy> for MultiPointBuffer
1094 where
1095 G: MultiPointTrait<ItemPoint = <G as Geometry>::Point>,
1096 G::Point: PointMut + Default + Copy,
1097 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1098 <G::Point as Point>::Cs: AngularCoordinateSystem,
1099 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1100 {
1101 fn apply(
1102 &self,
1103 geometry: &G,
1104 settings: BufferSettings,
1105 coordinate_strategy: &$strategy,
1106 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1107 let projection = projection_for_points(geometry.points(), coordinate_strategy)?;
1108 let projected = MultiPoint::from_vec(
1109 geometry
1110 .points()
1111 .map(|point| projected_point(point, projection))
1112 .collect(),
1113 );
1114 let output = MultiPointBuffer.apply(&projected, settings, &CartesianBuffer)?;
1115 Ok(unprojected_buffer(output, projection))
1116 }
1117 }
1118
1119 impl<G> BufferStrategy<G, $strategy> for MultiLinestringBuffer
1120 where
1121 G: MultiLinestringTrait,
1122 G::Point: PointMut + Default + Copy,
1123 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1124 <G::Point as Point>::Cs: AngularCoordinateSystem,
1125 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1126 {
1127 fn apply(
1128 &self,
1129 geometry: &G,
1130 settings: BufferSettings,
1131 coordinate_strategy: &$strategy,
1132 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1133 let coordinates = geometry
1134 .linestrings()
1135 .flat_map(|line| line.points().map(angular_coordinates))
1136 .collect::<Vec<_>>();
1137 let (longitude, latitude) = projection_center(&coordinates)?;
1138 let projection = coordinate_strategy.projection(longitude, latitude)?;
1139 let projected = MultiLinestring::from_vec(
1140 geometry
1141 .linestrings()
1142 .map(|line| {
1143 Linestring::from_vec(
1144 line.points()
1145 .map(|point| projected_point(point, projection))
1146 .collect(),
1147 )
1148 })
1149 .collect(),
1150 );
1151 let output = MultiLinestringBuffer.apply(&projected, settings, &CartesianBuffer)?;
1152 Ok(unprojected_buffer(output, projection))
1153 }
1154 }
1155
1156 impl<G> BufferStrategy<G, $strategy> for MultiPolygonBuffer
1157 where
1158 G: MultiPolygonTrait,
1159 G::Point: PointMut + Default + Copy,
1160 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1161 <G::Point as Point>::Cs: AngularCoordinateSystem,
1162 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1163 {
1164 fn apply(
1165 &self,
1166 geometry: &G,
1167 settings: BufferSettings,
1168 coordinate_strategy: &$strategy,
1169 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1170 let coordinates = geometry
1171 .polygons()
1172 .flat_map(|polygon| {
1173 polygon
1174 .exterior()
1175 .points()
1176 .chain(polygon.interiors().flat_map(RingTrait::points))
1177 .map(angular_coordinates)
1178 })
1179 .collect::<Vec<_>>();
1180 let (longitude, latitude) = projection_center(&coordinates)?;
1181 let projection = coordinate_strategy.projection(longitude, latitude)?;
1182 let projected = MultiPolygon::from_vec(
1183 geometry
1184 .polygons()
1185 .map(|polygon| projected_polygon(polygon, projection))
1186 .collect(),
1187 );
1188 let output = MultiPolygonBuffer.apply(&projected, settings, &CartesianBuffer)?;
1189 Ok(unprojected_buffer(output, projection))
1190 }
1191 }
1192 };
1193}
1194
1195impl_angular_buffer_strategy!(SphericalBuffer, SphericalFamily);
1196impl_angular_buffer_strategy!(GeographicBuffer, GeographicFamily);
1197
1198#[inline]
1219#[must_use]
1220pub fn buffer_point<P>(center: &P, distance: f64, strategy: PointStrategy) -> Ring<P>
1221where
1222 P: PointMut + Default + Copy,
1223 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
1224 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1225{
1226 let cx: f64 = center.get::<0>().into();
1227 let cy: f64 = center.get::<1>().into();
1228 match strategy {
1229 PointStrategy::Circle { points_per_circle } => {
1230 circle_ring(cx, cy, distance, points_per_circle.max(3))
1231 }
1232 PointStrategy::Square => {
1233 let d = distance;
1234 Ring::from_vec(alloc::vec![
1239 make_point(cx - d, cy - d),
1240 make_point(cx - d, cy + d),
1241 make_point(cx + d, cy + d),
1242 make_point(cx + d, cy - d),
1243 make_point(cx - d, cy - d),
1244 ])
1245 }
1246 }
1247}
1248
1249#[inline]
1279#[must_use]
1280pub fn buffer_convex_polygon<G, P>(polygon: &G, distance: f64, join: JoinStrategy) -> Polygon<P>
1281where
1282 G: PolygonTrait<Point = P>,
1283 P: PointMut + Default + Copy,
1284 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
1285 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1286{
1287 let strategy = match join {
1288 JoinStrategy::Round { points_per_circle } => {
1289 BufferJoinStrategy::Round { points_per_circle }
1290 }
1291 JoinStrategy::Miter => BufferJoinStrategy::Miter {
1292 limit: f64::INFINITY,
1293 },
1294 };
1295 offset_ring(polygon.exterior(), distance, strategy, true)
1296 .map_or_else(|| Polygon::new(Ring::new()), Polygon::new)
1297}
1298
1299fn offset_ring<R, P>(
1300 ring: &R,
1301 distance: f64,
1302 join: BufferJoinStrategy,
1303 clockwise: bool,
1304) -> Option<Ring<P>>
1305where
1306 R: RingTrait<Point = P>,
1307 P: PointMut + Default + Copy,
1308 P::Scalar: Into<f64> + FromF64,
1309{
1310 let mut vertices = distinct_vertices(ring);
1311 if vertices.len() < 3 || !distance.is_finite() || distance == 0.0 {
1312 return None;
1313 }
1314 if signed_area_ccw_positive(&vertices) < 0.0 {
1315 vertices.reverse();
1316 }
1317
1318 let count = vertices.len();
1319 let mut boundary = Vec::new();
1320 for index in 0..count {
1321 let previous = vertices[(index + count - 1) % count];
1322 let vertex = vertices[index];
1323 let next = vertices[(index + 1) % count];
1324 let incoming = (vertex.0 - previous.0, vertex.1 - previous.1);
1325 let outgoing = (next.0 - vertex.0, next.1 - vertex.1);
1326 let incoming_normal = outward_normal(incoming.0, incoming.1);
1327 let outgoing_normal = outward_normal(outgoing.0, outgoing.1);
1328 let before = (
1329 vertex.0 + incoming_normal.0 * distance,
1330 vertex.1 + incoming_normal.1 * distance,
1331 );
1332 let after = (
1333 vertex.0 + outgoing_normal.0 * distance,
1334 vertex.1 + outgoing_normal.1 * distance,
1335 );
1336 let intersection = line_intersection(before, incoming, after, outgoing);
1337 let cross = incoming.0 * outgoing.1 - incoming.1 * outgoing.0;
1338 let exterior_join = cross * distance > 0.0;
1339
1340 if !exterior_join {
1341 if let Some(point) = intersection {
1342 boundary.push(point);
1343 } else {
1344 boundary.push(after);
1345 }
1346 continue;
1347 }
1348
1349 push_join_points(
1350 join,
1351 vertex,
1352 before,
1353 after,
1354 intersection,
1355 distance,
1356 &mut boundary,
1357 );
1358 }
1359
1360 boundary.dedup();
1361 if boundary.len() < 3 || signed_area_ccw_positive(&boundary).abs() <= f64::EPSILON {
1362 return None;
1363 }
1364 if distance < 0.0 {
1365 let clearance = distance.abs();
1366 let tolerance = mul_add(clearance, 1e-9, f64::EPSILON * 16.0);
1367 if boundary.iter().any(|point| {
1368 !point_in_or_on_ring(*point, &vertices)
1369 || minimum_boundary_distance(*point, &vertices) + tolerance < clearance
1370 }) {
1371 return None;
1372 }
1373 }
1374 if clockwise == (signed_area_ccw_positive(&boundary) > 0.0) {
1375 boundary.reverse();
1376 }
1377 boundary.push(boundary[0]);
1378 Some(Ring::from_vec(
1379 boundary
1380 .into_iter()
1381 .map(|(x, y)| make_point(x, y))
1382 .collect(),
1383 ))
1384}
1385
1386fn push_join_points(
1395 join: BufferJoinStrategy,
1396 vertex: (f64, f64),
1397 before: (f64, f64),
1398 after: (f64, f64),
1399 intersection: Option<(f64, f64)>,
1400 distance: f64,
1401 boundary: &mut Vec<(f64, f64)>,
1402) {
1403 match join {
1404 BufferJoinStrategy::Round { points_per_circle } => {
1405 boundary.push(before);
1411 push_arc_between(
1412 boundary,
1413 vertex,
1414 before,
1415 after,
1416 distance.abs(),
1417 points_per_circle.max(4),
1418 distance > 0.0,
1419 );
1420 boundary.push(after);
1421 }
1422 BufferJoinStrategy::Miter { limit } => {
1423 if let Some(point) = intersection {
1424 let miter_length = hypot(point.0 - vertex.0, point.1 - vertex.1);
1425 if point.0.is_finite()
1426 && point.1.is_finite()
1427 && miter_length <= limit.max(1.0) * distance.abs()
1428 {
1429 boundary.push(point);
1430 } else {
1431 boundary.push(before);
1432 boundary.push(after);
1433 }
1434 } else {
1435 boundary.push(before);
1436 boundary.push(after);
1437 }
1438 }
1439 }
1440}
1441
1442fn offset_rings_need_dissolving<P>(
1459 outer: Option<&Ring<P>>,
1460 inners: &[Option<Ring<P>>],
1461 distance: f64,
1462) -> bool
1463where
1464 P: PointMut + Default + Copy,
1465 P::Scalar: CoordinateScalar + Into<f64>,
1466{
1467 let eroding = distance < 0.0;
1468 let Some(outer) = outer else {
1469 return eroding;
1470 };
1471 if ring_crosses_itself(outer) {
1472 return true;
1473 }
1474 let mut kept: Vec<&Ring<P>> = Vec::new();
1475 for inner in inners {
1476 match inner {
1477 Some(inner) => kept.push(inner),
1478 None if eroding => {}
1479 None => return true,
1480 }
1481 }
1482 if kept.iter().copied().any(ring_crosses_itself) {
1483 return true;
1484 }
1485 if !eroding {
1486 return false;
1489 }
1490 kept.iter().enumerate().any(|(index, inner)| {
1491 rings_cross(outer, inner)
1492 || kept[index + 1..]
1493 .iter()
1494 .any(|other| rings_cross(inner, other))
1495 })
1496}
1497
1498fn ring_sides<P>(ring: &Ring<P>) -> Vec<(P, P, [f64; 4])>
1500where
1501 P: Point + Copy,
1502 P::Scalar: Into<f64>,
1503{
1504 let points: Vec<P> = ring.points().copied().collect();
1505 points
1506 .windows(2)
1507 .map(|pair| {
1508 let (ax, ay): (f64, f64) = (pair[0].get::<0>().into(), pair[0].get::<1>().into());
1509 let (bx, by): (f64, f64) = (pair[1].get::<0>().into(), pair[1].get::<1>().into());
1510 (
1511 pair[0],
1512 pair[1],
1513 [ax.min(bx), ay.min(by), ax.max(bx), ay.max(by)],
1514 )
1515 })
1516 .collect()
1517}
1518
1519fn sides_meet<P>(one: &(P, P, [f64; 4]), two: &(P, P, [f64; 4])) -> bool
1524where
1525 P: PointMut + Default + Copy,
1526 P::Scalar: CoordinateScalar + Into<f64>,
1527{
1528 let (a, b) = (&one.2, &two.2);
1529 if a[0] > b[2] || b[0] > a[2] || a[1] > b[3] || b[1] > a[3] {
1530 return false;
1531 }
1532 !matches!(
1533 segment_intersection::<Segment<P>, P>(
1534 &Segment::new(one.0, one.1),
1535 &Segment::new(two.0, two.1)
1536 ),
1537 SegmentIntersection::Disjoint | SegmentIntersection::OutOfRange
1538 )
1539}
1540
1541fn ring_crosses_itself<P>(ring: &Ring<P>) -> bool
1543where
1544 P: PointMut + Default + Copy,
1545 P::Scalar: CoordinateScalar + Into<f64>,
1546{
1547 let sides = ring_sides(ring);
1548 let count = sides.len();
1549 (0..count).any(|first| {
1550 let last_neighbour = if first == 0 { count - 1 } else { count };
1551 (first + 2..last_neighbour).any(|second| sides_meet(&sides[first], &sides[second]))
1552 })
1553}
1554
1555fn rings_cross<P>(one: &Ring<P>, two: &Ring<P>) -> bool
1557where
1558 P: PointMut + Default + Copy,
1559 P::Scalar: CoordinateScalar + Into<f64>,
1560{
1561 let one = ring_sides(one);
1562 let two = ring_sides(two);
1563 one.iter()
1564 .any(|side| two.iter().any(|other| sides_meet(side, other)))
1565}
1566
1567fn dissolve_offset<G, P>(
1586 polygon: &G,
1587 distance: f64,
1588 join: BufferJoinStrategy,
1589) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
1590where
1591 G: PolygonTrait<Point = P>,
1592 P: PointMut + Default + Copy,
1593 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
1594 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1595{
1596 let mut pieces: Vec<Polygon<P>> = Vec::new();
1597 push_ring_pieces(polygon.exterior(), distance, join, &mut pieces);
1598 for ring in polygon.interiors() {
1599 push_ring_pieces(ring, -distance, join, &mut pieces);
1602 }
1603 let pieces = merge_pieces(pieces)?;
1604 if pieces.0.is_empty() {
1605 return Ok(MultiPolygon::new());
1606 }
1607 let original: MultiPolygon<Polygon<P>> = MultiPolygon(alloc::vec![Polygon::with_inners(
1608 Ring::from_vec(polygon.exterior().points().copied().collect()),
1609 polygon
1610 .interiors()
1611 .map(|ring| Ring::from_vec(ring.points().copied().collect()))
1612 .collect(),
1613 )]);
1614 if distance > 0.0 {
1615 union_multi(&original, &pieces)
1616 } else {
1617 difference_multi(&original, &pieces)
1618 }
1619}
1620
1621fn push_ring_pieces<R, P>(
1631 ring: &R,
1632 distance: f64,
1633 join: BufferJoinStrategy,
1634 pieces: &mut Vec<Polygon<P>>,
1635) where
1636 R: RingTrait<Point = P>,
1637 P: PointMut + Default + Copy,
1638 P::Scalar: Into<f64> + FromF64,
1639{
1640 let mut vertices = distinct_vertices(ring);
1641 vertices.dedup();
1642 while vertices.len() > 1 && vertices.last() == vertices.first() {
1643 vertices.pop();
1644 }
1645 if vertices.len() < 3 || distance == 0.0 {
1646 return;
1647 }
1648 if signed_area_ccw_positive(&vertices) < 0.0 {
1649 vertices.reverse();
1650 }
1651
1652 let count = vertices.len();
1653 for index in 0..count {
1654 let previous = vertices[(index + count - 1) % count];
1655 let vertex = vertices[index];
1656 let next = vertices[(index + 1) % count];
1657 let incoming = (vertex.0 - previous.0, vertex.1 - previous.1);
1658 let outgoing = (next.0 - vertex.0, next.1 - vertex.1);
1659 let incoming_normal = outward_normal(incoming.0, incoming.1);
1660 let outgoing_normal = outward_normal(outgoing.0, outgoing.1);
1661 let before = (
1662 vertex.0 + incoming_normal.0 * distance,
1663 vertex.1 + incoming_normal.1 * distance,
1664 );
1665 let after = (
1666 vertex.0 + outgoing_normal.0 * distance,
1667 vertex.1 + outgoing_normal.1 * distance,
1668 );
1669 let far = (
1670 next.0 + outgoing_normal.0 * distance,
1671 next.1 + outgoing_normal.1 * distance,
1672 );
1673 push_piece(pieces, alloc::vec![vertex, next, far, after]);
1674
1675 let cross = incoming.0 * outgoing.1 - incoming.1 * outgoing.0;
1676 if cross * distance > 0.0 {
1677 let intersection = line_intersection(before, incoming, after, outgoing);
1678 let mut wedge = alloc::vec![vertex, before];
1679 push_join_points(
1680 join,
1681 vertex,
1682 before,
1683 after,
1684 intersection,
1685 distance,
1686 &mut wedge,
1687 );
1688 wedge.push(after);
1689 push_piece(pieces, wedge);
1690 }
1691 }
1692}
1693
1694fn push_piece<P>(pieces: &mut Vec<Polygon<P>>, mut boundary: Vec<(f64, f64)>)
1696where
1697 P: PointMut + Default + Copy,
1698 P::Scalar: FromF64,
1699{
1700 boundary.dedup();
1701 if boundary.len() < 3 || signed_area_ccw_positive(&boundary).abs() <= f64::EPSILON {
1702 return;
1703 }
1704 boundary.push(boundary[0]);
1705 pieces.push(Polygon::new(Ring::from_vec(
1706 boundary
1707 .into_iter()
1708 .map(|(x, y)| make_point(x, y))
1709 .collect(),
1710 )));
1711}
1712
1713fn merge_pieces<P>(pieces: Vec<Polygon<P>>) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
1717where
1718 P: PointMut + Default + Copy,
1719 P::Scalar: CoordinateScalar + Into<f64>,
1720 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1721{
1722 let mut merged: Vec<MultiPolygon<Polygon<P>>> = pieces
1723 .into_iter()
1724 .map(|piece| MultiPolygon(alloc::vec![piece]))
1725 .collect();
1726 while merged.len() > 1 {
1727 let mut next = Vec::with_capacity(merged.len().div_ceil(2));
1728 let mut pairs = merged.into_iter();
1729 while let Some(left) = pairs.next() {
1730 match pairs.next() {
1731 Some(right) => next.push(union_multi(&left, &right)?),
1732 None => next.push(left),
1733 }
1734 }
1735 merged = next;
1736 }
1737 Ok(merged.pop().unwrap_or_default())
1738}
1739
1740fn buffer_linestring<L, P>(
1741 line: &L,
1742 left: f64,
1743 right: f64,
1744 join: BufferJoinStrategy,
1745 end: BufferEndStrategy,
1746) -> Result<Polygon<P>, OverlayError>
1747where
1748 L: LinestringTrait<Point = P>,
1749 P: PointMut + Default + Copy,
1750 P::Scalar: Into<f64> + FromF64,
1751{
1752 let mut vertices: Vec<(f64, f64)> = Vec::new();
1753 for point in line.points() {
1754 let value = (point.get::<0>().into(), point.get::<1>().into());
1755 if vertices.last().copied() != Some(value) {
1756 vertices.push(value);
1757 }
1758 }
1759 if vertices.len() < 2 {
1760 return Err(OverlayError::Unsupported);
1761 }
1762
1763 let left_path = offset_path(&vertices, left, true, join);
1764 let right_path = offset_path(&vertices, right, false, join);
1765 debug_assert!(!left_path.is_empty() && !right_path.is_empty());
1766 let mut boundary = left_path;
1767 match end {
1768 BufferEndStrategy::Flat => {}
1769 BufferEndStrategy::Round { points_per_circle } => {
1770 let center = *vertices.last().expect("linestring has an endpoint");
1771 let from = *boundary.last().expect("left path has an endpoint");
1772 let to = *right_path.last().expect("right path has an endpoint");
1773 push_end_arc(
1774 &mut boundary,
1775 center,
1776 from,
1777 to,
1778 points_per_circle.max(4),
1779 true,
1780 );
1781 }
1782 }
1783 boundary.extend(right_path.iter().rev().copied());
1784 if let BufferEndStrategy::Round { points_per_circle } = end {
1785 let to = boundary[0];
1786 push_end_arc(
1787 &mut boundary,
1788 vertices[0],
1789 right_path[0],
1790 to,
1791 points_per_circle.max(4),
1792 true,
1793 );
1794 }
1795 let first = boundary[0];
1796 boundary.push(first);
1797 Ok(Polygon::new(Ring::from_vec(
1798 boundary
1799 .into_iter()
1800 .map(|(x, y)| make_point(x, y))
1801 .collect(),
1802 )))
1803}
1804
1805fn offset_path(
1806 vertices: &[(f64, f64)],
1807 distance: f64,
1808 left: bool,
1809 join: BufferJoinStrategy,
1810) -> Vec<(f64, f64)> {
1811 let side = if left { 1.0 } else { -1.0 };
1812 let normals: Vec<(f64, f64)> = vertices
1813 .windows(2)
1814 .map(|edge| {
1815 let dx = edge[1].0 - edge[0].0;
1816 let dy = edge[1].1 - edge[0].1;
1817 let length = hypot(dx, dy);
1818 (-dy / length * side, dx / length * side)
1819 })
1820 .collect();
1821 let mut path = Vec::with_capacity(vertices.len());
1822 path.push((
1823 vertices[0].0 + normals[0].0 * distance,
1824 vertices[0].1 + normals[0].1 * distance,
1825 ));
1826 for index in 1..vertices.len() - 1 {
1827 let vertex = vertices[index];
1828 let previous = vertices[index - 1];
1829 let next = vertices[index + 1];
1830 let before = (
1831 vertex.0 + normals[index - 1].0 * distance,
1832 vertex.1 + normals[index - 1].1 * distance,
1833 );
1834 let after = (
1835 vertex.0 + normals[index].0 * distance,
1836 vertex.1 + normals[index].1 * distance,
1837 );
1838 let intersection = line_intersection(
1839 before,
1840 (vertex.0 - previous.0, vertex.1 - previous.1),
1841 after,
1842 (next.0 - vertex.0, next.1 - vertex.1),
1843 );
1844 match (join, intersection) {
1845 (BufferJoinStrategy::Miter { limit }, Some(point))
1846 if point.0.is_finite() && point.1.is_finite() =>
1847 {
1848 let miter_length = hypot(point.0 - vertex.0, point.1 - vertex.1);
1849 if distance == 0.0 || miter_length <= limit.max(1.0) * distance.abs() {
1850 path.push(point);
1851 } else {
1852 path.push(before);
1853 path.push(after);
1854 }
1855 }
1856 (BufferJoinStrategy::Round { points_per_circle }, _) => {
1857 path.push(before);
1858 push_arc_between(
1859 &mut path,
1860 vertex,
1861 before,
1862 after,
1863 distance.abs(),
1864 points_per_circle.max(4),
1865 left,
1866 );
1867 path.push(after);
1868 }
1869 _ => {
1870 path.push(before);
1871 path.push(after);
1872 }
1873 }
1874 }
1875 let last = vertices.len() - 1;
1876 path.push((
1877 vertices[last].0 + normals[last - 1].0 * distance,
1878 vertices[last].1 + normals[last - 1].1 * distance,
1879 ));
1880 path
1881}
1882
1883fn line_intersection(
1884 first_origin: (f64, f64),
1885 first_direction: (f64, f64),
1886 second_origin: (f64, f64),
1887 second_direction: (f64, f64),
1888) -> Option<(f64, f64)> {
1889 let denominator =
1890 first_direction.0 * second_direction.1 - first_direction.1 * second_direction.0;
1891 if denominator.abs() <= f64::EPSILON {
1892 return None;
1893 }
1894 let delta = (
1895 second_origin.0 - first_origin.0,
1896 second_origin.1 - first_origin.1,
1897 );
1898 let factor = (delta.0 * second_direction.1 - delta.1 * second_direction.0) / denominator;
1899 Some((
1900 first_origin.0 + factor * first_direction.0,
1901 first_origin.1 + factor * first_direction.1,
1902 ))
1903}
1904
1905fn push_arc_between(
1906 output: &mut Vec<(f64, f64)>,
1907 center: (f64, f64),
1908 from: (f64, f64),
1909 to: (f64, f64),
1910 radius: f64,
1911 points_per_circle: usize,
1912 counterclockwise: bool,
1913) {
1914 if radius == 0.0 {
1915 return;
1916 }
1917 let start = atan2(from.1 - center.1, from.0 - center.0);
1918 let mut end = atan2(to.1 - center.1, to.0 - center.0);
1919 if counterclockwise {
1920 while end < start {
1921 end += core::f64::consts::TAU;
1922 }
1923 } else {
1924 while end > start {
1925 end -= core::f64::consts::TAU;
1926 }
1927 }
1928 let sweep = end - start;
1929 let steps =
1930 ceil((sweep.abs() / core::f64::consts::TAU) * points_per_circle as f64).max(1.0) as usize;
1931 for step in 1..steps {
1932 let angle = start + sweep * step as f64 / steps as f64;
1933 output.push((
1934 center.0 + radius * cos(angle),
1935 center.1 + radius * sin(angle),
1936 ));
1937 }
1938}
1939
1940fn push_end_arc(
1941 output: &mut Vec<(f64, f64)>,
1942 center: (f64, f64),
1943 from: (f64, f64),
1944 to: (f64, f64),
1945 points_per_circle: usize,
1946 clockwise: bool,
1947) {
1948 let radius =
1949 hypot(from.0 - center.0, from.1 - center.1).max(hypot(to.0 - center.0, to.1 - center.1));
1950 push_arc_between(
1951 output,
1952 center,
1953 from,
1954 to,
1955 radius,
1956 points_per_circle,
1957 !clockwise,
1958 );
1959}
1960
1961fn make_point<P>(x: f64, y: f64) -> P
1963where
1964 P: PointMut + Default,
1965 P::Scalar: FromF64,
1966{
1967 let mut p = P::default();
1968 p.set::<0>(P::Scalar::from_f64(x));
1969 p.set::<1>(P::Scalar::from_f64(y));
1970 p
1971}
1972
1973fn circle_ring<P>(cx: f64, cy: f64, r: f64, segments: usize) -> Ring<P>
1975where
1976 P: PointMut + Default + Copy,
1977 P::Scalar: FromF64,
1978{
1979 let mut pts = Vec::with_capacity(segments + 1);
1980 let step = core::f64::consts::TAU / segments as f64;
1981 for k in 0..segments {
1982 let a = -step * k as f64;
1983 pts.push(make_point(cx + r * cos(a), cy + r * sin(a)));
1984 }
1985 pts.push(pts[0]);
1986 Ring::from_vec(pts)
1987}
1988
1989fn distinct_vertices<R>(ring: &R) -> Vec<(f64, f64)>
1992where
1993 R: RingTrait,
1994 <R::Point as Point>::Scalar: Into<f64>,
1995{
1996 let mut pts: Vec<(f64, f64)> = ring
1997 .points()
1998 .map(|p| (p.get::<0>().into(), p.get::<1>().into()))
1999 .collect();
2000 if pts.len() >= 2 {
2001 let first = pts[0];
2002 let last = pts[pts.len() - 1];
2003 if first == last {
2004 pts.pop();
2005 }
2006 }
2007 pts
2008}
2009
2010fn signed_area_ccw_positive(verts: &[(f64, f64)]) -> f64 {
2014 let n = verts.len();
2015 let mut acc = 0.0;
2016 for i in 0..n {
2017 let a = verts[i];
2018 let b = verts[(i + 1) % n];
2019 acc += a.0 * b.1 - b.0 * a.1;
2020 }
2021 acc * 0.5
2022}
2023
2024fn minimum_boundary_distance(point: (f64, f64), vertices: &[(f64, f64)]) -> f64 {
2025 let mut minimum = f64::INFINITY;
2026 for index in 0..vertices.len() {
2027 let start = vertices[index];
2028 let end = vertices[(index + 1) % vertices.len()];
2029 let delta = (end.0 - start.0, end.1 - start.1);
2030 let length_squared = delta.0 * delta.0 + delta.1 * delta.1;
2031 let fraction = if length_squared == 0.0 {
2032 0.0
2033 } else {
2034 (((point.0 - start.0) * delta.0 + (point.1 - start.1) * delta.1) / length_squared)
2035 .clamp(0.0, 1.0)
2036 };
2037 let nearest = (start.0 + fraction * delta.0, start.1 + fraction * delta.1);
2038 minimum = minimum.min(hypot(point.0 - nearest.0, point.1 - nearest.1));
2039 }
2040 minimum
2041}
2042
2043fn point_in_or_on_ring(point: (f64, f64), vertices: &[(f64, f64)]) -> bool {
2044 let scale = vertices.iter().fold(1.0_f64, |acc, vertex| {
2045 acc.max(vertex.0.abs()).max(vertex.1.abs())
2046 });
2047 if minimum_boundary_distance(point, vertices) <= scale * 1e-12 {
2048 return true;
2049 }
2050
2051 let mut inside = false;
2052 for index in 0..vertices.len() {
2053 let start = vertices[index];
2054 let end = vertices[(index + 1) % vertices.len()];
2055 if (start.1 > point.1) != (end.1 > point.1)
2056 && point.0 < (end.0 - start.0) * (point.1 - start.1) / (end.1 - start.1) + start.0
2057 {
2058 inside = !inside;
2059 }
2060 }
2061 inside
2062}
2063
2064fn outward_normal(dx: f64, dy: f64) -> (f64, f64) {
2067 let len = (dx * dx + dy * dy).sqrt();
2068 if len == 0.0 {
2069 return (0.0, 0.0);
2070 }
2071 (dy / len, -dx / len)
2073}
2074
2075#[cfg(test)]
2076mod tests {
2077 use super::{
2081 BufferJoinStrategy, dissolve_offset, offset_rings_need_dissolving, push_piece,
2082 push_ring_pieces,
2083 };
2084 use super::{JoinStrategy, PointStrategy, buffer, buffer_convex_polygon, buffer_point};
2085 use alloc::vec::Vec;
2086 use geometry_algorithm::ring_area;
2087 use geometry_cs::Cartesian;
2088 use geometry_model::{Point2D, Polygon, Ring, polygon};
2089 use geometry_trait::{MultiPolygon as _, Polygon as _};
2090
2091 type P = Point2D<f64, Cartesian>;
2092
2093 fn close(a: f64, b: f64, tol: f64) {
2094 assert!((a - b).abs() < tol, "expected {b}, got {a}");
2095 }
2096
2097 #[test]
2098 fn point_circle_area_approximates_pi_r_squared() {
2099 let disc = buffer_point(
2100 &P::new(0.0, 0.0),
2101 2.0,
2102 PointStrategy::Circle {
2103 points_per_circle: 720,
2104 },
2105 );
2106 close(ring_area(&disc).abs(), core::f64::consts::PI * 4.0, 1e-2);
2108 }
2109
2110 #[test]
2111 fn point_square_area() {
2112 let sq = buffer_point(&P::new(0.0, 0.0), 3.0, PointStrategy::Square);
2113 close(ring_area(&sq).abs(), 36.0, 1e-9);
2115 }
2116
2117 #[test]
2118 fn convex_square_round_buffer_area() {
2119 let sq: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
2120 let grown = buffer_convex_polygon(
2121 &sq,
2122 1.0,
2123 JoinStrategy::Round {
2124 points_per_circle: 720,
2125 },
2126 );
2127 let expected = 4.0 + 8.0 + core::f64::consts::PI;
2129 close(ring_area(grown.exterior()).abs(), expected, 1e-2);
2130 }
2131
2132 #[test]
2133 fn convex_triangle_round_buffer_grows() {
2134 let tri: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (0.0, 3.0), (0.0, 0.0)]];
2135 let base = ring_area(tri.exterior()).abs(); let grown = buffer_convex_polygon(
2137 &tri,
2138 0.5,
2139 JoinStrategy::Round {
2140 points_per_circle: 360,
2141 },
2142 );
2143 assert!(ring_area(grown.exterior()).abs() > base);
2145 }
2146
2147 #[test]
2148 fn buffer_is_winding_independent() {
2149 let ccw: Polygon<P> =
2154 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
2155 let cw: Polygon<P> = polygon![[(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0)]];
2156 let j = JoinStrategy::Round {
2157 points_per_circle: 720,
2158 };
2159 let expected = 4.0 + 8.0 + core::f64::consts::PI;
2160 let grown_from_counterclockwise =
2161 ring_area(buffer_convex_polygon(&ccw, 1.0, j).exterior()).abs();
2162 let grown_from_clockwise = ring_area(buffer_convex_polygon(&cw, 1.0, j).exterior()).abs();
2163 close(grown_from_counterclockwise, expected, 5e-2);
2164 close(grown_from_clockwise, expected, 5e-2);
2165 }
2166
2167 #[test]
2168 fn miter_square_area_is_16() {
2169 let sq: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
2175 let grown = buffer_convex_polygon(&sq, 1.0, JoinStrategy::Miter);
2176 close(ring_area(grown.exterior()).abs(), 16.0, 1e-9);
2177 }
2178
2179 #[test]
2180 fn miter_contains_near_corner_probe() {
2181 use geometry_algorithm::within;
2184 let sq: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
2185 let grown = buffer_convex_polygon(&sq, 1.0, JoinStrategy::Miter);
2186 let ang = 22.5_f64.to_radians();
2187 let probe = P::new(2.0 + 0.99 * ang.cos(), 2.0 + 0.99 * ang.sin());
2188 assert!(
2189 within(&probe, &grown),
2190 "buffer must contain points within d"
2191 );
2192 }
2193
2194 #[test]
2195 fn miter_is_superset_of_round_by_area() {
2196 let j_round = JoinStrategy::Round {
2199 points_per_circle: 720,
2200 };
2201 let square: Polygon<P> =
2202 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
2203 let triangle: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (0.0, 3.0), (0.0, 0.0)]];
2204 for pg in [square, triangle] {
2205 let m =
2206 ring_area(buffer_convex_polygon(&pg, 1.0, JoinStrategy::Miter).exterior()).abs();
2207 let r = ring_area(buffer_convex_polygon(&pg, 1.0, j_round).exterior()).abs();
2208 assert!(m >= r - 1e-9, "miter {m} must not be below round {r}");
2209 }
2210 }
2211
2212 #[test]
2213 fn non_model_polygon_buffers_like_the_model_polygon() {
2214 use geometry_model::Ring;
2218 use geometry_tag::PolygonTag;
2219 use geometry_trait::{Geometry, Polygon as PolygonTrait};
2220
2221 struct Parcel {
2222 outer: Ring<P>,
2223 }
2224 impl Geometry for Parcel {
2225 type Kind = PolygonTag;
2226 type Point = P;
2227 }
2228 impl PolygonTrait for Parcel {
2229 type Ring = Ring<P>;
2230 fn exterior(&self) -> &Ring<P> {
2231 &self.outer
2232 }
2233 fn interiors(&self) -> impl ExactSizeIterator<Item = &Ring<P>> {
2234 core::iter::empty()
2235 }
2236 }
2237
2238 let pts = vec![
2239 P::new(0.0, 0.0),
2240 P::new(2.0, 0.0),
2241 P::new(2.0, 2.0),
2242 P::new(0.0, 2.0),
2243 P::new(0.0, 0.0),
2244 ];
2245 let parcel = Parcel {
2246 outer: Ring::from_vec(pts.clone()),
2247 };
2248 let model: Polygon<P> = Polygon::new(Ring::from_vec(pts));
2249 let j = JoinStrategy::Round {
2250 points_per_circle: 360,
2251 };
2252 let parcel_buffer = buffer(&parcel, 1.0, j, PointStrategy::Square).unwrap();
2253 let model_buffer = buffer(&model, 1.0, j, PointStrategy::Square).unwrap();
2254 let a = ring_area(parcel_buffer.polygons().next().unwrap().exterior()).abs();
2255 let b = ring_area(model_buffer.polygons().next().unwrap().exterior()).abs();
2256 close(a, b, 1e-12);
2257 }
2258
2259 #[test]
2260 fn miter_is_winding_independent() {
2261 let ccw: Polygon<P> =
2263 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
2264 let cw: Polygon<P> = polygon![[(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0), (0.0, 0.0)]];
2265 close(
2266 ring_area(buffer_convex_polygon(&ccw, 1.0, JoinStrategy::Miter).exterior()).abs(),
2267 16.0,
2268 1e-9,
2269 );
2270 close(
2271 ring_area(buffer_convex_polygon(&cw, 1.0, JoinStrategy::Miter).exterior()).abs(),
2272 16.0,
2273 1e-9,
2274 );
2275 }
2276
2277 const JOIN: BufferJoinStrategy = BufferJoinStrategy::Miter { limit: 5.0 };
2287
2288 fn ring(points: &[(f64, f64)]) -> Ring<P> {
2289 Ring::from_vec(points.iter().map(|&(x, y)| P::new(x, y)).collect())
2290 }
2291
2292 fn unit_square() -> Ring<P> {
2293 ring(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)])
2294 }
2295
2296 #[test]
2300 fn a_ring_that_encloses_nothing_contributes_no_piece() {
2301 let mut pieces: Vec<Polygon<P>> = Vec::new();
2302
2303 push_ring_pieces(
2304 &ring(&[(0.0, 0.0), (1.0, 0.0), (0.0, 0.0)]),
2305 1.0,
2306 JOIN,
2307 &mut pieces,
2308 );
2309 assert!(pieces.is_empty(), "a two-vertex ring produced a piece");
2310
2311 push_ring_pieces(&ring(&[(0.0, 0.0)]), 1.0, JOIN, &mut pieces);
2312 assert!(pieces.is_empty(), "a single vertex produced a piece");
2313
2314 push_ring_pieces(&unit_square(), 0.0, JOIN, &mut pieces);
2315 assert!(pieces.is_empty(), "a zero distance produced a piece");
2316 }
2317
2318 #[test]
2323 fn repeated_closing_vertices_are_trimmed_before_the_sides_are_cut() {
2324 let mut clean: Vec<Polygon<P>> = Vec::new();
2325 push_ring_pieces(&unit_square(), 1.0, JOIN, &mut clean);
2326
2327 let mut repeated: Vec<Polygon<P>> = Vec::new();
2328 push_ring_pieces(
2329 &ring(&[
2330 (0.0, 0.0),
2331 (1.0, 0.0),
2332 (1.0, 1.0),
2333 (0.0, 1.0),
2334 (0.0, 0.0),
2335 (0.0, 0.0),
2336 (0.0, 0.0),
2337 ]),
2338 1.0,
2339 JOIN,
2340 &mut repeated,
2341 );
2342
2343 assert!(!clean.is_empty(), "the square should cut into pieces");
2344 assert_eq!(clean, repeated);
2345 }
2346
2347 #[test]
2351 fn a_piece_bounding_no_area_is_dropped() {
2352 let mut pieces: Vec<Polygon<P>> = Vec::new();
2353
2354 push_piece::<P>(&mut pieces, alloc::vec![(0.0, 0.0), (1.0, 0.0)]);
2355 assert!(pieces.is_empty(), "a two-point boundary was kept");
2356
2357 push_piece::<P>(&mut pieces, alloc::vec![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)]);
2358 assert!(pieces.is_empty(), "a collinear boundary was kept");
2359
2360 push_piece::<P>(&mut pieces, alloc::vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]);
2362 assert_eq!(pieces.len(), 1);
2363 assert_eq!(pieces[0].exterior().0.len(), 4);
2364 }
2365
2366 #[test]
2372 fn a_collapsed_hole_forces_the_pieces_path_only_when_growing() {
2373 let outer = unit_square();
2374 assert!(!offset_rings_need_dissolving(Some(&outer), &[None], -0.1));
2375 assert!(offset_rings_need_dissolving(Some(&outer), &[None], 0.1));
2376 }
2377
2378 #[test]
2382 fn a_self_crossing_hole_forces_the_pieces_path() {
2383 let outer = unit_square();
2384 let bow_tie = ring(&[(0.0, 0.0), (1.0, 1.0), (1.0, 0.0), (0.0, 1.0), (0.0, 0.0)]);
2386 assert!(offset_rings_need_dissolving(
2387 Some(&outer),
2388 &[Some(bow_tie.clone())],
2389 -0.1
2390 ));
2391 assert!(offset_rings_need_dissolving(
2392 Some(&outer),
2393 &[Some(bow_tie)],
2394 0.1
2395 ));
2396 }
2397
2398 #[test]
2403 fn a_dissolve_with_no_pieces_is_empty() {
2404 let square: Polygon<P> =
2405 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
2406 let dissolved = dissolve_offset(&square, 0.0, JOIN).expect("no pieces is not an error");
2407 assert!(dissolved.0.is_empty());
2408 }
2409}