use std::sync::OnceLock;
use crate::inspect::model::{CrsInfo, ExtentInfo};
pub const GEOGRAPHIC_EPSG: [i32; 3] = [4326, 4979, 4258];
const COASTLINE_CSV: &str = include_str!("../../assets/coastline.csv");
fn extent_in_lonlat(extent: &ExtentInfo) -> bool {
extent.min[0] >= -180.0
&& extent.max[0] <= 180.0
&& extent.min[1] >= -90.0
&& extent.max[1] <= 90.0
}
pub fn is_geographic(crs: Option<&CrsInfo>, extent: &ExtentInfo) -> bool {
match crs {
Some(c) => GEOGRAPHIC_EPSG.contains(&c.code) && extent_in_lonlat(extent),
None => extent_in_lonlat(extent),
}
}
pub fn coastline_points() -> &'static [(f64, f64)] {
static POINTS: OnceLock<Vec<(f64, f64)>> = OnceLock::new();
POINTS
.get_or_init(|| {
COASTLINE_CSV
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.filter_map(|l| {
let (lon, lat) = l.split_once(',')?;
Some((lon.trim().parse().ok()?, lat.trim().parse().ok()?))
})
.collect()
})
.as_slice()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inspect::model::{CrsInfo, ExtentInfo};
fn geo_extent() -> ExtentInfo {
ExtentInfo {
min: [4.0, 52.0, 0.0],
max: [5.0, 53.0, 10.0],
}
}
fn projected_extent() -> ExtentInfo {
ExtentInfo {
min: [84000.0, 447000.0, 0.0],
max: [85000.0, 448000.0, 10.0],
}
}
#[test]
fn geographic_epsg_with_lonlat_extent_is_geographic() {
let crs = CrsInfo {
authority: Some("EPSG".into()),
code: 4326,
version: 0,
code_string: None,
};
assert!(is_geographic(Some(&crs), &geo_extent()));
}
#[test]
fn projected_epsg_is_not_geographic() {
let crs = CrsInfo {
authority: Some("EPSG".into()),
code: 28992,
version: 0,
code_string: None,
};
assert!(!is_geographic(Some(&crs), &projected_extent()));
}
#[test]
fn no_crs_falls_back_to_extent_bounds() {
assert!(is_geographic(None, &geo_extent()));
assert!(!is_geographic(None, &projected_extent()));
}
#[test]
fn coastline_points_are_within_lonlat_bounds() {
let pts = coastline_points();
assert!(
pts.len() >= 1000,
"expected a substantial coastline, got {}",
pts.len()
);
for &(lon, lat) in pts {
assert!((-180.0..=180.0).contains(&lon), "lon out of range: {lon}");
assert!((-90.0..=90.0).contains(&lat), "lat out of range: {lat}");
}
}
}