Skip to main content

geometry_overlay/
relate.rs

1//! OVL6.T1 / OVL6.T2 — the DE-9IM relate matrix and the spatial
2//! predicates derived from it.
3//!
4//! Mirrors `boost/geometry/algorithms/relate.hpp`,
5//! `algorithms/relation.hpp`, and `detail/relate/`. The DE-9IM matrix
6//! records, for each pair drawn from {Interior, Boundary, Exterior} of
7//! two geometries, the **dimension** of their intersection. The
8//! `crosses` / `overlaps` / `touches` predicates
9//! (`algorithms/{crosses,overlaps,touches}.hpp`) are then thin tests on
10//! that matrix.
11//!
12//! Cartesian points, segments, linestrings, rings, boxes, polygons,
13//! homogeneous multis, runtime geometries, and heterogeneous geometry
14//! collections are dispatched through the same matrix interface. Union
15//! topology follows OGC boundary rules, including the mod-2 boundary of a
16//! multilinestring and absorption of members covered by areal interiors. The
17//! public mask-string interface consumes the completed matrix.
18
19#![allow(
20    clippy::float_cmp,
21    reason = "exact equality identifies stored endpoint identity for DE-9IM boundary classification"
22)]
23
24use alloc::vec::Vec;
25
26use geometry_coords::{CoordinateScalar, precise_math};
27use geometry_cs::{Cartesian, CartesianFamily, CoordinateSystem};
28use geometry_model::{DynGeometry, Point2D, Polygon, Ring};
29use geometry_tag::{
30    BoxTag, DynamicGeometryTag, GeometryCollectionTag, LinestringTag, MultiLinestringTag,
31    MultiPointTag, MultiPolygonTag, PointTag, PolygonTag, RingTag, SameAs, SegmentTag,
32};
33use geometry_trait::{
34    Box as BoxTrait, Geometry, GeometryCollection, Linestring as LinestringTrait, MultiLinestring,
35    MultiPoint, MultiPolygon, Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait,
36    Segment as SegmentTrait, corner,
37};
38
39use crate::operation::OverlayError;
40use crate::predicate::range_guard::{SAFE_ABS_MAX, polygon_in_range};
41
42/// The dimension of an intersection cell in a [`De9im`] matrix.
43///
44/// Mirrors the per-cell value of Boost's relate matrix: `F` (empty) or
45/// a dimension digit `0` / `1` / `2` (`detail/relate/result.hpp`).
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Dimension {
48    /// Empty intersection — Boost's `F`.
49    Empty,
50    /// Point (0-dimensional) intersection — Boost's `0`.
51    Point,
52    /// Curve (1-dimensional) intersection — Boost's `1`.
53    Curve,
54    /// Area (2-dimensional) intersection — Boost's `2`.
55    Area,
56}
57
58impl Dimension {
59    /// Whether the cell is non-empty (Boost's `T` — "true, any
60    /// dimension").
61    #[must_use]
62    pub fn is_set(self) -> bool {
63        self != Dimension::Empty
64    }
65}
66
67/// A DE-9IM 3×3 intersection matrix between two geometries.
68///
69/// Rows are the first geometry's {Interior, Boundary, Exterior}, columns
70/// the second's. `m[r][c]` is the dimension of the intersection of the
71/// first's feature `r` with the second's feature `c`. Mirrors Boost's
72/// `relate::matrix` (`detail/relate/result.hpp`).
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct De9im {
75    /// `[row][col]` over `[Interior, Boundary, Exterior]`.
76    pub m: [[Dimension; 3]; 3],
77}
78
79/// Row / column indices into a [`De9im`] matrix.
80pub mod feature {
81    /// Interior row/column index.
82    pub const INTERIOR: usize = 0;
83    /// Boundary row/column index.
84    pub const BOUNDARY: usize = 1;
85    /// Exterior row/column index.
86    pub const EXTERIOR: usize = 2;
87}
88
89impl De9im {
90    /// Swap the two related geometries by transposing the matrix.
91    #[must_use]
92    pub fn transposed(self) -> Self {
93        let mut result = [[Dimension::Empty; 3]; 3];
94        for (row, cells) in self.m.iter().enumerate() {
95            for (column, dimension) in cells.iter().enumerate() {
96                result[column][row] = *dimension;
97            }
98        }
99        Self { m: result }
100    }
101
102    /// The `[Interior][Interior]` cell — do the two interiors meet, and
103    /// in what dimension.
104    #[must_use]
105    pub fn interior_interior(&self) -> Dimension {
106        self.m[feature::INTERIOR][feature::INTERIOR]
107    }
108
109    /// The `[Boundary][Boundary]` cell.
110    #[must_use]
111    pub fn boundary_boundary(&self) -> Dimension {
112        self.m[feature::BOUNDARY][feature::BOUNDARY]
113    }
114
115    /// The `[Interior][Exterior]` cell — is part of the first's interior
116    /// outside the second.
117    #[must_use]
118    pub fn interior_exterior(&self) -> Dimension {
119        self.m[feature::INTERIOR][feature::EXTERIOR]
120    }
121
122    /// The `[Exterior][Interior]` cell.
123    #[must_use]
124    pub fn exterior_interior(&self) -> Dimension {
125        self.m[feature::EXTERIOR][feature::INTERIOR]
126    }
127
128    /// Test this matrix against a nine-character DE-9IM mask.
129    ///
130    /// Mirrors Boost's `de9im::mask` matching from
131    /// `algorithms/detail/relate/result.hpp:425-505`: `*` accepts any cell, `T`
132    /// accepts any non-empty cell, `F` accepts an empty cell, and `0`/`1`/`2`
133    /// require an exact point/curve/area dimension.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`RelateError::InvalidMask`] unless `mask` contains exactly
138    /// nine valid ASCII mask characters.
139    pub fn matches(&self, mask: &str) -> Result<bool, RelateError> {
140        let bytes = mask.as_bytes();
141        if bytes.len() != 9 {
142            return Err(RelateError::InvalidMask);
143        }
144
145        let mut result = true;
146        for (dimension, expected) in self.m.iter().flatten().zip(bytes) {
147            result &= match expected {
148                b'*' => true,
149                b'T' => dimension.is_set(),
150                b'F' => *dimension == Dimension::Empty,
151                b'0' => *dimension == Dimension::Point,
152                b'1' => *dimension == Dimension::Curve,
153                b'2' => *dimension == Dimension::Area,
154                _ => return Err(RelateError::InvalidMask),
155            };
156        }
157        Ok(result)
158    }
159}
160
161/// Failure while evaluating a DE-9IM relate mask.
162///
163/// Rust error adaptation for `boost::geometry::relate(g1, g2, mask)` from
164/// `algorithms/detail/relate/interface.hpp:347-382`; Boost reports a boolean,
165/// while this port preserves unsupported-kernel and malformed-mask failures.
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum RelateError {
168    /// The relation matrix could not be computed for the supplied geometry
169    /// pair.
170    Overlay(OverlayError),
171    /// The mask was not exactly nine characters drawn from `*TF012`.
172    InvalidMask,
173}
174
175/// Converts the overlay failure into the Rust relate-error adaptation for
176/// `algorithms/detail/relate/interface.hpp:347-382`.
177impl From<OverlayError> for RelateError {
178    fn from(error: OverlayError) -> Self {
179        Self::Overlay(error)
180    }
181}
182
183/// Per-pair DE-9IM relation strategy.
184///
185/// Mirrors Boost's tag-dispatched implementations under
186/// `algorithms/detail/relate/`.
187#[doc(hidden)]
188pub trait RelateStrategy<A, B> {
189    /// Compute the relation matrix for the ordered pair.
190    fn relate(&self, first: &A, second: &B) -> Result<De9im, OverlayError>;
191}
192
193/// Select a relation strategy from an ordered geometry-tag pair.
194#[doc(hidden)]
195pub trait RelatePairStrategy<Other> {
196    /// Strategy implementing this ordered pair.
197    type Strategy: Default;
198}
199
200#[doc(hidden)]
201#[derive(Debug, Default, Clone, Copy)]
202pub struct RelatePointPoint;
203#[doc(hidden)]
204#[derive(Debug, Default, Clone, Copy)]
205pub struct RelatePointLinestring;
206#[doc(hidden)]
207#[derive(Debug, Default, Clone, Copy)]
208pub struct RelatePointPolygon;
209#[doc(hidden)]
210#[derive(Debug, Default, Clone, Copy)]
211pub struct RelateLinestringPoint;
212#[doc(hidden)]
213#[derive(Debug, Default, Clone, Copy)]
214pub struct RelateLinestringLinestring;
215#[doc(hidden)]
216#[derive(Debug, Default, Clone, Copy)]
217pub struct RelateLinestringPolygon;
218#[doc(hidden)]
219#[derive(Debug, Default, Clone, Copy)]
220pub struct RelatePolygonPoint;
221#[doc(hidden)]
222#[derive(Debug, Default, Clone, Copy)]
223pub struct RelatePolygonLinestring;
224#[doc(hidden)]
225#[derive(Debug, Default, Clone, Copy)]
226pub struct RelatePolygonPolygon;
227#[doc(hidden)]
228#[derive(Debug, Default, Clone, Copy)]
229pub struct RelateTopology;
230
231impl RelatePairStrategy<PointTag> for PointTag {
232    type Strategy = RelatePointPoint;
233}
234impl RelatePairStrategy<LinestringTag> for PointTag {
235    type Strategy = RelatePointLinestring;
236}
237impl RelatePairStrategy<PolygonTag> for PointTag {
238    type Strategy = RelatePointPolygon;
239}
240impl RelatePairStrategy<PointTag> for LinestringTag {
241    type Strategy = RelateLinestringPoint;
242}
243impl RelatePairStrategy<LinestringTag> for LinestringTag {
244    type Strategy = RelateLinestringLinestring;
245}
246impl RelatePairStrategy<PolygonTag> for LinestringTag {
247    type Strategy = RelateLinestringPolygon;
248}
249impl RelatePairStrategy<PointTag> for PolygonTag {
250    type Strategy = RelatePolygonPoint;
251}
252impl RelatePairStrategy<LinestringTag> for PolygonTag {
253    type Strategy = RelatePolygonLinestring;
254}
255impl RelatePairStrategy<PolygonTag> for PolygonTag {
256    type Strategy = RelatePolygonPolygon;
257}
258
259trait TopologyKind {}
260
261impl TopologyKind for PointTag {}
262impl TopologyKind for LinestringTag {}
263impl TopologyKind for PolygonTag {}
264impl TopologyKind for SegmentTag {}
265impl TopologyKind for RingTag {}
266impl TopologyKind for BoxTag {}
267impl TopologyKind for MultiPointTag {}
268impl TopologyKind for MultiLinestringTag {}
269impl TopologyKind for MultiPolygonTag {}
270impl TopologyKind for DynamicGeometryTag {}
271impl TopologyKind for GeometryCollectionTag {}
272
273macro_rules! topology_pair_for_single {
274    ($single:ty, $($other:ty),+ $(,)?) => {
275        $(
276            impl RelatePairStrategy<$other> for $single {
277                type Strategy = RelateTopology;
278            }
279        )+
280    };
281}
282
283topology_pair_for_single!(
284    PointTag,
285    SegmentTag,
286    RingTag,
287    BoxTag,
288    MultiPointTag,
289    MultiLinestringTag,
290    MultiPolygonTag,
291    DynamicGeometryTag,
292    GeometryCollectionTag,
293);
294topology_pair_for_single!(
295    LinestringTag,
296    SegmentTag,
297    RingTag,
298    BoxTag,
299    MultiPointTag,
300    MultiLinestringTag,
301    MultiPolygonTag,
302    DynamicGeometryTag,
303    GeometryCollectionTag,
304);
305topology_pair_for_single!(
306    PolygonTag,
307    SegmentTag,
308    RingTag,
309    BoxTag,
310    MultiPointTag,
311    MultiLinestringTag,
312    MultiPolygonTag,
313    DynamicGeometryTag,
314    GeometryCollectionTag,
315);
316
317impl<Other: TopologyKind> RelatePairStrategy<Other> for SegmentTag {
318    type Strategy = RelateTopology;
319}
320impl<Other: TopologyKind> RelatePairStrategy<Other> for RingTag {
321    type Strategy = RelateTopology;
322}
323impl<Other: TopologyKind> RelatePairStrategy<Other> for BoxTag {
324    type Strategy = RelateTopology;
325}
326impl<Other: TopologyKind> RelatePairStrategy<Other> for MultiPointTag {
327    type Strategy = RelateTopology;
328}
329impl<Other: TopologyKind> RelatePairStrategy<Other> for MultiLinestringTag {
330    type Strategy = RelateTopology;
331}
332impl<Other: TopologyKind> RelatePairStrategy<Other> for MultiPolygonTag {
333    type Strategy = RelateTopology;
334}
335impl<Other: TopologyKind> RelatePairStrategy<Other> for DynamicGeometryTag {
336    type Strategy = RelateTopology;
337}
338impl<Other: TopologyKind> RelatePairStrategy<Other> for GeometryCollectionTag {
339    type Strategy = RelateTopology;
340}
341
342type PairStrategy<A, B> =
343    <<A as Geometry>::Kind as RelatePairStrategy<<B as Geometry>::Kind>>::Strategy;
344
345/// Compute the DE-9IM matrix for a supported pointlike, linear, or areal pair.
346///
347/// Mirrors the pair dispatch in
348/// `algorithms/detail/relate/interface.hpp:275-382`, including the
349/// geometry-collection path in `detail/relate/implementation_gc.hpp`.
350///
351/// # Errors
352///
353/// Returns [`OverlayError::Unsupported`] when coordinates exceed the
354/// Cartesian predicate range.
355#[inline]
356#[must_use = "relation computation can fail and the matrix should be used"]
357pub fn relate<A, B>(first: &A, second: &B) -> Result<De9im, OverlayError>
358where
359    A: Geometry,
360    B: Geometry,
361    A::Kind: RelatePairStrategy<B::Kind>,
362    PairStrategy<A, B>: RelateStrategy<A, B> + Default,
363{
364    PairStrategy::<A, B>::default().relate(first, second)
365}
366
367impl<A, B> RelateStrategy<A, B> for RelatePointPoint
368where
369    A: Point,
370    B: Point<Scalar = A::Scalar>,
371    <A::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
372    <B::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
373{
374    fn relate(&self, first: &A, second: &B) -> Result<De9im, OverlayError> {
375        Ok(relate_point_point(first, second))
376    }
377}
378
379impl<P, L> RelateStrategy<P, L> for RelatePointLinestring
380where
381    P: Point,
382    L: LinestringTrait<Point = P>,
383    P::Scalar: Into<f64>,
384    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
385{
386    fn relate(&self, point: &P, line: &L) -> Result<De9im, OverlayError> {
387        Ok(relate_point_linestring(point, line))
388    }
389}
390
391impl<P, G> RelateStrategy<P, G> for RelatePointPolygon
392where
393    P: Point + Copy,
394    G: PolygonTrait<Point = P>,
395    P::Scalar: Into<f64>,
396    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
397{
398    fn relate(&self, point: &P, polygon: &G) -> Result<De9im, OverlayError> {
399        Ok(relate_point_polygon(point, polygon))
400    }
401}
402
403impl<L, P> RelateStrategy<L, P> for RelateLinestringPoint
404where
405    P: Point,
406    L: LinestringTrait<Point = P>,
407    P::Scalar: Into<f64>,
408    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
409{
410    fn relate(&self, line: &L, point: &P) -> Result<De9im, OverlayError> {
411        Ok(relate_point_linestring(point, line).transposed())
412    }
413}
414
415impl<A, B, P> RelateStrategy<A, B> for RelateLinestringLinestring
416where
417    A: LinestringTrait<Point = P>,
418    B: LinestringTrait<Point = P>,
419    P: Point,
420    P::Scalar: Into<f64>,
421    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
422{
423    fn relate(&self, first: &A, second: &B) -> Result<De9im, OverlayError> {
424        Ok(relate_linestring_linestring(first, second))
425    }
426}
427
428impl<L, G, P> RelateStrategy<L, G> for RelateLinestringPolygon
429where
430    L: LinestringTrait<Point = P>,
431    G: PolygonTrait<Point = P>,
432    P: Point + Copy,
433    P::Scalar: Into<f64>,
434    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
435{
436    fn relate(&self, line: &L, polygon: &G) -> Result<De9im, OverlayError> {
437        Ok(relate_linestring_polygon(line, polygon))
438    }
439}
440
441impl<G, P> RelateStrategy<G, P> for RelatePolygonPoint
442where
443    G: PolygonTrait<Point = P>,
444    P: Point + Copy,
445    P::Scalar: Into<f64>,
446    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
447{
448    fn relate(&self, polygon: &G, point: &P) -> Result<De9im, OverlayError> {
449        Ok(relate_point_polygon(point, polygon).transposed())
450    }
451}
452
453impl<G, L, P> RelateStrategy<G, L> for RelatePolygonLinestring
454where
455    G: PolygonTrait<Point = P>,
456    L: LinestringTrait<Point = P>,
457    P: Point + Copy,
458    P::Scalar: Into<f64>,
459    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
460{
461    fn relate(&self, polygon: &G, line: &L) -> Result<De9im, OverlayError> {
462        Ok(relate_linestring_polygon(line, polygon).transposed())
463    }
464}
465
466impl<A, B, P> RelateStrategy<A, B> for RelatePolygonPolygon
467where
468    A: PolygonTrait<Point = P>,
469    B: PolygonTrait<Point = P>,
470    P: PointMut + Default + Copy,
471    P::Scalar: CoordinateScalar + Into<f64>,
472    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
473{
474    fn relate(&self, first: &A, second: &B) -> Result<De9im, OverlayError> {
475        relate_polygon_polygon(first, second)
476    }
477}
478
479type TopologyPointModel = Point2D<f64, Cartesian>;
480
481#[derive(Debug, Default, Clone)]
482struct Topology {
483    points: Vec<[f64; 2]>,
484    lines: Vec<Vec<[f64; 2]>>,
485    polygons: Vec<Polygon<TopologyPointModel>>,
486}
487
488impl Topology {
489    fn in_range(&self) -> bool {
490        let in_range = |point: [f64; 2]| {
491            point[0].is_finite()
492                && point[1].is_finite()
493                && point[0].abs() <= SAFE_ABS_MAX
494                && point[1].abs() <= SAFE_ABS_MAX
495        };
496        if !self
497            .points
498            .iter()
499            .chain(self.lines.iter().flatten())
500            .copied()
501            .all(in_range)
502        {
503            return false;
504        }
505        self.polygons.iter().all(|polygon| {
506            polygon
507                .outer
508                .0
509                .iter()
510                .chain(polygon.inners.iter().flat_map(|ring| ring.0.iter()))
511                .all(|point| in_range([point.x(), point.y()]))
512        })
513    }
514}
515
516trait TopologyBuilder<G> {
517    fn append(&self, geometry: &G, topology: &mut Topology);
518}
519
520trait TopologyBuilderForKind {
521    type Strategy: Default;
522}
523
524#[derive(Debug, Default, Clone, Copy)]
525struct TopologyPoint;
526#[derive(Debug, Default, Clone, Copy)]
527struct TopologyLinestring;
528#[derive(Debug, Default, Clone, Copy)]
529struct TopologyPolygon;
530#[derive(Debug, Default, Clone, Copy)]
531struct TopologySegment;
532#[derive(Debug, Default, Clone, Copy)]
533struct TopologyRing;
534#[derive(Debug, Default, Clone, Copy)]
535struct TopologyBox;
536#[derive(Debug, Default, Clone, Copy)]
537struct TopologyMultiPoint;
538#[derive(Debug, Default, Clone, Copy)]
539struct TopologyMultiLinestring;
540#[derive(Debug, Default, Clone, Copy)]
541struct TopologyMultiPolygon;
542#[derive(Debug, Default, Clone, Copy)]
543struct TopologyDynamic;
544#[derive(Debug, Default, Clone, Copy)]
545struct TopologyCollection;
546
547impl TopologyBuilderForKind for PointTag {
548    type Strategy = TopologyPoint;
549}
550impl TopologyBuilderForKind for LinestringTag {
551    type Strategy = TopologyLinestring;
552}
553impl TopologyBuilderForKind for PolygonTag {
554    type Strategy = TopologyPolygon;
555}
556impl TopologyBuilderForKind for SegmentTag {
557    type Strategy = TopologySegment;
558}
559impl TopologyBuilderForKind for RingTag {
560    type Strategy = TopologyRing;
561}
562impl TopologyBuilderForKind for BoxTag {
563    type Strategy = TopologyBox;
564}
565impl TopologyBuilderForKind for MultiPointTag {
566    type Strategy = TopologyMultiPoint;
567}
568impl TopologyBuilderForKind for MultiLinestringTag {
569    type Strategy = TopologyMultiLinestring;
570}
571impl TopologyBuilderForKind for MultiPolygonTag {
572    type Strategy = TopologyMultiPolygon;
573}
574impl TopologyBuilderForKind for DynamicGeometryTag {
575    type Strategy = TopologyDynamic;
576}
577impl TopologyBuilderForKind for GeometryCollectionTag {
578    type Strategy = TopologyCollection;
579}
580
581type TopologyBuilderStrategy<G> = <<G as Geometry>::Kind as TopologyBuilderForKind>::Strategy;
582
583impl<G> TopologyBuilder<G> for TopologyPoint
584where
585    G: Point,
586    G::Scalar: Into<f64>,
587    <G::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
588{
589    fn append(&self, geometry: &G, topology: &mut Topology) {
590        topology.points.push(xy(geometry));
591    }
592}
593
594impl<G> TopologyBuilder<G> for TopologyLinestring
595where
596    G: LinestringTrait,
597    <G::Point as Point>::Scalar: Into<f64>,
598    <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
599{
600    fn append(&self, geometry: &G, topology: &mut Topology) {
601        append_topology_line(geometry.points().map(xy).collect(), topology);
602    }
603}
604
605impl<G> TopologyBuilder<G> for TopologyPolygon
606where
607    G: PolygonTrait,
608    <G::Point as Point>::Scalar: Into<f64>,
609    <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
610{
611    fn append(&self, geometry: &G, topology: &mut Topology) {
612        append_topology_polygon(geometry, topology);
613    }
614}
615
616impl<G> TopologyBuilder<G> for TopologySegment
617where
618    G: SegmentTrait,
619    <G::Point as Point>::Scalar: Into<f64>,
620    <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
621{
622    fn append(&self, geometry: &G, topology: &mut Topology) {
623        append_topology_line(
624            alloc::vec![
625                [
626                    geometry.get_indexed::<0, 0>().into(),
627                    geometry.get_indexed::<0, 1>().into(),
628                ],
629                [
630                    geometry.get_indexed::<1, 0>().into(),
631                    geometry.get_indexed::<1, 1>().into(),
632                ],
633            ],
634            topology,
635        );
636    }
637}
638
639impl<G> TopologyBuilder<G> for TopologyRing
640where
641    G: RingTrait,
642    <G::Point as Point>::Scalar: Into<f64>,
643    <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
644{
645    fn append(&self, geometry: &G, topology: &mut Topology) {
646        topology
647            .polygons
648            .push(Polygon::new(topology_ring(geometry)));
649    }
650}
651
652impl<G> TopologyBuilder<G> for TopologyBox
653where
654    G: BoxTrait,
655    <G::Point as Point>::Scalar: Into<f64>,
656    <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
657{
658    fn append(&self, geometry: &G, topology: &mut Topology) {
659        let minimum = [
660            geometry.get_indexed::<{ corner::MIN }, 0>().into(),
661            geometry.get_indexed::<{ corner::MIN }, 1>().into(),
662        ];
663        let maximum = [
664            geometry.get_indexed::<{ corner::MAX }, 0>().into(),
665            geometry.get_indexed::<{ corner::MAX }, 1>().into(),
666        ];
667        topology
668            .polygons
669            .push(Polygon::new(Ring::from_vec(alloc::vec![
670                topology_point(minimum),
671                topology_point([minimum[0], maximum[1]]),
672                topology_point(maximum),
673                topology_point([maximum[0], minimum[1]]),
674                topology_point(minimum),
675            ])));
676    }
677}
678
679impl<G> TopologyBuilder<G> for TopologyMultiPoint
680where
681    G: MultiPoint,
682    <G::ItemPoint as Point>::Scalar: Into<f64>,
683    <<G::ItemPoint as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
684{
685    fn append(&self, geometry: &G, topology: &mut Topology) {
686        topology.points.extend(geometry.points().map(xy));
687    }
688}
689
690impl<G> TopologyBuilder<G> for TopologyMultiLinestring
691where
692    G: MultiLinestring,
693    <G::Point as Point>::Scalar: Into<f64>,
694    <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
695{
696    fn append(&self, geometry: &G, topology: &mut Topology) {
697        for line in geometry.linestrings() {
698            append_topology_line(line.points().map(xy).collect(), topology);
699        }
700    }
701}
702
703impl<G> TopologyBuilder<G> for TopologyMultiPolygon
704where
705    G: MultiPolygon,
706    <G::Point as Point>::Scalar: Into<f64>,
707    <<G::Point as Point>::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
708{
709    fn append(&self, geometry: &G, topology: &mut Topology) {
710        for polygon in geometry.polygons() {
711            append_topology_polygon(polygon, topology);
712        }
713    }
714}
715
716impl<Scalar, Cs> TopologyBuilder<DynGeometry<Scalar, Cs>> for TopologyDynamic
717where
718    Scalar: CoordinateScalar + Into<f64>,
719    Cs: CoordinateSystem,
720    Cs::Family: SameAs<CartesianFamily>,
721{
722    fn append(&self, geometry: &DynGeometry<Scalar, Cs>, topology: &mut Topology) {
723        append_dynamic_topology(geometry, topology);
724    }
725}
726
727impl<G> TopologyBuilder<G> for TopologyCollection
728where
729    G: GeometryCollection,
730    G::Item: Geometry,
731    <G::Item as Geometry>::Kind: TopologyBuilderForKind,
732    TopologyBuilderStrategy<G::Item>: TopologyBuilder<G::Item> + Default,
733{
734    fn append(&self, geometry: &G, topology: &mut Topology) {
735        for item in geometry.items() {
736            TopologyBuilderStrategy::<G::Item>::default().append(item, topology);
737        }
738    }
739}
740
741impl<A, B> RelateStrategy<A, B> for RelateTopology
742where
743    A: Geometry,
744    B: Geometry,
745    A::Kind: TopologyBuilderForKind,
746    B::Kind: TopologyBuilderForKind,
747    TopologyBuilderStrategy<A>: TopologyBuilder<A> + Default,
748    TopologyBuilderStrategy<B>: TopologyBuilder<B> + Default,
749{
750    fn relate(&self, first: &A, second: &B) -> Result<De9im, OverlayError> {
751        let mut first_topology = Topology::default();
752        TopologyBuilderStrategy::<A>::default().append(first, &mut first_topology);
753        let mut second_topology = Topology::default();
754        TopologyBuilderStrategy::<B>::default().append(second, &mut second_topology);
755        relate_topologies(&first_topology, &second_topology)
756    }
757}
758
759#[derive(Debug, Clone, Copy, PartialEq, Eq)]
760enum Location {
761    Interior,
762    Boundary,
763    Exterior,
764}
765
766impl Location {
767    fn index(self) -> usize {
768        match self {
769            Self::Interior => feature::INTERIOR,
770            Self::Boundary => feature::BOUNDARY,
771            Self::Exterior => feature::EXTERIOR,
772        }
773    }
774}
775
776fn empty_matrix() -> De9im {
777    let mut matrix = De9im {
778        m: [[Dimension::Empty; 3]; 3],
779    };
780    matrix.m[feature::EXTERIOR][feature::EXTERIOR] = Dimension::Area;
781    matrix
782}
783
784fn relate_point_point<A, B>(first: &A, second: &B) -> De9im
785where
786    A: Point,
787    B: Point<Scalar = A::Scalar>,
788{
789    let mut matrix = empty_matrix();
790    if point_equal(first, second) {
791        matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Point;
792    } else {
793        matrix.m[feature::INTERIOR][feature::EXTERIOR] = Dimension::Point;
794        matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Point;
795    }
796    matrix
797}
798
799fn relate_point_linestring<P, L>(point: &P, line: &L) -> De9im
800where
801    P: Point,
802    L: LinestringTrait<Point = P>,
803    P::Scalar: Into<f64>,
804{
805    let mut matrix = empty_matrix();
806    let location = point_location_linestring(point, line);
807    matrix.m[feature::INTERIOR][location.index()] = Dimension::Point;
808    if line_has_curve(line) {
809        matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Curve;
810    }
811    let boundaries = line_boundary_points(line);
812    if boundaries
813        .iter()
814        .any(|boundary| !point_equal(point, *boundary))
815    {
816        matrix.m[feature::EXTERIOR][feature::BOUNDARY] = Dimension::Point;
817    }
818    matrix
819}
820
821fn relate_point_polygon<P, G>(point: &P, polygon: &G) -> De9im
822where
823    P: Point + Copy,
824    G: PolygonTrait<Point = P>,
825    P::Scalar: Into<f64>,
826{
827    let mut matrix = empty_matrix();
828    let location = point_location_polygon(point, polygon);
829    matrix.m[feature::INTERIOR][location.index()] = Dimension::Point;
830    matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Area;
831    matrix.m[feature::EXTERIOR][feature::BOUNDARY] = Dimension::Curve;
832    matrix
833}
834
835fn relate_linestring_linestring<A, B, P>(first: &A, second: &B) -> De9im
836where
837    A: LinestringTrait<Point = P>,
838    B: LinestringTrait<Point = P>,
839    P: Point,
840    P::Scalar: Into<f64>,
841{
842    let mut matrix = empty_matrix();
843    let mut first_segments = Vec::new();
844    for_each_line_segment(first, |start, end| {
845        first_segments.push((xy(start), xy(end)));
846    });
847    let mut second_segments = Vec::new();
848    for_each_line_segment(second, |start, end| {
849        second_segments.push((xy(start), xy(end)));
850    });
851    for point in line_boundary_points(first) {
852        let location = point_location_linestring(point, second);
853        matrix.m[feature::BOUNDARY][location.index()] = Dimension::Point;
854    }
855    for point in line_boundary_points(second) {
856        let location = point_location_linestring(point, first);
857        matrix.m[location.index()][feature::BOUNDARY] = Dimension::Point;
858    }
859
860    for &first_segment in &first_segments {
861        for &second_segment in &second_segments {
862            match segment_relation(
863                first_segment.0,
864                first_segment.1,
865                second_segment.0,
866                second_segment.1,
867            ) {
868                SegmentRelation::Disjoint => {}
869                SegmentRelation::Point(point) => {
870                    let first_location = xy_location_linestring(point, first);
871                    let second_location = xy_location_linestring(point, second);
872                    matrix.m[first_location.index()][second_location.index()] = Dimension::Point;
873                }
874                SegmentRelation::Overlap => {
875                    matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Curve;
876                }
877            }
878        }
879        if !xy_equal(first_segment.0, first_segment.1) {
880            for interval in segment_parameters(first_segment, &second_segments, &[]).windows(2) {
881                debug_assert!(interval[1] - interval[0] > f64::EPSILON);
882                let sample = interpolate(
883                    first_segment.0,
884                    first_segment.1,
885                    f64::midpoint(interval[0], interval[1]),
886                );
887                let location = xy_location_linestring(sample, second);
888                matrix.m[feature::INTERIOR][location.index()] = Dimension::Curve;
889            }
890        }
891    }
892    for &second_segment in &second_segments {
893        if !xy_equal(second_segment.0, second_segment.1) {
894            for interval in segment_parameters(second_segment, &first_segments, &[]).windows(2) {
895                debug_assert!(interval[1] - interval[0] > f64::EPSILON);
896                let sample = interpolate(
897                    second_segment.0,
898                    second_segment.1,
899                    f64::midpoint(interval[0], interval[1]),
900                );
901                let location = xy_location_linestring(sample, first);
902                matrix.m[location.index()][feature::INTERIOR] = Dimension::Curve;
903            }
904        }
905    }
906    matrix
907}
908
909fn relate_linestring_polygon<L, G, P>(line: &L, polygon: &G) -> De9im
910where
911    L: LinestringTrait<Point = P>,
912    G: PolygonTrait<Point = P>,
913    P: Point + Copy,
914    P::Scalar: Into<f64>,
915{
916    let mut matrix = empty_matrix();
917    matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Area;
918    matrix.m[feature::EXTERIOR][feature::BOUNDARY] = Dimension::Curve;
919    for point in line_boundary_points(line) {
920        let location = point_location_polygon(point, polygon);
921        matrix.m[feature::BOUNDARY][location.index()] = Dimension::Point;
922    }
923
924    let mut boundary_crossings = 0usize;
925    for_each_line_segment(line, |line1, line2| {
926        for fraction in [0.125, 0.375, 0.625, 0.875] {
927            match xy_location_polygon(interpolate(xy(line1), xy(line2), fraction), polygon) {
928                Location::Interior => {
929                    matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Curve;
930                }
931                Location::Boundary => {
932                    matrix.m[feature::INTERIOR][feature::BOUNDARY] = Dimension::Point;
933                }
934                Location::Exterior => {
935                    matrix.m[feature::INTERIOR][feature::EXTERIOR] = Dimension::Curve;
936                }
937            }
938        }
939        for_each_polygon_segment(polygon, |polygon1, polygon2| {
940            if let SegmentRelation::Point(point) =
941                segment_relation(xy(line1), xy(line2), xy(polygon1), xy(polygon2))
942            {
943                boundary_crossings += 1;
944                let line_location = xy_location_linestring(point, line);
945                matrix.m[line_location.index()][feature::BOUNDARY] = Dimension::Point;
946            }
947        });
948    });
949    if boundary_crossings >= 2 {
950        matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Curve;
951    }
952    matrix
953}
954
955fn point_equal<A, B>(first: &A, second: &B) -> bool
956where
957    A: Point,
958    B: Point<Scalar = A::Scalar>,
959{
960    first.get::<0>() == second.get::<0>() && first.get::<1>() == second.get::<1>()
961}
962
963fn line_has_curve<L>(line: &L) -> bool
964where
965    L: LinestringTrait,
966    L::Point: Point,
967{
968    let mut points = line.points();
969    let Some(mut previous) = points.next() else {
970        return false;
971    };
972    for current in points {
973        if !point_equal(previous, current) {
974            return true;
975        }
976        previous = current;
977    }
978    false
979}
980
981fn line_boundary_points<L>(line: &L) -> alloc::vec::Vec<&L::Point>
982where
983    L: LinestringTrait,
984    L::Point: Point,
985{
986    let points = line.points();
987    let Some(first) = points.clone().next() else {
988        return alloc::vec::Vec::new();
989    };
990    let last = points
991        .last()
992        .expect("a non-empty iterator has a last point");
993    if point_equal(first, last) {
994        alloc::vec::Vec::new()
995    } else {
996        alloc::vec![first, last]
997    }
998}
999
1000fn point_location_linestring<P, L>(point: &P, line: &L) -> Location
1001where
1002    P: Point,
1003    L: LinestringTrait<Point = P>,
1004    P::Scalar: Into<f64>,
1005{
1006    xy_location_linestring(xy(point), line)
1007}
1008
1009fn xy_location_linestring<L>(point: [f64; 2], line: &L) -> Location
1010where
1011    L: LinestringTrait,
1012    L::Point: Point,
1013    <L::Point as Point>::Scalar: Into<f64>,
1014{
1015    for boundary in line_boundary_points(line) {
1016        if xy_equal(point, xy(boundary)) {
1017            return Location::Boundary;
1018        }
1019    }
1020    let mut interior = false;
1021    for_each_line_segment(line, |first, second| {
1022        if point_on_segment(point, xy(first), xy(second)) {
1023            interior = true;
1024        }
1025    });
1026    if interior {
1027        Location::Interior
1028    } else {
1029        Location::Exterior
1030    }
1031}
1032
1033fn point_location_polygon<P, G>(point: &P, polygon: &G) -> Location
1034where
1035    P: Point + Copy,
1036    G: PolygonTrait<Point = P>,
1037    P::Scalar: Into<f64>,
1038{
1039    xy_location_polygon(xy(point), polygon)
1040}
1041
1042fn xy_location_polygon<G>(point: [f64; 2], polygon: &G) -> Location
1043where
1044    G: PolygonTrait,
1045    G::Point: Point,
1046    <G::Point as Point>::Scalar: Into<f64>,
1047{
1048    let mut boundary = false;
1049    for_each_polygon_segment(polygon, |first, second| {
1050        if point_on_segment(point, xy(first), xy(second)) {
1051            boundary = true;
1052        }
1053    });
1054    if boundary {
1055        return Location::Boundary;
1056    }
1057    if point_in_ring_xy(point, polygon.exterior())
1058        && !polygon
1059            .interiors()
1060            .any(|ring| point_in_ring_xy(point, ring))
1061    {
1062        Location::Interior
1063    } else {
1064        Location::Exterior
1065    }
1066}
1067
1068fn point_in_ring_xy<R>(point: [f64; 2], ring: &R) -> bool
1069where
1070    R: RingTrait,
1071    R::Point: Point,
1072    <R::Point as Point>::Scalar: Into<f64>,
1073{
1074    let mut inside = false;
1075    for_each_ring_segment(ring, |first, second| {
1076        let first = xy(first);
1077        let second = xy(second);
1078        if (first[1] > point[1]) != (second[1] > point[1])
1079            && point[0]
1080                < (second[0] - first[0]) * (point[1] - first[1]) / (second[1] - first[1]) + first[0]
1081        {
1082            inside = !inside;
1083        }
1084    });
1085    inside
1086}
1087
1088fn for_each_line_segment<L>(line: &L, mut function: impl FnMut(&L::Point, &L::Point))
1089where
1090    L: LinestringTrait,
1091{
1092    let mut points = line.points();
1093    let Some(mut previous) = points.next() else {
1094        return;
1095    };
1096    for current in points {
1097        function(previous, current);
1098        previous = current;
1099    }
1100}
1101
1102fn for_each_ring_segment<R>(ring: &R, mut function: impl FnMut(&R::Point, &R::Point))
1103where
1104    R: RingTrait,
1105{
1106    let mut points = ring.points();
1107    let Some(first) = points.next() else {
1108        return;
1109    };
1110    let mut previous = first;
1111    for current in points {
1112        function(previous, current);
1113        previous = current;
1114    }
1115    if !point_equal(previous, first) {
1116        function(previous, first);
1117    }
1118}
1119
1120fn for_each_polygon_segment<G>(polygon: &G, mut function: impl FnMut(&G::Point, &G::Point))
1121where
1122    G: PolygonTrait,
1123{
1124    for_each_ring_segment(polygon.exterior(), &mut function);
1125    for ring in polygon.interiors() {
1126        for_each_ring_segment(ring, &mut function);
1127    }
1128}
1129
1130#[derive(Debug, Clone, Copy, PartialEq)]
1131enum SegmentRelation {
1132    Disjoint,
1133    Point([f64; 2]),
1134    Overlap,
1135}
1136
1137fn segment_relation(
1138    first1: [f64; 2],
1139    first2: [f64; 2],
1140    second1: [f64; 2],
1141    second2: [f64; 2],
1142) -> SegmentRelation {
1143    let d1 = precise_math::orient2d(second1, second2, first1);
1144    let d2 = precise_math::orient2d(second1, second2, first2);
1145    let d3 = precise_math::orient2d(first1, first2, second1);
1146    let d4 = precise_math::orient2d(first1, first2, second2);
1147    if opposite(d1, d2) && opposite(d3, d4) {
1148        return SegmentRelation::Point(line_cross(first1, first2, second1, second2));
1149    }
1150    if d1 == 0.0 && d2 == 0.0 && d3 == 0.0 && d4 == 0.0 {
1151        let overlap = collinear_overlap_length(first1, first2, second1, second2);
1152        return if overlap > 0.0 {
1153            SegmentRelation::Overlap
1154        } else if overlap == 0.0 {
1155            [first1, first2, second1, second2]
1156                .into_iter()
1157                .find(|point| {
1158                    point_on_segment(*point, first1, first2)
1159                        && point_on_segment(*point, second1, second2)
1160                })
1161                .map_or(SegmentRelation::Disjoint, SegmentRelation::Point)
1162        } else {
1163            SegmentRelation::Disjoint
1164        };
1165    }
1166    for (value, point, start, end) in [
1167        (d1, first1, second1, second2),
1168        (d2, first2, second1, second2),
1169        (d3, second1, first1, first2),
1170        (d4, second2, first1, first2),
1171    ] {
1172        if value == 0.0 && point_on_segment(point, start, end) {
1173            return SegmentRelation::Point(point);
1174        }
1175    }
1176    SegmentRelation::Disjoint
1177}
1178
1179fn xy<P>(point: &P) -> [f64; 2]
1180where
1181    P: Point,
1182    P::Scalar: Into<f64>,
1183{
1184    [point.get::<0>().into(), point.get::<1>().into()]
1185}
1186
1187fn xy_equal(first: [f64; 2], second: [f64; 2]) -> bool {
1188    first[0] == second[0] && first[1] == second[1]
1189}
1190
1191fn point_on_segment(point: [f64; 2], start: [f64; 2], end: [f64; 2]) -> bool {
1192    precise_math::orient2d(start, end, point) == 0.0
1193        && point[0] >= start[0].min(end[0])
1194        && point[0] <= start[0].max(end[0])
1195        && point[1] >= start[1].min(end[1])
1196        && point[1] <= start[1].max(end[1])
1197}
1198
1199fn opposite(first: f64, second: f64) -> bool {
1200    (first > 0.0 && second < 0.0) || (first < 0.0 && second > 0.0)
1201}
1202
1203fn line_cross(
1204    first1: [f64; 2],
1205    first2: [f64; 2],
1206    second1: [f64; 2],
1207    second2: [f64; 2],
1208) -> [f64; 2] {
1209    let denominator = (first1[0] - first2[0]) * (second1[1] - second2[1])
1210        - (first1[1] - first2[1]) * (second1[0] - second2[0]);
1211    let first_cross = first1[0] * first2[1] - first1[1] * first2[0];
1212    let second_cross = second1[0] * second2[1] - second1[1] * second2[0];
1213    [
1214        (first_cross * (second1[0] - second2[0]) - (first1[0] - first2[0]) * second_cross)
1215            / denominator,
1216        (first_cross * (second1[1] - second2[1]) - (first1[1] - first2[1]) * second_cross)
1217            / denominator,
1218    ]
1219}
1220
1221fn collinear_overlap_length(
1222    first1: [f64; 2],
1223    first2: [f64; 2],
1224    second1: [f64; 2],
1225    second2: [f64; 2],
1226) -> f64 {
1227    let use_x = (first1[0] - first2[0]).abs() >= (first1[1] - first2[1]).abs();
1228    let index = usize::from(!use_x);
1229    first1[index]
1230        .max(first2[index])
1231        .min(second1[index].max(second2[index]))
1232        - first1[index]
1233            .min(first2[index])
1234            .max(second1[index].min(second2[index]))
1235}
1236
1237fn interpolate(first: [f64; 2], second: [f64; 2], fraction: f64) -> [f64; 2] {
1238    [
1239        first[0] + (second[0] - first[0]) * fraction,
1240        first[1] + (second[1] - first[1]) * fraction,
1241    ]
1242}
1243
1244fn topology_point(coordinates: [f64; 2]) -> TopologyPointModel {
1245    TopologyPointModel::new(coordinates[0], coordinates[1])
1246}
1247
1248fn topology_ring<R>(ring: &R) -> Ring<TopologyPointModel>
1249where
1250    R: RingTrait,
1251    <R::Point as Point>::Scalar: Into<f64>,
1252{
1253    Ring::from_vec(
1254        ring.points()
1255            .map(|point| topology_point(xy(point)))
1256            .collect(),
1257    )
1258}
1259
1260fn append_topology_line(mut points: Vec<[f64; 2]>, topology: &mut Topology) {
1261    points.dedup_by(|first, second| xy_equal(*first, *second));
1262    if points.len() >= 2 {
1263        topology.lines.push(points);
1264    } else if let Some(point) = points.first() {
1265        topology.points.push(*point);
1266    }
1267}
1268
1269fn append_topology_polygon<G>(polygon: &G, topology: &mut Topology)
1270where
1271    G: PolygonTrait,
1272    <G::Point as Point>::Scalar: Into<f64>,
1273{
1274    let outer = topology_ring(polygon.exterior());
1275    if outer.0.len() < 3 {
1276        append_topology_line(outer.0.iter().map(xy).collect(), topology);
1277        return;
1278    }
1279    topology.polygons.push(Polygon::with_inners(
1280        outer,
1281        polygon.interiors().map(topology_ring).collect(),
1282    ));
1283}
1284
1285fn append_dynamic_topology<Scalar, Cs>(geometry: &DynGeometry<Scalar, Cs>, topology: &mut Topology)
1286where
1287    Scalar: CoordinateScalar + Into<f64>,
1288    Cs: CoordinateSystem,
1289    Cs::Family: SameAs<CartesianFamily>,
1290{
1291    match geometry {
1292        DynGeometry::Point(point) => topology.points.push(xy(point)),
1293        DynGeometry::LineString(line) => {
1294            append_topology_line(line.points().map(xy).collect(), topology);
1295        }
1296        DynGeometry::Polygon(polygon) => append_topology_polygon(polygon, topology),
1297        DynGeometry::MultiPoint(points) => topology.points.extend(points.points().map(xy)),
1298        DynGeometry::MultiLineString(lines) => {
1299            for line in lines.linestrings() {
1300                append_topology_line(line.points().map(xy).collect(), topology);
1301            }
1302        }
1303        DynGeometry::MultiPolygon(polygons) => {
1304            for polygon in polygons.polygons() {
1305                append_topology_polygon(polygon, topology);
1306            }
1307        }
1308        DynGeometry::GeometryCollection(items) => {
1309            for item in items {
1310                append_dynamic_topology(item, topology);
1311            }
1312        }
1313    }
1314}
1315
1316fn topology_segments(topology: &Topology) -> Vec<([f64; 2], [f64; 2])> {
1317    let mut segments = Vec::new();
1318    for line in &topology.lines {
1319        for points in line.windows(2) {
1320            debug_assert!(!xy_equal(points[0], points[1]));
1321            segments.push((points[0], points[1]));
1322        }
1323    }
1324    for polygon in &topology.polygons {
1325        append_boundary_segments(&polygon.outer, &mut segments);
1326        for ring in &polygon.inners {
1327            append_boundary_segments(ring, &mut segments);
1328        }
1329    }
1330    segments
1331}
1332
1333fn topology_location(topology: &Topology, point: [f64; 2]) -> Location {
1334    let mut polygon_boundary = false;
1335    for polygon in &topology.polygons {
1336        match xy_location_polygon(point, polygon) {
1337            Location::Interior => return Location::Interior,
1338            Location::Boundary => polygon_boundary = true,
1339            Location::Exterior => {}
1340        }
1341    }
1342
1343    let mut on_line = false;
1344    let mut endpoint_count = 0usize;
1345    for line in &topology.lines {
1346        for segment in line.windows(2) {
1347            if point_on_segment(point, segment[0], segment[1]) {
1348                on_line = true;
1349            }
1350        }
1351        let first = *line
1352            .first()
1353            .expect("topology lines have at least two points");
1354        let last = *line
1355            .last()
1356            .expect("topology lines have at least two points");
1357        if !xy_equal(first, last) {
1358            endpoint_count += usize::from(xy_equal(point, first));
1359            endpoint_count += usize::from(xy_equal(point, last));
1360        }
1361    }
1362    if on_line {
1363        return if endpoint_count % 2 == 1 && !polygon_boundary {
1364            Location::Boundary
1365        } else {
1366            Location::Interior
1367        };
1368    }
1369    if topology
1370        .points
1371        .iter()
1372        .any(|candidate| xy_equal(*candidate, point))
1373    {
1374        return Location::Interior;
1375    }
1376    if polygon_boundary {
1377        Location::Boundary
1378    } else {
1379        Location::Exterior
1380    }
1381}
1382
1383fn dimension_rank(dimension: Dimension) -> u8 {
1384    match dimension {
1385        Dimension::Empty => 0,
1386        Dimension::Point => 1,
1387        Dimension::Curve => 2,
1388        Dimension::Area => 3,
1389    }
1390}
1391
1392fn set_dimension(matrix: &mut De9im, row: Location, column: Location, dimension: Dimension) {
1393    let cell = &mut matrix.m[row.index()][column.index()];
1394    if dimension_rank(dimension) > dimension_rank(*cell) {
1395        *cell = dimension;
1396    }
1397}
1398
1399fn segment_parameter(point: [f64; 2], start: [f64; 2], end: [f64; 2]) -> f64 {
1400    let dx = end[0] - start[0];
1401    let dy = end[1] - start[1];
1402    if dx.abs() >= dy.abs() {
1403        debug_assert_ne!(dx, 0.0);
1404        (point[0] - start[0]) / dx
1405    } else {
1406        debug_assert_ne!(dy, 0.0);
1407        (point[1] - start[1]) / dy
1408    }
1409}
1410
1411fn segment_parameters(
1412    segment: ([f64; 2], [f64; 2]),
1413    all_segments: &[([f64; 2], [f64; 2])],
1414    split_points: &[[f64; 2]],
1415) -> Vec<f64> {
1416    let mut parameters = alloc::vec![0.0, 1.0];
1417    for &(start, end) in all_segments {
1418        match segment_relation(segment.0, segment.1, start, end) {
1419            SegmentRelation::Point(point) => {
1420                parameters.push(segment_parameter(point, segment.0, segment.1));
1421            }
1422            SegmentRelation::Overlap => {
1423                for point in [start, end] {
1424                    if point_on_segment(point, segment.0, segment.1) {
1425                        parameters.push(segment_parameter(point, segment.0, segment.1));
1426                    }
1427                }
1428            }
1429            SegmentRelation::Disjoint => {}
1430        }
1431    }
1432    for &point in split_points {
1433        if point_on_segment(point, segment.0, segment.1) {
1434            parameters.push(segment_parameter(point, segment.0, segment.1));
1435        }
1436    }
1437    parameters.retain(|parameter| (-f64::EPSILON..=1.0 + f64::EPSILON).contains(parameter));
1438    parameters.sort_by(f64::total_cmp);
1439    parameters.dedup_by(|first, second| (*first - *second).abs() <= f64::EPSILON);
1440    parameters
1441}
1442
1443fn record_segment_cells(
1444    matrix: &mut De9im,
1445    first: &Topology,
1446    second: &Topology,
1447    segment: ([f64; 2], [f64; 2]),
1448    all_segments: &[([f64; 2], [f64; 2])],
1449) {
1450    let parameters = segment_parameters(segment, all_segments, &second.points);
1451    for interval in parameters.windows(2) {
1452        debug_assert!(interval[1] - interval[0] > f64::EPSILON);
1453        let midpoint = interpolate(
1454            segment.0,
1455            segment.1,
1456            f64::midpoint(interval[0], interval[1]),
1457        );
1458        let first_location = topology_location(first, midpoint);
1459        let second_location = topology_location(second, midpoint);
1460        debug_assert_ne!(first_location, Location::Exterior);
1461        set_dimension(matrix, first_location, second_location, Dimension::Curve);
1462    }
1463}
1464
1465fn append_topology_candidates(
1466    topology: &Topology,
1467    segments: &[([f64; 2], [f64; 2])],
1468    output: &mut Vec<[f64; 2]>,
1469) {
1470    output.extend(topology.points.iter().copied());
1471    for &(start, end) in segments {
1472        output.push(start);
1473        output.push(end);
1474    }
1475}
1476
1477fn areas_intersect(first: &Topology, second: &Topology) -> Result<bool, OverlayError> {
1478    for first_polygon in &first.polygons {
1479        for second_polygon in &second.polygons {
1480            if !crate::operation::intersection(first_polygon, second_polygon)?
1481                .0
1482                .is_empty()
1483            {
1484                return Ok(true);
1485            }
1486        }
1487    }
1488    Ok(false)
1489}
1490
1491fn has_area_outside(first: &Topology, second: &Topology) -> Result<bool, OverlayError> {
1492    for polygon in &first.polygons {
1493        let mut pieces = alloc::vec![polygon.clone()];
1494        for clip in &second.polygons {
1495            let mut remainder = Vec::new();
1496            for piece in pieces {
1497                remainder.extend(crate::operation::difference(&piece, clip)?.0);
1498            }
1499            pieces = remainder;
1500            if pieces.is_empty() {
1501                break;
1502            }
1503        }
1504        if !pieces.is_empty() {
1505            return Ok(true);
1506        }
1507    }
1508    Ok(false)
1509}
1510
1511fn relate_topologies(first: &Topology, second: &Topology) -> Result<De9im, OverlayError> {
1512    if !first.in_range() || !second.in_range() {
1513        return Err(OverlayError::Unsupported);
1514    }
1515
1516    let first_segments = topology_segments(first);
1517    let second_segments = topology_segments(second);
1518    let all_segments = first_segments
1519        .iter()
1520        .chain(&second_segments)
1521        .copied()
1522        .collect::<Vec<_>>();
1523    let mut matrix = empty_matrix();
1524
1525    if areas_intersect(first, second)? {
1526        matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Area;
1527    }
1528    if has_area_outside(first, second)? {
1529        matrix.m[feature::INTERIOR][feature::EXTERIOR] = Dimension::Area;
1530    }
1531    if has_area_outside(second, first)? {
1532        matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Area;
1533    }
1534
1535    for &segment in &first_segments {
1536        record_segment_cells(&mut matrix, first, second, segment, &all_segments);
1537    }
1538    for &segment in &second_segments {
1539        for interval in segment_parameters(segment, &all_segments, &first.points).windows(2) {
1540            debug_assert!(interval[1] - interval[0] > f64::EPSILON);
1541            let midpoint = interpolate(
1542                segment.0,
1543                segment.1,
1544                f64::midpoint(interval[0], interval[1]),
1545            );
1546            let first_location = topology_location(first, midpoint);
1547            let second_location = topology_location(second, midpoint);
1548            debug_assert_ne!(second_location, Location::Exterior);
1549            set_dimension(
1550                &mut matrix,
1551                first_location,
1552                second_location,
1553                Dimension::Curve,
1554            );
1555        }
1556    }
1557
1558    let mut candidates = Vec::new();
1559    append_topology_candidates(first, &first_segments, &mut candidates);
1560    append_topology_candidates(second, &second_segments, &mut candidates);
1561    for &(first_start, first_end) in &first_segments {
1562        for &(second_start, second_end) in &second_segments {
1563            match segment_relation(first_start, first_end, second_start, second_end) {
1564                SegmentRelation::Point(point) => candidates.push(point),
1565                SegmentRelation::Overlap => {
1566                    for point in [first_start, first_end, second_start, second_end] {
1567                        if point_on_segment(point, first_start, first_end)
1568                            && point_on_segment(point, second_start, second_end)
1569                        {
1570                            candidates.push(point);
1571                        }
1572                    }
1573                }
1574                SegmentRelation::Disjoint => {}
1575            }
1576        }
1577    }
1578    candidates.sort_by(|first, second| {
1579        first[0]
1580            .total_cmp(&second[0])
1581            .then_with(|| first[1].total_cmp(&second[1]))
1582    });
1583    candidates.dedup_by(|first, second| xy_equal(*first, *second));
1584    for point in candidates {
1585        let first_location = topology_location(first, point);
1586        let second_location = topology_location(second, point);
1587        debug_assert!(
1588            first_location != Location::Exterior || second_location != Location::Exterior
1589        );
1590        set_dimension(
1591            &mut matrix,
1592            first_location,
1593            second_location,
1594            Dimension::Point,
1595        );
1596    }
1597
1598    Ok(matrix)
1599}
1600
1601/// Compute the DE-9IM matrix relating two polygons.
1602///
1603/// Fills the matrix from Boolean interior regions and exact segment-pair
1604/// boundary dimensions. Mirrors `boost::geometry::relation`
1605/// (`algorithms/relation.hpp`) for the areal × areal case.
1606///
1607/// # Errors
1608///
1609/// Returns [`OverlayError::Unsupported`] when either polygon leaves the exact
1610/// predicate range.
1611///
1612/// # Examples
1613///
1614/// ```
1615/// use geometry_cs::Cartesian;
1616/// use geometry_model::{polygon, Point2D, Polygon};
1617/// use geometry_overlay::relate::{relate, Dimension};
1618///
1619/// type P = Point2D<f64, Cartesian>;
1620/// let a: Polygon<P> = polygon![[(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]];
1621/// let b: Polygon<P> = polygon![[(1.0, 1.0), (3.0, 1.0), (3.0, 3.0), (1.0, 3.0), (1.0, 1.0)]];
1622/// let matrix = relate(&a, &b).unwrap();
1623/// // Overlapping squares: their interiors meet in an area.
1624/// assert_eq!(matrix.interior_interior(), Dimension::Area);
1625/// ```
1626fn relate_polygon_polygon<G1, G2, P>(g1: &G1, g2: &G2) -> Result<De9im, OverlayError>
1627where
1628    G1: PolygonTrait<Point = P>,
1629    G2: PolygonTrait<Point = P>,
1630    P: PointMut + Default + Copy,
1631    P::Scalar: CoordinateScalar + Into<f64>,
1632    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1633{
1634    if !polygon_in_range(g1) || !polygon_in_range(g2) {
1635        return Err(OverlayError::Unsupported);
1636    }
1637
1638    let interiors_overlap = !crate::operation::intersection(g1, g2)?.0.is_empty();
1639    let first_outside = !crate::operation::difference(g1, g2)?.0.is_empty();
1640    let second_outside = !crate::operation::difference(g2, g1)?.0.is_empty();
1641    let boundary_boundary = polygon_boundary_dimension(g1, g2);
1642
1643    let mut matrix = empty_matrix();
1644    if interiors_overlap {
1645        matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Area;
1646    }
1647    if first_outside {
1648        matrix.m[feature::INTERIOR][feature::EXTERIOR] = Dimension::Area;
1649        matrix.m[feature::BOUNDARY][feature::EXTERIOR] = Dimension::Curve;
1650    }
1651    if second_outside {
1652        matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Area;
1653        matrix.m[feature::EXTERIOR][feature::BOUNDARY] = Dimension::Curve;
1654    }
1655    matrix.m[feature::BOUNDARY][feature::BOUNDARY] = boundary_boundary;
1656
1657    if interiors_overlap {
1658        match (first_outside, second_outside) {
1659            (true, true) => {
1660                matrix.m[feature::INTERIOR][feature::BOUNDARY] = Dimension::Curve;
1661                matrix.m[feature::BOUNDARY][feature::INTERIOR] = Dimension::Curve;
1662            }
1663            (true, false) => {
1664                matrix.m[feature::INTERIOR][feature::BOUNDARY] = Dimension::Curve;
1665            }
1666            (false, true) => {
1667                matrix.m[feature::BOUNDARY][feature::INTERIOR] = Dimension::Curve;
1668            }
1669            (false, false) => {}
1670        }
1671    }
1672
1673    Ok(matrix)
1674}
1675
1676fn polygon_boundary_dimension<G1, G2, P>(first: &G1, second: &G2) -> Dimension
1677where
1678    G1: PolygonTrait<Point = P>,
1679    G2: PolygonTrait<Point = P>,
1680    P: Point,
1681    P::Scalar: Into<f64>,
1682{
1683    let first_segments = polygon_boundary_segments(first);
1684    let second_segments = polygon_boundary_segments(second);
1685    let mut dimension = Dimension::Empty;
1686    for (first_start, first_end) in &first_segments {
1687        for (second_start, second_end) in &second_segments {
1688            match segment_relation(*first_start, *first_end, *second_start, *second_end) {
1689                SegmentRelation::Overlap => return Dimension::Curve,
1690                SegmentRelation::Point(_) => dimension = Dimension::Point,
1691                SegmentRelation::Disjoint => {}
1692            }
1693        }
1694    }
1695    dimension
1696}
1697
1698fn polygon_boundary_segments<G, P>(polygon: &G) -> alloc::vec::Vec<([f64; 2], [f64; 2])>
1699where
1700    G: PolygonTrait<Point = P>,
1701    P: Point,
1702    P::Scalar: Into<f64>,
1703{
1704    let mut segments = alloc::vec::Vec::new();
1705    append_boundary_segments(polygon.exterior(), &mut segments);
1706    for ring in polygon.interiors() {
1707        append_boundary_segments(ring, &mut segments);
1708    }
1709    segments
1710}
1711
1712fn append_boundary_segments<R>(ring: &R, output: &mut alloc::vec::Vec<([f64; 2], [f64; 2])>)
1713where
1714    R: RingTrait,
1715    <R::Point as Point>::Scalar: Into<f64>,
1716{
1717    let points: alloc::vec::Vec<_> = ring.points().map(xy).collect();
1718    for pair in points.windows(2) {
1719        if !xy_equal(pair[0], pair[1]) {
1720            output.push((pair[0], pair[1]));
1721        }
1722    }
1723    if let (Some(first), Some(last)) = (points.first(), points.last())
1724        && !xy_equal(*first, *last)
1725    {
1726        output.push((*last, *first));
1727    }
1728}
1729
1730/// Test whether two polygons satisfy a DE-9IM mask.
1731///
1732/// Mirrors `boost::geometry::relate(g1, g2, mask)` from
1733/// `boost/geometry/algorithms/detail/relate/interface.hpp:347-382`. Use the
1734/// crate-root `relation(g1, g2)` entry to obtain the matrix itself, matching
1735/// `boost::geometry::relation` from `algorithms/relation.hpp`.
1736///
1737/// # Errors
1738///
1739/// Returns [`RelateError::Overlay`] when the areal relation is unsupported,
1740/// or [`RelateError::InvalidMask`] for a malformed mask.
1741#[inline]
1742#[must_use = "relate can fail and its predicate result should be used"]
1743pub fn relate_mask<G1, G2>(g1: &G1, g2: &G2, mask: &str) -> Result<bool, RelateError>
1744where
1745    G1: Geometry,
1746    G2: Geometry,
1747    G1::Kind: RelatePairStrategy<G2::Kind>,
1748    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1749{
1750    relate(g1, g2)?.matches(mask)
1751}
1752
1753/// `contains_properly`: the second geometry lies strictly inside the first.
1754///
1755/// Evaluates the OGC DE-9IM mask `T**FF*FF*`: the interiors intersect, and
1756/// neither the interior nor boundary of the second geometry intersects the
1757/// boundary or exterior of the first.
1758///
1759/// # Errors
1760///
1761/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1762#[inline]
1763#[must_use = "contains_properly can fail and its predicate result should be used"]
1764pub fn contains_properly<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1765where
1766    G1: Geometry,
1767    G2: Geometry,
1768    G1::Kind: RelatePairStrategy<G2::Kind>,
1769    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1770{
1771    let matrix = relate(g1, g2)?;
1772    Ok(matrix.interior_interior().is_set()
1773        && !matrix.m[feature::BOUNDARY][feature::INTERIOR].is_set()
1774        && !matrix.m[feature::BOUNDARY][feature::BOUNDARY].is_set()
1775        && !matrix.m[feature::EXTERIOR][feature::INTERIOR].is_set()
1776        && !matrix.m[feature::EXTERIOR][feature::BOUNDARY].is_set())
1777}
1778
1779/// `touches`: the boundaries meet but the interiors do not.
1780///
1781/// Mirrors `boost::geometry::touches` (`algorithms/touches.hpp`) for the
1782/// areal × areal case: `II = F` and the boundaries have non-empty
1783/// intersection.
1784///
1785/// # Errors
1786///
1787/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1788#[inline]
1789#[must_use = "touches can fail and its predicate result should be used"]
1790pub fn touches<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1791where
1792    G1: Geometry,
1793    G2: Geometry,
1794    G1::Kind: RelatePairStrategy<G2::Kind>,
1795    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1796{
1797    let matrix = relate(g1, g2)?;
1798    Ok(!matrix.interior_interior().is_set()
1799        && (matrix.m[feature::INTERIOR][feature::BOUNDARY].is_set()
1800            || matrix.m[feature::BOUNDARY][feature::INTERIOR].is_set()
1801            || matrix.boundary_boundary().is_set()))
1802}
1803
1804/// `overlaps`: the interiors intersect, and each geometry has interior
1805/// points outside the other, at the same dimension.
1806///
1807/// Mirrors `boost::geometry::overlaps` (`algorithms/overlaps.hpp`) for
1808/// areal × areal: `II = 2`, `IE = 2`, and `EI = 2`.
1809///
1810/// # Errors
1811///
1812/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1813#[inline]
1814#[must_use = "overlaps can fail and its predicate result should be used"]
1815pub fn overlaps<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1816where
1817    G1: Geometry,
1818    G2: Geometry,
1819    G1::Kind: RelatePairStrategy<G2::Kind>,
1820    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1821{
1822    let matrix = relate(g1, g2)?;
1823    let dimension = matrix.interior_interior();
1824    Ok(matches!(
1825        dimension,
1826        Dimension::Point | Dimension::Curve | Dimension::Area
1827    ) && matrix.interior_exterior() == dimension
1828        && matrix.exterior_interior() == dimension)
1829}
1830
1831/// `crosses`: test the DE-9IM crossing masks for supported pairs.
1832///
1833/// Mirrors `boost::geometry::crosses` (`algorithms/crosses.hpp`); the
1834/// areal × areal arm returns `false` by definition, while line/line and
1835/// line/areal pairs use their corresponding dimensional masks.
1836///
1837/// # Errors
1838///
1839/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1840#[inline]
1841#[must_use = "crosses can fail and its predicate result should be used"]
1842pub fn crosses<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1843where
1844    G1: Geometry,
1845    G2: Geometry,
1846    G1::Kind: RelatePairStrategy<G2::Kind>,
1847    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1848{
1849    let matrix = relate(g1, g2)?;
1850    Ok((matrix.interior_interior() == Dimension::Point
1851        && matrix.interior_exterior() == Dimension::Curve
1852        && matrix.exterior_interior() == Dimension::Curve)
1853        || (matrix.interior_interior() == Dimension::Curve
1854            && (matrix.interior_exterior() == Dimension::Curve
1855                || matrix.exterior_interior() == Dimension::Curve)))
1856}
1857
1858#[cfg(test)]
1859mod tests {
1860    //! OVL6.T1 / T2 done-when: matrix + predicate values. Mirrors the
1861    //! case families in `test/algorithms/relate/` and the
1862    //! `touches` / `overlaps` test files.
1863
1864    use super::{Dimension, contains_properly, crosses, overlaps, relate, touches};
1865    use geometry_cs::Cartesian;
1866    use geometry_model::{Point2D, Polygon, polygon};
1867
1868    type P = Point2D<f64, Cartesian>;
1869
1870    fn square(x: f64, y: f64, s: f64) -> Polygon<P> {
1871        polygon![[(x, y), (x + s, y), (x + s, y + s), (x, y + s), (x, y)]]
1872    }
1873
1874    #[test]
1875    fn overlapping_squares_overlap() {
1876        let a = square(0.0, 0.0, 2.0);
1877        let b = square(1.0, 1.0, 2.0);
1878        assert_eq!(relate(&a, &b).unwrap().interior_interior(), Dimension::Area);
1879        assert!(overlaps(&a, &b).unwrap());
1880        assert!(!touches(&a, &b).unwrap());
1881        assert!(!crosses(&a, &b).unwrap());
1882    }
1883
1884    #[test]
1885    fn proper_containment_excludes_boundary_contact() {
1886        let container = square(0.0, 0.0, 5.0);
1887        assert!(contains_properly(&container, &square(1.0, 1.0, 1.0)).unwrap());
1888        assert!(!contains_properly(&container, &square(0.0, 1.0, 1.0)).unwrap());
1889    }
1890
1891    #[test]
1892    fn edge_touching_squares_have_curve_boundary_intersection() {
1893        let a = square(0.0, 0.0, 2.0);
1894        let b = square(2.0, 0.0, 2.0);
1895        assert_eq!(
1896            relate(&a, &b).unwrap().boundary_boundary(),
1897            Dimension::Curve
1898        );
1899        assert!(touches(&a, &b).unwrap());
1900        assert!(!overlaps(&a, &b).unwrap());
1901    }
1902
1903    #[test]
1904    fn edge_aligned_overlap_is_detected() {
1905        let a: Polygon<P> = polygon![[(0.0, 0.0), (3.0, 0.0), (3.0, 1.0), (0.0, 1.0), (0.0, 0.0)]];
1906        let b: Polygon<P> = polygon![[(2.0, 0.0), (5.0, 0.0), (5.0, 1.0), (2.0, 1.0), (2.0, 0.0)]];
1907        assert!(overlaps(&a, &b).unwrap());
1908    }
1909
1910    #[test]
1911    fn out_of_range_coordinates_are_unsupported() {
1912        // Regression: past ±2^26 the turn kernel silently drops
1913        // intersections, so an emptied turn graph would be misread as
1914        // disjoint (II=Empty) for genuinely overlapping huge polygons.
1915        // relate must refuse rather than return that wrong matrix.
1916        use crate::operation::OverlayError;
1917        let a: Polygon<P> = polygon![[
1918            (0.0, 0.0),
1919            (2e14, 0.0),
1920            (2e14, 2e14),
1921            (0.0, 2e14),
1922            (0.0, 0.0)
1923        ]];
1924        let b: Polygon<P> = polygon![[
1925            (1e14, 1e14),
1926            (3e14, 1e14),
1927            (3e14, 3e14),
1928            (1e14, 3e14),
1929            (1e14, 1e14)
1930        ]];
1931        assert_eq!(relate(&a, &b), Err(OverlayError::Unsupported));
1932        assert_eq!(overlaps(&a, &b), Err(OverlayError::Unsupported));
1933    }
1934
1935    #[test]
1936    fn disjoint_squares_neither() {
1937        let a = square(0.0, 0.0, 1.0);
1938        let b = square(5.0, 5.0, 1.0);
1939        assert!(!touches(&a, &b).unwrap());
1940        assert!(!overlaps(&a, &b).unwrap());
1941        assert_eq!(
1942            relate(&a, &b).unwrap().interior_interior(),
1943            Dimension::Empty
1944        );
1945    }
1946
1947    #[test]
1948    fn contained_square_does_not_overlap_or_touch() {
1949        // small ⊂ big: interiors meet (II = area) but small has no
1950        // interior outside big, so it is containment, not overlap. The
1951        // rep-point containment makes this decidable (no boundary touch),
1952        // so it is answered normally.
1953        let big = square(0.0, 0.0, 10.0);
1954        let small = square(3.0, 3.0, 2.0);
1955        assert_eq!(
1956            relate(&big, &small).unwrap().interior_interior(),
1957            Dimension::Area
1958        );
1959        assert!(!overlaps(&big, &small).unwrap());
1960        assert!(!touches(&big, &small).unwrap());
1961    }
1962}