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};
50use geometry_strategy::buffer::{
51 BufferDistanceStrategy, BufferEndStrategy, BufferJoinStrategy, BufferPointStrategy,
52 BufferSettings, CartesianBuffer, DefaultBuffer, DefaultBufferStrategy, GeographicBuffer,
53 SphericalBuffer,
54};
55use geometry_tag::{
56 BoxTag, LinestringTag, MultiLinestringTag, MultiPointTag, MultiPolygonTag, PointTag,
57 PolygonTag, RingTag, SameAs, SegmentTag,
58};
59use geometry_trait::{
60 Box as BoxTrait, Geometry, Linestring as LinestringTrait,
61 MultiLinestring as MultiLinestringTrait, MultiPoint as MultiPointTrait,
62 MultiPolygon as MultiPolygonTrait, Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
63 Segment as SegmentTrait, box_max, box_min, segment_end, segment_start,
64};
65
66use crate::operation::OverlayError;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum JoinStrategy {
74 Round {
77 points_per_circle: usize,
80 },
81 Miter,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum PointStrategy {
98 Circle {
101 points_per_circle: usize,
103 },
104 Square,
107}
108
109#[doc(hidden)]
115pub trait BufferStrategy<G: Geometry, CoordinateStrategy> {
116 fn apply(
117 &self,
118 geometry: &G,
119 settings: BufferSettings,
120 coordinate_strategy: &CoordinateStrategy,
121 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>;
122}
123
124#[doc(hidden)]
130pub trait BufferStrategyForKind {
131 type S: Default;
132}
133
134#[doc(hidden)]
139#[derive(Debug, Default, Clone, Copy)]
140pub struct PointBuffer;
141
142#[doc(hidden)]
147#[derive(Debug, Default, Clone, Copy)]
148pub struct PolygonBuffer;
149
150#[doc(hidden)]
152#[derive(Debug, Default, Clone, Copy)]
153pub struct LinestringBuffer;
154
155#[doc(hidden)]
157#[derive(Debug, Default, Clone, Copy)]
158pub struct SegmentBuffer;
159
160#[doc(hidden)]
162#[derive(Debug, Default, Clone, Copy)]
163pub struct RingBuffer;
164
165#[doc(hidden)]
167#[derive(Debug, Default, Clone, Copy)]
168pub struct BoxBuffer;
169
170#[doc(hidden)]
172#[derive(Debug, Default, Clone, Copy)]
173pub struct MultiPointBuffer;
174
175#[doc(hidden)]
177#[derive(Debug, Default, Clone, Copy)]
178pub struct MultiLinestringBuffer;
179
180#[doc(hidden)]
182#[derive(Debug, Default, Clone, Copy)]
183pub struct MultiPolygonBuffer;
184
185impl BufferStrategyForKind for PointTag {
188 type S = PointBuffer;
189}
190
191impl BufferStrategyForKind for PolygonTag {
194 type S = PolygonBuffer;
195}
196
197impl BufferStrategyForKind for LinestringTag {
198 type S = LinestringBuffer;
199}
200
201impl BufferStrategyForKind for SegmentTag {
202 type S = SegmentBuffer;
203}
204
205impl BufferStrategyForKind for RingTag {
206 type S = RingBuffer;
207}
208
209impl BufferStrategyForKind for BoxTag {
210 type S = BoxBuffer;
211}
212
213impl BufferStrategyForKind for MultiPointTag {
214 type S = MultiPointBuffer;
215}
216
217impl BufferStrategyForKind for MultiLinestringTag {
218 type S = MultiLinestringBuffer;
219}
220
221impl BufferStrategyForKind for MultiPolygonTag {
222 type S = MultiPolygonBuffer;
223}
224
225#[inline]
239#[must_use = "buffering can fail and the generated geometry should be used"]
240pub fn buffer<G>(
241 geometry: &G,
242 distance: f64,
243 join: JoinStrategy,
244 point: PointStrategy,
245) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
246where
247 G: Geometry,
248 G::Kind: BufferStrategyForKind,
249 <<G::Point as Point>::Cs as CoordinateSystem>::Family:
250 DefaultBuffer<<<G::Point as Point>::Cs as CoordinateSystem>::Family>,
251 <G::Kind as BufferStrategyForKind>::S: BufferStrategy<G, DefaultBufferStrategy<G>>,
252{
253 let settings = BufferSettings {
254 distance: BufferDistanceStrategy::Symmetric(distance),
255 side: geometry_strategy::buffer::BufferSideStrategy::Straight,
256 join: match join {
257 JoinStrategy::Round { points_per_circle } => {
258 BufferJoinStrategy::Round { points_per_circle }
259 }
260 JoinStrategy::Miter => BufferJoinStrategy::Miter { limit: 5.0 },
261 },
262 end: BufferEndStrategy::Round {
263 points_per_circle: 36,
264 },
265 point: match point {
266 PointStrategy::Circle { points_per_circle } => {
267 BufferPointStrategy::Circle { points_per_circle }
268 }
269 PointStrategy::Square => BufferPointStrategy::Square,
270 },
271 };
272 buffer_with(geometry, settings)
273}
274
275#[inline]
286#[must_use = "buffering can fail and the generated geometry should be used"]
287pub fn buffer_with<G>(
288 geometry: &G,
289 settings: BufferSettings,
290) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
291where
292 G: Geometry,
293 G::Kind: BufferStrategyForKind,
294 <<G::Point as Point>::Cs as CoordinateSystem>::Family:
295 DefaultBuffer<<<G::Point as Point>::Cs as CoordinateSystem>::Family>,
296 <G::Kind as BufferStrategyForKind>::S: BufferStrategy<G, DefaultBufferStrategy<G>>,
297{
298 buffer_with_strategy(geometry, settings, DefaultBufferStrategy::<G>::default())
299}
300
301#[inline]
319#[must_use = "buffering can fail and the generated geometry should be used"]
320#[allow(
321 clippy::needless_pass_by_value,
322 reason = "Boost buffer coordinate strategies are small value objects passed explicitly"
323)]
324pub fn buffer_with_strategy<G, CoordinateStrategy>(
325 geometry: &G,
326 settings: BufferSettings,
327 coordinate_strategy: CoordinateStrategy,
328) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
329where
330 G: Geometry,
331 G::Kind: BufferStrategyForKind,
332 <G::Kind as BufferStrategyForKind>::S: BufferStrategy<G, CoordinateStrategy>,
333{
334 <<G::Kind as BufferStrategyForKind>::S as Default>::default().apply(
335 geometry,
336 settings,
337 &coordinate_strategy,
338 )
339}
340
341fn zero_width_polygon_buffer<G>(
354 polygon: &G,
355) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
356where
357 G: PolygonTrait,
358 G::Point: PointMut + Default + Copy,
359 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
360{
361 use crate::piece_collection::{ZeroWidthOutcome, zero_width_outcome, zero_width_rings};
362
363 let rings = zero_width_rings(polygon);
364 match zero_width_outcome(&rings) {
365 ZeroWidthOutcome::RingsStand => Ok(MultiPolygon(
366 rings.into_iter().map(Polygon::new).collect::<Vec<_>>(),
367 )),
368 ZeroWidthOutcome::NeedsTraversal => Err(OverlayError::Unsupported),
369 }
370}
371
372impl<G> BufferStrategy<G, CartesianBuffer> for PointBuffer
375where
376 G: Point + PointMut + Default + Copy,
377 G::Scalar: CoordinateScalar + Into<f64> + FromF64,
378 <G::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
379{
380 fn apply(
381 &self,
382 point_geometry: &G,
383 settings: BufferSettings,
384 _coordinate_strategy: &CartesianBuffer,
385 ) -> Result<MultiPolygon<Polygon<G>>, OverlayError> {
386 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
387 return Err(OverlayError::Unsupported);
388 };
389 if !distance.is_finite() {
390 return Err(OverlayError::Unsupported);
391 }
392 if distance <= 0.0 {
393 return Ok(MultiPolygon(alloc::vec![]));
394 }
395 let point = match settings.point {
396 BufferPointStrategy::Circle { points_per_circle } => {
397 PointStrategy::Circle { points_per_circle }
398 }
399 BufferPointStrategy::Square => PointStrategy::Square,
400 };
401 let ring = buffer_point(point_geometry, distance, point);
402 Ok(MultiPolygon(alloc::vec![Polygon::new(ring)]))
403 }
404}
405
406impl<G> BufferStrategy<G, CartesianBuffer> for PolygonBuffer
409where
410 G: PolygonTrait,
411 G::Point: PointMut + Default + Copy,
412 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
413 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
414{
415 fn apply(
416 &self,
417 polygon: &G,
418 settings: BufferSettings,
419 _coordinate_strategy: &CartesianBuffer,
420 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
421 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
422 return Err(OverlayError::Unsupported);
423 };
424 if !distance.is_finite() {
425 return Err(OverlayError::Unsupported);
426 }
427 if distance == 0.0 {
428 return zero_width_polygon_buffer(polygon);
433 }
434 let Some(outer) = offset_ring(polygon.exterior(), distance, settings.join, true) else {
435 return Ok(MultiPolygon(alloc::vec![]));
436 };
437 let inners = polygon
438 .interiors()
439 .filter_map(|ring| offset_ring(ring, -distance, settings.join, false))
440 .collect::<Vec<_>>();
441 let outer_vertices = distinct_vertices(&outer);
442 if inners.iter().any(|inner| {
443 let inner_vertices = distinct_vertices(inner);
444 outer_vertices
445 .iter()
446 .all(|point| point_in_or_on_ring(*point, &inner_vertices))
447 }) {
448 return Ok(MultiPolygon::new());
449 }
450
451 Ok(MultiPolygon(alloc::vec![Polygon::with_inners(
452 outer, inners,
453 )]))
454 }
455}
456
457impl<G> BufferStrategy<G, CartesianBuffer> for LinestringBuffer
458where
459 G: LinestringTrait,
460 G::Point: PointMut + Default + Copy,
461 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
462 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
463{
464 fn apply(
465 &self,
466 line: &G,
467 settings: BufferSettings,
468 _coordinate_strategy: &CartesianBuffer,
469 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
470 let (left, right) = match settings.distance {
471 BufferDistanceStrategy::Symmetric(distance) => (distance, distance),
472 BufferDistanceStrategy::Asymmetric { left, right } => (left, right),
473 };
474 if !left.is_finite() || !right.is_finite() || left < 0.0 || right < 0.0 {
475 return Err(OverlayError::Unsupported);
476 }
477 if left == 0.0 && right == 0.0 {
478 return Ok(MultiPolygon(alloc::vec![]));
479 }
480 let polygon = buffer_linestring(line, left, right, settings.join, settings.end)?;
481 Ok(MultiPolygon(alloc::vec![polygon]))
482 }
483}
484
485impl<G> BufferStrategy<G, CartesianBuffer> for SegmentBuffer
486where
487 G: SegmentTrait,
488 G::Point: PointMut + Default + Copy,
489 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
490 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
491{
492 fn apply(
493 &self,
494 segment: &G,
495 settings: BufferSettings,
496 coordinate_strategy: &CartesianBuffer,
497 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
498 let line: Linestring<G::Point> =
499 Linestring::from_vec(alloc::vec![segment_start(segment), segment_end(segment)]);
500 LinestringBuffer.apply(&line, settings, coordinate_strategy)
501 }
502}
503
504impl<G> BufferStrategy<G, CartesianBuffer> for RingBuffer
505where
506 G: RingTrait,
507 G::Point: PointMut + Default + Copy,
508 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
509 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
510{
511 fn apply(
512 &self,
513 ring: &G,
514 settings: BufferSettings,
515 _coordinate_strategy: &CartesianBuffer,
516 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
517 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
518 return Err(OverlayError::Unsupported);
519 };
520 if !distance.is_finite() || distance == 0.0 {
521 return Err(OverlayError::Unsupported);
522 }
523 Ok(offset_ring(ring, distance, settings.join, true)
524 .map_or_else(MultiPolygon::new, |outer| {
525 MultiPolygon::from_vec(alloc::vec![Polygon::new(outer)])
526 }))
527 }
528}
529
530impl<G> BufferStrategy<G, CartesianBuffer> for BoxBuffer
531where
532 G: BoxTrait,
533 G::Point: PointMut + Default + Copy,
534 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
535 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
536{
537 fn apply(
538 &self,
539 bounds: &G,
540 settings: BufferSettings,
541 coordinate_strategy: &CartesianBuffer,
542 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
543 let minimum = box_min(bounds);
544 let maximum = box_max(bounds);
545 let min_x = minimum.get::<0>().into();
546 let min_y = minimum.get::<1>().into();
547 let max_x = maximum.get::<0>().into();
548 let max_y = maximum.get::<1>().into();
549 let ring: Ring<G::Point> = Ring::from_vec(alloc::vec![
550 make_point(min_x, min_y),
551 make_point(min_x, max_y),
552 make_point(max_x, max_y),
553 make_point(max_x, min_y),
554 make_point(min_x, min_y),
555 ]);
556 RingBuffer.apply(&ring, settings, coordinate_strategy)
557 }
558}
559
560impl<G> BufferStrategy<G, CartesianBuffer> for MultiPointBuffer
561where
562 G: MultiPointTrait<ItemPoint = <G as Geometry>::Point>,
563 G::Point: PointMut + Default + Copy,
564 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
565 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
566{
567 fn apply(
568 &self,
569 points: &G,
570 settings: BufferSettings,
571 coordinate_strategy: &CartesianBuffer,
572 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
573 let mut output = MultiPolygon::new();
574 for point in points.points() {
575 output
576 .0
577 .extend(PointBuffer.apply(point, settings, coordinate_strategy)?.0);
578 }
579 crate::merge::merge_polygons(output.0)
580 }
581}
582
583impl<G> BufferStrategy<G, CartesianBuffer> for MultiLinestringBuffer
584where
585 G: MultiLinestringTrait,
586 G::Point: PointMut + Default + Copy,
587 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
588 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
589{
590 fn apply(
591 &self,
592 lines: &G,
593 settings: BufferSettings,
594 coordinate_strategy: &CartesianBuffer,
595 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
596 let mut output = MultiPolygon::new();
597 for line in lines.linestrings() {
598 output.0.extend(
599 LinestringBuffer
600 .apply(line, settings, coordinate_strategy)?
601 .0,
602 );
603 }
604 crate::merge::merge_polygons(output.0)
605 }
606}
607
608impl<G> BufferStrategy<G, CartesianBuffer> for MultiPolygonBuffer
609where
610 G: MultiPolygonTrait,
611 G::Point: PointMut + Default + Copy,
612 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
613 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
614{
615 fn apply(
616 &self,
617 polygons: &G,
618 settings: BufferSettings,
619 coordinate_strategy: &CartesianBuffer,
620 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
621 let mut output = MultiPolygon::new();
622 for polygon in polygons.polygons() {
623 output.0.extend(
624 PolygonBuffer
625 .apply(polygon, settings, coordinate_strategy)?
626 .0,
627 );
628 }
629 crate::merge::merge_polygons(output.0)
630 }
631}
632
633trait AngularCoordinateSystem {
634 type Units: AngleUnit;
635}
636
637impl<Units: AngleUnit> AngularCoordinateSystem for Spherical<Units> {
638 type Units = Units;
639}
640
641impl<Units: AngleUnit> AngularCoordinateSystem for Geographic<Units> {
642 type Units = Units;
643}
644
645#[derive(Debug, Clone, Copy)]
646struct LocalProjection {
647 longitude: f64,
648 latitude: f64,
649 east_scale: f64,
650 north_scale: f64,
651}
652
653impl LocalProjection {
654 fn project(self, longitude: f64, latitude: f64) -> (f64, f64) {
655 let mut delta_longitude = longitude - self.longitude;
656 if delta_longitude > core::f64::consts::PI {
657 delta_longitude -= 2.0 * core::f64::consts::PI;
658 } else if delta_longitude < -core::f64::consts::PI {
659 delta_longitude += 2.0 * core::f64::consts::PI;
660 }
661 (
662 delta_longitude * self.east_scale,
663 (latitude - self.latitude) * self.north_scale,
664 )
665 }
666
667 fn unproject(self, x: f64, y: f64) -> (f64, f64) {
668 let mut longitude = self.longitude + x / self.east_scale;
669 if longitude > core::f64::consts::PI {
670 longitude -= 2.0 * core::f64::consts::PI;
671 } else if longitude < -core::f64::consts::PI {
672 longitude += 2.0 * core::f64::consts::PI;
673 }
674 (longitude, self.latitude + y / self.north_scale)
675 }
676}
677
678trait AngularBufferProjection {
679 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError>;
680}
681
682impl AngularBufferProjection for SphericalBuffer {
683 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError> {
684 if !self.radius.is_finite() || self.radius <= 0.0 {
685 return Err(OverlayError::Unsupported);
686 }
687 let longitude_scale = cos(latitude);
688 if longitude_scale.abs() <= f64::EPSILON {
689 return Err(OverlayError::Unsupported);
690 }
691 let east_scale = self.radius * longitude_scale;
692 Ok(LocalProjection {
693 longitude,
694 latitude,
695 east_scale,
696 north_scale: self.radius,
697 })
698 }
699}
700
701impl AngularBufferProjection for GeographicBuffer {
702 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError> {
703 let spheroid = self.spheroid;
704 if !spheroid.equatorial_radius.is_finite()
705 || spheroid.equatorial_radius <= 0.0
706 || !spheroid.flattening.is_finite()
707 || !(0.0..1.0).contains(&spheroid.flattening)
708 {
709 return Err(OverlayError::Unsupported);
710 }
711
712 let eccentricity_squared = spheroid.eccentricity_squared();
713 let sin_latitude = sin(latitude);
714 let denominator = sqrt(1.0 - eccentricity_squared * sin_latitude * sin_latitude);
715 let prime_vertical = spheroid.equatorial_radius / denominator;
716 let meridional = spheroid.equatorial_radius * (1.0 - eccentricity_squared)
717 / (denominator * denominator * denominator);
718 let longitude_scale = cos(latitude);
719 if longitude_scale.abs() <= f64::EPSILON {
720 return Err(OverlayError::Unsupported);
721 }
722 let east_scale = prime_vertical * longitude_scale;
723 Ok(LocalProjection {
724 longitude,
725 latitude,
726 east_scale,
727 north_scale: meridional,
728 })
729 }
730}
731
732fn angular_coordinates<P>(point: &P) -> (f64, f64)
733where
734 P: Point,
735 P::Scalar: Into<f64>,
736 P::Cs: AngularCoordinateSystem,
737{
738 let longitude = <P::Cs as AngularCoordinateSystem>::Units::to_radians(point.get::<0>().into());
739 let latitude = <P::Cs as AngularCoordinateSystem>::Units::to_radians(point.get::<1>().into());
740 (longitude, latitude)
741}
742
743fn angular_point<P>(longitude: f64, latitude: f64) -> P
744where
745 P: PointMut + Default,
746 P::Scalar: FromF64,
747 P::Cs: AngularCoordinateSystem,
748{
749 let mut point = P::default();
750 let longitude = <P::Cs as AngularCoordinateSystem>::Units::from_radians(longitude);
751 let latitude = <P::Cs as AngularCoordinateSystem>::Units::from_radians(latitude);
752 point.set::<0>(P::Scalar::from_f64(longitude));
753 point.set::<1>(P::Scalar::from_f64(latitude));
754 point
755}
756
757fn projection_center(coordinates: &[(f64, f64)]) -> Result<(f64, f64), OverlayError> {
758 if coordinates.is_empty() {
759 return Err(OverlayError::Unsupported);
760 }
761 let mut longitude_sine = 0.0;
762 let mut longitude_cosine = 0.0;
763 let mut latitude = 0.0;
764 for &(longitude, point_latitude) in coordinates {
765 longitude_sine += sin(longitude);
766 longitude_cosine += cos(longitude);
767 latitude += point_latitude;
768 }
769 let count = coordinates.len() as f64;
770 Ok((atan2(longitude_sine, longitude_cosine), latitude / count))
771}
772
773type ProjectedPoint = Point2D<f64, Cartesian>;
774
775fn projected_point<P>(point: &P, projection: LocalProjection) -> ProjectedPoint
776where
777 P: Point,
778 P::Scalar: Into<f64>,
779 P::Cs: AngularCoordinateSystem,
780{
781 let (longitude, latitude) = angular_coordinates(point);
782 let (x, y) = projection.project(longitude, latitude);
783 ProjectedPoint::new(x, y)
784}
785
786fn projected_ring<R>(ring: &R, projection: LocalProjection) -> Ring<ProjectedPoint>
787where
788 R: RingTrait,
789 R::Point: Point,
790 <R::Point as Point>::Scalar: Into<f64>,
791 <R::Point as Point>::Cs: AngularCoordinateSystem,
792{
793 Ring::from_vec(
794 ring.points()
795 .map(|point| projected_point(point, projection))
796 .collect(),
797 )
798}
799
800fn projected_polygon<G>(polygon: &G, projection: LocalProjection) -> Polygon<ProjectedPoint>
801where
802 G: PolygonTrait,
803 G::Point: Point,
804 <G::Point as Point>::Scalar: Into<f64>,
805 <G::Point as Point>::Cs: AngularCoordinateSystem,
806{
807 Polygon::with_inners(
808 projected_ring(polygon.exterior(), projection),
809 polygon
810 .interiors()
811 .map(|ring| projected_ring(ring, projection))
812 .collect(),
813 )
814}
815
816fn unprojected_buffer<P>(
817 polygons: MultiPolygon<Polygon<ProjectedPoint>>,
818 projection: LocalProjection,
819) -> MultiPolygon<Polygon<P>>
820where
821 P: PointMut + Default,
822 P::Scalar: FromF64,
823 P::Cs: AngularCoordinateSystem,
824{
825 MultiPolygon::from_vec(
826 polygons
827 .0
828 .into_iter()
829 .map(|polygon| {
830 let outer = Ring::from_vec(
831 polygon
832 .outer
833 .0
834 .into_iter()
835 .map(|point| {
836 let (longitude, latitude) = projection.unproject(point.x(), point.y());
837 angular_point(longitude, latitude)
838 })
839 .collect(),
840 );
841 let inners = polygon
842 .inners
843 .into_iter()
844 .map(|ring| {
845 Ring::from_vec(
846 ring.0
847 .into_iter()
848 .map(|point| {
849 let (longitude, latitude) =
850 projection.unproject(point.x(), point.y());
851 angular_point(longitude, latitude)
852 })
853 .collect(),
854 )
855 })
856 .collect();
857 Polygon::with_inners(outer, inners)
858 })
859 .collect(),
860 )
861}
862
863fn projection_for_points<'a, P>(
864 points: impl IntoIterator<Item = &'a P>,
865 strategy: &impl AngularBufferProjection,
866) -> Result<LocalProjection, OverlayError>
867where
868 P: Point + 'a,
869 P::Scalar: Into<f64>,
870 P::Cs: AngularCoordinateSystem,
871{
872 let coordinates: Vec<_> = points.into_iter().map(angular_coordinates).collect();
873 let (longitude, latitude) = projection_center(&coordinates)?;
874 strategy.projection(longitude, latitude)
875}
876
877fn projected_point_apply<P>(
878 point: &P,
879 settings: BufferSettings,
880 strategy: &impl AngularBufferProjection,
881) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
882where
883 P: Point + PointMut + Default + Copy,
884 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
885 P::Cs: AngularCoordinateSystem,
886{
887 let projection = projection_for_points(core::iter::once(point), strategy)?;
888 let point = projected_point(point, projection);
889 let output = PointBuffer.apply(&point, settings, &CartesianBuffer)?;
890 Ok(unprojected_buffer(output, projection))
891}
892
893fn projected_linestring_apply<L>(
894 line: &L,
895 settings: BufferSettings,
896 strategy: &impl AngularBufferProjection,
897) -> Result<MultiPolygon<Polygon<L::Point>>, OverlayError>
898where
899 L: LinestringTrait,
900 L::Point: PointMut + Default + Copy,
901 <L::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
902 <L::Point as Point>::Cs: AngularCoordinateSystem,
903{
904 let projection = projection_for_points(line.points(), strategy)?;
905 let projected = Linestring::from_vec(
906 line.points()
907 .map(|point| projected_point(point, projection))
908 .collect(),
909 );
910 let output = LinestringBuffer.apply(&projected, settings, &CartesianBuffer)?;
911 Ok(unprojected_buffer(output, projection))
912}
913
914fn projected_ring_apply<R>(
915 ring: &R,
916 settings: BufferSettings,
917 strategy: &impl AngularBufferProjection,
918) -> Result<MultiPolygon<Polygon<R::Point>>, OverlayError>
919where
920 R: RingTrait,
921 R::Point: PointMut + Default + Copy,
922 <R::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
923 <R::Point as Point>::Cs: AngularCoordinateSystem,
924{
925 let projection = projection_for_points(ring.points(), strategy)?;
926 let output = RingBuffer.apply(
927 &projected_ring(ring, projection),
928 settings,
929 &CartesianBuffer,
930 )?;
931 Ok(unprojected_buffer(output, projection))
932}
933
934fn projected_polygon_apply<G>(
935 polygon: &G,
936 settings: BufferSettings,
937 strategy: &impl AngularBufferProjection,
938) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
939where
940 G: PolygonTrait,
941 G::Point: PointMut + Default + Copy,
942 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
943 <G::Point as Point>::Cs: AngularCoordinateSystem,
944{
945 let mut coordinates = polygon
946 .exterior()
947 .points()
948 .map(angular_coordinates)
949 .collect::<Vec<_>>();
950 for ring in polygon.interiors() {
951 coordinates.extend(ring.points().map(angular_coordinates));
952 }
953 let (longitude, latitude) = projection_center(&coordinates)?;
954 let projection = strategy.projection(longitude, latitude)?;
955 let output = PolygonBuffer.apply(
956 &projected_polygon(polygon, projection),
957 settings,
958 &CartesianBuffer,
959 )?;
960 Ok(unprojected_buffer(output, projection))
961}
962
963macro_rules! impl_angular_buffer_strategy {
964 ($strategy:ty, $family:ty) => {
965 impl<G> BufferStrategy<G, $strategy> for PointBuffer
966 where
967 G: Point + PointMut + Default + Copy,
968 G::Scalar: CoordinateScalar + Into<f64> + FromF64,
969 G::Cs: AngularCoordinateSystem,
970 <G::Cs as CoordinateSystem>::Family: SameAs<$family>,
971 {
972 fn apply(
973 &self,
974 geometry: &G,
975 settings: BufferSettings,
976 coordinate_strategy: &$strategy,
977 ) -> Result<MultiPolygon<Polygon<G>>, OverlayError> {
978 projected_point_apply(geometry, settings, coordinate_strategy)
979 }
980 }
981
982 impl<G> BufferStrategy<G, $strategy> for LinestringBuffer
983 where
984 G: LinestringTrait,
985 G::Point: PointMut + Default + Copy,
986 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
987 <G::Point as Point>::Cs: AngularCoordinateSystem,
988 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
989 {
990 fn apply(
991 &self,
992 geometry: &G,
993 settings: BufferSettings,
994 coordinate_strategy: &$strategy,
995 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
996 projected_linestring_apply(geometry, settings, coordinate_strategy)
997 }
998 }
999
1000 impl<G> BufferStrategy<G, $strategy> for SegmentBuffer
1001 where
1002 G: SegmentTrait,
1003 G::Point: PointMut + Default + Copy,
1004 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1005 <G::Point as Point>::Cs: AngularCoordinateSystem,
1006 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1007 {
1008 fn apply(
1009 &self,
1010 geometry: &G,
1011 settings: BufferSettings,
1012 coordinate_strategy: &$strategy,
1013 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1014 let line = Linestring::from_vec(alloc::vec![
1015 segment_start(geometry),
1016 segment_end(geometry),
1017 ]);
1018 projected_linestring_apply(&line, settings, coordinate_strategy)
1019 }
1020 }
1021
1022 impl<G> BufferStrategy<G, $strategy> for RingBuffer
1023 where
1024 G: RingTrait,
1025 G::Point: PointMut + Default + Copy,
1026 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1027 <G::Point as Point>::Cs: AngularCoordinateSystem,
1028 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1029 {
1030 fn apply(
1031 &self,
1032 geometry: &G,
1033 settings: BufferSettings,
1034 coordinate_strategy: &$strategy,
1035 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1036 projected_ring_apply(geometry, settings, coordinate_strategy)
1037 }
1038 }
1039
1040 impl<G> BufferStrategy<G, $strategy> for PolygonBuffer
1041 where
1042 G: PolygonTrait,
1043 G::Point: PointMut + Default + Copy,
1044 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1045 <G::Point as Point>::Cs: AngularCoordinateSystem,
1046 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1047 {
1048 fn apply(
1049 &self,
1050 geometry: &G,
1051 settings: BufferSettings,
1052 coordinate_strategy: &$strategy,
1053 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1054 projected_polygon_apply(geometry, settings, coordinate_strategy)
1055 }
1056 }
1057
1058 impl<G> BufferStrategy<G, $strategy> for BoxBuffer
1059 where
1060 G: BoxTrait,
1061 G::Point: PointMut + Default + Copy,
1062 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1063 <G::Point as Point>::Cs: AngularCoordinateSystem,
1064 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1065 {
1066 fn apply(
1067 &self,
1068 geometry: &G,
1069 settings: BufferSettings,
1070 coordinate_strategy: &$strategy,
1071 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1072 let minimum = box_min(geometry);
1073 let maximum = box_max(geometry);
1074 let projection = projection_for_points([&minimum, &maximum], coordinate_strategy)?;
1075 let projected = ModelBox::from_corners(
1076 projected_point(&minimum, projection),
1077 projected_point(&maximum, projection),
1078 );
1079 let output = BoxBuffer.apply(&projected, settings, &CartesianBuffer)?;
1080 Ok(unprojected_buffer(output, projection))
1081 }
1082 }
1083
1084 impl<G> BufferStrategy<G, $strategy> for MultiPointBuffer
1085 where
1086 G: MultiPointTrait<ItemPoint = <G as Geometry>::Point>,
1087 G::Point: PointMut + Default + Copy,
1088 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1089 <G::Point as Point>::Cs: AngularCoordinateSystem,
1090 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1091 {
1092 fn apply(
1093 &self,
1094 geometry: &G,
1095 settings: BufferSettings,
1096 coordinate_strategy: &$strategy,
1097 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1098 let projection = projection_for_points(geometry.points(), coordinate_strategy)?;
1099 let projected = MultiPoint::from_vec(
1100 geometry
1101 .points()
1102 .map(|point| projected_point(point, projection))
1103 .collect(),
1104 );
1105 let output = MultiPointBuffer.apply(&projected, settings, &CartesianBuffer)?;
1106 Ok(unprojected_buffer(output, projection))
1107 }
1108 }
1109
1110 impl<G> BufferStrategy<G, $strategy> for MultiLinestringBuffer
1111 where
1112 G: MultiLinestringTrait,
1113 G::Point: PointMut + Default + Copy,
1114 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1115 <G::Point as Point>::Cs: AngularCoordinateSystem,
1116 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1117 {
1118 fn apply(
1119 &self,
1120 geometry: &G,
1121 settings: BufferSettings,
1122 coordinate_strategy: &$strategy,
1123 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1124 let coordinates = geometry
1125 .linestrings()
1126 .flat_map(|line| line.points().map(angular_coordinates))
1127 .collect::<Vec<_>>();
1128 let (longitude, latitude) = projection_center(&coordinates)?;
1129 let projection = coordinate_strategy.projection(longitude, latitude)?;
1130 let projected = MultiLinestring::from_vec(
1131 geometry
1132 .linestrings()
1133 .map(|line| {
1134 Linestring::from_vec(
1135 line.points()
1136 .map(|point| projected_point(point, projection))
1137 .collect(),
1138 )
1139 })
1140 .collect(),
1141 );
1142 let output = MultiLinestringBuffer.apply(&projected, settings, &CartesianBuffer)?;
1143 Ok(unprojected_buffer(output, projection))
1144 }
1145 }
1146
1147 impl<G> BufferStrategy<G, $strategy> for MultiPolygonBuffer
1148 where
1149 G: MultiPolygonTrait,
1150 G::Point: PointMut + Default + Copy,
1151 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1152 <G::Point as Point>::Cs: AngularCoordinateSystem,
1153 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1154 {
1155 fn apply(
1156 &self,
1157 geometry: &G,
1158 settings: BufferSettings,
1159 coordinate_strategy: &$strategy,
1160 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1161 let coordinates = geometry
1162 .polygons()
1163 .flat_map(|polygon| {
1164 polygon
1165 .exterior()
1166 .points()
1167 .chain(polygon.interiors().flat_map(RingTrait::points))
1168 .map(angular_coordinates)
1169 })
1170 .collect::<Vec<_>>();
1171 let (longitude, latitude) = projection_center(&coordinates)?;
1172 let projection = coordinate_strategy.projection(longitude, latitude)?;
1173 let projected = MultiPolygon::from_vec(
1174 geometry
1175 .polygons()
1176 .map(|polygon| projected_polygon(polygon, projection))
1177 .collect(),
1178 );
1179 let output = MultiPolygonBuffer.apply(&projected, settings, &CartesianBuffer)?;
1180 Ok(unprojected_buffer(output, projection))
1181 }
1182 }
1183 };
1184}
1185
1186impl_angular_buffer_strategy!(SphericalBuffer, SphericalFamily);
1187impl_angular_buffer_strategy!(GeographicBuffer, GeographicFamily);
1188
1189#[inline]
1210#[must_use]
1211pub fn buffer_point<P>(center: &P, distance: f64, strategy: PointStrategy) -> Ring<P>
1212where
1213 P: PointMut + Default + Copy,
1214 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
1215 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1216{
1217 let cx: f64 = center.get::<0>().into();
1218 let cy: f64 = center.get::<1>().into();
1219 match strategy {
1220 PointStrategy::Circle { points_per_circle } => {
1221 circle_ring(cx, cy, distance, points_per_circle.max(3))
1222 }
1223 PointStrategy::Square => {
1224 let d = distance;
1225 Ring::from_vec(alloc::vec![
1230 make_point(cx - d, cy - d),
1231 make_point(cx - d, cy + d),
1232 make_point(cx + d, cy + d),
1233 make_point(cx + d, cy - d),
1234 make_point(cx - d, cy - d),
1235 ])
1236 }
1237 }
1238}
1239
1240#[inline]
1270#[must_use]
1271pub fn buffer_convex_polygon<G, P>(polygon: &G, distance: f64, join: JoinStrategy) -> Polygon<P>
1272where
1273 G: PolygonTrait<Point = P>,
1274 P: PointMut + Default + Copy,
1275 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
1276 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1277{
1278 let strategy = match join {
1279 JoinStrategy::Round { points_per_circle } => {
1280 BufferJoinStrategy::Round { points_per_circle }
1281 }
1282 JoinStrategy::Miter => BufferJoinStrategy::Miter {
1283 limit: f64::INFINITY,
1284 },
1285 };
1286 offset_ring(polygon.exterior(), distance, strategy, true)
1287 .map_or_else(|| Polygon::new(Ring::new()), Polygon::new)
1288}
1289
1290fn offset_ring<R, P>(
1291 ring: &R,
1292 distance: f64,
1293 join: BufferJoinStrategy,
1294 clockwise: bool,
1295) -> Option<Ring<P>>
1296where
1297 R: RingTrait<Point = P>,
1298 P: PointMut + Default + Copy,
1299 P::Scalar: Into<f64> + FromF64,
1300{
1301 let mut vertices = distinct_vertices(ring);
1302 if vertices.len() < 3 || !distance.is_finite() || distance == 0.0 {
1303 return None;
1304 }
1305 if signed_area_ccw_positive(&vertices) < 0.0 {
1306 vertices.reverse();
1307 }
1308
1309 let count = vertices.len();
1310 let mut boundary = Vec::new();
1311 for index in 0..count {
1312 let previous = vertices[(index + count - 1) % count];
1313 let vertex = vertices[index];
1314 let next = vertices[(index + 1) % count];
1315 let incoming = (vertex.0 - previous.0, vertex.1 - previous.1);
1316 let outgoing = (next.0 - vertex.0, next.1 - vertex.1);
1317 let incoming_normal = outward_normal(incoming.0, incoming.1);
1318 let outgoing_normal = outward_normal(outgoing.0, outgoing.1);
1319 let before = (
1320 vertex.0 + incoming_normal.0 * distance,
1321 vertex.1 + incoming_normal.1 * distance,
1322 );
1323 let after = (
1324 vertex.0 + outgoing_normal.0 * distance,
1325 vertex.1 + outgoing_normal.1 * distance,
1326 );
1327 let intersection = line_intersection(before, incoming, after, outgoing);
1328 let cross = incoming.0 * outgoing.1 - incoming.1 * outgoing.0;
1329 let exterior_join = cross * distance > 0.0;
1330
1331 if !exterior_join {
1332 if let Some(point) = intersection {
1333 boundary.push(point);
1334 } else {
1335 boundary.push(after);
1336 }
1337 continue;
1338 }
1339
1340 match join {
1341 BufferJoinStrategy::Round { points_per_circle } => {
1342 boundary.push(before);
1343 push_arc_between(
1344 &mut boundary,
1345 vertex,
1346 before,
1347 after,
1348 distance.abs(),
1349 points_per_circle.max(4),
1350 true,
1351 );
1352 boundary.push(after);
1353 }
1354 BufferJoinStrategy::Miter { limit } => {
1355 if let Some(point) = intersection {
1356 let miter_length = hypot(point.0 - vertex.0, point.1 - vertex.1);
1357 if point.0.is_finite()
1358 && point.1.is_finite()
1359 && miter_length <= limit.max(1.0) * distance.abs()
1360 {
1361 boundary.push(point);
1362 } else {
1363 boundary.push(before);
1364 boundary.push(after);
1365 }
1366 } else {
1367 boundary.push(before);
1368 boundary.push(after);
1369 }
1370 }
1371 }
1372 }
1373
1374 boundary.dedup();
1375 if boundary.len() < 3 || signed_area_ccw_positive(&boundary).abs() <= f64::EPSILON {
1376 return None;
1377 }
1378 if distance < 0.0 {
1379 let clearance = distance.abs();
1380 let tolerance = mul_add(clearance, 1e-9, f64::EPSILON * 16.0);
1381 if boundary.iter().any(|point| {
1382 !point_in_or_on_ring(*point, &vertices)
1383 || minimum_boundary_distance(*point, &vertices) + tolerance < clearance
1384 }) {
1385 return None;
1386 }
1387 }
1388 if clockwise == (signed_area_ccw_positive(&boundary) > 0.0) {
1389 boundary.reverse();
1390 }
1391 boundary.push(boundary[0]);
1392 Some(Ring::from_vec(
1393 boundary
1394 .into_iter()
1395 .map(|(x, y)| make_point(x, y))
1396 .collect(),
1397 ))
1398}
1399
1400fn buffer_linestring<L, P>(
1401 line: &L,
1402 left: f64,
1403 right: f64,
1404 join: BufferJoinStrategy,
1405 end: BufferEndStrategy,
1406) -> Result<Polygon<P>, OverlayError>
1407where
1408 L: LinestringTrait<Point = P>,
1409 P: PointMut + Default + Copy,
1410 P::Scalar: Into<f64> + FromF64,
1411{
1412 let mut vertices: Vec<(f64, f64)> = Vec::new();
1413 for point in line.points() {
1414 let value = (point.get::<0>().into(), point.get::<1>().into());
1415 if vertices.last().copied() != Some(value) {
1416 vertices.push(value);
1417 }
1418 }
1419 if vertices.len() < 2 {
1420 return Err(OverlayError::Unsupported);
1421 }
1422
1423 let left_path = offset_path(&vertices, left, true, join);
1424 let right_path = offset_path(&vertices, right, false, join);
1425 debug_assert!(!left_path.is_empty() && !right_path.is_empty());
1426 let mut boundary = left_path;
1427 match end {
1428 BufferEndStrategy::Flat => {}
1429 BufferEndStrategy::Round { points_per_circle } => {
1430 let center = *vertices.last().expect("linestring has an endpoint");
1431 let from = *boundary.last().expect("left path has an endpoint");
1432 let to = *right_path.last().expect("right path has an endpoint");
1433 push_end_arc(
1434 &mut boundary,
1435 center,
1436 from,
1437 to,
1438 points_per_circle.max(4),
1439 true,
1440 );
1441 }
1442 }
1443 boundary.extend(right_path.iter().rev().copied());
1444 if let BufferEndStrategy::Round { points_per_circle } = end {
1445 let to = boundary[0];
1446 push_end_arc(
1447 &mut boundary,
1448 vertices[0],
1449 right_path[0],
1450 to,
1451 points_per_circle.max(4),
1452 true,
1453 );
1454 }
1455 let first = boundary[0];
1456 boundary.push(first);
1457 Ok(Polygon::new(Ring::from_vec(
1458 boundary
1459 .into_iter()
1460 .map(|(x, y)| make_point(x, y))
1461 .collect(),
1462 )))
1463}
1464
1465fn offset_path(
1466 vertices: &[(f64, f64)],
1467 distance: f64,
1468 left: bool,
1469 join: BufferJoinStrategy,
1470) -> Vec<(f64, f64)> {
1471 let side = if left { 1.0 } else { -1.0 };
1472 let normals: Vec<(f64, f64)> = vertices
1473 .windows(2)
1474 .map(|edge| {
1475 let dx = edge[1].0 - edge[0].0;
1476 let dy = edge[1].1 - edge[0].1;
1477 let length = hypot(dx, dy);
1478 (-dy / length * side, dx / length * side)
1479 })
1480 .collect();
1481 let mut path = Vec::with_capacity(vertices.len());
1482 path.push((
1483 vertices[0].0 + normals[0].0 * distance,
1484 vertices[0].1 + normals[0].1 * distance,
1485 ));
1486 for index in 1..vertices.len() - 1 {
1487 let vertex = vertices[index];
1488 let previous = vertices[index - 1];
1489 let next = vertices[index + 1];
1490 let before = (
1491 vertex.0 + normals[index - 1].0 * distance,
1492 vertex.1 + normals[index - 1].1 * distance,
1493 );
1494 let after = (
1495 vertex.0 + normals[index].0 * distance,
1496 vertex.1 + normals[index].1 * distance,
1497 );
1498 let intersection = line_intersection(
1499 before,
1500 (vertex.0 - previous.0, vertex.1 - previous.1),
1501 after,
1502 (next.0 - vertex.0, next.1 - vertex.1),
1503 );
1504 match (join, intersection) {
1505 (BufferJoinStrategy::Miter { limit }, Some(point))
1506 if point.0.is_finite() && point.1.is_finite() =>
1507 {
1508 let miter_length = hypot(point.0 - vertex.0, point.1 - vertex.1);
1509 if distance == 0.0 || miter_length <= limit.max(1.0) * distance.abs() {
1510 path.push(point);
1511 } else {
1512 path.push(before);
1513 path.push(after);
1514 }
1515 }
1516 (BufferJoinStrategy::Round { points_per_circle }, _) => {
1517 path.push(before);
1518 push_arc_between(
1519 &mut path,
1520 vertex,
1521 before,
1522 after,
1523 distance.abs(),
1524 points_per_circle.max(4),
1525 left,
1526 );
1527 path.push(after);
1528 }
1529 _ => {
1530 path.push(before);
1531 path.push(after);
1532 }
1533 }
1534 }
1535 let last = vertices.len() - 1;
1536 path.push((
1537 vertices[last].0 + normals[last - 1].0 * distance,
1538 vertices[last].1 + normals[last - 1].1 * distance,
1539 ));
1540 path
1541}
1542
1543fn line_intersection(
1544 first_origin: (f64, f64),
1545 first_direction: (f64, f64),
1546 second_origin: (f64, f64),
1547 second_direction: (f64, f64),
1548) -> Option<(f64, f64)> {
1549 let denominator =
1550 first_direction.0 * second_direction.1 - first_direction.1 * second_direction.0;
1551 if denominator.abs() <= f64::EPSILON {
1552 return None;
1553 }
1554 let delta = (
1555 second_origin.0 - first_origin.0,
1556 second_origin.1 - first_origin.1,
1557 );
1558 let factor = (delta.0 * second_direction.1 - delta.1 * second_direction.0) / denominator;
1559 Some((
1560 first_origin.0 + factor * first_direction.0,
1561 first_origin.1 + factor * first_direction.1,
1562 ))
1563}
1564
1565fn push_arc_between(
1566 output: &mut Vec<(f64, f64)>,
1567 center: (f64, f64),
1568 from: (f64, f64),
1569 to: (f64, f64),
1570 radius: f64,
1571 points_per_circle: usize,
1572 counterclockwise: bool,
1573) {
1574 if radius == 0.0 {
1575 return;
1576 }
1577 let start = atan2(from.1 - center.1, from.0 - center.0);
1578 let mut end = atan2(to.1 - center.1, to.0 - center.0);
1579 if counterclockwise {
1580 while end < start {
1581 end += core::f64::consts::TAU;
1582 }
1583 } else {
1584 while end > start {
1585 end -= core::f64::consts::TAU;
1586 }
1587 }
1588 let sweep = end - start;
1589 let steps =
1590 ceil((sweep.abs() / core::f64::consts::TAU) * points_per_circle as f64).max(1.0) as usize;
1591 for step in 1..steps {
1592 let angle = start + sweep * step as f64 / steps as f64;
1593 output.push((
1594 center.0 + radius * cos(angle),
1595 center.1 + radius * sin(angle),
1596 ));
1597 }
1598}
1599
1600fn push_end_arc(
1601 output: &mut Vec<(f64, f64)>,
1602 center: (f64, f64),
1603 from: (f64, f64),
1604 to: (f64, f64),
1605 points_per_circle: usize,
1606 clockwise: bool,
1607) {
1608 let radius =
1609 hypot(from.0 - center.0, from.1 - center.1).max(hypot(to.0 - center.0, to.1 - center.1));
1610 push_arc_between(
1611 output,
1612 center,
1613 from,
1614 to,
1615 radius,
1616 points_per_circle,
1617 !clockwise,
1618 );
1619}
1620
1621fn make_point<P>(x: f64, y: f64) -> P
1623where
1624 P: PointMut + Default,
1625 P::Scalar: FromF64,
1626{
1627 let mut p = P::default();
1628 p.set::<0>(P::Scalar::from_f64(x));
1629 p.set::<1>(P::Scalar::from_f64(y));
1630 p
1631}
1632
1633fn circle_ring<P>(cx: f64, cy: f64, r: f64, segments: usize) -> Ring<P>
1635where
1636 P: PointMut + Default + Copy,
1637 P::Scalar: FromF64,
1638{
1639 let mut pts = Vec::with_capacity(segments + 1);
1640 let step = core::f64::consts::TAU / segments as f64;
1641 for k in 0..segments {
1642 let a = -step * k as f64;
1643 pts.push(make_point(cx + r * cos(a), cy + r * sin(a)));
1644 }
1645 pts.push(pts[0]);
1646 Ring::from_vec(pts)
1647}
1648
1649fn distinct_vertices<R>(ring: &R) -> Vec<(f64, f64)>
1652where
1653 R: RingTrait,
1654 <R::Point as Point>::Scalar: Into<f64>,
1655{
1656 let mut pts: Vec<(f64, f64)> = ring
1657 .points()
1658 .map(|p| (p.get::<0>().into(), p.get::<1>().into()))
1659 .collect();
1660 if pts.len() >= 2 {
1661 let first = pts[0];
1662 let last = pts[pts.len() - 1];
1663 if first == last {
1664 pts.pop();
1665 }
1666 }
1667 pts
1668}
1669
1670fn signed_area_ccw_positive(verts: &[(f64, f64)]) -> f64 {
1674 let n = verts.len();
1675 let mut acc = 0.0;
1676 for i in 0..n {
1677 let a = verts[i];
1678 let b = verts[(i + 1) % n];
1679 acc += a.0 * b.1 - b.0 * a.1;
1680 }
1681 acc * 0.5
1682}
1683
1684fn minimum_boundary_distance(point: (f64, f64), vertices: &[(f64, f64)]) -> f64 {
1685 let mut minimum = f64::INFINITY;
1686 for index in 0..vertices.len() {
1687 let start = vertices[index];
1688 let end = vertices[(index + 1) % vertices.len()];
1689 let delta = (end.0 - start.0, end.1 - start.1);
1690 let length_squared = delta.0 * delta.0 + delta.1 * delta.1;
1691 let fraction = if length_squared == 0.0 {
1692 0.0
1693 } else {
1694 (((point.0 - start.0) * delta.0 + (point.1 - start.1) * delta.1) / length_squared)
1695 .clamp(0.0, 1.0)
1696 };
1697 let nearest = (start.0 + fraction * delta.0, start.1 + fraction * delta.1);
1698 minimum = minimum.min(hypot(point.0 - nearest.0, point.1 - nearest.1));
1699 }
1700 minimum
1701}
1702
1703fn point_in_or_on_ring(point: (f64, f64), vertices: &[(f64, f64)]) -> bool {
1704 let scale = vertices.iter().fold(1.0_f64, |acc, vertex| {
1705 acc.max(vertex.0.abs()).max(vertex.1.abs())
1706 });
1707 if minimum_boundary_distance(point, vertices) <= scale * 1e-12 {
1708 return true;
1709 }
1710
1711 let mut inside = false;
1712 for index in 0..vertices.len() {
1713 let start = vertices[index];
1714 let end = vertices[(index + 1) % vertices.len()];
1715 if (start.1 > point.1) != (end.1 > point.1)
1716 && point.0 < (end.0 - start.0) * (point.1 - start.1) / (end.1 - start.1) + start.0
1717 {
1718 inside = !inside;
1719 }
1720 }
1721 inside
1722}
1723
1724fn outward_normal(dx: f64, dy: f64) -> (f64, f64) {
1727 let len = (dx * dx + dy * dy).sqrt();
1728 if len == 0.0 {
1729 return (0.0, 0.0);
1730 }
1731 (dy / len, -dx / len)
1733}
1734
1735#[cfg(test)]
1736mod tests {
1737 use super::{JoinStrategy, PointStrategy, buffer, buffer_convex_polygon, buffer_point};
1741 use geometry_algorithm::ring_area;
1742 use geometry_cs::Cartesian;
1743 use geometry_model::{Point2D, Polygon, polygon};
1744 use geometry_trait::{MultiPolygon as _, Polygon as _};
1745
1746 type P = Point2D<f64, Cartesian>;
1747
1748 fn close(a: f64, b: f64, tol: f64) {
1749 assert!((a - b).abs() < tol, "expected {b}, got {a}");
1750 }
1751
1752 #[test]
1753 fn point_circle_area_approximates_pi_r_squared() {
1754 let disc = buffer_point(
1755 &P::new(0.0, 0.0),
1756 2.0,
1757 PointStrategy::Circle {
1758 points_per_circle: 720,
1759 },
1760 );
1761 close(ring_area(&disc).abs(), core::f64::consts::PI * 4.0, 1e-2);
1763 }
1764
1765 #[test]
1766 fn point_square_area() {
1767 let sq = buffer_point(&P::new(0.0, 0.0), 3.0, PointStrategy::Square);
1768 close(ring_area(&sq).abs(), 36.0, 1e-9);
1770 }
1771
1772 #[test]
1773 fn convex_square_round_buffer_area() {
1774 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)]];
1775 let grown = buffer_convex_polygon(
1776 &sq,
1777 1.0,
1778 JoinStrategy::Round {
1779 points_per_circle: 720,
1780 },
1781 );
1782 let expected = 4.0 + 8.0 + core::f64::consts::PI;
1784 close(ring_area(grown.exterior()).abs(), expected, 1e-2);
1785 }
1786
1787 #[test]
1788 fn convex_triangle_round_buffer_grows() {
1789 let tri: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (0.0, 3.0), (0.0, 0.0)]];
1790 let base = ring_area(tri.exterior()).abs(); let grown = buffer_convex_polygon(
1792 &tri,
1793 0.5,
1794 JoinStrategy::Round {
1795 points_per_circle: 360,
1796 },
1797 );
1798 assert!(ring_area(grown.exterior()).abs() > base);
1800 }
1801
1802 #[test]
1803 fn buffer_is_winding_independent() {
1804 let ccw: Polygon<P> =
1809 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
1810 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)]];
1811 let j = JoinStrategy::Round {
1812 points_per_circle: 720,
1813 };
1814 let expected = 4.0 + 8.0 + core::f64::consts::PI;
1815 let grown_from_counterclockwise =
1816 ring_area(buffer_convex_polygon(&ccw, 1.0, j).exterior()).abs();
1817 let grown_from_clockwise = ring_area(buffer_convex_polygon(&cw, 1.0, j).exterior()).abs();
1818 close(grown_from_counterclockwise, expected, 5e-2);
1819 close(grown_from_clockwise, expected, 5e-2);
1820 }
1821
1822 #[test]
1823 fn miter_square_area_is_16() {
1824 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)]];
1830 let grown = buffer_convex_polygon(&sq, 1.0, JoinStrategy::Miter);
1831 close(ring_area(grown.exterior()).abs(), 16.0, 1e-9);
1832 }
1833
1834 #[test]
1835 fn miter_contains_near_corner_probe() {
1836 use geometry_algorithm::within;
1839 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)]];
1840 let grown = buffer_convex_polygon(&sq, 1.0, JoinStrategy::Miter);
1841 let ang = 22.5_f64.to_radians();
1842 let probe = P::new(2.0 + 0.99 * ang.cos(), 2.0 + 0.99 * ang.sin());
1843 assert!(
1844 within(&probe, &grown),
1845 "buffer must contain points within d"
1846 );
1847 }
1848
1849 #[test]
1850 fn miter_is_superset_of_round_by_area() {
1851 let j_round = JoinStrategy::Round {
1854 points_per_circle: 720,
1855 };
1856 let square: Polygon<P> =
1857 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
1858 let triangle: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (0.0, 3.0), (0.0, 0.0)]];
1859 for pg in [square, triangle] {
1860 let m =
1861 ring_area(buffer_convex_polygon(&pg, 1.0, JoinStrategy::Miter).exterior()).abs();
1862 let r = ring_area(buffer_convex_polygon(&pg, 1.0, j_round).exterior()).abs();
1863 assert!(m >= r - 1e-9, "miter {m} must not be below round {r}");
1864 }
1865 }
1866
1867 #[test]
1868 fn non_model_polygon_buffers_like_the_model_polygon() {
1869 use geometry_model::Ring;
1873 use geometry_tag::PolygonTag;
1874 use geometry_trait::{Geometry, Polygon as PolygonTrait};
1875
1876 struct Parcel {
1877 outer: Ring<P>,
1878 }
1879 impl Geometry for Parcel {
1880 type Kind = PolygonTag;
1881 type Point = P;
1882 }
1883 impl PolygonTrait for Parcel {
1884 type Ring = Ring<P>;
1885 fn exterior(&self) -> &Ring<P> {
1886 &self.outer
1887 }
1888 fn interiors(&self) -> impl ExactSizeIterator<Item = &Ring<P>> {
1889 core::iter::empty()
1890 }
1891 }
1892
1893 let pts = vec![
1894 P::new(0.0, 0.0),
1895 P::new(2.0, 0.0),
1896 P::new(2.0, 2.0),
1897 P::new(0.0, 2.0),
1898 P::new(0.0, 0.0),
1899 ];
1900 let parcel = Parcel {
1901 outer: Ring::from_vec(pts.clone()),
1902 };
1903 let model: Polygon<P> = Polygon::new(Ring::from_vec(pts));
1904 let j = JoinStrategy::Round {
1905 points_per_circle: 360,
1906 };
1907 let parcel_buffer = buffer(&parcel, 1.0, j, PointStrategy::Square).unwrap();
1908 let model_buffer = buffer(&model, 1.0, j, PointStrategy::Square).unwrap();
1909 let a = ring_area(parcel_buffer.polygons().next().unwrap().exterior()).abs();
1910 let b = ring_area(model_buffer.polygons().next().unwrap().exterior()).abs();
1911 close(a, b, 1e-12);
1912 }
1913
1914 #[test]
1915 fn miter_is_winding_independent() {
1916 let ccw: Polygon<P> =
1918 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
1919 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)]];
1920 close(
1921 ring_area(buffer_convex_polygon(&ccw, 1.0, JoinStrategy::Miter).exterior()).abs(),
1922 16.0,
1923 1e-9,
1924 );
1925 close(
1926 ring_area(buffer_convex_polygon(&cw, 1.0, JoinStrategy::Miter).exterior()).abs(),
1927 16.0,
1928 1e-9,
1929 );
1930 }
1931}