geo/algorithm/
interior_point.rs

1use std::cmp::{Ordering, Reverse};
2
3use crate::algorithm::{
4    bounding_rect::BoundingRect,
5    centroid::Centroid,
6    coords_iter::CoordsIter,
7    dimensions::HasDimensions,
8    line_intersection::LineIntersection,
9    line_measures::{Distance, Euclidean},
10    lines_iter::LinesIter,
11    relate::Relate,
12};
13use crate::geometry::*;
14// use crate::old_sweep::{Intersections, SweepPoint};
15use crate::GeoFloat;
16use crate::sweep::Intersections;
17
18/// Calculation of interior points.
19///
20/// An interior point is a point that's guaranteed to intersect a given geometry, and will be
21/// strictly on the interior of the geometry if possible, or on the edge if the geometry has zero
22/// area. A best effort will additionally be made to locate the point reasonably centrally.
23///
24/// For polygons, this point is located by drawing a line that approximately subdivides the
25/// bounding box around the polygon in half, intersecting it with the polygon, then calculating
26/// the midpoint of the longest line produced by the intersection. For lines, the non-endpoint
27/// vertex closest to the line's centroid is returned if the line has interior points, or an
28/// endpoint is returned otherwise.
29///
30/// For multi-geometries or collections, the interior points of the constituent components are
31/// calculated, and one of those is returned (for MultiPolygons, it's the point that's the midpoint
32/// of the longest intersection of the intersection lines of any of the constituent polygons, as
33/// described above; for all others, the interior point closest to the collection's centroid is
34/// used).
35///
36/// # Examples
37///
38/// ```
39/// use geo::InteriorPoint;
40/// use geo::{point, polygon};
41///
42/// // rhombus shaped polygon
43/// let polygon = polygon![
44///     (x: -2., y: 1.),
45///     (x: 1., y: 3.),
46///     (x: 4., y: 1.),
47///     (x: 1., y: -1.),
48///     (x: -2., y: 1.),
49/// ];
50///
51/// assert_eq!(
52///     Some(point!(x: 1., y: 2.)),
53///     polygon.interior_point(),
54/// );
55/// ```
56pub trait InteriorPoint {
57    type Output;
58
59    /// Calculates a representative point inside the `Geometry`
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use geo::InteriorPoint;
65    /// use geo::{line_string, point};
66    ///
67    /// let line_string = line_string![
68    ///     (x: 40.02f64, y: 116.34),
69    ///     (x: 40.02f64, y: 118.23),
70    ///     (x: 40.02f64, y: 120.15),
71    /// ];
72    ///
73    /// assert_eq!(
74    ///     Some(point!(x: 40.02, y: 118.23)),
75    ///     line_string.interior_point(),
76    /// );
77    /// ```
78    fn interior_point(&self) -> Self::Output;
79}
80
81impl<T> InteriorPoint for Line<T>
82where
83    T: GeoFloat,
84{
85    type Output = Point<T>;
86
87    fn interior_point(&self) -> Self::Output {
88        // the midpoint of the line isn't guaranteed to actually have an `intersects()`
89        // relationship with the line due to floating point rounding, so just use the start point
90        self.start_point()
91    }
92}
93
94impl<T> InteriorPoint for LineString<T>
95where
96    T: GeoFloat,
97{
98    type Output = Option<Point<T>>;
99
100    // The interior point of a LineString the non-endpoint vertex closest to the centroid if any,
101    // or the start point if there are no non-endpoint vertices
102    fn interior_point(&self) -> Self::Output {
103        match self.0.len() {
104            0 => None,
105            // for linestrings of length 2, as with lines, the calculated midpoint might not lie
106            // on the line, so just use the start point
107            1 | 2 => Some(self.0[0].into()),
108            _ => {
109                let centroid = self
110                    .centroid()
111                    .expect("expected centroid for non-empty linestring");
112                self.0[1..(self.0.len() - 1)]
113                    .iter()
114                    .map(|coord| {
115                        let pt = Point::from(*coord);
116                        (pt, Euclidean.distance(pt, centroid))
117                    })
118                    .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Less))
119                    .map(|(pt, _distance)| pt)
120            }
121        }
122    }
123}
124
125impl<T> InteriorPoint for MultiLineString<T>
126where
127    T: GeoFloat,
128{
129    type Output = Option<Point<T>>;
130
131    /// The interior point of a MultiLineString is, of the interior points of all the constituent
132    /// LineStrings, the one closest to the centroid of the MultiLineString
133    fn interior_point(&self) -> Self::Output {
134        if let Some(centroid) = self.centroid() {
135            self.iter()
136                .filter_map(|linestring| {
137                    linestring
138                        .interior_point()
139                        .map(|pt| (pt, Euclidean.distance(pt, centroid)))
140                })
141                .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Less))
142                .map(|(pt, _distance)| pt)
143        } else {
144            None
145        }
146    }
147}
148
149fn polygon_interior_point_with_segment_length<T: GeoFloat>(
150    polygon: &Polygon<T>,
151) -> Option<(Point<T>, T)> {
152    // special-case a one-point polygon since this algorithm won't otherwise support it
153    if polygon.exterior().0.len() == 1 {
154        return Some((polygon.exterior().0[0].into(), T::zero()));
155    }
156
157    let two = T::one() + T::one();
158
159    let bounds = polygon.bounding_rect()?;
160
161    // use the midpoint of the bounds to scan, unless that happens to match any vertices from
162    // polygon; if it does, perturb the line a bit by averaging with the Y coordinate of the
163    // next-closest-to-center vertex if possible, to reduce the likelihood of collinear
164    // intersections
165    let mut y_mid = (bounds.min().y + bounds.max().y) / two;
166    if polygon.coords_iter().any(|coord| coord.y == y_mid) {
167        let next_closest = polygon
168            .coords_iter()
169            .filter_map(|coord| {
170                if coord.y == y_mid {
171                    None
172                } else {
173                    Some((coord.y, (coord.y - y_mid).abs()))
174                }
175            })
176            .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Less));
177        if let Some((closest, _)) = next_closest {
178            y_mid = (y_mid + closest) / two
179        }
180    };
181
182    let scan_line = Line::new(
183        Coord {
184            x: bounds.min().x,
185            y: y_mid,
186        },
187        Coord {
188            x: bounds.max().x,
189            y: y_mid,
190        },
191    );
192
193    let lines = polygon.lines_iter().chain(std::iter::once(scan_line));
194
195    let mut intersections: Vec<Point<T>> = Vec::new();
196    for (l1, l2, inter) in Intersections::from_iter(lines) {
197        if !(l1 == scan_line || l2 == scan_line) {
198            continue;
199        }
200        match inter {
201            LineIntersection::Collinear { intersection } => {
202                intersections.push(intersection.start.into());
203                intersections.push(intersection.end.into());
204            }
205            LineIntersection::SinglePoint { intersection, .. } => {
206                intersections.push(intersection.into());
207            }
208        }
209    }
210    // Sort intersections by x-coordinate to ensure correct pairing
211    // we were previously doing a full lexicographic sort, but there's no need
212    // since the scan line starts at (bounds.min().x, y_mid) and ends at (bounds.max().x, y_mid):
213    // both lines have the same y coordinate (y_mid)!
214    // Therefore sorting by x coord is enough to order the intersections along the scan line
215    intersections.sort_by(|a, b| a.x().total_cmp(&b.x()));
216
217    let mut segments = Vec::new();
218    let mut intersections_iter = intersections.iter().peekable();
219    while let (Some(start), Some(end)) = (intersections_iter.next(), intersections_iter.peek()) {
220        let length = end.x() - start.x();
221        let midpoint = Point::new((start.x() + end.x()) / two, y_mid);
222        segments.push((midpoint, length));
223    }
224    segments.sort_by(|a, b| b.1.total_cmp(&a.1));
225
226    for (midpoint, segment_length) in segments {
227        // some pairs of consecutive points traveling east-west will bound segments inside the
228        // polygon, and some outside; confirm that this is the former
229        let relation = polygon.relate(&midpoint);
230        if relation.is_intersects() {
231            return Some((
232                midpoint,
233                if relation.is_contains() {
234                    segment_length
235                } else {
236                    // if our point is on the boundary, it must be because this is a zero-area
237                    // polygon, so if we're being called from a multipolygon context, we want this
238                    // option to be down-ranked as compared to other polygons that might have
239                    // non-zero area
240                    T::zero()
241                },
242            ));
243        }
244    }
245    // if we've gotten this far with no luck, return any vertex point, if there are any
246    polygon
247        .coords_iter()
248        .next()
249        .map(|coord| (coord.into(), T::zero()))
250}
251
252impl<T> InteriorPoint for Polygon<T>
253where
254    T: GeoFloat,
255{
256    type Output = Option<Point<T>>;
257
258    fn interior_point(&self) -> Self::Output {
259        polygon_interior_point_with_segment_length(self).map(|(point, _length)| point)
260    }
261}
262
263impl<T> InteriorPoint for MultiPolygon<T>
264where
265    T: GeoFloat,
266{
267    type Output = Option<Point<T>>;
268
269    fn interior_point(&self) -> Self::Output {
270        let segments = self
271            .iter()
272            .filter_map(polygon_interior_point_with_segment_length);
273        segments
274            .min_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Less))
275            .map(|(point, _length)| point)
276    }
277}
278
279impl<T> InteriorPoint for Rect<T>
280where
281    T: GeoFloat,
282{
283    type Output = Point<T>;
284
285    fn interior_point(&self) -> Self::Output {
286        self.center().into()
287    }
288}
289
290impl<T> InteriorPoint for Point<T>
291where
292    T: GeoFloat,
293{
294    type Output = Point<T>;
295
296    fn interior_point(&self) -> Self::Output {
297        *self
298    }
299}
300
301///
302/// ```
303/// use geo::InteriorPoint;
304/// use geo::{MultiPoint, Point};
305///
306/// let empty: Vec<Point> = Vec::new();
307/// let empty_multi_points: MultiPoint<_> = empty.into();
308/// assert_eq!(empty_multi_points.interior_point(), None);
309///
310/// let points: MultiPoint<_> = vec![(5., 1.), (1., 3.), (3., 2.)].into();
311/// assert_eq!(points.interior_point(), Some(Point::new(3., 2.)));
312/// ```
313impl<T> InteriorPoint for MultiPoint<T>
314where
315    T: GeoFloat,
316{
317    type Output = Option<Point<T>>;
318
319    fn interior_point(&self) -> Self::Output {
320        if let Some(centroid) = self.centroid() {
321            self.iter()
322                .map(|pt| (pt, Euclidean.distance(pt, &centroid)))
323                .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Less))
324                .map(|(pt, _distance)| *pt)
325        } else {
326            None
327        }
328    }
329}
330
331impl<T> InteriorPoint for Geometry<T>
332where
333    T: GeoFloat,
334{
335    type Output = Option<Point<T>>;
336
337    crate::geometry_delegate_impl! {
338        fn interior_point(&self) -> Self::Output;
339    }
340}
341
342impl<T> InteriorPoint for GeometryCollection<T>
343where
344    T: GeoFloat,
345{
346    type Output = Option<Point<T>>;
347
348    fn interior_point(&self) -> Self::Output {
349        if let Some(centroid) = self.centroid() {
350            self.iter()
351                .filter_map(|geom| {
352                    geom.interior_point().map(|pt| {
353                        (
354                            pt,
355                            // maximize dimensions, minimize distance
356                            (Reverse(geom.dimensions()), Euclidean.distance(pt, centroid)),
357                        )
358                    })
359                })
360                .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Less))
361                .map(|(pt, _distance)| pt)
362        } else {
363            None
364        }
365    }
366}
367
368impl<T> InteriorPoint for Triangle<T>
369where
370    T: GeoFloat,
371{
372    type Output = Point<T>;
373
374    fn interior_point(&self) -> Self::Output {
375        self.centroid()
376    }
377}
378
379#[cfg(test)]
380mod test {
381    use super::*;
382    use crate::{
383        algorithm::{contains::Contains, intersects::Intersects},
384        coord, line_string, point, polygon,
385    };
386
387    /// small helper to create a coordinate
388    fn c<T: GeoFloat>(x: T, y: T) -> Coord<T> {
389        coord! { x: x, y: y }
390    }
391
392    /// small helper to create a point
393    fn p<T: GeoFloat>(x: T, y: T) -> Point<T> {
394        point! { x: x, y: y }
395    }
396
397    // Tests: InteriorPoint of LineString
398    #[test]
399    fn empty_linestring_test() {
400        let linestring: LineString<f32> = line_string![];
401        let interior_point = linestring.interior_point();
402        assert!(interior_point.is_none());
403    }
404    #[test]
405    fn linestring_one_point_test() {
406        let coord = coord! {
407            x: 40.02f64,
408            y: 116.34,
409        };
410        let linestring = line_string![coord];
411        let interior_point = linestring.interior_point();
412        assert_eq!(interior_point, Some(Point::from(coord)));
413    }
414    #[test]
415    fn linestring_test() {
416        let linestring = line_string![
417            (x: 1., y: 1.),
418            (x: 7., y: 1.),
419            (x: 8., y: 1.),
420            (x: 9., y: 1.),
421            (x: 10., y: 1.),
422            (x: 11., y: 1.)
423        ];
424        assert_eq!(linestring.interior_point(), Some(point!(x: 7., y: 1. )));
425    }
426    #[test]
427    fn linestring_with_repeated_point_test() {
428        let l1 = LineString::from(vec![p(1., 1.), p(1., 1.), p(1., 1.)]);
429        assert_eq!(l1.interior_point(), Some(p(1., 1.)));
430
431        let l2 = LineString::from(vec![p(2., 2.), p(2., 2.), p(2., 2.)]);
432        let mls = MultiLineString::new(vec![l1, l2]);
433        assert_eq!(mls.interior_point(), Some(p(1., 1.)));
434    }
435    // Tests: InteriorPoint of MultiLineString
436    #[test]
437    fn empty_multilinestring_test() {
438        let mls: MultiLineString = MultiLineString::new(vec![]);
439        let interior_point = mls.interior_point();
440        assert!(interior_point.is_none());
441    }
442    #[test]
443    fn multilinestring_with_empty_line_test() {
444        let mls: MultiLineString = MultiLineString::new(vec![line_string![]]);
445        let interior_point = mls.interior_point();
446        assert!(interior_point.is_none());
447    }
448    #[test]
449    fn multilinestring_length_0_test() {
450        let coord = coord! {
451            x: 40.02f64,
452            y: 116.34,
453        };
454        let mls: MultiLineString = MultiLineString::new(vec![
455            line_string![coord],
456            line_string![coord],
457            line_string![coord],
458        ]);
459        assert_relative_eq!(mls.interior_point().unwrap(), Point::from(coord));
460    }
461    #[test]
462    fn multilinestring_one_line_test() {
463        let linestring = line_string![
464            (x: 1., y: 1.),
465            (x: 7., y: 1.),
466            (x: 8., y: 1.),
467            (x: 9., y: 1.),
468            (x: 10., y: 1.),
469            (x: 11., y: 1.)
470        ];
471        let mls: MultiLineString = MultiLineString::new(vec![linestring]);
472        assert_relative_eq!(mls.interior_point().unwrap(), point! { x: 7., y: 1. });
473    }
474    #[test]
475    fn multilinestring_test() {
476        let v1 = line_string![(x: 0.0, y: 0.0), (x: 1.0, y: 10.0)];
477        let v2 = line_string![(x: 1.0, y: 10.0), (x: 2.0, y: 0.0), (x: 3.0, y: 1.0)];
478        let v3 = line_string![(x: -12.0, y: -100.0), (x: 7.0, y: 8.0)];
479        let mls = MultiLineString::new(vec![v1, v2, v3]);
480        assert_eq!(mls.interior_point().unwrap(), point![x: 0., y: 0.]);
481    }
482    // Tests: InteriorPoint of Polygon
483    #[test]
484    fn empty_polygon_test() {
485        let poly: Polygon<f32> = polygon![];
486        assert!(poly.interior_point().is_none());
487    }
488    #[test]
489    fn polygon_one_point_test() {
490        let p = point![ x: 2., y: 1. ];
491        let v = Vec::new();
492        let linestring = line_string![p.0];
493        let poly = Polygon::new(linestring, v);
494        assert_relative_eq!(poly.interior_point().unwrap(), p);
495    }
496
497    #[test]
498    fn polygon_test() {
499        let poly = polygon![
500            (x: 0., y: 0.),
501            (x: 2., y: 0.),
502            (x: 2., y: 2.),
503            (x: 0., y: 2.),
504            (x: 0., y: 0.)
505        ];
506        assert_relative_eq!(poly.interior_point().unwrap(), point![x:1., y:1.]);
507    }
508    #[test]
509    fn polygon_hole_test() {
510        // hexagon
511        let ls1 = LineString::from(vec![
512            (5.0, 1.0),
513            (4.0, 2.0),
514            (4.0, 3.0),
515            (5.0, 4.0),
516            (6.0, 4.0),
517            (7.0, 3.0),
518            (7.0, 2.0),
519            (6.0, 1.0),
520            (5.0, 1.0),
521        ]);
522
523        let ls2 = LineString::from(vec![(5.0, 1.3), (5.5, 2.0), (6.0, 1.3), (5.0, 1.3)]);
524
525        let ls3 = LineString::from(vec![(5., 2.3), (5.5, 3.0), (6., 2.3), (5., 2.3)]);
526
527        let p1 = Polygon::new(ls1, vec![ls2, ls3]);
528        let interior_point = p1.interior_point().unwrap();
529        assert!(p1.contains(&interior_point));
530        assert_relative_eq!(interior_point, point!(x: 4.571428571428571, y: 2.5));
531    }
532    #[test]
533    fn flat_polygon_test() {
534        let poly = Polygon::new(
535            LineString::from(vec![p(0., 1.), p(1., 1.), p(0., 1.)]),
536            vec![],
537        );
538        assert_eq!(poly.interior_point(), Some(p(0.5, 1.)));
539    }
540    #[test]
541    fn diagonal_flat_polygon_test() {
542        // the regular intersection approach happens to not produce a point that intersects the
543        // polygon given these particular start values, so this tests falling back to a vertex
544        let start: Coord<f64> = Coord {
545            x: 0.632690318327692,
546            y: 0.08104532928154995,
547        };
548        let end: Coord<f64> = Coord {
549            x: 0.4685039949468325,
550            y: 0.31750332644855794,
551        };
552        let poly = Polygon::new(LineString::new(vec![start, end, start]), vec![]);
553
554        assert_eq!(poly.interior_point(), Some(start.into()));
555    }
556    #[test]
557    fn polygon_vertex_on_median() {
558        let poly = Polygon::new(
559            LineString::from(vec![
560                (0.5, 1.0),
561                (0.5, 0.5),
562                (0.0, 0.5),
563                (0.0, 0.0),
564                (1.0, 0.0),
565                (1.0, 1.0),
566                (0.5, 1.0),
567            ]),
568            vec![],
569        );
570        let interior_point = poly.interior_point().unwrap();
571        assert_eq!(&interior_point, &p(0.75, 0.75));
572    }
573    #[test]
574    fn multi_poly_with_flat_polygon_test() {
575        let poly = Polygon::new(
576            LineString::from(vec![p(0., 0.), p(1., 0.), p(0., 0.)]),
577            vec![],
578        );
579        let multipoly = MultiPolygon::new(vec![poly]);
580        assert_eq!(multipoly.interior_point(), Some(p(0.5, 0.)));
581    }
582    #[test]
583    fn multi_poly_with_multiple_flat_polygon_test() {
584        let p1 = Polygon::new(
585            LineString::from(vec![p(1., 1.), p(1., 3.), p(1., 1.)]),
586            vec![],
587        );
588        let p2 = Polygon::new(
589            LineString::from(vec![p(2., 2.), p(6., 2.), p(2., 2.)]),
590            vec![],
591        );
592        let multipoly = MultiPolygon::new(vec![p1, p2]);
593        let interior = multipoly.interior_point().unwrap();
594        assert_eq!(&interior, &p(1., 2.));
595        assert!(multipoly.intersects(&interior));
596    }
597    #[test]
598    fn multi_poly_with_only_points_test() {
599        let p1 = Polygon::new(
600            LineString::from(vec![p(1., 1.), p(1., 1.), p(1., 1.)]),
601            vec![],
602        );
603        assert_eq!(p1.interior_point(), Some(p(1., 1.)));
604        let p2 = Polygon::new(
605            LineString::from(vec![p(2., 2.), p(2., 2.), p(2., 2.)]),
606            vec![],
607        );
608        let multipoly = MultiPolygon::new(vec![p1, p2]);
609        let interior_point = multipoly.interior_point().unwrap();
610        assert_eq!(multipoly.interior_point(), Some(p(1.0, 1.0)));
611        assert!(multipoly.intersects(&interior_point));
612    }
613    #[test]
614    fn multi_poly_with_one_ring_and_one_real_poly() {
615        // if the multipolygon is composed of a 'normal' polygon (with an area not null)
616        // and a ring (a polygon with a null area)
617        // the interior_point of the multipolygon is the interior_point of the 'normal' polygon
618        let normal = Polygon::new(
619            LineString::from(vec![p(1., 1.), p(1., 3.), p(3., 1.), p(1., 1.)]),
620            vec![],
621        );
622        let flat = Polygon::new(
623            LineString::from(vec![p(2., 2.), p(6., 2.), p(2., 2.)]),
624            vec![],
625        );
626        let multipoly = MultiPolygon::new(vec![normal.clone(), flat]);
627        assert_eq!(multipoly.interior_point(), normal.interior_point());
628    }
629    #[test]
630    fn polygon_flat_interior_test() {
631        let poly = Polygon::new(
632            LineString::from(vec![p(0., 0.), p(0., 1.), p(1., 1.), p(1., 0.), p(0., 0.)]),
633            vec![LineString::from(vec![
634                p(0.1, 0.1),
635                p(0.1, 0.9),
636                p(0.1, 0.1),
637            ])],
638        );
639        assert_eq!(poly.interior_point(), Some(p(0.55, 0.5)));
640    }
641    #[test]
642    fn empty_interior_polygon_test() {
643        let poly = Polygon::new(
644            LineString::from(vec![p(0., 0.), p(0., 1.), p(1., 1.), p(1., 0.), p(0., 0.)]),
645            vec![LineString::new(vec![])],
646        );
647        assert_eq!(poly.interior_point(), Some(p(0.5, 0.5)));
648    }
649    #[test]
650    fn polygon_ring_test() {
651        let square = LineString::from(vec![p(0., 0.), p(0., 1.), p(1., 1.), p(1., 0.), p(0., 0.)]);
652        let poly = Polygon::new(square.clone(), vec![square]);
653        let interior_point = poly.interior_point().unwrap();
654        assert_eq!(&interior_point, &p(0.0, 0.5));
655        assert!(poly.intersects(&interior_point));
656        assert!(!poly.contains(&interior_point)); // there's no interior so won't be "contains"
657    }
658    #[test]
659    fn polygon_cell_test() {
660        // test the interior_point of polygon with a null area
661        // this one a polygon with 2 interior polygon that makes a partition of the exterior
662        let square = LineString::from(vec![p(0., 0.), p(0., 2.), p(2., 2.), p(2., 0.), p(0., 0.)]);
663        let bottom = LineString::from(vec![p(0., 0.), p(2., 0.), p(2., 1.), p(0., 1.), p(0., 0.)]);
664        let top = LineString::from(vec![p(0., 1.), p(2., 1.), p(2., 2.), p(0., 2.), p(0., 1.)]);
665        let poly = Polygon::new(square, vec![top, bottom]);
666        let interior_point = poly.interior_point().unwrap();
667        assert!(poly.intersects(&interior_point));
668        assert!(!poly.contains(&interior_point));
669    }
670    // Tests: InteriorPoint of MultiPolygon
671    #[test]
672    fn empty_multipolygon_polygon_test() {
673        assert!(
674            MultiPolygon::<f64>::new(Vec::new())
675                .interior_point()
676                .is_none()
677        );
678    }
679
680    #[test]
681    fn multipolygon_one_polygon_test() {
682        let linestring =
683            LineString::from(vec![p(0., 0.), p(2., 0.), p(2., 2.), p(0., 2.), p(0., 0.)]);
684        let poly = Polygon::new(linestring, Vec::new());
685        assert_eq!(
686            MultiPolygon::new(vec![poly]).interior_point(),
687            Some(p(1., 1.))
688        );
689    }
690    #[test]
691    fn multipolygon_two_polygons_test() {
692        let linestring =
693            LineString::from(vec![p(2., 1.), p(5., 1.), p(5., 3.), p(2., 3.), p(2., 1.)]);
694        let poly1 = Polygon::new(linestring, Vec::new());
695        let linestring =
696            LineString::from(vec![p(7., 1.), p(8., 1.), p(8., 2.), p(7., 2.), p(7., 1.)]);
697        let poly2 = Polygon::new(linestring, Vec::new());
698        let multipoly = MultiPolygon::new(vec![poly1, poly2]);
699        let interior_point = multipoly.interior_point().unwrap();
700        assert_relative_eq!(interior_point, point![x: 3.5, y: 2.]);
701        assert!(multipoly.contains(&interior_point));
702    }
703    #[test]
704    fn multipolygon_two_polygons_of_opposite_clockwise_test() {
705        let linestring = LineString::from(vec![(0., 0.), (2., 0.), (2., 2.), (0., 2.), (0., 0.)]);
706        let poly1 = Polygon::new(linestring, Vec::new());
707        let linestring = LineString::from(vec![(0., 0.), (-2., 0.), (-2., 2.), (0., 2.), (0., 0.)]);
708        let poly2 = Polygon::new(linestring, Vec::new());
709        let multipoly = MultiPolygon::new(vec![poly1, poly2]);
710        let interior_point = multipoly.interior_point().unwrap();
711        assert_relative_eq!(interior_point, point![x: 1.0, y: 1.0]);
712        assert!(multipoly.contains(&interior_point));
713    }
714    #[test]
715    fn bounding_rect_test() {
716        let bounding_rect = Rect::new(coord! { x: 0., y: 50. }, coord! { x: 4., y: 100. });
717        let point = point![x: 2., y: 75.];
718        assert_eq!(point, bounding_rect.interior_point());
719    }
720    #[test]
721    fn line_test() {
722        let line1 = Line::new(c(0., 1.), c(1., 3.));
723        assert_eq!(line1.interior_point(), point![x: 0., y: 1.]);
724    }
725    #[test]
726    fn collection_test() {
727        let p0 = point!(x: 0.0, y: 0.0);
728        let p1 = point!(x: 2.0, y: 0.0);
729        let p2 = point!(x: 2.0, y: 2.0);
730        let p3 = point!(x: 0.0, y: 2.0);
731
732        let multi_point = MultiPoint::new(vec![p0, p1, p2, p3]);
733        assert_eq!(
734            multi_point.interior_point().unwrap(),
735            point!(x: 0.0, y: 0.0)
736        );
737    }
738    #[test]
739    fn mixed_collection_test() {
740        let linestring =
741            LineString::from(vec![p(0., 1.), p(0., 0.), p(1., 0.), p(1., 1.), p(0., 1.)]);
742        let poly1 = Polygon::new(linestring, Vec::new());
743        let linestring = LineString::from(vec![
744            p(10., 1.),
745            p(10., 0.),
746            p(11., 0.),
747            p(11., 1.),
748            p(10., 1.),
749        ]);
750        let poly2 = Polygon::new(linestring, Vec::new());
751
752        let high_dimension_shapes = GeometryCollection::new_from(vec![poly1.into(), poly2.into()]);
753
754        let mut mixed_shapes = high_dimension_shapes.clone();
755        mixed_shapes.0.push(Point::new(5_f64, 0_f64).into());
756        mixed_shapes.0.push(Point::new(5_f64, 1_f64).into());
757
758        // lower-dimensional shapes shouldn't affect interior point if higher-dimensional shapes
759        // are present, even if the low-d ones are closer to the centroid
760        assert_eq!(
761            high_dimension_shapes.interior_point().unwrap(),
762            mixed_shapes.interior_point().unwrap()
763        )
764    }
765    #[test]
766    fn triangles() {
767        // boring triangle
768        assert_eq!(
769            Triangle::new(c(0., 0.), c(3., 0.), c(1.5, 3.)).interior_point(),
770            point!(x: 1.5, y: 1.0)
771        );
772
773        // flat triangle
774        assert_eq!(
775            Triangle::new(c(0., 0.), c(3., 0.), c(1., 0.)).interior_point(),
776            point!(x: 1.5, y: 0.0)
777        );
778
779        // flat triangle that's not axis-aligned
780        assert_eq!(
781            Triangle::new(c(0., 0.), c(3., 3.), c(1., 1.)).interior_point(),
782            point!(x: 1.5, y: 1.5)
783        );
784
785        // triangle with some repeated points
786        assert_eq!(
787            Triangle::new(c(0., 0.), c(0., 0.), c(1., 0.)).interior_point(),
788            point!(x: 0.5, y: 0.0)
789        );
790
791        // triangle with all repeated points
792        assert_eq!(
793            Triangle::new(c(0., 0.5), c(0., 0.5), c(0., 0.5)).interior_point(),
794            point!(x: 0., y: 0.5)
795        )
796    }
797
798    #[test]
799    fn degenerate_triangle_like_ring() {
800        let triangle = Triangle::new(c(0., 0.), c(1., 1.), c(2., 2.));
801        let poly: Polygon<_> = triangle.into();
802
803        let line = Line::new(c(0., 1.), c(1., 3.));
804
805        let g1 = GeometryCollection::new_from(vec![triangle.into(), line.into()]);
806        let g2 = GeometryCollection::new_from(vec![poly.into(), line.into()]);
807
808        let pt1 = g1.interior_point().unwrap();
809        let pt2 = g2.interior_point().unwrap();
810        // triangle and polygon have differing interior-point implementations, so we won't get the
811        // same point with both approaches, but both should produce points that are interior to
812        // either representation
813        assert!(g1.intersects(&pt1));
814        assert!(g1.intersects(&pt2));
815        assert!(g2.intersects(&pt1));
816        assert!(g2.intersects(&pt2));
817    }
818
819    #[test]
820    fn degenerate_rect_like_ring() {
821        let rect = Rect::new(c(0., 0.), c(0., 4.));
822        let poly: Polygon<_> = rect.into();
823
824        let line = Line::new(c(0., 1.), c(1., 3.));
825
826        let g1 = GeometryCollection::new_from(vec![rect.into(), line.into()]);
827        let g2 = GeometryCollection::new_from(vec![poly.into(), line.into()]);
828        assert_eq!(g1.interior_point(), g2.interior_point());
829    }
830
831    #[test]
832    fn rectangles() {
833        // boring rect
834        assert_eq!(
835            Rect::new(c(0., 0.), c(4., 4.)).interior_point(),
836            point!(x: 2.0, y: 2.0)
837        );
838
839        // flat rect
840        assert_eq!(
841            Rect::new(c(0., 0.), c(4., 0.)).interior_point(),
842            point!(x: 2.0, y: 0.0)
843        );
844
845        // rect with all repeated points
846        assert_eq!(
847            Rect::new(c(4., 4.), c(4., 4.)).interior_point(),
848            point!(x: 4., y: 4.)
849        );
850
851        // collection with rect
852        let collection = GeometryCollection::new_from(vec![
853            p(0., 0.).into(),
854            p(6., 0.).into(),
855            p(6., 6.).into(),
856        ]);
857        // check collection
858        assert_eq!(collection.interior_point().unwrap(), point!(x: 6., y: 0.));
859    }
860}