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 # Расстояние между двумя точками на глобусе.#
109 # http://www.platoscave.net/blog/2009/oct/5/calculate-distance-latitude-longitude-python/
111 radius = 6378700.0 # meters
113 lat1, lon1 = self.lat, self.lon
114 lat2, lon2 = other.lat, other.lon
116 dlat = math.radians(lat2-lat1)
117 dlon = math.radians(lon2-lon1)
118 a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
119 * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
120 c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
123 except ValueError, e:
127 def duration(self, other):
128 # Время между двумя точками.#
129 return other.time - self.time
134 def __init__(self, node, version):
136 self.version = version
138 self.additional_info = {}
142 if child.tag == tagprefix[version] + "name":
143 self.name = child.text
144 elif child.tag == tagprefix[version] + "link":
145 self.link = Link(child,version)
146 elif StripPrefix(child.tag,version) in [ "cmt", "desc", "src", "number", "type" ]:
147 self.additional_info[StripPrefix(child.tag,version)]=child.text
148 elif child.tag == tagprefix[version] + "rtept":
149 self.rtepts.append(GPXTrackPt(child, version))
150 elif child.tag == tagprefix[version] + "extensions":
153 raise ValueError("Неизвестный тип узла <%s>" % child.tag)
154 for i in self.additional_info.keys():
155 val = self.additional_info[i];
157 if not val.strip(' \n\t'):
158 del self.additional_info[i]
160 def write(self,root):
161 rtenode = etree.SubElement(root,"rte")
163 etree.SubElement(rtenode,"name").text = self.name
165 self.link.write(rtenode)
166 for i in self.additional_info.keys():
167 print "storing %s " % ( i )
168 etree.SubElement(rtenode,i).text = self.additional_info[i]
169 for i in self.rtepts:
170 i.write(rtenode,"rtept")
173 # Один сегмент трека.#
175 def __init__(self, node, version):
176 self.version = version
178 self.elevation_gain = 0.0
179 self.elevation_loss = 0.0
182 if child.tag == tagprefix[version] + "trkpt":
183 self.trkpts.append(GPXTrackPt(child, self.version))
185 raise ValueError("Неизвестный тип узла <%s>" % node.nodeName)
186 self._get_elevation()
188 def write(self,root):
189 trksegnode = etree.SubElement(root,"trkseg")
190 for i in self.trkpts:
191 i.write(trksegnode,"trkpt")
193 def _get_elevation(self):
196 for pt in self.trkpts:
198 elev_data.append(pt.elevation)
202 last_elevation = None
208 while i < len(elev_data) - window_size + 1:
209 this_window = elev_data[i : i + window_size]
210 window_average = sum(this_window) / window_size
211 moving_averages.append(window_average)
214 if len(moving_averages)>2:
215 elev_data = moving_averages
218 if last_elevation is not None:
220 if pt > last_elevation:
221 gain += pt - last_elevation
223 loss += last_elevation - pt
228 self.elevation_gain = gain
229 self.elevation_loss = loss
232 # Расчет длины сегмента.#
235 for pt in self.trkpts:
236 if last_pt is not None:
237 _length += last_pt.distance(pt)
241 def filtered_distance(self,max_speed):
242 # Расчет длины сегмента с фильтрацией предположительно сбойных участков.#
245 for pt in self.trkpts:
246 if last_pt is not None:
247 _delta = last_pt.distance(pt)
248 _time_delta = pt.time - last_pt.time
249 _time_delta_s = _time_delta.days*86400 + _time_delta.seconds + _time_delta.microseconds/1000000
250 if _time_delta_s > 0:
251 if _delta/_time_delta_s < max_speed:
259 # Расчет продолжительности сегмента.#
260 return self.trkpts[0].duration(self.trkpts[-1])
263 # Обрамляющий прямоугольник для сегмента.#
268 for pt in self.trkpts:
277 return ((minlat,minlon),(maxlat,maxlon))
282 def __init__(self, node, version):
283 # Создаем трек из GPX-данных.#
285 self.version = version
288 self.additional_info = {}
294 if child.tag == tagprefix[version] + "name":
295 self.name = child.text
296 elif child.tag == tagprefix[version] + "trkseg":
298 self.trksegs.append(GPXTrackSeg(child, self.version))
299 elif child.tag == tagprefix[version] + "link":
300 self.link = Link(child,version)
301 elif StripPrefix(child.tag,version) in [ "number", "desc", "cmt", "src", "type" ]:
302 self.additional_info[StripPrefix(child.tag,version)] = child.text
303 elif child.tag == tagprefix[version] + "extensions":
305 # Из расширений извлекаем вид спорта - специфично для Run.GPS.#
306 if ext.tag == tagprefix[version] + "sport":
307 self.sport = ext.text
309 for i in self.additional_info.keys():
310 val = self.additional_info[i];
312 if not val.strip(' \n\t'):
313 del self.additional_info[i]
315 def write(self,root):
316 trknode = etree.SubElement(root,"trk")
318 etree.SubElement(trknode,"name").text = self.name
320 etree.SubElement(etree.SubElement(trknode,"extensions"),"sport").text=self.sport
322 self.link.write(trknode)
323 for i in self.additional_info.keys():
324 etree.SubElement(trknode,i).text = self.additional_info[i]
325 for i in self.trksegs:
328 def elevation_gain(self):
329 # Набор высоты по треку.#
330 return sum([trkseg.elevation_gain for trkseg in self.trksegs])
332 def elevation_loss(self):
333 # Сброс высоты по треку.#
334 return sum([trkseg.elevation_loss for trkseg in self.trksegs])
339 return sum([trkseg.distance() for trkseg in self.trksegs])
341 print "Пустой сегмент трека, расчет длины невозможен."
343 def filtered_distance(self,max_speed=100):
344 # Длина трека с фильтрацией сбойных участков.#
346 return sum([trkseg.filtered_distance(max_speed=max_speed) for trkseg in self.trksegs])
348 print "Пустой сегмент трека, расчет длины невозможен."
351 # Продолжительность трека, не включая паузы между сегментами.#
352 dur = datetime.timedelta(0)
353 for trkseg in self.trksegs:
355 dur += trkseg.duration()
360 def full_duration(self):
361 # Продолжительность трека, включая паузы между сегментами.#
363 return self.start().duration(self.end())
370 return self.trksegs[0].trkpts[0]
377 return self.trksegs[-1].trkpts[-1]
381 def start_time(self):
384 return self.start().time
391 return self.end().time
396 # Обрамляющий прямоугольник для всего трека.#
401 for trkseg in self.trksegs:
402 for pt in trkseg.trkpts:
411 return ((minlat,minlon),(maxlat,maxlon))
415 # Работа с GPX-документами.#
418 # Создание пустого GPX-объекта#
419 PATH = os.path.dirname(__file__)
429 self.copyright = None
432 def ReadTree(self,tree):
433 # Загрузка из дерева etree.#
436 # Test if this is a GPX file or not and if it's version 1.1
437 if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1":
439 elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0":
441 elif root.get("version") not in [ "1.1", "1.0"]:
442 raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version"))
444 raise ValueError("Неизвестный формат дерева")
445 # attempt to validate the xml file against the schema
447 # initalize the GPX document for parsing.
448 self._init_version(root)
451 def ReadFile(self, fd):
452 # Загрузка из файла.#
453 gpx_doc = etree.parse(fd)
454 root = gpx_doc.getroot()
456 # Test if this is a GPX file or not and if it's version 1.1
457 if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1":
459 elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0":
461 elif root.get("version") not in [ "1.1", "1.0"]:
462 raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version"))
464 raise ValueError("Неизвестный формат файла")
465 # initalize the GPX document for parsing.
466 self._init_version(root)
468 def _init_version(self,root):
469 # Инициализация объекта.#
470 self.creator = root.get("creator")
473 if child.tag == tagprefix[self.version] + "author":
474 self.author = child.text
475 elif child.tag == tagprefix[self.version] + "trk":
476 self.tracks.append(GPXTrack(child, self.version))
477 elif child.tag == tagprefix[self.version] + "rte":
478 self.routes.append(GPXRoute(child, self.version))
479 elif child.tag == tagprefix[self.version] + "wpt":
480 self.waypoints.append(GPXTrackPt(child, self.version))
481 elif child.tag == tagprefix[self.version] + "metadata":
483 if meta.tag == tagprefix[self.version] + "author":
484 self.author = meta.text
485 elif meta.tag == tagprefix[self.version] + "name":
486 self.name = meta.text
487 elif meta.tag == tagprefix[self.version] + "bounds":
488 # Границы пропускаем - их будем вычислять сами заново
490 elif meta.tag == tagprefix[self.version] + "copyright":
491 self.copyright = meta.text
492 elif meta.tag == tagprefix[self.version] + "link":
493 self.link = Link(meta,self.version)
495 self.meta[StripPrefix(meta.tag,self.version)]=meta.text
497 for i in self.meta.keys():
500 if not val.strip(' \n\t'):
503 def elevation_gain(self):
504 # Суммарный набор высоты.#
505 return sum([track.elevation_gain() for track in self.tracks])
507 def elevation_loss(self):
508 # Суммарная потеря высоты.#
509 return sum([track.elevation_loss() for track in self.tracks])
512 # Суммарная дистанция.#
514 return sum([track.distance() for track in self.tracks])
518 def filtered_distance(self):
519 # Суммарная дистанция с фильтрацией сбойных данных.#
521 return sum([track.filtered_distance() for track in self.tracks])
526 # Суммарная продолжительность, не включая паузы.#
527 dur = datetime.timedelta(0)
528 for track in self.tracks:
529 dur += track.duration()
532 def full_duration(self):
533 # Суммарная продолжительность, включая паузы#
534 if hasattr(self.start(), 'duration'):
535 return self.start().duration(self.end())
541 return self.tracks[0].start()
545 return self.tracks[-1].end()
547 def start_time(self):
550 return self.start().time
556 return self.end().time
559 # Обрамляющий прямоугольник для всех треков в файле.#
564 for track in self.tracks:
565 for trkseg in track.trksegs:
566 for pt in trkseg.trkpts:
575 return ((minlat,minlon),(maxlat,maxlon))
577 def FixNames(self,linkname):
579 pprint(type(linkname))
580 if type(linkname) is unicode:
583 goodname = linkname.decode('UTF-8')
584 badname = goodname.encode('ascii','replace')
585 if self.name and self.name.startswith(badname):
586 self.name=self.name.replace(badname,goodname)
587 for i in self.tracks:
588 if i.name and i.name.startswith(badname):
589 i.name = i.name.replace(badname,goodname)
591 def ProcessTrackSegs(self,seconds_gap=120):
592 for tr in self.tracks:
593 # Объединяем все точки трека в единую последовательность
595 for trseg in tr.trksegs:
596 all_trackpoints.extend(trseg.trkpts)
600 for pt in all_trackpoints:
602 # Начало нового сегмента
603 current_seg = GPXTrackSeg(None,self.version)
604 current_seg.trkpts.append(pt)
606 delta = pt.time - prev_pt.time
607 if delta.days > 0 or delta.seconds > seconds_gap:
608 # Нашли место разрыва
609 new_segs.append(current_seg)
612 current_seg.trkpts.append(pt)
614 # Если есть, что закинуть в хвост - пишем
615 if current_seg is not None:
616 new_segs.append(current_seg)
620 tr.trksegs = new_segs
624 root = etree.Element("gpx", attrib ={"creator": "pygpx",
626 "xmlns": "http://www.topografix.com/GPX/1/1"});
630 meta = etree.SubElement( root, "metadata" );
632 metarecord = etree.SubElement( meta, "name" )
633 metarecord.text = self.name
635 metarecord = etree.SubElement( meta, "author" )
636 metarecord.text = self.author
638 metarecord = etree.SubElement( meta, "copyright" )
639 metarecord.text = self.copyright
641 self.link.write(meta)
642 for metatag in self.meta.iterkeys():
643 metarecord = etree.SubElement( meta, metatag )
644 metarecord.text = self.meta[metatag]
648 for i in self.waypoints:
653 for i in self.routes:
658 for i in self.tracks: