use crate::foundation::{GeoError, GridGeometry, Result};
use crate::io::SurfaceData;
use ndarray::{Array2, ShapeBuilder};
use std::path::Path;
const UNDEF_IRAP_ASCII: f64 = 9999900.0;
fn next_f64<'a>(it: &mut impl Iterator<Item = &'a str>, what: &str) -> Result<f64> {
let t = it
.next()
.ok_or_else(|| GeoError::Parse(format!("IRAP classic: missing {what}")))?;
t.parse::<f64>()
.map_err(|e| GeoError::Parse(format!("IRAP classic: bad {what} '{t}': {e}")))
}
fn next_usize<'a>(it: &mut impl Iterator<Item = &'a str>, what: &str) -> Result<usize> {
let t = it
.next()
.ok_or_else(|| GeoError::Parse(format!("IRAP classic: missing {what}")))?;
t.parse::<usize>()
.map_err(|e| GeoError::Parse(format!("IRAP classic: bad {what} '{t}': {e}")))
}
pub fn load_irap_classic(path: &Path) -> Result<SurfaceData> {
let text = crate::io::decode_latin1(&std::fs::read(path)?);
parse_irap_classic(&text)
}
fn parse_irap_classic(text: &str) -> Result<SurfaceData> {
let mut it = text.split_whitespace();
let id = {
let t = it
.next()
.ok_or_else(|| GeoError::Parse("IRAP classic: empty file".into()))?;
t.parse::<i64>()
.map_err(|e| GeoError::Parse(format!("IRAP classic: bad id flag '{t}': {e}")))?
};
if id != -996 {
return Err(GeoError::Parse(format!(
"IRAP classic: expected id -996, got {id}"
)));
}
let nrow = next_usize(&mut it, "nrow")?;
let xinc = next_f64(&mut it, "xinc")?;
let yinc = next_f64(&mut it, "yinc")?;
let _xmin = next_f64(&mut it, "xmin")?;
let _xmax = next_f64(&mut it, "xmax")?; let _ymin = next_f64(&mut it, "ymin")?;
let _ymax = next_f64(&mut it, "ymax")?;
let ncol = next_usize(&mut it, "ncol")?;
let rotation = next_f64(&mut it, "rotation")?;
let x0 = next_f64(&mut it, "x0")?;
let y0 = next_f64(&mut it, "y0")?;
for _ in 0..7 {
let _ = next_f64(&mut it, "reserved value")?;
}
let geom = GridGeometry {
xori: x0,
yori: y0,
xinc: xinc.abs(),
yinc: yinc.abs(),
ncol,
nrow,
rotation_deg: rotation,
yflip: yinc < 0.0,
};
let n = ncol
.checked_mul(nrow)
.ok_or_else(|| GeoError::Parse("IRAP classic: ncol*nrow overflows".into()))?;
let mut data = Vec::with_capacity(n);
for _ in 0..n {
let v = next_f64(&mut it, "grid value")?;
data.push(if v >= UNDEF_IRAP_ASCII { f64::NAN } else { v });
}
let values = Array2::from_shape_vec((ncol, nrow).f(), data)
.map_err(|e| GeoError::Parse(format!("IRAP classic: shape error: {e}")))?;
SurfaceData::new(geom, values)
}
pub fn save_irap_classic(path: &Path, geom: &GridGeometry, values: &Array2<f64>) -> Result<()> {
if values.dim() != (geom.ncol, geom.nrow) {
return Err(GeoError::GeometryMismatch(format!(
"save_irap_classic: values shape {:?} != grid (ncol={}, nrow={})",
values.dim(),
geom.ncol,
geom.nrow
)));
}
let yinc_signed = geom.yinc * geom.yflip_factor();
let xmax = geom.xori + (geom.ncol.saturating_sub(1)) as f64 * geom.xinc;
let ymax = geom.yori + (geom.nrow.saturating_sub(1)) as f64 * geom.yinc;
let mut lines = vec![
format!("-996 {} {} {}", geom.nrow, geom.xinc, yinc_signed),
format!("{} {} {} {}", geom.xori, xmax, geom.yori, ymax),
format!(
"{} {} {} {}",
geom.ncol, geom.rotation_deg, geom.xori, geom.yori
),
"0 0 0 0 0 0 0".to_string(),
];
let mut tokens = Vec::with_capacity(geom.ncol * geom.nrow);
for j in 0..geom.nrow {
for i in 0..geom.ncol {
let v = values[[i, j]];
tokens.push(if v.is_nan() {
UNDEF_IRAP_ASCII.to_string()
} else {
v.to_string()
});
}
}
for chunk in tokens.chunks(6) {
lines.push(chunk.join(" "));
}
let mut out = lines.join("\n");
out.push('\n');
std::fs::write(path, out)?;
Ok(())
}