X-Git-Url: https://git.toastfreeware.priv.at/philipp/winterrodeln/wradmin.git/blobdiff_plain/bafbd4f1c90f8b0077eb76e8329e6446279377fa..355802a7214e1ba0764b70bad0299641dbd634c2:/wradmin/lib/wrgpx.py diff --git a/wradmin/lib/wrgpx.py b/wradmin/lib/wrgpx.py new file mode 100644 index 0000000..2b9b314 --- /dev/null +++ b/wradmin/lib/wrgpx.py @@ -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