use proj_core::Transform;
use serde::Deserialize;
#[derive(Deserialize)]
struct ReferencePoint {
from_epsg: u32,
to_epsg: u32,
input_x: f64,
input_y: f64,
expected_x: f64,
expected_y: f64,
tolerance: f64,
description: String,
}
fn load_corpus() -> Vec<ReferencePoint> {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../testdata/reference_values.json"
);
let data =
std::fs::read_to_string(path).unwrap_or_else(|e| panic!("failed to read {path}: {e}"));
serde_json::from_str(&data).unwrap_or_else(|e| panic!("failed to parse {path}: {e}"))
}
#[test]
fn corpus_matches_c_proj() {
let corpus = load_corpus();
assert!(!corpus.is_empty(), "corpus is empty");
let mut pass = 0;
let mut skip = 0;
let mut failures = Vec::new();
for r in &corpus {
let t = match Transform::from_epsg(r.from_epsg, r.to_epsg) {
Ok(t) => t,
Err(_) => {
skip += 1;
continue;
}
};
let result = match t.convert((r.input_x, r.input_y)) {
Ok(r) => r,
Err(e) => {
failures.push(format!("{}: transform failed: {e}", r.description));
continue;
}
};
let (rx, ry) = result;
let dx = (rx - r.expected_x).abs();
let dy = (ry - r.expected_y).abs();
if dx > r.tolerance || dy > r.tolerance {
failures.push(format!(
"{}: expected ({}, {}), got ({}, {}), delta ({:e}, {:e}), tol {:e}",
r.description, r.expected_x, r.expected_y, rx, ry, dx, dy, r.tolerance
));
} else {
pass += 1;
}
}
eprintln!(
"Corpus results: {} passed, {} skipped, {} failed out of {} total",
pass,
skip,
failures.len(),
corpus.len()
);
if !failures.is_empty() {
panic!(
"{} of {} reference values failed:\n{}",
failures.len(),
corpus.len(),
failures.join("\n")
);
}
}
#[test]
fn corpus_has_adequate_coverage() {
let corpus = load_corpus();
let epsg_targets: Vec<u32> = corpus.iter().map(|r| r.to_epsg).collect();
assert!(epsg_targets.contains(&3857), "missing Web Mercator");
assert!(
epsg_targets.contains(&3413),
"missing Polar Stereographic North"
);
assert!(
epsg_targets.contains(&3031),
"missing Antarctic Polar Stereographic"
);
assert!(epsg_targets.contains(&3395), "missing World Mercator");
assert!(epsg_targets.contains(&2154), "missing Lambert-93");
assert!(epsg_targets.contains(&5070), "missing CONUS Albers");
assert!(epsg_targets.contains(&32662), "missing Plate Carree");
let has_utm = epsg_targets.iter().any(|e| (32601..=32660).contains(e));
assert!(has_utm, "missing UTM north zones");
let has_utm_south = epsg_targets.iter().any(|e| (32701..=32760).contains(e));
assert!(has_utm_south, "missing UTM south zones");
let from_epsgs: Vec<u32> = corpus.iter().map(|r| r.from_epsg).collect();
assert!(from_epsgs.contains(&4267), "missing NAD27 datum shift");
assert!(from_epsgs.contains(&4277), "missing OSGB36 datum shift");
assert!(from_epsgs.contains(&4230), "missing ED50 datum shift");
assert!(from_epsgs.contains(&3857), "missing 3857→4326 inverse");
assert!(from_epsgs.contains(&3413), "missing 3413→4326 inverse");
assert!(
corpus.len() >= 100,
"corpus too small: {} points",
corpus.len()
);
eprintln!("Corpus coverage: {} reference points", corpus.len());
}