├── README.md ├── doc └── images │ ├── gpxplot-google-chart-example-1.png │ └── track.png ├── gpxplot.py └── online ├── app.yaml ├── gpxplot.py ├── index.html ├── index.py └── index.yaml /README.md: -------------------------------------------------------------------------------- 1 | # gpxplot: Visualize elevation or velocity profile of GPS track 2 | 3 | Automatically exported from code.google.com/p/gpxplot 4 | 5 | This script can plot profiles of GPX tracks or prepare plot data for external tools. 6 | Two types of profiles are currently supported: velocity and elevation (altitude) 7 | as a function of time or distance travelled. 8 | Multi-segment (non-continuous) GPS tracks are supported. 9 | 10 | Normally, `gpxplot` reads GPX track and evaluates distance and velocity data. 11 | It can print data in tabular form suitable for plotting by external plotting tools. 12 | It can also plot trip profile directly using gnuplot (see options `-g` and `-o`). 13 | 14 | Features: 15 | 16 | * using haversine formula to calculate distances (spherical Earth) 17 | * support of multi-segment (discontinuous) tracks 18 | * gnuplot support: 19 | * generate plots if gnuplot.py is available 20 | * generate gnuplot script if gnuplot.py is not available 21 | * plot interactively and plot-to-file modes 22 | * ~~Google Chart API support~~ _DEPRECATED_ 23 | * ~~print URL or the plot~~ _DEPRECATED_ 24 | * tabular track profile data can be generated 25 | * metric and English units 26 | * timezone support 27 | 28 | ~~You can also use `gpxplot` online, please check [gpxplot](http://gpxplot.appspot.com) page. 29 | There is also [a simple API](http://code.google.com/p/gpxplot/source/detail?r=19) for the web script.~~ 30 | 31 | _The online version may stop working without notice, 32 | because image-based Google Charts 33 | [are deprecated](http://googledevelopers.blogspot.it/2012/04/changes-to-deprecation-policies-and-api.html)_ 34 | 35 | ## Download 36 | 37 | Current version: [gpxplot.py](https://raw.githubusercontent.com/astanin/gpxplot/master/gpxplot.py) 38 | 39 | ## Stand-alone program 40 | 41 | ``` 42 | Usage: gpxplot.py [action] [options] track.gpx 43 | 44 | Actions: 45 | -g plot using gnuplot.py 46 | --gprint print gnuplot script to standard output 47 | --google print Google Chart URL 48 | --table print data table (default) 49 | 50 | Options: 51 | -h, --help print this message 52 | -E use English units (metric units used by default) 53 | -x var plot var = { time | distance } against x-axis 54 | -y var plot var = { elevation | velocity } against y-axis 55 | -o imagefile save plot to image file (supported: PNG, JPG, EPS, SVG) 56 | -t tzname use local timezone tzname (e.g. 'Europe/Moscow') 57 | -n N_points reduce number of points in the plot to approximately N_points 58 | ``` 59 | 60 | ## Online version (DEPRECATED) 61 | 62 | Please visit [gpxplot.appspot.com](http://gpxplot.appspot.com/) to plot your GPX tracks online. 63 | It should be straightforward, but online version is less powerful than the stand-alone script. 64 | _This feauture is deprecated_ 65 | 66 | ### Web API (DEPRECATED) 67 | 68 | If you want to plot profiles for your tracks automatically, 69 | but don't want to install the script, you can use web API. 70 | Just put your GPX tracks online, and reference them in the request: 71 | 72 | ``` 73 | http://gpxplot.appspot.com/api/0.1.2/plot?gpxurl=URL_OF_YOUR_GPX_FILE&output=png 74 | ``` 75 | 76 | You can use this request URL as a URL of the image, for example: 77 | 78 | ``` 79 | Elevation profile 81 | ``` 82 | 83 | Availability of this service is subject to free quotas on Google App Engine. 84 | Also, Google App Engine is not very good at processing big files (1 MB and more). 85 | Incompatible changes to the API correspond will be reflected in the second version number (0.*1*.2 ⇒ 0.*2*.0). 86 | 87 | Web API versions: 88 | [0.1](https://github.com/astanin/gpxplot/commit/4a5c9e6702916b25e42e7967cb52a1f0bcab5a9a), 89 | [0.1.1](https://github.com/astanin/gpxplot/commit/fa5801bc091b6432d4d18b1c99da4d4becc8f460), 90 | [0.1.2](https://github.com/astanin/gpxplot/commit/3dba6769fc160b6cbe5b4a16b644fa22778b52a3). 91 | 92 | ## Examples 93 | 94 | If you run gpxplot like this: 95 | 96 | ``` 97 | $ ./gpxplot.py -g -x time -y elevation test.gpx 98 | ``` 99 | 100 | it will try to plot elevation-against-time profile for given track. 101 | You need [gnuplot.py](http://gnuplot-py.sourceforge.net/) installed run this example. 102 | A new window with a plot will open. 103 | 104 | If you have only `gnuplot` installed, but not `gnuplot.py`, you can print the gnuplot script to standard output. 105 | In this case run `gnuplot` manually like this: 106 | 107 | ``` 108 | $ ./gpxplot.py --gprint -x time -y elevation test.gpx | gnuplot -persist - 109 | ``` 110 | 111 | To save the plot to file use `-o filename`: 112 | 113 | ``` 114 | ./gpxplot.py -g -x time -y elevation -o track.svg test.gpx 115 | ``` 116 | 117 | The plot generated may look like this: 118 | 119 | ![](doc/images/track.png) 120 | 121 | To plot using Google Chart API use `--google`: 122 | 123 | ``` 124 | ./gpxplot.py --google -n 200 -E test.gpx 125 | ``` 126 | 127 | It will print a long URL, which points to the PNG image with a plot: 128 | 129 | ![](doc/images/gpxplot-google-chart-example-1.png) 130 | 131 | A small tip: unix shell considers “&” symbols in the URL as an “end of command” markers. 132 | To avoid it, quote the URL properly in shell scripts. 133 | For example, to view the plot in the browser, the shell command is 134 | 135 | ``` 136 | firefox "$(gpxplot.py -google -n 200 -E test.gpx)" 137 | ``` 138 | 139 | Please note that the number of points was reduced to approximately 200 (option `-n 200`) 140 | and the units are miles/feet (option `-E`). 141 | 142 | ## Tips & Tricks 143 | 144 | * Please note that time or elevation data may be missing from the GPX file. 145 | Such tracks may be produced, for example, by Garmin devices, 146 | if you use their [Save track](http://www.gpsmap.net/GarminHints.html#GarminSaveFunction) function 147 | (use their ACTIVE LOG instead). 148 | Track files without time data cannot be used to plot velocity or time profiles. 149 | 150 | * Preprocessing tracks with [gpsbabel](http://www.gpsbabel.org/) is a good idea. 151 | For example, this would remove many ``wrong'' points from the track: 152 | 153 | ``` 154 | gpsbabel -t -i gpx -f original-track.gpx -x discard,hdop=3,vdop=3 -o gpx -F fixed-track.gpx 155 | ``` 156 | 157 | * Saving images to vector graphics formats (SVG, EPS) may produce higher quality plots. 158 | 159 | * You can smooth and average your track with gpsbabel before plotting with gpxplot. 160 | Two gpsbabel filters are especially useful: `-x simplify,count=N` (to reduce number of points) 161 | and `-x position,distance=Nm` (to increase minimal distance between track points to N meters). 162 | 163 | * gpxplot may be used in unix pipes, a special filename "-" means that the data (GPX file) 164 | is to be read from stdin. For instance: 165 | 166 | ``` 167 | cat my-track.gpx | \ 168 | gpsbabel -i gpx -f - -x position,distance=100m -o gpx -F - | \ 169 | gpxplot.py --gprint -x dist -y vel - | \ 170 | gnuplot -persist - 171 | ``` 172 | -------------------------------------------------------------------------------- /doc/images/gpxplot-google-chart-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astanin/gpxplot/fcbb433710a9d9dcfff095dad99a559e0f7f04a7/doc/images/gpxplot-google-chart-example-1.png -------------------------------------------------------------------------------- /doc/images/track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astanin/gpxplot/fcbb433710a9d9dcfff095dad99a559e0f7f04a7/doc/images/track.png -------------------------------------------------------------------------------- /gpxplot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set fileencoding=utf8 ts=4 sw=4 noexpandtab: 3 | 4 | # (c) Sergey Astanin 2008 5 | 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | """usage: gpxplot.py [action] [options] track.gpx 20 | 21 | Analyze GPS track and plot elevation and velocity profiles. 22 | 23 | Features: 24 | * using haversine formula to calculate distances (spherical Earth) 25 | * support of multi-segment (discontinuous) tracks 26 | * gnuplot support: 27 | - generate plots if gnuplot.py is available 28 | - generate gnuplot script if gnuplot.py is not available 29 | - plot interactively and plot-to-file modes 30 | * Google Chart API support: 31 | - print URL or the plot 32 | * tabular track profile data can be generated 33 | * metric and English units 34 | * timezone support 35 | 36 | Actions: 37 | -g plot using gnuplot.py 38 | --gprint print gnuplot script to standard output 39 | --google print Google Chart URL 40 | --table print data table (default) 41 | 42 | Options: 43 | -h, --help print this message 44 | -E use English units (metric units used by default) 45 | -x var plot var = { time | distance } against x-axis 46 | -y var plot var = { elevation | velocity } against y-axis 47 | -o imagefile save plot to image file (supported: PNG, JPG, EPS, SVG) 48 | -t tzname use local timezone tzname (e.g. 'Europe/Moscow') 49 | -n N_points reduce number of points in the plot to approximately N_points 50 | """ 51 | 52 | import sys 53 | import datetime 54 | import getopt 55 | from string import join 56 | from math import sqrt,sin,cos,asin,pi,ceil 57 | from os.path import basename 58 | from re import sub 59 | 60 | import logging 61 | #logging.basicConfig(level=logging.DEBUG,format='%(levelname)s: %(message)s') 62 | debug=logging.debug 63 | 64 | try: 65 | import pytz 66 | except: 67 | pass 68 | 69 | GPX10='{http://www.topografix.com/GPX/1/0}' 70 | GPX11='{http://www.topografix.com/GPX/1/1}' 71 | dateformat='%Y-%m-%dT%H:%M:%SZ' 72 | 73 | R=6371.0008 # Earth volumetric radius 74 | milesperkm=0.621371192 75 | feetperm=3.2808399 76 | 77 | strptime=datetime.datetime.strptime 78 | 79 | var_time=2 80 | var_ele=3 81 | var_dist=4 82 | var_vel=5 83 | 84 | var_names={ 't': var_time, 85 | 'time': var_time, 86 | 'd': var_dist, 87 | 'dist': var_dist, 88 | 'distance': var_dist, 89 | 'ele': var_ele, 90 | 'elevation': var_ele, 91 | 'a': var_ele, 92 | 'alt': var_ele, 93 | 'altitude': var_ele, 94 | 'v': var_vel, 95 | 'vel': var_vel, 96 | 'velocity': var_vel, 97 | } 98 | 99 | EXIT_EOPTION=1 100 | EXIT_EDEPENDENCY=2 101 | EXIT_EFORMAT=3 102 | 103 | def haversin(theta): 104 | return sin(0.5*theta)**2 105 | 106 | def distance(p1,p2): 107 | lat1,lon1=[a*pi/180.0 for a in p1] 108 | lat2,lon2=[a*pi/180.0 for a in p2] 109 | deltalat=lat2-lat1 110 | deltalon=lon2-lon1 111 | h=haversin(deltalat)+cos(lat1)*cos(lat2)*haversin(deltalon) 112 | dist=2*R*asin(sqrt(h)) 113 | return dist 114 | 115 | def read_all_segments(trksegs,tzname=None,ns=GPX10,pttag='trkpt'): 116 | trk=[] 117 | for seg in trksegs: 118 | s=[] 119 | prev_ele,prev_time=0.0,None 120 | trkpts=seg.findall(ns+pttag) 121 | for pt in trkpts: 122 | lat=float(pt.attrib['lat']) 123 | lon=float(pt.attrib['lon']) 124 | time=pt.findtext(ns+'time') 125 | def prettify_time(time): 126 | time=sub(r'\.\d+Z$','Z',time) 127 | time=strptime(time,dateformat) 128 | if tzname: 129 | time=time.replace(tzinfo=pytz.utc) 130 | time=time.astimezone(pytz.timezone(tzname)) 131 | return time 132 | if time: 133 | prev_time=time 134 | time=prettify_time(time) 135 | elif prev_time: # timestamp is missing, use the prev point 136 | time=prev_time 137 | time=prettify_time(time) 138 | ele=pt.findtext(ns+'ele') 139 | if ele: 140 | ele=float(ele) 141 | prev_ele=ele 142 | else: 143 | ele=prev_ele # elevation data is missing, use the prev point 144 | s.append([lat, lon, time, ele]) 145 | trk.append(s) 146 | return trk 147 | 148 | def reduce_points(trk,npoints=None): 149 | count=sum([len(s) for s in trk]) 150 | if npoints: 151 | ptperpt=1.0*count/npoints 152 | else: 153 | ptperpt=1.0 154 | skip=int(ceil(ptperpt)) 155 | debug('ptperpt=%f skip=%d'%(ptperpt,skip)) 156 | newtrk=[] 157 | for seg in trk: 158 | if len(seg) > 0: 159 | newseg=seg[:-1:skip]+[seg[-1]] 160 | newtrk.append(newseg) 161 | debug('original: %d pts, filtered: %d pts'%\ 162 | (count,sum([len(s) for s in newtrk]))) 163 | return newtrk 164 | 165 | def eval_dist_velocity(trk): 166 | dist=0.0 167 | newtrk=[] 168 | for seg in trk: 169 | if len(seg)>0: 170 | newseg=[] 171 | prev_lat,prev_lon,prev_time,prev_ele=None,None,None,None 172 | for pt in seg: 173 | lat,lon,time,ele=pt 174 | if prev_lat and prev_lon: 175 | delta=distance([lat,lon],[prev_lat,prev_lon]) 176 | if time and prev_time: 177 | try: 178 | vel=3600*delta/((time-prev_time).seconds) 179 | except ZeroDivisionError: 180 | vel=0.0 # probably the point lacked the timestamp 181 | else: 182 | vel=0.0 183 | else: # new segment 184 | delta=0.0 185 | vel=0.0 186 | dist=dist+delta 187 | newseg.append([lat,lon,time,ele,dist,vel]) 188 | prev_lat,prev_lon,prev_time=lat,lon,time 189 | newtrk.append(newseg) 190 | return newtrk 191 | 192 | def parse_gpx_data(gpxdata,tzname=None,npoints=None): 193 | try: 194 | import xml.etree.ElementTree as ET 195 | except: 196 | try: 197 | import elementtree.ElementTree as ET 198 | except: 199 | try: 200 | import cElementTree as ET 201 | except: 202 | try: 203 | import lxml.etree as ET 204 | except: 205 | print 'this script needs ElementTree (Python>=2.5)' 206 | sys.exit(EXIT_EDEPENDENCY) 207 | 208 | def find_trksegs_or_route(etree, ns): 209 | trksegs=etree.findall('.//'+ns+'trkseg') 210 | if trksegs: 211 | return trksegs, "trkpt" 212 | else: # try to display route if track is missing 213 | rte=etree.findall('.//'+ns+'rte') 214 | return rte, "rtept" 215 | 216 | # try GPX10 namespace first 217 | etree=ET.XML(gpxdata) 218 | trksegs,pttag=find_trksegs_or_route(etree, GPX10) 219 | NS=GPX10 220 | if not trksegs: # try GPX11 namespace otherwise 221 | trksegs,pttag=find_trksegs_or_route(etree, GPX11) 222 | NS=GPX11 223 | if not trksegs: # try without any namespace 224 | trksegs,pttag=find_trksegs_or_route(etree, "") 225 | NS="" 226 | trk=read_all_segments(trksegs,tzname=tzname,ns=NS,pttag=pttag) 227 | trk=reduce_points(trk,npoints=npoints) 228 | trk=eval_dist_velocity(trk) 229 | return trk 230 | 231 | def read_gpx_trk(filename,tzname,npoints): 232 | if filename == "-": 233 | gpx=sys.stdin.read() 234 | debug("length(gpx) from stdin = %d" % len(gpx)) 235 | else: 236 | gpx=open(filename).read() 237 | debug("length(gpx) from file = %d" % len(gpx)) 238 | return parse_gpx_data(gpx,tzname,npoints) 239 | 240 | def google_ext_encode(i): 241 | """Google Charts' extended encoding, 242 | see http://code.google.com/apis/chart/mappings.html#extended_values""" 243 | enc='ABCDEFGHIJKLMNOPQRSTUVWXYZ' 244 | enc=enc+enc.lower()+'0123456789-.' 245 | i=int(i)%4096 # modulo 4096 246 | figure=enc[int(i/len(enc))]+enc[int(i%len(enc))] 247 | return figure 248 | 249 | def google_text_encode_data(trk,x,y,min_x,max_x,min_y,max_y,metric=True): 250 | if metric: 251 | mlpkm,fpm=1.0,1.0 252 | else: 253 | mlpkm,fpm=milesperkm,feetperm 254 | xenc=lambda x: "%.1f"%x 255 | yenc=lambda y: "%.1f"%y 256 | data='&chd=t:'+join([ join([xenc(p[x]*mlpkm) for p in seg],',')+\ 257 | '|'+join([yenc(p[y]*fpm) for p in seg],',') \ 258 | for seg in trk if len(seg) > 0],'|') 259 | data=data+'&chds='+join([join([xenc(min_x),xenc(max_x),yenc(min_y),yenc(max_y)],',') \ 260 | for seg in trk if len(seg) > 0],',') 261 | return data 262 | 263 | def google_ext_encode_data(trk,x,y,min_x,max_x,min_y,max_y,metric=True): 264 | if metric: 265 | mlpkm,fpm=1.0,1.0 266 | else: 267 | mlpkm,fpm=milesperkm,feetperm 268 | if max_x != min_x: 269 | xenc=lambda x: google_ext_encode((x-min_x)*4095/(max_x-min_x)) 270 | else: 271 | xenc=lambda x: google_ext_encode(0) 272 | if max_y != min_y: 273 | yenc=lambda y: google_ext_encode((y-min_y)*4095/(max_y-min_y)) 274 | else: 275 | yenc=lambda y: google_ext_encode(0) 276 | data='&chd=e:'+join([ join([xenc(p[x]*mlpkm) for p in seg],'')+\ 277 | ','+join([yenc(p[y]*fpm) for p in seg],'') \ 278 | for seg in trk if len(seg) > 0],',') 279 | return data 280 | 281 | def google_chart_url(trk,x,y,metric=True): 282 | if x != var_dist or y != var_ele: 283 | print 'only distance-elevation profiles are supported in --google mode' 284 | return 285 | if not trk: 286 | raise ValueError("Parsed track is empty") 287 | if metric: 288 | ele_units,dist_units='m','km' 289 | mlpkm,fpm=1.0,1.0 290 | else: 291 | ele_units,dist_units='ft','miles' 292 | mlpkm,fpm=milesperkm,feetperm 293 | urlprefix='http://chart.apis.google.com/chart?chtt=gpxplot.appspot.com&chts=cccccc,9&' 294 | url='chs=600x400&chco=9090FF&cht=lxy&chxt=x,y,x,y&chxp=2,100|3,100&'\ 295 | 'chxl=2:|distance, %s|3:|elevation, %s|'%(dist_units,ele_units) 296 | min_x=0 297 | max_x=mlpkm*(max([max([p[x] for p in seg]) for seg in trk if len(seg) > 0])) 298 | max_y=fpm*(max([max([p[y] for p in seg]) for seg in trk if len(seg) > 0])) 299 | min_y=fpm*(min([min([p[y] for p in seg]) for seg in trk if len(seg) > 0])) 300 | range='&chxr=0,0,%s|1,%s,%s'%(int(max_x),int(min_y),int(max_y)) 301 | data=google_ext_encode_data(trk,x,y,min_x,max_x,min_y,max_y,metric) 302 | url=urlprefix+url+range+data 303 | if len(url) > 2048: 304 | raise OverflowError("URL too long, reduce number of points: "+(url)) 305 | return url 306 | 307 | def print_gpx_trk(trk,file=sys.stdout,metric=True): 308 | f=file 309 | if metric: 310 | f.write('# time(ISO) elevation(m) distance(km) velocity(km/h)\n') 311 | km,m=1.0,1.0 312 | else: 313 | f.write('# time(ISO) elevation(ft) distance(miles) velocity(miles/h)\n') 314 | km,m=milesperkm,feetperm 315 | if not trk: 316 | return 317 | for seg in trk: 318 | if len(seg) == 0: 319 | continue 320 | for p in seg: 321 | f.write('%s %f %f %f\n'%\ 322 | ((p[var_time].isoformat(),\ 323 | m*p[var_ele],km*p[var_dist],km*p[var_vel]))) 324 | f.write('\n') 325 | 326 | def gen_gnuplot_script(trk,x,y,file=sys.stdout,metric=True,savefig=None): 327 | if metric: 328 | ele_units,dist_units='m','km' 329 | else: 330 | ele_units,dist_units='ft','miles' 331 | file.write("unset key\n") 332 | if x == var_time: 333 | file.write("""set xdata time 334 | set timefmt '%Y-%m-%dT%H:%M:%S' 335 | set xlabel 'time'\n""") 336 | else: 337 | file.write("set xlabel 'distance, %s'\n"%dist_units) 338 | if y == var_ele: 339 | file.write("set ylabel 'elevation, %s'\n"%ele_units) 340 | else: 341 | file.write("set ylabel 'velocity, %s/h\n"%dist_units) 342 | if savefig: 343 | import re 344 | ext=re.sub(r'.*\.','',savefig.lower()) 345 | if ext == 'png': 346 | file.write("set terminal png; set output '%s';\n"%(savefig)) 347 | elif ext in ['jpg','jpeg']: 348 | file.write("set terminal jpeg; set output '%s';\n"%(savefig)) 349 | elif ext == 'eps': 350 | file.write("set terminal post eps; set output '%s';\n"%(savefig)) 351 | elif ext == 'svg': 352 | file.write("set terminal svg; set output '%s';\n"%(savefig)) 353 | else: 354 | print 'unsupported file type: %s'%ext 355 | sys.exit(EXIT_EFORMAT) 356 | file.write("plot '-' u %d:%d w l\n"%(x-1,y-1,)) 357 | print_gpx_trk(trk,file=file,metric=metric) 358 | file.write('e') 359 | 360 | def get_gnuplot_script(trk,x,y,metric,savefig): 361 | import StringIO 362 | script=StringIO.StringIO() 363 | gen_gnuplot_script(trk,x,y,file=script,metric=metric,savefig=savefig) 364 | script=script.getvalue() 365 | return script 366 | 367 | def plot_in_gnuplot(trk,x,y,metric=True,savefig=None): 368 | script=get_gnuplot_script(trk,x,y,metric,savefig) 369 | try: 370 | import Gnuplot 371 | if not savefig: 372 | g=Gnuplot.Gnuplot(persist=True) 373 | else: 374 | g=Gnuplot.Gnuplot() 375 | g(script) 376 | except: # python-gnuplot is not available or is broken 377 | print 'gnuplot.py is not found' 378 | 379 | def print_gnuplot_script(trk,x,y,metric=True,savefig=None): 380 | script=get_gnuplot_script(trk,x,y,metric,savefig) 381 | print script 382 | 383 | def main(): 384 | metric=True 385 | xvar=var_dist 386 | action='printtable' 387 | yvar=var_ele 388 | imagefile=None 389 | tzname=None 390 | npoints=None 391 | def print_see_usage(): 392 | print 'see usage: ' + basename(sys.argv[0]) + ' --help' 393 | 394 | try: opts,args=getopt.getopt(sys.argv[1:],'hgEx:y:o:t:n:', 395 | ['help','gprint','google','table']) 396 | except Exception, e: 397 | print e 398 | print_see_usage() 399 | sys.exit(EXIT_EOPTION) 400 | for o, a in opts: 401 | if o in ['-h','--help']: 402 | print __doc__ 403 | sys.exit(0) 404 | if o == '-E': 405 | metric=False 406 | if o == '-g': 407 | action='gnuplot' 408 | if o == '--gprint': 409 | action='printgnuplot' 410 | if o == '--google': 411 | action='googlechart' 412 | if o == '--table': 413 | action='printtable' 414 | if o == '-x': 415 | if var_names.has_key(a): 416 | xvar=var_names[a] 417 | else: 418 | print 'unknown x variable' 419 | print_see_usage() 420 | sys.exit(EXIT_EOPTION) 421 | if o == '-y': 422 | if var_names.has_key(a): 423 | yvar=var_names[a] 424 | else: 425 | print 'unknown y variable' 426 | print_see_usage() 427 | sys.exit(EXIT_EOPTION) 428 | if o == '-o': 429 | imagefile=a 430 | if o == '-t': 431 | if not globals().has_key('pytz'): 432 | print 'pytz module is required to change timezone' 433 | sys.exit(EXIT_EDEPENDENCY) 434 | tzname=a 435 | if o == '-n': 436 | npoints=int(a) 437 | if len(args) > 1: 438 | print 'only one GPX file should be specified' 439 | print_see_usage() 440 | sys.exit(EXIT_EOPTION) 441 | elif len(args) == 0: 442 | print 'please provide a GPX file to process.' 443 | print_see_usage() 444 | sys.exit(EXIT_EOPTION) 445 | 446 | file=args[0] 447 | trk=read_gpx_trk(file,tzname,npoints) 448 | if action == 'gnuplot': 449 | plot_in_gnuplot(trk,x=xvar,y=yvar,metric=metric,savefig=imagefile) 450 | elif action == 'printgnuplot': 451 | print_gnuplot_script(trk,x=xvar,y=yvar,metric=metric,savefig=imagefile) 452 | elif action == 'printtable': 453 | print_gpx_trk(trk,metric=metric) 454 | elif action == 'googlechart': 455 | print google_chart_url(trk,x=xvar,y=yvar,metric=metric) 456 | 457 | if __name__ == '__main__': 458 | main() 459 | -------------------------------------------------------------------------------- /online/app.yaml: -------------------------------------------------------------------------------- 1 | application: gpxplot 2 | version: 1 3 | runtime: python 4 | api_version: 1 5 | 6 | handlers: 7 | - url: /.* 8 | script: index.py 9 | 10 | -------------------------------------------------------------------------------- /online/gpxplot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set fileencoding=utf8 ts=4 sw=4 noexpandtab: 3 | 4 | # (c) Sergey Astanin 2008 5 | 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | """usage: gpxplot.py [action] [options] track.gpx 20 | 21 | Analyze GPS track and plot elevation and velocity profiles. 22 | 23 | Features: 24 | * using haversine formula to calculate distances (spherical Earth) 25 | * support of multi-segment (discontinuous) tracks 26 | * gnuplot support: 27 | - generate plots if gnuplot.py is available 28 | - generate gnuplot script if gnuplot.py is not available 29 | - plot interactively and plot-to-file modes 30 | * Google Chart API support: 31 | - print URL or the plot 32 | * tabular track profile data can be generated 33 | * metric and English units 34 | * timezone support 35 | 36 | Actions: 37 | -g plot using gnuplot.py 38 | --gprint print gnuplot script to standard output 39 | --google print Google Chart URL 40 | --table print data table (default) 41 | 42 | Options: 43 | -h, --help print this message 44 | -E use English units (metric units used by default) 45 | -x var plot var = { time | distance } against x-axis 46 | -y var plot var = { elevation | velocity } against y-axis 47 | -o imagefile save plot to image file (supported: PNG, JPG, EPS, SVG) 48 | -t tzname use local timezone tzname (e.g. 'Europe/Moscow') 49 | -n N_points reduce number of points in the plot to approximately N_points 50 | """ 51 | 52 | import sys 53 | import datetime 54 | import getopt 55 | from string import join 56 | from math import sqrt,sin,cos,asin,pi,ceil 57 | from os.path import basename 58 | from re import sub 59 | 60 | import logging 61 | #logging.basicConfig(level=logging.DEBUG,format='%(levelname)s: %(message)s') 62 | debug=logging.debug 63 | 64 | try: 65 | import pytz 66 | except: 67 | pass 68 | 69 | GPX10='{http://www.topografix.com/GPX/1/0}' 70 | GPX11='{http://www.topografix.com/GPX/1/1}' 71 | dateformat='%Y-%m-%dT%H:%M:%SZ' 72 | 73 | R=6371.0008 # Earth volumetric radius 74 | milesperkm=0.621371192 75 | feetperm=3.2808399 76 | 77 | strptime=datetime.datetime.strptime 78 | 79 | var_time=2 80 | var_ele=3 81 | var_dist=4 82 | var_vel=5 83 | 84 | var_names={ 't': var_time, 85 | 'time': var_time, 86 | 'd': var_dist, 87 | 'dist': var_dist, 88 | 'distance': var_dist, 89 | 'ele': var_ele, 90 | 'elevation': var_ele, 91 | 'a': var_ele, 92 | 'alt': var_ele, 93 | 'altitude': var_ele, 94 | 'v': var_vel, 95 | 'vel': var_vel, 96 | 'velocity': var_vel, 97 | } 98 | 99 | EXIT_EOPTION=1 100 | EXIT_EDEPENDENCY=2 101 | EXIT_EFORMAT=3 102 | 103 | def haversin(theta): 104 | return sin(0.5*theta)**2 105 | 106 | def distance(p1,p2): 107 | lat1,lon1=[a*pi/180.0 for a in p1] 108 | lat2,lon2=[a*pi/180.0 for a in p2] 109 | deltalat=lat2-lat1 110 | deltalon=lon2-lon1 111 | h=haversin(deltalat)+cos(lat1)*cos(lat2)*haversin(deltalon) 112 | dist=2*R*asin(sqrt(h)) 113 | return dist 114 | 115 | def read_all_segments(trksegs,tzname=None,ns=GPX10,pttag='trkpt'): 116 | trk=[] 117 | for seg in trksegs: 118 | s=[] 119 | prev_ele,prev_time=0.0,None 120 | trkpts=seg.findall(ns+pttag) 121 | for pt in trkpts: 122 | lat=float(pt.attrib['lat']) 123 | lon=float(pt.attrib['lon']) 124 | time=pt.findtext(ns+'time') 125 | def prettify_time(time): 126 | time=sub(r'\.\d+Z$','Z',time) 127 | time=strptime(time,dateformat) 128 | if tzname: 129 | time=time.replace(tzinfo=pytz.utc) 130 | time=time.astimezone(pytz.timezone(tzname)) 131 | return time 132 | if time: 133 | prev_time=time 134 | time=prettify_time(time) 135 | elif prev_time: # timestamp is missing, use the prev point 136 | time=prev_time 137 | time=prettify_time(time) 138 | ele=pt.findtext(ns+'ele') 139 | if ele: 140 | ele=float(ele) 141 | prev_ele=ele 142 | else: 143 | ele=prev_ele # elevation data is missing, use the prev point 144 | s.append([lat, lon, time, ele]) 145 | trk.append(s) 146 | return trk 147 | 148 | def reduce_points(trk,npoints=None): 149 | count=sum([len(s) for s in trk]) 150 | if npoints: 151 | ptperpt=1.0*count/npoints 152 | else: 153 | ptperpt=1.0 154 | skip=int(ceil(ptperpt)) 155 | debug('ptperpt=%f skip=%d'%(ptperpt,skip)) 156 | newtrk=[] 157 | for seg in trk: 158 | if len(seg) > 0: 159 | newseg=seg[:-1:skip]+[seg[-1]] 160 | newtrk.append(newseg) 161 | debug('original: %d pts, filtered: %d pts'%\ 162 | (count,sum([len(s) for s in newtrk]))) 163 | return newtrk 164 | 165 | def eval_dist_velocity(trk): 166 | dist=0.0 167 | newtrk=[] 168 | for seg in trk: 169 | if len(seg)>0: 170 | newseg=[] 171 | prev_lat,prev_lon,prev_time,prev_ele=None,None,None,None 172 | for pt in seg: 173 | lat,lon,time,ele=pt 174 | if prev_lat and prev_lon: 175 | delta=distance([lat,lon],[prev_lat,prev_lon]) 176 | if time and prev_time: 177 | try: 178 | vel=3600*delta/((time-prev_time).seconds) 179 | except ZeroDivisionError: 180 | vel=0.0 # probably the point lacked the timestamp 181 | else: 182 | vel=0.0 183 | else: # new segment 184 | delta=0.0 185 | vel=0.0 186 | dist=dist+delta 187 | newseg.append([lat,lon,time,ele,dist,vel]) 188 | prev_lat,prev_lon,prev_time=lat,lon,time 189 | newtrk.append(newseg) 190 | return newtrk 191 | 192 | def parse_gpx_data(gpxdata,tzname=None,npoints=None): 193 | try: 194 | import xml.etree.ElementTree as ET 195 | except: 196 | try: 197 | import elementtree.ElementTree as ET 198 | except: 199 | try: 200 | import cElementTree as ET 201 | except: 202 | try: 203 | import lxml.etree as ET 204 | except: 205 | print 'this script needs ElementTree (Python>=2.5)' 206 | sys.exit(EXIT_EDEPENDENCY) 207 | 208 | def find_trksegs_or_route(etree, ns): 209 | trksegs=etree.findall('.//'+ns+'trkseg') 210 | if trksegs: 211 | return trksegs, "trkpt" 212 | else: # try to display route if track is missing 213 | rte=etree.findall('.//'+ns+'rte') 214 | return rte, "rtept" 215 | 216 | # try GPX10 namespace first 217 | etree=ET.XML(gpxdata) 218 | trksegs,pttag=find_trksegs_or_route(etree, GPX10) 219 | NS=GPX10 220 | if not trksegs: # try GPX11 namespace otherwise 221 | trksegs,pttag=find_trksegs_or_route(etree, GPX11) 222 | NS=GPX11 223 | if not trksegs: # try without any namespace 224 | trksegs,pttag=find_trksegs_or_route(etree, "") 225 | NS="" 226 | trk=read_all_segments(trksegs,tzname=tzname,ns=NS,pttag=pttag) 227 | trk=reduce_points(trk,npoints=npoints) 228 | trk=eval_dist_velocity(trk) 229 | return trk 230 | 231 | def read_gpx_trk(filename,tzname,npoints): 232 | if filename == "-": 233 | gpx=sys.stdin.read() 234 | debug("length(gpx) from stdin = %d" % len(gpx)) 235 | else: 236 | gpx=open(filename).read() 237 | debug("length(gpx) from file = %d" % len(gpx)) 238 | return parse_gpx_data(gpx,tzname,npoints) 239 | 240 | def google_ext_encode(i): 241 | """Google Charts' extended encoding, 242 | see http://code.google.com/apis/chart/mappings.html#extended_values""" 243 | enc='ABCDEFGHIJKLMNOPQRSTUVWXYZ' 244 | enc=enc+enc.lower()+'0123456789-.' 245 | i=int(i)%4096 # modulo 4096 246 | figure=enc[int(i/len(enc))]+enc[int(i%len(enc))] 247 | return figure 248 | 249 | def google_text_encode_data(trk,x,y,min_x,max_x,min_y,max_y,metric=True): 250 | if metric: 251 | mlpkm,fpm=1.0,1.0 252 | else: 253 | mlpkm,fpm=milesperkm,feetperm 254 | xenc=lambda x: "%.1f"%x 255 | yenc=lambda y: "%.1f"%y 256 | data='&chd=t:'+join([ join([xenc(p[x]*mlpkm) for p in seg],',')+\ 257 | '|'+join([yenc(p[y]*fpm) for p in seg],',') \ 258 | for seg in trk if len(seg) > 0],'|') 259 | data=data+'&chds='+join([join([xenc(min_x),xenc(max_x),yenc(min_y),yenc(max_y)],',') \ 260 | for seg in trk if len(seg) > 0],',') 261 | return data 262 | 263 | def google_ext_encode_data(trk,x,y,min_x,max_x,min_y,max_y,metric=True): 264 | if metric: 265 | mlpkm,fpm=1.0,1.0 266 | else: 267 | mlpkm,fpm=milesperkm,feetperm 268 | if max_x != min_x: 269 | xenc=lambda x: google_ext_encode((x-min_x)*4095/(max_x-min_x)) 270 | else: 271 | xenc=lambda x: google_ext_encode(0) 272 | if max_y != min_y: 273 | yenc=lambda y: google_ext_encode((y-min_y)*4095/(max_y-min_y)) 274 | else: 275 | yenc=lambda y: google_ext_encode(0) 276 | data='&chd=e:'+join([ join([xenc(p[x]*mlpkm) for p in seg],'')+\ 277 | ','+join([yenc(p[y]*fpm) for p in seg],'') \ 278 | for seg in trk if len(seg) > 0],',') 279 | return data 280 | 281 | def google_chart_url(trk,x,y,metric=True): 282 | if x != var_dist or y != var_ele: 283 | print 'only distance-elevation profiles are supported in --google mode' 284 | return 285 | if not trk: 286 | raise ValueError("Parsed track is empty") 287 | if metric: 288 | ele_units,dist_units='m','km' 289 | mlpkm,fpm=1.0,1.0 290 | else: 291 | ele_units,dist_units='ft','miles' 292 | mlpkm,fpm=milesperkm,feetperm 293 | urlprefix='http://chart.apis.google.com/chart?chtt=gpxplot.appspot.com&chts=cccccc,9&' 294 | url='chs=600x400&chco=9090FF&cht=lxy&chxt=x,y,x,y&chxp=2,100|3,100&'\ 295 | 'chxl=2:|distance, %s|3:|elevation, %s|'%(dist_units,ele_units) 296 | min_x=0 297 | max_x=mlpkm*(max([max([p[x] for p in seg]) for seg in trk if len(seg) > 0])) 298 | max_y=fpm*(max([max([p[y] for p in seg]) for seg in trk if len(seg) > 0])) 299 | min_y=fpm*(min([min([p[y] for p in seg]) for seg in trk if len(seg) > 0])) 300 | range='&chxr=0,0,%s|1,%s,%s'%(int(max_x),int(min_y),int(max_y)) 301 | data=google_ext_encode_data(trk,x,y,min_x,max_x,min_y,max_y,metric) 302 | url=urlprefix+url+range+data 303 | if len(url) > 2048: 304 | raise OverflowError("URL too long, reduce number of points: "+(url)) 305 | return url 306 | 307 | def print_gpx_trk(trk,file=sys.stdout,metric=True): 308 | f=file 309 | if metric: 310 | f.write('# time(ISO) elevation(m) distance(km) velocity(km/h)\n') 311 | km,m=1.0,1.0 312 | else: 313 | f.write('# time(ISO) elevation(ft) distance(miles) velocity(miles/h)\n') 314 | km,m=milesperkm,feetperm 315 | if not trk: 316 | return 317 | for seg in trk: 318 | if len(seg) == 0: 319 | continue 320 | for p in seg: 321 | f.write('%s %f %f %f\n'%\ 322 | ((p[var_time].isoformat(),\ 323 | m*p[var_ele],km*p[var_dist],km*p[var_vel]))) 324 | f.write('\n') 325 | 326 | def gen_gnuplot_script(trk,x,y,file=sys.stdout,metric=True,savefig=None): 327 | if metric: 328 | ele_units,dist_units='m','km' 329 | else: 330 | ele_units,dist_units='ft','miles' 331 | file.write("unset key\n") 332 | if x == var_time: 333 | file.write("""set xdata time 334 | set timefmt '%Y-%m-%dT%H:%M:%S' 335 | set xlabel 'time'\n""") 336 | else: 337 | file.write("set xlabel 'distance, %s'\n"%dist_units) 338 | if y == var_ele: 339 | file.write("set ylabel 'elevation, %s'\n"%ele_units) 340 | else: 341 | file.write("set ylabel 'velocity, %s/h\n"%dist_units) 342 | if savefig: 343 | import re 344 | ext=re.sub(r'.*\.','',savefig.lower()) 345 | if ext == 'png': 346 | file.write("set terminal png; set output '%s';\n"%(savefig)) 347 | elif ext in ['jpg','jpeg']: 348 | file.write("set terminal jpeg; set output '%s';\n"%(savefig)) 349 | elif ext == 'eps': 350 | file.write("set terminal post eps; set output '%s';\n"%(savefig)) 351 | elif ext == 'svg': 352 | file.write("set terminal svg; set output '%s';\n"%(savefig)) 353 | else: 354 | print 'unsupported file type: %s'%ext 355 | sys.exit(EXIT_EFORMAT) 356 | file.write("plot '-' u %d:%d w l\n"%(x-1,y-1,)) 357 | print_gpx_trk(trk,file=file,metric=metric) 358 | file.write('e') 359 | 360 | def get_gnuplot_script(trk,x,y,metric,savefig): 361 | import StringIO 362 | script=StringIO.StringIO() 363 | gen_gnuplot_script(trk,x,y,file=script,metric=metric,savefig=savefig) 364 | script=script.getvalue() 365 | return script 366 | 367 | def plot_in_gnuplot(trk,x,y,metric=True,savefig=None): 368 | script=get_gnuplot_script(trk,x,y,metric,savefig) 369 | try: 370 | import Gnuplot 371 | if not savefig: 372 | g=Gnuplot.Gnuplot(persist=True) 373 | else: 374 | g=Gnuplot.Gnuplot() 375 | g(script) 376 | except: # python-gnuplot is not available or is broken 377 | print 'gnuplot.py is not found' 378 | 379 | def print_gnuplot_script(trk,x,y,metric=True,savefig=None): 380 | script=get_gnuplot_script(trk,x,y,metric,savefig) 381 | print script 382 | 383 | def main(): 384 | metric=True 385 | xvar=var_dist 386 | action='printtable' 387 | yvar=var_ele 388 | imagefile=None 389 | tzname=None 390 | npoints=None 391 | def print_see_usage(): 392 | print 'see usage: ' + basename(sys.argv[0]) + ' --help' 393 | 394 | try: opts,args=getopt.getopt(sys.argv[1:],'hgEx:y:o:t:n:', 395 | ['help','gprint','google','table']) 396 | except Exception, e: 397 | print e 398 | print_see_usage() 399 | sys.exit(EXIT_EOPTION) 400 | for o, a in opts: 401 | if o in ['-h','--help']: 402 | print __doc__ 403 | sys.exit(0) 404 | if o == '-E': 405 | metric=False 406 | if o == '-g': 407 | action='gnuplot' 408 | if o == '--gprint': 409 | action='printgnuplot' 410 | if o == '--google': 411 | action='googlechart' 412 | if o == '--table': 413 | action='printtable' 414 | if o == '-x': 415 | if var_names.has_key(a): 416 | xvar=var_names[a] 417 | else: 418 | print 'unknown x variable' 419 | print_see_usage() 420 | sys.exit(EXIT_EOPTION) 421 | if o == '-y': 422 | if var_names.has_key(a): 423 | yvar=var_names[a] 424 | else: 425 | print 'unknown y variable' 426 | print_see_usage() 427 | sys.exit(EXIT_EOPTION) 428 | if o == '-o': 429 | imagefile=a 430 | if o == '-t': 431 | if not globals().has_key('pytz'): 432 | print 'pytz module is required to change timezone' 433 | sys.exit(EXIT_EDEPENDENCY) 434 | tzname=a 435 | if o == '-n': 436 | npoints=int(a) 437 | if len(args) > 1: 438 | print 'only one GPX file should be specified' 439 | print_see_usage() 440 | sys.exit(EXIT_EOPTION) 441 | elif len(args) == 0: 442 | print 'please provide a GPX file to process.' 443 | print_see_usage() 444 | sys.exit(EXIT_EOPTION) 445 | 446 | file=args[0] 447 | trk=read_gpx_trk(file,tzname,npoints) 448 | if action == 'gnuplot': 449 | plot_in_gnuplot(trk,x=xvar,y=yvar,metric=metric,savefig=imagefile) 450 | elif action == 'printgnuplot': 451 | print_gnuplot_script(trk,x=xvar,y=yvar,metric=metric,savefig=imagefile) 452 | elif action == 'printtable': 453 | print_gpx_trk(trk,metric=metric) 454 | elif action == 'googlechart': 455 | print google_chart_url(trk,x=xvar,y=yvar,metric=metric) 456 | 457 | if __name__ == '__main__': 458 | main() 459 | -------------------------------------------------------------------------------- /online/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ title }} 4 | 6 | 18 | 23 |
24 | 25 | {% if form %} 26 |

Upload your track

27 | 28 |
29 |

30 | 31 |

32 |

33 | 34 |

35 |

36 |

37 |
38 | {% endif %} 39 | 40 | {% if error %} 41 |

{{error}}

42 | 43 | {% if bugreportme %} 44 |

If you see this message, something is wrong with this page. Please report this issue here.

45 | 46 |

Attaching a GPX file to reproduce the problem is very helpful. Send it by e-mail s punto as tan in plus gpx plot at gmail punto com, if it is confidential. Thank you!

56 | {% endif %} 57 | 58 | {% endif %} 59 | 60 | {% if imgsrc %} 61 |

GPS elavation–distance profile

62 |

63 | 64 | 67 |

68 | {% endif %} 69 | 70 |
71 | 75 | 79 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /online/index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # vim: set fileencoding=utf-8 noexpandtab ts=4 sw=4 : 3 | 4 | from google.appengine.ext import webapp 5 | from google.appengine.ext.webapp.util import run_wsgi_app 6 | from google.appengine.ext.webapp import template 7 | from google.appengine.runtime.apiproxy_errors import OverQuotaError 8 | 9 | from django.utils import simplejson as json 10 | 11 | import urllib2 12 | import logging 13 | 14 | from gpxplot import parse_gpx_data,google_chart_url,var_dist,var_ele 15 | 16 | max_gpx_size = 1048576 17 | 18 | class GPXSizeError (Exception): 19 | pass 20 | 21 | class NoAltitudeData (Exception): 22 | pass 23 | 24 | def plot_on_request(request): 25 | "Process POST request with GPX data. Return a URL of the plot." 26 | imperial=request.get('imperial') 27 | if imperial == 'on': 28 | metric=False 29 | else: 30 | metric=True 31 | logging.debug('metric='+str(metric)) 32 | try: 33 | url=request.get("gpxurl") 34 | if url: # fetch GPX data 35 | logging.debug('fetching GPX from '+url) 36 | reader=urllib2.urlopen(url) 37 | gpxsize = int(reader.headers["Content-Length"]) 38 | logging.debug('gpxsize=%d' % gpxsize) 39 | if gpxsize > max_gpx_size: 40 | raise GPXSizeError("File is too large") 41 | gpxdata=reader.read() 42 | else: 43 | logging.debug('using submitted GPX data') 44 | gpxdata=request.get("gpxfile") 45 | gpxsize=len(gpxdata) 46 | logging.debug('gpxsize=%d' % gpxsize) 47 | if gpxsize > max_gpx_size: 48 | raise GPXSizeError("File is too large") 49 | logging.debug('gpxdata='+gpxdata[:320]) 50 | except Exception, e: 51 | logging.debug(unicode(e)) 52 | raise e 53 | if len(gpxdata) == 0: 54 | raise Exception("There is no GPX data to plot!") 55 | # reduce number of points gradually, to fit URL length 56 | npoints=700 57 | url=None 58 | while not url: 59 | try: 60 | trk=parse_gpx_data(gpxdata,npoints=npoints) 61 | y=var_ele 62 | max_ele=max([max([p[y] for p in s]) for s in trk if len(s) > 0]) 63 | min_ele=min([min([p[y] for p in s]) for s in trk if len(s) > 0]) 64 | if abs(max_ele) < 1e-3 and abs(min_ele) < 1e-3: 65 | msg = 'File does not contain altitude data ' \ 66 | + 'or it is flat sea level. Nothing to plot.' 67 | raise NoAltitudeData(msg) 68 | url=google_chart_url(trk,var_dist,var_ele,metric=metric) 69 | except OverflowError, e: 70 | npoints -= 100 71 | if npoints <= 0: 72 | raise e 73 | return url 74 | 75 | class MainPage(webapp.RequestHandler): 76 | def get(self): 77 | content={'title':'Visualize GPX profile online', 78 | 'form':True,'bg':'#efefff'} 79 | self.response.out.write(template.render('index.html',content)) 80 | 81 | def post(self): 82 | content={'title':'Elevation–distance profile','form':False, 'bg':'#fff'} 83 | try: 84 | url=plot_on_request(self.request) 85 | content['imgsrc']=url 86 | except GPXSizeError, e: 87 | msg = 'File is too large to be processed on gpxplot.appspot.com. ' + \ 88 | '
Reduce file size with gpsbabel or plot it offline with a ' + \ 89 | 'stand-alone version of gpxplot.' 90 | content['error'] = msg 91 | logging.error(e) 92 | except NoAltitudeData, e: 93 | content['error'] = e.message 94 | logging.error(e) 95 | except OverQuotaError, e: 96 | msg = 'Application exceeded free quota. Try again later.' 97 | content['error'] = msg 98 | logging.error(e) 99 | except Exception, e: 100 | msg = 'Your GPX track cannot be processed. Sorry.' 101 | msg += '
'+unicode(e) 102 | content['error'] = msg 103 | content['bugreportme'] = True 104 | logging.error(e) 105 | self.response.out.write(template.render('index.html',content)) 106 | 107 | class ApiHandler(webapp.RequestHandler): 108 | "Return a URL of the image in a file." 109 | def get(self): 110 | return self.post() 111 | def post(self): 112 | try: 113 | url=plot_on_request(self.request) 114 | format=self.request.get("output","json") 115 | if format == "json": 116 | self.response.headers['Content-Type']='application/json' 117 | self.response.out.write(json.dumps({'url':url})) 118 | elif format == "png": 119 | self.redirect(url) 120 | else: 121 | raise Exception("Output format not supported.") 122 | except Exception, e: 123 | self.response.set_status(400,message='Exception: '+unicode(e)) 124 | 125 | application = webapp.WSGIApplication( 126 | [('/', MainPage), 127 | (r'/api/0.1/plot',ApiHandler), 128 | (r'/api/0.1.1/plot',ApiHandler), 129 | (r'/api/0.1.2/plot',ApiHandler), 130 | ], debug=True) 131 | 132 | def main(): 133 | run_wsgi_app(application) 134 | 135 | if __name__ == "__main__": 136 | main() 137 | -------------------------------------------------------------------------------- /online/index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | # AUTOGENERATED 4 | 5 | # This index.yaml is automatically updated whenever the dev_appserver 6 | # detects that a new type of query is run. If you want to manage the 7 | # index.yaml file manually, remove the above marker line (the line 8 | # saying "# AUTOGENERATED"). If you want to manage some indexes 9 | # manually, move them above the marker line. The index.yaml file is 10 | # automatically uploaded to the admin console when you next deploy 11 | # your application using appcfg.py. 12 | --------------------------------------------------------------------------------