mapping_algorithms/polygons/
point_in_polygon.rs

1// SPDX-License-Identifier: MIT
2/*
3 * Copyright (c) [2023 - Present] Emily Matheys <emilymatt96@gmail.com>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24use nalgebra::{Point2, RealField, Vector2};
25use num_traits::{AsPrimitive, Bounded};
26
27use crate::Vec;
28
29use super::calculate_polygon_extents;
30
31#[inline]
32#[cfg_attr(
33    feature = "tracing",
34    tracing::instrument("Does Ray Intersect Polygon", skip_all, level = "trace")
35)]
36fn does_ray_intersect_polygon_segment<T>(
37    point: &Vector2<T>,
38    vertex1: Point2<T>,
39    vertex2: Point2<T>,
40) -> bool
41where
42    T: Copy + RealField,
43    f32: AsPrimitive<T>,
44{
45    if point.y > vertex1.y.min(vertex2.y)
46        && point.y <= vertex1.y.max(vertex2.y)
47        && point.x <= vertex1.x.max(vertex2.x)
48    {
49        let origin_x = (vertex1.y != vertex2.y)
50            .then(|| {
51                (point.y - vertex1.y) * (vertex2.x - vertex1.x) / (vertex2.y - vertex1.y)
52                    + vertex1.x
53            })
54            .unwrap_or(point.x);
55
56        if vertex1.x == vertex2.x || point.x <= origin_x {
57            return true;
58        }
59    }
60
61    false
62}
63
64/// Check if the provided point is within the provided polygon.
65///
66/// # Arguments
67/// * `point`: A reference to a [`Point2`].
68/// * `polygon`: A slice of [`Point2`]s representing the vertices.
69///
70/// # Generics:
71/// * `T`: Either an [`prim@f32`] or [`prim@f64`]
72///
73/// # Returns
74/// A boolean value, specifying if the point is within the polygon.
75#[cfg_attr(
76    feature = "tracing",
77    tracing::instrument("Is Point In Polygon", skip_all, level = "debug")
78)]
79pub fn is_single_point_in_polygon<T>(point: &Point2<T>, polygon: &[Point2<T>]) -> bool
80where
81    T: Copy + RealField,
82    f32: AsPrimitive<T>,
83{
84    let polygon_len = polygon.len();
85    (0..polygon_len)
86        .filter_map(|current_vertex_idx| {
87            let current_vertex = polygon[current_vertex_idx];
88            let next_vertex = polygon[(current_vertex_idx + 1) % polygon_len];
89
90            does_ray_intersect_polygon_segment(&point.coords, current_vertex, next_vertex)
91                .then_some(1)
92        })
93        .sum::<usize>()
94        % 2
95        == 1 // If the number of intersections is odd - we didn't exit the polygon, and are therefor in it.
96}
97
98/// This function will run the [`is_single_point_in_polygon`] for each on of the points given, and the provided polygon,
99/// But pre-calculates the polygon extents to reduce workloads for larger datasets, please profile this for you specific use-case.
100///
101/// # Arguments
102/// * `points`: A slice of [`Point2`].
103/// * `polygon`: A slice of [`Point2`]s, representing the vertices.
104///
105/// # Generics:
106/// * `T`: Either an [`prim@f32`] or [`prim@f64`]
107///
108/// # Returns
109/// A [`Vec`] of booleans, with the same size as `points`, containing the result for each point.
110#[cfg_attr(
111    feature = "tracing",
112    tracing::instrument("Are Points In Polygon", skip_all, level = "info")
113)]
114pub fn are_multiple_points_in_polygon<T>(
115    points: &[Point2<T>],
116    polygon: &[Point2<T>],
117) -> Option<Vec<bool>>
118where
119    T: Bounded + Copy + RealField,
120    f32: AsPrimitive<T>,
121{
122    if polygon.len() < 3 {
123        return None;
124    }
125    let polygon_extents = calculate_polygon_extents(polygon)?;
126
127    Some(
128        points
129            .iter()
130            .map(|current_point| {
131                // Verify that each coordinate is within the bounds of the polygon, will save a lot of computational load for large polygons
132                polygon_extents
133                    .iter()
134                    .zip(current_point.coords.iter())
135                    .fold(
136                        true,
137                        |is_in_extents, (extent_for_dimension, vertex_coord)| {
138                            is_in_extents && extent_for_dimension.contains(vertex_coord)
139                        },
140                    )
141                    && is_single_point_in_polygon(current_point, polygon)
142            })
143            .collect(),
144    )
145}
146
147#[cfg(feature = "pregenerated")]
148macro_rules! impl_p_i_p_algorithm {
149    ($prec:expr, doc $doc:tt) => {
150        ::paste::paste! {
151            pub(super) mod [<$doc _precision>] {
152                use nalgebra::Point2;
153                use crate::Vec;
154
155                #[doc = "A premade variant of the single point-in-polygon algorithm function, made for " $doc " precision floating-point arithmetic."]
156                pub fn is_single_point_in_polygon(point: &Point2<$prec>, polygon: &[Point2<$prec>]) -> bool {
157                    super::is_single_point_in_polygon(point, polygon)
158                }
159
160                #[doc = "A premade variant of the multiple point-in-polygon algorithm function, made for " $doc " precision floating-point arithmetic."]
161                pub fn are_multiple_points_in_polygon(
162                    points: &[Point2<$prec>],
163                    polygon: &[Point2<$prec>],
164                ) -> Option<Vec<bool>> {
165                    super::are_multiple_points_in_polygon(points, polygon)
166                }
167            }
168        }
169    };
170}
171
172#[cfg(feature = "pregenerated")]
173impl_p_i_p_algorithm!(f32, doc single);
174#[cfg(feature = "pregenerated")]
175impl_p_i_p_algorithm!(f64, doc double);
176
177#[cfg(test)]
178mod tests {
179    use nalgebra::{Point2, Vector2};
180
181    use crate::Vec;
182
183    use super::*;
184
185    fn get_polygon_for_tests() -> Vec<Point2<f32>> {
186        Vec::from([
187            Point2::from([0.0, 0.0]),
188            Point2::from([1.0, 1.2]),
189            Point2::from([1.4, 1.2]),
190            Point2::from([1.4, 2.0]),
191            Point2::from([0.5, 1.8]),
192            Point2::from([-2.0, 0.2]),
193            Point2::from([-1.2, -0.4]),
194            Point2::from([-0.3, -0.4]),
195        ])
196    }
197
198    #[test]
199    fn test_does_ray_intersect() {
200        let point_a = Vector2::new(0.5, -1.5);
201        let vertex_a1 = Point2::new(5.0, 0.0);
202        let vertex_a2 = Point2::new(1.0, -4.0);
203        assert!(does_ray_intersect_polygon_segment(
204            &point_a, vertex_a1, vertex_a2
205        ));
206
207        let point_b = Vector2::new(-0.5, -1.5);
208        let vertex_b1 = Point2::new(0.0, 0.0);
209        let vertex_b2 = Point2::new(1.0, 5.0);
210        assert!(!does_ray_intersect_polygon_segment(
211            &point_b, vertex_b1, vertex_b2
212        ));
213    }
214
215    #[test]
216    fn test_is_single_points_in_polygon() {
217        let polygon = get_polygon_for_tests();
218
219        let point = Point2::from([0.5, 1.5]);
220
221        assert!(is_single_point_in_polygon(&point, &polygon));
222    }
223
224    #[test]
225    fn test_multiple_points_in_polygon_clockwise() {
226        let polygon = get_polygon_for_tests();
227
228        // One point inside the polygon and one outside.
229        let points = &[
230            Point2::from([0.5, 1.5]), // Inside
231            Point2::from([1.5, 1.5]), // Outside
232        ];
233
234        let result = are_multiple_points_in_polygon(points, &polygon);
235
236        // Expecting [true, false] since the first point is inside and the second is outside.
237        assert_eq!(result, Some(Vec::from([true, false])));
238    }
239
240    #[test]
241    fn test_multiple_points_in_polygon_counter_clockwise() {
242        let polygon = get_polygon_for_tests();
243
244        // One point inside the polygon and one outside.
245        let points = &[
246            Point2::from([0.5, 1.5]), // Inside
247            Point2::from([1.5, 1.5]), // Outside
248        ];
249
250        let result = are_multiple_points_in_polygon(points, &polygon);
251
252        // Expecting [true, false] since the first point is inside and the second is outside.
253        assert_eq!(result, Some(Vec::from([true, false])));
254    }
255}