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 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 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 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 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 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 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 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 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#[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 pub const fn degrees(&self) -> &UnsignedRational {
176 &self.0
177 }
178
179 pub const fn minutes(&self) -> &UnsignedRational {
181 &self.1
182 }
183
184 pub const fn seconds(&self) -> &UnsignedRational {
186 &self.2
187 }
188
189 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}