mod util;
use mgrs_convert::{Error, LatLon, Mgrs, Precision, UtmUps};
use util::{ground_distance_m, parse_utm_triplet, utmups_from_input};
const TOL_M: f64 = 1e-6;
fn parse_geo(s: &str) -> LatLon {
let toks: Vec<&str> = s.split_whitespace().collect();
LatLon::new(toks[0].parse().expect("lat"), toks[1].parse().expect("lon"))
.expect("valid geo input")
}
fn assert_utm_close(id: &str, got: UtmUps, expected: &str) {
let (zone, north, e, n) = parse_utm_triplet(expected);
assert_eq!(got.zone(), zone, "{id}: zone");
assert_eq!(got.is_north(), north, "{id}: hemisphere");
assert!(
(got.easting() - e).abs() <= TOL_M,
"{id}: easting {} vs {e}",
got.easting()
);
assert!(
(got.northing() - n).abs() <= TOL_M,
"{id}: northing {} vs {n}",
got.northing()
);
}
#[test]
fn frozen_vector_table() {
let rows = util::read_tsv("frozen.tsv");
assert!(rows.len() >= 60, "frozen.tsv is truncated");
let mut executed = 0usize;
for row in &rows {
let (id, kind, input, path, expected) = (&row[0], &row[1], &row[2], &row[3], &row[4]);
if (id.ends_with("raw") && id != "V13raw") || id == "Z0nu" {
continue;
}
if kind == "geoz31" {
continue;
}
match (kind.as_str(), path.as_str()) {
("geo", "utm") => {
let got = parse_geo(input).to_utmups();
assert_utm_close(id, got, expected);
}
("geo", p) if p.starts_with("mgrs") => {
let prec = Precision::new(p[4..].parse().expect("prec")).expect("prec range");
let got = parse_geo(input).to_mgrs(prec).to_string();
assert_eq!(&got, expected, "{id}");
}
("utm" | "ups", p) if p.starts_with("mgrs") => {
let prec = Precision::new(p[4..].parse().expect("prec")).expect("prec range");
let u = utmups_from_input(input).expect("wide range accepts the input");
match u.to_mgrs(prec) {
Ok(m) => assert_eq!(&m.to_string(), expected, "{id}"),
Err(Error::OutsideMgrsExtent { .. }) => {
assert!(
expected.starts_with("ERROR"),
"{id}: unexpected OutsideMgrsExtent"
);
}
Err(e) => panic!("{id}: unexpected error {e}"),
}
}
("utm" | "ups", "geo") => {
let u = utmups_from_input(input).expect("wide range accepts the input");
let got = u.to_latlon();
let toks: Vec<&str> = expected.split_whitespace().collect();
let (elat, elon): (f64, f64) =
(toks[0].parse().expect("lat"), toks[1].parse().expect("lon"));
let d = ground_distance_m(got.lat(), got.lon(), elat, elon);
assert!(d <= TOL_M, "{id}: ground distance {d} m");
}
("mgrs", "geo") => {
let m: Mgrs = input.parse().expect("valid MGRS input");
let got = m.to_latlon();
let toks: Vec<&str> = expected.split_whitespace().collect();
let (elat, elon): (f64, f64) =
(toks[0].parse().expect("lat"), toks[1].parse().expect("lon"));
let d = ground_distance_m(got.lat(), got.lon(), elat, elon);
assert!(d <= TOL_M, "{id}: ground distance {d} m");
}
("mgrs", "utm") => {
let m: Mgrs = input.parse().expect("valid MGRS input");
assert_utm_close(id, m.to_utmups(), expected);
}
other => panic!("unhandled row shape {other:?} in {id}"),
}
executed += 1;
}
assert!(executed >= 55, "only {executed} rows executed");
}
#[test]
fn deliberate_divergences() {
let rows = util::read_tsv("frozen.tsv");
let raw = |id: &str| -> String {
rows.iter()
.find(|r| r[0] == id)
.unwrap_or_else(|| panic!("{id} missing"))[4]
.clone()
};
assert_eq!(raw("V2mraw"), "31MAV660214999999");
let p6 = Precision::new(6).unwrap();
let zero = LatLon::new(0.0, 0.0).unwrap().to_mgrs(p6).to_string();
let negzero = LatLon::new(-0.0, 0.0).unwrap().to_mgrs(p6).to_string();
assert_eq!(zero, "31NAA660214000000");
assert_eq!(negzero, zero);
let u = LatLon::new(-0.0, 0.0).unwrap().to_utmups();
assert!(u.is_north(), "-0.0 is northern");
assert_eq!(u.northing(), 0.0);
assert!(
raw("Z0nu").starts_with("31s"),
"oracle raw -0.0 is southern"
);
assert!(matches!(
"18T".parse::<Mgrs>(),
Err(Error::Parse(mgrs_convert::ParseErrorKind::GridZoneOnly))
));
let divs = util::read_tsv("divergences.tsv");
assert_eq!(divs.len(), 2, "divergence file grew without a test");
}
#[test]
fn latitude_84_matches_the_reference() {
let p11 = Precision::new(11).unwrap();
let at84 = LatLon::new(84.0, 0.0).unwrap();
let u = at84.to_utmups();
assert_eq!(u.zone(), 0, "84 exactly is UPS");
assert!(u.is_north(), "84 exactly is the north cap");
assert_eq!(at84.to_mgrs(p11).to_string(), "ZAA0000000000033272296316");
let below = f64::from_bits(84.0_f64.to_bits() - 1);
let last_utm = LatLon::new(below, 0.0).unwrap();
assert_eq!(last_utm.to_utmups().zone(), 31);
assert_eq!(
last_utm.to_mgrs(p11).to_string(),
"31XDP6500534493829005182447"
);
}