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#[derive(PartialEq, Eq, Clone, Copy, Debug)]
11pub enum CoordPos {
12 OnBoundary,
13 Inside,
14 Outside,
15}
16
17pub 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 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 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 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 match self.0.len() {
141 0 => {
142 warn!("invalid line string with 0 coords");
143 return;
144 }
145 1 => {
146 warn!("invalid line string with only 1 coord");
147 return self[0].calculate_coordinate_position(coord, is_inside, boundary_count);
149 }
150 2 => {
151 return Line::new(self.0[0], self.0[1]).calculate_coordinate_position(
153 coord,
154 is_inside,
155 boundary_count,
156 );
157 }
158 _ => {}
159 }
160
161 if !self.bounding_rect().unwrap().intersects(coord) {
164 return;
165 }
166
167 if !self.is_closed() {
169 if coord == self.0.first().unwrap() || coord == self.0.last().unwrap() {
171 *boundary_count += 1;
172 return;
173 }
174 }
175
176 if self.intersects(coord) {
177 *is_inside = true
180 }
181 }
182}
183
184impl<T> CoordinatePosition for Triangle<T>
185where
186 T: GeoNum,
187{
188 type Scalar = T;
189 fn calculate_coordinate_position(
190 &self,
191 coord: &Coord<T>,
192 is_inside: &mut bool,
193 boundary_count: &mut usize,
194 ) {
195 *is_inside = self
196 .to_lines()
197 .map(|l| {
198 let orientation = T::Ker::orient2d(l.start, l.end, *coord);
199 if orientation == Orientation::Collinear
200 && point_in_rect(*coord, l.start, l.end)
201 && *coord != l.end
207 {
208 *boundary_count += 1;
209 }
210 orientation
211 })
212 .windows(2)
213 .all(|win| win[0] == win[1] && win[0] != Orientation::Collinear);
214 }
215}
216
217impl<T> CoordinatePosition for Rect<T>
218where
219 T: GeoNum,
220{
221 type Scalar = T;
222 fn calculate_coordinate_position(
223 &self,
224 coord: &Coord<T>,
225 is_inside: &mut bool,
226 boundary_count: &mut usize,
227 ) {
228 let mut boundary = false;
229
230 let min = self.min();
231
232 match coord.x.partial_cmp(&min.x).unwrap() {
233 Ordering::Less => return,
234 Ordering::Equal => boundary = true,
235 Ordering::Greater => {}
236 }
237 match coord.y.partial_cmp(&min.y).unwrap() {
238 Ordering::Less => return,
239 Ordering::Equal => boundary = true,
240 Ordering::Greater => {}
241 }
242
243 let max = self.max();
244
245 match max.x.partial_cmp(&coord.x).unwrap() {
246 Ordering::Less => return,
247 Ordering::Equal => boundary = true,
248 Ordering::Greater => {}
249 }
250 match max.y.partial_cmp(&coord.y).unwrap() {
251 Ordering::Less => return,
252 Ordering::Equal => boundary = true,
253 Ordering::Greater => {}
254 }
255
256 if boundary {
257 *boundary_count += 1;
258 } else {
259 *is_inside = true;
260 }
261 }
262}
263
264impl<T> CoordinatePosition for MultiPoint<T>
265where
266 T: GeoNum,
267{
268 type Scalar = T;
269 fn calculate_coordinate_position(
270 &self,
271 coord: &Coord<T>,
272 is_inside: &mut bool,
273 _boundary_count: &mut usize,
274 ) {
275 if self.0.iter().any(|p| &p.0 == coord) {
276 *is_inside = true;
277 }
278 }
279}
280
281impl<T> CoordinatePosition for Polygon<T>
282where
283 T: GeoNum,
284{
285 type Scalar = T;
286 fn calculate_coordinate_position(
287 &self,
288 coord: &Coord<T>,
289 is_inside: &mut bool,
290 boundary_count: &mut usize,
291 ) {
292 if self.is_empty() {
293 return;
294 }
295
296 match coord_pos_relative_to_ring(*coord, self.exterior()) {
297 CoordPos::Outside => {}
298 CoordPos::OnBoundary => {
299 *boundary_count += 1;
300 }
301 CoordPos::Inside => {
302 for hole in self.interiors() {
303 match coord_pos_relative_to_ring(*coord, hole) {
304 CoordPos::Outside => {}
305 CoordPos::OnBoundary => {
306 *boundary_count += 1;
307 return;
308 }
309 CoordPos::Inside => {
310 return;
311 }
312 }
313 }
314 *is_inside = true;
316 }
317 }
318 }
319}
320
321impl<T> CoordinatePosition for MultiLineString<T>
322where
323 T: GeoNum,
324{
325 type Scalar = T;
326 fn calculate_coordinate_position(
327 &self,
328 coord: &Coord<T>,
329 is_inside: &mut bool,
330 boundary_count: &mut usize,
331 ) {
332 for line_string in &self.0 {
333 line_string.calculate_coordinate_position(coord, is_inside, boundary_count);
334 }
335 }
336}
337
338impl<T> CoordinatePosition for MultiPolygon<T>
339where
340 T: GeoNum,
341{
342 type Scalar = T;
343 fn calculate_coordinate_position(
344 &self,
345 coord: &Coord<T>,
346 is_inside: &mut bool,
347 boundary_count: &mut usize,
348 ) {
349 for polygon in &self.0 {
350 polygon.calculate_coordinate_position(coord, is_inside, boundary_count);
351 }
352 }
353}
354
355impl<T> CoordinatePosition for GeometryCollection<T>
356where
357 T: GeoNum,
358{
359 type Scalar = T;
360 fn calculate_coordinate_position(
361 &self,
362 coord: &Coord<T>,
363 is_inside: &mut bool,
364 boundary_count: &mut usize,
365 ) {
366 for geometry in self {
367 geometry.calculate_coordinate_position(coord, is_inside, boundary_count);
368 }
369 }
370}
371
372impl<T> CoordinatePosition for Geometry<T>
373where
374 T: GeoNum,
375{
376 type Scalar = T;
377 crate::geometry_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
386impl<T: GeoNum> CoordinatePosition for GeometryCow<'_, T> {
387 type Scalar = T;
388 crate::geometry_cow_delegate_impl! {
389 fn calculate_coordinate_position(
390 &self,
391 coord: &Coord<T>,
392 is_inside: &mut bool,
393 boundary_count: &mut usize) -> ();
394 }
395}
396
397pub fn coord_pos_relative_to_ring<T>(coord: Coord<T>, linestring: &LineString<T>) -> CoordPos
400where
401 T: GeoNum,
402{
403 debug_assert!(linestring.is_closed());
404
405 if linestring.0.is_empty() {
407 return CoordPos::Outside;
408 }
409 if linestring.0.len() == 1 {
410 return if coord == linestring.0[0] {
413 CoordPos::OnBoundary
414 } else {
415 CoordPos::Outside
416 };
417 }
418
419 let mut winding_number = 0;
422 for line in linestring.lines() {
423 if line.start.y <= coord.y {
429 if line.end.y >= coord.y {
430 let o = T::Ker::orient2d(line.start, line.end, coord);
431 if o == Orientation::CounterClockwise && line.end.y != coord.y {
432 winding_number += 1
433 } else if o == Orientation::Collinear
434 && value_in_between(coord.x, line.start.x, line.end.x)
435 {
436 return CoordPos::OnBoundary;
437 }
438 };
439 } else if line.end.y <= coord.y {
440 let o = T::Ker::orient2d(line.start, line.end, coord);
441 if o == Orientation::Clockwise {
442 winding_number -= 1
443 } else if o == Orientation::Collinear
444 && value_in_between(coord.x, line.start.x, line.end.x)
445 {
446 return CoordPos::OnBoundary;
447 }
448 }
449 }
450 if winding_number == 0 {
451 CoordPos::Outside
452 } else {
453 CoordPos::Inside
454 }
455}
456
457#[cfg(test)]
458mod test {
459 use super::*;
460 use crate::algorithm::Convert;
461 use crate::{coord, line_string, point, polygon, wkt};
462
463 #[test]
464 fn test_empty_poly() {
465 let square_poly: Polygon<f64> = Polygon::empty();
466 assert_eq!(
467 square_poly.coordinate_position(&Coord::zero()),
468 CoordPos::Outside
469 );
470 }
471
472 #[test]
473 fn test_simple_poly() {
474 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)];
475
476 let inside_coord = coord! { x: 1.0, y: 1.0 };
477 assert_eq!(
478 square_poly.coordinate_position(&inside_coord),
479 CoordPos::Inside
480 );
481
482 let vertex_coord = coord! { x: 0.0, y: 0.0 };
483 assert_eq!(
484 square_poly.coordinate_position(&vertex_coord),
485 CoordPos::OnBoundary
486 );
487
488 let boundary_coord = coord! { x: 0.0, y: 1.0 };
489 assert_eq!(
490 square_poly.coordinate_position(&boundary_coord),
491 CoordPos::OnBoundary
492 );
493
494 let outside_coord = coord! { x: 5.0, y: 5.0 };
495 assert_eq!(
496 square_poly.coordinate_position(&outside_coord),
497 CoordPos::Outside
498 );
499 }
500
501 #[test]
502 fn test_poly_interior() {
503 let poly = polygon![
504 exterior: [
505 (x: 11., y: 11.),
506 (x: 20., y: 11.),
507 (x: 20., y: 20.),
508 (x: 11., y: 20.),
509 (x: 11., y: 11.),
510 ],
511 interiors: [
512 [
513 (x: 13., y: 13.),
514 (x: 13., y: 17.),
515 (x: 17., y: 17.),
516 (x: 17., y: 13.),
517 (x: 13., y: 13.),
518 ]
519 ],
520 ];
521
522 let inside_hole = coord! { x: 14.0, y: 14.0 };
523 assert_eq!(poly.coordinate_position(&inside_hole), CoordPos::Outside);
524
525 let outside_poly = coord! { x: 30.0, y: 30.0 };
526 assert_eq!(poly.coordinate_position(&outside_poly), CoordPos::Outside);
527
528 let on_outside_border = coord! { x: 20.0, y: 15.0 };
529 assert_eq!(
530 poly.coordinate_position(&on_outside_border),
531 CoordPos::OnBoundary
532 );
533
534 let on_inside_border = coord! { x: 13.0, y: 15.0 };
535 assert_eq!(
536 poly.coordinate_position(&on_inside_border),
537 CoordPos::OnBoundary
538 );
539
540 let inside_coord = coord! { x: 12.0, y: 12.0 };
541 assert_eq!(poly.coordinate_position(&inside_coord), CoordPos::Inside);
542 }
543
544 #[test]
545 fn test_simple_line() {
546 use crate::point;
547 let line = Line::new(point![x: 0.0, y: 0.0], point![x: 10.0, y: 10.0]);
548
549 let start = coord! { x: 0.0, y: 0.0 };
550 assert_eq!(line.coordinate_position(&start), CoordPos::OnBoundary);
551
552 let end = coord! { x: 10.0, y: 10.0 };
553 assert_eq!(line.coordinate_position(&end), CoordPos::OnBoundary);
554
555 let interior = coord! { x: 5.0, y: 5.0 };
556 assert_eq!(line.coordinate_position(&interior), CoordPos::Inside);
557
558 let outside = coord! { x: 6.0, y: 5.0 };
559 assert_eq!(line.coordinate_position(&outside), CoordPos::Outside);
560 }
561
562 #[test]
563 fn test_degenerate_line() {
564 let line = Line::new(point![x: 0.0, y: 0.0], point![x: 0.0, y: 0.0]);
565
566 let start = coord! { x: 0.0, y: 0.0 };
567 assert_eq!(line.coordinate_position(&start), CoordPos::Inside);
568
569 let outside = coord! { x: 10.0, y: 10.0 };
570 assert_eq!(line.coordinate_position(&outside), CoordPos::Outside);
571 }
572
573 #[test]
574 fn test_point() {
575 let p1 = point![x: 2.0, y: 0.0];
576
577 let c1 = coord! { x: 2.0, y: 0.0 };
578 let c2 = coord! { x: 3.0, y: 3.0 };
579
580 assert_eq!(p1.coordinate_position(&c1), CoordPos::Inside);
581 assert_eq!(p1.coordinate_position(&c2), CoordPos::Outside);
582
583 assert_eq!(c1.coordinate_position(&c1), CoordPos::Inside);
584 assert_eq!(c1.coordinate_position(&c2), CoordPos::Outside);
585 }
586
587 mod line_string {
588 use super::*;
589 #[test]
590 fn test_simple_line_string() {
591 let line_string: LineString = wkt!(LINESTRING(0 0,1 1,2 0,3 0)).convert();
592
593 let start = Coord::zero();
594 assert_eq!(
595 line_string.coordinate_position(&start),
596 CoordPos::OnBoundary
597 );
598
599 let midpoint = coord! { x: 0.5, y: 0.5 };
600 assert_eq!(line_string.coordinate_position(&midpoint), CoordPos::Inside);
601
602 let vertex = coord! { x: 2.0, y: 0.0 };
603 assert_eq!(line_string.coordinate_position(&vertex), CoordPos::Inside);
604
605 let end = coord! { x: 3.0, y: 0.0 };
606 assert_eq!(line_string.coordinate_position(&end), CoordPos::OnBoundary);
607
608 let outside = coord! { x: 3.0, y: 1.0 };
609 assert_eq!(line_string.coordinate_position(&outside), CoordPos::Outside);
610 }
611
612 #[test]
613 fn test_degenerate_line_strings() {
614 let origin = Coord::zero();
615
616 assert_eq!(
617 wkt!(LINESTRING EMPTY).coordinate_position(&origin),
618 CoordPos::Outside
619 );
620
621 assert_eq!(
623 wkt!(LINESTRING(0. 0.)).coordinate_position(&origin),
624 CoordPos::Inside
625 );
626
627 assert_eq!(
629 wkt!(LINESTRING(0. 0.,0. 0.)).coordinate_position(&origin),
630 CoordPos::Inside
631 );
632
633 assert_eq!(
635 wkt!(LINESTRING(0. 0.,2. 0.)).coordinate_position(&origin),
636 CoordPos::OnBoundary
637 );
638 }
639
640 #[test]
641 fn test_closed_line_string() {
642 let line_string: LineString = wkt!(LINESTRING(0 0,1 1,2 0,3 2,0 2,0 0)).convert();
643
644 assert!(line_string.is_closed());
646
647 let start = Coord::zero();
649 assert_eq!(line_string.coordinate_position(&start), CoordPos::Inside);
650
651 let midpoint = coord! { x: 0.5, y: 0.5 };
652 assert_eq!(line_string.coordinate_position(&midpoint), CoordPos::Inside);
653
654 let outside = coord! { x: 3.0, y: 1.0 };
655 assert_eq!(line_string.coordinate_position(&outside), CoordPos::Outside);
656 }
657 }
658
659 #[test]
660 fn test_boundary_rule() {
661 let multi_line_string = MultiLineString::new(vec![
662 line_string![(x: 0.0, y: 0.0), (x: 1.0, y: 1.0)],
664 line_string![(x: 0.0, y: 0.0), (x: -1.0, y: -1.0)],
665 line_string![(x: 0.0, y: 1.0), (x: 0.5, y: 0.5)],
667 line_string![(x: 0.0, y: -1.0), (x: -0.5, y: -0.5)],
670 line_string![(x: 0.0, y: -2.0), (x: -0.5, y: -0.5)],
671 ]);
672
673 let outside_of_all = coord! { x: 123.0, y: 123.0 };
674 assert_eq!(
675 multi_line_string.coordinate_position(&outside_of_all),
676 CoordPos::Outside
677 );
678
679 let end_of_one_line = coord! { x: -1.0, y: -1.0 };
680 assert_eq!(
681 multi_line_string.coordinate_position(&end_of_one_line),
682 CoordPos::OnBoundary
683 );
684
685 let shared_start = Coord::zero();
687 assert_eq!(
688 multi_line_string.coordinate_position(&shared_start),
689 CoordPos::Outside
690 );
691
692 let one_end_plus_midpoint = coord! { x: 0.5, y: 0.5 };
694 assert_eq!(
695 multi_line_string.coordinate_position(&one_end_plus_midpoint),
696 CoordPos::OnBoundary
697 );
698
699 let two_ends_plus_midpoint = coord! { x: -0.5, y: -0.5 };
701 assert_eq!(
702 multi_line_string.coordinate_position(&two_ends_plus_midpoint),
703 CoordPos::Inside
704 );
705 }
706
707 #[test]
708 fn test_rect() {
709 let rect = Rect::new((0.0, 0.0), (10.0, 10.0));
710 assert_eq!(
711 rect.coordinate_position(&coord! { x: 5.0, y: 5.0 }),
712 CoordPos::Inside
713 );
714 assert_eq!(
715 rect.coordinate_position(&coord! { x: 0.0, y: 5.0 }),
716 CoordPos::OnBoundary
717 );
718 assert_eq!(
719 rect.coordinate_position(&coord! { x: 15.0, y: 15.0 }),
720 CoordPos::Outside
721 );
722 }
723
724 #[test]
725 fn test_triangle() {
726 let triangle = Triangle::new((0.0, 0.0).into(), (5.0, 10.0).into(), (10.0, 0.0).into());
727 assert_eq!(
728 triangle.coordinate_position(&coord! { x: 5.0, y: 5.0 }),
729 CoordPos::Inside
730 );
731 assert_eq!(
732 triangle.coordinate_position(&coord! { x: 2.5, y: 5.0 }),
733 CoordPos::OnBoundary
734 );
735 assert_eq!(
736 triangle.coordinate_position(&coord! { x: 5.0, y: 10.0 }),
737 CoordPos::OnBoundary
738 );
739 assert_eq!(
740 triangle.coordinate_position(&coord! { x: 2.49, y: 5.0 }),
741 CoordPos::Outside
742 );
743 }
744
745 #[test]
746 fn test_right_triangle() {
747 let triangle: Triangle = wkt!(TRIANGLE(0. 0.,10. 0.,10. 10.));
748 assert_eq!(
749 triangle.coordinate_position(&coord!(x: 0., y: 0.)),
750 CoordPos::OnBoundary
751 );
752 assert_eq!(
753 triangle.coordinate_position(&coord!(x: 10., y: 5.)),
754 CoordPos::OnBoundary
755 );
756 }
757
758 #[test]
759 fn test_collection() {
760 let triangle = Triangle::new((0.0, 0.0).into(), (5.0, 10.0).into(), (10.0, 0.0).into());
761 let rect = Rect::new((0.0, 0.0), (10.0, 10.0));
762 let collection = GeometryCollection::new_from(vec![triangle.into(), rect.into()]);
763
764 assert_eq!(
766 collection.coordinate_position(&coord! { x: 15.0, y: 15.0 }),
767 CoordPos::Outside
768 );
769
770 assert_eq!(
772 collection.coordinate_position(&coord! { x: 5.0, y: 5.0 }),
773 CoordPos::Inside
774 );
775
776 assert_eq!(
778 collection.coordinate_position(&coord! { x: 2.5, y: 5.0 }),
779 CoordPos::OnBoundary
780 );
781
782 assert_eq!(
784 collection.coordinate_position(&coord! { x: 5.0, y: 10.0 }),
785 CoordPos::Outside
786 );
787 }
788}