3 # Copyright (c) 2020 Tilman Sauerbeck (tilman at code-monkey de)
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 from datetime import datetime
29 def __init__(self, unix_time, latitude, longitude):
30 self.unix_time = unix_time
31 self.latitude = latitude
32 self.longitude = longitude
34 class PacketExtractor(object):
38 def __init__(self, source):
40 self._unix_time = None
47 header = self._source.read(5)
49 format_version, self._unix_time = struct.unpack('<BI', header)
51 if format_version != 1:
52 s = 'Unexpected format version {}'.format(format_version)
56 shift = self._num_points % PacketExtractor.POINTS_PER_GROUP
60 self._flags = PacketExtractor.PADDING_BYTE
63 while self._flags == PacketExtractor.PADDING_BYTE:
64 self._flags, = struct.unpack('B', self._source.read(1))
66 if (self._flags & (1 << shift)) == 0:
69 d_time = self.read_uvarint()
71 # End-of-stream marker hit?
72 if d_time == 0xffffffff:
75 d_lat = self.read_svarint()
76 d_lon = self.read_svarint()
78 yield self.process_deltas(d_time, d_lat, d_lon)
80 def process_deltas(self, d_time, d_lat, d_lon):
81 self._unix_time += d_time
82 self._latitude += d_lat
83 self._longitude += d_lon
87 return Packet(self._unix_time, self._latitude / q, self._longitude / q)
89 def read_uvarint(self):
96 b, = struct.unpack('B', self._source.read(1))
100 v |= c << total_shift
108 def read_svarint(self):
109 u = self.read_uvarint()
116 if __name__ == '__main__':
117 filename = sys.argv[1]
119 with open(filename, 'rb') as f:
120 print('<?xml version="1.0"?>')
121 print('<gpx version="1.1" creator="gpxify">')
123 print('\t\t<name>{}</name>'.format(filename))
124 print('\t\t<number>{}</number>'.format(1))
125 print('\t\t<trkseg>')
127 for packet in PacketExtractor(f).run():
128 print('\t\t\t<trkpt lat="{:.8f}" lon="{:.8f}">'.format(
129 packet.latitude, packet.longitude))
131 dt = datetime.utcfromtimestamp(packet.unix_time)
133 print(dt.strftime('\t\t\t\t<time>%Y-%m-%dT%H:%M:%SZ</time>'))
134 print('\t\t\t</trkpt>')
136 print('\t\t</trkseg>')