Wednesday, March 27, 2013

Back To The Future

Vandaag ontdekt: een website die de nadruk legt op historische gebouwen zoals kastelen en kapellen. Verder ook aandacht voor beschermde monumenten, grenspalen e.d.

Hieronder een werkende mini-versie, maar je kan beter naar geschichtskarten surfen
 

Monday, March 25, 2013

Over De Lijn

Toen ik me pas aanmeldde op de mailing list voor de Belgische mappers, werd ik direct onder de arm genomen door Polyglot. Dat is de alias waaronder Jo bijdragen levert aan OpenStreetMap. Ik beschouw hem dan ook een beetje als mijn "mentor" :-)

Jo is al jaren bezig om de buslijnen in Vlaanderen op de kaart te zetten. Hij heeft tevens contact gezocht met De Lijn om de gegevens te mogen overzetten van hun databank naar die van OpenStreetMap. Heel lang stootte hij op een njet en ondertussen schonk De Lijn de gegevens dan ook nog eens aan Google. Dus al die bus symbooltjes op Google maps komen van De Lijn.


Er zat dan niets anders op dan alle 40.000 haltes te bezoeken en de gegevens. Jo deed en oproep via de mailing list. Ik vermoed dat hij toch enkele vrijwilligers vond om de haltes te catalogiseren.

We hadden er al zo'n 9000, al dan niet volledig, in kaart gebracht, toen Jo het bericht kreeg dat hij toch over de gegevens mocht beschikken. Hij is dan begonnen met te kijken in hoeverre de gegevens die al in OpenStreetMap zitten afwijken van de gegevens van De Lijn.

Af en toe bleken er toch wel grote verschillen te zijn. Had de mapper de verkeerde positie ingegeven ? Of was de halte daadwerkelijk verplaatst ? De gehele lijst werd door Jo nagekeken en daarna doorgegeven aan De Lijn. Zij kunnen dan zien of ze de verbeteringen willen terugkoppelen. Op deze manier zouden ze er wel zelf nog iets aan hebben ook.

Welke gegevens brengen we nu juist in kaart:

  • de plaats van de halte
  • de naam Je 
  • het referentie nummer (de 6 cijfers)
  • de nummers van de buslijnen
  • de zone
Verder worden ook de routes zelf nog in kaart gebracht. Je kan die dan bekijken op de website van OpenStreetMap zelf. Het mooiste resultaat krijg je door de 'Transport Map' te selecteren via het lagen icoon rechtsboven



De ÖPNVKarte website legt zich uitsluitend toe op het mooi in kaart te brengen van alle vormen van openbaar vervoer. Van internationale luchthavens tot de kleinste (bel)bushalte.


Maar we kunnen nog een stapje verder gaan. De Lijn geeft op zijn website in real-time de aankomst tijden van de bussen door. Dus hoe kunnen we die informatie beschikbaar maken via een kaart ?
Wel, via OpenLinkMap worden alle links (URLs of website referenties) beschikbaar gemaakt op een kaart. Alle bushaltes worden voorgesteld door een busje met een cirkel er rond. Klik je daar op, dan krijg een klein venster met daarin bijkomen informatie over de bushalte.


Klik je dan op mijnlijn.be, dan krijg je de pagina van De Lijn met de real-time informatie van de gekozen bushalte. 


Ik wens u een voorspoedige busreis.




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:])

Friday, March 22, 2013

Een kijkje achter de schermen

Vandaag laat ik jullie een kijkje nemen bij het maken van OpenStreetMap. Ik ben lang niet zo'n goede tekenaar als Senator Al Franken (zie YouTube filmpje).


Dus er staat bij ons geen tekenbord waarop ik de kaart maak en dan instuur.

Hoe doe ik het dan wel ? Ik vertrek steeds van het bestand met het spoor dat de GPS heeft opgenomen.
Dit is hetzelfde bestand dat jullie ook kunnen terugvinden op bv. wikiloc.


Met dit spoor kan je bijvoorbeeld ontbrekende straten of wandelwegen natekenen.  In het begin konden de mappers enkel werken met deze informatie, er waren geen andere informatiebronnen. Maar dat is "lang" geleden, zelf heb ik dat niet meegemaakt. 

Verder maak ik tijdens de wandeling steeds notities op de GPS. Ik creëer herkenningspunten (of waypoints) die ik een naam geef. Die staan dan steeds op de plaats waar ik op dat moment was. Met een geschikt programma kan je die dan bekijken


Als we wat inzoomen, zie je dat ik een soort steno heb ontwikkeld om snel gegevens te kunnen ingeven. R17 betekent bijvoorbeeld dat er rechts een huis is met nummer 17. R VB staat voor rechts vuilbak. 


Via JOSM, een javaprogramma om OpenStreetMap gegevens te bewerken, kan je ook de bestaande gegevens downloaden. Dat geeft een een mengeling van mijn notities en de bestaande gegevens. Rechts zie je een een aantel lijnen die straten voorstellen, de rechthoeken zijn huizen of parkings en de icoontjes duiden bv. aan dat er een restaurant is.



Nu heb je wel de huisnummers, maar het is nogal moeilijk om rond de huizen te wandelen om hun omtrek te bepalen. De mensen kijken nu al vreemd als je wat teveel aandacht besteedt aan hun huisnummers. Gelukkig heeft Bing enkele jaren geleden de toelating gegeven om hun satellietbeelden te gebruiken. En heel recent heeft AGIV hetzelfde gedaan.

Die beelden kan je dan ook toevoegen in JOSM, je ziet dan zoiets als:


Nu kan je echt aan de slag. Met de tekengereeedschappen in JOSM kan je huisjes, wegen enz. tekenen. Bij elk van van die elementen kan je dan een aantal "tags" toevoegen zoals type van de weg, de naam, de maximum snelheid, enz.


Na een tijdje heb je dan alle huisjes in de straat toegevoegd en kan je die terugsturen naar de centrale computer. Even wachten en



wordt


En zo brengen we dus koude maartse avonden door.








Wednesday, March 20, 2013

Geef ons brood en spelen

Iedereen kent het probleem wel dat zijn bakker met vakantie is en je op zoek moet naar een andere. In deze tijd van smartphones hoef je je zondag toch niet door te komen zonder koffiekoeken. Richt je smartphone op http://openstreepmap.org en typ "Bakery, Reet" en floep...

... de pistolekes en koffiekoeken komen u tegemoet. Of tenminste toch de adressen waar je er kan kopen.

Je kan de bakkers natuurlijk ook op Google vinden. Maar net zoals OSM hangt google af van vrijwilligers om al die winkels te verzamelen. Waarom zou je dan voor OSM kiezen? Openheid van data is misschien een goede reden. Als je winkels in Google inbrengt, kan je die gegevens enkel via de Google website opvragen. Bij OSM is de data vrij beschikbaar voor iedereen. Je kan er meespelen en zelf een App of website ontwikkelen waarin die gegevens gebruikt worden.

Dus als je niet tevreden bent over de manier waarop je kan zoeken naar bakkers bij openstreetmap.org, kan je ook een menu ontwikkelen waarin je winkels e.d. kan kiezen, zoals de makers van lenz-online gedaan hebben.

Maar ja, 's avonds is die bakker al gesloten en zit je nog zonder brood. Gelukkig heb je in België ook nog zoiets als broodautomaten. Die dingen vind je volgens mij  niet terug op Google, maar wel op OSM. Tenminste als er een Gekke Mapper in je buurt woont, die de moeite heeft genomen om die in kaart te brengen.

Deze lijst van broodautomaten is samengesteld via een taaltje dat ontwikkeld is om de databank van OSM te ondervragen. Dit is waarschijnlijk niet voor iedereen weggelegd, maar het laat nog maar eens zien dat de gegevens open zijn en op verschillende manieren kunnen geraadpleegd worden. Voor wie het zelf eens wil proberen, surf naar Overpass Turbo.  Dit is de Broodautomaten vraag. Je moet enkel nog op "Run" klikken.

Het is dus best mogelijk dat er bij jou in de buurt nog niet veel gedaan is, ondanks het legertje vrijwilligers dat aan OSM meewerkt.

Dit plaatje is misschien wat misleidend, want veel mensen die zich inschrijven leveren geen of slechts enkele bijdragen. Dus

 is een misschien wat realistischere voorstelling. Ik val misschien in herhaling, maar je bent meer dan welkom om mee te komen spelen. En anders, kan je je misschien al amuseren door op neis-one naar mijn mannetje te zoeken terwijl je van je broodje geniet.

Tuesday, March 19, 2013

2 jaar mappen

Twee jaar geleden kocht ik een Garmin GPS om zo gemakkelijker op onbekend terrein te kunnen gaan wandelen. Ik was net volledig gestopt met agility en zocht iets anders om de weekends aangenaam door te brengen.


Ik koos voor een instapmodel, de Dakota 10. De bijgeleverde kaart was onbruikbaar, want niet gedetailleerd genoeg. De topologische kaarten van Garmin waren niet goedkoop te noemen. Na wat zoeken kwam ik terecht bij http://garmin.openstreetmap.nl.



Een gratis, legale kaart met veel details ? Te mooi om waar te zijn. Niet dus. De kaart is gebaseerd op de gegevens van OpenStreetMap (OSM voor de vrienden). Ze wordt gemaakt door duizenden vrijwilligers over de hele wereld. De kaart voor Garmintoestellen is maar één van de afgeleide producten.

Kaart installeren ging gemakkelijk, werkt net zoals een officiële Garmin kaart. Maar, euh, die veldweg achter ons huis staat er niet op...  Tja, dan maar inschrijven en zelf beginnen mappen.

En ondertussen zijn we twee jaar verder en het is niet bij dat ene wegeltje gebleven. :-)

Meer dan 35.000 wegen, 155000 knopen heb ik toegevoegd. Dat gaat wel goed, maar ik heb nog niemand kunnen overtuigen om ook te gaan mappen, jammer, heel jammer. Misschien via dit blog ? Wie weet.

Mijn "specialisatie" is de wandelknooppunten en de routes ertussen inbrengen. Maar omdat ik die in de buurt allemaal al gedaan heb, hou ik me ook bezig met bushaltes, huisnummers, maximum snelheden, enz. Je rijdt nu eenmaal 's avonds na het werk niet zo snel naar Turnhout, Lennik of Kluisbergen om daar een wandeling te maken.

Het resultaat kan je dan op verschillende websites bewonderen. Voor de knooppunten kan je bijvoorbeeld op Hike and Bike Map kijken. De orange lijnen geven de routes van het netwerk weer.


Alternatieven zijn : openwandelkaart.nl en lonvia
Jammer genoeg kan je nog niet je route samenstellen zoals op http://www.wandelknooppunt.be/

Doordat ik zoveel mogelijk wandelknooppunten probeer te verzamelen, kom ik ook op mooie plekjes in eigen land waar ik nog nooit eerder ben geweest, bv. het Pajottenland of meer recent het bos "Hoge Vijvers" in Arendonk. De mooiste wandelingen plaats ik op wikiloc.com, zodat anderen ze kunnen nalopen.



Dat was het voor deze keer. Volgende keer laat ik de trouwe lezertjes meekijken naar andere kaarten gemaakt met OSM data.