]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/lib/wrgpx.py
9ba5c813beb6bb6f311b3438a16242a3d213c190
[philipp/winterrodeln/wradmin.git] / wradmin / lib / wrgpx.py
1 #!/usr/bin/python2.6
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."""
7
8 import mapnik2 as mapnik
9 import datetime
10 import os
11 from lxml import etree
12 import numpy as np
13 import matplotlib
14 matplotlib.use('Agg')
15 import matplotlib.pyplot as plt
16 import io
17 from pylons.i18n.translation import _
18
19
20
21
22 class MetaData:
23     def __init__(self):
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
36
37
38 class Waypoint:
39     def __init__(self):
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
45     def __repr__(self):
46         return "Waypoint %s" % str(self.name)
47
48
49 class Track:
50     def __init__(self):
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)
54     def __repr__(self):
55         return "Track %s" % str(self.name)
56
57
58 class WrGpx:
59     def __init__(self):
60         self.metadata = MetaData()
61         self.waypoints = []
62         self.tracks = []
63     def __repr__(self):
64         return "WrGpx (%d waypoints; %d tracks)" % (len(self.waypoints), len(self.tracks))
65
66
67 class Hint:
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
71         self.level = level
72         self.line_nr = line_nr
73     def __repr__(self):
74         return "Hint: %s" % self.message
75
76
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."""
80     hints = []
81     wrgpx = WrGpx()
82
83     ns = '{http://www.topografix.com/GPX/1/1}'
84
85     def parse_wpt(element):
86         waypoint = Waypoint()
87         waypoint.lat = float(element.attrib['lat'])
88         waypoint.lon = float(element.attrib['lon'])
89         for el in element:
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
95         return waypoint
96
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)
100     else: 
101         # xml = etree.parse(open(filename, 'rb'), parser)
102         xml = etree.parse(filename, parser)
103         root = xml.getroot()
104     assert root.tag == ns + 'gpx' # Nothing else should be possible after validation
105     
106     wrgpx_has_metadata = False
107     for element in root:
108         # Metadata
109         if element.tag == ns+'metadata':
110             wrgpx_has_metadata = True
111             for el in element:
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':
115                     for e in el:
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':
120                     for e in el:
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))
132
133         # Waypoints
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))
141
142         # Routes
143         elif element.tag == ns+'rte':
144             hints.append(Hint(_("GPX file has a 'rte' element. This is not used in WRGPX."), 2))
145
146         # Tracks
147         elif element.tag == ns+'trk':
148             track = Track()
149             for el in element:
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': 
155                     no_ele = 0
156                     for e in el:
157                         if e.tag == ns+'trkpt':
158                             trkpt = parse_wpt(e)
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)
165
166         # Extensions
167         elif element.tag == ns+'extensions':
168             hints.append(Hint(_("GPX file has a 'extensions' element. This is not used in WRGPX."), 2))
169
170     if not wrgpx_has_metadata: hints.append(Hint(_("GPX file has a no metadata (like author, ...)."), 2))
171     return (wrgpx, hints)
172
173
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."))
179
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)
185     points = []
186     for track in bahntracks: points.extend(track.points)
187
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)
192         points[i][0] = c.y
193         points[i][1] = c.x
194
195     # Calculate profile
196     s = np.sqrt(np.diff(points[:, 0]) ** 2 + np.diff(points[:, 1]) ** 2)
197     x = np.hstack((0, np.cumsum(s)))
198     h = points[:, 2]
199
200     # Plot
201     axxmin = 0
202     axxmax = x[-1]
203     axymin = np.floor_divide(min(h)-50, 100)*100
204     axymax = np.floor_divide(max(h)+150, 100)*100
205     f = plt.figure()
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])
209     plt.grid()
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)
213
214     width_px = 500
215     imgdata = io.StringIO()
216     f.savefig(imgdata, dpi=width_px/plt.gcf().get_figwidth())
217     s = imgdata.getvalue()
218     imgdata.close()
219     return s