From: Roman Bazalevsky Date: Fri, 29 Nov 2013 14:11:47 +0000 (+0400) Subject: Набор скрипотов для работы с веб-сервисом RunGPS - скачивание и обработка треков... X-Git-Url: https://git.rvb.name/pyrungps.git/commitdiff_plain/50682c1f7ad6f88896b241d11ab47b57c49364d2 Набор скрипотов для работы с веб-сервисом RunGPS - скачивание и обработка треков тренировок. --- 50682c1f7ad6f88896b241d11ab47b57c49364d2 diff --git a/parsegpx.py b/parsegpx.py new file mode 100644 index 0000000..5247c27 --- /dev/null +++ b/parsegpx.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# coding: UTF-8 + +import sys +import os +from lxml import etree +from urllib2 import unquote +import pygpx +import pyosmname +import sqlite3 +import datetime + +def write_parsed_to_db(db,gpx,filename): + + conn = sqlite3.connect(db) + cur = conn.cursor() + + cur.execute ("delete from tracks where filename=?" , (filename.decode('UTF-8'),)) + + tracks = gpx.tracks + + for track in tracks: + + try: + author = gpx.author + except: + author = None + + try: + name = gpx.name + except: + name = None + + if author: + try: + cur.execute("insert into authors(name,description) values(?,?)", (author,'')) + print "created author %s" % (author) + except: + print "failed to create author %s" % (author) + pass + + try: + + print "processing..." + start = track.start() + if start: + printable = pyosmname.GeoName(start.lat,start.lon).printable + start_time = track.start_time() + full_duration = track.full_duration().total_seconds() + distance = track.distance() + filtered_distance = track.filtered_distance(max_speed=50) + ascent = track.elevation_gain() + descent = track.elevation_loss() + ((minlat,minlon),(maxlat,maxlon)) = track.bound_box() + params = ( + gpx.author,name,filename.decode('UTF-8'), + track.sport,start_time,full_duration, + distance,filtered_distance,ascent,descent, + start.lat,start.lon, + printable, + minlat,minlon,maxlat,maxlon + ) + cur.execute(""" + insert into tracks( + author,name,filename,sport, + start_time,duration, + distance,distance_filtered, + ascent,descent, + lat,lon,printable_location,minlat,minlon,maxlat,maxlon) + values( + ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + """ + , params ) + conn.commit() + print "created track %s" % (filename) + + except: + + raise + +def write_tree_to_db(db,tree,filename): + + gpx = pygpx.GPX() + gpx.ReadTree(tree) + + write_parsed_to_db(db,gpx,filename) + + \ No newline at end of file diff --git a/pygpx.py b/pygpx.py new file mode 100644 index 0000000..fea5e67 --- /dev/null +++ b/pygpx.py @@ -0,0 +1,634 @@ +# coding: UTF-8 +#------------------------ +# Работа с GPX-файлами +#------------------------ +from lxml import etree +import os +import math +import datetime +from dateutil.parser import parse + +def deg2rad(deg): +# Преобразование из градусов в радианы. + return deg / (180 / math.pi) + +tagprefix = { '1.0': '{http://www.topografix.com/GPX/1/0}', '1.1': '{http://www.topografix.com/GPX/1/1}'} + +def StripPrefix(tag,version): + nstag=tagprefix[version] + return tag.replace(nstag,'') + +def formattime(dt): + return dt.strftime("%Y-%m-%dT%H:%M:%S")+"."+str(int(dt.microsecond/1000)).zfill(3)+"Z" + +class Link: + # Ссылка. Может присутствовать в точке, треке, файле. + def __init__(self, node, version): + self.version = version + self.href = node.get("href") + self.text = None + self.type = None + if node is not None: + for child in node: + if child.tag == tagprefix[version] + "text": + self.text = child.text + elif child.tag == tagprefix[version] + "type": + self.type = child.text + else: + raise ValueError("Неизвестный тип узла: '%s' " % child.tag) + + def write(self,root): + linknode = etree.SubElement(root,"link"); + linknode.set("href",self.href) + if self.text: + etree.SubElement(linknode,"text").text = self.text + if self.type: + etree.SubElement(linknode,"type").text = self.type + +class GPXTrackPt: + # Точка с координатами. Может присутствовать в последовательности точек в треке, маршруте, а также в списке путевых точек.# + + def __init__(self, node, version): + # Извлекаем данные из GPX-тэгов.# + self.version = version + # Сначала обязательные.# + if node is not None: + self.lat = float(node.get("lat")) + self.lon = float(node.get("lon")) + self.elevation = None + self.time = None + self.speed = None + self.link = None + self.additional_info = {} + # Потом необязательные.# + if node is not None: + for child in node: + if child.tag == tagprefix[version] + "time": + self.time = parse(child.text) + elif child.tag == tagprefix[version] + "ele": + self.elevation = float(child.text) + # Стандартом скорость не предусмотрена, но де-факто в Run.GPS используется.# + elif child.tag == tagprefix[version] + "speed": + self.speed = float(child.text) + elif child.tag == tagprefix[version] + "link": + self.link = Link(child,version) + elif StripPrefix(child.tag,version) in ['magvar','geoidheight','name','cmt','desc', + 'src','sym','type','fix','sat','hdop','vdop','pdop','ageofdgpsdata','dgpsid']: + self.additional_info[StripPrefix(child.tag,version)]=child.text + elif child.tag == tagprefix[version] + "extensions": + pass + else: + raise ValueError("Неизвестный тип узла: '%s'" % child.tag) + + for i in self.additional_info.keys(): + val = self.additional_info[i]; + if val: + if not val.strip(' \n\t'): + del self.additional_info[i] + + + def write(self,root,tag ="wpt"): + wptnode = etree.SubElement(root,tag) + wptnode.set("lat",repr(self.lat)); + wptnode.set("lon",repr(self.lon)); + if self.elevation: + etree.SubElement(wptnode,"ele").text = repr(self.elevation) + if self.time: + etree.SubElement(wptnode,"time").text = formattime(self.time) + if self.speed: + etree.SubElement(wptnode,"speed").text = repr(self.speed) + if self.link: + self.link.write(wptnode) + for i in self.additional_info.keys(): + etree.SubElement(wptnode,i).text = self.additional_info[i] + + def distance(self, other): + # Расстояние между двумя точками на глобусе.# + try: + # http://www.platoscave.net/blog/2009/oct/5/calculate-distance-latitude-longitude-python/ + + radius = 6378700.0 # meters + + lat1, lon1 = self.lat, self.lon + lat2, lon2 = other.lat, other.lon + + dlat = math.radians(lat2-lat1) + dlon = math.radians(lon2-lon1) + a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \ + * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) + d = radius * c + + except ValueError, e: + raise ValueError(e) + return d + + def duration(self, other): + # Время между двумя точками.# + return other.time - self.time + +class GPXRoute: + # Маршрут.# + + def __init__(self, node, version): + self.name = None + self.version = version + self.rtepts = [] + self.additional_info = {} + self.link = None + if node is not None: + for child in node: + if child.tag == tagprefix[version] + "name": + self.name = child.text + elif child.tag == tagprefix[version] + "link": + self.link = Link(child,version) + elif StripPrefix(child.tag,version) in [ "cmt", "desc", "src", "number", "type" ]: + self.additional_info[StripPrefix(child.tag,version)]=child.text + elif child.tag == tagprefix[version] + "rtept": + self.rtepts.append(GPXTrackPt(child, version)) + elif child.tag == tagprefix[version] + "extensions": + pass + else: + raise ValueError("Неизвестный тип узла <%s>" % child.tag) + for i in self.additional_info.keys(): + val = self.additional_info[i]; + if val: + if not val.strip(' \n\t'): + del self.additional_info[i] + + def write(self,root): + rtenode = etree.SubElement(root,"rte") + if self.name: + etree.SubElement(rtenode,"name").text = self.name + if self.link: + self.link.write(rtenode) + for i in self.additional_info.keys(): + print "storing %s " % ( i ) + etree.SubElement(rtenode,i).text = self.additional_info[i] + for i in self.rtepts: + i.write(rtenode,"rtept") + +class GPXTrackSeg: + # Один сегмент трека.# + + def __init__(self, node, version): + self.version = version + self.trkpts = [] + self.elevation_gain = 0.0 + self.elevation_loss = 0.0 + if node is not None: + for child in node: + if child.tag == tagprefix[version] + "trkpt": + self.trkpts.append(GPXTrackPt(child, self.version)) + else: + raise ValueError("Неизвестный тип узла <%s>" % node.nodeName) + self._get_elevation() + + def write(self,root): + trksegnode = etree.SubElement(root,"trkseg") + for i in self.trkpts: + i.write(trksegnode,"trkpt") + + def _get_elevation(self): + gain = 0.0 + loss = 0.0 + last_pt = None + for pt in self.trkpts: + if last_pt is not None: + last_elevation = last_pt.elevation + try: + if pt.elevation > last_elevation: + gain += pt.elevation -last_elevation + else: + loss += last_elevation - pt.elevation + except: + pass + last_pt=pt + self.elevation_gain = gain + self.elevation_loss = loss + + def distance(self): + # Расчет длины сегмента.# + _length = 0.0 + last_pt = None + for pt in self.trkpts: + if last_pt is not None: + _length += last_pt.distance(pt) + last_pt = pt + return _length + + def filtered_distance(self,max_speed): + # Расчет длины сегмента с фильтрацией предположительно сбойных участков.# + _length = 0.0 + last_pt = None + for pt in self.trkpts: + if last_pt is not None: + _delta = last_pt.distance(pt) + _time_delta = pt.time - last_pt.time + _time_delta_s = _time_delta.days*86400 + _time_delta.seconds + _time_delta.microseconds/1000000 + if _time_delta_s > 0: + if _delta/_time_delta_s < max_speed: + _length += _delta + else: + _length += _delta + last_pt = pt + return _length + + def duration(self): + # Расчет продолжительности сегмента.# + return self.trkpts[0].duration(self.trkpts[-1]) + + def bound_box(self): + # Обрамляющий прямоугольник для сегмента.# + minlat =360 + maxlat = -360 + minlon = 360 + maxlon = -360 + for pt in self.trkpts: + if pt.latmaxlat: + maxlat=pt.lat + if pt.lonmaxlon: + maxlon=pt.lon + return ((minlat,minlon),(maxlat,maxlon)) + +class GPXTrack: + # Трек.# + + def __init__(self, node, version): + # Создаем трек из GPX-данных.# + + self.version = version + self.trksegs = [] + self.sport= None + self.additional_info = {} + self.name = None + self.link = None + + if node is not None: + for child in node: + if child.tag == tagprefix[version] + "name": + self.name = child.text + elif child.tag == tagprefix[version] + "trkseg": + if len(child) > 0: + self.trksegs.append(GPXTrackSeg(child, self.version)) + elif child.tag == tagprefix[version] + "link": + self.link = Link(child,version) + elif StripPrefix(child.tag,version) in [ "number", "desc", "cmt", "src", "type" ]: + self.additional_info[StripPrefix(child.tag,version)] = child.text + elif child.tag == tagprefix[version] + "extensions": + for ext in child: + # Из расширений извлекаем вид спорта - специфично для Run.GPS.# + if ext.tag == tagprefix[version] + "sport": + self.sport = ext.text + + for i in self.additional_info.keys(): + val = self.additional_info[i]; + if val: + if not val.strip(' \n\t'): + del self.additional_info[i] + + def write(self,root): + trknode = etree.SubElement(root,"trk") + if self.name: + etree.SubElement(trknode,"name").text = self.name + if self.sport: + etree.SubElement(etree.SubElement(trknode,"extensions"),"sport").text=self.sport + if self.link: + self.link.write(trknode) + for i in self.additional_info.keys(): + etree.SubElement(trknode,i).text = self.additional_info[i] + for i in self.trksegs: + i.write(trknode) + + def elevation_gain(self): + # Набор высоты по треку.# + return sum([trkseg.elevation_gain for trkseg in self.trksegs]) + + def elevation_loss(self): + # Сброс высоты по треку.# + return sum([trkseg.elevation_loss for trkseg in self.trksegs]) + + def distance(self): + # Длина трека.# + try: + return sum([trkseg.distance() for trkseg in self.trksegs]) + except IndexError: + print "Пустой сегмент трека, расчет длины невозможен." + + def filtered_distance(self,max_speed=100): + # Длина трека с фильтрацией сбойных участков.# + try: + return sum([trkseg.filtered_distance(max_speed=max_speed) for trkseg in self.trksegs]) + except IndexError: + print "Пустой сегмент трека, расчет длины невозможен." + + def duration(self): + # Продолжительность трека, не включая паузы между сегментами.# + dur = datetime.timedelta(0) + for trkseg in self.trksegs: + try: + dur += trkseg.duration() + except: + pass + return dur + + def full_duration(self): + # Продолжительность трека, включая паузы между сегментами.# + try: + return self.start().duration(self.end()) + except: + return None + + def start(self): + # Стартовая точка.# + try: + return self.trksegs[0].trkpts[0] + except IndexError: + return None + + def end(self): + # Финишная точка.# + try: + return self.trksegs[-1].trkpts[-1] + except IndexError: + return None + + def start_time(self): + # Время старта.# + try: + return self.start().time + except: + return None + + def end_time(self): + # Время финиша.# + try: + return self.end().time + except: + return None + + def bound_box(self): + # Обрамляющий прямоугольник для всего трека.# + minlat =360 + maxlat = -360 + minlon = 360 + maxlon = -360 + for trkseg in self.trksegs: + for pt in trkseg.trkpts: + if pt.latmaxlat: + maxlat=pt.lat + if pt.lonmaxlon: + maxlon=pt.lon + return ((minlat,minlon),(maxlat,maxlon)) + + +class GPX: + # Работа с GPX-документами.# + + def __init__(self): + # Создание пустого GPX-объекта# + PATH = os.path.dirname(__file__) + self.creator = None + self.time = None + self.tracks = [] + self.routes = [] + self.waypoints = [] + self.version = "" + self.author = None + self.name = None + self.link = None + self.copyright = None + self.meta = {} + + def ReadTree(self,tree): + # Загрузка из дерева etree.# + gpx_doc = tree; + root = gpx_doc.getroot() + + # Test if this is a GPX file or not and if it's version 1.1 + if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1": + self.version = "1.1" + elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0": + self.version = "1.0" + elif root.get("version") not in [ "1.1", "1.0"]: + raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version")) + else: + raise ValueError("Неизвестный формат дерева") + # attempt to validate the xml file against the schema + + # initalize the GPX document for parsing. + self._init_version(root) + + + def ReadFile(self, fd): + # Загрузка из файла.# + gpx_doc = etree.parse(fd) + root = gpx_doc.getroot() + + # Test if this is a GPX file or not and if it's version 1.1 + if root.tag == "{http://www.topografix.com/GPX/1/1}gpx" and root.get("version") == "1.1": + self.version = "1.1" + elif root.tag == "{http://www.topografix.com/GPX/1/0}gpx" and root.get("version") == "1.0": + self.version = "1.0" + elif root.get("version") not in [ "1.1", "1.0"]: + raise ValueError("Версия формата %s не поддерживается. Используйте GPX 1.1." % root.get("version")) + else: + raise ValueError("Неизвестный формат файла") + # initalize the GPX document for parsing. + self._init_version(root) + + def _init_version(self,root): + # Инициализация объекта.# + self.creator = root.get("creator") + if root is not None: + for child in root: + if child.tag == tagprefix[self.version] + "author": + self.author = child.text + elif child.tag == tagprefix[self.version] + "trk": + self.tracks.append(GPXTrack(child, self.version)) + elif child.tag == tagprefix[self.version] + "rte": + self.routes.append(GPXRoute(child, self.version)) + elif child.tag == tagprefix[self.version] + "wpt": + self.waypoints.append(GPXTrackPt(child, self.version)) + elif child.tag == tagprefix[self.version] + "metadata": + for meta in child: + if meta.tag == tagprefix[self.version] + "author": + self.author = meta.text + elif meta.tag == tagprefix[self.version] + "name": + self.name = meta.text + elif meta.tag == tagprefix[self.version] + "bounds": + # Границы пропускаем - их будем вычислять сами заново + pass + elif meta.tag == tagprefix[self.version] + "copyright": + self.copyright = meta.text + elif meta.tag == tagprefix[self.version] + "link": + self.link = Link(meta,self.version) + else: + self.meta[StripPrefix(meta.tag,self.version)]=meta.text + + for i in self.meta.keys(): + val = self.meta[i]; + if val: + if not val.strip(' \n\t'): + del self.meta[i] + + def elevation_gain(self): + # Суммарный набор высоты.# + return sum([track.elevation_gain() for track in self.tracks]) + + def elevation_loss(self): + # Суммарная потеря высоты.# + return sum([track.elevation_loss() for track in self.tracks]) + + def distance(self): + # Суммарная дистанция.# + try: + return sum([track.distance() for track in self.tracks]) + except IndexError: + print "Пустой файл!" + + def filtered_distance(self): + # Суммарная дистанция с фильтрацией сбойных данных.# + try: + return sum([track.filtered_distance() for track in self.tracks]) + except IndexError: + print "Пустой файл!" + + def duration(self): + # Суммарная продолжительность, не включая паузы.# + dur = datetime.timedelta(0) + for track in self.tracks: + dur += track.duration() + return dur + + def full_duration(self): + # Суммарная продолжительность, включая паузы# + if hasattr(self.start(), 'duration'): + return self.start().duration(self.end()) + else: + return None + + def start(self): + # Точка старта.# + return self.tracks[0].start() + + def end(self): + # Точка финиша.# + return self.tracks[-1].end() + + def start_time(self): + # Время старта.# + if self.start(): + return self.start().time + else: + return None + + def end_time(self): + # Время финиша.# + return self.end().time + + def bound_box(self): + # Обрамляющий прямоугольник для всех треков в файле.# + minlat =360 + maxlat = -360 + minlon = 360 + maxlon = -360 + for track in self.tracks: + for trkseg in track.trksegs: + for pt in trkseg.trkpts: + if pt.latmaxlat: + maxlat=pt.lat + if pt.lonmaxlon: + maxlon=pt.lon + return ((minlat,minlon),(maxlat,maxlon)) + + def FixNames(self,linkname): + goodname = linkname.decode('UTF-8') + badname = goodname.encode('ascii','replace') + if self.name and self.name.startswith(badname): + self.name=self.name.replace(badname,goodname) + for i in self.tracks: + if i.name and i.name.startswith(badname): + i.name = i.name.replace(badname,goodname) + + def ProcessTrackSegs(self,seconds_gap=120): + for tr in self.tracks: + # Объединяем все точки трека в единую последовательность + all_trackpoints = [] + for trseg in tr.trksegs: + all_trackpoints.extend(trseg.trkpts) + new_segs = [] + current_seg = None + prev_pt = None + for pt in all_trackpoints: + if not current_seg: + # Начало нового сегмента + current_seg = GPXTrackSeg(None,self.version) + current_seg.trkpts.append(pt) + else: + delta = pt.time - prev_pt.time + if delta.days > 0 or delta.seconds > seconds_gap: + # Нашли место разрыва + new_segs.append(current_seg) + current_seg = None + else: + current_seg.trkpts.append(pt) + prev_pt=pt + # Если есть, что закинуть в хвост - пишем + if current_seg is not None: + new_segs.append(current_seg) + # Обработали + tr.trksegs = new_segs + + def XMLTree(self): + root = etree.Element("gpx", attrib ={"creator": "pygpx", + "version": "1.1", + "xmlns": "http://www.topografix.com/GPX/1/1"}); + + # meta subtree + + meta = etree.SubElement( root, "metadata" ); + if self.name: + metarecord = etree.SubElement( meta, "name" ) + metarecord.text = self.name + if self.author: + metarecord = etree.SubElement( meta, "author" ) + metarecord.text = self.author + if self.copyright: + metarecord = etree.SubElement( meta, "copyright" ) + metarecord.text = self.copyright + if self.link: + self.link.write(meta) + for metatag in self.meta.iterkeys(): + metarecord = etree.SubElement( meta, metatag ) + metarecord.text = self.meta[metatag] + + # wpts subtree + + for i in self.waypoints: + i.write(root) + + # routes subtree + + for i in self.routes: + i.write(root) + + # tracks subtree + + for i in self.tracks: + i.write(root) + + return root + \ No newline at end of file diff --git a/pyosmname.py b/pyosmname.py new file mode 100644 index 0000000..7140f9f --- /dev/null +++ b/pyosmname.py @@ -0,0 +1,104 @@ +# coding=UTF-8 +""" +nominatim.openstreetmap.org API +""" + +import urllib2 +from lxml import etree + +def GetXMLDescr(lat,lon): + + req = urllib2.Request("http://nominatim.openstreetmap.org/reverse?lat=%s&lon=%s&accept-language=ru,en" %(lat,lon), None, {'User-agent': 'Mozilla/5.0'}) + page = urllib2.urlopen(req).read() + + return etree.fromstring(page) + +def printable_name(addr): + + if 'country' in addr: + if 'state' in addr: + if addr['country'] == u"Российская Федерация" and addr['state'] == u"Москва": + if 'road' in addr: + str = addr['road'] + ', ' + addr['state'] + ', ' + addr['country'] + else: + str = addr['state'] + ', ' + addr['country'] + else: + if 'city' in addr: + if 'road' in addr: + str = addr['road'] + ', ' + addr['city'] + ', ' + addr['state'] + ', ' + addr['country'] + else: + str = addr['city'] + ', ' + addr['state'] + ', ' + addr['country'] + elif 'town' in addr: + if 'road' in addr: + str = addr['road'] + ', ' + addr['town'] + ', ' + addr['state'] + ', ' + addr['country'] + else: + str = addr['town'] + ', ' + addr['state'] + ', ' + addr['country'] + elif 'village' in addr: + if 'road' in addr: + str = addr['road'] + ', ' + addr['village'] + ', ' + addr['state'] + ', ' + addr['country'] + else: + str = addr['village'] + ', ' + addr['state'] + ', ' + addr['country'] + elif 'county' in addr: + if 'road' in addr: + str = addr['road'] + ', ' + addr['county'] + ', ' + addr['state'] + ', ' + addr['country'] + else: + str = addr['county'] + ', ' + addr['state'] + ', ' + addr['country'] + else: + if 'road' in addr: + str = addr['road'] + ', ' + addr['state'] + ', ' + addr['country'] + else: + str = addr['state'] + ', ' + addr['country'] + else: + if 'city' in addr: + if 'road' in addr: + str = addr['road'] + ', ' + addr['city'] + addr['country'] + else: + str = addr['city'] + ', ' + addr['country'] + elif 'village' in addr: + if 'road' in addr: + str = addr['road'] + ', ' + addr['village'] + addr['country'] + else: + str = addr['village'] + ', ' + addr['country'] + else: + if 'road' in addr: + str = addr['road'] + ', ' + addr['country'] + else: + str = addr['country'] + else: + str = u"Планета Земля, " + if addr['lat'] > 0: + str = str + u"%s с.ш.," % (addr['lat']) + else: + str = str + u"%s ю.ш.," % (-addr['lat']) + if addr['lon'] > 0: + str = str + u"%s в.д." % (addr['lon']) + else: + str = str + u"%s з.д." % (-addr['lon']) + + return str + +class GeoName: + + def __init__(self, lat, lon): + + for elem in GetXMLDescr(lat,lon): + self.addr = {'lat':lat,'lon':lon} + self.road = '' + self.city = '' + self.country = '' + + if elem.tag == "addressparts": + for addrpart in elem: + tag = addrpart.tag + text = urllib2.unquote(addrpart.text) + self.addr[tag] = text + if tag == "road": + self.road = text + elif tag == "city": + self.city = text + elif tag == "country": + self.country = text + + self.printable = printable_name(self.addr) + + \ No newline at end of file diff --git a/pyrungps b/pyrungps new file mode 100755 index 0000000..314e177 --- /dev/null +++ b/pyrungps @@ -0,0 +1 @@ +#!/usr/bin/python pyrungps.py diff --git a/pyrungps.py b/pyrungps.py new file mode 100644 index 0000000..76d52f1 --- /dev/null +++ b/pyrungps.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# coding: UTF-8 + +import urllib2 +#import sys +import os +from lxml import html,etree +from optparse import OptionParser +from datetime import date +from parsegpx import write_parsed_to_db +import pygpx + +def get_page(uname,year,month): + + trainings = [] + + req = urllib2.Request("http://www.gps-sport.net/embedCalendar.jsp?userName=%s&year=%s&month=%s"% (uname,year,month), None, {'User-agent': 'Mozilla/5.0'}) + page = urllib2.urlopen(req).read() + dom = html.document_fromstring(page) + + for element, attribute, link, pos in dom.iterlinks(): + if attribute == "href": + if link.startswith("/trainings/"): + dummy, dummy, link = link.split('/') + name, id = link.split('_') + trainings.append([ urllib2.unquote(name), id ]) + + return trainings + +def get_gpx_track(trid,name): + + req = urllib2.urlopen("http://www.gps-sport.net/services/trainingGPX.jsp?trainingID=%s" % (trid)) + + xml = etree.parse(req) + + return xml + +def sync_folder(username,year,month,dir=".",verbose=False,force=False): + + training_list = get_page(username,year,month) + for tr in training_list: + + filename = "%s/%s_%s.gpx" % (dir,tr[0],tr[1]) + + if os.path.exists(filename) and not force: + + if verbose: + print "training %s exists, skipping" % (filename) + + else: + + xml=get_gpx_track(tr[1],tr[0]) + + if verbose: + print "writing training %s" % (filename) + + gpx = pygpx.GPX() + gpx.ReadTree(xml) + + gpx.FixNames(tr[0]) + gpx.ProcessTrackSegs() + + xml = gpx.XMLTree(); + f = open(filename,"w") + f.write(etree.tostring(xml,encoding='UTF-8',pretty_print=True)) + f.close + write_parsed_to_db(db,gpx,filename) + +def main(): + + global db; + parser = OptionParser() + parser.add_option("-d", "--dir", dest="dirname", + help="write data to directory", metavar="DIR") + parser.add_option("-q", "--quiet", + action="store_false", dest="verbose", default=True, + help="don't print status messages to stdout") + parser.add_option("-f", "--force", + action="store_true", dest="force", default=False, + help="rewrite all files") + parser.add_option("-y", "--yearmonth", dest="yearmonth", + help="year and month in YYYY-MM format", metavar="YYYY-MM") + parser.add_option("-u", "--username", dest="username", + help="Run.GPS username") + (options, args) = parser.parse_args() + + username = options.username + if not username: + print "Run.GPS username is mandatory!" + return + + try: + if options.yearmonth: + year,month = options.yearmonth.split('-') + month = int(month) -1 + year = int(year) + if month<0 or month>11: + raise invalid_number + else: + year = None + month = None + except: + print "Year and month should be in YYYY-MM format!" + return + + if options.dirname: + outdir = options.dirname + else: + outdir = '.' + + db = outdir + '/gpx.db' + + if year: + if options.verbose: + print "retrieving trainings for user %s, year %s, month %s to %s" % (username,year,month+1,outdir) + sync_folder(username,year,month,outdir,options.verbose,options.force) + else: + if options.verbose: + print "retrieving two last months for user %s to %s" % (username,outdir) + now = date.today() + current_year = now.year + current_month = now.month + sync_folder(username,current_year,current_month-1,outdir,options.verbose,options.force) + current_month = current_month -1 + if current_month == 0: + current_month = 12 + current_year = current_year -1 + sync_folder(username,current_year,current_month-1,outdir,options.verbose,options.force) + +if __name__ == "__main__": + + main() + \ No newline at end of file