geo/algorithm/
coordinate_position.rs

1use crate::geometry::*;
2use crate::intersects::point_in_rect;
3use crate::kernels::*;
4use crate::{BoundingRect, HasDimensions, Intersects};
5use crate::{GeoNum, GeometryCow};
6
7/// The position of a `Coord` relative to a `Geometry`
8#[derive(PartialEq, Eq, Clone, Copy, Debug)]
9pub enum CoordPos {
10    OnBoundary,
11    Inside,
12    Outside,
13}
14
15/// Determine whether a `Coord` lies inside, outside, or on the boundary of a geometry.
16///
17/// # Examples
18///
19/// ```rust
20/// use geo::{polygon, coord};
21/// use geo::coordinate_position::{CoordinatePosition, CoordPos};
22///
23/// let square_poly = polygon![(x: 0.0, y: 0.0), (x: 2.0, y: 0.0), (x: 2.0, y: 2.0), (x: 0.0, y: 2.0), (x: 0.0, y: 0.0)];
24///
25/// let inside_coord = coord! { x: 1.0, y: 1.0 };
26/// assert_eq!(square_poly.coordinate_position(&inside_coord), CoordPos::Inside);
27///
28/// let boundary_coord = coord! { x: 0.0, y: 1.0 };
29/// assert_eq!(square_poly.coordinate_position(&boundary_coord), CoordPos::OnBoundary);
30///
31/// let outside_coord = coord! { x: 5.0, y: 5.0 };
32/// assert_eq!(square_poly.coordinate_position(&outside_coord), CoordPos::Outside);
33/// ```
34pub trait CoordinatePosition {
35    type Scalar: GeoNum;
36    fn coordinate_position(&self, coord: &Coord<Self::Scalar>) -> CoordPos {
37        let mut is_inside = false;
38        let mut boundary_count = 0;
39
40        self.calculate_coordinate_position(coord, &mut is_inside, &mut boundary_count);
41
42        // “The boundary of an arbitrary collection of geometries whose interiors are disjoint
43        // consists of geometries drawn from the boundaries of the element geometries by
44        // application of the ‘mod 2’ union rule”
45        //
46        // ― OpenGIS Simple Feature Access § 6.1.15.1
47        if boundary_count % 2 == 1 {
48            CoordPos::OnBoundary
49        } else if is_inside {
50            CoordPos::Inside
51        } else {
52            CoordPos::Outside
53        }
54    }
55
56    // impls of this trait must:
57    //  1. set `is_inside = true` if `coord` is contained within the Interior of any component.
58    //  2. increment `boundary_count` for each component whose Boundary contains `coord`.
59    fn calculate_coordinate_position(
60        &self,
61        coord: &Coord<Self::Scalar>,
62        is_inside: &mut bool,
63        boundary_count: &mut usize,
64    );
65}
66
67impl<T> CoordinatePosition for Coord<T>
68where
69    T: GeoNum,
70{
71    type Scalar = T;
72    fn calculate_coordinate_position(
73        &self,
74        coord: &Coord<T>,
75        is_inside: &mut bool,
76        _boundary_count: &mut usize,
77    ) {
78        if self == coord {
79            *is_inside = true;
80        }
81    }
82}
83
84impl<T> CoordinatePosition for Point<T>
85where
86    T: GeoNum,
87{
88    type Scalar = T;
89    fn calculate_coordinate_position(
90        &self,
91        coord: &Coord<T>,
92        is_inside: &mut bool,
93        _boundary_count: &mut usize,
94    ) {
95        if &self.0 == coord {
96            *is_inside = true;
97        }
98    }
99}
100
101impl<T> CoordinatePosition for Line<T>
102where
103    T: GeoNum,
104{
105    type Scalar = T;
106    fn calculate_coordinate_position(
107        &self,
108        coord: &Coord<T>,
109        is_inside: &mut bool,
110        boundary_count: &mut usize,
111    ) {
112        // degenerate line is a point
113        if self.start == self.end {
114            self.start
115                .calculate_coordinate_position(coord, is_inside, boundary_count);
116            return;
117        }
118
119        if coord == &self.start || coord == &self.end {
120            *boundary_count += 1;
121        } else if self.intersects(coord) {
122            *is_inside = true;
123        }
124    }
125}
126
127impl<T> CoordinatePosition for LineString<T>
128where
129    T: GeoNum,
130{
131    type Scalar = T;
132    fn calculate_coordinate_position(
133        &self,
134        coord: &Coord<T>,
135        is_inside: &mut bool,
136        boundary_count: &mut usize,
137    ) {
138        if self.0.len() < 2 {
139            debug_assert!(false, "invalid line string with less than 2 coords");
140            return;
141        }
142
143        if self.0.len() == 2 {
144            // line string with two coords is just a line
145            Line::new(self.0[0], self.0[1]).calculate_coordinate_position(
146                coord,
147                is_inside,
148                boundary_count,
149            );
150            return;
151        }
152
153        // optimization: return early if there's no chance of an intersection
154        // since self.0 is non-empty, safe to `unwrap`
155        if !self.bounding_rect().unwrap().intersects(coord) {
156            return;
157        }
158
159        // A closed linestring has no boundary, per SFS
160        if !self.is_closed() {
161            // since self.0 is non-empty, safe to `unwrap`
162            if coord == self.0.first().unwrap() || coord == self.0.last().unwrap() {
163                *boundary_count += 1;
164                return;
165            }
166        }
167
168        if self.intersects(coord) {
169            // We've already checked for "Boundary" condition, so if there's an intersection at
170            // this point, coord must be on the interior
171            *is_inside = true
172        }
173    }
174}
175
176impl<T> CoordinatePosition for Triangle<T>
177where
178    T: GeoNum,
179{
180    type Scalar = T;
181    fn calculate_coordinate_position(
182        &self,
183        coord: &Coord<T>,
184        is_inside: &mut bool,
185        boundary_count: &mut usize,
186    ) {
187        // PERF TODO: I'm sure there's a better way to calculate than converting to a polygon
188        self.to_polygon()
189            .calculate_coordinate_position(coord, is_inside, boundary_count);
190    }
191}
192
193impl<T> CoordinatePosition for Rect<T>
194where
195    T: GeoNum,
196{
197    type Scalar = T;
198    fn calculate_coordinate_position(
199        &self,
200        coord: &Coord<T>,
201        is_inside: &mut bool,
202        boundary_count: &mut usize,
203    ) {
204        // PERF TODO: I'm sure there's a better way to calculate than converting to a polygon
205        self.to_polygon()
206            .calculate_coordinate_position(coord, is_inside, boundary_count);
207    }
208}
209
210impl<T> CoordinatePosition for MultiPoint<T>
211where
212    T: GeoNum,
213{
214    type Scalar = T;
215    fn calculate_coordinate_position(
216        &self,
217        coord: &Coord<T>,
218        is_inside: &mut bool,
219        _boundary_count: &mut usize,
220    ) {
221        if self.0.iter().any(|p| &p.0 == coord) {
222            *is_inside = true;
223        }
224    }
225}
226
227impl<T> CoordinatePosition for Polygon<T>
228where
229    T: GeoNum,
230{
231    type Scalar = T;
232    fn calculate_coordinate_position(
233        &self,
234        coord: &Coord<T>,
235        is_inside: &mut bool,
236        boundary_count: &mut usize,
237    ) {
238        if self.is_empty() {
239            return;
240        }
241
242        // Ok to `unwrap` since we know `self` is non-empty, so bounding-rect is non-null
243        if !self.bounding_rect().unwrap().intersects(coord) {
244            return;
245        }
246
247        match coord_pos_relative_to_ring(*coord, self.exterior()) {
248            CoordPos::Outside => {}
249            CoordPos::OnBoundary => {
250                *boundary_count += 1;
251            }
252            CoordPos::Inside => {
253                for hole in self.interiors() {
254                    match coord_pos_relative_to_ring(*coord, hole) {
255                        CoordPos::Outside => {}
256                        CoordPos::OnBoundary => {
257                            *boundary_count += 1;
258                            return;
259                        }
260                        CoordPos::Inside => {
261                            return;
262                        }
263                    }
264                }
265                // the coord is *outside* the interior holes, so it's *inside* the polygon
266                *is_inside = true;
267            }
268        }
269    }
270}
271
272impl<T> CoordinatePosition for MultiLineString<T>
273where
274    T: GeoNum,
275{
276    type Scalar = T;
277    fn calculate_coordinate_position(
278        &self,
279        coord: &Coord<T>,
280        is_inside: &mut bool,
281        boundary_count: &mut usize,
282    ) {
283        for line_string in &self.0 {
284            line_string.calculate_coordinate_position(coord, is_inside, boundary_count);
285        }
286    }
287}
288
289impl<T> CoordinatePosition for MultiPolygon<T>
290where
291    T: GeoNum,
292{
293    type Scalar = T;
294    fn calculate_coordinate_position(
295        &self,
296        coord: &Coord<T>,
297        is_inside: &mut bool,
298        boundary_count: &mut usize,
299    ) {
300        for polygon in &self.0 {
301            polygon.calculate_coordinate_position(coord, is_inside, boundary_count);
302        }
303    }
304}
305
306impl<T> CoordinatePosition for GeometryCollection<T>
307where
308    T: GeoNum,
309{
310    type Scalar = T;
311    fn calculate_coordinate_position(
312        &self,
313        coord: &Coord<T>,
314        is_inside: &mut bool,
315        boundary_count: &mut usize,
316    ) {
317        for geometry in self {
318            geometry.calculate_coordinate_position(coord, is_inside, boundary_count);
319        }
320    }
321}
322
323impl<T> CoordinatePosition for Geometry<T>
324where
325    T: GeoNum,
326{
327    type Scalar = T;
328    crate::geometry_delegate_impl! {
329        fn calculate_coordinate_position(
330            &self,
331            coord: &Coord<T>,
332            is_inside: &mut bool,
333            boundary_count: &mut usize) -> ();
334    }
335}
336
337impl<'a, T: GeoNum> CoordinatePosition for GeometryCow<'a, T> {
338    type Scalar = T;
339    crate::geometry_cow_delegate_impl! {
340        fn calculate_coordinate_position(
341            &self,
342            coord: &Coord<T>,
343            is_inside: &mut bool,
344            boundary_count: &mut usize) -> ();
345    }
346}
347
348/// Calculate the position of a `Coord` relative to a
349/// closed `LineString`.
350pub fn coord_pos_relative_to_ring<T>(coord: Coord<T>, linestring: &LineString<T>) -> CoordPos
351where
352    T: GeoNum,
353{
354    debug_assert!(linestring.is_closed());
355
356    // LineString without points
357    if linestring.0.is_empty() {
358        return CoordPos::Outside;
359    }
360    if linestring.0.len() == 1 {
361        // If LineString has one point, it will not generate
362        // any lines.  So, we handle this edge case separately.
363        return if coord == linestring.0[0] {
364            CoordPos::OnBoundary
365        } else {
366            CoordPos::Outside
367        };
368    }
369
370    // Use winding number algorithm with on boundary short-cicuit
371    // See: https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm
372    let mut winding_number = 0;
373    for line in linestring.lines() {
374        if line.start.y <= coord.y {
375            let o = T::Ker::orient2d(line.start, line.end, coord);
376            if o == Orientation::Collinear && point_in_rect(coord, line.start, line.end) {
377                return CoordPos::OnBoundary;
378            }
379            if line.end.y > coord.y && o == Orientation::CounterClockwise {
380                winding_number += 1;
381            }
382        } else if line.end.y <= coord.y {
383            let o = T::Ker::orient2d(line.start, line.end, coord);
384            if o == Orientation::Collinear && point_in_rect(coord, line.start, line.end) {
385                return CoordPos::OnBoundary;
386            }
387            if o == Orientation::Clockwise {
388                winding_number -= 1;
389            }
390        }
391    }
392    if winding_number == 0 {
393        CoordPos::Outside
394    } else {
395        CoordPos::Inside
396    }
397}
398
399#[cfg(test)]
400mod test {
401    use geo_types::coord;
402
403    use super::*;
404    use crate::{line_string, point, polygon};
405
406    #[test]
407    fn test_empty_poly() {
408        let square_poly: Polygon<f64> = Polygon::new(LineString::new(vec![]), vec![]);
409        assert_eq!(
410            square_poly.coordinate_position(&Coord::zero()),
411            CoordPos::Outside
412        );
413    }
414
415    #[test]
416    fn test_simple_poly() {
417        let square_poly = polygon![(x: 0.0, y: 0.0), (x: 2.0, y: 0.0), (x: 2.0, y: 2.0), (x: 0.0, y: 2.0), (x: 0.0, y: 0.0)];
418
419        let inside_coord = coord! { x: 1.0, y: 1.0 };
420        assert_eq!(
421            square_poly.coordinate_position(&inside_coord),
422            CoordPos::Inside
423        );
424
425        let vertex_coord = coord! { x: 0.0, y: 0.0 };
426        assert_eq!(
427            square_poly.coordinate_position(&vertex_coord),
428            CoordPos::OnBoundary
429        );
430
431        let boundary_coord = coord! { x: 0.0, y: 1.0 };
432        assert_eq!(
433            square_poly.coordinate_position(&boundary_coord),
434            CoordPos::OnBoundary
435        );
436
437        let outside_coord = coord! { x: 5.0, y: 5.0 };
438        assert_eq!(
439            square_poly.coordinate_position(&outside_coord),
440            CoordPos::Outside
441        );
442    }
443
444    #[test]
445    fn test_poly_interior() {
446        let poly = polygon![
447            exterior: [
448                (x: 11., y: 11.),
449                (x: 20., y: 11.),
450                (x: 20., y: 20.),
451                (x: 11., y: 20.),
452                (x: 11., y: 11.),
453            ],
454            interiors: [
455                [
456                    (x: 13., y: 13.),
457                    (x: 13., y: 17.),
458                    (x: 17., y: 17.),
459                    (x: 17., y: 13.),
460                    (x: 13., y: 13.),
461                ]
462            ],
463        ];
464
465        let inside_hole = coord! { x: 14.0, y: 14.0 };
466        assert_eq!(poly.coordinate_position(&inside_hole), CoordPos::Outside);
467
468        let outside_poly = coord! { x: 30.0, y: 30.0 };
469        assert_eq!(poly.coordinate_position(&outside_poly), CoordPos::Outside);
470
471        let on_outside_border = coord! { x: 20.0, y: 15.0 };
472        assert_eq!(
473            poly.coordinate_position(&on_outside_border),
474            CoordPos::OnBoundary
475        );
476
477        let on_inside_border = coord! { x: 13.0, y: 15.0 };
478        assert_eq!(
479            poly.coordinate_position(&on_inside_border),
480            CoordPos::OnBoundary
481        );
482
483        let inside_coord = coord! { x: 12.0, y: 12.0 };
484        assert_eq!(poly.coordinate_position(&inside_coord), CoordPos::Inside);
485    }
486
487    #[test]
488    fn test_simple_line() {
489        use crate::point;
490        let line = Line::new(point![x: 0.0, y: 0.0], point![x: 10.0, y: 10.0]);
491
492        let start = coord! { x: 0.0, y: 0.0 };
493        assert_eq!(line.coordinate_position(&start), CoordPos::OnBoundary);
494
495        let end = coord! { x: 10.0, y: 10.0 };
496        assert_eq!(line.coordinate_position(&end), CoordPos::OnBoundary);
497
498        let interior = coord! { x: 5.0, y: 5.0 };
499        assert_eq!(line.coordinate_position(&interior), CoordPos::Inside);
500
501        let outside = coord! { x: 6.0, y: 5.0 };
502        assert_eq!(line.coordinate_position(&outside), CoordPos::Outside);
503    }
504
505    #[test]
506    fn test_degenerate_line() {
507        let line = Line::new(point![x: 0.0, y: 0.0], point![x: 0.0, y: 0.0]);
508
509        let start = coord! { x: 0.0, y: 0.0 };
510        assert_eq!(line.coordinate_position(&start), CoordPos::Inside);
511
512        let outside = coord! { x: 10.0, y: 10.0 };
513        assert_eq!(line.coordinate_position(&outside), CoordPos::Outside);
514    }
515
516    #[test]
517    fn test_point() {
518        let p1 = point![x: 2.0, y: 0.0];
519
520        let c1 = coord! { x: 2.0, y: 0.0 };
521        let c2 = coord! { x: 3.0, y: 3.0 };
522
523        assert_eq!(p1.coordinate_position(&c1), CoordPos::Inside);
524        assert_eq!(p1.coordinate_position(&c2), CoordPos::Outside);
525
526        assert_eq!(c1.coordinate_position(&c1), CoordPos::Inside);
527        assert_eq!(c1.coordinate_position(&c2), CoordPos::Outside);
528    }
529
530    #[test]
531    fn test_simple_line_string() {
532        let line_string =
533            line_string![(x: 0.0, y: 0.0), (x: 1.0, y: 1.0), (x: 2.0, y: 0.0), (x: 3.0, y: 0.0)];
534
535        let start = Coord::zero();
536        assert_eq!(
537            line_string.coordinate_position(&start),
538            CoordPos::OnBoundary
539        );
540
541        let midpoint = coord! { x: 0.5, y: 0.5 };
542        assert_eq!(line_string.coordinate_position(&midpoint), CoordPos::Inside);
543
544        let vertex = coord! { x: 2.0, y: 0.0 };
545        assert_eq!(line_string.coordinate_position(&vertex), CoordPos::Inside);
546
547        let end = coord! { x: 3.0, y: 0.0 };
548        assert_eq!(line_string.coordinate_position(&end), CoordPos::OnBoundary);
549
550        let outside = coord! { x: 3.0, y: 1.0 };
551        assert_eq!(line_string.coordinate_position(&outside), CoordPos::Outside);
552    }
553
554    #[test]
555    fn test_degenerate_line_strings() {
556        let line_string = line_string![(x: 0.0, y: 0.0), (x: 0.0, y: 0.0)];
557
558        let start = Coord::zero();
559        assert_eq!(line_string.coordinate_position(&start), CoordPos::Inside);
560
561        let line_string = line_string![(x: 0.0, y: 0.0), (x: 2.0, y: 0.0)];
562
563        let start = Coord::zero();
564        assert_eq!(
565            line_string.coordinate_position(&start),
566            CoordPos::OnBoundary
567        );
568    }
569
570    #[test]
571    fn test_closed_line_string() {
572        let line_string = line_string![(x: 0.0, y: 0.0), (x: 1.0, y: 1.0), (x: 2.0, y: 0.0), (x: 3.0, y: 2.0), (x: 0.0, y: 2.0), (x: 0.0, y: 0.0)];
573
574        // sanity check
575        assert!(line_string.is_closed());
576
577        // closed line strings have no boundary
578        let start = Coord::zero();
579        assert_eq!(line_string.coordinate_position(&start), CoordPos::Inside);
580
581        let midpoint = coord! { x: 0.5, y: 0.5 };
582        assert_eq!(line_string.coordinate_position(&midpoint), CoordPos::Inside);
583
584        let outside = coord! { x: 3.0, y: 1.0 };
585        assert_eq!(line_string.coordinate_position(&outside), CoordPos::Outside);
586    }
587
588    #[test]
589    fn test_boundary_rule() {
590        let multi_line_string = MultiLineString::new(vec![
591            // first two lines have same start point but different end point
592            line_string![(x: 0.0, y: 0.0), (x: 1.0, y: 1.0)],
593            line_string![(x: 0.0, y: 0.0), (x: -1.0, y: -1.0)],
594            // third line has its own start point, but it's end touches the middle of first line
595            line_string![(x: 0.0, y: 1.0), (x: 0.5, y: 0.5)],
596            // fourth and fifth have independent start points, but both end at the middle of the
597            // second line
598            line_string![(x: 0.0, y: -1.0), (x: -0.5, y: -0.5)],
599            line_string![(x: 0.0, y: -2.0), (x: -0.5, y: -0.5)],
600        ]);
601
602        let outside_of_all = coord! { x: 123.0, y: 123.0 };
603        assert_eq!(
604            multi_line_string.coordinate_position(&outside_of_all),
605            CoordPos::Outside
606        );
607
608        let end_of_one_line = coord! { x: -1.0, y: -1.0 };
609        assert_eq!(
610            multi_line_string.coordinate_position(&end_of_one_line),
611            CoordPos::OnBoundary
612        );
613
614        // in boundary of first and second, so considered *not* in the boundary by mod 2 rule
615        let shared_start = Coord::zero();
616        assert_eq!(
617            multi_line_string.coordinate_position(&shared_start),
618            CoordPos::Outside
619        );
620
621        // *in* the first line, on the boundary of the third line
622        let one_end_plus_midpoint = coord! { x: 0.5, y: 0.5 };
623        assert_eq!(
624            multi_line_string.coordinate_position(&one_end_plus_midpoint),
625            CoordPos::OnBoundary
626        );
627
628        // *in* the first line, on the *boundary* of the fourth and fifth line
629        let two_ends_plus_midpoint = coord! { x: -0.5, y: -0.5 };
630        assert_eq!(
631            multi_line_string.coordinate_position(&two_ends_plus_midpoint),
632            CoordPos::Inside
633        );
634    }
635
636    #[test]
637    fn test_rect() {
638        let rect = Rect::new((0.0, 0.0), (10.0, 10.0));
639        assert_eq!(
640            rect.coordinate_position(&coord! { x: 5.0, y: 5.0 }),
641            CoordPos::Inside
642        );
643        assert_eq!(
644            rect.coordinate_position(&coord! { x: 0.0, y: 5.0 }),
645            CoordPos::OnBoundary
646        );
647        assert_eq!(
648            rect.coordinate_position(&coord! { x: 15.0, y: 15.0 }),
649            CoordPos::Outside
650        );
651    }
652
653    #[test]
654    fn test_triangle() {
655        let triangle = Triangle::new((0.0, 0.0).into(), (5.0, 10.0).into(), (10.0, 0.0).into());
656        assert_eq!(
657            triangle.coordinate_position(&coord! { x: 5.0, y: 5.0 }),
658            CoordPos::Inside
659        );
660        assert_eq!(
661            triangle.coordinate_position(&coord! { x: 2.5, y: 5.0 }),
662            CoordPos::OnBoundary
663        );
664        assert_eq!(
665            triangle.coordinate_position(&coord! { x: 2.49, y: 5.0 }),
666            CoordPos::Outside
667        );
668    }
669
670    #[test]
671    fn test_collection() {
672        let triangle = Triangle::new((0.0, 0.0).into(), (5.0, 10.0).into(), (10.0, 0.0).into());
673        let rect = Rect::new((0.0, 0.0), (10.0, 10.0));
674        let collection = GeometryCollection::new_from(vec![triangle.into(), rect.into()]);
675
676        //  outside of both
677        assert_eq!(
678            collection.coordinate_position(&coord! { x: 15.0, y: 15.0 }),
679            CoordPos::Outside
680        );
681
682        // inside both
683        assert_eq!(
684            collection.coordinate_position(&coord! { x: 5.0, y: 5.0 }),
685            CoordPos::Inside
686        );
687
688        // inside one, boundary of other
689        assert_eq!(
690            collection.coordinate_position(&coord! { x: 2.5, y: 5.0 }),
691            CoordPos::OnBoundary
692        );
693
694        //  boundary of both
695        assert_eq!(
696            collection.coordinate_position(&coord! { x: 5.0, y: 10.0 }),
697            CoordPos::Outside
698        );
699    }
700}