Набор скрипотов для работы с веб-сервисом RunGPS - скачивание и обработка треков...
authorRoman Bazalevsky <rvb@rvb.name>
Fri, 29 Nov 2013 14:11:47 +0000 (18:11 +0400)
committerRoman Bazalevsky <rvb@rvb.name>
Fri, 29 Nov 2013 14:11:47 +0000 (18:11 +0400)
parsegpx.py [new file with mode: 0644]
pygpx.py [new file with mode: 0644]
pyosmname.py [new file with mode: 0644]
pyrungps [new file with mode: 0755]
pyrungps.py [new file with mode: 0644]

diff --git a/parsegpx.py b/parsegpx.py
new file mode 100644 (file)
index 0000000..5247c27
--- /dev/null
@@ -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 (file)
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.lat<minlat:
+          minlat=pt.lat
+        if pt.lat>maxlat:
+          maxlat=pt.lat
+        if pt.lon<minlon:
+          minlon=pt.lon
+        if pt.lon>maxlon:
+          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.lat<minlat:
+            minlat=pt.lat
+          if pt.lat>maxlat:
+            maxlat=pt.lat
+          if pt.lon<minlon:
+            minlon=pt.lon
+          if pt.lon>maxlon:
+            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.lat<minlat:
+              minlat=pt.lat
+            if pt.lat>maxlat:
+              maxlat=pt.lat
+            if pt.lon<minlon:
+              minlon=pt.lon
+            if pt.lon>maxlon:
+              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 (file)
index 0000000..7140f9f
--- /dev/null
@@ -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 (executable)
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 (file)
index 0000000..76d52f1
--- /dev/null
@@ -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