use anyhow::Result;
use float_cmp::assert_approx_eq;
use mappers::{
Ellipsoid,
projections::{LambertConformalConic, LongitudeLatitude},
};
use mappers_warp::{CubicBSpline, RasterBoundsDefinition, Warper, WarperError};
use ndarray::{Array2, Zip, s};
mod utils;
use utils::*;
#[test]
fn result_ok() -> Result<()> {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()?;
let source_bounds =
RasterBoundsDefinition::new((60.00, 68.25), (31.75, 40.0), 0.25, 0.25, src_proj)?;
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)?;
let warper = Warper::initialize::<CubicBSpline, LongitudeLatitude, LambertConformalConic>(
&source_bounds,
&target_bounds,
)?;
let source_raster: Array2<f64> = open_nc_data("./tests/data/waves_34.nc")?;
let ref_raster: Array2<f64> = open_nc_data("./tests/data/waves_ref.nc")?;
let target_raster = warper.warp_unchecked(&source_raster);
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
let target_raster = warper.warp_reject_nodata(&source_raster)?;
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
let target_raster = warper.warp_ignore_nodata(&source_raster)?;
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
let target_raster = warper.warp_discard_nodata(&source_raster)?;
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
#[cfg(feature = "multithreading")]
{
let target_raster = warper.warp_unchecked_parallel(&source_raster);
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
let target_raster = warper.warp_reject_nodata_parallel(&source_raster)?;
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
let target_raster = warper.warp_ignore_nodata_parallel(&source_raster)?;
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
let target_raster = warper.warp_discard_nodata_parallel(&source_raster)?;
assert_eq!(target_raster.shape(), ref_raster.shape());
Zip::from(&target_raster)
.and(&ref_raster)
.map_collect(|&f, &o| assert_approx_eq!(f64, f, o, epsilon = 1e-6));
}
Ok(())
}
#[test]
fn single_nan_input_area() -> Result<()> {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()?;
let source_bounds =
RasterBoundsDefinition::new((60.00, 68.25), (31.75, 40.0), 0.25, 0.25, src_proj)?;
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)?;
let warper = Warper::initialize::<CubicBSpline, LongitudeLatitude, LambertConformalConic>(
&source_bounds,
&target_bounds,
)?;
let mut source_raster: Array2<f64> = open_nc_data("./tests/data/waves_34.nc")?;
source_raster.slice_mut(s![14..15, 18..19]).fill(f64::NAN);
let _ = warper.warp_unchecked(&source_raster);
let target_raster = warper.warp_reject_nodata(&source_raster).unwrap_err();
assert!(matches!(target_raster, WarperError::NanError));
let target_raster = warper.warp_ignore_nodata(&source_raster);
assert!(target_raster.is_ok());
let target_raster = warper.warp_discard_nodata(&source_raster);
assert!(target_raster.is_ok());
#[cfg(feature = "multithreading")]
{
let _ = warper.warp_unchecked_parallel(&source_raster);
let target_raster = warper
.warp_reject_nodata_parallel(&source_raster)
.unwrap_err();
assert!(matches!(target_raster, WarperError::NanError));
let target_raster = warper.warp_ignore_nodata_parallel(&source_raster);
assert!(target_raster.is_ok());
let target_raster = warper.warp_discard_nodata_parallel(&source_raster);
assert!(target_raster.is_ok());
}
Ok(())
}
#[test]
fn multi_nan_input_area() -> Result<()> {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()?;
let source_bounds =
RasterBoundsDefinition::new((60.00, 68.25), (31.75, 40.0), 0.25, 0.25, src_proj)?;
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)?;
let warper = Warper::initialize::<CubicBSpline, LongitudeLatitude, LambertConformalConic>(
&source_bounds,
&target_bounds,
)?;
let mut source_raster: Array2<f64> = open_nc_data("./tests/data/waves_34.nc")?;
source_raster.slice_mut(s![13..15, 13..15]).fill(f64::NAN);
source_raster.slice_mut(s![22..24, 18..20]).fill(f64::NAN);
source_raster.slice_mut(s![18..25, 19..24]).fill(f64::NAN);
source_raster.slice_mut(s![13..15, 21..23]).fill(f64::NAN);
let _ = warper.warp_unchecked(&source_raster);
let target_raster = warper.warp_reject_nodata(&source_raster).unwrap_err();
assert!(matches!(target_raster, WarperError::NanError));
let target_raster = warper.warp_ignore_nodata(&source_raster);
assert!(target_raster.is_ok());
let target_raster = warper.warp_discard_nodata(&source_raster);
assert!(target_raster.is_ok());
#[cfg(feature = "multithreading")]
{
let _ = warper.warp_unchecked_parallel(&source_raster);
let target_raster = warper
.warp_reject_nodata_parallel(&source_raster)
.unwrap_err();
assert!(matches!(target_raster, WarperError::NanError));
let target_raster = warper.warp_ignore_nodata_parallel(&source_raster);
assert!(target_raster.is_ok());
let target_raster = warper.warp_discard_nodata_parallel(&source_raster);
assert!(target_raster.is_ok());
}
Ok(())
}
#[test]
fn non_finite_result() -> Result<()> {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()?;
let source_bounds =
RasterBoundsDefinition::new((60.00, 68.25), (31.75, 40.0), 0.25, 0.25, src_proj)?;
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)?;
let warper = Warper::initialize::<CubicBSpline, LongitudeLatitude, LambertConformalConic>(
&source_bounds,
&target_bounds,
)?;
let mut source_raster: Array2<f64> = open_nc_data("./tests/data/waves_34.nc")?;
source_raster
.slice_mut(s![13..15, 21..23])
.fill(f64::INFINITY);
let _ = warper.warp_unchecked(&source_raster);
assert!(matches!(
warper.warp_discard_nodata(&source_raster).unwrap_err(),
WarperError::WarpingError
));
assert!(matches!(
warper.warp_reject_nodata(&source_raster).unwrap_err(),
WarperError::WarpingError
));
assert!(matches!(
warper.warp_ignore_nodata(&source_raster).unwrap_err(),
WarperError::WarpingError
));
#[cfg(feature = "multithreading")]
{
let _ = warper.warp_unchecked_parallel(&source_raster);
assert!(matches!(
warper
.warp_discard_nodata_parallel(&source_raster)
.unwrap_err(),
WarperError::WarpingError
));
assert!(matches!(
warper
.warp_reject_nodata_parallel(&source_raster)
.unwrap_err(),
WarperError::WarpingError
));
assert!(matches!(
warper
.warp_ignore_nodata_parallel(&source_raster)
.unwrap_err(),
WarperError::WarpingError
));
}
Ok(())
}
#[test]
fn init_error() -> Result<()> {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()?;
let source_bounds =
RasterBoundsDefinition::new((61.00, 67.00), (31.75, 40.0), 0.25, 0.25, src_proj)?;
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)?;
let result = Warper::initialize::<CubicBSpline, LongitudeLatitude, LambertConformalConic>(
&source_bounds,
&target_bounds,
)
.unwrap_err();
assert!(matches!(result, WarperError::SourceRasterTooSmall));
Ok(())
}
#[test]
fn invalid_input_shape() -> Result<()> {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()?;
let source_bounds =
RasterBoundsDefinition::new((60.00, 68.25), (31.75, 40.0), 0.25, 0.25, src_proj)?;
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)?;
let warper = Warper::initialize::<CubicBSpline, _, _>(&source_bounds, &target_bounds)?;
let invalid_source_rasters = vec![
Array2::zeros((10, 10)),
Array2::zeros((34, 10)),
Array2::zeros((10, 34)),
];
for source_raster in invalid_source_rasters {
assert!(matches!(
warper.warp_reject_nodata(&source_raster).unwrap_err(),
WarperError::InvalidRasterDimensions
));
assert!(matches!(
warper.warp_ignore_nodata(&source_raster).unwrap_err(),
WarperError::InvalidRasterDimensions
));
assert!(matches!(
warper.warp_discard_nodata(&source_raster).unwrap_err(),
WarperError::InvalidRasterDimensions
));
#[cfg(feature = "multithreading")]
assert!(matches!(
warper
.warp_reject_nodata_parallel(&source_raster)
.unwrap_err(),
WarperError::InvalidRasterDimensions
));
#[cfg(feature = "multithreading")]
assert!(matches!(
warper
.warp_ignore_nodata_parallel(&source_raster)
.unwrap_err(),
WarperError::InvalidRasterDimensions
));
#[cfg(feature = "multithreading")]
assert!(matches!(
warper
.warp_discard_nodata_parallel(&source_raster)
.unwrap_err(),
WarperError::InvalidRasterDimensions
));
}
Ok(())
}
#[test]
#[should_panic = "Slice end 11 is past end of axis of length 10"]
fn invalid_input_shape_unchecked() {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()
.unwrap();
let source_bounds =
RasterBoundsDefinition::new((60.00, 68.25), (31.75, 40.0), 0.25, 0.25, src_proj).unwrap();
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)
.unwrap();
let warper = Warper::initialize::<CubicBSpline, _, _>(&source_bounds, &target_bounds).unwrap();
let invalid_source_raster = Array2::<f64>::zeros((10, 10));
let _ = warper.warp_unchecked(&invalid_source_raster);
}
#[test]
#[should_panic = "Slice end 11 is past end of axis of length 10"]
#[cfg(feature = "multithreading")]
fn invalid_input_shape_unchecked_parallel() {
let src_proj = LongitudeLatitude;
let tgt_proj = LambertConformalConic::builder()
.ref_lonlat(80., 24.)
.standard_parallels(12.472955, 35.1728044444444)
.ellipsoid(Ellipsoid::WGS84)
.initialize_projection()
.unwrap();
let source_bounds =
RasterBoundsDefinition::new((60.00, 68.25), (31.75, 40.0), 0.25, 0.25, src_proj).unwrap();
let target_bounds = RasterBoundsDefinition::new(
(2_320_000. - 4_000_000., 2_740_000. - 4_000_000.),
(5_090_000. - 4_000_000., 5_640_000. - 4_000_000.),
10_000.,
10_000.,
tgt_proj,
)
.unwrap();
let warper = Warper::initialize::<CubicBSpline, _, _>(&source_bounds, &target_bounds).unwrap();
let invalid_source_raster = Array2::<f64>::zeros((10, 10));
let _ = warper.warp_unchecked_parallel(&invalid_source_raster);
}