use proj_core::{
registry, AreaOfInterest, Coord, CoordinateOperationId, SelectionOptions, Transform,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct ParityEntry {
source_epsg: u32,
target_epsg: u32,
probe_lon: f64,
probe_lat: f64,
#[serde(default)]
expected_operation_epsg: Option<u32>,
expected_operation_name: String,
#[serde(default)]
probe_extent_name: String,
}
const KNOWN_DIVERGENCES: &[(u32, u32, &str, &str)] = &[
(
4143,
4326,
"Cote d'Ivoire (Ivory Coast) - offshore",
"C PROJ uses EPSG:1470",
),
(
4149,
4326,
"Europe - Liechtenstein and Switzerland",
"C PROJ uses EPSG:1766",
),
(4152, 4326, "USA - CONUS - onshore", "C PROJ uses EPSG:1901"),
(4156, 4258, "Slovakia", "C PROJ uses EPSG:4829"),
(4156, 4326, "Czechia", "C PROJ uses EPSG:5239"),
(4168, 4326, "Ghana - offshore", "C PROJ uses EPSG:1569"),
(4181, 4258, "Luxembourg", "C PROJ uses EPSG:9938"),
(4201, 4326, "Burkina Faso", "C PROJ uses EPSG:1100"),
(4201, 4326, "Cameroon - onshore", "C PROJ uses EPSG:1100"),
(4201, 4326, "Mali", "C PROJ uses EPSG:1100"),
(4201, 4326, "Senegal - onshore", "C PROJ uses EPSG:1100"),
(
4208,
4326,
"Brazil - Campos; Espirito Santo and Santos basins",
"C PROJ uses EPSG:5051",
),
(
4208,
4326,
"Brazil - Reconcavo and Jacuipe",
"C PROJ uses EPSG:5063",
),
(
4208,
4674,
"Brazil - Reconcavo and Jacuipe",
"C PROJ uses EPSG:5062",
),
(4209, 4326, "Lesotho", "C PROJ uses EPSG:1113"),
(4209, 4326, "Zambia", "C PROJ uses EPSG:1120"),
(
4211,
4326,
"Indonesia - Bali Sea west",
"C PROJ uses EPSG:8452",
),
(
4211,
4326,
"Indonesia - Bali, Java and western Sumatra onshore",
"C PROJ uses EPSG:8452",
),
(4214, 4326, "China - Ordos basin", "C PROJ uses EPSG:15936"),
];
fn load_corpus() -> Vec<ParityEntry> {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../testdata/selection_parity.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 selection_matches_c_proj() {
let corpus = load_corpus();
assert!(!corpus.is_empty(), "selection parity corpus is empty");
let mut matched = 0;
let mut outside_registry = 0;
let mut known = 0;
let mut failures = Vec::new();
for entry in &corpus {
let label = format!(
"EPSG:{}→EPSG:{} at ({:.4}, {:.4}) [{}]",
entry.source_epsg,
entry.target_epsg,
entry.probe_lon,
entry.probe_lat,
entry.probe_extent_name
);
if registry::lookup_epsg(entry.source_epsg).is_none()
|| registry::lookup_epsg(entry.target_epsg).is_none()
{
outside_registry += 1;
continue;
}
let expected_in_registry = entry
.expected_operation_epsg
.map(|code| registry::lookup_operation(CoordinateOperationId(code)).is_some())
.unwrap_or(false);
if !expected_in_registry {
outside_registry += 1;
continue;
}
if KNOWN_DIVERGENCES.iter().any(|(s, t, extent, _)| {
*s == entry.source_epsg
&& *t == entry.target_epsg
&& entry.probe_extent_name.contains(extent)
}) {
known += 1;
continue;
}
let options = SelectionOptions {
area_of_interest: Some(AreaOfInterest::geographic_point(Coord::new(
entry.probe_lon,
entry.probe_lat,
))),
..SelectionOptions::default()
};
let transform = match Transform::with_selection_options(
&format!("EPSG:{}", entry.source_epsg),
&format!("EPSG:{}", entry.target_epsg),
options,
) {
Ok(transform) => transform,
Err(error) => {
failures.push(format!("{label}: transform construction failed: {error}"));
continue;
}
};
let selected = transform.selected_operation();
let selected_code = selected.id.map(|id| id.0);
if selected_code == entry.expected_operation_epsg {
matched += 1;
} else {
failures.push(format!(
"{label}: C PROJ used EPSG:{:?} ({}), proj-core selected EPSG:{:?} ({})",
entry.expected_operation_epsg,
entry.expected_operation_name,
selected_code,
selected.name
));
}
}
eprintln!(
"Selection parity: {matched} matched, {} diverged, {known} known divergences, \
{outside_registry} outside registry subset, {} total",
failures.len(),
corpus.len()
);
if !failures.is_empty() {
panic!(
"{} selection parity failures:\n{}",
failures.len(),
failures.join("\n")
);
}
assert!(matched > 0, "no comparable selection parity entries");
}