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}