2 # -*- coding: iso-8859-15 -*-
3 """Library to handle WRGPX files.
4 A WRGPX file (WR stands for Winterrodeln) is a normal GPX file
5 that follows several conventions so that it can be rendered to
6 a map automatically."""
8 import mapnik2 as mapnik
11 from lxml import etree
15 import matplotlib.pyplot as plt
17 from pylons.i18n.translation import _
24 self.name = None # name as string or None
25 self.desc = None # description as string or None
26 self.author = None # author as string or None
27 self.author_email = None # email as string or None
28 self.copyright_year = None # year of copyright as int or None
29 self.license = None # link to license
30 self.link = None # link as string or None
31 self.time = None # datetime class or None
32 self.minlat = None # float coordinate or None
33 self.minlon = None # float coordinate or None
34 self.maxlat = None # float coordinate or None
35 self.maxlon = None # float coordinate or None
40 self.lat = None # float coordinate or None
41 self.lon = None # float coordinate or None
42 self.ele = None # height as float value or None
43 self.name = None # name as string or None
44 self.type = None # type as string or None
46 return "Waypoint %s" % str(self.name)
51 self.name = None # name as string or None
52 self.type = None # type as string or None
53 self.points = [] # one point is a tuple (lat, lon, ele) or (lat, lon, None)
55 return "Track %s" % str(self.name)
60 self.metadata = MetaData()
64 return "WrGpx (%d waypoints; %d tracks)" % (len(self.waypoints), len(self.tracks))
68 def __init__(self, message, level, line_nr=None):
69 "level ... 0 informal information; 1 ... suggestion for improvement; 2 ... warning; 3 ... error"
70 self.message = message
72 self.line_nr = line_nr
74 return "Hint: %s" % self.message
77 def parse_wrgpx(filename=None, string=None):
78 """Parses a WRGPX file and returns a tuple of a data structure representing the content of the GPX file
79 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."""
83 ns = '{http://www.topografix.com/GPX/1/1}'
85 def parse_wpt(element):
87 waypoint.lat = float(element.attrib['lat'])
88 waypoint.lon = float(element.attrib['lon'])
90 if el.tag == ns+'ele': waypoint.ele = float(el.text)
91 elif el.tag == ns+'name': waypoint.name = el.text
92 elif el.tag == ns+'desc': waypoint.desc = el.text
93 elif el.tag == ns+'link': waypoint.link = el.attrib['href']
94 elif el.tag == ns+'type': waypoint.type = el.text
97 schema = etree.XMLSchema(file=os.path.dirname(os.path.realpath(__file__)) + os.sep + 'gpx_1_1.xsd')
98 parser = etree.XMLParser(schema=schema)
99 if not string is None: root = etree.fromstring(string, parser)
101 # xml = etree.parse(open(filename, 'rb'), parser)
102 xml = etree.parse(filename, parser)
104 assert root.tag == ns + 'gpx' # Nothing else should be possible after validation
106 wrgpx_has_metadata = False
109 if element.tag == ns+'metadata':
110 wrgpx_has_metadata = True
112 if el.tag == ns+'name': wrgpx.metadata.name = el.text
113 elif el.tag == ns+'desc': wrgpx.metadata.desc = el.text
114 elif el.tag == ns+'author':
116 if e.tag == ns+'name': wrgpx.metadata.author = e.text
117 elif e.tag == ns+'email':
118 wrgpx.metadata.author_email = e.attrib['id'] + '@' + e.attrib['domain']
119 elif el.tag == ns+'copyright':
121 if e.tag == ns+'year': wrgpx.metadata.copyright_year = int(e.text)
122 elif e.tag == ns+'license': wrgpx.metadata.license = e.text
123 elif el.tag == ns+'link': wrgpx.metadata.link = el.attrib['href']
124 elif el.tag == ns+'time':
125 wrgpx.metadata.time = datetime.datetime.strptime(el.text, '%Y-%m-%dT%H:%M:%SZ') # '2008-11-10T20:44:52Z'
126 elif el.tag == ns+'bounds':
127 wrgpx.metadata.minlat = float(el.attrib['minlat'])
128 wrgpx.metadata.minlon = float(el.attrib['minlon'])
129 wrgpx.metadata.maxlat = float(el.attrib['maxlat'])
130 wrgpx.metadata.maxlon = float(el.attrib['maxlon'])
131 elif el.tag == ns+'extensions': hints.append(Hint("XML element extensions within XML element metadata is not used by WRGPX.", 2))
134 elif element.tag == ns+'wpt':
135 waypoint = parse_wpt(element)
136 wrgpx.waypoints.append(waypoint)
137 if waypoint.ele is None: hints.append(Hint(_("Elevation of waypoint '%s' should be given.") % str(waypoint), 1))
138 if waypoint.name is None: hints.append(Hint(_("Name of waypoint '%s' must be given.") % str(waypoint), 2))
139 if waypoint.type is None: hints.append(Hint(_("Type of waypoint '%s' must be given (Gasthaus, Bushaltestelle, ...)") % str(waypoint), 2))
140 if not waypoint.type in ['Gasthaus', 'Parkplatz', 'Bushaltestelle']: hints.append(Hint(_("Type of the waypoint '%s' has to be one of (Gasthaus, Bushaltestelle, ...)") % str(waypoint), 2))
143 elif element.tag == ns+'rte':
144 hints.append(Hint(_("GPX file has a 'rte' element. This is not used in WRGPX."), 2))
147 elif element.tag == ns+'trk':
150 if el.tag == ns+'name': track.name = el.text
151 elif el.tag == ns+'desc': track.desc = el.text
152 elif el.tag == ns+'link': track.link = el.attrib['href']
153 elif el.tag == ns+'type': track.type = el.text
154 elif el.tag == ns+'trkseg':
157 if e.tag == ns+'trkpt':
159 track.points.append((trkpt.lat, trkpt.lon, trkpt.ele))
160 if trkpt.ele is None: no_ele += 1
161 elif e.tag == ns+'extensions': hints.append(Hint(_("XML element extensions within XML element trkpt is not used by WRGPX."), 2))
162 if no_ele > 0: hints.append(Hint(_("%d of %d track points have no elevation (%s).") % (no_ele, len(track.points), str(track)), 1))
163 if len(track.points) == 0: hints.append(Hint(_("track '%s' is empty.") % str(track), 2))
164 wrgpx.tracks.append(track)
167 elif element.tag == ns+'extensions':
168 hints.append(Hint(_("GPX file has a 'extensions' element. This is not used in WRGPX."), 2))
170 if not wrgpx_has_metadata: hints.append(Hint(_("GPX file has a no metadata (like author, ...)."), 2))
171 return (wrgpx, hints)
174 def height_profile(wrgpx):
175 "Create a elevation profile for the wrgpx class. Raises a RuntimError in case of an error, otherwise returns the PNG file as string."
176 proj_utm32 = mapnik.Projection("+proj=utm +zone=32 +ellps=WGS84 +datum=WGS84 +units=m +no_defs")
177 bahntracks = [t for t in wrgpx.tracks if t.type=='Rodelbahn' and len(t.points) > 0]
178 if len(bahntracks) == 0: raise RuntimeError(_("There is no 'Rodelbahn' type track in the GPX file."))
180 # Descending order and check
181 for track in bahntracks:
182 if track.points[0][2] is None or track.points[-1][2] is None: raise RuntimeError(_('Track has no elevation data'))
183 if track.points[0][2] < track.points[-1][2]: track.points[:] = track.points[::-1]
184 bahntracks.sort(lambda x, y: -1 if x.points[0][2] > y.points[0][2] else 1)
186 for track in bahntracks: points.extend(track.points)
188 points = np.array(points)
189 for i in range(len(points)):
190 c = mapnik.Coord(points[i][1], points[i][0])
191 c = c.forward(proj_utm32)
196 s = np.sqrt(np.diff(points[:, 0]) ** 2 + np.diff(points[:, 1]) ** 2)
197 x = np.hstack((0, np.cumsum(s)))
203 axymin = np.floor_divide(min(h)-50, 100)*100
204 axymax = np.floor_divide(max(h)+150, 100)*100
206 plt.fill(np.hstack((x, x[-1], x[0])), np.hstack((h, 0, 0)), fc='#014e9a', ec='none')
207 plt.plot(x, h, color='black')
208 plt.axis([axxmin, axxmax, axymin, axymax])
210 plt.xlabel('Strecke in m [%d m Gesamtstrecke]' % round(x[-1], -1))
211 plt.ylabel('Höhe in m [%d m Differenz]' % round(h[0]-h[-1]))
212 plt.title('%s - Höhenprofil' % wrgpx.metadata.name)
215 imgdata = io.StringIO()
216 f.savefig(imgdata, dpi=width_px/plt.gcf().get_figwidth())
217 s = imgdata.getvalue()