#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct RegularLlGeometry {
pub(crate) lat_first: f64,
pub(crate) lon_first: f64,
pub(crate) lat_last: f64,
pub(crate) lon_last: f64,
pub(crate) i_scans_negatively: i64,
pub(crate) j_scans_positively: i64,
pub(crate) ni: i64,
pub(crate) i_direction_increment: f64,
}
pub(crate) fn compute_regular_ll_area(g: RegularLlGeometry) -> Option<[f64; 4]> {
if g.i_scans_negatively != 0 || g.j_scans_positively != 0 {
return None;
}
if !g.lat_first.is_finite()
|| !g.lon_first.is_finite()
|| !g.lat_last.is_finite()
|| !g.lon_last.is_finite()
|| !g.i_direction_increment.is_finite()
{
return None;
}
if g.ni < 2 || g.i_direction_increment <= 0.0 {
return None;
}
let north = g.lat_first;
let south = g.lat_last;
let mut west = g.lon_first;
let east = g.lon_last;
if west > east {
if !is_full_global_i(g.ni, g.i_direction_increment) {
return None;
}
let shifted_span = east - (west - 360.0);
let expected_span = (g.ni - 1) as f64 * g.i_direction_increment;
if (shifted_span - expected_span).abs() >= 0.5 * g.i_direction_increment {
return None;
}
west -= 360.0;
}
if north <= south || west >= east {
return None;
}
Some([north, west, south, east])
}
fn is_full_global_i(ni: i64, i_direction_increment: f64) -> bool {
let span = (ni as f64) * i_direction_increment;
(span - 360.0).abs() < 0.5 * i_direction_increment
}
#[cfg(test)]
mod tests {
use super::*;
fn quarter_degree_global(lon_first: f64, lon_last: f64) -> RegularLlGeometry {
RegularLlGeometry {
lat_first: 90.0,
lat_last: -90.0,
lon_first,
lon_last,
i_scans_negatively: 0,
j_scans_positively: 0,
ni: 1440,
i_direction_increment: 0.25,
}
}
#[test]
fn dateline_first_global_normalises() {
assert_eq!(
compute_regular_ll_area(quarter_degree_global(180.0, 179.75)),
Some([90.0, -180.0, -90.0, 179.75]),
);
}
#[test]
fn greenwich_first_global_no_normalise() {
assert_eq!(
compute_regular_ll_area(quarter_degree_global(0.0, 359.75)),
Some([90.0, 0.0, -90.0, 359.75]),
);
}
#[test]
fn half_degree_grid_also_normalises() {
let g = RegularLlGeometry {
lat_first: 90.0,
lat_last: -90.0,
lon_first: 180.0,
lon_last: 179.5,
i_scans_negatively: 0,
j_scans_positively: 0,
ni: 720,
i_direction_increment: 0.5,
};
assert_eq!(
compute_regular_ll_area(g),
Some([90.0, -180.0, -90.0, 179.5]),
);
}
#[test]
fn tenth_degree_grid_tolerates_f64_roundoff() {
let g = RegularLlGeometry {
lat_first: 90.0,
lat_last: -90.0,
lon_first: 180.0,
lon_last: 179.9,
i_scans_negatively: 0,
j_scans_positively: 0,
ni: 3600,
i_direction_increment: 0.1,
};
let area = compute_regular_ll_area(g).expect("0.1° global must succeed");
assert!((area[1] - (-180.0)).abs() < 1e-9);
}
#[test]
fn greenwich_first_subdomain_passes_through() {
let g = RegularLlGeometry {
lat_first: 70.0,
lat_last: 30.0,
lon_first: -30.0,
lon_last: 50.0,
i_scans_negatively: 0,
j_scans_positively: 0,
ni: 321,
i_direction_increment: 0.25,
};
assert_eq!(compute_regular_ll_area(g), Some([70.0, -30.0, 30.0, 50.0]));
}
#[test]
fn dateline_crossing_regional_bails() {
let g = RegularLlGeometry {
lat_first: 60.0,
lat_last: -60.0,
lon_first: 170.0,
lon_last: -170.0,
i_scans_negatively: 0,
j_scans_positively: 0,
ni: 81,
i_direction_increment: 0.25,
};
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn inconsistent_endpoints_bail_even_when_full_global_in_ni() {
let g = RegularLlGeometry {
lat_first: 90.0,
lat_last: -90.0,
lon_first: 10.0,
lon_last: 5.0,
i_scans_negatively: 0,
j_scans_positively: 0,
ni: 360,
i_direction_increment: 1.0,
};
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn non_standard_i_scan_bails() {
let mut g = quarter_degree_global(180.0, 179.75);
g.i_scans_negatively = 1;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn non_standard_j_scan_bails() {
let mut g = quarter_degree_global(180.0, 179.75);
g.j_scans_positively = 1;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn nan_lat_bails() {
let mut g = quarter_degree_global(180.0, 179.75);
g.lat_first = f64::NAN;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn nan_lon_bails() {
let mut g = quarter_degree_global(180.0, 179.75);
g.lon_last = f64::NAN;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn infinite_increment_bails() {
let mut g = quarter_degree_global(180.0, 179.75);
g.i_direction_increment = f64::INFINITY;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn zero_increment_bails() {
let mut g = quarter_degree_global(180.0, 179.75);
g.i_direction_increment = 0.0;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn negative_increment_bails() {
let mut g = quarter_degree_global(180.0, 179.75);
g.i_direction_increment = -0.25;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn single_column_grid_bails() {
let mut g = quarter_degree_global(0.0, 0.0);
g.ni = 1;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn degenerate_west_equals_east_bails() {
assert_eq!(
compute_regular_ll_area(quarter_degree_global(10.0, 10.0)),
None,
);
}
#[test]
fn degenerate_north_equals_south_bails() {
let mut g = quarter_degree_global(0.0, 359.75);
g.lat_first = 45.0;
g.lat_last = 45.0;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn inverted_latitudes_bail() {
let mut g = quarter_degree_global(0.0, 359.75);
g.lat_first = -90.0;
g.lat_last = 90.0;
assert_eq!(compute_regular_ll_area(g), None);
}
#[test]
fn full_global_test_accepts_exact_and_roundoff() {
assert!(is_full_global_i(1440, 0.25));
assert!(is_full_global_i(720, 0.5));
assert!(is_full_global_i(360, 1.0));
assert!(is_full_global_i(3600, 0.1));
}
#[test]
fn full_global_test_rejects_regional() {
assert!(!is_full_global_i(81, 0.25));
assert!(!is_full_global_i(720, 0.25));
assert!(!is_full_global_i(719, 0.5));
}
}