]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blobdiff - wradmin/lib/wrgpx.py
Additional cleanup.
[philipp/winterrodeln/wradmin.git] / wradmin / lib / wrgpx.py
diff --git a/wradmin/lib/wrgpx.py b/wradmin/lib/wrgpx.py
new file mode 100644 (file)
index 0000000..2b9b314
--- /dev/null
@@ -0,0 +1,219 @@
+#!/usr/bin/python2.6
+# -*- coding: iso-8859-15 -*-
+"""Library to handle WRGPX files.
+A WRGPX file (WR stands for Winterrodeln) is a normal GPX file
+that follows several conventions so that it can be rendered to
+a map automatically."""
+
+import mapnik2 as mapnik
+import datetime
+import os
+from lxml import etree
+import numpy as np
+import matplotlib
+matplotlib.use('Agg')
+import matplotlib.pyplot as plt
+import StringIO
+from pylons.i18n.translation import _
+
+
+
+
+class MetaData:
+    def __init__(self):
+        self.name = None           # name as string or None
+        self.desc = None           # description as string or None
+        self.author = None         # author as string or None
+        self.author_email = None   # email as string or None
+        self.copyright_year = None # year of copyright as int or None
+        self.license = None        # link to license
+        self.link = None           # link as string or None
+        self.time = None           # datetime class or None
+        self.minlat = None         # float coordinate or None
+        self.minlon = None         # float coordinate or None
+        self.maxlat = None         # float coordinate or None
+        self.maxlon = None         # float coordinate or None
+
+
+class Waypoint:
+    def __init__(self):
+        self.lat = None            # float coordinate or None
+        self.lon = None            # float coordinate or None
+        self.ele = None            # height as float value or None
+        self.name = None           # name as string or None
+        self.type = None           # type as string or None
+    def __repr__(self):
+        return u"Waypoint %s" % unicode(self.name)
+
+
+class Track:
+    def __init__(self):
+        self.name = None           # name as string or None
+        self.type = None           # type as string or None
+        self.points = []           # one point is a tuple (lat, lon, ele) or (lat, lon, None)
+    def __repr__(self):
+        return u"Track %s" % unicode(self.name)
+
+
+class WrGpx:
+    def __init__(self):
+        self.metadata = MetaData()
+        self.waypoints = []
+        self.tracks = []
+    def __repr__(self):
+        return "WrGpx (%d waypoints; %d tracks)" % (len(self.waypoints), len(self.tracks))
+
+
+class Hint:
+    def __init__(self, message, level, line_nr=None):
+        "level ... 0 informal information; 1 ... suggestion for improvement; 2 ... warning; 3 ... error"
+        self.message = message
+        self.level = level
+        self.line_nr = line_nr
+    def __repr__(self):
+        return "Hint: %s" % self.message
+
+
+def parse_wrgpx(filename=None, string=None):
+    """Parses a WRGPX file and returns a tuple of a data structure representing the content of the GPX file
+    and a list of Hint classes. If the file is not well-formed or not an GPX 1.1 file, an etree.XMLSyntaxError exception is raised."""
+    hints = []
+    wrgpx = WrGpx()
+
+    ns = '{http://www.topografix.com/GPX/1/1}'
+
+    def parse_wpt(element):
+        waypoint = Waypoint()
+        waypoint.lat = float(element.attrib['lat'])
+        waypoint.lon = float(element.attrib['lon'])
+        for el in element:
+            if el.tag == ns+'ele': waypoint.ele = float(el.text)
+            elif el.tag == ns+'name': waypoint.name = el.text
+            elif el.tag == ns+'desc': waypoint.desc = el.text
+            elif el.tag == ns+'link': waypoint.link = el.attrib['href']
+            elif el.tag == ns+'type': waypoint.type = el.text
+        return waypoint
+
+    schema = etree.XMLSchema(file=os.path.dirname(os.path.realpath(__file__)) + os.sep + 'gpx_1_1.xsd')
+    parser = etree.XMLParser(schema=schema)
+    if not string is None: root = etree.fromstring(string, parser)
+    else: 
+        # xml = etree.parse(open(filename, 'rb'), parser)
+        xml = etree.parse(filename, parser)
+        root = xml.getroot()
+    assert root.tag == ns + 'gpx' # Nothing else should be possible after validation
+    
+    wrgpx_has_metadata = False
+    for element in root:
+        # Metadata
+        if element.tag == ns+'metadata':
+            wrgpx_has_metadata = True
+            for el in element:
+                if el.tag == ns+'name': wrgpx.metadata.name = el.text
+                elif el.tag == ns+'desc': wrgpx.metadata.desc = el.text
+                elif el.tag == ns+'author':
+                    for e in el:
+                        if e.tag == ns+'name': wrgpx.metadata.author = e.text
+                        elif e.tag == ns+'email':
+                            wrgpx.metadata.author_email = e.attrib['id'] + '@' + e.attrib['domain']
+                elif el.tag == ns+'copyright':
+                    for e in el:
+                        if e.tag == ns+'year': wrgpx.metadata.copyright_year = int(e.text)
+                        elif e.tag == ns+'license': wrgpx.metadata.license = e.text
+                elif el.tag == ns+'link': wrgpx.metadata.link = el.attrib['href']
+                elif el.tag == ns+'time':
+                    wrgpx.metadata.time = datetime.datetime.strptime(el.text, '%Y-%m-%dT%H:%M:%SZ') # '2008-11-10T20:44:52Z'
+                elif el.tag == ns+'bounds':
+                    wrgpx.metadata.minlat = float(el.attrib['minlat'])
+                    wrgpx.metadata.minlon = float(el.attrib['minlon'])
+                    wrgpx.metadata.maxlat = float(el.attrib['maxlat'])
+                    wrgpx.metadata.maxlon = float(el.attrib['maxlon'])
+                elif el.tag == ns+'extensions': hints.append(Hint("XML element extensions within XML element metadata is not used by WRGPX.", 2))
+
+        # Waypoints
+        elif element.tag == ns+'wpt':
+            waypoint = parse_wpt(element)
+            wrgpx.waypoints.append(waypoint)
+            if waypoint.ele is None: hints.append(Hint(_(u"Elevation of waypoint '%s' should be given.") % unicode(waypoint), 1))
+            if waypoint.name is None: hints.append(Hint(_(u"Name of waypoint '%s' must be given.") % unicode(waypoint), 2))
+            if waypoint.type is None: hints.append(Hint(_(u"Type of waypoint '%s' must be given (Gasthaus, Bushaltestelle, ...)") % unicode(waypoint), 2))
+            if not waypoint.type in ['Gasthaus', 'Parkplatz', 'Bushaltestelle']: hints.append(Hint(_(u"Type of the waypoint '%s' has to be one of (Gasthaus, Bushaltestelle, ...)") % unicode(waypoint), 2))
+
+        # Routes
+        elif element.tag == ns+'rte':
+            hints.append(Hint(_("GPX file has a 'rte' element. This is not used in WRGPX."), 2))
+
+        # Tracks
+        elif element.tag == ns+'trk':
+            track = Track()
+            for el in element:
+                if el.tag == ns+'name': track.name = el.text
+                elif el.tag == ns+'desc': track.desc = el.text
+                elif el.tag == ns+'link': track.link = el.attrib['href']
+                elif el.tag == ns+'type': track.type = el.text
+                elif el.tag == ns+'trkseg': 
+                    no_ele = 0
+                    for e in el:
+                        if e.tag == ns+'trkpt':
+                            trkpt = parse_wpt(e)
+                            track.points.append((trkpt.lat, trkpt.lon, trkpt.ele))
+                            if trkpt.ele is None: no_ele += 1
+                        elif e.tag == ns+'extensions': hints.append(Hint(_("XML element extensions within XML element trkpt is not used by WRGPX."), 2))
+                    if no_ele > 0: hints.append(Hint(_(u"%d of %d track points have no elevation (%s).") % (no_ele, len(track.points), unicode(track)), 1))
+            if len(track.points) == 0: hints.append(Hint(_(u"track '%s' is empty.") % unicode(track), 2))
+            wrgpx.tracks.append(track)
+
+        # Extensions
+        elif element.tag == ns+'extensions':
+            hints.append(Hint(_("GPX file has a 'extensions' element. This is not used in WRGPX."), 2))
+
+    if not wrgpx_has_metadata: hints.append(Hint(_("GPX file has a no metadata (like author, ...)."), 2))
+    return (wrgpx, hints)
+
+
+def height_profile(wrgpx):
+    "Create a elevation profile for the wrgpx class. Raises a RuntimError in case of an error, otherwise returns the PNG file as string."
+    proj_utm32 = mapnik.Projection("+proj=utm +zone=32 +ellps=WGS84 +datum=WGS84 +units=m +no_defs")
+    bahntracks = [t for t in wrgpx.tracks if t.type=='Rodelbahn' and len(t.points) > 0]
+    if len(bahntracks) == 0: raise RuntimeError(_(u"There is no 'Rodelbahn' type track in the GPX file."))
+
+    # Descending order and check
+    for track in bahntracks:
+        if track.points[0][2] is None or track.points[-1][2] is None: raise RuntimeError(_(u'Track has no elevation data'))
+        if track.points[0][2] < track.points[-1][2]: track.points[:] = track.points[::-1]
+    bahntracks.sort(lambda x, y: -1 if x.points[0][2] > y.points[0][2] else 1)
+    points = []
+    for track in bahntracks: points.extend(track.points)
+
+    points = np.array(points)
+    for i in xrange(len(points)):
+        c = mapnik.Coord(points[i][1], points[i][0])
+        c = c.forward(proj_utm32)
+        points[i][0] = c.y
+        points[i][1] = c.x
+
+    # Calculate profile
+    s = np.sqrt(np.diff(points[:, 0]) ** 2 + np.diff(points[:, 1]) ** 2)
+    x = np.hstack((0, np.cumsum(s)))
+    h = points[:, 2]
+
+    # Plot
+    axxmin = 0
+    axxmax = x[-1]
+    axymin = np.floor_divide(min(h)-50, 100)*100
+    axymax = np.floor_divide(max(h)+150, 100)*100
+    f = plt.figure()
+    plt.fill(np.hstack((x, x[-1], x[0])), np.hstack((h, 0, 0)), fc='#014e9a', ec='none')
+    plt.plot(x, h, color='black')
+    plt.axis([axxmin, axxmax, axymin, axymax])
+    plt.grid()
+    plt.xlabel(u'Strecke in m [%d m Gesamtstrecke]' % round(x[-1], -1))
+    plt.ylabel(u'Höhe in m [%d m Differenz]' % round(h[0]-h[-1]))
+    plt.title(u'%s - Höhenprofil' % wrgpx.metadata.name)
+
+    width_px = 500
+    imgdata = StringIO.StringIO()
+    f.savefig(imgdata, dpi=width_px/plt.gcf().get_figwidth())
+    s = imgdata.getvalue()
+    imgdata.close()
+    return s