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 !!!