From 94f62add438cdf546fdae207ede72c05daf66c00 Mon Sep 17 00:00:00 2001
From: Roman Bazalevskiy <rvb@rvb.name>
Date: Fri, 11 Dec 2020 16:17:02 +0300
Subject: [PATCH] Packaged at last...

---
 .gitignore                                    |   1 +
 pyosmname.py                                  | 107 ------------------
 pyrungps/__init__.py                          |  16 +++
 .../generate_image.py                         |  19 ++--
 parsegpx.py => pyrungps/parsegpx.py           |  21 ++--
 pygeocode.py => pyrungps/pygeocode.py         |   9 +-
 pygpx.py => pyrungps/pygpx.py                 |  50 ++++----
 pyrungps.py => pyrungps/pyrungps_sync.py      |  58 +++++-----
 render_tiles.py => pyrungps/render_tiles.py   |  24 ++--
 pyrungps => pyrungps_sync                     |   0
 setup.py                                      |  13 +++
 11 files changed, 116 insertions(+), 202 deletions(-)
 create mode 100644 .gitignore
 delete mode 100644 pyosmname.py
 create mode 100644 pyrungps/__init__.py
 rename generate_image.py => pyrungps/generate_image.py (94%)
 rename parsegpx.py => pyrungps/parsegpx.py (88%)
 rename pygeocode.py => pyrungps/pygeocode.py (65%)
 rename pygpx.py => pyrungps/pygpx.py (94%)
 rename pyrungps.py => pyrungps/pyrungps_sync.py (88%)
 rename render_tiles.py => pyrungps/render_tiles.py (89%)
 rename pyrungps => pyrungps_sync (100%)
 create mode 100644 setup.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/pyosmname.py b/pyosmname.py
deleted file mode 100644
index e6dc29e..0000000
--- a/pyosmname.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# coding=UTF-8
-"""
-nominatim.openstreetmap.org API
-"""
-
-import urllib2
-from lxml import etree
-
-def GetXMLDescr(lat,lon):
-
-    reqstr = "http://nominatim.openstreetmap.org/reverse?lat=%s&lon=%s&accept-language=ru,en" %(lat,lon)
-    print reqstr
-    headers = { 'User-Agent' : 'PyRunGPS/1.0', 'Referer' : 'https://maps.rvb.name' }    
-    req = urllib2.Request(reqstr, None, headers)
-    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/__init__.py b/pyrungps/__init__.py
new file mode 100644
index 0000000..94b233f
--- /dev/null
+++ b/pyrungps/__init__.py
@@ -0,0 +1,16 @@
+from .render_tiles import deg2num, queue_render, queue_tiles, process_queue
+from .parsegpx import check_db_for_training, write_parsed_to_db, write_tree_to_db, print_parsed_file
+from .pygeocode import GetDescr, GeoName
+from .pygpx import deg2rad, StripPrefix, formattime, Link, GPXTrackPt, GPXRoute, GPXTrackSeg, GPXTrack, GPX
+from .pyrungps_sync import get_page, get_gpx_track, get_osm_list, get_osm_gpx, get_db_gpx, get_dbx_list, sync_db, sync_osm, sync_folder
+from .generate_image import render_map, render_all
+
+__all__ = [ 
+  "deg2num", "queue_render", "queue_tiles", "process_queue",
+  "check_db_for_training", "write_parsed_to_db", "write_tree_to_db", "print_parsed_file",
+  "GetDescr", "GeoName",
+  "deg2rad", "StripPrefix", "formattime", "Link", "GPXTrackPt", "GPXRoute", "GPXTrackSeg", "GPXTrack", "GPX",
+  "get_page", "get_gpx_track", "get_osm_list", "get_osm_gpx", "get_db_gpx", "get_dbx_list", "sync_db", "sync_osm", "sync_folder",
+  "render_map", "render_all"
+  ]
+  
\ No newline at end of file
diff --git a/generate_image.py b/pyrungps/generate_image.py
similarity index 94%
rename from generate_image.py
rename to pyrungps/generate_image.py
index 12169ca..6e1fa3a 100644
--- a/generate_image.py
+++ b/pyrungps/generate_image.py
@@ -9,7 +9,7 @@ from optparse import OptionParser
 
 from pprint import pprint
 
-import pygpx
+import pyrungps.pygpx
 
 # Set up projections
 # spherical mercator (most common target map projection of osm data imported with osm2pgsql)
@@ -38,12 +38,12 @@ def render_map(mapfile,map_uri,gpx_file,imgx,imgy):
     pprint(bbox)
     cx=(bbox[0][1]+bbox[1][1])/2
     cy=(bbox[0][0]+bbox[1][0])/2
-    print cx,cy
+    print(cx,cy)
     w=(bbox[1][1]-bbox[0][1])*1.2
     h=(bbox[1][0]-bbox[0][0])*1.2
-    print w,h
+    print(w,h)
     bounds = (cx-w/2,cy-h/2,cx+w/2,cy+h/2)
-    print bounds
+    print(bounds)
 
     m = mapnik.Map(imgx,imgy)
     mapnik.load_map(m,mapfile)
@@ -106,7 +106,7 @@ def render_all(db,mapfile,imgx,imgy):
       filename = rec[1]
       preview_name = dirname(filename) + '/' + str(id) + '.png'
 
-      print id,filename,preview_name
+      print(id,filename,preview_name)
 
       try:      
         render_map(mapfile,preview_name.encode('utf8'),filename,imgx,imgy)
@@ -159,9 +159,12 @@ def main():
         gpx_file = options.gpxfile
         render_map(mapfile,map_uri,gpx_file,imgx,imgy)
       else:
-        print "No input file"
-        exit(1)
+        print("No input file")
+        raise IOError("No input file")
 
 if __name__ == "__main__":
 
-    main()                    
+    try:
+        main()                    
+    except:
+        exit(1)    
diff --git a/parsegpx.py b/pyrungps/parsegpx.py
similarity index 88%
rename from parsegpx.py
rename to pyrungps/parsegpx.py
index c8ca599..c774aaa 100644
--- a/parsegpx.py
+++ b/pyrungps/parsegpx.py
@@ -4,9 +4,9 @@
 import sys
 import os
 from lxml import etree
-from urllib2 import unquote
-import pygpx
-import pygeocode
+from urllib.parse import unquote
+import pyrungps.pygpx
+import pyrungps.pygeocode
 import sqlite3
 import datetime
 
@@ -25,9 +25,6 @@ def write_parsed_to_db(db,gpx,filename):
   conn.text_factory = str
   cur = conn.cursor()
 
-  if type(filename) is str:
-    filename=filename.decode('UTF-8')
-
   cur.execute ("delete from tracks where filename=?" , (filename,))
   
   tracks = gpx.tracks
@@ -41,22 +38,20 @@ def write_parsed_to_db(db,gpx,filename):
         
       try:
         name = gpx.name
-        if type(name) is str:
-          name=name.decode('UTF-8')
       except:
         name = None
         
       if author:
         try:
           cur.execute("insert into authors(name,description) values(?,?)", (author,''))         
-          print "created author %s" % (author)
+          print("created author %s" % (author))
         except:
-          print "failed to create author %s" % (author)
+          print("failed to create author %s" % (author))
           pass
 
       try:
 
-        print "processing..."
+        print("processing...")
         start = track.start()
         if start:
           printable = pygeocode.GeoName(start.lat,start.lon).printable
@@ -88,7 +83,7 @@ def write_parsed_to_db(db,gpx,filename):
               """ 
               , params )
           conn.commit()
-          print "created track %s" % (filename)
+          print("created track %s" % (filename))
 
       except:
       
@@ -129,7 +124,7 @@ def print_parsed_file(filename):
       descent = track.elevation_loss()
       ((minlat,minlon),(maxlat,maxlon)) = track.bound_box()
       params = ( 
-        gpx.author,name,filename.decode('UTF-8'),
+        gpx.author,name,filename,
         track.sport,start_time,full_duration,
         distance,filtered_distance,ascent,descent,
         start.lat,start.lon,
diff --git a/pygeocode.py b/pyrungps/pygeocode.py
similarity index 65%
rename from pygeocode.py
rename to pyrungps/pygeocode.py
index f9fb0fb..d048622 100644
--- a/pygeocode.py
+++ b/pyrungps/pygeocode.py
@@ -3,16 +3,15 @@
 nominatim.openstreetmap.org API
 """
 
-import urllib2
+import urllib3
 
 def GetDescr(lat,lon):
 
     reqstr = "http://maps.rvb.name/geocode.php?lat=%s&lon=%s" %(lat,lon)
     headers = { 'User-Agent' : 'PyRunGPS/1.0', 'Referer' : 'https://maps.rvb.name' }    
-    req = urllib2.Request(reqstr, None, headers)
-    page = urllib2.urlopen(req).read()
-
-    return page
+    http_pool = urllib3.connection_from_url(reqstr, headers=headers)
+    req = http_pool.urlopen('GET',reqstr)
+    return req.data.decode('utf-8','ignore')
 
 class GeoName:
 
diff --git a/pygpx.py b/pyrungps/pygpx.py
similarity index 94%
rename from pygpx.py
rename to pyrungps/pygpx.py
index 49ccdff..e80077d 100644
--- a/pygpx.py
+++ b/pyrungps/pygpx.py
@@ -62,7 +62,7 @@ class GPXTrackPt:
         self.link = None
         self.additional_info = {}
 	# Потом необязательные.# 
-	if node is not None:
+        if node is not None:
           for child in node:
             if child.tag == tagprefix[version] + "time":
                 self.time = parse(child.text)
@@ -105,23 +105,20 @@ class GPXTrackPt:
 
     def distance(self, other):
         # Расстояние между двумя точками на глобусе.# 
-        try:
-            # http://www.platoscave.net/blog/2009/oct/5/calculate-distance-latitude-longitude-python/
+        # http://www.platoscave.net/blog/2009/oct/5/calculate-distance-latitude-longitude-python/
 
-            radius = 6378700.0 # meters
+        radius = 6378700.0 # meters
 
-            lat1, lon1 = self.lat, self.lon
-            lat2, lon2 = other.lat, other.lon
+        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
+        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):
@@ -132,12 +129,12 @@ class GPXRoute:
 	# Маршрут.# 
 
     def __init__(self, node, version):
-	self.name = None
+        self.name = None
         self.version = version
         self.rtepts = []
-	self.additional_info = {}
-	self.link = None
-	if node is not None:
+        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
@@ -164,7 +161,7 @@ class GPXRoute:
       if self.link:
         self.link.write(rtenode)
       for i in self.additional_info.keys():
-        print "storing %s " % ( i )
+        print("storing %s " % ( i ))
         etree.SubElement(rtenode,i).text = self.additional_info[i]
       for i in self.rtepts:
         i.write(rtenode,"rtept") 
@@ -338,14 +335,14 @@ class GPXTrack:
         try:
             return sum([trkseg.distance() for trkseg in self.trksegs])
         except IndexError:
-            print "Пустой сегмент трека, расчет длины невозможен."
+            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 "Пустой сегмент трека, расчет длины невозможен."
+            print("Пустой сегмент трека, расчет длины невозможен.")
 
     def duration(self):
         # Продолжительность трека, не включая паузы между сегментами.# 
@@ -513,14 +510,14 @@ class GPX:
         try:
             return sum([track.distance() for track in self.tracks])
         except IndexError:
-            print "Пустой файл!"
+            print("Пустой файл!")
 
     def filtered_distance(self):
         # Суммарная дистанция с фильтрацией сбойных данных.# 
         try:
             return sum([track.filtered_distance() for track in self.tracks])
         except IndexError:
-            print "Пустой файл!"
+            print("Пустой файл!")
 
     def duration(self):
         # Суммарная продолжительность, не включая паузы.# 
@@ -577,10 +574,7 @@ class GPX:
     def FixNames(self,linkname):
       pprint(linkname)
       pprint(type(linkname))
-      if type(linkname) is unicode:
-        goodname=linkname
-      else:  
-        goodname = linkname.decode('UTF-8')
+      goodname = linkname
       badname = goodname.encode('ascii','replace')
       if self.name and self.name.startswith(badname):
         self.name=self.name.replace(badname,goodname)
@@ -639,7 +633,7 @@ class GPX:
         metarecord.text = self.copyright
       if self.link:  
         self.link.write(meta)
-      for metatag in self.meta.iterkeys():
+      for metatag in self.meta.keys():
         metarecord = etree.SubElement( meta, metatag )
         metarecord.text = self.meta[metatag]
 
diff --git a/pyrungps.py b/pyrungps/pyrungps_sync.py
similarity index 88%
rename from pyrungps.py
rename to pyrungps/pyrungps_sync.py
index ed3fce5..b87a17e 100644
--- a/pyrungps.py
+++ b/pyrungps/pyrungps_sync.py
@@ -3,26 +3,26 @@
 
 import requests
 
-from urllib3.exceptions import InsecureRequestWarning
-requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
-    
+import urllib3
+urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+
 #import sys
 import os
-from urllib2 import quote,unquote
+from urllib.parse import quote,unquote
 from lxml import html,etree
 from optparse import OptionParser
 from datetime import date,datetime
-from mx.DateTime.ISO import ParseDateTimeUTC
-from parsegpx import write_parsed_to_db,check_db_for_training
-import pygpx
+from dateutil.parser import isoparse
+from pyrungps.parsegpx import write_parsed_to_db,check_db_for_training
+import pyrungps.pygpx
 
 from tempfile import NamedTemporaryFile
 
 from pprint import pprint
 
-import render_tiles
+import pyrungps.render_tiles
 
-import generate_image
+import pyrungps.generate_image
 
 def get_page(uname,year,month):
   
@@ -43,7 +43,7 @@ def get_page(uname,year,month):
 
 def get_gpx_track(trid,name):
 
-  print "trid=",trid
+  print("trid=",trid)
 
   req = requests.get("http://www.gps-sport.net/services/trainingGPX.jsp?trainingID=%s&tz=-180" % (trid), verify=False)
   xml = etree.fromstring(req.text.encode('utf-8'))
@@ -63,7 +63,7 @@ def get_osm_list(username,password,year,month):
   for gpx_file in xml:
     attrib = dict(gpx_file.attrib)
     try:
-      timestamp=datetime.fromtimestamp(ParseDateTimeUTC(attrib['timestamp']))
+      timestamp=datetime.fromtimestamp(isoparse(attrib['timestamp']))
       id = attrib['id']
       description=None
       for descr in gpx_file.iter('description'):
@@ -119,7 +119,7 @@ def get_dbx_list(dbx,username,year,month):
       if entry.name == u'Приложения':
         for entry_app in dbx.files_list_folder(entry.id).entries:
           if entry_app.name == u'Run.GPS':
-            print "id="+entry_app.id
+            print("id="+entry_app.id)
             gpx_list_id=entry_app.id
             break
         break
@@ -167,7 +167,7 @@ def sync_db(dbx,username,year,month,dir=".",verbose=False,force=False):
       if os.path.exists(filename) and not force:
 
         if verbose:
-          print "training %s exists, skipping" % (filename)
+          print("training %s exists, skipping" % (filename))
 
       else:  
 
@@ -179,7 +179,7 @@ def sync_db(dbx,username,year,month,dir=".",verbose=False,force=False):
         xml = get_db_gpx(dbx,training['id'])
 
         if verbose:
-          print "writing training %s" % (filename)
+          print("writing training %s" % (filename))
 
         gpx = pygpx.GPX()
         gpx.ReadTree(xml)
@@ -187,10 +187,10 @@ def sync_db(dbx,username,year,month,dir=".",verbose=False,force=False):
         sport = training['sport']
         timestamp = gpx.tracks[0].start_time()
         
-        print sport, timestamp
+        print(sport, timestamp)
         
         if check_db_for_training(db,sport,timestamp):
-          print "The same training found"
+          print("The same training found")
           continue
 
         gpx.author = username
@@ -200,7 +200,7 @@ def sync_db(dbx,username,year,month,dir=".",verbose=False,force=False):
           track.sport=training['sport']
 
         xml = gpx.XMLTree();
-        f = open(filename,"w")
+        f = open(filename,"wb")
         f.write(etree.tostring(xml,encoding='UTF-8',pretty_print=True))
         f.close
         write_parsed_to_db(db,gpx,filename)
@@ -222,7 +222,7 @@ def sync_osm(username,password,year,month,dir=".",verbose=False,force=False):
       if os.path.exists(filename) and not force:
 
         if verbose:
-          print "training %s exists, skipping" % (filename)
+          print("training %s exists, skipping" % (filename))
 
       else:  
       
@@ -234,7 +234,7 @@ def sync_osm(username,password,year,month,dir=".",verbose=False,force=False):
         xml = get_osm_gpx(username,password,training['id'])
 
         if verbose:
-          print "writing training %s" % (filename)
+          print("writing training %s" % (filename))
 
         gpx = pygpx.GPX()
         gpx.ReadTree(xml)
@@ -243,7 +243,7 @@ def sync_osm(username,password,year,month,dir=".",verbose=False,force=False):
         timestamp = gpx.tracks[0].start_time()
         
         if check_db_for_training(db,sport,timestamp):
-          print "The same training found"
+          print("The same training found")
           continue
 
         gpx.author = username
@@ -253,7 +253,7 @@ def sync_osm(username,password,year,month,dir=".",verbose=False,force=False):
           track.sport=training['sport']
 
         xml = gpx.XMLTree();
-        f = open(filename,"w")
+        f = open(filename,"wb")
         f.write(etree.tostring(xml,encoding='UTF-8',pretty_print=True))
         f.close
         write_parsed_to_db(db,gpx,filename)
@@ -274,7 +274,7 @@ def sync_folder(username,year,month,dir=".",verbose=False,force=False):
       if os.path.exists(filename) and not force:
 
         if verbose:
-          print "training %s exists, skipping" % (filename)
+          print("training %s exists, skipping" % (filename))
 
       else:  
 
@@ -286,7 +286,7 @@ def sync_folder(username,year,month,dir=".",verbose=False,force=False):
         xml=get_gpx_track(tr[1],tr[0])
 
         if verbose:
-          print "writing training %s" % (filename)
+          print("writing training %s" % (filename))
 
         gpx = pygpx.GPX()
         gpx.ReadTree(xml)
@@ -295,14 +295,14 @@ def sync_folder(username,year,month,dir=".",verbose=False,force=False):
         timestamp = gpx.tracks[0].start_time()
         
         if check_db_for_training(db,sport,timestamp):
-          print "The same training found"
+          print("The same training found")
           continue
 
         gpx.FixNames(tr[0])
         gpx.ProcessTrackSegs()
         
         xml = gpx.XMLTree();
-        f = open(filename,"w")
+        f = open(filename,"wb")
         f.write(etree.tostring(xml,encoding='UTF-8',pretty_print=True))
         f.close
         write_parsed_to_db(db,gpx,filename)
@@ -343,7 +343,7 @@ def main():
     osmpwd = options.osmpasswd
     dbauth = options.dropboxauth
     if not (username or (osmname and osmpwd) or dbauth):
-      print "Run.GPS username or OSM username/password or Dropbox Auth token is mandatory!"
+      print("Run.GPS username or OSM username/password or Dropbox Auth token is mandatory!")
       return
 
     if username:
@@ -366,7 +366,7 @@ def main():
         year = None
         month = None
     except:
-      print "Year and month should be in YYYY-MM format!"
+      print("Year and month should be in YYYY-MM format!")
       return 
       
     if options.dirname:  
@@ -378,7 +378,7 @@ def main():
     
     if year:
       if options.verbose:
-        print "retrieving trainings for user %s, year %s, month %s to %s" % (pusername,year,month+1,outdir)
+        print("retrieving trainings for user %s, year %s, month %s to %s" % (pusername,year,month+1,outdir))
       if username:
         sync_folder(username,year,month,outdir,options.verbose,options.force)
       elif osmname:
@@ -387,7 +387,7 @@ def main():
         sync_db(dbx,pusername,year,month+1,outdir,options.verbose,options.force)  
     else:
       if options.verbose:
-        print "retrieving two last months for user %s to %s" % (pusername ,outdir)
+        print("retrieving two last months for user %s to %s" % (pusername ,outdir))
       now = date.today()
       current_year = now.year
       current_month = now.month
diff --git a/render_tiles.py b/pyrungps/render_tiles.py
similarity index 89%
rename from render_tiles.py
rename to pyrungps/render_tiles.py
index 443ae80..ea342e8 100755
--- a/render_tiles.py
+++ b/pyrungps/render_tiles.py
@@ -11,7 +11,7 @@ def queue_render(db,filename,forced_max_zoom=None):
   conn.text_factory = str
   cur = conn.cursor()
         
-  cur.execute("select minlat,minlon,maxlat,maxlon from tracks where filename=?" , (filename.decode('UTF-8'),))
+  cur.execute("select minlat,minlon,maxlat,maxlon from tracks where filename=?" , (filename,))
   minlat,minlon,maxlat,maxlon=cur.fetchone()
   queue_tiles(db,minlat,minlon,maxlat,maxlon,forced_max_zoom)
 
@@ -38,7 +38,7 @@ def queue_tiles(db,minlat,minlon,maxlat,maxlon,forced_max_zoom=None):
       minx,miny=deg2num(minlat,minlon,maxzoom)
       maxx,maxy=deg2num(maxlat,maxlon,maxzoom)
       
-      print maxzoom,':',minx,'-',maxx,'/',miny,'-',maxy
+      print(maxzoom,':',minx,'-',maxx,'/',miny,'-',maxy)
       if (maxx-minx>16) or (maxy-miny>12) or (maxzoom==16):
         break
       else:
@@ -48,7 +48,7 @@ def queue_tiles(db,minlat,minlon,maxlat,maxlon,forced_max_zoom=None):
     minzoom=maxzoom
   
   ins = conn.cursor()
-  print minlat,minlon,maxlat,maxlon,minzoom,maxzoom
+  print(minlat,minlon,maxlat,maxlon,minzoom,maxzoom)
   ins.execute('insert into render_queue(minlat,maxlat,minlon,maxlon,minzoom,maxzoom) values(?,?,?,?,?,?)',(minlat,maxlat,minlon,maxlon,minzoom,maxzoom))
 
   conn.commit()        
@@ -78,7 +78,7 @@ def process_queue(db,map,force=False,backend="renderd"):
       else:
         command = 'tirex-batch -n 0 --prio=50 '+command+' -f not-exists'  
 
-      print command  
+      print(command  )
       
       if system(command)==0:
         dcur=conn.cursor()
@@ -87,7 +87,7 @@ def process_queue(db,map,force=False,backend="renderd"):
 
     elif backend == "renderd":
     
-      print minlat,minlon,maxlat,maxlon
+      print(minlat,minlon,maxlat,maxlon)
     
       for zoom in range(minzoom,maxzoom+1):
         minx,miny=deg2num(minlat,minlon,zoom)
@@ -103,7 +103,7 @@ def process_queue(db,map,force=False,backend="renderd"):
           maxy=miny
           miny=ty
 
-        print zoom,minx,miny,maxx,maxy
+        print(zoom,minx,miny,maxx,maxy)
 
         maps = map.split(',')
         
@@ -116,9 +116,9 @@ def process_queue(db,map,force=False,backend="renderd"):
           if force:
             command = command+ ' --force'
     
-          print command  
+          print(command  )
       
-          if system(command)<>0:
+          if not system(command) == 0:
             return
 
       dcur=conn.cursor()
@@ -152,20 +152,20 @@ def main():
   if options.renderer:
     renderer=options.renderer
   else:
-    print "Using default rendering backend..."
+    print("Using default rendering backend...")
     renderer="default"
 
   if len(args)==1:
     filename,=args
-    print "Rendering file: "+filename+"\n"
+    print("Rendering file: "+filename+"\n")
     queue_render(db,filename)
   elif len(args)==4:
     minlat,minlon,maxlat,maxlon=args
-    print "Rendering region "+minlat+'..'+maxlat+' / '+minlon+'..'+maxlon+"\n"
+    print("Rendering region "+minlat+'..'+maxlat+' / '+minlon+'..'+maxlon+"\n")
     queue_tiles(db,float(minlat),float(minlon),float(maxlat),float(maxlon),int(zoom))
 
   if map:
-    print "Processing map(s) "+map+"\n"
+    print("Processing map(s) "+map+"\n")
     process_queue(db,map,force,renderer)
   
 if __name__ == "__main__":
diff --git a/pyrungps b/pyrungps_sync
similarity index 100%
rename from pyrungps
rename to pyrungps_sync
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..1ccb8be
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+setup(
+  name='pyrungps',
+  version='0.1',
+  description='Simple python tool to sync gpx tracks from GPS-SPORT site',
+  url='http://rvb.name/gpx',
+  author='Roman Bazalevskiy',
+  author_email='rvb@rvb.name',
+  license='WTFPL?',
+  packages=['pyrungps'],
+  zip_safe=False
+)
-- 
2.34.1