Skip to content

Commit

Permalink
Port to Python 3, whilst retaining compatibility with Python 2, from …
Browse files Browse the repository at this point in the history
…a single codebase. Also fixed a defect in gpx.reduce_points() revealed by Python 3 being stricter which object comparisons.
  • Loading branch information
rob-smallshire committed Nov 27, 2012
1 parent 8916e42 commit 166afba
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 136 deletions.
4 changes: 2 additions & 2 deletions gpxpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import gpx as mod_gpx
import parser as mod_parser
from . import gpx as mod_gpx
from . import parser as mod_parser

def parse(xml_or_file):
""" Parse xml (string) or file object. This is just an wrapper for GPXParser.parse() function """
Expand Down
3 changes: 2 additions & 1 deletion gpxpy/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
# limitations under the License.

import math as mod_math
import utils as mod_utils

from . import utils as mod_utils

# Generic geo related function and class(es)

Expand Down
34 changes: 18 additions & 16 deletions gpxpy/gpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.

"""
GPX related stuff
"""

import pdb

import logging as mod_logging
import math as mod_math
import datetime as mod_datetime
import collections as mod_collections

import utils as mod_utils
import copy as mod_copy
import geo as mod_geo

"""
GPX related stuff
"""
from . import utils as mod_utils
from . import geo as mod_geo

# GPX date format
DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
Expand Down Expand Up @@ -63,9 +62,7 @@
('point', 'distance_from_start', 'track_no', 'segment_no', 'point_no'))

class GPXException(Exception):

def __init__(self, message):
Exception.__init__(self, message)
pass

class GPXWaypoint(mod_geo.Location):

Expand Down Expand Up @@ -252,7 +249,7 @@ def __str__(self):

def to_xml(self, version=None):
content = ''
if self.elevation != None:
if self.elevation is not None:
content += mod_utils.to_xml('ele', content=self.elevation)
if self.time:
content += mod_utils.to_xml('time', content=self.time.strftime(DATE_FORMAT))
Expand Down Expand Up @@ -1292,17 +1289,22 @@ def get_moving_data(self, stopped_speed_treshold=None):

return MovingData(moving_time, stopped_time, moving_distance, stopped_distance, max_speed)

def reduce_points(self, max_points_no, min_distance=None):
def reduce_points(self, max_points_no=None, min_distance=None):
"""
Reduce this track to the desired number of points
max_points = The maximum number of points after the reduction
min_distance = The minimum distance between two points
"""

if not min_distance:
points_no = len(list(self.walk()))
if not max_points_no or points_no <= max_points_no:
return
if max_points_no is None and min_distance is None:
raise ValueError("Either max_point_no or min_distance must be supplied")

if max_points_no is not None and max_points_no < 2:
raise ValueError("max_points_no must be greater than or equal to 2")

points_no = len(list(self.walk()))
if max_points_no is not None and points_no <= max_points_no:
return

length = self.length_3d()

Expand Down
85 changes: 34 additions & 51 deletions gpxpy/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,41 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import pdb
from __future__ import print_function

import logging as mod_logging
import datetime as mod_datetime

import xml.dom.minidom as mod_minidom

import gpx as mod_gpx
import utils as mod_utils
import re as mod_re

from . import gpx as mod_gpx
from . import utils as mod_utils
from .portability import has_key


def parse_time(string):
if not string:
return None
try:
return mod_datetime.datetime.strptime(string, mod_gpx.DATE_FORMAT)
except Exception, e:
except Exception as e:
if mod_re.match('^.*\.\d+Z$', string):
string = mod_re.sub('\.\d+Z', 'Z', string)
try:
return mod_datetime.datetime.strptime(string, mod_gpx.DATE_FORMAT)
except Exception, e:
except Exception as e:
mod_logging.error('Invalid timestemp %s' % string)
return None



class AbstractXMLParser:
""" Common methods used in GPXParser and KMLParser """

gpx = None
xml = None
def __init__(self, xml):

valid = None
error = None

def init(self, xml_or_file):
if hasattr(xml_or_file, 'read'):
self.xml = xml_or_file.read()
else:
if isinstance(xml_or_file, unicode):
self.xml = xml_or_file.encode('utf-8')
else:
self.xml = str(xml_or_file)
text = xml.read() if hasattr(xml, 'read') else xml
self.xml = mod_utils.make_str(text)

self.valid = False
self.error = None
Expand All @@ -81,18 +74,13 @@ def get_node_data(self, node):

class GPXParser(AbstractXMLParser):

def __init__(self, xml_or_file=None):
self.init(xml_or_file)

self.gpx = mod_gpx.GPX()

def parse(self):
try:
dom = mod_minidom.parseString(self.xml)
self.__parse_dom(dom)

return self.gpx
except Exception, e:
except Exception as e:
mod_logging.debug('Error in:\n%s\n-----------\n' % self.xml)
mod_logging.exception(e)
self.error = str(e)
Expand Down Expand Up @@ -144,19 +132,19 @@ def __parse_dom(self, dom):
self.valid = True

def _parse_bounds(self, node):
if node.attributes.has_key('minlat'):
if has_key(node.attributes, 'minlat'):
self.gpx.min_latitude = mod_utils.to_number(node.attributes['minlat'].nodeValue)
if node.attributes.has_key('maxlat'):
if has_key(node.attributes, 'maxlat'):
self.gpx.min_latitude = mod_utils.to_number(node.attributes['maxlat'].nodeValue)
if node.attributes.has_key('minlon'):
if has_key(node.attributes, 'minlon'):
self.gpx.min_longitude = mod_utils.to_number(node.attributes['minlon'].nodeValue)
if node.attributes.has_key('maxlon'):
if has_key(node.attributes, 'maxlon'):
self.gpx.min_longitude = mod_utils.to_number(node.attributes['maxlon'].nodeValue)

def _parse_waypoint(self, node):
if not node.attributes.has_key('lat'):
if not has_key(node.attributes, 'lat'):
raise mod_gpx.GPXException('Waypoint without latitude')
if not node.attributes.has_key('lon'):
if not has_key(node.attributes, 'lon'):
raise mod_gpx.GPXException('Waypoint without longitude')

lat = mod_utils.to_number(node.attributes['lat'].nodeValue)
Expand Down Expand Up @@ -220,9 +208,9 @@ def _parse_route(self, node):
return route

def _parse_route_point(self, node):
if not node.attributes.has_key('lat'):
if not has_key(node.attributes, 'lat'):
raise mod_gpx.GPXException('Waypoint without latitude')
if not node.attributes.has_key('lon'):
if not has_key(node.attributes, 'lon'):
raise mod_gpx.GPXException('Waypoint without longitude')

lat = mod_utils.to_number(node.attributes['lat'].nodeValue)
Expand Down Expand Up @@ -297,11 +285,11 @@ def __parse_track_segment(self, node):

def __parse_track_point(self, node):
latitude = None
if node.attributes.has_key('lat'):
if has_key(node.attributes, 'lat'):
latitude = mod_utils.to_number(node.attributes['lat'].nodeValue)

longitude = None
if node.attributes.has_key('lon'):
if has_key(node.attributes, 'lon'):
longitude = mod_utils.to_number(node.attributes['lon'].nodeValue)

time_node = mod_utils.find_first_node(node, 'time')
Expand Down Expand Up @@ -346,18 +334,13 @@ class KMLParser(AbstractXMLParser):
See http://code.google.com/apis/kml/documentation/kmlreference.html for more details.
"""

gpx = None

def __init__(self, xml_or_file=None):
self.init(xml_or_file)

def parse(self):
try:
dom = mod_minidom.parseString(self.xml)
self.__parse_dom(dom)

return self.gpx
except Exception, e:
except Exception as e:
mod_logging.debug('Error in:\n%s\n-----------\n' % self.xml)
mod_logging.exception(e)
self.error = str(e)
Expand All @@ -377,23 +360,23 @@ def __parse_dom(self, xml):
gpx_xml = file.read()
file.close()

parser = mod_gpx.GPXParser(gpx_xml)
parser = GPXParser(gpx_xml)
gpx = parser.parse()

print gpx.to_xml()
print(gpx.to_xml())

if parser.is_valid():
print 'TRACKS:'
print('TRACKS:')
for track in gpx.tracks:
print 'name%s, 2d:%s, 3d:%s' % (track.name, track.length_2d(), track.length_3d())
print '\tTRACK SEGMENTS:'
print('name%s, 2d:%s, 3d:%s' % (track.name, track.length_2d(), track.length_3d()))
print('\tTRACK SEGMENTS:')
for track_segment in track.segments:
print '\t2d:%s, 3d:%s' % (track_segment.length_2d(), track_segment.length_3d())
print('\t2d:%s, 3d:%s' % (track_segment.length_2d(), track_segment.length_3d()))

print 'ROUTES:'
print('ROUTES:')
for route in gpx.routes:
print route.name
print(route.name)
else:
print 'error: %s' % parser.get_error()
print('error: %s' % parser.get_error())


51 changes: 51 additions & 0 deletions gpxpy/portability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Utilities for assisting with portability between Python 2 and Python 3.
"""

try:
# Python 2
#noinspection PyCompatibility
base = basestring
def is_string(s):
return isinstance(s, base)
except NameError:
# Python 3
def is_string(s):
return isinstance(s, str)

try:
# Python 2
#noinspection PyCompatibility
unicode()

def has_unicode_type():
return True

except NameError:
# Python 3
def has_unicode_type():
return False


try:
# Python 2
trial_dict = dict()
trial_dict.has_key('foo')
def has_key(d, key):
return d.has_key(key)
except AttributeError:
# Python 3
def has_key(d, key):
return key in d
finally:
del trial_dict


try:
# Python 2.x and Python 3.2+
is_callable = callable
except NameError:
# Python 3.0 and Python 3.1
from collections import Callable

def is_callable(x):
return isinstance(x, Callable)
13 changes: 10 additions & 3 deletions gpxpy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import logging
import xml.sax.saxutils as mod_saxutils
from gpxpy.portability import has_unicode_type

def to_xml(tag, attributes={}, content=None, default=None, escape=False):
result = '\n<%s' % tag
Expand All @@ -38,8 +39,7 @@ def to_xml(tag, attributes={}, content=None, default=None, escape=False):

result += ''

if isinstance(result, unicode):
result = result.encode('utf-8')
result = make_str(result)

return result

Expand Down Expand Up @@ -72,7 +72,7 @@ def __hash(obj):
if obj == None:
return result
elif isinstance(obj, dict):
raise Error('__hash_single_object for dict not yet implemented')
raise RuntimeError('__hash_single_object for dict not yet implemented')
elif isinstance(obj, list) or isinstance(obj, tuple):
return hash_list_or_tuple(obj)

Expand All @@ -95,3 +95,10 @@ def hash_object(obj, *attributes):
return result


def make_str(s):
"""Convert a str or unicode object into a str type.
"""
if has_unicode_type():
if isinstance(s, unicode):
return s.encode("utf-8")
return str(s)
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
url = 'http://www.trackprofiler.com/gpxpy/index.html',
packages = [
'gpxpy',
],
],
classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
],
)

Loading

0 comments on commit 166afba

Please sign in to comment.