Skip to main content

geotiff_reader/
geokeys.rs

1//! GeoKey directory parsing (TIFF tag 34735).
2//!
3//! The GeoKey directory is stored as a TIFF SHORT array with the structure:
4//! - Header: KeyDirectoryVersion, KeyRevision, MinorRevision, NumberOfKeys
5//! - Entries: KeyID, TIFFTagLocation, Count, ValueOffset (repeated)
6//!
7//! GeoKeys reference values either inline (location=0), from the
8//! GeoDoubleParams tag (34736), or from the GeoAsciiParams tag (34737).
9
10// Well-known GeoKey IDs.
11pub const GT_MODEL_TYPE: u16 = 1024;
12pub const GT_RASTER_TYPE: u16 = 1025;
13pub const GEOGRAPHIC_TYPE: u16 = 2048;
14pub const GEOG_CITATION: u16 = 2049;
15pub const GEOG_GEODETIC_DATUM: u16 = 2050;
16pub const GEOG_ANGULAR_UNITS: u16 = 2054;
17pub const PROJECTED_CS_TYPE: u16 = 3072;
18pub const PROJ_CITATION: u16 = 3073;
19pub const PROJECTION: u16 = 3074;
20pub const PROJ_COORD_TRANS: u16 = 3075;
21pub const PROJ_LINEAR_UNITS: u16 = 3076;
22pub const VERTICAL_CS_TYPE: u16 = 4096;
23pub const VERTICAL_DATUM: u16 = 4098;
24pub const VERTICAL_UNITS: u16 = 4099;
25
26/// A parsed GeoKey entry.
27#[derive(Debug, Clone)]
28pub struct GeoKey {
29    pub id: u16,
30    pub value: GeoKeyValue,
31}
32
33/// The value of a GeoKey.
34#[derive(Debug, Clone)]
35pub enum GeoKeyValue {
36    /// Short value stored inline.
37    Short(u16),
38    /// Double value(s) from GeoDoubleParams.
39    Double(Vec<f64>),
40    /// ASCII string from GeoAsciiParams.
41    Ascii(String),
42}
43
44/// Parsed GeoKey directory.
45#[derive(Debug, Clone)]
46pub struct GeoKeyDirectory {
47    pub version: u16,
48    pub major_revision: u16,
49    pub minor_revision: u16,
50    pub keys: Vec<GeoKey>,
51}
52
53impl GeoKeyDirectory {
54    /// Parse the GeoKey directory from the three GeoTIFF tags.
55    ///
56    /// - `directory`: contents of tag 34735 (SHORT array)
57    /// - `double_params`: contents of tag 34736 (DOUBLE array), may be empty
58    /// - `ascii_params`: contents of tag 34737 (ASCII), may be empty
59    pub fn parse(directory: &[u16], double_params: &[f64], ascii_params: &str) -> Option<Self> {
60        if directory.len() < 4 {
61            return None;
62        }
63
64        let version = directory[0];
65        let major_revision = directory[1];
66        let minor_revision = directory[2];
67        let num_keys = directory[3] as usize;
68
69        if directory.len() < 4 + num_keys * 4 {
70            return None;
71        }
72
73        let mut keys = Vec::with_capacity(num_keys);
74        for i in 0..num_keys {
75            let base = 4 + i * 4;
76            let key_id = directory[base];
77            let location = directory[base + 1];
78            let count = directory[base + 2] as usize;
79            let value_offset = directory[base + 3];
80
81            let value = match location {
82                0 => {
83                    // Value is the offset itself (short).
84                    GeoKeyValue::Short(value_offset)
85                }
86                34736 => {
87                    // Value is in GeoDoubleParams.
88                    let start = value_offset as usize;
89                    let end = start + count;
90                    if end <= double_params.len() {
91                        GeoKeyValue::Double(double_params[start..end].to_vec())
92                    } else {
93                        continue;
94                    }
95                }
96                34737 => {
97                    // Value is in GeoAsciiParams.
98                    let start = value_offset as usize;
99                    let end = start + count;
100                    if end <= ascii_params.len() {
101                        let s = ascii_params[start..end]
102                            .trim_end_matches('|')
103                            .trim_end_matches('\0')
104                            .to_string();
105                        GeoKeyValue::Ascii(s)
106                    } else {
107                        continue;
108                    }
109                }
110                _ => continue,
111            };
112
113            keys.push(GeoKey { id: key_id, value });
114        }
115
116        Some(Self {
117            version,
118            major_revision,
119            minor_revision,
120            keys,
121        })
122    }
123
124    /// Look up a GeoKey by ID.
125    pub fn get(&self, id: u16) -> Option<&GeoKey> {
126        self.keys.iter().find(|k| k.id == id)
127    }
128
129    /// Get a short value for a key.
130    pub fn get_short(&self, id: u16) -> Option<u16> {
131        self.get(id).and_then(|k| match &k.value {
132            GeoKeyValue::Short(v) => Some(*v),
133            _ => None,
134        })
135    }
136
137    /// Get an ASCII value for a key.
138    pub fn get_ascii(&self, id: u16) -> Option<&str> {
139        self.get(id).and_then(|k| match &k.value {
140            GeoKeyValue::Ascii(s) => Some(s.as_str()),
141            _ => None,
142        })
143    }
144
145    /// Get double value(s) for a key.
146    pub fn get_double(&self, id: u16) -> Option<&[f64]> {
147        self.get(id).and_then(|k| match &k.value {
148            GeoKeyValue::Double(v) => Some(v.as_slice()),
149            _ => None,
150        })
151    }
152}