use geo_types::{Rect, coord};
#[inline(always)]
fn normalize_longitude(longitude: f64) -> f64 {
((longitude + 540.0f64) % 360.0f64) - 180.0f64
}
#[derive(Debug)]
pub(crate) struct SplittedRect {
pub(crate) rect: Rect,
pub(crate) difference_due_to_antimeridian_split: f64,
}
pub(crate) fn split_rect_at_antimeridian(rect: Rect) -> Vec<SplittedRect> {
let min_x = rect.min().x;
let max_x = rect.max().x;
let min_x_normalized = normalize_longitude(min_x);
let max_x_normalized = normalize_longitude(max_x);
let mut splits = Vec::new();
if min_x_normalized <= max_x_normalized {
splits.push(SplittedRect {
rect: Rect::new(
coord! {x: min_x_normalized, y: rect.min().y},
coord! {x: max_x_normalized, y: rect.max().y},
),
difference_due_to_antimeridian_split: min_x - min_x_normalized,
});
} else {
if max_x_normalized > -180.0 {
splits.push(SplittedRect {
rect: Rect::new(
coord! {x: -180.0, y: rect.min().y},
coord! {x: max_x_normalized, y: rect.max().y},
),
difference_due_to_antimeridian_split: max_x - max_x_normalized,
});
}
if min_x_normalized < 180.0 {
splits.push(SplittedRect {
rect: Rect::new(
coord! {x: min_x_normalized, y: rect.min().y},
coord! {x: 180.0, y: rect.max().y},
),
difference_due_to_antimeridian_split: min_x - min_x_normalized,
});
}
}
splits
}
#[cfg(test)]
mod tests {
use crate::util::split_rect_at_antimeridian;
use geo_types::{Rect, coord};
#[test]
fn test_split_rect_at_antimeridian_not_crossing() {
let rect = Rect::new(coord! {x: 45.0, y:12.0}, coord! {x:67.0, y: 23.0});
let splitted = split_rect_at_antimeridian(rect);
assert_eq!(splitted.len(), 1);
assert_eq!(splitted[0].rect, rect);
assert_eq!(splitted[0].difference_due_to_antimeridian_split, 0.0);
}
#[test]
fn test_split_rect_at_antimeridian_lower() {
let rect = Rect::new(coord! {x: -185.0, y:12.0}, coord! {x:-178.0, y: 23.0});
let splitted = split_rect_at_antimeridian(rect);
assert_eq!(splitted.len(), 2);
assert_eq!(
splitted[0].rect,
Rect::new(coord! {x: -180.0, y:12.0}, coord! {x: -178.0, y: 23.0})
);
assert_eq!(splitted[0].difference_due_to_antimeridian_split, 0.0);
assert_eq!(
splitted[1].rect,
Rect::new(coord! {x: 175.0, y:12.0}, coord! {x: 180.0, y: 23.0})
);
assert_eq!(splitted[1].difference_due_to_antimeridian_split, -360.0);
}
#[test]
fn test_split_rect_at_antimeridian_upper() {
let rect = Rect::new(coord! {x: 185.0, y:12.0}, coord! {x:178.0, y: 23.0});
let splitted = split_rect_at_antimeridian(rect);
assert_eq!(splitted.len(), 2);
assert_eq!(
splitted[0].rect,
Rect::new(coord! {x: -180.0, y:12.0}, coord! {x: -175.0, y: 23.0})
);
assert_eq!(splitted[0].difference_due_to_antimeridian_split, 360.0);
assert_eq!(
splitted[1].rect,
Rect::new(coord! {x: 178.0, y:12.0}, coord! {x: 180.0, y: 23.0})
);
assert_eq!(splitted[1].difference_due_to_antimeridian_split, 0.0);
}
#[test]
fn test_split_rect_at_antimeridian_fully_east() {
let rect = Rect::new(coord! {x: 180.5, y: 12.0}, coord! {x: 181.5, y: 23.0});
let splitted = split_rect_at_antimeridian(rect);
assert_eq!(splitted.len(), 1);
assert_eq!(
splitted[0].rect,
Rect::new(coord! {x: -179.5, y: 12.0}, coord! {x: -178.5, y: 23.0})
);
assert_eq!(splitted[0].difference_due_to_antimeridian_split, 360.0);
}
#[test]
fn test_split_rect_at_antimeridian_exact_180() {
let rect = Rect::new(coord! {x: 178.0, y: 12.0}, coord! {x: 180.0, y: 23.0});
let splitted = split_rect_at_antimeridian(rect);
assert_eq!(splitted.len(), 1);
assert_eq!(
splitted[0].rect,
Rect::new(coord! {x: 178.0, y: 12.0}, coord! {x: 180.0, y: 23.0})
);
assert_eq!(splitted[0].difference_due_to_antimeridian_split, 0.0);
}
fn assert_split_roundtrips(rect: Rect) {
let min_x = rect.min().x;
let max_x = rect.max().x;
let splitted = split_rect_at_antimeridian(rect);
assert!(!splitted.is_empty(), "no splits produced for {rect:?}");
for s in &splitted {
assert!(s.rect.min().x >= -180.0, "min x {} < -180", s.rect.min().x);
assert!(s.rect.min().x <= 180.0, "min x {} > 180", s.rect.min().x);
assert!(s.rect.max().x >= -180.0, "max x {} < -180", s.rect.max().x);
assert!(s.rect.max().x <= 180.0, "max x {} > 180", s.rect.max().x);
assert!(s.rect.min().x < s.rect.max().x, "zero-width split {s:?}");
for &x_norm in &[s.rect.min().x, s.rect.max().x] {
let x_orig = x_norm + s.difference_due_to_antimeridian_split;
assert!(
x_orig >= min_x - 1e-9 && x_orig <= max_x + 1e-9,
"roundtrip failed: {x_norm} + {} = {x_orig} not in [{min_x}, {max_x}]",
s.difference_due_to_antimeridian_split
);
}
}
}
#[test]
fn test_split_rect_at_antimeridian_roundtrip() {
assert_split_roundtrips(Rect::new(
coord! {x: 45.0, y: 12.0},
coord! {x: 67.0, y: 23.0},
));
assert_split_roundtrips(Rect::new(
coord! {x: -185.0, y: 12.0},
coord! {x: -178.0, y: 23.0},
));
assert_split_roundtrips(Rect::new(
coord! {x: 178.0, y: 12.0},
coord! {x: 185.0, y: 23.0},
));
assert_split_roundtrips(Rect::new(
coord! {x: 180.5, y: 12.0},
coord! {x: 181.5, y: 23.0},
));
assert_split_roundtrips(Rect::new(
coord! {x: -181.5, y: 12.0},
coord! {x: -180.5, y: 23.0},
));
assert_split_roundtrips(Rect::new(
coord! {x: 178.0, y: 12.0},
coord! {x: 180.0, y: 23.0},
));
assert_split_roundtrips(Rect::new(
coord! {x: -180.0, y: 12.0},
coord! {x: -178.0, y: 23.0},
));
}
}