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
341impl<G> BufferStrategy<G, CartesianBuffer> for PointBuffer
344where
345 G: Point + PointMut + Default + Copy,
346 G::Scalar: CoordinateScalar + Into<f64> + FromF64,
347 <G::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
348{
349 fn apply(
350 &self,
351 point_geometry: &G,
352 settings: BufferSettings,
353 _coordinate_strategy: &CartesianBuffer,
354 ) -> Result<MultiPolygon<Polygon<G>>, OverlayError> {
355 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
356 return Err(OverlayError::Unsupported);
357 };
358 if !distance.is_finite() {
359 return Err(OverlayError::Unsupported);
360 }
361 if distance <= 0.0 {
362 return Ok(MultiPolygon(alloc::vec![]));
363 }
364 let point = match settings.point {
365 BufferPointStrategy::Circle { points_per_circle } => {
366 PointStrategy::Circle { points_per_circle }
367 }
368 BufferPointStrategy::Square => PointStrategy::Square,
369 };
370 let ring = buffer_point(point_geometry, distance, point);
371 Ok(MultiPolygon(alloc::vec![Polygon::new(ring)]))
372 }
373}
374
375impl<G> BufferStrategy<G, CartesianBuffer> for PolygonBuffer
378where
379 G: PolygonTrait,
380 G::Point: PointMut + Default + Copy,
381 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
382 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
383{
384 fn apply(
385 &self,
386 polygon: &G,
387 settings: BufferSettings,
388 _coordinate_strategy: &CartesianBuffer,
389 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
390 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
391 return Err(OverlayError::Unsupported);
392 };
393 if !distance.is_finite() || distance == 0.0 {
394 return Err(OverlayError::Unsupported);
395 }
396 let Some(outer) = offset_ring(polygon.exterior(), distance, settings.join, true) else {
397 return Ok(MultiPolygon(alloc::vec![]));
398 };
399 let inners = polygon
400 .interiors()
401 .filter_map(|ring| offset_ring(ring, -distance, settings.join, false))
402 .collect::<Vec<_>>();
403 let outer_vertices = distinct_vertices(&outer);
404 if inners.iter().any(|inner| {
405 let inner_vertices = distinct_vertices(inner);
406 outer_vertices
407 .iter()
408 .all(|point| point_in_or_on_ring(*point, &inner_vertices))
409 }) {
410 return Ok(MultiPolygon::new());
411 }
412
413 Ok(MultiPolygon(alloc::vec![Polygon::with_inners(
414 outer, inners,
415 )]))
416 }
417}
418
419impl<G> BufferStrategy<G, CartesianBuffer> for LinestringBuffer
420where
421 G: LinestringTrait,
422 G::Point: PointMut + Default + Copy,
423 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
424 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
425{
426 fn apply(
427 &self,
428 line: &G,
429 settings: BufferSettings,
430 _coordinate_strategy: &CartesianBuffer,
431 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
432 let (left, right) = match settings.distance {
433 BufferDistanceStrategy::Symmetric(distance) => (distance, distance),
434 BufferDistanceStrategy::Asymmetric { left, right } => (left, right),
435 };
436 if !left.is_finite() || !right.is_finite() || left < 0.0 || right < 0.0 {
437 return Err(OverlayError::Unsupported);
438 }
439 if left == 0.0 && right == 0.0 {
440 return Ok(MultiPolygon(alloc::vec![]));
441 }
442 let polygon = buffer_linestring(line, left, right, settings.join, settings.end)?;
443 Ok(MultiPolygon(alloc::vec![polygon]))
444 }
445}
446
447impl<G> BufferStrategy<G, CartesianBuffer> for SegmentBuffer
448where
449 G: SegmentTrait,
450 G::Point: PointMut + Default + Copy,
451 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
452 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
453{
454 fn apply(
455 &self,
456 segment: &G,
457 settings: BufferSettings,
458 coordinate_strategy: &CartesianBuffer,
459 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
460 let line: Linestring<G::Point> =
461 Linestring::from_vec(alloc::vec![segment_start(segment), segment_end(segment)]);
462 LinestringBuffer.apply(&line, settings, coordinate_strategy)
463 }
464}
465
466impl<G> BufferStrategy<G, CartesianBuffer> for RingBuffer
467where
468 G: RingTrait,
469 G::Point: PointMut + Default + Copy,
470 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
471 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
472{
473 fn apply(
474 &self,
475 ring: &G,
476 settings: BufferSettings,
477 _coordinate_strategy: &CartesianBuffer,
478 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
479 let BufferDistanceStrategy::Symmetric(distance) = settings.distance else {
480 return Err(OverlayError::Unsupported);
481 };
482 if !distance.is_finite() || distance == 0.0 {
483 return Err(OverlayError::Unsupported);
484 }
485 Ok(offset_ring(ring, distance, settings.join, true)
486 .map_or_else(MultiPolygon::new, |outer| {
487 MultiPolygon::from_vec(alloc::vec![Polygon::new(outer)])
488 }))
489 }
490}
491
492impl<G> BufferStrategy<G, CartesianBuffer> for BoxBuffer
493where
494 G: BoxTrait,
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 bounds: &G,
502 settings: BufferSettings,
503 coordinate_strategy: &CartesianBuffer,
504 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
505 let minimum = box_min(bounds);
506 let maximum = box_max(bounds);
507 let min_x = minimum.get::<0>().into();
508 let min_y = minimum.get::<1>().into();
509 let max_x = maximum.get::<0>().into();
510 let max_y = maximum.get::<1>().into();
511 let ring: Ring<G::Point> = Ring::from_vec(alloc::vec![
512 make_point(min_x, min_y),
513 make_point(min_x, max_y),
514 make_point(max_x, max_y),
515 make_point(max_x, min_y),
516 make_point(min_x, min_y),
517 ]);
518 RingBuffer.apply(&ring, settings, coordinate_strategy)
519 }
520}
521
522impl<G> BufferStrategy<G, CartesianBuffer> for MultiPointBuffer
523where
524 G: MultiPointTrait<ItemPoint = <G as Geometry>::Point>,
525 G::Point: PointMut + Default + Copy,
526 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
527 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
528{
529 fn apply(
530 &self,
531 points: &G,
532 settings: BufferSettings,
533 coordinate_strategy: &CartesianBuffer,
534 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
535 let mut output = MultiPolygon::new();
536 for point in points.points() {
537 output
538 .0
539 .extend(PointBuffer.apply(point, settings, coordinate_strategy)?.0);
540 }
541 crate::merge::merge_polygons(output.0)
542 }
543}
544
545impl<G> BufferStrategy<G, CartesianBuffer> for MultiLinestringBuffer
546where
547 G: MultiLinestringTrait,
548 G::Point: PointMut + Default + Copy,
549 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
550 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
551{
552 fn apply(
553 &self,
554 lines: &G,
555 settings: BufferSettings,
556 coordinate_strategy: &CartesianBuffer,
557 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
558 let mut output = MultiPolygon::new();
559 for line in lines.linestrings() {
560 output.0.extend(
561 LinestringBuffer
562 .apply(line, settings, coordinate_strategy)?
563 .0,
564 );
565 }
566 crate::merge::merge_polygons(output.0)
567 }
568}
569
570impl<G> BufferStrategy<G, CartesianBuffer> for MultiPolygonBuffer
571where
572 G: MultiPolygonTrait,
573 G::Point: PointMut + Default + Copy,
574 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
575 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
576{
577 fn apply(
578 &self,
579 polygons: &G,
580 settings: BufferSettings,
581 coordinate_strategy: &CartesianBuffer,
582 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
583 let mut output = MultiPolygon::new();
584 for polygon in polygons.polygons() {
585 output.0.extend(
586 PolygonBuffer
587 .apply(polygon, settings, coordinate_strategy)?
588 .0,
589 );
590 }
591 crate::merge::merge_polygons(output.0)
592 }
593}
594
595trait AngularCoordinateSystem {
596 type Units: AngleUnit;
597}
598
599impl<Units: AngleUnit> AngularCoordinateSystem for Spherical<Units> {
600 type Units = Units;
601}
602
603impl<Units: AngleUnit> AngularCoordinateSystem for Geographic<Units> {
604 type Units = Units;
605}
606
607#[derive(Debug, Clone, Copy)]
608struct LocalProjection {
609 longitude: f64,
610 latitude: f64,
611 east_scale: f64,
612 north_scale: f64,
613}
614
615impl LocalProjection {
616 fn project(self, longitude: f64, latitude: f64) -> (f64, f64) {
617 let mut delta_longitude = longitude - self.longitude;
618 if delta_longitude > core::f64::consts::PI {
619 delta_longitude -= 2.0 * core::f64::consts::PI;
620 } else if delta_longitude < -core::f64::consts::PI {
621 delta_longitude += 2.0 * core::f64::consts::PI;
622 }
623 (
624 delta_longitude * self.east_scale,
625 (latitude - self.latitude) * self.north_scale,
626 )
627 }
628
629 fn unproject(self, x: f64, y: f64) -> (f64, f64) {
630 let mut longitude = self.longitude + x / self.east_scale;
631 if longitude > core::f64::consts::PI {
632 longitude -= 2.0 * core::f64::consts::PI;
633 } else if longitude < -core::f64::consts::PI {
634 longitude += 2.0 * core::f64::consts::PI;
635 }
636 (longitude, self.latitude + y / self.north_scale)
637 }
638}
639
640trait AngularBufferProjection {
641 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError>;
642}
643
644impl AngularBufferProjection for SphericalBuffer {
645 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError> {
646 if !self.radius.is_finite() || self.radius <= 0.0 {
647 return Err(OverlayError::Unsupported);
648 }
649 let longitude_scale = cos(latitude);
650 if longitude_scale.abs() <= f64::EPSILON {
651 return Err(OverlayError::Unsupported);
652 }
653 let east_scale = self.radius * longitude_scale;
654 Ok(LocalProjection {
655 longitude,
656 latitude,
657 east_scale,
658 north_scale: self.radius,
659 })
660 }
661}
662
663impl AngularBufferProjection for GeographicBuffer {
664 fn projection(&self, longitude: f64, latitude: f64) -> Result<LocalProjection, OverlayError> {
665 let spheroid = self.spheroid;
666 if !spheroid.equatorial_radius.is_finite()
667 || spheroid.equatorial_radius <= 0.0
668 || !spheroid.flattening.is_finite()
669 || !(0.0..1.0).contains(&spheroid.flattening)
670 {
671 return Err(OverlayError::Unsupported);
672 }
673
674 let eccentricity_squared = spheroid.eccentricity_squared();
675 let sin_latitude = sin(latitude);
676 let denominator = sqrt(1.0 - eccentricity_squared * sin_latitude * sin_latitude);
677 let prime_vertical = spheroid.equatorial_radius / denominator;
678 let meridional = spheroid.equatorial_radius * (1.0 - eccentricity_squared)
679 / (denominator * denominator * denominator);
680 let longitude_scale = cos(latitude);
681 if longitude_scale.abs() <= f64::EPSILON {
682 return Err(OverlayError::Unsupported);
683 }
684 let east_scale = prime_vertical * longitude_scale;
685 Ok(LocalProjection {
686 longitude,
687 latitude,
688 east_scale,
689 north_scale: meridional,
690 })
691 }
692}
693
694fn angular_coordinates<P>(point: &P) -> (f64, f64)
695where
696 P: Point,
697 P::Scalar: Into<f64>,
698 P::Cs: AngularCoordinateSystem,
699{
700 let longitude = <P::Cs as AngularCoordinateSystem>::Units::to_radians(point.get::<0>().into());
701 let latitude = <P::Cs as AngularCoordinateSystem>::Units::to_radians(point.get::<1>().into());
702 (longitude, latitude)
703}
704
705fn angular_point<P>(longitude: f64, latitude: f64) -> P
706where
707 P: PointMut + Default,
708 P::Scalar: FromF64,
709 P::Cs: AngularCoordinateSystem,
710{
711 let mut point = P::default();
712 let longitude = <P::Cs as AngularCoordinateSystem>::Units::from_radians(longitude);
713 let latitude = <P::Cs as AngularCoordinateSystem>::Units::from_radians(latitude);
714 point.set::<0>(P::Scalar::from_f64(longitude));
715 point.set::<1>(P::Scalar::from_f64(latitude));
716 point
717}
718
719fn projection_center(coordinates: &[(f64, f64)]) -> Result<(f64, f64), OverlayError> {
720 if coordinates.is_empty() {
721 return Err(OverlayError::Unsupported);
722 }
723 let mut longitude_sine = 0.0;
724 let mut longitude_cosine = 0.0;
725 let mut latitude = 0.0;
726 for &(longitude, point_latitude) in coordinates {
727 longitude_sine += sin(longitude);
728 longitude_cosine += cos(longitude);
729 latitude += point_latitude;
730 }
731 let count = coordinates.len() as f64;
732 Ok((atan2(longitude_sine, longitude_cosine), latitude / count))
733}
734
735type ProjectedPoint = Point2D<f64, Cartesian>;
736
737fn projected_point<P>(point: &P, projection: LocalProjection) -> ProjectedPoint
738where
739 P: Point,
740 P::Scalar: Into<f64>,
741 P::Cs: AngularCoordinateSystem,
742{
743 let (longitude, latitude) = angular_coordinates(point);
744 let (x, y) = projection.project(longitude, latitude);
745 ProjectedPoint::new(x, y)
746}
747
748fn projected_ring<R>(ring: &R, projection: LocalProjection) -> Ring<ProjectedPoint>
749where
750 R: RingTrait,
751 R::Point: Point,
752 <R::Point as Point>::Scalar: Into<f64>,
753 <R::Point as Point>::Cs: AngularCoordinateSystem,
754{
755 Ring::from_vec(
756 ring.points()
757 .map(|point| projected_point(point, projection))
758 .collect(),
759 )
760}
761
762fn projected_polygon<G>(polygon: &G, projection: LocalProjection) -> Polygon<ProjectedPoint>
763where
764 G: PolygonTrait,
765 G::Point: Point,
766 <G::Point as Point>::Scalar: Into<f64>,
767 <G::Point as Point>::Cs: AngularCoordinateSystem,
768{
769 Polygon::with_inners(
770 projected_ring(polygon.exterior(), projection),
771 polygon
772 .interiors()
773 .map(|ring| projected_ring(ring, projection))
774 .collect(),
775 )
776}
777
778fn unprojected_buffer<P>(
779 polygons: MultiPolygon<Polygon<ProjectedPoint>>,
780 projection: LocalProjection,
781) -> MultiPolygon<Polygon<P>>
782where
783 P: PointMut + Default,
784 P::Scalar: FromF64,
785 P::Cs: AngularCoordinateSystem,
786{
787 MultiPolygon::from_vec(
788 polygons
789 .0
790 .into_iter()
791 .map(|polygon| {
792 let outer = Ring::from_vec(
793 polygon
794 .outer
795 .0
796 .into_iter()
797 .map(|point| {
798 let (longitude, latitude) = projection.unproject(point.x(), point.y());
799 angular_point(longitude, latitude)
800 })
801 .collect(),
802 );
803 let inners = polygon
804 .inners
805 .into_iter()
806 .map(|ring| {
807 Ring::from_vec(
808 ring.0
809 .into_iter()
810 .map(|point| {
811 let (longitude, latitude) =
812 projection.unproject(point.x(), point.y());
813 angular_point(longitude, latitude)
814 })
815 .collect(),
816 )
817 })
818 .collect();
819 Polygon::with_inners(outer, inners)
820 })
821 .collect(),
822 )
823}
824
825fn projection_for_points<'a, P>(
826 points: impl IntoIterator<Item = &'a P>,
827 strategy: &impl AngularBufferProjection,
828) -> Result<LocalProjection, OverlayError>
829where
830 P: Point + 'a,
831 P::Scalar: Into<f64>,
832 P::Cs: AngularCoordinateSystem,
833{
834 let coordinates: Vec<_> = points.into_iter().map(angular_coordinates).collect();
835 let (longitude, latitude) = projection_center(&coordinates)?;
836 strategy.projection(longitude, latitude)
837}
838
839fn projected_point_apply<P>(
840 point: &P,
841 settings: BufferSettings,
842 strategy: &impl AngularBufferProjection,
843) -> Result<MultiPolygon<Polygon<P>>, OverlayError>
844where
845 P: Point + PointMut + Default + Copy,
846 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
847 P::Cs: AngularCoordinateSystem,
848{
849 let projection = projection_for_points(core::iter::once(point), strategy)?;
850 let point = projected_point(point, projection);
851 let output = PointBuffer.apply(&point, settings, &CartesianBuffer)?;
852 Ok(unprojected_buffer(output, projection))
853}
854
855fn projected_linestring_apply<L>(
856 line: &L,
857 settings: BufferSettings,
858 strategy: &impl AngularBufferProjection,
859) -> Result<MultiPolygon<Polygon<L::Point>>, OverlayError>
860where
861 L: LinestringTrait,
862 L::Point: PointMut + Default + Copy,
863 <L::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
864 <L::Point as Point>::Cs: AngularCoordinateSystem,
865{
866 let projection = projection_for_points(line.points(), strategy)?;
867 let projected = Linestring::from_vec(
868 line.points()
869 .map(|point| projected_point(point, projection))
870 .collect(),
871 );
872 let output = LinestringBuffer.apply(&projected, settings, &CartesianBuffer)?;
873 Ok(unprojected_buffer(output, projection))
874}
875
876fn projected_ring_apply<R>(
877 ring: &R,
878 settings: BufferSettings,
879 strategy: &impl AngularBufferProjection,
880) -> Result<MultiPolygon<Polygon<R::Point>>, OverlayError>
881where
882 R: RingTrait,
883 R::Point: PointMut + Default + Copy,
884 <R::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
885 <R::Point as Point>::Cs: AngularCoordinateSystem,
886{
887 let projection = projection_for_points(ring.points(), strategy)?;
888 let output = RingBuffer.apply(
889 &projected_ring(ring, projection),
890 settings,
891 &CartesianBuffer,
892 )?;
893 Ok(unprojected_buffer(output, projection))
894}
895
896fn projected_polygon_apply<G>(
897 polygon: &G,
898 settings: BufferSettings,
899 strategy: &impl AngularBufferProjection,
900) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError>
901where
902 G: PolygonTrait,
903 G::Point: PointMut + Default + Copy,
904 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
905 <G::Point as Point>::Cs: AngularCoordinateSystem,
906{
907 let mut coordinates = polygon
908 .exterior()
909 .points()
910 .map(angular_coordinates)
911 .collect::<Vec<_>>();
912 for ring in polygon.interiors() {
913 coordinates.extend(ring.points().map(angular_coordinates));
914 }
915 let (longitude, latitude) = projection_center(&coordinates)?;
916 let projection = strategy.projection(longitude, latitude)?;
917 let output = PolygonBuffer.apply(
918 &projected_polygon(polygon, projection),
919 settings,
920 &CartesianBuffer,
921 )?;
922 Ok(unprojected_buffer(output, projection))
923}
924
925macro_rules! impl_angular_buffer_strategy {
926 ($strategy:ty, $family:ty) => {
927 impl<G> BufferStrategy<G, $strategy> for PointBuffer
928 where
929 G: Point + PointMut + Default + Copy,
930 G::Scalar: CoordinateScalar + Into<f64> + FromF64,
931 G::Cs: AngularCoordinateSystem,
932 <G::Cs as CoordinateSystem>::Family: SameAs<$family>,
933 {
934 fn apply(
935 &self,
936 geometry: &G,
937 settings: BufferSettings,
938 coordinate_strategy: &$strategy,
939 ) -> Result<MultiPolygon<Polygon<G>>, OverlayError> {
940 projected_point_apply(geometry, settings, coordinate_strategy)
941 }
942 }
943
944 impl<G> BufferStrategy<G, $strategy> for LinestringBuffer
945 where
946 G: LinestringTrait,
947 G::Point: PointMut + Default + Copy,
948 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
949 <G::Point as Point>::Cs: AngularCoordinateSystem,
950 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
951 {
952 fn apply(
953 &self,
954 geometry: &G,
955 settings: BufferSettings,
956 coordinate_strategy: &$strategy,
957 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
958 projected_linestring_apply(geometry, settings, coordinate_strategy)
959 }
960 }
961
962 impl<G> BufferStrategy<G, $strategy> for SegmentBuffer
963 where
964 G: SegmentTrait,
965 G::Point: PointMut + Default + Copy,
966 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
967 <G::Point as Point>::Cs: AngularCoordinateSystem,
968 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
969 {
970 fn apply(
971 &self,
972 geometry: &G,
973 settings: BufferSettings,
974 coordinate_strategy: &$strategy,
975 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
976 let line = Linestring::from_vec(alloc::vec![
977 segment_start(geometry),
978 segment_end(geometry),
979 ]);
980 projected_linestring_apply(&line, settings, coordinate_strategy)
981 }
982 }
983
984 impl<G> BufferStrategy<G, $strategy> for RingBuffer
985 where
986 G: RingTrait,
987 G::Point: PointMut + Default + Copy,
988 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
989 <G::Point as Point>::Cs: AngularCoordinateSystem,
990 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
991 {
992 fn apply(
993 &self,
994 geometry: &G,
995 settings: BufferSettings,
996 coordinate_strategy: &$strategy,
997 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
998 projected_ring_apply(geometry, settings, coordinate_strategy)
999 }
1000 }
1001
1002 impl<G> BufferStrategy<G, $strategy> for PolygonBuffer
1003 where
1004 G: PolygonTrait,
1005 G::Point: PointMut + Default + Copy,
1006 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1007 <G::Point as Point>::Cs: AngularCoordinateSystem,
1008 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1009 {
1010 fn apply(
1011 &self,
1012 geometry: &G,
1013 settings: BufferSettings,
1014 coordinate_strategy: &$strategy,
1015 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1016 projected_polygon_apply(geometry, settings, coordinate_strategy)
1017 }
1018 }
1019
1020 impl<G> BufferStrategy<G, $strategy> for BoxBuffer
1021 where
1022 G: BoxTrait,
1023 G::Point: PointMut + Default + Copy,
1024 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1025 <G::Point as Point>::Cs: AngularCoordinateSystem,
1026 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1027 {
1028 fn apply(
1029 &self,
1030 geometry: &G,
1031 settings: BufferSettings,
1032 coordinate_strategy: &$strategy,
1033 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1034 let minimum = box_min(geometry);
1035 let maximum = box_max(geometry);
1036 let projection = projection_for_points([&minimum, &maximum], coordinate_strategy)?;
1037 let projected = ModelBox::from_corners(
1038 projected_point(&minimum, projection),
1039 projected_point(&maximum, projection),
1040 );
1041 let output = BoxBuffer.apply(&projected, settings, &CartesianBuffer)?;
1042 Ok(unprojected_buffer(output, projection))
1043 }
1044 }
1045
1046 impl<G> BufferStrategy<G, $strategy> for MultiPointBuffer
1047 where
1048 G: MultiPointTrait<ItemPoint = <G as Geometry>::Point>,
1049 G::Point: PointMut + Default + Copy,
1050 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1051 <G::Point as Point>::Cs: AngularCoordinateSystem,
1052 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1053 {
1054 fn apply(
1055 &self,
1056 geometry: &G,
1057 settings: BufferSettings,
1058 coordinate_strategy: &$strategy,
1059 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1060 let projection = projection_for_points(geometry.points(), coordinate_strategy)?;
1061 let projected = MultiPoint::from_vec(
1062 geometry
1063 .points()
1064 .map(|point| projected_point(point, projection))
1065 .collect(),
1066 );
1067 let output = MultiPointBuffer.apply(&projected, settings, &CartesianBuffer)?;
1068 Ok(unprojected_buffer(output, projection))
1069 }
1070 }
1071
1072 impl<G> BufferStrategy<G, $strategy> for MultiLinestringBuffer
1073 where
1074 G: MultiLinestringTrait,
1075 G::Point: PointMut + Default + Copy,
1076 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1077 <G::Point as Point>::Cs: AngularCoordinateSystem,
1078 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1079 {
1080 fn apply(
1081 &self,
1082 geometry: &G,
1083 settings: BufferSettings,
1084 coordinate_strategy: &$strategy,
1085 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1086 let coordinates = geometry
1087 .linestrings()
1088 .flat_map(|line| line.points().map(angular_coordinates))
1089 .collect::<Vec<_>>();
1090 let (longitude, latitude) = projection_center(&coordinates)?;
1091 let projection = coordinate_strategy.projection(longitude, latitude)?;
1092 let projected = MultiLinestring::from_vec(
1093 geometry
1094 .linestrings()
1095 .map(|line| {
1096 Linestring::from_vec(
1097 line.points()
1098 .map(|point| projected_point(point, projection))
1099 .collect(),
1100 )
1101 })
1102 .collect(),
1103 );
1104 let output = MultiLinestringBuffer.apply(&projected, settings, &CartesianBuffer)?;
1105 Ok(unprojected_buffer(output, projection))
1106 }
1107 }
1108
1109 impl<G> BufferStrategy<G, $strategy> for MultiPolygonBuffer
1110 where
1111 G: MultiPolygonTrait,
1112 G::Point: PointMut + Default + Copy,
1113 <G::Point as Point>::Scalar: CoordinateScalar + Into<f64> + FromF64,
1114 <G::Point as Point>::Cs: AngularCoordinateSystem,
1115 <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<$family>,
1116 {
1117 fn apply(
1118 &self,
1119 geometry: &G,
1120 settings: BufferSettings,
1121 coordinate_strategy: &$strategy,
1122 ) -> Result<MultiPolygon<Polygon<G::Point>>, OverlayError> {
1123 let coordinates = geometry
1124 .polygons()
1125 .flat_map(|polygon| {
1126 polygon
1127 .exterior()
1128 .points()
1129 .chain(polygon.interiors().flat_map(RingTrait::points))
1130 .map(angular_coordinates)
1131 })
1132 .collect::<Vec<_>>();
1133 let (longitude, latitude) = projection_center(&coordinates)?;
1134 let projection = coordinate_strategy.projection(longitude, latitude)?;
1135 let projected = MultiPolygon::from_vec(
1136 geometry
1137 .polygons()
1138 .map(|polygon| projected_polygon(polygon, projection))
1139 .collect(),
1140 );
1141 let output = MultiPolygonBuffer.apply(&projected, settings, &CartesianBuffer)?;
1142 Ok(unprojected_buffer(output, projection))
1143 }
1144 }
1145 };
1146}
1147
1148impl_angular_buffer_strategy!(SphericalBuffer, SphericalFamily);
1149impl_angular_buffer_strategy!(GeographicBuffer, GeographicFamily);
1150
1151#[inline]
1172#[must_use]
1173pub fn buffer_point<P>(center: &P, distance: f64, strategy: PointStrategy) -> Ring<P>
1174where
1175 P: PointMut + Default + Copy,
1176 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
1177 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1178{
1179 let cx: f64 = center.get::<0>().into();
1180 let cy: f64 = center.get::<1>().into();
1181 match strategy {
1182 PointStrategy::Circle { points_per_circle } => {
1183 circle_ring(cx, cy, distance, points_per_circle.max(3))
1184 }
1185 PointStrategy::Square => {
1186 let d = distance;
1187 Ring::from_vec(alloc::vec![
1192 make_point(cx - d, cy - d),
1193 make_point(cx - d, cy + d),
1194 make_point(cx + d, cy + d),
1195 make_point(cx + d, cy - d),
1196 make_point(cx - d, cy - d),
1197 ])
1198 }
1199 }
1200}
1201
1202#[inline]
1232#[must_use]
1233pub fn buffer_convex_polygon<G, P>(polygon: &G, distance: f64, join: JoinStrategy) -> Polygon<P>
1234where
1235 G: PolygonTrait<Point = P>,
1236 P: PointMut + Default + Copy,
1237 P::Scalar: CoordinateScalar + Into<f64> + FromF64,
1238 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1239{
1240 let strategy = match join {
1241 JoinStrategy::Round { points_per_circle } => {
1242 BufferJoinStrategy::Round { points_per_circle }
1243 }
1244 JoinStrategy::Miter => BufferJoinStrategy::Miter {
1245 limit: f64::INFINITY,
1246 },
1247 };
1248 offset_ring(polygon.exterior(), distance, strategy, true)
1249 .map_or_else(|| Polygon::new(Ring::new()), Polygon::new)
1250}
1251
1252fn offset_ring<R, P>(
1253 ring: &R,
1254 distance: f64,
1255 join: BufferJoinStrategy,
1256 clockwise: bool,
1257) -> Option<Ring<P>>
1258where
1259 R: RingTrait<Point = P>,
1260 P: PointMut + Default + Copy,
1261 P::Scalar: Into<f64> + FromF64,
1262{
1263 let mut vertices = distinct_vertices(ring);
1264 if vertices.len() < 3 || !distance.is_finite() || distance == 0.0 {
1265 return None;
1266 }
1267 if signed_area_ccw_positive(&vertices) < 0.0 {
1268 vertices.reverse();
1269 }
1270
1271 let count = vertices.len();
1272 let mut boundary = Vec::new();
1273 for index in 0..count {
1274 let previous = vertices[(index + count - 1) % count];
1275 let vertex = vertices[index];
1276 let next = vertices[(index + 1) % count];
1277 let incoming = (vertex.0 - previous.0, vertex.1 - previous.1);
1278 let outgoing = (next.0 - vertex.0, next.1 - vertex.1);
1279 let incoming_normal = outward_normal(incoming.0, incoming.1);
1280 let outgoing_normal = outward_normal(outgoing.0, outgoing.1);
1281 let before = (
1282 vertex.0 + incoming_normal.0 * distance,
1283 vertex.1 + incoming_normal.1 * distance,
1284 );
1285 let after = (
1286 vertex.0 + outgoing_normal.0 * distance,
1287 vertex.1 + outgoing_normal.1 * distance,
1288 );
1289 let intersection = line_intersection(before, incoming, after, outgoing);
1290 let cross = incoming.0 * outgoing.1 - incoming.1 * outgoing.0;
1291 let exterior_join = cross * distance > 0.0;
1292
1293 if !exterior_join {
1294 if let Some(point) = intersection {
1295 boundary.push(point);
1296 } else {
1297 boundary.push(after);
1298 }
1299 continue;
1300 }
1301
1302 match join {
1303 BufferJoinStrategy::Round { points_per_circle } => {
1304 boundary.push(before);
1305 push_arc_between(
1306 &mut boundary,
1307 vertex,
1308 before,
1309 after,
1310 distance.abs(),
1311 points_per_circle.max(4),
1312 true,
1313 );
1314 boundary.push(after);
1315 }
1316 BufferJoinStrategy::Miter { limit } => {
1317 if let Some(point) = intersection {
1318 let miter_length = hypot(point.0 - vertex.0, point.1 - vertex.1);
1319 if point.0.is_finite()
1320 && point.1.is_finite()
1321 && miter_length <= limit.max(1.0) * distance.abs()
1322 {
1323 boundary.push(point);
1324 } else {
1325 boundary.push(before);
1326 boundary.push(after);
1327 }
1328 } else {
1329 boundary.push(before);
1330 boundary.push(after);
1331 }
1332 }
1333 }
1334 }
1335
1336 boundary.dedup();
1337 if boundary.len() < 3 || signed_area_ccw_positive(&boundary).abs() <= f64::EPSILON {
1338 return None;
1339 }
1340 if distance < 0.0 {
1341 let clearance = distance.abs();
1342 let tolerance = mul_add(clearance, 1e-9, f64::EPSILON * 16.0);
1343 if boundary.iter().any(|point| {
1344 !point_in_or_on_ring(*point, &vertices)
1345 || minimum_boundary_distance(*point, &vertices) + tolerance < clearance
1346 }) {
1347 return None;
1348 }
1349 }
1350 if clockwise == (signed_area_ccw_positive(&boundary) > 0.0) {
1351 boundary.reverse();
1352 }
1353 boundary.push(boundary[0]);
1354 Some(Ring::from_vec(
1355 boundary
1356 .into_iter()
1357 .map(|(x, y)| make_point(x, y))
1358 .collect(),
1359 ))
1360}
1361
1362fn buffer_linestring<L, P>(
1363 line: &L,
1364 left: f64,
1365 right: f64,
1366 join: BufferJoinStrategy,
1367 end: BufferEndStrategy,
1368) -> Result<Polygon<P>, OverlayError>
1369where
1370 L: LinestringTrait<Point = P>,
1371 P: PointMut + Default + Copy,
1372 P::Scalar: Into<f64> + FromF64,
1373{
1374 let mut vertices: Vec<(f64, f64)> = Vec::new();
1375 for point in line.points() {
1376 let value = (point.get::<0>().into(), point.get::<1>().into());
1377 if vertices.last().copied() != Some(value) {
1378 vertices.push(value);
1379 }
1380 }
1381 if vertices.len() < 2 {
1382 return Err(OverlayError::Unsupported);
1383 }
1384
1385 let left_path = offset_path(&vertices, left, true, join);
1386 let right_path = offset_path(&vertices, right, false, join);
1387 debug_assert!(!left_path.is_empty() && !right_path.is_empty());
1388 let mut boundary = left_path;
1389 match end {
1390 BufferEndStrategy::Flat => {}
1391 BufferEndStrategy::Round { points_per_circle } => {
1392 let center = *vertices.last().expect("linestring has an endpoint");
1393 let from = *boundary.last().expect("left path has an endpoint");
1394 let to = *right_path.last().expect("right path has an endpoint");
1395 push_end_arc(
1396 &mut boundary,
1397 center,
1398 from,
1399 to,
1400 points_per_circle.max(4),
1401 true,
1402 );
1403 }
1404 }
1405 boundary.extend(right_path.iter().rev().copied());
1406 if let BufferEndStrategy::Round { points_per_circle } = end {
1407 let to = boundary[0];
1408 push_end_arc(
1409 &mut boundary,
1410 vertices[0],
1411 right_path[0],
1412 to,
1413 points_per_circle.max(4),
1414 true,
1415 );
1416 }
1417 let first = boundary[0];
1418 boundary.push(first);
1419 Ok(Polygon::new(Ring::from_vec(
1420 boundary
1421 .into_iter()
1422 .map(|(x, y)| make_point(x, y))
1423 .collect(),
1424 )))
1425}
1426
1427fn offset_path(
1428 vertices: &[(f64, f64)],
1429 distance: f64,
1430 left: bool,
1431 join: BufferJoinStrategy,
1432) -> Vec<(f64, f64)> {
1433 let side = if left { 1.0 } else { -1.0 };
1434 let normals: Vec<(f64, f64)> = vertices
1435 .windows(2)
1436 .map(|edge| {
1437 let dx = edge[1].0 - edge[0].0;
1438 let dy = edge[1].1 - edge[0].1;
1439 let length = hypot(dx, dy);
1440 (-dy / length * side, dx / length * side)
1441 })
1442 .collect();
1443 let mut path = Vec::with_capacity(vertices.len());
1444 path.push((
1445 vertices[0].0 + normals[0].0 * distance,
1446 vertices[0].1 + normals[0].1 * distance,
1447 ));
1448 for index in 1..vertices.len() - 1 {
1449 let vertex = vertices[index];
1450 let previous = vertices[index - 1];
1451 let next = vertices[index + 1];
1452 let before = (
1453 vertex.0 + normals[index - 1].0 * distance,
1454 vertex.1 + normals[index - 1].1 * distance,
1455 );
1456 let after = (
1457 vertex.0 + normals[index].0 * distance,
1458 vertex.1 + normals[index].1 * distance,
1459 );
1460 let intersection = line_intersection(
1461 before,
1462 (vertex.0 - previous.0, vertex.1 - previous.1),
1463 after,
1464 (next.0 - vertex.0, next.1 - vertex.1),
1465 );
1466 match (join, intersection) {
1467 (BufferJoinStrategy::Miter { limit }, Some(point))
1468 if point.0.is_finite() && point.1.is_finite() =>
1469 {
1470 let miter_length = hypot(point.0 - vertex.0, point.1 - vertex.1);
1471 if distance == 0.0 || miter_length <= limit.max(1.0) * distance.abs() {
1472 path.push(point);
1473 } else {
1474 path.push(before);
1475 path.push(after);
1476 }
1477 }
1478 (BufferJoinStrategy::Round { points_per_circle }, _) => {
1479 path.push(before);
1480 push_arc_between(
1481 &mut path,
1482 vertex,
1483 before,
1484 after,
1485 distance.abs(),
1486 points_per_circle.max(4),
1487 left,
1488 );
1489 path.push(after);
1490 }
1491 _ => {
1492 path.push(before);
1493 path.push(after);
1494 }
1495 }
1496 }
1497 let last = vertices.len() - 1;
1498 path.push((
1499 vertices[last].0 + normals[last - 1].0 * distance,
1500 vertices[last].1 + normals[last - 1].1 * distance,
1501 ));
1502 path
1503}
1504
1505fn line_intersection(
1506 first_origin: (f64, f64),
1507 first_direction: (f64, f64),
1508 second_origin: (f64, f64),
1509 second_direction: (f64, f64),
1510) -> Option<(f64, f64)> {
1511 let denominator =
1512 first_direction.0 * second_direction.1 - first_direction.1 * second_direction.0;
1513 if denominator.abs() <= f64::EPSILON {
1514 return None;
1515 }
1516 let delta = (
1517 second_origin.0 - first_origin.0,
1518 second_origin.1 - first_origin.1,
1519 );
1520 let factor = (delta.0 * second_direction.1 - delta.1 * second_direction.0) / denominator;
1521 Some((
1522 first_origin.0 + factor * first_direction.0,
1523 first_origin.1 + factor * first_direction.1,
1524 ))
1525}
1526
1527fn push_arc_between(
1528 output: &mut Vec<(f64, f64)>,
1529 center: (f64, f64),
1530 from: (f64, f64),
1531 to: (f64, f64),
1532 radius: f64,
1533 points_per_circle: usize,
1534 counterclockwise: bool,
1535) {
1536 if radius == 0.0 {
1537 return;
1538 }
1539 let start = atan2(from.1 - center.1, from.0 - center.0);
1540 let mut end = atan2(to.1 - center.1, to.0 - center.0);
1541 if counterclockwise {
1542 while end < start {
1543 end += core::f64::consts::TAU;
1544 }
1545 } else {
1546 while end > start {
1547 end -= core::f64::consts::TAU;
1548 }
1549 }
1550 let sweep = end - start;
1551 let steps =
1552 ceil((sweep.abs() / core::f64::consts::TAU) * points_per_circle as f64).max(1.0) as usize;
1553 for step in 1..steps {
1554 let angle = start + sweep * step as f64 / steps as f64;
1555 output.push((
1556 center.0 + radius * cos(angle),
1557 center.1 + radius * sin(angle),
1558 ));
1559 }
1560}
1561
1562fn push_end_arc(
1563 output: &mut Vec<(f64, f64)>,
1564 center: (f64, f64),
1565 from: (f64, f64),
1566 to: (f64, f64),
1567 points_per_circle: usize,
1568 clockwise: bool,
1569) {
1570 let radius =
1571 hypot(from.0 - center.0, from.1 - center.1).max(hypot(to.0 - center.0, to.1 - center.1));
1572 push_arc_between(
1573 output,
1574 center,
1575 from,
1576 to,
1577 radius,
1578 points_per_circle,
1579 !clockwise,
1580 );
1581}
1582
1583fn make_point<P>(x: f64, y: f64) -> P
1585where
1586 P: PointMut + Default,
1587 P::Scalar: FromF64,
1588{
1589 let mut p = P::default();
1590 p.set::<0>(P::Scalar::from_f64(x));
1591 p.set::<1>(P::Scalar::from_f64(y));
1592 p
1593}
1594
1595fn circle_ring<P>(cx: f64, cy: f64, r: f64, segments: usize) -> Ring<P>
1597where
1598 P: PointMut + Default + Copy,
1599 P::Scalar: FromF64,
1600{
1601 let mut pts = Vec::with_capacity(segments + 1);
1602 let step = core::f64::consts::TAU / segments as f64;
1603 for k in 0..segments {
1604 let a = -step * k as f64;
1605 pts.push(make_point(cx + r * cos(a), cy + r * sin(a)));
1606 }
1607 pts.push(pts[0]);
1608 Ring::from_vec(pts)
1609}
1610
1611fn distinct_vertices<R>(ring: &R) -> Vec<(f64, f64)>
1614where
1615 R: RingTrait,
1616 <R::Point as Point>::Scalar: Into<f64>,
1617{
1618 let mut pts: Vec<(f64, f64)> = ring
1619 .points()
1620 .map(|p| (p.get::<0>().into(), p.get::<1>().into()))
1621 .collect();
1622 if pts.len() >= 2 {
1623 let first = pts[0];
1624 let last = pts[pts.len() - 1];
1625 if first == last {
1626 pts.pop();
1627 }
1628 }
1629 pts
1630}
1631
1632fn signed_area_ccw_positive(verts: &[(f64, f64)]) -> f64 {
1636 let n = verts.len();
1637 let mut acc = 0.0;
1638 for i in 0..n {
1639 let a = verts[i];
1640 let b = verts[(i + 1) % n];
1641 acc += a.0 * b.1 - b.0 * a.1;
1642 }
1643 acc * 0.5
1644}
1645
1646fn minimum_boundary_distance(point: (f64, f64), vertices: &[(f64, f64)]) -> f64 {
1647 let mut minimum = f64::INFINITY;
1648 for index in 0..vertices.len() {
1649 let start = vertices[index];
1650 let end = vertices[(index + 1) % vertices.len()];
1651 let delta = (end.0 - start.0, end.1 - start.1);
1652 let length_squared = delta.0 * delta.0 + delta.1 * delta.1;
1653 let fraction = if length_squared == 0.0 {
1654 0.0
1655 } else {
1656 (((point.0 - start.0) * delta.0 + (point.1 - start.1) * delta.1) / length_squared)
1657 .clamp(0.0, 1.0)
1658 };
1659 let nearest = (start.0 + fraction * delta.0, start.1 + fraction * delta.1);
1660 minimum = minimum.min(hypot(point.0 - nearest.0, point.1 - nearest.1));
1661 }
1662 minimum
1663}
1664
1665fn point_in_or_on_ring(point: (f64, f64), vertices: &[(f64, f64)]) -> bool {
1666 let scale = vertices.iter().fold(1.0_f64, |acc, vertex| {
1667 acc.max(vertex.0.abs()).max(vertex.1.abs())
1668 });
1669 if minimum_boundary_distance(point, vertices) <= scale * 1e-12 {
1670 return true;
1671 }
1672
1673 let mut inside = false;
1674 for index in 0..vertices.len() {
1675 let start = vertices[index];
1676 let end = vertices[(index + 1) % vertices.len()];
1677 if (start.1 > point.1) != (end.1 > point.1)
1678 && point.0 < (end.0 - start.0) * (point.1 - start.1) / (end.1 - start.1) + start.0
1679 {
1680 inside = !inside;
1681 }
1682 }
1683 inside
1684}
1685
1686fn outward_normal(dx: f64, dy: f64) -> (f64, f64) {
1689 let len = (dx * dx + dy * dy).sqrt();
1690 if len == 0.0 {
1691 return (0.0, 0.0);
1692 }
1693 (dy / len, -dx / len)
1695}
1696
1697#[cfg(test)]
1698mod tests {
1699 use super::{JoinStrategy, PointStrategy, buffer, buffer_convex_polygon, buffer_point};
1703 use geometry_algorithm::ring_area;
1704 use geometry_cs::Cartesian;
1705 use geometry_model::{Point2D, Polygon, polygon};
1706 use geometry_trait::{MultiPolygon as _, Polygon as _};
1707
1708 type P = Point2D<f64, Cartesian>;
1709
1710 fn close(a: f64, b: f64, tol: f64) {
1711 assert!((a - b).abs() < tol, "expected {b}, got {a}");
1712 }
1713
1714 #[test]
1715 fn point_circle_area_approximates_pi_r_squared() {
1716 let disc = buffer_point(
1717 &P::new(0.0, 0.0),
1718 2.0,
1719 PointStrategy::Circle {
1720 points_per_circle: 720,
1721 },
1722 );
1723 close(ring_area(&disc).abs(), core::f64::consts::PI * 4.0, 1e-2);
1725 }
1726
1727 #[test]
1728 fn point_square_area() {
1729 let sq = buffer_point(&P::new(0.0, 0.0), 3.0, PointStrategy::Square);
1730 close(ring_area(&sq).abs(), 36.0, 1e-9);
1732 }
1733
1734 #[test]
1735 fn convex_square_round_buffer_area() {
1736 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)]];
1737 let grown = buffer_convex_polygon(
1738 &sq,
1739 1.0,
1740 JoinStrategy::Round {
1741 points_per_circle: 720,
1742 },
1743 );
1744 let expected = 4.0 + 8.0 + core::f64::consts::PI;
1746 close(ring_area(grown.exterior()).abs(), expected, 1e-2);
1747 }
1748
1749 #[test]
1750 fn convex_triangle_round_buffer_grows() {
1751 let tri: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (0.0, 3.0), (0.0, 0.0)]];
1752 let base = ring_area(tri.exterior()).abs(); let grown = buffer_convex_polygon(
1754 &tri,
1755 0.5,
1756 JoinStrategy::Round {
1757 points_per_circle: 360,
1758 },
1759 );
1760 assert!(ring_area(grown.exterior()).abs() > base);
1762 }
1763
1764 #[test]
1765 fn buffer_is_winding_independent() {
1766 let ccw: Polygon<P> =
1771 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
1772 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)]];
1773 let j = JoinStrategy::Round {
1774 points_per_circle: 720,
1775 };
1776 let expected = 4.0 + 8.0 + core::f64::consts::PI;
1777 let grown_from_counterclockwise =
1778 ring_area(buffer_convex_polygon(&ccw, 1.0, j).exterior()).abs();
1779 let grown_from_clockwise = ring_area(buffer_convex_polygon(&cw, 1.0, j).exterior()).abs();
1780 close(grown_from_counterclockwise, expected, 5e-2);
1781 close(grown_from_clockwise, expected, 5e-2);
1782 }
1783
1784 #[test]
1785 fn miter_square_area_is_16() {
1786 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)]];
1792 let grown = buffer_convex_polygon(&sq, 1.0, JoinStrategy::Miter);
1793 close(ring_area(grown.exterior()).abs(), 16.0, 1e-9);
1794 }
1795
1796 #[test]
1797 fn miter_contains_near_corner_probe() {
1798 use geometry_algorithm::within;
1801 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)]];
1802 let grown = buffer_convex_polygon(&sq, 1.0, JoinStrategy::Miter);
1803 let ang = 22.5_f64.to_radians();
1804 let probe = P::new(2.0 + 0.99 * ang.cos(), 2.0 + 0.99 * ang.sin());
1805 assert!(
1806 within(&probe, &grown),
1807 "buffer must contain points within d"
1808 );
1809 }
1810
1811 #[test]
1812 fn miter_is_superset_of_round_by_area() {
1813 let j_round = JoinStrategy::Round {
1816 points_per_circle: 720,
1817 };
1818 let square: Polygon<P> =
1819 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
1820 let triangle: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (0.0, 3.0), (0.0, 0.0)]];
1821 for pg in [square, triangle] {
1822 let m =
1823 ring_area(buffer_convex_polygon(&pg, 1.0, JoinStrategy::Miter).exterior()).abs();
1824 let r = ring_area(buffer_convex_polygon(&pg, 1.0, j_round).exterior()).abs();
1825 assert!(m >= r - 1e-9, "miter {m} must not be below round {r}");
1826 }
1827 }
1828
1829 #[test]
1830 fn non_model_polygon_buffers_like_the_model_polygon() {
1831 use geometry_model::Ring;
1835 use geometry_tag::PolygonTag;
1836 use geometry_trait::{Geometry, Polygon as PolygonTrait};
1837
1838 struct Parcel {
1839 outer: Ring<P>,
1840 }
1841 impl Geometry for Parcel {
1842 type Kind = PolygonTag;
1843 type Point = P;
1844 }
1845 impl PolygonTrait for Parcel {
1846 type Ring = Ring<P>;
1847 fn exterior(&self) -> &Ring<P> {
1848 &self.outer
1849 }
1850 fn interiors(&self) -> impl ExactSizeIterator<Item = &Ring<P>> {
1851 core::iter::empty()
1852 }
1853 }
1854
1855 let pts = vec![
1856 P::new(0.0, 0.0),
1857 P::new(2.0, 0.0),
1858 P::new(2.0, 2.0),
1859 P::new(0.0, 2.0),
1860 P::new(0.0, 0.0),
1861 ];
1862 let parcel = Parcel {
1863 outer: Ring::from_vec(pts.clone()),
1864 };
1865 let model: Polygon<P> = Polygon::new(Ring::from_vec(pts));
1866 let j = JoinStrategy::Round {
1867 points_per_circle: 360,
1868 };
1869 let parcel_buffer = buffer(&parcel, 1.0, j, PointStrategy::Square).unwrap();
1870 let model_buffer = buffer(&model, 1.0, j, PointStrategy::Square).unwrap();
1871 let a = ring_area(parcel_buffer.polygons().next().unwrap().exterior()).abs();
1872 let b = ring_area(model_buffer.polygons().next().unwrap().exterior()).abs();
1873 close(a, b, 1e-12);
1874 }
1875
1876 #[test]
1877 fn miter_is_winding_independent() {
1878 let ccw: Polygon<P> =
1880 polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
1881 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)]];
1882 close(
1883 ring_area(buffer_convex_polygon(&ccw, 1.0, JoinStrategy::Miter).exterior()).abs(),
1884 16.0,
1885 1e-9,
1886 );
1887 close(
1888 ring_area(buffer_convex_polygon(&cw, 1.0, JoinStrategy::Miter).exterior()).abs(),
1889 16.0,
1890 1e-9,
1891 );
1892 }
1893}