1pub 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#[derive(Debug, Clone)]
28pub struct GeoKey {
29 pub id: u16,
30 pub value: GeoKeyValue,
31}
32
33#[derive(Debug, Clone)]
35pub enum GeoKeyValue {
36 Short(u16),
38 Double(Vec<f64>),
40 Ascii(String),
42}
43
44#[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 pub fn new() -> Self {
56 Self {
57 version: 1,
58 major_revision: 1,
59 minor_revision: 0,
60 keys: Vec::new(),
61 }
62 }
63
64 pub fn parse(directory: &[u16], double_params: &[f64], ascii_params: &str) -> Option<Self> {
70 if directory.len() < 4 {
71 return None;
72 }
73
74 let version = directory[0];
75 let major_revision = directory[1];
76 let minor_revision = directory[2];
77 let num_keys = directory[3] as usize;
78
79 if directory.len() < 4 + num_keys * 4 {
80 return None;
81 }
82
83 let mut keys = Vec::with_capacity(num_keys);
84 for i in 0..num_keys {
85 let base = 4 + i * 4;
86 let key_id = directory[base];
87 let location = directory[base + 1];
88 let count = directory[base + 2] as usize;
89 let value_offset = directory[base + 3];
90
91 let value = match location {
92 0 => {
93 GeoKeyValue::Short(value_offset)
95 }
96 34736 => {
97 let start = value_offset as usize;
99 let end = start + count;
100 if end <= double_params.len() {
101 GeoKeyValue::Double(double_params[start..end].to_vec())
102 } else {
103 continue;
104 }
105 }
106 34737 => {
107 let start = value_offset as usize;
109 let end = start + count;
110 if end <= ascii_params.len() {
111 let s = ascii_params[start..end]
112 .trim_end_matches('|')
113 .trim_end_matches('\0')
114 .to_string();
115 GeoKeyValue::Ascii(s)
116 } else {
117 continue;
118 }
119 }
120 _ => continue,
121 };
122
123 keys.push(GeoKey { id: key_id, value });
124 }
125
126 Some(Self {
127 version,
128 major_revision,
129 minor_revision,
130 keys,
131 })
132 }
133
134 pub fn get(&self, id: u16) -> Option<&GeoKey> {
136 self.keys.iter().find(|k| k.id == id)
137 }
138
139 pub fn get_short(&self, id: u16) -> Option<u16> {
141 self.get(id).and_then(|k| match &k.value {
142 GeoKeyValue::Short(v) => Some(*v),
143 _ => None,
144 })
145 }
146
147 pub fn get_ascii(&self, id: u16) -> Option<&str> {
149 self.get(id).and_then(|k| match &k.value {
150 GeoKeyValue::Ascii(s) => Some(s.as_str()),
151 _ => None,
152 })
153 }
154
155 pub fn get_double(&self, id: u16) -> Option<&[f64]> {
157 self.get(id).and_then(|k| match &k.value {
158 GeoKeyValue::Double(v) => Some(v.as_slice()),
159 _ => None,
160 })
161 }
162
163 pub fn set(&mut self, id: u16, value: GeoKeyValue) {
165 if let Some(existing) = self.keys.iter_mut().find(|k| k.id == id) {
166 existing.value = value;
167 } else {
168 self.keys.push(GeoKey { id, value });
169 }
170 }
171
172 pub fn remove(&mut self, id: u16) {
174 self.keys.retain(|k| k.id != id);
175 }
176
177 pub fn serialize(&self) -> (Vec<u16>, Vec<f64>, String) {
184 let mut sorted_keys = self.keys.clone();
185 sorted_keys.sort_by_key(|k| k.id);
186
187 let mut directory = Vec::new();
188 let mut double_params = Vec::new();
189 let mut ascii_params = String::new();
190
191 directory.push(self.version);
193 directory.push(self.major_revision);
194 directory.push(self.minor_revision);
195 directory.push(sorted_keys.len() as u16);
196
197 for key in &sorted_keys {
198 directory.push(key.id);
199 match &key.value {
200 GeoKeyValue::Short(v) => {
201 directory.push(0); directory.push(1); directory.push(*v); }
205 GeoKeyValue::Double(v) => {
206 directory.push(34736); directory.push(v.len() as u16);
208 directory.push(double_params.len() as u16); double_params.extend_from_slice(v);
210 }
211 GeoKeyValue::Ascii(s) => {
212 directory.push(34737); let ascii_with_pipe = format!("{}|", s);
214 directory.push(ascii_with_pipe.len() as u16);
215 directory.push(ascii_params.len() as u16); ascii_params.push_str(&ascii_with_pipe);
217 }
218 }
219 }
220
221 (directory, double_params, ascii_params)
222 }
223}
224
225impl Default for GeoKeyDirectory {
226 fn default() -> Self {
227 Self::new()
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn parse_roundtrip() {
237 let mut dir = GeoKeyDirectory::new();
238 dir.set(GT_MODEL_TYPE, GeoKeyValue::Short(2));
239 dir.set(GEOGRAPHIC_TYPE, GeoKeyValue::Short(4326));
240 dir.set(GEOG_CITATION, GeoKeyValue::Ascii("WGS 84".into()));
241
242 let (shorts, doubles, ascii) = dir.serialize();
243 let parsed = GeoKeyDirectory::parse(&shorts, &doubles, &ascii).unwrap();
244
245 assert_eq!(parsed.get_short(GT_MODEL_TYPE), Some(2));
246 assert_eq!(parsed.get_short(GEOGRAPHIC_TYPE), Some(4326));
247 assert_eq!(parsed.get_ascii(GEOG_CITATION), Some("WGS 84"));
248 }
249
250 #[test]
251 fn set_replaces_existing() {
252 let mut dir = GeoKeyDirectory::new();
253 dir.set(GT_MODEL_TYPE, GeoKeyValue::Short(1));
254 dir.set(GT_MODEL_TYPE, GeoKeyValue::Short(2));
255 assert_eq!(dir.get_short(GT_MODEL_TYPE), Some(2));
256 assert_eq!(dir.keys.len(), 1);
257 }
258
259 #[test]
260 fn remove_key() {
261 let mut dir = GeoKeyDirectory::new();
262 dir.set(GT_MODEL_TYPE, GeoKeyValue::Short(1));
263 dir.remove(GT_MODEL_TYPE);
264 assert!(dir.get(GT_MODEL_TYPE).is_none());
265 }
266}