minigps/
poi.rs

1//! Working with POI.DAT file
2use 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/// Single POI
11///
12/// Only contains creation timestamp and two coordinates as IEEE754 doubles
13/// Layout in file:
14/// - `timestamp: i64` - unix timestamp
15/// - `lat: f64` - latitude
16/// - `lon: f64` - longtitude
17/// - `_: u64`   - zeros, apparently just padding to 32 bit size
18#[derive(Debug, Clone, Copy)]
19pub struct POI {
20    /// POI timestamp
21    timestamp: OffsetDateTime,
22    /// Latitude
23    lat: f64,
24    /// Longtitude
25    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>()?; // Padding?
34
35        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
77/// Read a typical POI.DAT file and convert it into `Vec<POI>`
78pub 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
92/// Write a POI.DAT file with up to 16 POIs
93pub 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(); // We have to pad file up to 512 bytes
98    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        // read takes any io::Read and gives a Result<Gpx, Error>.
228        {
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}