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                    (interval[0] + interval[1]) * 0.5,
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                    (interval[0] + interval[1]) * 0.5,
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(segment.0, segment.1, (interval[0] + interval[1]) * 0.5);
1454        let first_location = topology_location(first, midpoint);
1455        let second_location = topology_location(second, midpoint);
1456        debug_assert_ne!(first_location, Location::Exterior);
1457        set_dimension(matrix, first_location, second_location, Dimension::Curve);
1458    }
1459}
1460
1461fn append_topology_candidates(
1462    topology: &Topology,
1463    segments: &[([f64; 2], [f64; 2])],
1464    output: &mut Vec<[f64; 2]>,
1465) {
1466    output.extend(topology.points.iter().copied());
1467    for &(start, end) in segments {
1468        output.push(start);
1469        output.push(end);
1470    }
1471}
1472
1473fn areas_intersect(first: &Topology, second: &Topology) -> Result<bool, OverlayError> {
1474    for first_polygon in &first.polygons {
1475        for second_polygon in &second.polygons {
1476            if !crate::operation::intersection(first_polygon, second_polygon)?
1477                .0
1478                .is_empty()
1479            {
1480                return Ok(true);
1481            }
1482        }
1483    }
1484    Ok(false)
1485}
1486
1487fn has_area_outside(first: &Topology, second: &Topology) -> Result<bool, OverlayError> {
1488    for polygon in &first.polygons {
1489        let mut pieces = alloc::vec![polygon.clone()];
1490        for clip in &second.polygons {
1491            let mut remainder = Vec::new();
1492            for piece in pieces {
1493                remainder.extend(crate::operation::difference(&piece, clip)?.0);
1494            }
1495            pieces = remainder;
1496            if pieces.is_empty() {
1497                break;
1498            }
1499        }
1500        if !pieces.is_empty() {
1501            return Ok(true);
1502        }
1503    }
1504    Ok(false)
1505}
1506
1507fn relate_topologies(first: &Topology, second: &Topology) -> Result<De9im, OverlayError> {
1508    if !first.in_range() || !second.in_range() {
1509        return Err(OverlayError::Unsupported);
1510    }
1511
1512    let first_segments = topology_segments(first);
1513    let second_segments = topology_segments(second);
1514    let all_segments = first_segments
1515        .iter()
1516        .chain(&second_segments)
1517        .copied()
1518        .collect::<Vec<_>>();
1519    let mut matrix = empty_matrix();
1520
1521    if areas_intersect(first, second)? {
1522        matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Area;
1523    }
1524    if has_area_outside(first, second)? {
1525        matrix.m[feature::INTERIOR][feature::EXTERIOR] = Dimension::Area;
1526    }
1527    if has_area_outside(second, first)? {
1528        matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Area;
1529    }
1530
1531    for &segment in &first_segments {
1532        record_segment_cells(&mut matrix, first, second, segment, &all_segments);
1533    }
1534    for &segment in &second_segments {
1535        for interval in segment_parameters(segment, &all_segments, &first.points).windows(2) {
1536            debug_assert!(interval[1] - interval[0] > f64::EPSILON);
1537            let midpoint = interpolate(segment.0, segment.1, (interval[0] + interval[1]) * 0.5);
1538            let first_location = topology_location(first, midpoint);
1539            let second_location = topology_location(second, midpoint);
1540            debug_assert_ne!(second_location, Location::Exterior);
1541            set_dimension(
1542                &mut matrix,
1543                first_location,
1544                second_location,
1545                Dimension::Curve,
1546            );
1547        }
1548    }
1549
1550    let mut candidates = Vec::new();
1551    append_topology_candidates(first, &first_segments, &mut candidates);
1552    append_topology_candidates(second, &second_segments, &mut candidates);
1553    for &(first_start, first_end) in &first_segments {
1554        for &(second_start, second_end) in &second_segments {
1555            match segment_relation(first_start, first_end, second_start, second_end) {
1556                SegmentRelation::Point(point) => candidates.push(point),
1557                SegmentRelation::Overlap => {
1558                    for point in [first_start, first_end, second_start, second_end] {
1559                        if point_on_segment(point, first_start, first_end)
1560                            && point_on_segment(point, second_start, second_end)
1561                        {
1562                            candidates.push(point);
1563                        }
1564                    }
1565                }
1566                SegmentRelation::Disjoint => {}
1567            }
1568        }
1569    }
1570    candidates.sort_by(|first, second| {
1571        first[0]
1572            .total_cmp(&second[0])
1573            .then_with(|| first[1].total_cmp(&second[1]))
1574    });
1575    candidates.dedup_by(|first, second| xy_equal(*first, *second));
1576    for point in candidates {
1577        let first_location = topology_location(first, point);
1578        let second_location = topology_location(second, point);
1579        debug_assert!(
1580            first_location != Location::Exterior || second_location != Location::Exterior
1581        );
1582        set_dimension(
1583            &mut matrix,
1584            first_location,
1585            second_location,
1586            Dimension::Point,
1587        );
1588    }
1589
1590    Ok(matrix)
1591}
1592
1593/// Compute the DE-9IM matrix relating two polygons.
1594///
1595/// Fills the matrix from Boolean interior regions and exact segment-pair
1596/// boundary dimensions. Mirrors `boost::geometry::relation`
1597/// (`algorithms/relation.hpp`) for the areal × areal case.
1598///
1599/// # Errors
1600///
1601/// Returns [`OverlayError::Unsupported`] when either polygon leaves the exact
1602/// predicate range.
1603///
1604/// # Examples
1605///
1606/// ```
1607/// use geometry_cs::Cartesian;
1608/// use geometry_model::{polygon, Point2D, Polygon};
1609/// use geometry_overlay::relate::{relate, Dimension};
1610///
1611/// type P = Point2D<f64, Cartesian>;
1612/// 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)]];
1613/// 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)]];
1614/// let matrix = relate(&a, &b).unwrap();
1615/// // Overlapping squares: their interiors meet in an area.
1616/// assert_eq!(matrix.interior_interior(), Dimension::Area);
1617/// ```
1618fn relate_polygon_polygon<G1, G2, P>(g1: &G1, g2: &G2) -> Result<De9im, OverlayError>
1619where
1620    G1: PolygonTrait<Point = P>,
1621    G2: PolygonTrait<Point = P>,
1622    P: PointMut + Default + Copy,
1623    P::Scalar: CoordinateScalar + Into<f64>,
1624    <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
1625{
1626    if !polygon_in_range(g1) || !polygon_in_range(g2) {
1627        return Err(OverlayError::Unsupported);
1628    }
1629
1630    let interiors_overlap = !crate::operation::intersection(g1, g2)?.0.is_empty();
1631    let first_outside = !crate::operation::difference(g1, g2)?.0.is_empty();
1632    let second_outside = !crate::operation::difference(g2, g1)?.0.is_empty();
1633    let boundary_boundary = polygon_boundary_dimension(g1, g2);
1634
1635    let mut matrix = empty_matrix();
1636    if interiors_overlap {
1637        matrix.m[feature::INTERIOR][feature::INTERIOR] = Dimension::Area;
1638    }
1639    if first_outside {
1640        matrix.m[feature::INTERIOR][feature::EXTERIOR] = Dimension::Area;
1641        matrix.m[feature::BOUNDARY][feature::EXTERIOR] = Dimension::Curve;
1642    }
1643    if second_outside {
1644        matrix.m[feature::EXTERIOR][feature::INTERIOR] = Dimension::Area;
1645        matrix.m[feature::EXTERIOR][feature::BOUNDARY] = Dimension::Curve;
1646    }
1647    matrix.m[feature::BOUNDARY][feature::BOUNDARY] = boundary_boundary;
1648
1649    if interiors_overlap {
1650        match (first_outside, second_outside) {
1651            (true, true) => {
1652                matrix.m[feature::INTERIOR][feature::BOUNDARY] = Dimension::Curve;
1653                matrix.m[feature::BOUNDARY][feature::INTERIOR] = Dimension::Curve;
1654            }
1655            (true, false) => {
1656                matrix.m[feature::INTERIOR][feature::BOUNDARY] = Dimension::Curve;
1657            }
1658            (false, true) => {
1659                matrix.m[feature::BOUNDARY][feature::INTERIOR] = Dimension::Curve;
1660            }
1661            (false, false) => {}
1662        }
1663    }
1664
1665    Ok(matrix)
1666}
1667
1668fn polygon_boundary_dimension<G1, G2, P>(first: &G1, second: &G2) -> Dimension
1669where
1670    G1: PolygonTrait<Point = P>,
1671    G2: PolygonTrait<Point = P>,
1672    P: Point,
1673    P::Scalar: Into<f64>,
1674{
1675    let first_segments = polygon_boundary_segments(first);
1676    let second_segments = polygon_boundary_segments(second);
1677    let mut dimension = Dimension::Empty;
1678    for (first_start, first_end) in &first_segments {
1679        for (second_start, second_end) in &second_segments {
1680            match segment_relation(*first_start, *first_end, *second_start, *second_end) {
1681                SegmentRelation::Overlap => return Dimension::Curve,
1682                SegmentRelation::Point(_) => dimension = Dimension::Point,
1683                SegmentRelation::Disjoint => {}
1684            }
1685        }
1686    }
1687    dimension
1688}
1689
1690fn polygon_boundary_segments<G, P>(polygon: &G) -> alloc::vec::Vec<([f64; 2], [f64; 2])>
1691where
1692    G: PolygonTrait<Point = P>,
1693    P: Point,
1694    P::Scalar: Into<f64>,
1695{
1696    let mut segments = alloc::vec::Vec::new();
1697    append_boundary_segments(polygon.exterior(), &mut segments);
1698    for ring in polygon.interiors() {
1699        append_boundary_segments(ring, &mut segments);
1700    }
1701    segments
1702}
1703
1704fn append_boundary_segments<R>(ring: &R, output: &mut alloc::vec::Vec<([f64; 2], [f64; 2])>)
1705where
1706    R: RingTrait,
1707    <R::Point as Point>::Scalar: Into<f64>,
1708{
1709    let points: alloc::vec::Vec<_> = ring.points().map(xy).collect();
1710    for pair in points.windows(2) {
1711        if !xy_equal(pair[0], pair[1]) {
1712            output.push((pair[0], pair[1]));
1713        }
1714    }
1715    if let (Some(first), Some(last)) = (points.first(), points.last())
1716        && !xy_equal(*first, *last)
1717    {
1718        output.push((*last, *first));
1719    }
1720}
1721
1722/// Test whether two polygons satisfy a DE-9IM mask.
1723///
1724/// Mirrors `boost::geometry::relate(g1, g2, mask)` from
1725/// `boost/geometry/algorithms/detail/relate/interface.hpp:347-382`. Use the
1726/// crate-root `relation(g1, g2)` entry to obtain the matrix itself, matching
1727/// `boost::geometry::relation` from `algorithms/relation.hpp`.
1728///
1729/// # Errors
1730///
1731/// Returns [`RelateError::Overlay`] when the areal relation is unsupported,
1732/// or [`RelateError::InvalidMask`] for a malformed mask.
1733#[inline]
1734#[must_use = "relate can fail and its predicate result should be used"]
1735pub fn relate_mask<G1, G2>(g1: &G1, g2: &G2, mask: &str) -> Result<bool, RelateError>
1736where
1737    G1: Geometry,
1738    G2: Geometry,
1739    G1::Kind: RelatePairStrategy<G2::Kind>,
1740    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1741{
1742    relate(g1, g2)?.matches(mask)
1743}
1744
1745/// `contains_properly`: the second geometry lies strictly inside the first.
1746///
1747/// Evaluates the OGC DE-9IM mask `T**FF*FF*`: the interiors intersect, and
1748/// neither the interior nor boundary of the second geometry intersects the
1749/// boundary or exterior of the first.
1750///
1751/// # Errors
1752///
1753/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1754#[inline]
1755#[must_use = "contains_properly can fail and its predicate result should be used"]
1756pub fn contains_properly<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1757where
1758    G1: Geometry,
1759    G2: Geometry,
1760    G1::Kind: RelatePairStrategy<G2::Kind>,
1761    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1762{
1763    let matrix = relate(g1, g2)?;
1764    Ok(matrix.interior_interior().is_set()
1765        && !matrix.m[feature::BOUNDARY][feature::INTERIOR].is_set()
1766        && !matrix.m[feature::BOUNDARY][feature::BOUNDARY].is_set()
1767        && !matrix.m[feature::EXTERIOR][feature::INTERIOR].is_set()
1768        && !matrix.m[feature::EXTERIOR][feature::BOUNDARY].is_set())
1769}
1770
1771/// `touches`: the boundaries meet but the interiors do not.
1772///
1773/// Mirrors `boost::geometry::touches` (`algorithms/touches.hpp`) for the
1774/// areal × areal case: `II = F` and the boundaries have non-empty
1775/// intersection.
1776///
1777/// # Errors
1778///
1779/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1780#[inline]
1781#[must_use = "touches can fail and its predicate result should be used"]
1782pub fn touches<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1783where
1784    G1: Geometry,
1785    G2: Geometry,
1786    G1::Kind: RelatePairStrategy<G2::Kind>,
1787    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1788{
1789    let matrix = relate(g1, g2)?;
1790    Ok(!matrix.interior_interior().is_set()
1791        && (matrix.m[feature::INTERIOR][feature::BOUNDARY].is_set()
1792            || matrix.m[feature::BOUNDARY][feature::INTERIOR].is_set()
1793            || matrix.boundary_boundary().is_set()))
1794}
1795
1796/// `overlaps`: the interiors intersect, and each geometry has interior
1797/// points outside the other, at the same dimension.
1798///
1799/// Mirrors `boost::geometry::overlaps` (`algorithms/overlaps.hpp`) for
1800/// areal × areal: `II = 2`, `IE = 2`, and `EI = 2`.
1801///
1802/// # Errors
1803///
1804/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1805#[inline]
1806#[must_use = "overlaps can fail and its predicate result should be used"]
1807pub fn overlaps<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1808where
1809    G1: Geometry,
1810    G2: Geometry,
1811    G1::Kind: RelatePairStrategy<G2::Kind>,
1812    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1813{
1814    let matrix = relate(g1, g2)?;
1815    let dimension = matrix.interior_interior();
1816    Ok(matches!(
1817        dimension,
1818        Dimension::Point | Dimension::Curve | Dimension::Area
1819    ) && matrix.interior_exterior() == dimension
1820        && matrix.exterior_interior() == dimension)
1821}
1822
1823/// `crosses`: test the DE-9IM crossing masks for supported pairs.
1824///
1825/// Mirrors `boost::geometry::crosses` (`algorithms/crosses.hpp`); the
1826/// areal × areal arm returns `false` by definition, while line/line and
1827/// line/areal pairs use their corresponding dimensional masks.
1828///
1829/// # Errors
1830///
1831/// Propagates [`OverlayError::Unsupported`] from [`relate`].
1832#[inline]
1833#[must_use = "crosses can fail and its predicate result should be used"]
1834pub fn crosses<G1, G2>(g1: &G1, g2: &G2) -> Result<bool, OverlayError>
1835where
1836    G1: Geometry,
1837    G2: Geometry,
1838    G1::Kind: RelatePairStrategy<G2::Kind>,
1839    PairStrategy<G1, G2>: RelateStrategy<G1, G2> + Default,
1840{
1841    let matrix = relate(g1, g2)?;
1842    Ok((matrix.interior_interior() == Dimension::Point
1843        && matrix.interior_exterior() == Dimension::Curve
1844        && matrix.exterior_interior() == Dimension::Curve)
1845        || (matrix.interior_interior() == Dimension::Curve
1846            && (matrix.interior_exterior() == Dimension::Curve
1847                || matrix.exterior_interior() == Dimension::Curve)))
1848}
1849
1850#[cfg(test)]
1851mod tests {
1852    //! OVL6.T1 / T2 done-when: matrix + predicate values. Mirrors the
1853    //! case families in `test/algorithms/relate/` and the
1854    //! `touches` / `overlaps` test files.
1855
1856    use super::{Dimension, contains_properly, crosses, overlaps, relate, touches};
1857    use geometry_cs::Cartesian;
1858    use geometry_model::{Point2D, Polygon, polygon};
1859
1860    type P = Point2D<f64, Cartesian>;
1861
1862    fn square(x: f64, y: f64, s: f64) -> Polygon<P> {
1863        polygon![[(x, y), (x + s, y), (x + s, y + s), (x, y + s), (x, y)]]
1864    }
1865
1866    #[test]
1867    fn overlapping_squares_overlap() {
1868        let a = square(0.0, 0.0, 2.0);
1869        let b = square(1.0, 1.0, 2.0);
1870        assert_eq!(relate(&a, &b).unwrap().interior_interior(), Dimension::Area);
1871        assert!(overlaps(&a, &b).unwrap());
1872        assert!(!touches(&a, &b).unwrap());
1873        assert!(!crosses(&a, &b).unwrap());
1874    }
1875
1876    #[test]
1877    fn proper_containment_excludes_boundary_contact() {
1878        let container = square(0.0, 0.0, 5.0);
1879        assert!(contains_properly(&container, &square(1.0, 1.0, 1.0)).unwrap());
1880        assert!(!contains_properly(&container, &square(0.0, 1.0, 1.0)).unwrap());
1881    }
1882
1883    #[test]
1884    fn edge_touching_squares_have_curve_boundary_intersection() {
1885        let a = square(0.0, 0.0, 2.0);
1886        let b = square(2.0, 0.0, 2.0);
1887        assert_eq!(
1888            relate(&a, &b).unwrap().boundary_boundary(),
1889            Dimension::Curve
1890        );
1891        assert!(touches(&a, &b).unwrap());
1892        assert!(!overlaps(&a, &b).unwrap());
1893    }
1894
1895    #[test]
1896    fn edge_aligned_overlap_is_detected() {
1897        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)]];
1898        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)]];
1899        assert!(overlaps(&a, &b).unwrap());
1900    }
1901
1902    #[test]
1903    fn out_of_range_coordinates_are_unsupported() {
1904        // Regression: past ±2^26 the turn kernel silently drops
1905        // intersections, so an emptied turn graph would be misread as
1906        // disjoint (II=Empty) for genuinely overlapping huge polygons.
1907        // relate must refuse rather than return that wrong matrix.
1908        use crate::operation::OverlayError;
1909        let a: Polygon<P> = polygon![[
1910            (0.0, 0.0),
1911            (2e14, 0.0),
1912            (2e14, 2e14),
1913            (0.0, 2e14),
1914            (0.0, 0.0)
1915        ]];
1916        let b: Polygon<P> = polygon![[
1917            (1e14, 1e14),
1918            (3e14, 1e14),
1919            (3e14, 3e14),
1920            (1e14, 3e14),
1921            (1e14, 1e14)
1922        ]];
1923        assert_eq!(relate(&a, &b), Err(OverlayError::Unsupported));
1924        assert_eq!(overlaps(&a, &b), Err(OverlayError::Unsupported));
1925    }
1926
1927    #[test]
1928    fn disjoint_squares_neither() {
1929        let a = square(0.0, 0.0, 1.0);
1930        let b = square(5.0, 5.0, 1.0);
1931        assert!(!touches(&a, &b).unwrap());
1932        assert!(!overlaps(&a, &b).unwrap());
1933        assert_eq!(
1934            relate(&a, &b).unwrap().interior_interior(),
1935            Dimension::Empty
1936        );
1937    }
1938
1939    #[test]
1940    fn contained_square_does_not_overlap_or_touch() {
1941        // small ⊂ big: interiors meet (II = area) but small has no
1942        // interior outside big, so it is containment, not overlap. The
1943        // rep-point containment makes this decidable (no boundary touch),
1944        // so it is answered normally.
1945        let big = square(0.0, 0.0, 10.0);
1946        let small = square(3.0, 3.0, 2.0);
1947        assert_eq!(
1948            relate(&big, &small).unwrap().interior_interior(),
1949            Dimension::Area
1950        );
1951        assert!(!overlaps(&big, &small).unwrap());
1952        assert!(!touches(&big, &small).unwrap());
1953    }
1954}