geo/algorithm/
coordinate_position.rs

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