2 #------------------------
4 #------------------------
9 from dateutil.parser import parse
10 from pprint import pprint
13 # Преобразование из градусов в радианы.
14 return deg / (180 / math.pi)
16 tagprefix = { '1.0': '{http://www.topografix.com/GPX/1/0}', '1.1': '{http://www.topografix.com/GPX/1/1}'}
18 def StripPrefix(tag,version):
19 nstag=tagprefix[version]
20 return tag.replace(nstag,'')
23 return dt.strftime("%Y-%m-%dT%H:%M:%S")+"."+str(int(dt.microsecond/1000)).zfill(3)+"Z"
26 # Ссылка. Может присутствовать в точке, треке, файле.
27 def __init__(self, node, version):
28 self.version = version
29 self.href = node.get("href")
34 if child.tag == tagprefix[version] + "text":
35 self.text = child.text
36 elif child.tag == tagprefix[version] + "type":
37 self.type = child.text
39 raise ValueError("Неизвестный тип узла: '%s' " % child.tag)
42 linknode = etree.SubElement(root,"link");
43 linknode.set("href",self.href)
45 etree.SubElement(linknode,"text").text = self.text
47 etree.SubElement(linknode,"type").text = self.type
50 # Точка с координатами. Может присутствовать в последовательности точек в треке, маршруте, а также в списке путевых точек.#
52 def __init__(self, node, version):
53 # Извлекаем данные из GPX-тэгов.#
54 self.version = version
55 # Сначала обязательные.#
57 self.lat = float(node.get("lat"))
58 self.lon = float(node.get("lon"))
63 self.additional_info = {}
64 # Потом необязательные.#
67 if child.tag == tagprefix[version] + "time":
68 self.time = parse(child.text)
69 elif child.tag == tagprefix[version] + "ele":
70 self.elevation = float(child.text)
71 # Стандартом скорость не предусмотрена, но де-факто в Run.GPS используется.#
72 elif child.tag == tagprefix[version] + "speed":
73 self.speed = float(child.text)
74 elif child.tag == tagprefix[version] + "link":
75 self.link = Link(child,version)
76 elif StripPrefix(child.tag,version) in ['magvar','geoidheight','name','cmt','desc',
77 'src','sym','type','fix','sat','hdop','vdop','pdop','ageofdgpsdata','dgpsid']:
78 self.additional_info[StripPrefix(child.tag,version)]=child.text
79 elif child.tag == tagprefix[version] + "extensions":
82 raise ValueError("Неизвестный тип узла: '%s'" % child.tag)
84 for i in self.additional_info.keys():
85 val = self.additional_info[i];
87 if not val.strip(' \n\t'):
88 del self.additional_info[i]
91 def write(self,root,tag ="wpt"):
92 wptnode = etree.SubElement(root,tag)
93 wptnode.set("lat",repr(self.lat));
94 wptnode.set("lon",repr(self.lon));
96 etree.SubElement(wptnode,"ele").text = repr(self.elevation)
98 etree.SubElement(wptnode,"time").text = formattime(self.time)
100 etree.SubElement(wptnode,"speed").text = repr(self.speed)
102 self.link.write(wptnode)
103 for i in self.additional_info.keys():
104 etree.SubElement(wptnode,i).text = self.additional_info[i]
106 def distance(self, other):
107 # Расстояние между двумя точками на глобусе.#
108 # http://www.platoscave.net/blog/2009/oct/5/calculate-distance-latitude-longitude-python/
110 radius = 6378700.0 # meters
112 lat1, lon1 = self.lat, self.lon
113 lat2, lon2 = other.lat, other.lon
115 dlat = math.radians(lat2-lat1)
116 dlon = math.radians(lon2-lon1)
117 a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
118 * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
119 c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
124 def duration(self, other):
125 # Время между двумя точками.#
126 return other.time - self.time
131 def __init__(self, node, version):
133 self.version = version
135 self.additional_info = {}
139 if child.tag == tagprefix[version] + "name":
140 self.name = child.text
141 elif child.tag == tagprefix[version] + "link":
142 self.link = Link(child,version)
143 elif StripPrefix(child.tag,version) in [ "cmt", "desc", "src", "number", "type" ]:
144 self.additional_info[StripPrefix(child.tag,version)]=child.text
145 elif child.tag == tagprefix[version] + "rtept":
146 self.rtepts.append(GPXTrackPt(child, version))
147 elif child.tag == tagprefix[version] + "extensions":
150 raise ValueError("Неизвестный тип узла <%s>" % child.tag)
151 for i in self.additional_info.keys():
152 val = self.additional_info[i];
154 if not val.strip(' \n\t'):
155 del self.additional_info[i]
157 def write(self,root):
158 rtenode = etree.SubElement(root,"rte")
160 etree.SubElement(rtenode,"name").text = self.name
162 self.link.write(rtenode)
163 for i in self.additional_info.keys():
164 print("storing %s " % ( i ))
165 etree.SubElement(rtenode,i).text = self.additional_info[i]
166 for i in self.rtepts:
167 i.write(rtenode,"rtept")
170 # Один сегмент трека.#
172 def __init__(self, node, version):
173 self.version = version
175 self.elevation_gain = 0.0
176 self.elevation_loss = 0.0
179 if child.tag == tagprefix[version] + "trkpt":
180 self.trkpts.append(GPXTrackPt(child, self.version))
182 raise ValueError("Неизвестный тип узла <%s>" % node.nodeName)
183 self._get_elevation()
185 def write(self,root):
186 trksegnode = etree.SubElement(root,"trkseg")
187 for i in self.trkpts:
188 i.write(trksegnode,"trkpt")
190 def _get_elevation(self):
193 for pt in self.trkpts:
195 elev_data.append(pt.elevation)
199 last_elevation = None
205 while i < len(elev_data) - window_size + 1:
206 this_window = elev_data[i : i + window_size]
207 window_average = sum(this_window) / window_size
208 moving_averages.append(window_average)
211 if len(moving_averages)>2:
212 elev_data = moving_averages
215 if last_elevation is not None:
217 if pt > last_elevation:
218 gain += pt - last_elevation
220 loss += last_elevation - pt
225 self.elevation_gain = gain
226 self.elevation_loss = loss
229 # Расчет длины сегмента.#
232 for pt in self.trkpts:
233 if last_pt is not None:
234 _length += last_pt.distance(pt)
238 def filtered_distance(self,max_speed):
239 # Расчет длины сегмента с фильтрацией предположительно сбойных участков.#
242 for pt in self.trkpts:
243 if last_pt is not None:
244 _delta = last_pt.distance(pt)
245 _time_delta = pt.time - last_pt.time
246 _time_delta_s = _time_delta.days*86400 + _time_delta.seconds + _time_delta.microseconds/1000000
247 if _time_delta_s > 0:
248 if _delta/_time_delta_s < max_speed:
256 # Расчет продолжительности сегмента.#
257 return self.trkpts[0].duration(self.trkpts[-1])
260 # Обрамляющий прямоугольник для сегмента.#
265 for pt in self.trkpts:
274 return ((minlat,minlon),(maxlat,maxlon))
279 def __init__(self, node, version):
280 # Создаем трек из GPX-данных.#
282 self.version = version
285 self.additional_info = {}
291 if child.tag == tagprefix[version] + "name":
292 self.name = child.text
293 elif child.tag == tagprefix[version] + "trkseg":
295 self.trksegs.append(GPXTrackSeg(child, self.version))
296 elif child.tag == tagprefix[version] + "link":
297 self.link = Link(child,version)
298 elif StripPrefix(child.tag,version) in [ "number", "desc", "cmt", "src", "type" ]:
299 self.additional_info[StripPrefix(child.tag,version)] = child.text
300 elif child.tag == tagprefix[version] + "extensions":
302 # Из расширений извлекаем вид спорта - специфично для Run.GPS.#
303 if ext.tag == tagprefix[version] + "sport":
304 self.sport = ext.text
306 for i in self.additional_info.keys():
307 val = self.additional_info[i];
309 if not val.strip(' \n\t'):
310 del self.additional_info[i]
312 def write(self,root):
313 trknode = etree.SubElement(root,"trk")
315 etree.SubElement(trknode,"name").text = self.name
317 etree.SubElement(etree.SubElement(trknode,"extensions"),"sport").text=self.sport
319 self.link.write(trknode)
320 for i in self.additional_info.keys():
321 etree.SubElement(trknode,i).text = self.additional_info[i]
322 for i in self.trksegs:
325 def elevation_gain(self):
326 # Набор высоты по треку.#
327 return sum([trkseg.elevation_gain for trkseg in self.trksegs])
329 def elevation_loss(self):
330 # Сброс высоты по треку.#
331 return sum([trkseg.elevation_loss for trkseg in self.trksegs])
336 return sum([trkseg.distance() for trkseg in self.trksegs])
338 print("Пустой сегмент трека, расчет длины невозможен.")
340 def filtered_distance(self,max_speed=100):
341 # Длина трека с фильтрацией сбойных участков.#
343 return sum([trkseg.filtered_distance(max_speed=max_speed) for trkseg in self.trksegs])
345 print("Пустой сегмент трека, расчет длины невозможен.")
348 # Продолжительность трека, не включая паузы между сегментами.#
349 dur = datetime.timedelta(0)
350 for trkseg in self.trksegs:
352 dur += trkseg.duration()
357 def full_duration(self):
358 # Продолжительность трека, включая паузы между сегментами.#
360 return self.start().duration(self.end())
367 return self.trksegs[0].trkpts[0]
374 return self.trksegs[-1].trkpts[-1]
378 def start_time(self):
381 return self.start().time
388 return self.end().time
393 # Обрамляющий прямоугольник для всего трека.#
398 for trkseg in self.trksegs:
399 for pt in trkseg.trkpts:
408 return ((minlat,minlon),(maxlat,maxlon))
412 # Работа с GPX-документами.#
415 # Создание пустого GPX-объекта#
416 PATH = os.path.dirname(__file__)
426 self.copyright = None
429 def ReadTree(self,tree):
430 # Загрузка из дерева etree.#
433 # Test if this is a GPX file or not and if it's version 1.1
434 if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1":
436 elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0":
438 elif root.get("version") not in [ "1.1", "1.0"]:
439 raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version"))
441 raise ValueError("Неизвестный формат дерева")
442 # attempt to validate the xml file against the schema
444 # initalize the GPX document for parsing.
445 self._init_version(root)
448 def ReadFile(self, fd):
449 # Загрузка из файла.#
450 gpx_doc = etree.parse(fd)
451 root = gpx_doc.getroot()
453 # Test if this is a GPX file or not and if it's version 1.1
454 if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1":
456 elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0":
458 elif root.get("version") not in [ "1.1", "1.0"]:
459 raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version"))
461 raise ValueError("Неизвестный формат файла")
462 # initalize the GPX document for parsing.
463 self._init_version(root)
465 def _init_version(self,root):
466 # Инициализация объекта.#
467 self.creator = root.get("creator")
470 if child.tag == tagprefix[self.version] + "author":
471 self.author = child.text
472 elif child.tag == tagprefix[self.version] + "trk":
473 self.tracks.append(GPXTrack(child, self.version))
474 elif child.tag == tagprefix[self.version] + "rte":
475 self.routes.append(GPXRoute(child, self.version))
476 elif child.tag == tagprefix[self.version] + "wpt":
477 self.waypoints.append(GPXTrackPt(child, self.version))
478 elif child.tag == tagprefix[self.version] + "metadata":
480 if meta.tag == tagprefix[self.version] + "author":
481 self.author = meta.text
482 elif meta.tag == tagprefix[self.version] + "name":
483 self.name = meta.text
484 elif meta.tag == tagprefix[self.version] + "bounds":
485 # Границы пропускаем - их будем вычислять сами заново
487 elif meta.tag == tagprefix[self.version] + "copyright":
488 self.copyright = meta.text
489 elif meta.tag == tagprefix[self.version] + "link":
490 self.link = Link(meta,self.version)
492 self.meta[StripPrefix(meta.tag,self.version)]=meta.text
494 for i in self.meta.keys():
497 if not val.strip(' \n\t'):
500 def elevation_gain(self):
501 # Суммарный набор высоты.#
502 return sum([track.elevation_gain() for track in self.tracks])
504 def elevation_loss(self):
505 # Суммарная потеря высоты.#
506 return sum([track.elevation_loss() for track in self.tracks])
509 # Суммарная дистанция.#
511 return sum([track.distance() for track in self.tracks])
513 print("Пустой файл!")
515 def filtered_distance(self):
516 # Суммарная дистанция с фильтрацией сбойных данных.#
518 return sum([track.filtered_distance() for track in self.tracks])
520 print("Пустой файл!")
523 # Суммарная продолжительность, не включая паузы.#
524 dur = datetime.timedelta(0)
525 for track in self.tracks:
526 dur += track.duration()
529 def full_duration(self):
530 # Суммарная продолжительность, включая паузы#
531 if hasattr(self.start(), 'duration'):
532 return self.start().duration(self.end())
538 return self.tracks[0].start()
542 return self.tracks[-1].end()
544 def start_time(self):
547 return self.start().time
553 return self.end().time
556 # Обрамляющий прямоугольник для всех треков в файле.#
561 for track in self.tracks:
562 for trkseg in track.trksegs:
563 for pt in trkseg.trkpts:
572 return ((minlat,minlon),(maxlat,maxlon))
574 def FixNames(self,linkname):
576 pprint(type(linkname))
578 badname = goodname.encode('ascii','replace')
579 if self.name and self.name.startswith(badname):
580 self.name=self.name.replace(badname,goodname)
581 for i in self.tracks:
582 if i.name and i.name.startswith(badname):
583 i.name = i.name.replace(badname,goodname)
585 def ProcessTrackSegs(self,seconds_gap=120):
586 for tr in self.tracks:
587 # Объединяем все точки трека в единую последовательность
589 for trseg in tr.trksegs:
590 all_trackpoints.extend(trseg.trkpts)
594 for pt in all_trackpoints:
596 # Начало нового сегмента
597 current_seg = GPXTrackSeg(None,self.version)
598 current_seg.trkpts.append(pt)
600 delta = pt.time - prev_pt.time
601 if delta.days > 0 or delta.seconds > seconds_gap:
602 # Нашли место разрыва
603 new_segs.append(current_seg)
606 current_seg.trkpts.append(pt)
608 # Если есть, что закинуть в хвост - пишем
609 if current_seg is not None:
610 new_segs.append(current_seg)
614 tr.trksegs = new_segs
618 root = etree.Element("gpx", attrib ={"creator": "pygpx",
620 "xmlns": "http://www.topografix.com/GPX/1/1"});
624 meta = etree.SubElement( root, "metadata" );
626 metarecord = etree.SubElement( meta, "name" )
627 metarecord.text = self.name
629 metarecord = etree.SubElement( meta, "author" )
630 metarecord.text = self.author
632 metarecord = etree.SubElement( meta, "copyright" )
633 metarecord.text = self.copyright
635 self.link.write(meta)
636 for metatag in self.meta.keys():
637 metarecord = etree.SubElement( meta, metatag )
638 metarecord.text = self.meta[metatag]
642 for i in self.waypoints:
647 for i in self.routes:
652 for i in self.tracks: