Skip to main content

fastexif/tags/
gps.rs

1use crate::InvalidExif;
2use crate::UnsignedRational;
3use crate::Value;
4use crate::define_tag_enum;
5use crate::define_value_enums;
6use crate::define_value_str_enums;
7
8use chrono::DateTime;
9use chrono::NaiveDate;
10use chrono::NaiveTime;
11use chrono::Utc;
12
13define_tag_enum! {
14    Tag "GPS tag"
15    Entry "GPS entry"
16    EntryMap "GPS entries"
17    (Iter)
18    ("GPS tag version" VersionId 0 (&'a str) parse_str)
19    ("North or south latitude" LatitudeRef 1 (LatitudeRef) parse_latitude_ref)
20    ("Latitude" Latitude 2 (Degrees) parse_latitude_longitude)
21    ("East or west longitude" LongitudeRef 3 (LongitudeRef) parse_longitude_ref)
22    ("Longitude" Longitude 4 (Degrees) parse_latitude_longitude)
23    ("Altitude reference" AltitudeRef 5 (AltitudeRef) parse_altitude_ref)
24    ("Altitude in meters" Altitude 6 (UnsignedRational) parse_altitude)
25    ("GPS time (atomic clock)" TimeStamp 7 (NaiveTime) parse_time_stamp)
26    ("GPS satellites used for measurement" Satellites 8 (&'a str) parse_str)
27    ("GPS receiver status" Status 9 (Status) parse_status)
28    ("GPS measurement mode" MeasureMode 10 (MeasureMode) parse_measure_mode)
29    ("Measurement precision (data degree of precision)" Dop 11 (UnsignedRational) parse_unsigned_rational)
30    ("Speed unit" SpeedRef 12 (SpeedRef) parse_speed_ref)
31    ("Speed of GPS receiver" Speed 13 (UnsignedRational) parse_unsigned_rational)
32    ("Reference for direction of movement" TrackRef 14 (Direction) parse_direction)
33    ("Direction of movement" Track 15 (UnsignedRational) parse_unsigned_rational)
34    ("Reference for direction of image" ImgDirectionRef 16 (Direction) parse_direction)
35    ("Direction of image" ImgDirection 17 (UnsignedRational) parse_unsigned_rational)
36    ("Geodetic survey data used" MapDatum 18 (&'a str) parse_str)
37    ("Reference for latitude of destination" DestLatitudeRef 19 (&'a str) parse_str)
38    ("Latitude of destination" DestLatitude 20 (LatitudeRef) parse_latitude_ref)
39    ("Reference for longitude of destination" DestLongitudeRef 21 (LongitudeRef) parse_longitude_ref)
40    ("Longitude of destination" DestLongitude 22 (UnsignedRational) parse_unsigned_rational)
41    ("Reference for bearing of destination" DestBearingRef 23 (Direction) parse_direction)
42    ("Bearing of destination" DestBearing 24 (UnsignedRational) parse_unsigned_rational)
43    ("Reference for distance to destination" DestDistanceRef 25 (DistanceRef) parse_distance_ref)
44    ("Distance to destination" DestDistance 26 (UnsignedRational) parse_unsigned_rational)
45    ("Name of GPS processing method" ProcessingMethod 27 (&'a [u8]) parse_bytes)
46    ("Name of GPS area" AreaInformation 28 (&'a [u8]) parse_bytes)
47    ("GPS date" DateStamp 29 (NaiveDate) parse_date_stamp)
48    ("GPS differential correction" Differential 30 (Differential) parse_differential)
49    ("Horizontal positioning error in meters" HPositioningError 31 (UnsignedRational) parse_unsigned_rational)
50}
51
52define_value_enums! {
53    (AltitudeRef u8 "Reference altitude."
54        (AboveEllipsoidalSurface 0 "Positive ellipsoidal height (at or above ellipsoidal surface)")
55        (BelowEllipsoidalSurface 1 "Negative ellipsoidal height (below ellipsoidal surface)")
56        (AboveSeaLevel 2 "Positive sea level value (at or above sea level reference)")
57        (BelowSeaLevel 3 "Negative sea level value (below sea level reference)"))
58    (Differential u16 "Differential correction"
59        (NoCorrection 0 "Measurement without differential correction")
60        (CorrectionApplied 1 "Differential correction applied"))
61}
62
63define_value_str_enums! {
64    (LatitudeRef "Reference latitude."
65        (North (b'N') "North")
66        (South (b'S') "South"))
67    (LongitudeRef "Reference longitude."
68        (East (b'E') "East")
69        (West (b'W') "West"))
70    (Status "GPS receiver status."
71        (InProgress (b'A') "Measurement in progress")
72        (Interrupted (b'V') "Measurement interrupted"))
73    (MeasureMode "Measurement mode."
74        (TwoDimensional (b'2') "2-dimensional measurement")
75        (ThreeDimensional (b'3') "3-dimensional measurement"))
76    (SpeedRef "Speed unit."
77        (KilometersPerHour (b'K') "Kilometers per hour")
78        (MilesPerHour (b'M') "Miles per hour")
79        (KnotsPerHour (b'N') "Knots"))
80    (Direction "Direction"
81        (True (b'T') "True direction")
82        (Magnetic (b'M') "Magnetic direction"))
83    (DistanceRef "Distance unit."
84        (Kilometers (b'K') "Kilometers")
85        (Miles (b'M') "Miles")
86        (NauticalMiles (b'N') "Nautical miles"))
87}
88
89impl EntryMap<'_> {
90    /// Returns atomic time obtained by GPS receiver.
91    pub fn time(&self) -> Option<DateTime<Utc>> {
92        let date = self.get_date_stamp()?;
93        let time = self.get_time_stamp()?;
94        Some(date.and_time(*time).and_utc())
95    }
96
97    /// Returns longitude in degrees.
98    pub fn latitude(&self) -> Option<f64> {
99        let latitude = self.get_latitude()?;
100        let latitude_ref = self.get_latitude_ref()?;
101        match latitude_ref {
102            LatitudeRef::North => Some(latitude.as_f64()),
103            LatitudeRef::South => Some(-latitude.as_f64()),
104        }
105    }
106
107    /// Returns longitude in degrees.
108    pub fn longitude(&self) -> Option<f64> {
109        let longitude = self.get_longitude()?;
110        let longitude_ref = self.get_longitude_ref()?;
111        match longitude_ref {
112            LongitudeRef::East => Some(longitude.as_f64()),
113            LongitudeRef::West => Some(-longitude.as_f64()),
114        }
115    }
116
117    /// Returns altitude in meters.
118    pub fn altitude(&self) -> Option<f64> {
119        use AltitudeRef::*;
120        let altitude = self.get_altitude()?;
121        let altitude_ref = self.get_altitude_ref()?;
122        match altitude_ref {
123            AboveSeaLevel | AboveEllipsoidalSurface => Some(altitude.as_f64()),
124            BelowSeaLevel | BelowEllipsoidalSurface => Some(-altitude.as_f64()),
125        }
126    }
127
128    /// Returns the speed in km/h.
129    pub fn speed(&self) -> Option<f64> {
130        use SpeedRef::*;
131        let speed = self.get_speed()?;
132        let speed_ref = self.get_speed_ref()?;
133        match speed_ref {
134            KilometersPerHour => Some(speed.as_f64()),
135            MilesPerHour => Some(speed.as_f64() * 1.609344),
136            KnotsPerHour => Some(speed.as_f64() * 1.852),
137        }
138    }
139
140    /// Returns movement direction with respect to true north.
141    pub fn movement_direction(&self) -> Option<f64> {
142        let direction = self.get_track_ref()?;
143        if *direction != Direction::True {
144            return None;
145        }
146        self.get_track().map(|d| d.as_f64())
147    }
148
149    /// Returns image direction with respect to true north.
150    pub fn image_direction(&self) -> Option<f64> {
151        let direction = self.get_img_direction_ref()?;
152        if *direction != Direction::True {
153            return None;
154        }
155        self.get_img_direction().map(|d| d.as_f64())
156    }
157
158    /// Returns destination direction with respect to true north.
159    pub fn destination_direction(&self) -> Option<f64> {
160        let direction = self.get_dest_bearing_ref()?;
161        if *direction != Direction::True {
162            return None;
163        }
164        self.get_dest_bearing().map(|d| d.as_f64())
165    }
166}
167
168/// GPS location component specified as degrees.
169#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize))]
171pub struct Degrees(UnsignedRational, UnsignedRational, UnsignedRational);
172
173impl Degrees {
174    /// Returns degrees.
175    pub const fn degrees(&self) -> &UnsignedRational {
176        &self.0
177    }
178
179    /// Returns minutes.
180    pub const fn minutes(&self) -> &UnsignedRational {
181        &self.1
182    }
183
184    /// Returns seconds.
185    pub const fn seconds(&self) -> &UnsignedRational {
186        &self.2
187    }
188
189    /// Returns fractional degress.
190    pub fn as_f64(&self) -> f64 {
191        self.degrees().as_f64()
192            + self.minutes().as_f64() * (1.0 / 60.0)
193            + self.seconds().as_f64() * (1.0 / 3600.0)
194    }
195}
196
197impl<'a> crate::RawEntry<'a> {
198    fn parse_time_stamp(&self) -> Result<NaiveTime, InvalidExif> {
199        let [hours, minutes, seconds] = self.parse_unsigned_rational_3()?;
200        let common_denominator = u64::from(hours.1)
201            .checked_mul(u64::from(minutes.1))
202            .ok_or(InvalidExif)?
203            .checked_mul(u64::from(seconds.1))
204            .ok_or(InvalidExif)?;
205        if common_denominator == 0 {
206            return Err(InvalidExif);
207        }
208        let mut nanos: u64 = 0;
209        nanos = nanos
210            .checked_add(
211                u64::from(hours.0)
212                    .checked_mul(common_denominator / u64::from(hours.1))
213                    .ok_or(InvalidExif)?
214                    .checked_mul(3600 * 1_000_000_000)
215                    .ok_or(InvalidExif)?,
216            )
217            .ok_or(InvalidExif)?;
218        nanos = nanos
219            .checked_add(
220                u64::from(minutes.0)
221                    .checked_mul(common_denominator / u64::from(minutes.1))
222                    .ok_or(InvalidExif)?
223                    .checked_mul(60 * 1_000_000_000)
224                    .ok_or(InvalidExif)?,
225            )
226            .ok_or(InvalidExif)?;
227        nanos = nanos
228            .checked_add(
229                u64::from(seconds.0)
230                    .checked_mul(common_denominator / u64::from(seconds.1))
231                    .ok_or(InvalidExif)?
232                    .checked_mul(1_000_000_000)
233                    .ok_or(InvalidExif)?,
234            )
235            .ok_or(InvalidExif)?;
236        nanos /= common_denominator;
237        let s = nanos
238            .checked_div(1_000_000_000)
239            .ok_or(InvalidExif)?
240            .try_into()
241            .map_err(|_| InvalidExif)?;
242        let n = nanos
243            .checked_rem(1_000_000_000)
244            .ok_or(InvalidExif)?
245            .try_into()
246            .map_err(|_| InvalidExif)?;
247        NaiveTime::from_num_seconds_from_midnight_opt(s, n).ok_or(InvalidExif)
248    }
249
250    fn parse_latitude_longitude(&self) -> Result<Degrees, InvalidExif> {
251        let [degrees, minutes, seconds] = self.parse_unsigned_rational_3()?;
252        Ok(Degrees(degrees, minutes, seconds))
253    }
254
255    fn parse_altitude(&self) -> Result<UnsignedRational, InvalidExif> {
256        let Value::UnsignedRational(altitude) = self.parse_value()? else {
257            return Err(InvalidExif);
258        };
259        Ok(altitude)
260    }
261
262    fn parse_date_stamp(&self) -> Result<NaiveDate, InvalidExif> {
263        let string = self.parse_str()?;
264        NaiveDate::parse_from_str(string, "%Y:%m:%d").map_err(|_| InvalidExif)
265    }
266}