diff --git a/CHANGES.md b/CHANGES.md index 9fd3cc1c..f7bf4283 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,9 +36,16 @@ - +- Added `GdalDataType` to provide access to metadata and supporting routines around `GDALDataType` ordinals. +- **Breaking**: `GDALDataType` is no longer `pub use` in `gdal::raster`, + as `GdalType` and `GdalDataType` sufficiently cover use cases in safe code. + Still accessible via `gdal_sys::GDALDataType`. + + - + ## 0.13 -- Add prebuild bindings for GDAL 3.5 +- Add prebuilt bindings for GDAL 3.5 - diff --git a/src/driver.rs b/src/driver.rs index 870c8538..8328d0b7 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -98,7 +98,7 @@ impl Driver { /// let ds = d.create("in-memory", 64, 64, 3)?; /// assert_eq!(ds.raster_count(), 3); /// assert_eq!(ds.raster_size(), (64, 64)); - /// assert_eq!(ds.rasterband(1)?.band_type(), u8::gdal_type()); + /// assert_eq!(ds.rasterband(1)?.band_type(), u8::gdal_ordinal()); /// # Ok(()) /// # } /// ``` @@ -127,7 +127,7 @@ impl Driver { /// let ds = d.create_with_band_type::("in-memory", 64, 64, 3)?; /// assert_eq!(ds.raster_count(), 3); /// assert_eq!(ds.raster_size(), (64, 64)); - /// assert_eq!(ds.rasterband(1)?.band_type(), f64::gdal_type()); + /// assert_eq!(ds.rasterband(1)?.band_type(), f64::gdal_ordinal()); /// # Ok(()) /// # } /// ``` @@ -168,7 +168,7 @@ impl Driver { /// ds.set_spatial_ref(&SpatialRef::from_epsg(4326)?)?; /// assert_eq!(ds.raster_count(), 1); /// assert_eq!(ds.raster_size(), (64, 64)); - /// assert_eq!(ds.rasterband(1)?.band_type(), u8::gdal_type()); + /// assert_eq!(ds.rasterband(1)?.band_type(), u8::gdal_ordinal()); /// assert_eq!(ds.spatial_ref()?.auth_code()?, 4326); /// # Ok(()) /// # } @@ -212,7 +212,7 @@ impl Driver { size_x as c_int, size_y as c_int, bands as c_int, - T::gdal_type(), + T::gdal_ordinal(), options_c.as_ptr(), ) }; diff --git a/src/raster/mdarray.rs b/src/raster/mdarray.rs index ffeb4171..6b6d3697 100644 --- a/src/raster/mdarray.rs +++ b/src/raster/mdarray.rs @@ -164,7 +164,7 @@ impl<'a> MDArray<'a> { let n_dst_buffer_alloc_size = 0; let rv = unsafe { - let data_type = GDALExtendedDataTypeCreate(T::gdal_type()); + let data_type = GDALExtendedDataTypeCreate(T::gdal_ordinal()); if !self.datatype().class().is_numeric() { return Err(GdalError::UnsupportedMdDataType { diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 82330400..1e266b90 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -91,7 +91,7 @@ pub use rasterband::{ StatisticsMinMax, }; pub use rasterize::{rasterize, BurnSource, MergeAlgorithm, OptimizeMode, RasterizeOptions}; -pub use types::{GDALDataType, GdalType}; +pub use types::{AdjustedValue, GdalDataType, GdalType}; pub use warp::reproject; /// Key/value pair for passing driver-specific creation options to diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index 8ca9dde0..1efe22c7 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -1,13 +1,14 @@ use crate::dataset::Dataset; use crate::gdal_major_object::MajorObject; use crate::metadata::Metadata; -use crate::raster::{GDALDataType, GdalType}; +use crate::raster::GdalType; use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; use gdal_sys::{ self, CPLErr, GDALColorEntry, GDALColorInterp, GDALColorTableH, GDALComputeRasterMinMax, - GDALCreateColorRamp, GDALCreateColorTable, GDALDestroyColorTable, GDALGetPaletteInterpretation, - GDALGetRasterStatistics, GDALMajorObjectH, GDALPaletteInterp, GDALRIOResampleAlg, GDALRWFlag, - GDALRasterBandH, GDALRasterIOExtraArg, GDALSetColorEntry, GDALSetRasterColorTable, + GDALCreateColorRamp, GDALCreateColorTable, GDALDataType, GDALDestroyColorTable, + GDALGetPaletteInterpretation, GDALGetRasterStatistics, GDALMajorObjectH, GDALPaletteInterp, + GDALRIOResampleAlg, GDALRWFlag, GDALRasterBandH, GDALRasterIOExtraArg, GDALSetColorEntry, + GDALSetRasterColorTable, }; use libc::c_int; use std::ffi::CString; @@ -232,7 +233,7 @@ impl<'a> RasterBand<'a> { /// use gdal::raster::{GdalType, ResampleAlg}; /// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?; /// let band1 = dataset.rasterband(1)?; - /// assert_eq!(band1.band_type(), u8::gdal_type()); + /// assert_eq!(band1.band_type(), u8::gdal_ordinal()); /// let size = 4; /// let mut buf = vec![0; size*size]; /// band1.read_into_slice::((0, 0), band1.size(), (size, size), buf.as_mut_slice(), Some(ResampleAlg::Bilinear))?; @@ -272,7 +273,7 @@ impl<'a> RasterBand<'a> { buffer.as_mut_ptr() as GDALRasterBandH, size.0 as c_int, size.1 as c_int, - T::gdal_type(), + T::gdal_ordinal(), 0, 0, options_ptr, @@ -302,7 +303,7 @@ impl<'a> RasterBand<'a> { /// use gdal::raster::{GdalType, ResampleAlg}; /// let dataset = Dataset::open("fixtures/m_3607824_se_17_1_20160620_sub.tif")?; /// let band1 = dataset.rasterband(1)?; - /// assert_eq!(band1.band_type(), u8::gdal_type()); + /// assert_eq!(band1.band_type(), u8::gdal_ordinal()); /// let size = 4; /// let buf = band1.read_as::((0, 0), band1.size(), (size, size), Some(ResampleAlg::Bilinear))?; /// assert_eq!(buf.size, (size, size)); @@ -346,7 +347,7 @@ impl<'a> RasterBand<'a> { data.as_mut_ptr() as GDALRasterBandH, size.0 as c_int, size.1 as c_int, - T::gdal_type(), + T::gdal_ordinal(), 0, 0, options_ptr, @@ -463,7 +464,7 @@ impl<'a> RasterBand<'a> { buffer.data.as_ptr() as GDALRasterBandH, buffer.size.0 as c_int, buffer.size.1 as c_int, - T::gdal_type(), + T::gdal_ordinal(), 0, 0, ) diff --git a/src/raster/types.rs b/src/raster/types.rs index 18a75108..10871362 100644 --- a/src/raster/types.rs +++ b/src/raster/types.rs @@ -1,41 +1,522 @@ -pub use gdal_sys::GDALDataType; +use crate::errors::{GdalError, Result}; +use crate::utils::_string; +use gdal_sys::{ + GDALAdjustValueToDataType, GDALDataType, GDALDataTypeIsConversionLossy, GDALDataTypeIsFloating, + GDALDataTypeIsInteger, GDALDataTypeIsSigned, GDALDataTypeUnion, GDALFindDataTypeForValue, + GDALGetDataTypeByName, GDALGetDataTypeName, GDALGetDataTypeSizeBits, GDALGetDataTypeSizeBytes, +}; +use std::ffi::CString; +use std::fmt::{Debug, Display, Formatter}; +/// Provides ergonomic access to functions describing [`GDALDataType`] ordinals. +/// +/// A [`GDALDataType`] indicates the primitive storage value of a cell/pixel in a [`RasterBand`][crate::raster::RasterBand]. +/// +/// # Example +/// ```rust, no_run +/// use gdal::raster::GdalType; +/// let td = ::datatype(); +/// println!("{} is {} and uses {} bits.", +/// td.name(), +/// if td.is_signed() { "signed" } else { "unsigned" }, +/// td.bits() +/// ); +/// ``` +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] +#[repr(u32)] +pub enum GdalDataType { + /// Unknown or unspecified type + Unknown = GDALDataType::GDT_Unknown, + /// Eight bit unsigned integer + UInt8 = GDALDataType::GDT_Byte, + /// Sixteen bit unsigned integer + UInt16 = GDALDataType::GDT_UInt16, + /// Sixteen bit signed integer + Int16 = GDALDataType::GDT_Int16, + /// Thirty two bit unsigned integer + UInt32 = GDALDataType::GDT_UInt32, + /// Thirty two bit signed integer + Int32 = GDALDataType::GDT_Int32, + #[cfg(all(major_ge_3, minor_ge_5))] + /// 64 bit unsigned integer (GDAL >= 3.5) + UInt64 = GDALDataType::GDT_UInt64, + #[cfg(all(major_ge_3, minor_ge_5))] + /// 64 bit signed integer (GDAL >= 3.5) + Int64 = GDALDataType::GDT_Int64, + /// Thirty two bit floating point + Float32 = GDALDataType::GDT_Float32, + /// Sixty four bit floating point + Float64 = GDALDataType::GDT_Float64, +} + +impl GdalDataType { + /// Find `GdalTypeDescriptor` by name, as would be returned by [`name`][Self::name]. + /// + /// # Example + /// + /// ```rust, no_run + /// use gdal::raster::{GdalType, GdalDataType}; + /// assert_eq!(GdalDataType::from_name("UInt16").unwrap(), ::datatype()) + /// ``` + pub fn from_name(name: &str) -> Result { + let c_name = CString::new(name)?; + let gdal_type = unsafe { GDALGetDataTypeByName(c_name.as_ptr()) }; + match gdal_type { + GDALDataType::GDT_Unknown => Err(GdalError::BadArgument(format!( + "unable to find datatype with name '{}'", + name + ))), + _ => gdal_type.try_into(), + } + } + + /// Finds the smallest data type able to support the provided value. + /// + /// See [`GDALFindDataTypeForValue`](https://gdal.org/api/raster_c_api.html#_CPPv424GDALFindDataTypeForValuedi) + /// + /// # Example + /// + /// ```rust, no_run + /// use gdal::raster::{GdalType, GdalDataType}; + /// assert_eq!(GdalDataType::for_value(0), ::datatype()); + /// assert_eq!(GdalDataType::for_value(256), ::datatype()); + /// assert_eq!(GdalDataType::for_value(-1), ::datatype()); + /// assert_eq!(GdalDataType::for_value(::MAX as f64 * -2.0), ::datatype()); + /// ``` + pub fn for_value>(value: N) -> Self { + let gdal_type = unsafe { GDALFindDataTypeForValue(value.into(), 0) }; + Self::try_from(gdal_type).expect("GDALFindDataTypeForValue") + } + + /// Get the name of the [`GDALDataType`]. + /// + /// # Example + /// + /// ```rust, no_run + /// use gdal::raster::GdalType; + /// assert_eq!(::datatype().name(), "UInt16"); + /// ``` + pub fn name(&self) -> String { + let c_str = unsafe { GDALGetDataTypeName(self.gdal_ordinal()) }; + if c_str.is_null() { + // This case shouldn't happen, because `self` only exists for valid + // GDALDataType ordinals. + panic!( + "GDALGetDataTypeName unexpectedly returned an empty name for {:?}", + &self + ); + } + _string(c_str) + } + + /// Get the [`GDALDataType`] size in **bits**. + pub fn bits(&self) -> u8 { + unsafe { GDALGetDataTypeSizeBits(self.gdal_ordinal()) } + .try_into() + .expect("GDALGetDataTypeSizeBits") + } + + /// Get the [`GDALDataType`] size in **bytes**. + pub fn bytes(&self) -> u8 { + unsafe { GDALGetDataTypeSizeBytes(self.gdal_ordinal()) } + .try_into() + .expect("GDALGetDataTypeSizeBytes") + } + + /// Returns `true` if [`GDALDataType`] is integral (non-floating point) + pub fn is_integer(&self) -> bool { + (unsafe { GDALDataTypeIsInteger(self.gdal_ordinal()) }) > 0 + } + + /// Returns `true` if [`GDALDataType`] is floating point (non-integral) + pub fn is_floating(&self) -> bool { + (unsafe { GDALDataTypeIsFloating(self.gdal_ordinal()) }) > 0 + } + + /// Returns `true` if [`GDALDataType`] supports negative values. + pub fn is_signed(&self) -> bool { + (unsafe { GDALDataTypeIsSigned(self.gdal_ordinal()) }) > 0 + } + + /// Return the descriptor for smallest [`GDALDataType`] that fully contains both data types + /// indicated by `self` and `other`. + /// + /// # Example + /// + /// ```rust, no_run + /// use gdal::raster::GdalType; + /// println!("To safely store all possible '{}' and '{}' values, you should use '{}'", + /// ::datatype(), + /// ::datatype(), + /// ::datatype().union(::datatype()) + /// ); + /// ``` + pub fn union(&self, other: Self) -> Self { + let gdal_type = unsafe { GDALDataTypeUnion(self.gdal_ordinal(), other.gdal_ordinal()) }; + Self::try_from(gdal_type).expect("GDALDataTypeUnion") + } + + /// Change a given value to fit within the constraints of this [`GDALDataType`]. + /// + /// Returns an enum indicating if the wrapped value is unchanged, clamped + /// (to min or max datatype value) or rounded (for integral data types). + /// + /// # Example + /// + /// ```rust, no_run + /// use gdal::raster::{GdalType, AdjustedValue::*}; + /// assert_eq!(::datatype().adjust_value(255), Unchanged(255.)); + /// assert_eq!(::datatype().adjust_value(1.2334), Rounded(1.)); + /// assert_eq!(::datatype().adjust_value(1000.2334), Clamped(255.)); + /// ``` + pub fn adjust_value>(&self, value: N) -> AdjustedValue { + let mut is_clamped: libc::c_int = 0; + let mut is_rounded: libc::c_int = 0; + + let result = unsafe { + GDALAdjustValueToDataType( + self.gdal_ordinal(), + value.into(), + &mut is_clamped, + &mut is_rounded, + ) + }; + + match (is_clamped > 0, is_rounded > 0) { + (false, false) => AdjustedValue::Unchanged(result), + (true, false) => AdjustedValue::Clamped(result), + (false, true) => AdjustedValue::Rounded(result), + (true, true) => panic!("Unexpected adjustment result: clamped and rounded."), + } + } + + /// Determine if converting a value from [`GDALDataType`] described by `self` to one + /// described by `other` is potentially lossy. + /// + /// # Example + /// + /// ```rust, no_run + /// use gdal::raster::GdalType; + /// assert!(::datatype().is_conversion_lossy(::datatype())) + /// ``` + pub fn is_conversion_lossy(&self, other: Self) -> bool { + let r = unsafe { GDALDataTypeIsConversionLossy(self.gdal_ordinal(), other.gdal_ordinal()) }; + r != 0 + } + + /// Subset of the GDAL data types supported by Rust bindings. + pub fn iter() -> impl Iterator { + use GdalDataType::*; + [ + UInt8, + UInt16, + Int16, + UInt32, + Int32, + #[cfg(all(major_ge_3, minor_ge_5))] + UInt64, + #[cfg(all(major_ge_3, minor_ge_5))] + Int64, + Float32, + Float64, + ] + .iter() + .copied() + } + + #[inline] + pub(crate) fn gdal_ordinal(&self) -> GDALDataType::Type { + *self as GDALDataType::Type + } +} + +impl Debug for GdalDataType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GdalTypeDescriptor") + .field("name", &self.name()) + .field("bits", &self.bits()) + .field("signed", &self.is_signed()) + .field("floating", &self.is_floating()) + .field("gdal_ordinal", &self.gdal_ordinal()) + .finish() + } +} + +impl Display for GdalDataType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.name()) + } +} + +/// Converts from a possible [`GDALDataType`] ordinal value to a [`GdalTypeDescriptor`]. +/// +/// # Example +/// +/// ```rust, no_run +/// use gdal::raster::GdalDataType; +/// let gdt: GdalDataType = 3.try_into().unwrap(); +/// println!("{gdt:#?}") +/// ``` +impl TryFrom for GdalDataType { + type Error = GdalError; + + fn try_from(value: u32) -> std::result::Result { + use GDALDataType::*; + #[allow(non_upper_case_globals)] + match value { + GDT_Unknown => Ok(GdalDataType::Unknown), + GDT_Byte => Ok(GdalDataType::UInt8), + GDT_UInt16 => Ok(GdalDataType::UInt16), + GDT_Int16 => Ok(GdalDataType::Int16), + GDT_UInt32 => Ok(GdalDataType::UInt32), + GDT_Int32 => Ok(GdalDataType::Int32), + #[cfg(all(major_ge_3, minor_ge_5))] + GDT_UInt64 => Ok(GdalDataType::UInt64), + #[cfg(all(major_ge_3, minor_ge_5))] + GDT_Int64 => Ok(GdalDataType::Int64), + GDT_Float32 => Ok(GdalDataType::Float32), + GDT_Float64 => Ok(GdalDataType::Float64), + GDT_CInt16 | GDT_CInt32 | GDT_CFloat32 | GDT_CFloat64 => Err(GdalError::BadArgument( + "Complex data types are not available".into(), + )), + o => Err(GdalError::BadArgument(format!( + "unknown GDALDataType ordinal' {o}'" + ))), + } + } +} + +/// Return type for [`GdalTypeDescriptor::adjust_value`]. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum AdjustedValue { + /// Value was not changed + Unchanged(f64), + /// Value was clamped to fit within the min/max bounds of data type + Clamped(f64), + /// The value was rounded to fit in an integral type + Rounded(f64), +} + +impl From for f64 { + fn from(av: AdjustedValue) -> Self { + match av { + AdjustedValue::Unchanged(v) => v, + AdjustedValue::Clamped(v) => v, + AdjustedValue::Rounded(v) => v, + } + } +} + +/// Type-level constraint for bounding primitive numeric values for generic +/// functions requiring a [`GDALDataType`]. +/// +/// See [`GdalTypeDescriptor`] for access to metadata describing the data type. pub trait GdalType { - fn gdal_type() -> GDALDataType::Type; + /// Get the [`GDALDataType`] ordinal value used in `gdal_sys` to represent a GDAL cell/pixel + /// data type. + /// + /// See also: [GDAL API](https://gdal.org/api/raster_c_api.html#_CPPv412GDALDataType) + fn gdal_ordinal() -> GDALDataType::Type; + + /// Get the metadata type over a `GdalType`. + /// + /// # Example + /// + /// ```rust, no_run + /// use gdal::raster::GdalType; + /// let gdt = ::datatype(); + /// println!("{gdt:#?}"); + /// ``` + fn datatype() -> GdalDataType { + // We can call `unwrap` because existence is guaranteed in this case. + Self::gdal_ordinal().try_into().expect("GdalDataType") + } } +/// Provides evidence `u8` is a valid [`GDALDataType`]. impl GdalType for u8 { - fn gdal_type() -> GDALDataType::Type { + fn gdal_ordinal() -> GDALDataType::Type { GDALDataType::GDT_Byte } } + +/// Provides evidence `u16` is a valid [`GDALDataType`]. impl GdalType for u16 { - fn gdal_type() -> GDALDataType::Type { + fn gdal_ordinal() -> GDALDataType::Type { GDALDataType::GDT_UInt16 } } + +/// Provides evidence `u32` is a valid [`GDALDataType`]. impl GdalType for u32 { - fn gdal_type() -> GDALDataType::Type { + fn gdal_ordinal() -> GDALDataType::Type { GDALDataType::GDT_UInt32 } } + +#[cfg(all(major_ge_3, minor_ge_5))] +/// Provides evidence `u64` is a valid [`GDALDataType`]. +impl GdalType for u64 { + fn gdal_ordinal() -> GDALDataType::Type { + GDALDataType::GDT_UInt64 + } +} + +/// Provides evidence `i16` is a valid [`GDALDataType`]. impl GdalType for i16 { - fn gdal_type() -> GDALDataType::Type { + fn gdal_ordinal() -> GDALDataType::Type { GDALDataType::GDT_Int16 } } + +/// Provides evidence `i32` is a valid [`GDALDataType`]. impl GdalType for i32 { - fn gdal_type() -> GDALDataType::Type { + fn gdal_ordinal() -> GDALDataType::Type { GDALDataType::GDT_Int32 } } + +#[cfg(all(major_ge_3, minor_ge_5))] +/// Provides evidence `i64` is a valid [`GDALDataType`]. +impl GdalType for i64 { + fn gdal_ordinal() -> GDALDataType::Type { + GDALDataType::GDT_Int64 + } +} + +/// Provides evidence `f32` is a valid [`GDALDataType`]. impl GdalType for f32 { - fn gdal_type() -> GDALDataType::Type { + fn gdal_ordinal() -> GDALDataType::Type { GDALDataType::GDT_Float32 } } + +/// Provides evidence `f64` is a valid [`GDALDataType`]. impl GdalType for f64 { - fn gdal_type() -> GDALDataType::Type { + fn gdal_ordinal() -> GDALDataType::Type { GDALDataType::GDT_Float64 } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::raster::types::AdjustedValue::{Clamped, Rounded, Unchanged}; + use gdal_sys::GDALDataType::*; + + #[test] + #[allow(non_upper_case_globals)] + fn test_gdal_data_type() { + for t in GdalDataType::iter() { + // Test converting from GDALDataType:Type + let t2: GdalDataType = t.gdal_ordinal().try_into().unwrap(); + assert_eq!(&t, &t2, "{}", t); + assert!(t.bits() > 0, "{}", t); + assert_eq!(t.bits(), t.bytes() * 8, "{}", t); + let name = t.name(); + match t.gdal_ordinal() { + GDT_Byte | GDT_UInt16 | GDT_Int16 | GDT_UInt32 | GDT_Int32 => { + assert!(t.is_integer(), "{}", &name); + assert!(!t.is_floating(), "{}", &name); + } + #[cfg(all(major_ge_3, minor_ge_5))] + GDT_UInt64 | GDT_Int64 => { + assert!(t.is_integer(), "{}", &name); + assert!(!t.is_floating(), "{}", &name); + } + GDT_Float32 | GDT_Float64 => { + assert!(!t.is_integer(), "{}", &name); + assert!(t.is_floating(), "{}", &name); + } + + o => panic!("unknown type ordinal '{}'", o), + } + match t.gdal_ordinal() { + GDT_Byte | GDT_UInt16 | GDT_UInt32 => { + assert!(!t.is_signed(), "{}", &name); + } + #[cfg(all(major_ge_3, minor_ge_5))] + GDT_UInt64 => { + assert!(!t.is_signed(), "{}", &name); + } + GDT_Int16 | GDT_Int32 | GDT_Float32 | GDT_Float64 => { + assert!(t.is_signed(), "{}", &name); + } + #[cfg(all(major_ge_3, minor_ge_5))] + GDT_Int64 => { + assert!(t.is_signed(), "{}", &name); + } + o => panic!("unknown type ordinal '{}'", o), + } + } + + for t in [GDT_CInt16, GDT_CInt32, GDT_CFloat32, GDT_CFloat64] { + assert!(TryInto::::try_into(t).is_err()); + } + } + + #[test] + fn test_data_type_from_name() { + assert!(GdalDataType::from_name("foobar").is_err()); + + for t in GdalDataType::iter() { + let name = t.name(); + let t2 = GdalDataType::from_name(&name); + assert!(t2.is_ok()); + } + } + + #[test] + fn test_data_type_union() { + let f32d = ::datatype(); + let f64d = ::datatype(); + + let u8d = ::datatype(); + let u16d = ::datatype(); + let i16d = ::datatype(); + let i32d = ::datatype(); + + // reflexivity + assert_eq!(i16d.union(i16d), i16d); + // symmetry + assert_eq!(i16d.union(f32d), f32d); + assert_eq!(f32d.union(i16d), f32d); + // widening + assert_eq!(u8d.union(u16d), u16d); + assert_eq!(f32d.union(i32d), f64d); + + #[cfg(all(major_ge_3, minor_ge_5))] + { + let u32d = ::datatype(); + let i64d = ::datatype(); + assert_eq!(i16d.union(u32d), i64d); + } + } + + #[test] + fn test_for_value() { + assert_eq!(GdalDataType::for_value(0), ::datatype()); + assert_eq!(GdalDataType::for_value(256), ::datatype()); + assert_eq!(GdalDataType::for_value(-1), ::datatype()); + assert_eq!( + GdalDataType::for_value(::MAX as f64 * -2.0), + ::datatype() + ); + } + + #[test] + fn test_adjust_value() { + assert_eq!(::datatype().adjust_value(255), Unchanged(255.)); + assert_eq!(::datatype().adjust_value(1.2334), Rounded(1.)); + assert_eq!(::datatype().adjust_value(1000.2334), Clamped(255.)); + assert_eq!(::datatype().adjust_value(-1), Clamped(0.)); + assert_eq!(::datatype().adjust_value(-32768), Unchanged(-32768.0)); + assert_eq!(::datatype().adjust_value(-32767.4), Rounded(-32767.0)); + assert_eq!( + ::datatype().adjust_value(1e300), + Clamped(f32::MAX as f64) + ); + let v: f64 = ::datatype().adjust_value(-32767.4).into(); + assert_eq!(v, -32767.0); + } +}