diff --git a/script/doctest-output.sh b/script/doctest-output.sh new file mode 100755 index 000000000..2b0d5432c --- /dev/null +++ b/script/doctest-output.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# Runs doc-tests without capturing output. + +# Source: + +RUSTDOCFLAGS="-Z unstable-options --nocapture" cargo +nightly test --doc \ No newline at end of file diff --git a/src/raster/tests.rs b/src/raster/tests.rs index efddb58f7..8239e647a 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -2,7 +2,7 @@ use crate::dataset::Dataset; use crate::metadata::Metadata; use crate::raster::rasterband::ResampleAlg; use crate::raster::{ - ByteBuffer, ColorEntry, ColorInterpretation, ColorTable, GdalTypeDescriptor, + ByteBuffer, ColorEntry, ColorInterpretation, ColorTable, GdalType, GdalTypeDescriptor, RasterCreationOption, StatisticsAll, StatisticsMinMax, }; use crate::test_utils::TempFixture; @@ -926,3 +926,40 @@ fn test_gdal_data_type() { } } } + +#[test] +fn test_data_type_from_name() { + assert!(GdalTypeDescriptor::from_name("foobar").is_err()); + + for t in GdalTypeDescriptor::available_types() { + let name = t.name().unwrap(); + let t2 = GdalTypeDescriptor::from_name(&name); + assert!(t2.is_ok()); + } +} + +#[test] +fn test_data_type_union() { + let f32d = ::descriptor(); + let f64d = ::descriptor(); + + let u8d = ::descriptor(); + let u16d = ::descriptor(); + let i16d = ::descriptor(); + let u32d = ::descriptor(); + let i32d = ::descriptor(); + + #[cfg(all(major_ge_3, minor_ge_5))] + let i64d = ::descriptor(); + + // 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))] + assert_eq!(i16d.union(u32d), i64d); +} diff --git a/src/raster/types.rs b/src/raster/types.rs index 5cc51e696..7789d4357 100644 --- a/src/raster/types.rs +++ b/src/raster/types.rs @@ -2,74 +2,40 @@ use crate::errors::{GdalError, Result}; use crate::utils::{_last_null_pointer_err, _string}; pub use gdal_sys::GDALDataType; use gdal_sys::{ - GDALDataTypeIsFloating, GDALDataTypeIsInteger, GDALDataTypeIsSigned, GDALGetDataTypeName, - GDALGetDataTypeSizeBits, GDALGetDataTypeSizeBytes, + GDALDataTypeIsFloating, GDALDataTypeIsInteger, GDALDataTypeIsSigned, GDALDataTypeUnion, + GDALGetDataTypeByName, GDALGetDataTypeName, GDALGetDataTypeSizeBits, GDALGetDataTypeSizeBytes, }; -use std::fmt::{Display, Formatter}; +use std::ffi::CString; +use std::fmt::{Debug, Display, Formatter}; -/// Type-level constraint for limiting which primitive numeric values can be passed -/// to functions needing target data type. -pub trait GdalType { - fn gdal_type() -> GDALDataType::Type; - fn descriptor() -> GdalTypeDescriptor { - // We can call `unwrap` because existence is guaranteed in this case. - Self::gdal_type().try_into().unwrap() - } -} - -impl GdalType for u8 { - fn gdal_type() -> GDALDataType::Type { - GDALDataType::GDT_Byte - } -} - -impl GdalType for u16 { - fn gdal_type() -> GDALDataType::Type { - GDALDataType::GDT_UInt16 - } -} - -impl GdalType for u32 { - fn gdal_type() -> GDALDataType::Type { - GDALDataType::GDT_UInt32 - } -} - -impl GdalType for i16 { - fn gdal_type() -> GDALDataType::Type { - GDALDataType::GDT_Int16 - } -} - -impl GdalType for i32 { - fn gdal_type() -> GDALDataType::Type { - GDALDataType::GDT_Int32 - } -} - -impl GdalType for f32 { - fn gdal_type() -> GDALDataType::Type { - GDALDataType::GDT_Float32 - } -} - -impl GdalType for f64 { - fn gdal_type() -> GDALDataType::Type { - GDALDataType::GDT_Float64 - } -} - -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)] pub struct GdalTypeDescriptor(GDALDataType::Type); impl GdalTypeDescriptor { + /// Find `GDALDataType` by name, as would be returned by [`GdalTypeDescriptor.name()`]. + #[allow(non_snake_case)] + pub fn from_name(name: &str) -> Result { + let c_name = CString::new(name.to_owned())?; + 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(), + } + } + + /// Get the `GDALDataType` ordinal value pub fn gdal_type(&self) -> GDALDataType::Type { self.0 } + + /// Get the name of the `GDALDataType`. pub fn name(&self) -> Result { let c_str = unsafe { GDALGetDataTypeName(self.gdal_type()) }; if c_str.is_null() { - return Err(_last_null_pointer_err("GDALGetDescription")); + return Err(_last_null_pointer_err("GDALGetDataTypeName")); } Ok(_string(c_str)) } @@ -103,6 +69,20 @@ impl GdalTypeDescriptor { (unsafe { GDALDataTypeIsSigned(self.gdal_type()) }) > 0 } + /// Return the smallest data type that can fully express both `self` and + /// `other` data types. + /// + /// # Example + /// + /// ```rust + /// use gdal::raster::GdalType; + /// assert_eq!(::descriptor().union(::descriptor()), ::descriptor()); + /// ``` + pub fn union(&self, other: Self) -> Self { + let gdal_type = unsafe { GDALDataTypeUnion(self.gdal_type(), other.gdal_type()) }; + Self(gdal_type) + } + /// Subset of the GDAL data types supported by Rust bindings. pub fn available_types() -> &'static [GdalTypeDescriptor] { use GDALDataType::*; @@ -112,7 +92,9 @@ impl GdalTypeDescriptor { GdalTypeDescriptor(GDT_Int16), GdalTypeDescriptor(GDT_UInt32), GdalTypeDescriptor(GDT_Int32), + #[cfg(all(major_ge_3, minor_ge_5))] GdalTypeDescriptor(GDT_UInt64), + #[cfg(all(major_ge_3, minor_ge_5))] GdalTypeDescriptor(GDT_Int64), GdalTypeDescriptor(GDT_Float32), GdalTypeDescriptor(GDT_Float64), @@ -120,6 +102,17 @@ impl GdalTypeDescriptor { } } +impl Debug for GdalTypeDescriptor { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GdalTypeDescriptor") + .field("name", &self.name().unwrap_or_else(|e| format!("{e:?}"))) + .field("bits", &self.bits()) + .field("signed", &self.is_signed()) + .field("gdal_ordinal", &self.gdal_type()) + .finish() + } +} + impl Display for GdalTypeDescriptor { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&self.name().unwrap()) @@ -140,3 +133,70 @@ impl TryFrom for GdalTypeDescriptor { } } } + +/// Type-level constraint for bounding primitive numeric values passed +/// to functions requiring a data type. See [`GdalTypeDescriptor`] for access to +/// metadata describing the data type. +pub trait GdalType { + fn gdal_type() -> GDALDataType::Type; + fn descriptor() -> GdalTypeDescriptor { + // We can call `unwrap` because existence is guaranteed in this case. + Self::gdal_type().try_into().unwrap() + } +} + +impl GdalType for u8 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_Byte + } +} + +impl GdalType for u16 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_UInt16 + } +} + +impl GdalType for u32 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_UInt32 + } +} + +#[cfg(all(major_ge_3, minor_ge_5))] +impl GdalType for u64 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_UInt64 + } +} + +impl GdalType for i16 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_Int16 + } +} + +impl GdalType for i32 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_Int32 + } +} + +#[cfg(all(major_ge_3, minor_ge_5))] +impl GdalType for i64 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_Int64 + } +} + +impl GdalType for f32 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_Float32 + } +} + +impl GdalType for f64 { + fn gdal_type() -> GDALDataType::Type { + GDALDataType::GDT_Float64 + } +}