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 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::new(self.0[0], self.0[1]).calculate_coordinate_position(
148 coord,
149 is_inside,
150 boundary_count,
151 );
152 return;
153 }
154
155 if !self.bounding_rect().unwrap().intersects(coord) {
158 return;
159 }
160
161 if !self.is_closed() {
163 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 *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 *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
386pub 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 if linestring.0.is_empty() {
396 return CoordPos::Outside;
397 }
398 if linestring.0.len() == 1 {
399 return if coord == linestring.0[0] {
402 CoordPos::OnBoundary
403 } else {
404 CoordPos::Outside
405 };
406 }
407
408 let mut winding_number = 0;
411 for line in linestring.lines() {
412 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 assert!(line_string.is_closed());
623
624 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 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 line_string![(x: 0.0, y: 1.0), (x: 0.5, y: 0.5)],
643 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 let shared_start = Coord::zero();
663 assert_eq!(
664 multi_line_string.coordinate_position(&shared_start),
665 CoordPos::Outside
666 );
667
668 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 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 assert_eq!(
725 collection.coordinate_position(&coord! { x: 15.0, y: 15.0 }),
726 CoordPos::Outside
727 );
728
729 assert_eq!(
731 collection.coordinate_position(&coord! { x: 5.0, y: 5.0 }),
732 CoordPos::Inside
733 );
734
735 assert_eq!(
737 collection.coordinate_position(&coord! { x: 2.5, y: 5.0 }),
738 CoordPos::OnBoundary
739 );
740
741 assert_eq!(
743 collection.coordinate_position(&coord! { x: 5.0, y: 10.0 }),
744 CoordPos::Outside
745 );
746 }
747}