This is an expansion of the excellent work done by Mr. Davis at his Open Source GPS HOW TO page. He has a google maps hack here that allows one to make a GPX xml file by clicking on intersections. This is a very quick way to make a route map.
I wanted to expand on his work in two ways:
- I wanted to fill in waypoint names for items that were left blank, and
- I wanted to automatically check for waypoint name duplication, because duplicates cause data to be overwritten in Etrex GPS devices.
I wrote a python script that does both things. All one has to do now is:
- Create a map on his website
- Name major waypoints on the map (I like to name intersections using the name of the street I’ll be turning on to)
- Save the GPX file to disk
- Run gpxrecode.py [inputfile] [outputfile]
gpxrecode removes the leading number and hyphen from the waypoint name. If the waypoint is blank it gives it the same name as the last one. If the waypoint is already in a cache of waypoints, it adds a number to the end of the name. The cache is maintained between runs of the program, so multiple routes shouldn’t clobber each-other’s names.
Source after the break.
#!/usr/bin/python
#take a gpx xml file from http://www.marengo-ltd.com/map/ and recode it to fill out blanks
#and remove numbers from beginning. Eventually use an sqlite db or something to check for dupes over all routes
import string
import pickle
import os,os.path,sys
import math
import xml.sax
from xml.sax.saxutils import XMLFilterBase, XMLGenerator
waypoints = []
NAME=0
LAT=1
LON=2
class gpxfilter(XMLFilterBase):
def __init__(self, In, Out):
XMLFilterBase.__init__(self, In)
self.element_stack = []
self.Out = Out
self.last_waypoint = None
def startElement(self, name, attrs):
self.Out.characters("\\n"+" "*len(self.element_stack))
self.Out.startElement(name, attrs)
if not attrs.has_key('lat'):
self.element_stack.append([name,None, None])
else:
self.element_stack.append([name,attrs['lat'],attrs['lon']])
def characters(self, content):
if len(content)==0:
return
white=True
for c in content:
if not isWhitespace(c):
white=False
break
if white:
return
if self.element_stack[-1][NAME] == "name":
name = content
if name.isdigit():
name = self.last_waypoint
split = name.split('-')
if len(split) == 2 and split[0].isdigit() and len(split[1])>0: #get rid of 03-BLAH
name = split[1]
if len(name)>6:
name = name[0:6]
self.last_waypoint = name
basename = name
match_index = -1
#first try original name
#if name is unique, add it to cache and break
#else if coords are close, break (use dupe name)
#else loop (try new name)
found = False
for i in range(0,9999):
if i>0:
name = basename[0:6-len(str(i))]+str(i)
try:
match_index = [n[NAME] for n in waypoints].index(name)
except: #didn't find a match
if self.element_stack[-2][LAT] is not None:
waypoints.append([name,self.element_stack[-2][LAT],self.element_stack[-2][LON]])
found = True
break
diff_lat = abs(float(self.element_stack[-2][LAT]) - float(waypoints[match_index][LAT]))
diff_lon = abs(float(self.element_stack[-2][LON]) - float(waypoints[match_index][LON]))
if diff_lat < = .0003 and diff_lon <= .0003:
found = True
break
if not found:
print "ran out of tries??? (should never happen)"
self.Out.characters(name)
def endElement(self, name):
if name != self.element_stack[-1][NAME]:
print "ERROR expected "+str(self.element_stack[-1])[0]+" got "+str(name)
self.element_stack.pop(-1)
self.Out.endElement(name)
self.Out.characters("\\n"+" "*(len(self.element_stack)-1))
def isWhitespace(s):
"""Is 's' a single character and whitespace?"""
if s in string.whitespace:
return 1
return 0
if __name__ == "__main__":
if len(sys.argv) < 3:
print "need input file and output file"
sys.exit(1)
try:
#try to load waypoint cache
f = open(os.getenv('HOME')+"/.gpswaypoints","r")
contrib,waypoints = pickle.load(f)
except:
contrib = []
waypoints = []
try:
out = open(sys.argv[2],"w")
except:
print "couldn't open output file"
In = xml.sax.make_parser()
Out = XMLGenerator(out)
filter_handler = gpxfilter(In, Out)
filter_handler.parse(sys.argv[1])
f = open(os.getenv('HOME')+"/.gpswaypoints","w")
#save the cache
if os.path.split(sys.argv[1])[1] not in contrib:
contrib = contrib+[os.path.split(sys.argv[1])[1]]
pickle.dump([contrib,waypoints],f)
Hi Owen, thanks for linking to my HOWTO, I don't mind you linking directly to the main site (at http://www.marengo-ltd.com/map), the discussion site is more for feedback if anyone's got anything.
P.S, my name's Martyn Davis, the h.h.monro thing was an attempt to obfuscate the email address - obviously worked too well !!!