diff --git a/src/extension/mod.rs b/src/extension/mod.rs new file mode 100644 index 00000000..8188e1a5 --- /dev/null +++ b/src/extension/mod.rs @@ -0,0 +1,3 @@ +mod registry; + +pub use registry::{ExtensionRegistry, TiffExtension, TiffExtensionFactory}; diff --git a/src/extension/registry.rs b/src/extension/registry.rs new file mode 100644 index 00000000..eba05783 --- /dev/null +++ b/src/extension/registry.rs @@ -0,0 +1,149 @@ +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, LazyLock}; + +use crate::geo::GeoKeyDirectory; +use crate::tags::Tag; +use crate::{tags, TagValue}; + +/// A registry for extensions that extend the set of tags able to be parsed from the TIFF +/// [`ImageFileDirectory``]. +#[derive(Debug)] +pub struct ExtensionRegistry(HashMap>); + +impl ExtensionRegistry { + /// Create a new extension registry with no extensions registered + pub fn new() -> Self { + Self(HashMap::new()) + } + + pub fn add(&mut self, extension: Box) { + // TODO: assert that no two extensions register the same tag values? + // Or, you could allow multiple extensions to register the same tag, and call all + // known extensions for every tag id they register. + self.0.insert(extension.name().to_string(), extension); + } + + pub(crate) fn inner(&self) -> &HashMap> { + &self.0 + } +} + +/// Something that knows how to create a TIFF extension. +pub trait TiffExtensionFactory: std::fmt::Debug + Send + Sync { + /// The name of the extension. + fn name(&self) -> &str; + + fn from_tags(&self, tag_data: HashMap) -> Box; +} + +/// Something that holds parsed IFD extension data. +pub trait TiffExtension: std::fmt::Debug + Send + Sync { + /// The name of the extension. + fn name(&self) -> &str; + + /// The u16 tag values this extension supports. + fn supported_tags(&self) -> &HashSet; + + fn insert(&mut self, tag: u16, value: TagValue); + + fn finish(&mut self); + + fn parse_tag(&self, tag: u16) -> Option; +} + +pub trait IfdExtension: std::fmt::Debug + Send + Sync {} + +#[derive(Debug)] +pub struct GeoTIFFExtensionFactory; + +impl TiffExtensionFactory for GeoTIFFExtensionFactory { + fn name(&self) -> &str { + "GeoTIFF" + } + + fn from_tags(&self, tag_data: HashMap) -> Box { + let mut geo_key_directory_data = None; + let mut model_pixel_scale = None; + let mut model_tiepoint = None; + let mut model_transformation = None; + let mut geo_ascii_params: Option = None; + let mut geo_double_params: Option> = None; + let mut gdal_nodata = None; + let mut gdal_metadata = None; + + for (k, v) in tag_data {} + } +} + +#[derive(Debug)] +pub struct GeoTIFFExtension { + // Geospatial tags + pub(crate) geo_key_directory: Option, + pub(crate) model_pixel_scale: Option>, + pub(crate) model_tiepoint: Option>, + pub(crate) model_transformation: Option>, + + // GDAL tags + pub(crate) gdal_nodata: Option, + pub(crate) gdal_metadata: Option, +} + +tags! { +enum GeoTIFFTag(u16) { + ModelPixelScale = 33550, + ModelTransformation = 34264, + ModelTiepoint = 33922, + GeoKeyDirectory = 34735, + GeoDoubleParams = 34736, + GeoAsciiParams = 34737, + GdalNodata = 42113, + GdalMetadata = 42112, +} +} + +static GEOTIFF_TAGS: LazyLock> = LazyLock::new(|| { + [ + GeoTIFFTag::ModelPixelScale, + GeoTIFFTag::ModelTransformation, + GeoTIFFTag::ModelTiepoint, + GeoTIFFTag::GeoKeyDirectory, + GeoTIFFTag::GeoDoubleParams, + GeoTIFFTag::GeoAsciiParams, + GeoTIFFTag::GdalNodata, + GeoTIFFTag::GdalMetadata, + ] + .map(|x| x.to_u16()) + .iter() + .copied() + .collect() +}); + +impl TiffExtension for GeoTIFFExtension { + fn name(&self) -> &str { + "GeoTIFF" + } + + fn supported_tags(&self) -> &HashSet { + &GEOTIFF_TAGS + } + + fn insert(&mut self, tag: u16, value: TagValue) { + let geotiff_tag = + GeoTIFFTag::from_u16(tag).expect("tag should be supported by this extension"); + + match geotiff_tag { + // Geospatial tags + // http://geotiff.maptools.org/spec/geotiff2.4.html + GeoTIFFTag::GeoKeyDirectory => { + self.geo_key_directory_data = Some(value.into_u16_vec()?) + } + GeoTIFFTag::ModelPixelScale => model_pixel_scale = Some(value.into_f64_vec()?), + GeoTIFFTag::ModelTiepoint => model_tiepoint = Some(value.into_f64_vec()?), + GeoTIFFTag::ModelTransformation => model_transformation = Some(value.into_f64_vec()?), + GeoTIFFTag::GeoAsciiParams => geo_ascii_params = Some(value.into_string()?), + GeoTIFFTag::GeoDoubleParams => geo_double_params = Some(value.into_f64_vec()?), + GeoTIFFTag::GdalNodata => gdal_nodata = Some(value.into_string()?), + GeoTIFFTag::GdalMetadata => gdal_metadata = Some(value.into_string()?), + } + } +} diff --git a/src/ifd.rs b/src/ifd.rs index 2819764a..04bc2d36 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -5,6 +5,7 @@ use bytes::Bytes; use num_enum::TryFromPrimitive; use crate::error::{AsyncTiffError, AsyncTiffResult, TiffError}; +use crate::extension::{ExtensionRegistry, TiffExtension}; use crate::geo::{GeoKeyDirectory, GeoKeyTag}; use crate::predictor::PredictorInfo; use crate::reader::{AsyncFileReader, Endianness}; @@ -133,15 +134,6 @@ pub struct ImageFileDirectory { pub(crate) copyright: Option, - // Geospatial tags - pub(crate) geo_key_directory: Option, - pub(crate) model_pixel_scale: Option>, - pub(crate) model_tiepoint: Option>, - pub(crate) model_transformation: Option>, - - // GDAL tags - pub(crate) gdal_nodata: Option, - pub(crate) gdal_metadata: Option, pub(crate) other_tags: HashMap, } @@ -150,6 +142,7 @@ impl ImageFileDirectory { pub fn from_tags( tag_data: HashMap, endianness: Endianness, + extension_registry: &ExtensionRegistry, ) -> AsyncTiffResult { let mut new_subfile_type = None; let mut image_width = None; @@ -184,16 +177,13 @@ impl ImageFileDirectory { let mut sample_format = None; let mut jpeg_tables = None; let mut copyright = None; - let mut geo_key_directory_data = None; - let mut model_pixel_scale = None; - let mut model_tiepoint = None; - let mut model_transformation = None; - let mut geo_ascii_params: Option = None; - let mut geo_double_params: Option> = None; - let mut gdal_nodata = None; - let mut gdal_metadata = None; let mut other_tags = HashMap::new(); + let mut extension_instances: HashMap> = extension_registry + .inner() + .iter() + .map(|(name, factory)| (name.clone(), factory.create())) + .collect(); tag_data.into_iter().try_for_each(|(tag, value)| { match tag { @@ -253,20 +243,15 @@ impl ImageFileDirectory { Tag::JPEGTables => jpeg_tables = Some(value.into_u8_vec()?.into()), Tag::Copyright => copyright = Some(value.into_string()?), - // Geospatial tags - // http://geotiff.maptools.org/spec/geotiff2.4.html - Tag::GeoKeyDirectory => geo_key_directory_data = Some(value.into_u16_vec()?), - Tag::ModelPixelScale => model_pixel_scale = Some(value.into_f64_vec()?), - Tag::ModelTiepoint => model_tiepoint = Some(value.into_f64_vec()?), - Tag::ModelTransformation => model_transformation = Some(value.into_f64_vec()?), - Tag::GeoAsciiParams => geo_ascii_params = Some(value.into_string()?), - Tag::GeoDoubleParams => geo_double_params = Some(value.into_f64_vec()?), - Tag::GdalNodata => gdal_nodata = Some(value.into_string()?), - Tag::GdalMetadata => gdal_metadata = Some(value.into_string()?), // Tags for which the tiff crate doesn't have a hard-coded enum variant Tag::Unknown(DOCUMENT_NAME) => document_name = Some(value.into_string()?), - _ => { - other_tags.insert(tag, value); + Tag::Unknown(extension_tag_name) => { + for extension in extension_instances.values() { + if extension.supported_tags().contains(&extension_tag_name) { + extension.insert(extension_tag_name, value); + return Ok(()); + } + } } }; Ok::<_, TiffError>(()) @@ -629,43 +614,43 @@ impl ImageFileDirectory { self.copyright.as_deref() } - /// Geospatial tags - /// - pub fn geo_key_directory(&self) -> Option<&GeoKeyDirectory> { - self.geo_key_directory.as_ref() - } - - /// Used in interchangeable GeoTIFF files. - /// - pub fn model_pixel_scale(&self) -> Option<&[f64]> { - self.model_pixel_scale.as_deref() - } - - /// Used in interchangeable GeoTIFF files. - /// - pub fn model_tiepoint(&self) -> Option<&[f64]> { - self.model_tiepoint.as_deref() - } - - /// Stores a full 4×4 affine transformation matrix that maps pixel/line coordinates directly - /// into model (map) coordinates. - pub fn model_transformation(&self) -> Option<&[f64]> { - self.model_transformation.as_deref() - } - - /// GDAL NoData value - /// - pub fn gdal_nodata(&self) -> Option<&str> { - self.gdal_nodata.as_deref() - } - - /// GDAL Metadata XML information - /// - /// Non standard metadata items are grouped together into a XML string stored in the non - /// standard `TIFFTAG_GDAL_METADATA` ASCII tag (code `42112`). - pub fn gdal_metadata(&self) -> Option<&str> { - self.gdal_metadata.as_deref() - } + // /// Geospatial tags + // /// + // pub fn geo_key_directory(&self) -> Option<&GeoKeyDirectory> { + // self.geo_key_directory.as_ref() + // } + + // /// Used in interchangeable GeoTIFF files. + // /// + // pub fn model_pixel_scale(&self) -> Option<&[f64]> { + // self.model_pixel_scale.as_deref() + // } + + // /// Used in interchangeable GeoTIFF files. + // /// + // pub fn model_tiepoint(&self) -> Option<&[f64]> { + // self.model_tiepoint.as_deref() + // } + + // /// Stores a full 4×4 affine transformation matrix that maps pixel/line coordinates directly + // /// into model (map) coordinates. + // pub fn model_transformation(&self) -> Option<&[f64]> { + // self.model_transformation.as_deref() + // } + + // /// GDAL NoData value + // /// + // pub fn gdal_nodata(&self) -> Option<&str> { + // self.gdal_nodata.as_deref() + // } + + // /// GDAL Metadata XML information + // /// + // /// Non standard metadata items are grouped together into a XML string stored in the non + // /// standard `TIFFTAG_GDAL_METADATA` ASCII tag (code `42112`). + // pub fn gdal_metadata(&self) -> Option<&str> { + // self.gdal_metadata.as_deref() + // } /// Tags for which the tiff crate doesn't have a hard-coded enum variant. pub fn other_tags(&self) -> &HashMap { diff --git a/src/lib.rs b/src/lib.rs index 1b28de35..ffceb904 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod array; mod data_type; +mod extension; #[cfg(feature = "ndarray")] pub mod ndarray; pub mod reader; diff --git a/src/tags.rs b/src/tags.rs index b6a9c165..7551d765 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -2,6 +2,7 @@ #![allow(missing_docs)] #![allow(clippy::upper_case_acronyms)] +#[macro_export] macro_rules! tags { { // Permit arbitrary meta items, which include documentation. @@ -127,15 +128,6 @@ pub enum Tag(u16) unknown("A private or extension tag") { SMaxSampleValue = 341, // TODO add support // JPEG JPEGTables = 347, - // GeoTIFF - ModelPixelScale = 33550, // (SoftDesk) - ModelTransformation = 34264, // (JPL Carto Group) - ModelTiepoint = 33922, // (Intergraph) - GeoKeyDirectory = 34735, // (SPOT) - GeoDoubleParams = 34736, // (SPOT) - GeoAsciiParams = 34737, // (SPOT) - GdalNodata = 42113, // Contains areas with missing data - GdalMetadata = 42112, // XML metadata string } }