gistools/readers/geotiff/
header.rs

1// https://docs.ogc.org/is/19-008r4/19-008r4.html#_requirements_class_tiff
2use super::constants::GeoTIFFTypes;
3use crate::{
4    parsers::Reader,
5    readers::{GeoKeyDirectoryKeys, GeoStore},
6};
7use alloc::vec::Vec;
8
9/// A key value pair
10#[derive(Debug, Clone, Default, PartialEq)]
11struct KeyValue {
12    key: u16,
13    value: Vec<u8>,
14    field_type: GeoTIFFTypes,
15}
16
17/// A tiepoint structured for decoding images
18#[derive(Debug, Clone, Copy, Default, PartialEq)]
19pub struct GeoTiePoint {
20    /// The i index
21    pub i: f64,
22    /// The j index
23    pub j: f64,
24    /// The k index
25    pub k: f64,
26    /// The x coordinate
27    pub x: f64,
28    /// The y coordinate
29    pub y: f64,
30    /// The z coordinate
31    pub z: f64,
32}
33
34/// The pixel scale
35#[derive(Debug, Clone, Copy, Default, PartialEq)]
36pub struct GeoPixelScale {
37    /// The pixel x scale
38    pub x: f64,
39    /// The pixel y scale
40    pub y: f64,
41    /// The pixel z scale
42    pub z: f64,
43}
44
45/// The image directory
46#[derive(Debug, Clone, Default, PartialEq)]
47pub struct ImageDirectory {
48    /// the geo key directory
49    pub geo_key_directory: GeoStore,
50    /// The pixel scale
51    pub pixel_scale: GeoPixelScale,
52    /// The tie point
53    pub tie_point: GeoTiePoint,
54    /// Variables
55    pub variables: GeoStore,
56}
57impl ImageDirectory {
58    /// Insert a variable into the image directory
59    pub fn insert(&mut self, key: u16, value: Vec<u8>, field_type: GeoTIFFTypes) {
60        self.variables.insert(key, value, field_type);
61    }
62    /// Get the length of variables
63    pub fn len(&self) -> usize {
64        self.variables.len() + self.geo_key_directory.len()
65    }
66    /// Check if no variables are set
67    pub fn is_empty(&self) -> bool {
68        self.geo_key_directory.is_empty() && self.variables.is_empty()
69    }
70}
71
72/// GeoTIFF Header Reader
73#[derive(Debug, Clone, Default, PartialEq)]
74pub struct GeoTIFFHeaderReader {
75    /// true if reading in the data is little endian
76    pub little_endian: bool,
77    /// true if reading in the data is big endian
78    pub big_tiff: bool,
79    /// Key-Value pairs (value is an index pointing to where in the data the value exists)
80    pub image_directories: Vec<ImageDirectory>,
81}
82impl GeoTIFFHeaderReader {
83    /// Create a new GeoTIFFHeaderReader
84    pub fn new<T: Reader>(reader: &T) -> GeoTIFFHeaderReader {
85        let mut tiff_reader = GeoTIFFHeaderReader {
86            little_endian: true,
87            big_tiff: false,
88            image_directories: Vec::new(),
89        };
90        tiff_reader.parse_header(reader);
91
92        tiff_reader
93    }
94
95    /// parses the header data to begin parsing the GeoTIFF
96    fn parse_header<T: Reader>(&mut self, reader: &T) {
97        // pull the endianess from the header
98        let bom = reader.uint16_be(Some(0));
99        if bom == 0x4949 {
100            self.little_endian = true;
101        } else if bom == 0x4d4d {
102            self.little_endian = false;
103        } else {
104            panic!("Invalid byte order value.");
105        }
106        let le = self.little_endian;
107
108        let magic_number = reader.uint16(Some(2), Some(le));
109        if magic_number == 42 {
110            self.big_tiff = false;
111        } else if magic_number == 43 {
112            self.big_tiff = true;
113            let offset_byte_size = reader.uint16(Some(4), Some(le));
114            if offset_byte_size != 8 {
115                panic!("Unsupported offset byte-size.");
116            }
117        } else {
118            panic!("Invalid magic number.");
119        }
120
121        let first_ifd_offset = if self.big_tiff {
122            reader.uint64(Some(8), Some(le))
123        } else {
124            reader.uint32(Some(4), Some(le)) as u64
125        };
126
127        self.get_image_metadata(first_ifd_offset, reader);
128    }
129
130    /// Instructs to parse an image file directory at the given file offset.
131    /// As there is no way to ensure that a location is indeed the start of an IFD,
132    /// this function must be called with caution (e.g only using the IFD offsets from
133    /// the headers or other IFDs).
134    ///
135    /// ## Parameters
136    /// - `first_offset`: the offset to begin parsing the IFDs (Image File Directory) at.
137    fn get_image_metadata<T: Reader>(&mut self, first_offset: u64, reader: &T) {
138        let GeoTIFFHeaderReader { big_tiff, little_endian, .. } = *self;
139        let entry_size = if big_tiff { 20 } else { 12 };
140        let offset_size = if big_tiff { 8 } else { 2 };
141        let mut offset = first_offset;
142
143        let mut ifd_offset = first_offset;
144        while ifd_offset != 0 {
145            let mut ifd = ImageDirectory::default();
146            let num_dir_entries = self.read_tag(offset, reader);
147
148            let mut i = offset + offset_size;
149            let mut geokey_dir_offset: Option<u64> = None;
150            let mut prev_tag = 0;
151            for _ in 0..num_dir_entries {
152                let field_tag = if little_endian {
153                    reader.uint16_le(Some(i))
154                } else {
155                    reader.uint16_be(Some(i))
156                };
157                if field_tag < prev_tag {
158                    panic!("Invalid IFD, {} < {}", field_tag, prev_tag);
159                }
160                prev_tag = field_tag;
161                if field_tag == 33550 {
162                    // GeoPixelScaleTag
163                    ifd.pixel_scale = self.get_pixel_scale(i, reader);
164                } else if field_tag == 33922 {
165                    // TiepointTag
166                    ifd.tie_point = self.get_tiepoint(i, reader);
167                } else if field_tag == 34735 {
168                    // GeoKeyDirectory - map to use after all keys are cached.
169                    geokey_dir_offset = Some(i);
170                }
171                // TIFFTAG_GEODOUBLEPARAMS can just be placed inside the variables section
172                // else if field_tag == 34736 { ... }
173                // TIFFTAG_GEODOUBLEPARAMS can just be placed inside the variables section
174                // else if field_tag == 34737 { ... }
175                else {
176                    let KeyValue { key, value, field_type } =
177                        self.get_key_value(field_tag as u64, i, reader);
178                    ifd.insert(key, value, field_type);
179                }
180                i += entry_size;
181            }
182            // Validate it has a TransformationTag or a TiepointTag before storing
183            if let Some(geokey_dir_offset) = geokey_dir_offset {
184                self.get_geo_key_directory(&mut ifd, geokey_dir_offset, reader);
185            } else {
186                // panic!("No GeoKeyDirectory found. May contain errors");
187            }
188            if !ifd.is_empty() {
189                self.image_directories.push(ifd);
190            } else {
191                break;
192            }
193            // increment offset and check for the next IFD
194            offset += offset_size + entry_size * num_dir_entries;
195            ifd_offset = self.read_tag(offset, reader);
196            offset += offset_size;
197        }
198    }
199
200    /// Reads the value of the tag at the given offset (16 bits if not big_tIFF)
201    ///
202    /// ## Parameters
203    /// - `offset`: the offset to read the tag from
204    ///
205    /// ## Returns
206    /// The value of the tag
207    fn read_tag<T: Reader>(&mut self, offset: u64, reader: &T) -> u64 {
208        let Self { big_tiff, little_endian, .. } = self;
209        if *big_tiff {
210            reader.uint64(Some(offset), Some(*little_endian))
211        } else {
212            reader.uint16(Some(offset), Some(*little_endian)) as u64
213        }
214    }
215
216    /// Reads the value of the tag at the given offset (32 bits if not big_tIFF)
217    ///
218    /// ## Parameters
219    /// - `offset`: the offset to read the tag from
220    ///
221    /// ## Returns
222    /// The value of the tag
223    fn read_offset<T: Reader>(&mut self, offset: u64, reader: &T) -> u64 {
224        let Self { big_tiff, little_endian, .. } = self;
225        if *big_tiff {
226            reader.uint64(Some(offset), Some(*little_endian))
227        } else {
228            reader.uint32(Some(offset), Some(*little_endian)) as u64
229        }
230    }
231
232    /// Get the pixel scale from the GeoKeyDirectory
233    ///
234    /// ## Parameters
235    /// - `offset`: the offset to begin parsing the IFDs (GeoKeyDirectory) at.
236    ///
237    /// ## Returns
238    /// The parsed GeoKeyDirectory
239    fn get_pixel_scale<T: Reader>(&mut self, offset: u64, reader: &T) -> GeoPixelScale {
240        let Self { little_endian, big_tiff, .. } = *self;
241        let field_type = reader.uint16(Some(offset + 2), Some(little_endian));
242        if field_type != 12 {
243            panic!("Invalid GeoKeyDirectory type {}", field_type);
244        }
245        let num_keys = self.read_offset(offset + 4, reader);
246        if num_keys != 3 {
247            panic!("Invalid GeoKeyDirectory num_keys {}", num_keys);
248        }
249        let value_offset = self.read_offset(offset + if big_tiff { 12 } else { 8 }, reader);
250
251        GeoPixelScale {
252            x: reader.f64(Some(value_offset), Some(little_endian)),
253            y: reader.f64(Some(value_offset + 8), Some(little_endian)),
254            z: reader.f64(Some(value_offset + 16), Some(little_endian)),
255        }
256    }
257
258    /// Get the GeoKeyDirectory
259    ///
260    /// [Learn More...](https://docs.ogc.org/is/19-008r4/19-008r4.html#_geokey_directory_test)
261    ///
262    /// ## Parameters
263    /// - `offset`: the offset to begin parsing the IFDs (GeoKeyDirectory) at.
264    /// - `file_dir`: the parsed ImageFileDirectory thus far
265    ///
266    /// ## Returns
267    /// The parsed GeoKeyDirectory
268    fn get_geo_key_directory<T: Reader>(
269        &mut self,
270        ifd: &mut ImageDirectory,
271        offset: u64,
272        reader: &T,
273    ) {
274        let file_dir = &mut ifd.variables;
275        let num_keys = self.read_offset(offset + 4, reader);
276        let value_offset = self.read_offset(offset + (if self.big_tiff { 12 } else { 8 }), reader);
277        let raw_geokeys = reader.slice(Some(value_offset), Some(value_offset + num_keys * 2));
278        let raw_geokeys: Vec<u16> = raw_geokeys
279            .chunks_exact(2)
280            .map(|chunk| u16::from_le_bytes(chunk.try_into().unwrap()))
281            .collect();
282        let geo_key_directory = parse_geotiff_raw_geokeys(&raw_geokeys, file_dir);
283        // Validate that there is a GTModelType GeoKey in the GeoKey Directory
284        if !geo_key_directory.has(GeoKeyDirectoryKeys::GTModelTypeGeoKey as u16) {
285            panic!("Missing \"GTModelTypeGeoKey\" in GeoKeyDirectory");
286        }
287
288        ifd.geo_key_directory = geo_key_directory;
289    }
290
291    /// Get the tiepoint
292    ///
293    /// ## Parameters
294    /// - `offset`: the offset to begin parsing the IFDs (TiepointTag) at.
295    ///
296    /// ## Returns
297    /// The parsed Tiepoint
298    fn get_tiepoint<T: Reader>(&mut self, offset: u64, reader: &T) -> GeoTiePoint {
299        let Self { big_tiff, little_endian, .. } = *self;
300        // Validate that Bytes 2-3 = 12 (Double)
301        let field_type = reader.uint16(Some(offset + 2), Some(little_endian));
302        if field_type != 12 {
303            panic!("Invalid TiepointTag type ${field_type}");
304        }
305        let mut tie_point = GeoTiePoint::default();
306        // get size to the value in Bytes 4-7
307        let count = self.read_offset(offset + 4, reader);
308        // Set TagValue to the value in Bytes 8-11
309        let value_offset = self.read_offset(offset + if big_tiff { 12 } else { 8 }, reader);
310        for i in 0..count {
311            let val = reader.f64(Some(value_offset + i * 8), Some(little_endian));
312            match i {
313                0 => tie_point.i = val,
314                1 => tie_point.j = val,
315                2 => tie_point.k = val,
316                3 => tie_point.x = val,
317                4 => tie_point.y = val,
318                5 => tie_point.z = val,
319                _ => panic!("Invalid TiepointTag index {}", i),
320            }
321        }
322
323        tie_point
324    }
325
326    /// Get the key-value of the tag at the given offset
327    ///
328    /// ## Parameters
329    /// - `field_tag`: the tag to read
330    /// - `offset`: the current offset in the IFD header data
331    ///
332    /// ## Returns
333    /// The parsed key value
334    fn get_key_value<T: Reader>(&mut self, field_tag: u64, offset: u64, reader: &T) -> KeyValue {
335        let field_type = reader.uint16(Some(offset + 2), Some(self.little_endian));
336        let type_count = self.read_offset(offset + 4, reader);
337        let field_type_length = GeoTIFFTypes::from(field_type).to_size();
338        let value_offset = offset + (if self.big_tiff { 12 } else { 8 });
339        let actual_offset =
340            if (field_type_length as u64) * type_count <= (if self.big_tiff { 8 } else { 4 }) {
341                value_offset
342            } else {
343                self.read_offset(value_offset, reader)
344            };
345        let value = self.get_value(
346            field_tag as usize,
347            field_type.into(),
348            type_count,
349            actual_offset,
350            reader,
351        );
352
353        // write the tags value to the file directly
354        KeyValue { key: field_tag as u16, value, field_type: field_type.into() }
355    }
356
357    /// Get the value of the tag
358    ///
359    /// ## Parameters
360    /// - `field_tag`: the tag to read
361    /// - `field_type`: the field type
362    /// - `type_count`: the number of values
363    /// - `value_offset`: the value offset
364    ///
365    /// ## Returns
366    /// The parsed value
367    fn get_value<T: Reader>(
368        &self,
369        _field_tag: usize,
370        field_type: GeoTIFFTypes,
371        type_count: u64,
372        value_offset: u64,
373        reader: &T,
374    ) -> Vec<u8> {
375        match field_type {
376            GeoTIFFTypes::ASCII
377            | GeoTIFFTypes::BYTE
378            | GeoTIFFTypes::UNDEFINED
379            | GeoTIFFTypes::SBYTE => {
380                reader.slice(Some(value_offset), Some(value_offset + type_count))
381            }
382            GeoTIFFTypes::SHORT | GeoTIFFTypes::SSHORT => {
383                reader.slice(Some(value_offset), Some(value_offset + type_count * 2))
384            }
385            GeoTIFFTypes::LONG | GeoTIFFTypes::SLONG | GeoTIFFTypes::FLOAT | GeoTIFFTypes::IFD => {
386                reader.slice(Some(value_offset), Some(value_offset + type_count * 4))
387            }
388            GeoTIFFTypes::RATIONAL | GeoTIFFTypes::SRATIONAL | GeoTIFFTypes::DOUBLE => {
389                reader.slice(Some(value_offset), Some(value_offset + type_count * 8))
390            }
391            GeoTIFFTypes::LONG8 | GeoTIFFTypes::SLONG8 | GeoTIFFTypes::IFD8 => {
392                reader.slice(Some(value_offset), Some(value_offset + type_count * 8))
393            }
394        }
395    }
396}
397
398/// Parse the raw geo keys
399///
400/// ## Parameters
401/// - `raw_geokeys`: the raw geo keys
402/// - `file_dir`: the image file directory
403///
404/// ## Returns
405/// The parsed geo keys
406pub fn parse_geotiff_raw_geokeys(raw_geokeys: &[u16], file_dir: &GeoStore) -> GeoStore {
407    let mut geo_key_directory = GeoStore::default();
408    let mut i = 4;
409    let geo_key_count = raw_geokeys[3] as usize * 4;
410    while i <= geo_key_count {
411        let key = raw_geokeys[i];
412        let location = raw_geokeys[i + 1];
413        let count = raw_geokeys[i + 2] as usize;
414        let offset = raw_geokeys[i + 3];
415
416        if location == 0 {
417            geo_key_directory.set_short(key, offset as i16);
418        } else if let Some((value, _)) = file_dir.get(location) {
419            let offset = offset as usize;
420            geo_key_directory.set(
421                key,
422                value[offset..(offset + count)].to_vec(),
423                GeoTIFFTypes::BYTE,
424            );
425        }
426
427        i += 4;
428    }
429
430    geo_key_directory
431}