From 62a1a500a5f576a9efa45ab80c543f0466457a36 Mon Sep 17 00:00:00 2001 From: ruffson Date: Sat, 17 Oct 2020 11:35:05 +0200 Subject: [PATCH] Add support for GeoTiff geokey dir --- src/decoder/mod.rs | 11 +++ src/geo.rs | 195 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/tags.rs | 8 ++ 4 files changed, 215 insertions(+) create mode 100644 src/geo.rs diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 5b99382..4888763 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -13,6 +13,7 @@ use crate::tags::{ use self::stream::{ ByteOrder, DeflateReader, EndianReader, JpegReader, LZWReader, PackBitsReader, SmartReader, }; +use geo; pub mod ifd; mod stream; @@ -472,6 +473,16 @@ impl Decoder { } _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()), } + + match self.get_tag_u16_vec(Tag::GeoKeyDirectoryTag) { + Ok(geodir) => { + let ascii_params = Result::ok(self.get_tag_ascii_string(Tag::GeoAsciiParamsTag)); + let double_params = Result::ok(self.get_tag_f64_vec(Tag::GeoDoubleParamsTag)); + geo::parse_geo_keys(geodir, ascii_params, double_params); + } + Err(_) => {} + } + Ok(()) } diff --git a/src/geo.rs b/src/geo.rs new file mode 100644 index 0000000..1ac9907 --- /dev/null +++ b/src/geo.rs @@ -0,0 +1,195 @@ +use std::{collections::HashMap, convert::TryFrom}; +// use tags::Tag; + +macro_rules! enum_try_from { + {#[$enum_attr:meta] $vis:vis enum $tyname:ident { $($name:ident = $value:expr),* $(,)* }} => { + #[$enum_attr] + $vis enum $tyname { + $($name = $value,)* + } + + impl ::std::convert::TryFrom for $tyname { + type Error = (); + /// Get enum key for value + fn try_from(num: usize) -> Result { + $( + if num == $value { + Ok($tyname::$name) + } else + )* { + Err(()) + } + } + } + }; +} + +// More info about geotiff keys can be found +// [here](http://geotiff.maptools.org/spec/geotiff2.7.html) +enum_try_from! { + #[derive(Debug, PartialEq, Eq, Hash)] + pub enum GeoKey { + // GeoTIFF Configuration GeoKeys + GTModelTypeGeoKey = 1024, + GTRasterTypeGeoKey = 1025, + GTCitationGeoKey = 1026, + // Geographic CS Parameter GeoKeys + GeographicTypeGeoKey = 2048, + GeogCitationGeoKey = 2049, + GeogGeodeticDatumGeoKey = 2050, + GeogPrimeMeridianGeoKey = 2051, + GeogLinearUnitsGeoKey = 2052, + GeogLinearUnitSizeGeoKey = 2053, + GeogAngularUnitsGeoKey = 2054, + GeogAngularUnitSizeGeoKey = 2055, + GeogEllipsoidGeoKey = 2056, + GeogSemiMajorAxisGeoKey = 2057, + GeogSemiMinorAxisGeoKey = 2058, + GeogInvFlatteningGeoKey = 2059, + GeogAzimuthUnitsGeoKey = 2060, + GeogPrimeMeridianLongGeoKey = 2061, + // Projected CS Parameter GeoKeys + ProjectedCSTypeGeoKey = 3072, + PCSCitationGeoKey = 3073, + // Projection Definition GeoKeys + ProjectionGeoKey = 3074, + ProjCoordTransGeoKey = 3075, + ProjLinearUnitsGeoKey = 3076, + ProjLinearUnitSizeGeoKey = 3077, + ProjStdParallel1GeoKey = 3078, + ProjStdParallel2GeoKey = 3079, + ProjNatOriginLongGeoKey = 3080, + ProjNatOriginLatGeoKey = 3081, + ProjFalseEastingGeoKey = 3082, + ProjFalseNorthingGeoKey = 3083, + ProjFalseOriginLongGeoKey = 3084, + ProjFalseOriginLatGeoKey = 3085, + ProjFalseOriginEastingGeoKey = 3086, + ProjFalseOriginNorthingGeoKey = 3087, + ProjCenterLongGeoKey = 3088, + ProjCenterLatGeoKey = 3089, + ProjCenterEastingGeoKey = 3090, + ProjCenterNorthingGeoKey = 3091, + ProjScaleAtNatOriginGeoKey = 3092, + ProjScaleAtCenterGeoKey = 3093, + ProjAzimuthAngleGeoKey = 3094, + ProjStraightVertPoleLongGeoKey = 3095, + // Vertical CS Parameter Keys + VerticalCSTypeGeoKey = 4096, + VerticalCitationGeoKey = 4097, + VerticalDatumGeoKey = 4098, + VerticalUnitsGeoKey = 4099, +} +} +#[derive(Clone, Debug)] +pub enum GeoKeyType { + Short(u16), + Double(Vec), + Ascii(String), + ShortVec(Vec), +} + +pub fn get_geo_key(geodir: HashMap, key: &GeoKey) -> Option { + let res = geodir.get(key); + + return match res { + Some(val) => Some(val.clone()), + None => None, + }; +} + +pub fn parse_geo_keys( + geokey_dir: Vec, + ascii_params: Option, + double_params: Option>, +) -> HashMap { + let mut geodir: HashMap = HashMap::new(); + if geokey_dir.len() < 4 { + // TODO: or return a error in a result + return geodir; + } + println!( + "Directory Version: {}, {}.{}, dir length: {}", + geokey_dir[0], geokey_dir[1], geokey_dir[2], geokey_dir[3], + ); + + let num_keys = geokey_dir[3] as usize; + if geokey_dir.len() > 4 * num_keys + 4 { + println!("There are some aditional keys!!!"); + } + + for i in 0..num_keys { + let idx = 4 + i * 4; + // Each keyEntry is made up of SHORTS: KeyID, TIFFTagLocation, Count, Value_Offset + let key_id = match GeoKey::try_from(geokey_dir[idx] as usize) { + Ok(value) => value, + Err(_) => { + println!("Unknown tag found with geokey: {}", geokey_dir[idx]); + continue; + } + }; + let tiff_tag_location = geokey_dir[idx + 1]; + let count = geokey_dir[idx + 2]; + let value_offset = geokey_dir[idx + 3]; + + println!( + "KeyID: {:?}, TiffTag: {:?}, Count {:?}, ValueOffset {:?}", + key_id, tiff_tag_location, count, value_offset + ); + + if tiff_tag_location == 0 { + geodir.insert(key_id, GeoKeyType::Short(value_offset as u16)); + } else if tiff_tag_location == 34736 { + // should be accessed rather by Tag::GeoDoubleParamsTag + match &double_params { + Some(double_params) => { + let start = value_offset as usize; + let stop = (value_offset + count) as usize; + + if start > double_params.len() || stop > double_params.len() { + continue; + } + let mut value = vec![0.; count as usize]; + value.copy_from_slice(&double_params[start..stop]); + geodir.insert(key_id, GeoKeyType::Double(value)); + } + None => continue, + } + } else if tiff_tag_location == 34737 { + // should be accessed rather by Tag::GeoAsciiParamsTag + match &ascii_params { + Some(ascii_params) => { + let start = value_offset as usize; + let stop = (value_offset + count) as usize; + + if start > ascii_params.len() || stop > ascii_params.len() { + continue; + } + let value = String::from(&ascii_params[start..stop]); + geodir.insert(key_id, GeoKeyType::Ascii(value)); + } + None => continue, + } + } else if tiff_tag_location == 34735 { + println!("\n===========================================\n\nTHE CASE IS HERE\n\n\n---------------------------------\n"); + // should be accessed rather by Tag::GeoKeyDirectoryTag + // If the tag is the same as the GeoKeyDirectoryTag, the value_offset represents + // SHORT values at the end of the `geokey_dir` itself. + let start = value_offset as usize; + let stop = (value_offset + count) as usize; + + if start > geokey_dir.len() || stop > geokey_dir.len() { + continue; + } + + let mut value = vec![0; count as usize]; + value.copy_from_slice(&geokey_dir[start..stop]); + geodir.insert(key_id, GeoKeyType::ShortVec(value)); + } else { + // undefined + continue; + } + } + println!("{:#?}", geodir); + geodir +} diff --git a/src/lib.rs b/src/lib.rs index 7de0f5e..cb8816d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ mod bytecast; pub mod decoder; pub mod encoder; mod error; +pub mod geo; pub mod tags; pub use self::error::{InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; diff --git a/src/tags.rs b/src/tags.rs index 2741725..1b17cdd 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -120,6 +120,14 @@ pub enum Tag(u16) unknown("A private or extension tag") { TileOffsets = 324, TileByteCounts = 325, SampleFormat = 339, + // GeoTIFF + ModelPixelScaleTag = 33550, // (SoftDesk) + ModelTransformationTag = 34264, // (JPL Carto Group) + ModelTiepointTag = 33922, // (Intergraph) + GeoKeyDirectoryTag = 34735, // (SPOT) + GeoDoubleParamsTag = 34736, // (SPOT) + GeoAsciiParamsTag = 34737, // (SPOT) + // GdalNodata = 42113, } }