dted2/dted.rs
1//! Contains the primary abstract entities parsed from a DTED file.
2//!
3//! The main entry point is [`DTEDData`].
4
5// --------------------------------------------------
6// external
7// --------------------------------------------------
8use std::io::Read;
9use thisenum::Const;
10
11// --------------------------------------------------
12// local
13// --------------------------------------------------
14use crate::parsers;
15use crate::primitives::{self, Angle, AxisElement};
16use crate::Error as DTEDError;
17
18// --------------------------------------------------
19// constants
20// --------------------------------------------------
21/// User Header Label (UHL) Length
22pub const DT2_UHL_LENGTH: u64 = 80;
23/// Data Set Identification (DSI) Record Length
24pub const DT2_DSI_RECORD_LENGTH: usize = 648;
25/// Accuracy Description (ACC) Record Length
26pub const DT2_ACC_RECORD_LENGTH: usize = 2700;
27
28#[derive(Const)]
29#[armtype(&[u8])]
30/// DTED Recognition Sentinels
31///
32/// Used to locate DTED data and DTED records
33///
34/// See: [https://www.dlr.de/de/eoc/downloads/dokumente/7_sat_miss/SRTM-XSAR-DEM-DTED-1.1.pdf](https://www.dlr.de/de/eoc/downloads/dokumente/7_sat_miss/SRTM-XSAR-DEM-DTED-1.1.pdf)
35///
36/// # Examples
37///
38/// ```
39/// use nom::bytes::complete::tag;
40/// use dted2::dted::RecognitionSentinel;
41///
42/// assert_eq!(RecognitionSentinel::UHL.value(), b"UHL1");
43/// assert_eq!(RecognitionSentinel::DSI.value(), b"DSIU");
44/// assert_eq!(RecognitionSentinel::ACC.value(), b"ACC");
45/// assert_eq!(RecognitionSentinel::DATA.value(), &[0xAA]);
46///
47/// fn is_user_header_label(input: &[u8]) -> nom::IResult<&[u8], ()> {
48/// let (input, _) = tag(RecognitionSentinel::UHL.value())(input)?;
49/// Ok((input, ()))
50/// }
51///
52/// assert!(is_user_header_label(b"DSI").is_err());
53/// assert!(is_user_header_label(b"UHL1").is_ok());
54/// assert!(is_user_header_label(b"xxxUHL1xxx").is_err());
55/// ```
56pub enum RecognitionSentinel {
57 /// User Header Label
58 #[value = b"UHL1"] // 85 72 76 49
59 UHL,
60 /// Data Set Identification
61 #[value = b"DSIU"] // 68 83 73 85
62 DSI,
63 /// Accuracy Description
64 #[value = b"ACC"] // 65 67 67
65 ACC,
66 /// Data Record
67 #[value = b"\xAA"] // 170
68 DATA,
69 /// Not Available
70 #[value = b"NA"] // 78 65
71 NA,
72}
73
74#[derive(Debug, PartialEq)]
75/// DTED User Header Label (UHL)
76///
77/// See: [https://www.dlr.de/de/eoc/downloads/dokumente/7_sat_miss/SRTM-XSAR-DEM-DTED-1.1.pdf](https://www.dlr.de/de/eoc/downloads/dokumente/7_sat_miss/SRTM-XSAR-DEM-DTED-1.1.pdf)
78///
79/// # Fields
80///
81/// * `origin` - latitude and longitude of the lower left corner of the grid
82/// * `interval_secs_x_10` - data interval in seconds (decimal point is implied after third integer)
83/// * `accuracy`- absolute vertical accuracy in meters (with 90%
84/// assurance that the linear errors will not exceed this value relative to
85/// mean sea level)
86/// * `count` - number of longitude lines and latitude points
87pub struct RawDTEDHeader {
88 pub origin: AxisElement<Angle>,
89 pub interval_secs_x_10: AxisElement<u16>,
90 pub accuracy: Option<u16>,
91 pub count: AxisElement<u16>,
92}
93
94#[derive(Clone)]
95/// DTED metadata
96///
97/// # Fields
98///
99/// * `filename` - filename
100/// * `origin` - position of the lower left corner of the grid (floating point precision)
101/// * `origin_angle` - position of the lower left corner of the grid
102/// * `interval` - interval (floating point precision)
103/// * `interval_secs` - interval (as seconds of an [Angle])
104/// * `accuracy` - absolute vertical accuracy in meters (with 90%
105/// assurance that the linear errors will not exceed this value relative to
106/// mean sea level)
107/// * `count` - number of longitude lines and latitude points
108pub struct DTEDMetadata {
109 pub filename: String,
110 pub origin: AxisElement<f64>,
111 pub origin_angle: AxisElement<Angle>,
112 pub interval: AxisElement<f64>,
113 pub interval_secs: AxisElement<f32>,
114 pub accuracy: Option<u16>,
115 pub count: AxisElement<u16>,
116}
117impl DTEDMetadata {
118 /// Create a [DTEDMetadata] from a [RawDTEDHeader]
119 ///
120 /// # Arguments
121 ///
122 /// * `raw` - [RawDTEDHeader]
123 /// * `fname` - filename
124 ///
125 /// # Returns
126 ///
127 /// * [DTEDMetadata]: DTED metadata
128 pub fn from_header(raw: &RawDTEDHeader, fname: &str) -> DTEDMetadata {
129 DTEDMetadata {
130 filename: fname.to_string(),
131 origin: raw.origin.into(),
132 origin_angle: raw.origin,
133 interval: raw.interval_secs_x_10 / (primitives::SEC2DEG * 10.0),
134 interval_secs: raw.interval_secs_x_10 / 10.0,
135 accuracy: raw.accuracy,
136 count: raw.count,
137 }
138 }
139}
140
141/// DTED Data
142///
143/// This is the main entry point for reading DTED files.
144/// Usage consists of either [DTEDData::read] or [DTEDData::read_header]
145///
146/// # Fields
147///
148/// * `metadata` - [DTEDMetadata]
149/// * `min` - minimum lat/lon
150/// * `max` - maximum lat/lon
151/// * `data` - data
152pub struct DTEDData {
153 pub metadata: DTEDMetadata,
154 pub min: AxisElement<f64>,
155 pub max: AxisElement<f64>,
156 pub data: Vec<RawDTEDRecord>,
157}
158impl DTEDData {
159 /// Read a DTED file
160 ///
161 /// # Arguments
162 ///
163 /// * `path` (str): Path to the DTED file
164 ///
165 /// # Returns
166 ///
167 /// * [DTEDData]: DTED data
168 ///
169 /// # Examples
170 ///
171 /// ```
172 /// use dted2::DTEDData;
173 /// assert!(DTEDData::read("tests/test_data.dt2").is_ok());
174 /// ```
175 pub fn read(path: &str) -> Result<DTEDData, DTEDError> {
176 let mut file = std::fs::File::open(path)?;
177 let mut content = Vec::new();
178 file.read_to_end(&mut content)?;
179 match parsers::dted_file_parser(&content) {
180 Ok((_, data)) => {
181 let metadata = DTEDMetadata::from_header(&data.header, path);
182 let interval = metadata.interval;
183 let origin_f64: AxisElement<f64> = data.header.origin.into();
184 Ok(DTEDData {
185 metadata,
186 min: origin_f64,
187 max: origin_f64 + ((data.header.count - 1) * interval),
188 data: data.data,
189 })
190 }
191 Err(e) => match e {
192 nom::Err::Incomplete(e) => Err(e.into()),
193 nom::Err::Error(e) | nom::Err::Failure(e) => Err(e.code.into()),
194 },
195 }
196 }
197
198 /// Read the header from a DTED file
199 ///
200 /// # Arguments
201 ///
202 /// * `path` (str): Path to the DTED file
203 ///
204 /// # Returns
205 ///
206 /// * [DTEDMetadata]: DTED metadata
207 ///
208 /// # Examples
209 ///
210 /// ```
211 /// use dted2::DTEDData;
212 /// assert!(DTEDData::read_header("tests/test_data.dt2").is_ok());
213 /// ```
214 pub fn read_header(path: &str) -> Result<DTEDMetadata, DTEDError> {
215 let mut file = std::fs::File::open(path)?;
216 let mut content = Vec::new();
217 file.read_to_end(&mut content)?;
218 match parsers::dted_uhl_parser(&content) {
219 Ok((_, header)) => Ok(DTEDMetadata::from_header(&header, path)),
220 Err(e) => match e {
221 nom::Err::Incomplete(e) => Err(e.into()),
222 nom::Err::Error(e) | nom::Err::Failure(e) => Err(e.code.into()),
223 },
224 }
225 }
226
227 /// Get the elevation at a lat/lon
228 ///
229 /// # Arguments
230 ///
231 /// * `lat` - latitude
232 /// * `lon` - longitude
233 ///
234 /// # Returns
235 ///
236 /// * Elevation (in meters) or None if out of bounds
237 ///
238 /// # Examples
239 ///
240 /// ```
241 /// use dted2::DTEDData;
242 /// let dted_data = DTEDData::read("tests/test_data.dt2").unwrap();
243 /// assert!(dted_data.get_elevation(42.52, 15.75).is_some());
244 /// assert!(dted_data.get_elevation(0.0, 0.0).is_none());
245 /// ```
246 pub fn get_elevation<T: Into<f64>, U: Into<f64>>(&self, lat: T, lon: U) -> Option<f64> {
247 // --------------------------------------------------
248 // get the indices + fractions
249 // --------------------------------------------------
250 let (lat_idx, lon_idx) = self.get_indices(lat, lon)?;
251 let mut lat_int = lat_idx as usize;
252 let mut lon_int = lon_idx as usize;
253 let mut lat_frac = lat_idx - lat_int as f64;
254 let mut lon_frac = lon_idx - lon_int as f64;
255 // --------------------------------------------------
256 // handle the edge case of max lat/lon
257 // --------------------------------------------------
258 if lat_int == self.metadata.count.lat as usize - 1 {
259 lat_int -= 1;
260 lat_frac += 1.0;
261 }
262 if lon_int == self.metadata.count.lon as usize - 1 {
263 lon_int -= 1;
264 lon_frac += 1.0;
265 }
266 // --------------------------------------------------
267 // values for the 4 corners for bilinear interpolation
268 // --------------------------------------------------
269 let elev00 = self.data[lon_int].elevations[lat_int] as f64;
270 let elev01 = self.data[lon_int].elevations[lat_int + 1] as f64;
271 let elev10 = self.data[lon_int + 1].elevations[lat_int] as f64;
272 let elev11 = self.data[lon_int + 1].elevations[lat_int + 1] as f64;
273 // --------------------------------------------------
274 // return interpolated value
275 // --------------------------------------------------
276 let result = 0.0
277 + elev00 * (1.0 - lon_frac) * (1.0 - lat_frac)
278 + elev01 * (1.0 - lon_frac) * lat_frac
279 + elev10 * lon_frac * (1.0 - lat_frac)
280 + elev11 * lon_frac * lat_frac;
281 Some(result)
282 }
283
284 /// Get the indices of a lat/lon
285 ///
286 /// # Arguments
287 ///
288 /// * `lat` - latitude
289 /// * `lon` - longitude
290 ///
291 /// # Returns
292 ///
293 /// * `(lat_index, lon_index)` or None if out of bounds
294 ///
295 /// # Examples
296 ///
297 /// ```
298 /// use dted2::DTEDData;
299 /// let dted_data = DTEDData::read("tests/test_data.dt2").unwrap();
300 /// assert!(dted_data.get_indices(42.52, 15.75).is_some());
301 /// assert!(dted_data.get_indices(0.0, 0.0).is_none());
302 /// ```
303 pub fn get_indices<T: Into<f64>, U: Into<f64>>(&self, lat: T, lon: U) -> Option<(f64, f64)> {
304 // --------------------------------------------------
305 // check bounds
306 // --------------------------------------------------
307 let lat: f64 = lat.into();
308 let lon: f64 = lon.into();
309 if lat < self.min.lat || lat > self.max.lat || lon < self.min.lon || lon > self.max.lon {
310 return None;
311 }
312 let lat_idx = (lat - self.min.lat) / self.metadata.interval.lat;
313 let lon_idx = (lon - self.min.lon) / self.metadata.interval.lon;
314 Some((lat_idx, lon_idx))
315 }
316}
317
318/// TODO
319///
320/// DTED Data Set Identification (DSI) Record
321///
322/// See: [https://www.dlr.de/de/eoc/downloads/dokumente/7_sat_miss/SRTM-XSAR-DEM-DTED-1.1.pdf](https://www.dlr.de/de/eoc/downloads/dokumente/7_sat_miss/SRTM-XSAR-DEM-DTED-1.1.pdf)
323pub struct DTEDRecordDSI {
324 /// Security Control and Release Markings
325 pub security_release: Option<String>,
326 /// Security Handling Description
327 pub security_handling: Option<String>,
328 pub version: String,
329 pub edition: u8,
330 pub mm_version: char,
331 pub maintenance_data: u16,
332 pub mm_date: u16,
333 pub maintenance_code: u16,
334 pub product_specs_desc: String,
335 pub product_specs_code: u8,
336 pub product_specs_date: u16,
337 pub compilation_date: u16,
338 pub lat_origin: Angle,
339 pub lon_origin: Angle,
340 pub lat_sw: Angle,
341 pub lon_sw: Angle,
342 pub lat_nw: Angle,
343 pub lon_nw: Angle,
344 pub lat_ne: Angle,
345 pub lon_ne: Angle,
346 pub lat_se: Angle,
347 pub lon_se: Angle,
348 pub clockwise_orientation: u32,
349 pub lat_interval_s: u16,
350 pub lon_interval_s: u16,
351 pub lat_count: u16,
352 pub lon_count: u16,
353 pub partial_cell_flag: f64,
354 pub coverage: f64,
355}
356
357/// TODO
358pub struct DTEDRecordACC {}
359
360pub struct RawDTEDFile {
361 pub header: RawDTEDHeader,
362 pub data: Vec<RawDTEDRecord>,
363 pub dsi_record: Option<u8>,
364 pub acc_record: Option<u8>,
365}
366
367pub struct RawDTEDRecord {
368 pub blk_count: u32,
369 pub lon_count: u16,
370 pub lat_count: u16,
371 pub elevations: Vec<i16>,
372}