use std::path::Path;
use netcdf3::{DataType, FileReader};
use super::BathymetryData;
use crate::{
datatype::{Gradient, Point},
error::{Error, Result},
interpolator,
};
pub(crate) struct CartesianNetcdf3 {
x: Vec<f32>,
y: Vec<f32>,
depth: Vec<f64>,
}
impl BathymetryData for CartesianNetcdf3 {
fn depth(&self, point: &Point<f32>) -> Result<f32> {
let x = point.x();
let y = point.y();
if x.is_nan() || y.is_nan() {
return Ok(f32::NAN);
}
let corner_points = match self.four_corners(x, y) {
Ok(point) => point,
Err(e) => return Err(e),
};
self.interpolate(&corner_points, &(*x, *y))
}
fn depth_and_gradient(&self, point: &Point<f32>) -> Result<(f32, Gradient<f32>)> {
let x = point.x();
let y = point.y();
if x.is_nan() || y.is_nan() {
return Ok((f32::NAN, Gradient::new(f32::NAN, f32::NAN)));
}
let corner_points = match self.four_corners(x, y) {
Ok(point) => point,
Err(e) => return Err(e),
};
let depth = self.interpolate(&corner_points, &(*x, *y))?;
let x_space = self.x[1] as f64 - self.x[0] as f64;
let y_space = self.y[1] as f64 - self.y[0] as f64;
let sw_point = &corner_points[0];
let nw_point = &corner_points[1];
let se_point = &corner_points[3];
let x_gradient = (self.depth_at_indexes(&se_point.0, &se_point.1)?
- self.depth_at_indexes(&sw_point.0, &sw_point.1)?)
/ x_space;
let y_gradient = (self.depth_at_indexes(&nw_point.0, &nw_point.1)?
- self.depth_at_indexes(&sw_point.0, &sw_point.1)?)
/ y_space;
Ok((depth, Gradient::new(x_gradient as f32, y_gradient as f32)))
}
}
impl CartesianNetcdf3 {
#[allow(dead_code)]
pub(crate) fn open(path: &Path, xname: &str, yname: &str, depth_name: &str) -> Result<Self> {
let mut data = FileReader::open(path)?;
let x = data.read_var(xname)?;
let x = match x.data_type() {
DataType::I16 => x
.get_i16_into()
.unwrap()
.iter()
.map(|x| *x as f32)
.collect(),
DataType::I8 => x.get_i8_into().unwrap().iter().map(|x| *x as f32).collect(),
DataType::U8 => x.get_u8_into().unwrap().iter().map(|x| *x as f32).collect(),
DataType::I32 => x
.get_i32_into()
.unwrap()
.iter()
.map(|x| *x as f32)
.collect(),
DataType::F32 => x.get_f32_into().unwrap(),
DataType::F64 => x
.get_f64_into()
.unwrap()
.iter()
.map(|x| *x as f32)
.collect(),
};
let y = data.read_var(yname)?;
let y = match y.data_type() {
DataType::I16 => y
.get_i16_into()
.unwrap()
.iter()
.map(|x| *x as f32)
.collect(),
DataType::I8 => y.get_i8_into().unwrap().iter().map(|x| *x as f32).collect(),
DataType::U8 => y.get_u8_into().unwrap().iter().map(|x| *x as f32).collect(),
DataType::I32 => y
.get_i32_into()
.unwrap()
.iter()
.map(|x| *x as f32)
.collect(),
DataType::F32 => y.get_f32_into().unwrap(),
DataType::F64 => y
.get_f64_into()
.unwrap()
.iter()
.map(|x| *x as f32)
.collect(),
};
let depth = data.read_var(depth_name)?;
let depth = match depth.data_type() {
DataType::I16 => depth
.get_i16_into()
.unwrap()
.iter()
.map(|x| *x as f64)
.collect(),
DataType::I8 => depth
.get_i8_into()
.unwrap()
.iter()
.map(|x| *x as f64)
.collect(),
DataType::U8 => depth
.get_u8_into()
.unwrap()
.iter()
.map(|x| *x as f64)
.collect(),
DataType::I32 => depth
.get_i32_into()
.unwrap()
.iter()
.map(|x| *x as f64)
.collect(),
DataType::F32 => depth
.get_f32_into()
.unwrap()
.iter()
.map(|x| *x as f64)
.collect(),
DataType::F64 => depth.get_f64_into().unwrap(),
};
Ok(CartesianNetcdf3 { x, y, depth })
}
fn nearest(&self, target: &f32, array: &[f32]) -> Result<f32> {
if array.is_empty() {
return Err(Error::IndexOutOfBounds); }
if array.len() == 1 {
return Ok(0.0);
}
let spacing = (array[1] - array[0]).abs();
let index = (target - array[0]) / spacing;
if index < 0.0 || index > (array.len() - 1) as f32 {
Err(Error::IndexOutOfBounds)
} else {
Ok(index)
}
}
fn nearest_point(&self, x: &f32, y: &f32) -> Result<(f32, f32)> {
let xindex = self.nearest(x, &self.x)?;
let yindex = self.nearest(y, &self.y)?;
Ok((xindex, yindex))
}
fn four_corners(&self, x: &f32, y: &f32) -> Result<Vec<(usize, usize)>> {
let (xindex, yindex) = self.nearest_point(x, y)?;
let xlow = 0.0;
let xhigh = (self.x.len() - 1) as f32;
let ylow = 0.0;
let yhigh = (self.y.len() - 1) as f32;
let (x1, x2) = if xindex == xlow {
let x1 = xindex as usize;
let x2 = xindex as usize + 1;
(x1, x2)
} else if xindex == xhigh {
let x1 = xindex as usize - 1;
let x2 = xindex as usize;
(x1, x2)
} else if xindex.fract() == 0.0 {
let x1 = xindex.round() as usize;
let x2 = x1 + 1;
(x1, x2)
} else {
let x1 = xindex.floor() as usize;
let x2 = xindex.ceil() as usize;
(x1, x2)
};
let (y1, y2) = if yindex == ylow {
let y1 = yindex as usize;
let y2 = yindex as usize + 1;
(y1, y2)
} else if yindex == yhigh {
let y1 = yindex as usize - 1;
let y2 = yindex as usize;
(y1, y2)
} else if yindex.fract() == 0.0 {
let y1 = yindex.round() as usize;
let y2 = y1 + 1;
(y1, y2)
} else {
let y1 = yindex.floor() as usize;
let y2 = yindex.ceil() as usize;
(y1, y2)
};
Ok(vec![(x1, y1), (x1, y2), (x2, y2), (x2, y1)])
}
fn interpolate(
&self,
index_points: &[(usize, usize)],
target_point: &(f32, f32),
) -> Result<f32> {
let depth_points = vec![
(
self.x[index_points[0].0],
self.y[index_points[0].1],
self.depth_at_indexes(&index_points[0].0, &index_points[0].1)? as f32,
),
(
self.x[index_points[1].0],
self.y[index_points[1].1],
self.depth_at_indexes(&index_points[1].0, &index_points[1].1)? as f32,
),
(
self.x[index_points[2].0],
self.y[index_points[2].1],
self.depth_at_indexes(&index_points[2].0, &index_points[2].1)? as f32,
),
(
self.x[index_points[3].0],
self.y[index_points[3].1],
self.depth_at_indexes(&index_points[3].0, &index_points[3].1)? as f32,
),
];
interpolator::bilinear(&depth_points, target_point)
}
fn depth_at_indexes(&self, xindex: &usize, yindex: &usize) -> Result<f64> {
let index = self.x.len() * yindex + xindex;
if index >= self.depth.len() {
return Err(Error::IndexOutOfBounds);
}
Ok(self.depth[index])
}
}
#[cfg(test)]
mod test_cartesian_file {
use tempfile::NamedTempFile;
use crate::{
bathymetry::{cartesian_netcdf3::CartesianNetcdf3, BathymetryData},
datatype::Point,
error::Error,
io::utility::create_netcdf3_bathymetry,
};
fn four_depth_fn(x: f32, y: f32) -> f64 {
if x < 25000.0 {
if y < 12500.0 {
20.0
} else {
10.0
}
} else {
if y < 12500.0 {
5.0
} else {
15.0
}
}
}
#[test]
fn test_vars() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
assert!((data.x[10] - 5000.0).abs() < f32::EPSILON)
}
#[test]
fn test_nearest() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
assert!(data.nearest(&5499.0, &data.x).unwrap().round() == 11.0);
assert!(data.nearest(&-1.0, &data.y).is_err());
assert!(data.nearest(&25_501.0, &data.y).is_err());
assert!((data.nearest(&5500.0, &data.x).unwrap() - 11.0).abs() <= f32::EPSILON);
}
#[test]
fn test_nearest_point() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
assert!(data.nearest_point(&1.0, &24_999.0).unwrap().0.round() == 0.0);
assert!(data.nearest_point(&1.0, &24_999.0).unwrap().1.round() == 50.0);
assert!(data.nearest_point(&1.0, &25_001.0).is_err());
assert!(data.nearest_point(&-1.0, &25_000.0).is_err());
assert!((data.nearest_point(&0.0, &25_000.0).unwrap().0 - 0.0).abs() <= f32::EPSILON);
assert!((data.nearest_point(&0.0, &25_000.0).unwrap().1 - 50.0).abs() <= f32::EPSILON);
}
#[test]
fn test_get_corners() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
assert!(
data.four_corners(&0.0, &25_000.0).unwrap() == vec![(0, 49), (0, 50), (1, 50), (1, 49)]
);
assert!(
data.four_corners(&0.0, &5_500.0).unwrap() == vec![(0, 11), (0, 12), (1, 12), (1, 11)]
);
assert!(data.four_corners(&0.0, &0.0).unwrap() == vec![(0, 0), (0, 1), (1, 1), (1, 0)]);
assert!(
data.four_corners(&5_500.0, &25_000.0).unwrap()
== vec![(11, 49), (11, 50), (12, 50), (12, 49)]
);
assert!(
data.four_corners(&5_500.0, &0.0).unwrap() == vec![(11, 0), (11, 1), (12, 1), (12, 0)]
);
assert!(
data.four_corners(&50_000.0, &25_000.0).unwrap()
== vec![(99, 49), (99, 50), (100, 50), (100, 49)]
);
assert!(
data.four_corners(&50_000.0, &5_500.0).unwrap()
== vec![(99, 11), (99, 12), (100, 12), (100, 11)]
);
assert!(
data.four_corners(&50_000.0, &0.0).unwrap()
== vec![(99, 0), (99, 1), (100, 1), (100, 0)]
);
assert!(match data.four_corners(&50_001.0, &0.0) {
Err(Error::IndexOutOfBounds) => true,
_ => false,
});
assert!(match data.four_corners(&50_000.0, &25_001.0) {
Err(Error::IndexOutOfBounds) => true,
_ => false,
});
assert!(match data.four_corners(&-1.0, &0.0) {
Err(Error::IndexOutOfBounds) => true,
_ => false,
});
assert!(match data.four_corners(&50_000.0, &-1.0) {
Err(Error::IndexOutOfBounds) => true,
_ => false,
});
assert!(
data.four_corners(&5_500.0, &5_500.0).unwrap()
== vec![(11, 11), (11, 12), (12, 12), (12, 11)]
);
assert!(
data.four_corners(&5_500.0, &5_750.0).unwrap()
== vec![(11, 11), (11, 12), (12, 12), (12, 11)]
);
assert!(
data.four_corners(&5_750.0, &5_500.0).unwrap()
== vec![(11, 11), (11, 12), (12, 12), (12, 11)]
);
assert!(
data.four_corners(&5_750.0, &5_750.0).unwrap()
== vec![(11, 11), (11, 12), (12, 12), (12, 11)]
);
}
#[test]
fn test_depth() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
let check_depth = vec![
(10099.0, 5099.0, 20.0),
(30099.0, 5099.0, 5.0),
(10099.0, 15099.0, 10.0),
(30099.0, 15099.0, 15.0),
];
for (x, y, h) in &check_depth {
let depth = data.depth_and_gradient(&Point::new(*x, *y)).unwrap().0;
assert!(
(depth - h).abs() < f32::EPSILON,
"Expected {}, but got {}",
h,
depth
);
}
}
#[test]
fn test_x_out_of_bounds() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
if let Error::IndexOutOfBounds = data.depth(&Point::new(-500.1, 500.1)).unwrap_err() {
assert!(true);
} else {
assert!(false);
}
}
#[test]
fn test_y_out_of_bounds() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
if let Error::IndexOutOfBounds = data.depth(&Point::new(500.1, -500.1)).unwrap_err() {
assert!(true);
} else {
assert!(false);
}
}
#[test]
fn test_nan() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
create_netcdf3_bathymetry(&temp_path, 101, 51, 500.0, 500.0, four_depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
let nan = f32::NAN;
assert!(data.depth(&Point::new(nan, nan)).unwrap().is_nan());
assert!(data.depth(&Point::new(10000.0, nan)).unwrap().is_nan());
assert!(data.depth(&Point::new(nan, 10000.0)).unwrap().is_nan());
}
#[test]
fn test_depth_and_gradient_x() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
fn depth_fn(x: f32, _y: f32) -> f64 {
x as f64 * 0.05
}
create_netcdf3_bathymetry(&temp_path, 100, 100, 1.0, 1.0, depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
let dhdx = 0.05;
let dhdy = 0.0;
for x in 0..100 {
for y in 0..100 {
let x = x as f32;
let y = y as f32;
let (depth, gradient) = data.depth_and_gradient(&Point::new(x, y)).unwrap();
assert!(
(gradient.dx() - dhdx).abs() < f32::EPSILON,
"Expected {}, but got {}",
dhdx,
gradient.dx()
);
assert!(
(gradient.dy() - dhdy).abs() < f32::EPSILON,
"Expected {}, but got {}",
dhdy,
gradient.dy()
);
assert!(
(depth - depth_fn(x, y) as f32).abs() < f32::EPSILON,
"Expected {}, but got {}",
depth_fn(x, y),
depth
);
}
}
}
#[test]
fn test_depth_and_gradient_y() {
let temp_file = NamedTempFile::new().unwrap();
let temp_path = temp_file.into_temp_path();
fn depth_fn(_x: f32, y: f32) -> f64 {
y as f64 * 0.05
}
create_netcdf3_bathymetry(&temp_path, 100, 100, 1.0, 1.0, depth_fn);
let data = CartesianNetcdf3::open(&temp_path, "x", "y", "depth").unwrap();
let dhdx = 0.0;
let dhdy = 0.05;
for x in 0..100 {
for y in 0..100 {
let x = x as f32;
let y = y as f32;
let (depth, gradient) = data.depth_and_gradient(&Point::new(x, y)).unwrap();
assert!(
(gradient.dx() - dhdx).abs() < f32::EPSILON,
"Expected {}, but got {}",
dhdx,
gradient.dx()
);
assert!(
(gradient.dy() - dhdy).abs() < f32::EPSILON,
"Expected {}, but got {}",
dhdy,
gradient.dy()
);
assert!(
(depth - depth_fn(x, y) as f32).abs() < f32::EPSILON,
"Expected {}, but got {}",
depth_fn(x, y),
depth
);
}
}
}
}