Sunday, March 24, 2013

Weekend Projectje

Omdat ik behoorlijk wat data verzamel als waypoints in GPX files, duurt het steeds een hele poos vooraleer ik alle data heb verwerkt. Dit weekend heb ik wat tijd besteed aan het schrijven van een Python script om de GPX data om te zetten in OSM-formaat. Het onderstaande script is het resultaat. Het kan nog lang niet alles omzetten, maar is toch al een start.

Deze week ga ik het wat uittesten in de praktijk.

#!/usr/bin/python
# -*- coding: utf-8 -*-
import xml.sax
import sys
import getopt
import io
import re
import time;

nodeId = -1

'''
    This Python 2.7 compliant script will turn WayPoints coming from a Garmin device
    into an OSM-compliant file that can be imported with JOSM.
    
    It relies on the syntax used in the name of the waypoints
    The following stuff is recognized:
    
    - VB  for amenity: waste_basket  (Vuilbak)
    - BK  for amenity: bench (Bank or Zitbank)
    - FIREH for emergency: fire_hydrant
    - M50/M70/Z70/Z30/Z50 for maxspeeds (Z for zones)
    - PIKNIK for tourism: picnic_site
    - WSS [name] for historic: wayside_shrine
    - CDC for power: cable_distribution_cabinet
    - ESS for power: sub_station
    - STOP/GW  for highway: stop / give_way
    - LLI/RLI for highway: street_lamp
    - PAAL, KGATE, SWINGGATE, SWGATE, GATE, FBAR for
          barrier: bollard / kissing_gate, swing_gate, swing_gate, gate, cycle_barrier
    - house number notations, e.g. R10, L10-12, L10+-+20, L10+12---13+15
    
    Execution:
        parseWayPoints.py -f 
    waypoint file has to end on '.gpx'
    The output is a file name the same as the waypoint file, but ending on '.osm'
'''
class Node():
    
    def __init__(self, lon, lat):
        global nodeId
        self.lon = lon
        self.lat = lat
        self.id = nodeId
        nodeId = nodeId - 1

    def moveABit(self):
        return Node(self.lon + 0.00001, self.lat + 0.00001)

    def moveABitMore(self, move):
        return Node(self.lon + move, self.lat + move)

class NodePlacer():
    def __init__(self, file):
        self.file = file
        self.updatedText = ''
        self.nodeCount = 0
        self.node = None

    def placeNode(self, node, tags):
        self.file.write('')
        for kv in tags:
            self.file.write('')
        self.file.write('\n')
        self.node = node

    def placeMovedNode(self, node, tags):
        node = node.moveABit()
        self.placeNode(node, tags)

class AmenityNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)

    def match(self, node, text):
        if text.find("BK VB") > -1 or text.find("VB BK") > -1 :
            self.placeNode(node, [('amenity', 'waste_basket')] )
            self.placeMovedNode(node, [('amenity', 'bench')] )
            self.updatedText = text.replace("BK VB", "").replace('VB BK', '')
            self.nodeCount = 2
            return True
        if text.find("VB") > -1:
            self.placeNode(node, [('amenity', 'waste_basket')] )
            self.updatedText = text.replace('VB', '')
            self.nodeCount = 1
            return True
        if text.find("BK") > -1:
            self.placeNode(node, [('amenity', 'bench')] )
            self.updatedText = text.replace('BK', '')
            self.nodeCount = 1
            return True
        if text.find("FIREH") > -1:
            self.placeNode(node, [('emergency', 'fire_hydrant')] )
            self.updatedText = text.replace('FIREH', '')
            self.nodeCount = 1
            return True
        return False
        
class PicnicNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)

    def match(self, node, text):
        if text.find("PIKNIK") > -1:
            self.placeNode(node, [('tourism', 'picnic_site')] )
            self.updatedText.replace('PIKNIK', '')
            self.nodeCount = 1
            return True
            
class MaxSpeedNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)

    def match(self, node, text):
        if text.find("M50") > -1:
            self.placeNode(node, [('maxspeed', '50'), ('zone:traffic', 'BE:rural')])
            self.nodeCount = 1
            self.updatedText = text.replace('M50', '')
        if text.find('M70') > -1:
            self.placeNode(node, [('maxspeed', '70'), ('zone:traffic', 'BE:rural')])
            self.nodeCount = 1
            self.updatedText = text.replace('M70', '')
        if text.find('Z70') > -1:
            self.placeNode(node, [('maxspeed', '70'), ('zone:traffic', 'BE:rural'), ('source:maxspeed', 'zone70')])
            self.nodeCount = 1
            self.updatedText = text.replace('Z70', '')
        if text.find('Z50') > -1:
            self.placeNode(node, [('maxspeed', '50'), ('zone:traffic', 'BE:rural'), ("source:maxspeed", "zone50")])
            self.nodeCount = 1
            self.updatedText = text.replace('Z50', '')
        if text.find('Z30') > -1:
            self.placeNode(node, [('maxspeed', '30'), ('zone:traffic', 'BE:urban'), ("source:maxspeed", "zone30")])
            self.nodeCount = 1
            self.updatedText = text.replace('Z30', '')
      

class WaySideShrineNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)
        self.wssPattern = re.compile(r'\A[LR]+[ ]WSS([ A-Za-z0-9]*)')
        
    def match(self, node, text):
        wssMatch = self.wssPattern.match(text)
        if wssMatch:
            if (wssMatch.group(1) == ""):
                self.placeNode(node, [('historic', 'wayside_shrine'), ('religion', 'christian'), ('denomination', 'roman_catholic')]) 
            else:
                self.placeNode(node, [('historic', 'wayside_shrine'), ('religion', 'christian'), ('denomination', 'roman_catholic'), ('name', wssMatch.group(1).strip())]) 
            self.updatedText = ''
            self.nodeCount = 1
            return True
        return False

class BarrierNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)

    def match(self, node, text):
        if text.find("PAAL") > -1:
            self.placeNode(node, [('barrier', 'bollard'), ('foot','yes'), ('bicycle', 'yes')] )
            self.updatedText = text.replace('PAAL', '')
            self.nodeCount = 1
            return True
        if text.find("SWINGGATE") > -1 or text.find("SWGATE") > -1:
            self.placeNode(node, [('barrier', 'swing_gate'), ('foot','yes'), ('bicycle', 'yes')] )
            self.updatedText = text.replace('SWINGGATE', '').replace('SWGATE', '')
            self.nodeCount = 1
            return True
        if text.find("KGATE") > -1 or text.find("KISSGATE") > -1:
            self.placeNode(node, [('barrier', 'kiss_gate'), ('foot','yes'), ('bicycle', 'no')] )
            self.updatedText = ''
            self.nodeCount = 1
            return True
        if text.find("GATE") > -1:
            self.placeNode(node, [('barrier', 'gate'), ('foot','yes'), ('bicycle', 'yes')] )
            self.updatedText = text.replace('GATE', '')
            self.nodeCount = 1
            return True
        if text.find("FBAR") > -1 :
            self.placeNode(node, [('barrier', 'cycle_barrier'), ('foot','yes'), ('bicycle', 'yes')] )
            self.updatedText = text.replace('FBAR', '')
            self.nodeCount = 1
            return True

class PowerNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)

    def match(self, node, text):
        if text == "L ESS" or text == "R ESS" or text == "ESS":
            self.placeNode(node, [('power', 'sub_station')] )
            self.updatedText = ''
            self.nodeCount = 1
            return True
        if text == "L CDC" or text == "R CDC" or text == "CDC":
            self.placeNode(node, [('power', 'cable_distribution_cabinet')] )
            self.updatedText = ''
            self.nodeCount = 1
            return True
            
class HighwayNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)

    def match(self, node, text):
        if text == "RLI"  or text == "LLI" :
            self.placeNode(node, [('highway', 'street_lamp')] )
            self.updatedText = ''
            self.nodeCount = 1
            return True
        if text == "STOP" :
            self.placeNode(node, [('highway', 'stop')] )
            self.updatedText = ''
            self.nodeCount = 1
            return True
        if text.find("GW") == 0:
            self.placeNode(node, [('highway', 'give_way')] )
            self.updatedText = ''
            self.nodeCount = 1
            return True

class HouseNumberNode(NodePlacer):
    def __init__(self, file):
        NodePlacer.__init__(self, file)
        self.numberPattern = re.compile('[0-9]')
        self.houseNumberPattern = re.compile(r'\A[+-]*([0-9]+[a-zA-Z]*)')
        self.terracePattern = re.compile(r'[+-]*([0-9]+[a-zA-Z]*)\+\-\+([0-9]+[a-zA-Z]*)')
        
    def match(self, node, text):
        if not ( text.startswith("L") or text.startswith("R")):
            return False
        numberpart = text.strip('LR ')
        terraceMatch = self.terracePattern.match(numberpart)
        if terraceMatch:
            group1 = int(terraceMatch.group(1))
            group2 = int(terraceMatch.group(2))
            startNr = min(group1, group2)
            endNr = max(group1, group2)
            terraceNrs = range(startNr, endNr + 1, 2)
            self.nodeCount = 0
            for nr in terraceNrs:
                self.placeNode(node, [('building', 'house'), ('addr:housenumber', str(nr))])
                node = node.moveABitMore(0.00005)
                self.nodeCount = self.nodeCount + 1
            self.updatedText =  'L' + self.terracePattern.sub('', numberpart, 1)
            return True

        houseNumberMatch = self.houseNumberPattern.match(numberpart)
        if houseNumberMatch:
            self.placeNode(node, [('building', 'house'), ('addr:housenumber', houseNumberMatch.group(1).strip())])
            self.updatedText = 'L' + self.houseNumberPattern.sub('', numberpart, 1)
            self.nodeCount = 1
            return True
        
class WaypointContentHandler(xml.sax.ContentHandler):
    def __init__(self, filename):
        xml.sax.ContentHandler.__init__(self)
        self.nodeCount = 0
        self.text = ""
        self.f = open(filename, 'w')
        self.f.write('\n')
        self.f.write('\n')
        self.f.write('\n')

        self._matchers = [ AmenityNode(self.f), PicnicNode(self.f), HighwayNode(self.f),
            BarrierNode(self.f), PowerNode(self.f), HouseNumberNode(self.f), WaySideShrineNode(self.f),
            MaxSpeedNode(self.f) ]

    def startElement(self, tagname, attrs):
         if tagname == "wpt":
            self.node = Node(float(attrs.getValue("lon")), float(attrs.getValue("lat")))
         if tagname == "name":
            self.text = ''

    def endElement(self, tagname):
        if tagname == "name":
            self.tagNode(self.node, self.text.strip())

    def endDocument(self):
        self.f.write('\n')
        self.f.close()
         
    def characters(self, content):
        self.text = self.text + content

    def tagNode(self, node, text):
        for matcher in self._matchers:
            theText = text
            matcher.node = None
            while matcher.match(node, theText):
                theText = matcher.updatedText.strip()
                self.nodeCount = self.nodeCount + matcher.nodeCount
                if matcher.node is None:
                    node = node.moveABitMore(0.00005)
                else:
                    node = matcher.node.moveABitMore(0.00005)
         
class FileNameGenerator():
    def generateFileName(self, waypointFile):
        outputfile = waypointFile.replace('gpx', 'osm')
        return outputfile

def main(argv):
    file = ''
    try:
        opts, args = getopt.getopt(argv,"hf:",["file="])
    except getopt.GetoptError:
        print 'parseWayPoints.py -f '
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print 'Usage: parseWayPoints.py -f '
            sys.exit(0)
        elif opt in ("-f", "--file"):
            file = arg
    if file == '':
        print 'No file specified'
        sys.exit(2)
    print 'Parsing WayPoint file: ', file

    outputfile = FileNameGenerator().generateFileName(file)
    print outputfile
    waypointHandler = WaypointContentHandler(outputfile)    
    fileHandle = io.open(file, "r")
    xml.sax.parse(fileHandle, waypointHandler)
         
    print 'Wrote ' + outputfile + ' containing ' + str(waypointHandler.nodeCount) + ' nodes.'

if __name__ == "__main__":
    main(sys.argv[1:])

2 comments:

  1. Nice script - thanks for pointing me to it.
    It would be IMHO more handy when you would set the shortcuts and the values to replace them with at the beginning of the file so other users can find change them more easily.
    Though I am not much of a coder I'd even think a coding like
    if
    there is set a variable of "VB" set to be replaced with "amenity;waste_basked"; do so, else
    fi
    so you wouldn't have that much redundant lines for hardcoded values in that script.
    But who am I proposing such things and myself not being able to code it. :)

    ReplyDelete
    Replies
    1. Yes, you are correct about that. It would make the script more customizable.
      Maybe, one day, when time permits ....

      Delete