2 #------------------------
4 #------------------------
9 from dateutil.parser import parse
12 # Преобразование из градусов в радианы.
13 return deg / (180 / math.pi)
15 tagprefix = { '1.0': '{http://www.topografix.com/GPX/1/0}', '1.1': '{http://www.topografix.com/GPX/1/1}'}
17 def StripPrefix(tag,version):
18 nstag=tagprefix[version]
19 return tag.replace(nstag,'')
22 return dt.strftime("%Y-%m-%dT%H:%M:%S")+"."+str(int(dt.microsecond/1000)).zfill(3)+"Z"
25 # Ссылка. Может присутствовать в точке, треке, файле.
26 def __init__(self, node, version):
27 self.version = version
28 self.href = node.get("href")
33 if child.tag == tagprefix[version] + "text":
34 self.text = child.text
35 elif child.tag == tagprefix[version] + "type":
36 self.type = child.text
38 raise ValueError("Неизвестный тип узла: '%s' " % child.tag)
41 linknode = etree.SubElement(root,"link");
42 linknode.set("href",self.href)
44 etree.SubElement(linknode,"text").text = self.text
46 etree.SubElement(linknode,"type").text = self.type
49 # Точка с координатами. Может присутствовать в последовательности точек в треке, маршруте, а также в списке путевых точек.#
51 def __init__(self, node, version):
52 # Извлекаем данные из GPX-тэгов.#
53 self.version = version
54 # Сначала обязательные.#
56 self.lat = float(node.get("lat"))
57 self.lon = float(node.get("lon"))
62 self.additional_info = {}
63 # Потом необязательные.#
66 if child.tag == tagprefix[version] + "time":
67 self.time = parse(child.text)
68 elif child.tag == tagprefix[version] + "ele":
69 self.elevation = float(child.text)
70 # Стандартом скорость не предусмотрена, но де-факто в Run.GPS используется.#
71 elif child.tag == tagprefix[version] + "speed":
72 self.speed = float(child.text)
73 elif child.tag == tagprefix[version] + "link":
74 self.link = Link(child,version)
75 elif StripPrefix(child.tag,version) in ['magvar','geoidheight','name','cmt','desc',
76 'src','sym','type','fix','sat','hdop','vdop','pdop','ageofdgpsdata','dgpsid']:
77 self.additional_info[StripPrefix(child.tag,version)]=child.text
78 elif child.tag == tagprefix[version] + "extensions":
81 raise ValueError("Неизвестный тип узла: '%s'" % child.tag)
83 for i in self.additional_info.keys():
84 val = self.additional_info[i];
86 if not val.strip(' \n\t'):
87 del self.additional_info[i]
90 def write(self,root,tag ="wpt"):
91 wptnode = etree.SubElement(root,tag)
92 wptnode.set("lat",repr(self.lat));
93 wptnode.set("lon",repr(self.lon));
95 etree.SubElement(wptnode,"ele").text = repr(self.elevation)
97 etree.SubElement(wptnode,"time").text = formattime(self.time)
99 etree.SubElement(wptnode,"speed").text = repr(self.speed)
101 self.link.write(wptnode)
102 for i in self.additional_info.keys():
103 etree.SubElement(wptnode,i).text = self.additional_info[i]
105 def distance(self, other):
106 # Расстояние между двумя точками на глобусе.#
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))
122 except ValueError, e:
126 def duration(self, other):
127 # Время между двумя точками.#
128 return other.time - self.time
133 def __init__(self, node, version):
135 self.version = version
137 self.additional_info = {}
141 if child.tag == tagprefix[version] + "name":
142 self.name = child.text
143 elif child.tag == tagprefix[version] + "link":
144 self.link = Link(child,version)
145 elif StripPrefix(child.tag,version) in [ "cmt", "desc", "src", "number", "type" ]:
146 self.additional_info[StripPrefix(child.tag,version)]=child.text
147 elif child.tag == tagprefix[version] + "rtept":
148 self.rtepts.append(GPXTrackPt(child, version))
149 elif child.tag == tagprefix[version] + "extensions":
152 raise ValueError("Неизвестный тип узла <%s>" % child.tag)
153 for i in self.additional_info.keys():
154 val = self.additional_info[i];
156 if not val.strip(' \n\t'):
157 del self.additional_info[i]
159 def write(self,root):
160 rtenode = etree.SubElement(root,"rte")
162 etree.SubElement(rtenode,"name").text = self.name
164 self.link.write(rtenode)
165 for i in self.additional_info.keys():
166 print "storing %s " % ( i )
167 etree.SubElement(rtenode,i).text = self.additional_info[i]
168 for i in self.rtepts:
169 i.write(rtenode,"rtept")
172 # Один сегмент трека.#
174 def __init__(self, node, version):
175 self.version = version
177 self.elevation_gain = 0.0
178 self.elevation_loss = 0.0
181 if child.tag == tagprefix[version] + "trkpt":
182 self.trkpts.append(GPXTrackPt(child, self.version))
184 raise ValueError("Неизвестный тип узла <%s>" % node.nodeName)
185 self._get_elevation()
187 def write(self,root):
188 trksegnode = etree.SubElement(root,"trkseg")
189 for i in self.trkpts:
190 i.write(trksegnode,"trkpt")
192 def _get_elevation(self):
196 for pt in self.trkpts:
197 if last_pt is not None:
198 last_elevation = last_pt.elevation
200 if pt.elevation > last_elevation:
201 gain += pt.elevation -last_elevation
203 loss += last_elevation - pt.elevation
207 self.elevation_gain = gain
208 self.elevation_loss = loss
211 # Расчет длины сегмента.#
214 for pt in self.trkpts:
215 if last_pt is not None:
216 _length += last_pt.distance(pt)
220 def filtered_distance(self,max_speed):
221 # Расчет длины сегмента с фильтрацией предположительно сбойных участков.#
224 for pt in self.trkpts:
225 if last_pt is not None:
226 _delta = last_pt.distance(pt)
227 _time_delta = pt.time - last_pt.time
228 _time_delta_s = _time_delta.days*86400 + _time_delta.seconds + _time_delta.microseconds/1000000
229 if _time_delta_s > 0:
230 if _delta/_time_delta_s < max_speed:
238 # Расчет продолжительности сегмента.#
239 return self.trkpts[0].duration(self.trkpts[-1])
242 # Обрамляющий прямоугольник для сегмента.#
247 for pt in self.trkpts:
256 return ((minlat,minlon),(maxlat,maxlon))
261 def __init__(self, node, version):
262 # Создаем трек из GPX-данных.#
264 self.version = version
267 self.additional_info = {}
273 if child.tag == tagprefix[version] + "name":
274 self.name = child.text
275 elif child.tag == tagprefix[version] + "trkseg":
277 self.trksegs.append(GPXTrackSeg(child, self.version))
278 elif child.tag == tagprefix[version] + "link":
279 self.link = Link(child,version)
280 elif StripPrefix(child.tag,version) in [ "number", "desc", "cmt", "src", "type" ]:
281 self.additional_info[StripPrefix(child.tag,version)] = child.text
282 elif child.tag == tagprefix[version] + "extensions":
284 # Из расширений извлекаем вид спорта - специфично для Run.GPS.#
285 if ext.tag == tagprefix[version] + "sport":
286 self.sport = ext.text
288 for i in self.additional_info.keys():
289 val = self.additional_info[i];
291 if not val.strip(' \n\t'):
292 del self.additional_info[i]
294 def write(self,root):
295 trknode = etree.SubElement(root,"trk")
297 etree.SubElement(trknode,"name").text = self.name
299 etree.SubElement(etree.SubElement(trknode,"extensions"),"sport").text=self.sport
301 self.link.write(trknode)
302 for i in self.additional_info.keys():
303 etree.SubElement(trknode,i).text = self.additional_info[i]
304 for i in self.trksegs:
307 def elevation_gain(self):
308 # Набор высоты по треку.#
309 return sum([trkseg.elevation_gain for trkseg in self.trksegs])
311 def elevation_loss(self):
312 # Сброс высоты по треку.#
313 return sum([trkseg.elevation_loss for trkseg in self.trksegs])
318 return sum([trkseg.distance() for trkseg in self.trksegs])
320 print "Пустой сегмент трека, расчет длины невозможен."
322 def filtered_distance(self,max_speed=100):
323 # Длина трека с фильтрацией сбойных участков.#
325 return sum([trkseg.filtered_distance(max_speed=max_speed) for trkseg in self.trksegs])
327 print "Пустой сегмент трека, расчет длины невозможен."
330 # Продолжительность трека, не включая паузы между сегментами.#
331 dur = datetime.timedelta(0)
332 for trkseg in self.trksegs:
334 dur += trkseg.duration()
339 def full_duration(self):
340 # Продолжительность трека, включая паузы между сегментами.#
342 return self.start().duration(self.end())
349 return self.trksegs[0].trkpts[0]
356 return self.trksegs[-1].trkpts[-1]
360 def start_time(self):
363 return self.start().time
370 return self.end().time
375 # Обрамляющий прямоугольник для всего трека.#
380 for trkseg in self.trksegs:
381 for pt in trkseg.trkpts:
390 return ((minlat,minlon),(maxlat,maxlon))
394 # Работа с GPX-документами.#
397 # Создание пустого GPX-объекта#
398 PATH = os.path.dirname(__file__)
408 self.copyright = None
411 def ReadTree(self,tree):
412 # Загрузка из дерева etree.#
415 # Test if this is a GPX file or not and if it's version 1.1
416 if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1":
418 elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0":
420 elif root.get("version") not in [ "1.1", "1.0"]:
421 raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version"))
423 raise ValueError("Неизвестный формат дерева")
424 # attempt to validate the xml file against the schema
426 # initalize the GPX document for parsing.
427 self._init_version(root)
430 def ReadFile(self, fd):
431 # Загрузка из файла.#
432 gpx_doc = etree.parse(fd)
433 root = gpx_doc.getroot()
435 # Test if this is a GPX file or not and if it's version 1.1
436 if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1":
438 elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0":
440 elif root.get("version") not in [ "1.1", "1.0"]:
441 raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version"))
443 raise ValueError("Неизвестный формат файла")
444 # initalize the GPX document for parsing.
445 self._init_version(root)
447 def _init_version(self,root):
448 # Инициализация объекта.#
449 self.creator = root.get("creator")
452 if child.tag == tagprefix[self.version] + "author":
453 self.author = child.text
454 elif child.tag == tagprefix[self.version] + "trk":
455 self.tracks.append(GPXTrack(child, self.version))
456 elif child.tag == tagprefix[self.version] + "rte":
457 self.routes.append(GPXRoute(child, self.version))
458 elif child.tag == tagprefix[self.version] + "wpt":
459 self.waypoints.append(GPXTrackPt(child, self.version))
460 elif child.tag == tagprefix[self.version] + "metadata":
462 if meta.tag == tagprefix[self.version] + "author":
463 self.author = meta.text
464 elif meta.tag == tagprefix[self.version] + "name":
465 self.name = meta.text
466 elif meta.tag == tagprefix[self.version] + "bounds":
467 # Границы пропускаем - их будем вычислять сами заново
469 elif meta.tag == tagprefix[self.version] + "copyright":
470 self.copyright = meta.text
471 elif meta.tag == tagprefix[self.version] + "link":
472 self.link = Link(meta,self.version)
474 self.meta[StripPrefix(meta.tag,self.version)]=meta.text
476 for i in self.meta.keys():
479 if not val.strip(' \n\t'):
482 def elevation_gain(self):
483 # Суммарный набор высоты.#
484 return sum([track.elevation_gain() for track in self.tracks])
486 def elevation_loss(self):
487 # Суммарная потеря высоты.#
488 return sum([track.elevation_loss() for track in self.tracks])
491 # Суммарная дистанция.#
493 return sum([track.distance() for track in self.tracks])
497 def filtered_distance(self):
498 # Суммарная дистанция с фильтрацией сбойных данных.#
500 return sum([track.filtered_distance() for track in self.tracks])
505 # Суммарная продолжительность, не включая паузы.#
506 dur = datetime.timedelta(0)
507 for track in self.tracks:
508 dur += track.duration()
511 def full_duration(self):
512 # Суммарная продолжительность, включая паузы#
513 if hasattr(self.start(), 'duration'):
514 return self.start().duration(self.end())
520 return self.tracks[0].start()
524 return self.tracks[-1].end()
526 def start_time(self):
529 return self.start().time
535 return self.end().time
538 # Обрамляющий прямоугольник для всех треков в файле.#
543 for track in self.tracks:
544 for trkseg in track.trksegs:
545 for pt in trkseg.trkpts:
554 return ((minlat,minlon),(maxlat,maxlon))
556 def FixNames(self,linkname):
557 goodname = linkname.decode('UTF-8')
558 badname = goodname.encode('ascii','replace')
559 if self.name and self.name.startswith(badname):
560 self.name=self.name.replace(badname,goodname)
561 for i in self.tracks:
562 if i.name and i.name.startswith(badname):
563 i.name = i.name.replace(badname,goodname)
565 def ProcessTrackSegs(self,seconds_gap=120):
566 for tr in self.tracks:
567 # Объединяем все точки трека в единую последовательность
569 for trseg in tr.trksegs:
570 all_trackpoints.extend(trseg.trkpts)
574 for pt in all_trackpoints:
576 # Начало нового сегмента
577 current_seg = GPXTrackSeg(None,self.version)
578 current_seg.trkpts.append(pt)
580 delta = pt.time - prev_pt.time
581 if delta.days > 0 or delta.seconds > seconds_gap:
582 # Нашли место разрыва
583 new_segs.append(current_seg)
586 current_seg.trkpts.append(pt)
588 # Если есть, что закинуть в хвост - пишем
589 if current_seg is not None:
590 new_segs.append(current_seg)
592 tr.trksegs = new_segs
595 root = etree.Element("gpx", attrib ={"creator": "pygpx",
597 "xmlns": "http://www.topografix.com/GPX/1/1"});
601 meta = etree.SubElement( root, "metadata" );
603 metarecord = etree.SubElement( meta, "name" )
604 metarecord.text = self.name
606 metarecord = etree.SubElement( meta, "author" )
607 metarecord.text = self.author
609 metarecord = etree.SubElement( meta, "copyright" )
610 metarecord.text = self.copyright
612 self.link.write(meta)
613 for metatag in self.meta.iterkeys():
614 metarecord = etree.SubElement( meta, metatag )
615 metarecord.text = self.meta[metatag]
619 for i in self.waypoints:
624 for i in self.routes:
629 for i in self.tracks: