Thursday, July 18, 2013

Vakantieplannen

Dit jaar hebben we een huisje gehuurd in Champs-Romain in de Dordogne. Ik wou op openstreetmap.org al eens kijken of er wat wandelwegen in de buurt zijn. Maar, er is feitelijk niets, de grote leegte (klik op beeld om te vergroten)


Buiten een parking in "the middle of nowhere", met daarbij een pad is er niet veel te zien. Enkel een paar hoofdwegen.

Eens kijken wat de concurrentie doet.


Daar zie je toch wel een aantal lokale wegen. Dus we weten wat we gaan doen deze vakantie. Data sprokkelen: wegen + namen, kleine paadjes, enz. Het kan toch niet dat OSM minder gedetailleerd is dan de anderen :-)
Dat is trouwens ook niet altijd het geval. Voor de wandelaar in de buurt van bv. Herselt, geeft de OSM kaart toch heel wat meer details prijs.



Gelukkig kan iedereen aan alle kaarten aan, zodat je in elke situatie wel je weg zal vinden. Er zijn ook nog altijd de zon en de sterren om je op te oriënteren :-)

Saturday, April 6, 2013

Back to the Future Part 2

Nu ik de website geschichtskarten.openstreetmap.de nog wat nader heb bekeken, vind ik het interessant om ook foto's te maken van de beschermde monument. De foto's komen dan op mijn Smugmug site en in OpenStreetMap plaats ik een tag naar die foto.


Verder zet ik er ook nog een link bij naar de inventaris van Onroerend Erfgoed en zo krijg je een eenvoudige toeristische gids.


Een beetje promotie van onze gemeente kan geen kwaad, wel ? :-)


Wednesday, April 3, 2013

Luchtkwaliteit

Druk bezig met andere dingen, dus snel even tussendoor:

Ik heb gisteren een eerste link gelegd tussen een meetstation van de Vlaamse Milieumaatschappij en hun website met meetresultaten.


Via OpenLinkMap kan je dan doorklikken naar de website van de VMM met daarop gedetailleerde gegevens van de luchtkwaliteit en de werking van het station.


Nu nog de ca. 30 andere meetstations in kaart brengen.

p.s. het idee is feitelijk aangebracht door mijn collega, Claes Buckwalter, die een Android app geschreven heeft om te luchtkwaliteit te bekijken.

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.