use std::f64::consts::PI;
pub fn polygon_crosses_antimeridian(vertices: &[(f64, f64)]) -> bool {
if vertices.len() < 2 {
return false;
}
for i in 0..vertices.len() - 1 {
let lon1 = vertices[i].0;
let lon2 = vertices[i + 1].0;
let diff = (lon2 - lon1).abs();
if diff > PI {
return true;
}
}
false
}
fn normalize_longitude_positive(lon: f64) -> f64 {
let two_pi = 2.0 * PI;
let mut normalized = lon % two_pi;
if normalized < 0.0 {
normalized += two_pi;
}
normalized
}
pub fn point_in_polygon(lon: f64, lat: f64, vertices: &[(f64, f64)]) -> bool {
if vertices.len() < 4 {
return false;
}
let crosses_antimeridian = polygon_crosses_antimeridian(vertices);
if crosses_antimeridian {
let normalized_vertices: Vec<(f64, f64)> = vertices
.iter()
.map(|(vlon, vlat)| (normalize_longitude_positive(*vlon), *vlat))
.collect();
let normalized_lon = normalize_longitude_positive(lon);
point_in_polygon_internal(normalized_lon, lat, &normalized_vertices)
} else {
point_in_polygon_internal(lon, lat, vertices)
}
}
fn point_in_polygon_internal(lon: f64, lat: f64, vertices: &[(f64, f64)]) -> bool {
let n = vertices.len();
if n < 4 {
return false;
}
let mut inside = false;
let mut j = n - 2;
for i in 0..n - 1 {
let (xi, yi) = vertices[i];
let (xj, yj) = vertices[j];
let intersects =
((yi > lat) != (yj > lat)) && (lon < (xj - xi) * (lat - yi) / (yj - yi) + xi);
if intersects {
inside = !inside;
}
j = i;
}
inside
}
#[cfg(test)]
#[allow(non_snake_case)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
fn deg_to_rad(deg: f64) -> f64 {
deg.to_radians()
}
fn polygon_from_degrees(coords: &[(f64, f64)]) -> Vec<(f64, f64)> {
coords
.iter()
.map(|(lon, lat)| (deg_to_rad(*lon), deg_to_rad(*lat)))
.collect()
}
#[test]
fn test_normalize_longitude_positive_zero() {
assert_abs_diff_eq!(normalize_longitude_positive(0.0), 0.0, epsilon = 1e-10);
}
#[test]
fn test_normalize_longitude_positive_positive() {
assert_abs_diff_eq!(normalize_longitude_positive(PI), PI, epsilon = 1e-10);
}
#[test]
fn test_normalize_longitude_positive_negative() {
let result = normalize_longitude_positive(-PI / 2.0);
assert_abs_diff_eq!(result, 3.0 * PI / 2.0, epsilon = 1e-10);
}
#[test]
fn test_normalize_longitude_positive_wrap() {
let result = normalize_longitude_positive(deg_to_rad(370.0));
assert_abs_diff_eq!(result, deg_to_rad(10.0), epsilon = 1e-10);
}
#[test]
fn test_normalize_longitude_positive_negative_wrap() {
let result = normalize_longitude_positive(deg_to_rad(-190.0));
assert_abs_diff_eq!(result, deg_to_rad(170.0), epsilon = 1e-10);
}
#[test]
fn test_polygon_crosses_antimeridian_empty() {
let vertices: Vec<(f64, f64)> = vec![];
assert!(!polygon_crosses_antimeridian(&vertices));
}
#[test]
fn test_polygon_crosses_antimeridian_single() {
let vertices = polygon_from_degrees(&[(10.0, 10.0)]);
assert!(!polygon_crosses_antimeridian(&vertices));
}
#[test]
fn test_polygon_crosses_antimeridian_simple_no_cross() {
let vertices = polygon_from_degrees(&[
(10.0, 50.0),
(20.0, 50.0),
(20.0, 55.0),
(10.0, 55.0),
(10.0, 50.0),
]);
assert!(!polygon_crosses_antimeridian(&vertices));
}
#[test]
fn test_polygon_crosses_antimeridian_crosses() {
let vertices = polygon_from_degrees(&[
(170.0, 10.0),
(-170.0, 10.0),
(-170.0, 20.0),
(170.0, 20.0),
(170.0, 10.0),
]);
assert!(polygon_crosses_antimeridian(&vertices));
}
#[test]
fn test_polygon_crosses_antimeridian_near_but_no_cross() {
let vertices = polygon_from_degrees(&[
(170.0, 10.0),
(175.0, 10.0),
(175.0, 20.0),
(170.0, 20.0),
(170.0, 10.0),
]);
assert!(!polygon_crosses_antimeridian(&vertices));
}
#[test]
fn test_point_in_polygon_empty() {
let vertices: Vec<(f64, f64)> = vec![];
assert!(!point_in_polygon(0.0, 0.0, &vertices));
}
#[test]
fn test_point_in_polygon_insufficient_vertices() {
let vertices = polygon_from_degrees(&[(10.0, 10.0), (20.0, 10.0), (15.0, 20.0)]);
assert!(!point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(15.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_square_inside() {
let vertices = polygon_from_degrees(&[
(10.0, 10.0),
(20.0, 10.0),
(20.0, 20.0),
(10.0, 20.0),
(10.0, 10.0),
]);
assert!(point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(15.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_square_outside_west() {
let vertices = polygon_from_degrees(&[
(10.0, 10.0),
(20.0, 10.0),
(20.0, 20.0),
(10.0, 20.0),
(10.0, 10.0),
]);
assert!(!point_in_polygon(
deg_to_rad(5.0),
deg_to_rad(15.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_square_outside_east() {
let vertices = polygon_from_degrees(&[
(10.0, 10.0),
(20.0, 10.0),
(20.0, 20.0),
(10.0, 20.0),
(10.0, 10.0),
]);
assert!(!point_in_polygon(
deg_to_rad(25.0),
deg_to_rad(15.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_square_outside_north() {
let vertices = polygon_from_degrees(&[
(10.0, 10.0),
(20.0, 10.0),
(20.0, 20.0),
(10.0, 20.0),
(10.0, 10.0),
]);
assert!(!point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(25.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_square_outside_south() {
let vertices = polygon_from_degrees(&[
(10.0, 10.0),
(20.0, 10.0),
(20.0, 20.0),
(10.0, 20.0),
(10.0, 10.0),
]);
assert!(!point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(5.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_concave_inside() {
let vertices = polygon_from_degrees(&[
(10.0, 10.0),
(30.0, 10.0),
(30.0, 20.0),
(20.0, 20.0),
(20.0, 30.0),
(10.0, 30.0),
(10.0, 10.0),
]);
assert!(point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(15.0),
&vertices
));
assert!(point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(25.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_concave_outside_in_concavity() {
let vertices = polygon_from_degrees(&[
(10.0, 10.0),
(30.0, 10.0),
(30.0, 20.0),
(20.0, 20.0),
(20.0, 30.0),
(10.0, 30.0),
(10.0, 10.0),
]);
assert!(!point_in_polygon(
deg_to_rad(25.0),
deg_to_rad(25.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_antimeridian_inside() {
let vertices = polygon_from_degrees(&[
(170.0, 10.0),
(-170.0, 10.0), (-170.0, 20.0),
(170.0, 20.0),
(170.0, 10.0),
]);
assert!(point_in_polygon(
deg_to_rad(175.0),
deg_to_rad(15.0),
&vertices
));
assert!(point_in_polygon(
deg_to_rad(-175.0),
deg_to_rad(15.0),
&vertices
));
assert!(point_in_polygon(
deg_to_rad(180.0),
deg_to_rad(15.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_antimeridian_outside() {
let vertices = polygon_from_degrees(&[
(170.0, 10.0),
(-170.0, 10.0),
(-170.0, 20.0),
(170.0, 20.0),
(170.0, 10.0),
]);
assert!(!point_in_polygon(
deg_to_rad(160.0),
deg_to_rad(15.0),
&vertices
));
assert!(!point_in_polygon(
deg_to_rad(-160.0),
deg_to_rad(15.0),
&vertices
));
assert!(!point_in_polygon(
deg_to_rad(175.0),
deg_to_rad(25.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_southern_hemisphere() {
let vertices = polygon_from_degrees(&[
(10.0, -20.0),
(20.0, -20.0),
(20.0, -10.0),
(10.0, -10.0),
(10.0, -20.0),
]);
assert!(point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(-15.0),
&vertices
));
assert!(!point_in_polygon(
deg_to_rad(15.0),
deg_to_rad(-25.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_western_hemisphere() {
let vertices = polygon_from_degrees(&[
(-120.0, 35.0),
(-100.0, 35.0),
(-100.0, 45.0),
(-120.0, 45.0),
(-120.0, 35.0),
]);
assert!(point_in_polygon(
deg_to_rad(-110.0),
deg_to_rad(40.0),
&vertices
));
assert!(!point_in_polygon(
deg_to_rad(-90.0),
deg_to_rad(40.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_triangle() {
let vertices =
polygon_from_degrees(&[(10.0, 10.0), (30.0, 10.0), (20.0, 30.0), (10.0, 10.0)]);
assert!(point_in_polygon(
deg_to_rad(20.0),
deg_to_rad(15.0),
&vertices
));
assert!(!point_in_polygon(
deg_to_rad(20.0),
deg_to_rad(5.0),
&vertices
));
}
#[test]
fn test_point_in_polygon_high_latitude() {
let vertices = polygon_from_degrees(&[
(0.0, 80.0),
(90.0, 80.0),
(90.0, 85.0),
(0.0, 85.0),
(0.0, 80.0),
]);
assert!(point_in_polygon(
deg_to_rad(45.0),
deg_to_rad(82.0),
&vertices
));
assert!(!point_in_polygon(
deg_to_rad(45.0),
deg_to_rad(75.0),
&vertices
));
}
}