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.#
414 root = gpx_doc.getroot()
416 # Test if this is a GPX file or not and if it's version 1.1
417 if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1":
419 elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0":
421 elif root.get("version") not in [ "1.1", "1.0"]:
422 raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version"))
424 raise ValueError("Неизвестный формат дерева")
425 # attempt to validate the xml file against the schema
427 # initalize the GPX document for parsing.
428 self._init_version(root)
431 def ReadFile(self, fd):
432 # Загрузка из файла.#
433 gpx_doc = etree.parse(fd)
434 root = gpx_doc.getroot()
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 # initalize the GPX document for parsing.
446 self._init_version(root)
448 def _init_version(self,root):
449 # Инициализация объекта.#
450 self.creator = root.get("creator")
453 if child.tag == tagprefix[self.version] + "author":
454 self.author = child.text
455 elif child.tag == tagprefix[self.version] + "trk":
456 self.tracks.append(GPXTrack(child, self.version))
457 elif child.tag == tagprefix[self.version] + "rte":
458 self.routes.append(GPXRoute(child, self.version))
459 elif child.tag == tagprefix[self.version] + "wpt":
460 self.waypoints.append(GPXTrackPt(child, self.version))
461 elif child.tag == tagprefix[self.version] + "metadata":
463 if meta.tag == tagprefix[self.version] + "author":
464 self.author = meta.text
465 elif meta.tag == tagprefix[self.version] + "name":
466 self.name = meta.text
467 elif meta.tag == tagprefix[self.version] + "bounds":
468 # Границы пропускаем - их будем вычислять сами заново
470 elif meta.tag == tagprefix[self.version] + "copyright":
471 self.copyright = meta.text
472 elif meta.tag == tagprefix[self.version] + "link":
473 self.link = Link(meta,self.version)
475 self.meta[StripPrefix(meta.tag,self.version)]=meta.text
477 for i in self.meta.keys():
480 if not val.strip(' \n\t'):
483 def elevation_gain(self):
484 # Суммарный набор высоты.#
485 return sum([track.elevation_gain() for track in self.tracks])
487 def elevation_loss(self):
488 # Суммарная потеря высоты.#
489 return sum([track.elevation_loss() for track in self.tracks])
492 # Суммарная дистанция.#
494 return sum([track.distance() for track in self.tracks])
498 def filtered_distance(self):
499 # Суммарная дистанция с фильтрацией сбойных данных.#
501 return sum([track.filtered_distance() for track in self.tracks])
506 # Суммарная продолжительность, не включая паузы.#
507 dur = datetime.timedelta(0)
508 for track in self.tracks:
509 dur += track.duration()
512 def full_duration(self):
513 # Суммарная продолжительность, включая паузы#
514 if hasattr(self.start(), 'duration'):
515 return self.start().duration(self.end())
521 return self.tracks[0].start()
525 return self.tracks[-1].end()
527 def start_time(self):
530 return self.start().time
536 return self.end().time
539 # Обрамляющий прямоугольник для всех треков в файле.#
544 for track in self.tracks:
545 for trkseg in track.trksegs:
546 for pt in trkseg.trkpts:
555 return ((minlat,minlon),(maxlat,maxlon))
557 def FixNames(self,linkname):
558 goodname = linkname.decode('UTF-8')
559 badname = goodname.encode('ascii','replace')
560 if self.name and self.name.startswith(badname):
561 self.name=self.name.replace(badname,goodname)
562 for i in self.tracks:
563 if i.name and i.name.startswith(badname):
564 i.name = i.name.replace(badname,goodname)
566 def ProcessTrackSegs(self,seconds_gap=120):
567 for tr in self.tracks:
568 # Объединяем все точки трека в единую последовательность
570 for trseg in tr.trksegs:
571 all_trackpoints.extend(trseg.trkpts)
575 for pt in all_trackpoints:
577 # Начало нового сегмента
578 current_seg = GPXTrackSeg(None,self.version)
579 current_seg.trkpts.append(pt)
581 delta = pt.time - prev_pt.time
582 if delta.days > 0 or delta.seconds > seconds_gap:
583 # Нашли место разрыва
584 new_segs.append(current_seg)
587 current_seg.trkpts.append(pt)
589 # Если есть, что закинуть в хвост - пишем
590 if current_seg is not None:
591 new_segs.append(current_seg)
593 tr.trksegs = new_segs
596 root = etree.Element("gpx", attrib ={"creator": "pygpx",
598 "xmlns": "http://www.topografix.com/GPX/1/1"});
602 meta = etree.SubElement( root, "metadata" );
604 metarecord = etree.SubElement( meta, "name" )
605 metarecord.text = self.name
607 metarecord = etree.SubElement( meta, "author" )
608 metarecord.text = self.author
610 metarecord = etree.SubElement( meta, "copyright" )
611 metarecord.text = self.copyright
613 self.link.write(meta)
614 for metatag in self.meta.iterkeys():
615 metarecord = etree.SubElement( meta, metatag )
616 metarecord.text = self.meta[metatag]
620 for i in self.waypoints:
625 for i in self.routes:
630 for i in self.tracks: