1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
3use geo_types::Point;
4use gpx::Time;
5use gpx::Waypoint;
6use std::convert::{From, Into};
7use std::io::{self, ErrorKind, Read, Write};
8use time::OffsetDateTime;
9
10#[derive(Debug, Clone, Copy)]
19pub struct POI {
20 timestamp: OffsetDateTime,
22 lat: f64,
24 lon: f64,
26}
27
28impl POI {
29 fn deserialize(rdr: &mut impl Read) -> io::Result<Self> {
30 let timeval = rdr.read_i64::<LittleEndian>()?;
31 let lat = rdr.read_f64::<LittleEndian>()?;
32 let lon = rdr.read_f64::<LittleEndian>()?;
33 rdr.read_u64::<LittleEndian>()?; let timestamp = match OffsetDateTime::from_unix_timestamp(timeval) {
36 Ok(val) => val,
37 _ => OffsetDateTime::now_utc(),
38 };
39 Ok(POI {
40 timestamp,
41 lat,
42 lon,
43 })
44 }
45
46 fn serialize(self, wr: &mut impl Write) -> io::Result<()> {
47 wr.write_i64::<LittleEndian>(self.timestamp.unix_timestamp())?;
48 wr.write_f64::<LittleEndian>(self.lat)?;
49 wr.write_f64::<LittleEndian>(self.lon)?;
50 wr.write_u64::<LittleEndian>(0)?;
51 Ok(())
52 }
53}
54
55impl From<Waypoint> for POI {
56 fn from(wpt: Waypoint) -> Self {
57 POI {
58 timestamp: match wpt.time {
59 Some(t) => t.into(),
60 _ => OffsetDateTime::now_utc(),
61 },
62 lat: wpt.point().y(),
63 lon: wpt.point().x(),
64 }
65 }
66}
67
68#[allow(clippy::from_over_into)]
69impl Into<Waypoint> for POI {
70 fn into(self) -> Waypoint {
71 let mut result = Waypoint::new(Point::new(self.lon, self.lat));
72 result.time = Some(Time::from(self.timestamp));
73 result
74 }
75}
76
77pub fn read_pois(mut rdr: impl Read) -> io::Result<Vec<POI>> {
79 let mut pois: Vec<POI> = Vec::new();
80 loop {
81 match POI::deserialize(&mut rdr) {
82 Ok(poi) => pois.push(poi),
83 Err(io_error) => match io_error.kind() {
84 io::ErrorKind::UnexpectedEof => break,
85 _ => return Err(io_error),
86 },
87 }
88 }
89 Ok(pois)
90}
91
92pub fn write_pois(pois: Vec<POI>, wr: &mut impl Write) -> io::Result<()> {
94 if pois.len() > 16 {
95 return Err(io::Error::new(ErrorKind::Other, "Too main POIs"));
96 }
97 let pad_size = 16 - pois.len(); for p in pois {
99 p.serialize(wr)?;
100 }
101 for _ in 0..pad_size * 4 {
102 wr.write_u64::<LittleEndian>(0)?;
103 }
104
105 Ok(())
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use assert_approx_eq::assert_approx_eq;
112 use gpx::{read, Gpx};
113 use std::fs::File;
114 use std::io::{BufReader, BufWriter};
115 use std::path::PathBuf;
116 use time::macros::datetime;
117
118 #[test]
119 fn test_deserialize() -> Result<(), io::Error> {
120 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
121 d.push("resources/testdata/POI.DAT");
122 let mut f = File::open(&d)?;
123 let v = POI::deserialize(&mut f)?;
124 assert_eq!(v.timestamp, datetime!(2022-01-15 06:59:15 UTC));
125 assert_approx_eq!(v.lat, 55.789, 0.001);
126 assert_approx_eq!(v.lon, 37.536, 0.001);
127
128 Ok(())
129 }
130
131 #[test]
132 fn test_read_many() -> Result<(), io::Error> {
133 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
134 d.push("resources/testdata/POI.DAT");
135 let f = File::open(&d)?;
136 let v = read_pois(&f)?;
137 assert_eq!(v.len(), 16);
138 assert_approx_eq!(v[0].lat, 55.789, 0.001);
139 assert_approx_eq!(v[0].lon, 37.536, 0.001);
140 assert_approx_eq!(v[15].lat, 10.000, 0.0001);
141 assert_approx_eq!(v[15].lon, 0.0, 0.001);
142
143 Ok(())
144 }
145
146 #[test]
147 fn test_serialize() -> Result<(), io::Error> {
148 let testpoi = POI {
149 timestamp: datetime!(2022-01-15 06:59:15 UTC),
150 lat: 55.78938888888889,
151 lon: 37.536833333333334,
152 };
153 let refbytes: [u8; 32] = [
154 0xC3, 0x70, 0xE2, 0x61, 0x00, 0x00, 0x00, 0x00, 0x41, 0xCD, 0xF2, 0xB1, 0x0A, 0xE5,
155 0x4B, 0x40, 0xE0, 0x08, 0x65, 0xF4, 0xB6, 0xC4, 0x42, 0x40, 0x00, 0x00, 0x00, 0x00,
156 0x00, 0x00, 0x00, 0x00,
157 ];
158 let mut bytes = [0; 32];
159 {
160 let bytes_ref: &mut [u8] = &mut bytes;
161 let mut writer = BufWriter::new(bytes_ref);
162 testpoi.serialize(&mut writer)?;
163 }
164 assert_eq!(bytes, refbytes);
165
166 Ok(())
167 }
168
169 #[test]
170 fn test_write_many() -> Result<(), io::Error> {
171 let testpoi = POI {
172 timestamp: datetime!(2022-01-15 06:59:15 UTC),
173 lat: 55.78938888888889,
174 lon: 37.536833333333334,
175 };
176 let testvec = vec![testpoi; 16];
177
178 let mut bytes = [0; 512];
179 {
180 let bytes_ref: &mut [u8] = &mut bytes;
181 let mut writer = BufWriter::new(bytes_ref);
182 write_pois(testvec, &mut writer)?;
183 }
184 assert_eq!(bytes[0x23], 0x61);
185 assert_eq!(bytes[0x1f7], 0x40);
186
187 let testvec = vec![testpoi; 1];
188 {
189 let bytes_ref: &mut [u8] = &mut bytes;
190 let mut writer = BufWriter::new(bytes_ref);
191 write_pois(testvec, &mut writer)?;
192 }
193 assert_eq!(bytes[0x23], 0x0);
194 assert_eq!(bytes[0x1f7], 0x0);
195
196 Ok(())
197 }
198
199 #[test]
200 fn test_write_many_fail() -> Result<(), io::Error> {
201 let testpoi = POI {
202 timestamp: datetime!(2022-01-15 06:59:15 UTC),
203 lat: 55.78938888888889,
204 lon: 37.536833333333334,
205 };
206 let testvec = vec![testpoi; 19];
207
208 let mut bytes = [0; 512];
209 {
210 let bytes_ref: &mut [u8] = &mut bytes;
211 let mut writer = BufWriter::new(bytes_ref);
212 let result = write_pois(testvec, &mut writer).map_err(|e| e.kind());
213 let expected = Err(io::ErrorKind::Other);
214 assert_eq!(expected, result);
215 }
216
217 Ok(())
218 }
219
220 #[test]
221 fn test_convert_poi_to_gpx() -> Result<(), io::Error> {
222 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
223 d.push("resources/testdata/test.gpx");
224 let f = File::open(&d)?;
225 let reader = BufReader::new(f);
226
227 {
229 let mut gpx: Gpx = read(reader).unwrap();
230 let newpoi = POI::from(gpx.waypoints.remove(0));
231 assert_approx_eq!(newpoi.lat, 55.789, 0.001);
232 assert_approx_eq!(newpoi.lon, 37.536, 0.001);
233 assert_eq!(newpoi.timestamp, datetime!(2022-05-22 20:36:54.804 UTC));
234 }
235 Ok(())
236 }
237
238 #[test]
239 fn test_convert_gpx_to_poi() -> Result<(), io::Error> {
240 let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
241 d.push("resources/testdata/POI.DAT");
242 let mut f = File::open(&d)?;
243 let v = POI::deserialize(&mut f)?;
244 let w: Waypoint = v.into();
245
246 assert_eq!(
247 "2022-01-15T06:59:15.000000000Z",
248 w.time.unwrap().format().unwrap()
249 );
250 assert_approx_eq!(w.point().y(), 55.789, 0.001);
251 assert_approx_eq!(w.point().x(), 37.536, 0.001);
252
253 Ok(())
254 }
255}