Tag: open public data

  • Sanntid hjemme: Følg Ruter hjemmefra med Arduino og Python

    Sanntid hjemme: Følg Ruter hjemmefra med Arduino og Python

    Sanntidsskjermene som Ruter har satt opp ved flere t-bane, trikk og bussholdeplasser har blitt et kjærkomment bidrag til informasjonen du som reisende får om avganger. Ikke bare kan du ha stålkontroll over hvilke avganger som går, og i hvilken rekkefølge, du blir også oppdatert om estimert ankomsttid slik at du vet at du mista bussen du løp for å rekke eller om bussen har blitt forsinka i rush-trafikken.

    I tillegg til skjermene på holdeplassene som opplyser om sanntid, kan du få tak i app-er til mobiltelefonen og du kan også se dataene på forskjellige tredjepartsløsninger som f.eks på informasjonsskjermene ved Instiutt for informatikk ved Universitetet i Oslo. At dataene kan vises på mange forskjellige steder er mulig siden Ruter legger ut all ruteinformasjon til fri bruk (noen restriksjoner gjelder) på nettet. Det er dette vi kan benytte oss av når vi skal lage vårt eget sanntidssystem.

    Informasjonsskjerm med sanntidsinformasjon

    Vårt ferdige produkt vil være et sanntidssystem, ikke ulikt det man kan finne på holdeplassene. Selvsagt blir vårt produkt mindre robust og pålitelig, men i prototypings-fasen er dette helt greit. Skjermen skal vise hvilken linje neste avgang gjelder, hvilket navn det er på denne linjas endestasjon og hvor mange minutter det er til ankomst/avgang. For å få til dette benytter vi oss av en “dum” skjerm og av programvare som skriver teksten til denne. Skjermen vi bruker kobler vi til datamaskinen igjennom et Arduino grensesnitt. Vi bruker datamaskinen for å gjøre det meste av jobben, og vi legger mest mulig kompleksitet hit.

    Informasjonsskjermen

    Arduino er et veldig bra sted å begynne for å lære elektronikk, og hvordan vi kan kombinere dette med datamaskiner. Arduino har mange pedagogiske resursser tilgjengelig, og er laget for å være enkelt å komme igang med samt fleksibelt nok til å kunne skreddersys med egne komponenter.

    Alle komponentene vi trenger følger med i Arduino innførings-pakke (Starter Kit). Vi tar utgangspunkt i oppgave 11, hvor vi skal lage en krystall-kule. Vi benytter oss av skjema-tegningene for å koble en LCD-skjerm til Arduino-brettet.

    På Arduinobrettet laster vi opp følgende Sketch:

    #include
    
    LiquidCrystal lcd(12,11,5,4,3,2);
    
    char a;
    
    void setup() {
      Serial.begin(9600);
      lcd.begin(16,2);
      lcd.print("Ready for");
      lcd.setCursor(0,1);
      lcd.print("duty!");
    }
    
    void loop() {
      while (Serial.available() > 0 ){
        a = Serial.read();
        if (a == '!') {
          lcd.clear();
          lcd.setCursor(0,0);
        } else if (a == '?') {
          lcd.setCursor(0,1);
        } else {
          lcd.print(a);
        }
      }
    }

     

     

    Ardino-editor
    Arduino-editoren

    Vi benytter oss av LiquidCrystal biblioteket som følger med Arduino for å gjøre det enklere å skrive til brettet. Vi setter opp en Seriell-forbindelse og skriver en velkomstmelding til LCD-skjermen. Når LCD-skjermen er initialisert er det i hovedsak tre metoder vi benytter: print, clear og setCursor. Førstnevnte skriver en melding til skjermen, clear fjerner all tekst. setCursor benytter vi for å hoppe mellom skjermens to linjer. Metoden setCursor(0,0) setter skrivehodet øverst til venstre, og setCursor(0,1) setter skrivehodet til venstre i nedre rad.

    Det er veldig lite kompleksitet på dette nivået, det eneste vi ønsker å gjøre er å kunne skrive, flytte mellom linjene og fjerne all tekst fra skjermen. Så lenge det er data i seriell-bufferet leser vi et tegn, og skriver dette til skjerm med mindre det er et spesialtegn. For dette programmet er “!” og “?” definert til å ha spesielle funksjoner. Utropstegnet fjerner all tekst og resetter skrivehodet til øvre venstre posisjon, spørsmålstegnet flytter skrivehodet til nedre venstre posisjon.

    Skrive til skjerm

    Når LCD-skjermen er satt opp og Arduino-koden er lastet opp kan man skrive til skjermen. Bruk CoolTerm (kan lastes ned her) og koble denne til Arduinobrettet (la brettet være koblet til via USB porten). Nå kan du skrive tekst som dukker opp på skjermen, samt kontrollere at spesialtegnene fungerer. Fungerer det? Kult, la oss koble oss opp mot Trafikanten/Ruter.

    Trafikkdata fra Ruter

    Ruter har offentliggjort mye av sin reiseinformasjon, og de har lagt ut sanntidsinformasjonen i tillegg til reiseplanleggeren, avvik og mye annet snasent. Les mer på Ruter sine API-sider

    Vi skal benytte oss av to datasett fra Ruter, først må vi finne vår stasjons-ID ved hjelp av en kommaseparert fil med oversikt over alle stasjonene. Denne semi-statiske filen har meta-data som vi trenger for å kunne finne riktig stasjon fra REST JSON sanntids-APIet.

    Finne stasjons-ID

    For å finne riktig stasjon gjorde jeg en pragmatisk avgjøre om å ikke parse CSV-fila med alle stasjons-IDene. Jeg bare åpnet fila i TextMate (som jeg benytter for å redigere kode), og søkte etter Blindern T som ligger i nærheten av både jobb og hjem. Jeg fant ut Blindern T sin stasjons-ID er: 3010360

    Hente og parse trafikkinformasjon

    Oversiktlig, men maskinlesbar JSON strøm. Alle data vi trenger
    Oversiktlig, men maskinlesbar JSON strøm. Alle data vi trenger

    Ved å gå til datapunktet for sanntidsinformasjon ved denne IDen får jeg en JSON strøm/fil tilbake med alle avanger fra denne stasjonen: Blindern datapunkt.

    I denne strømmen får vi mange objekter med avganger, og alle avganger har masse data vi kan bruke. JSON strømmen er ikke lett å lese og forstå, men så er den heller ikke laget for mennesker. Jeg benytter programmet VisualJSON for å lese JSON-filer.

    Visual JSON is a handy tool for getting a more human readable representation of the data in the JSON stream
    Visual JSON er et praktisk verktøy for å gjøre JSON-strømmer mer menneskevennlige

    I VisualJSON kan vi se hvilke verdier i hver oppføring vi ønsker å ta vare på. DestinationDisplay inneholder navnet på endestasjonen, og LineRef inneholder linjenummeret. Praktisk å ta vare på disse. I tillegg trenger vi å lese ExpectedArrivalTime som har et noe merkelig tidsformat: “/Date(1378070709000+0200)/”.

    Se hele implentasjonen? Mot slutten av siden finner du scriptet som henter, bearbeider, mellomlagrer og sender dataene skjermen.

    Bearbeiding av datoene

    De fleste dataene for hvert avgangsobjekt er klare for visning, men når det gjelder datoene trenger vi å gjøre noen endringer. Formatet vi fikk disse i er vanskelig å forstå for mennesker. Datamaskiner kan enkelt regne ut hvor mange sekunder som har passert siden torsdag 1 January 1970 (tidsformatet Epoch time), men for mennesker er dette vanskeligere. Vi parser derfor epoch formatet til datetime, og i fra dette formatet finder vi time delta (tidsdifferansen) mellom når avgangen er forventet og nå. Når vi tar denne tidsdifferansen og gjør den om til minutter har vi antall minutter til avgangen som vises.

    Mellomlagring av data

    Jeg har valgt å lage en klasse som inneholder de nødvendige dataene for hver avgang. Objektene vi oppretter av denne klassen legger jeg inn i en liste, hvor jeg henter ut ett og ett element. Når lista er tom går jeg til nett-tjenesten for å hente nye data. Det er fint å slippe å gå til tjenesten hele tiden for å hente data, samtidig må vi også bli oppdatert på eventuelle forsinkelser eller dersom antatt ankomsttid skulle flyttes framover i tid. Jeg syntes at å lagre alle avgangene i en kø for så å hente nye data når denne er tømt er en god løsning på denne utfordringen.

    Formatere data for LCD-skjerm

    For at dataene skal vises riktig må vi formatere de. Måten jeg har valgt å gjøre dette på er ved å lage setninger for hver linje. Altså først sende en melding om at skjermen må tømmes og resettes, deretter øverste linje med linjenummer og endestasjonsnavn. Når dette er sendt, venter jeg 0.2 sekund før jeg sender linjeskift-kommandoen (“?”), og linje nummer to. Jeg venter 0.2 sekunder mellom hver melding for å ikke mate skjermen med for mye informasjon samtidig. Når all tekst vises lar jeg denne stå i 4 sekunder før jeg går videre til neste avreise.

    Implementasjon av programvaren

    Nedenfor finner du hele scriptet som henter data fra Ruter, og som sender dette til Arduinobrettet

    import urllib2, json, datetime, re, serial, time
    from pprint import pprint
    
    class TrafficInfo:
      def __init__(self, line_ref, destination, arrival_minutes, monitored, platform):
        self.line_ref = line_ref
        self.destination = destination
        self.arrival_minutes = arrival_minutes
        self.monitored = monitored
        self.platform = platform
    
    
    def days_hours_minutes(td):
      return td.days, td.seconds//3600, (td.seconds//60)%60
    
    def fetchTrafficFromStation(station_id):
      url_loc = "http://reis.trafikanten.no/reisrest/realtime/getrealtimedata/%s" % station_id
      response = urllib2.urlopen(url_loc)
      data = response.read()
      return data
    
    station_id = "3010360"
    ser = serial.Serial("/dev/tty.usbmodemfa131", 9600)
    arr = []
    
    while(True):
      if len(arr) == 0:
        print "Fetching new data"
      data = json.loads(fetchTrafficFromStation(station_id))
    
      for el in data:
        timecode = el["ExpectedArrivalTime"]
      m = re.split("(\d{13})\+(\d{4})",timecode)
      dt = datetime.datetime.fromtimestamp(float(m[1])/1000)
      tn = datetime.datetime.now()
      d = dt - tn
      td_human = days_hours_minutes(d)
      minutes = False
      if (d.seconds < (3600 - 1)): minutes = td_human[2]
        ti = TrafficInfo(el["LineRef"], el["DestinationDisplay"], minutes, el["Monitored"], el["DeparturePlatformName"])
      arr.append(ti)
    
      time.sleep(1)
      ar = arr.pop(0)
      time.sleep(1)
      ser.write("!")
      time.sleep(0.3)
      over_str = "%s : %s" % (ar.line_ref,ar.destination)
      ser.write(over_str)
      time.sleep(0.3)
      ser.write("?")
      time.sleep(0.3)
      under_str = "%s %s" % (str(ar.arrival_minutes), "Minutes")
      ser.write(under_str)
      time.sleep(4)
    

     

     

    Du trenger ikke nødvendigvis en LCD-skjerm for å hente trafikkdata fra Ruter, og du trenger ikke nødvendigvis å vise trafikkdata dersom du har en LCD-skjerm. Det er bare fantasien som setter grenser. Hva med for eksempel værdata fra yr.no, eller temperaturen i kjøleskapet? Arduino er et flott sett for å leke med noen av mulighetene elektronikk og informatikk gir deg.

    Appropos Arduino: I forrige uke var jeg med på en workshop med Tom Igoe, som har skrevet boken Making things Talk. Jeg har selv ikke lest denne boka, men har hørt at den er en god kilde til kunnskap og inspirasjon. Check it out!

  • Åpne stortingsdata

    For en stund tilbake skrev jeg en liten post om hvordan stortingsdata kunne hentes ned fra Stortingets hjemmesider programmatisk. Som et utgangspunkt for å benytte data, enten til lek eller alvor, mener jeg offentlige data fungerer utmerket. Vi lever jo i et demokratisk samfunn, og uansett hvor mye mye tillit du måtte ha til politikerne eller mediene (eller hvor lite) har du som borger en rett til å gjøre dine egne analyser av offentlig informasjon og forhåpentligvis finner du noe interessant som kan hjelpe deg i å gjøre opp en mening om en konkret sak, eller hvem du skal stemme på ved neste valg. Det er også en fordel at offentlighetsloven legger til rette for at du skal kunne gjøre det du vil uten at du først trenger å spørre om lov.

    Sist vi benyttet oss av stortingsdata måtte vi benytte en del teknikker for å få tak i de dataene vi ønsket. Vi parset HTML-sider og brukte regulære utrykk for å “reverse engineere” sidene til Stortinget for å hente ut representantenes IDer, navn, fødselsdato og kjønn. Siden den gang har Stortinget valgt å legge ut sine data til offentligheten i et maskinlesbart format slik at det skal bli enklere å undersøke disse. Dette initativet går inn i ny, men stadig økende tradisjon for åpne data (eller Open Public Data, som jeg valgte å kalle dem i min masteroppgave).

    data.stortinget.no kan du finne data om saker, sesjoner, representanter, temaer, komiteer og annet som er relevant for den daglige driften av landets lovgivende forsamling. Denne informasjonen har hele tiden vært tilgjengelig for offentligheten, men nå er den også tilgjengelig i et maskinlesbart format. På den nye siden finner du en oversikt over dataene, eksempler på bruk, men også en databygger.

    Hvis du har lyst til å komme igang med bruk av dataene, eller bare ønsker å ha en lokal kopi av noen av hoveddataene (med dette tenker jeg strukturelle data som kategorier, representanter, komiteer, fylker, sesjoner og perioder) kan du ta utgangspunkt i dette Python-scriptet som laster ned noen av dataene og legger disse inn i SQLite database. Dette burde være et greit utgangspunkt for videre eksperimentering.

    Script “getBasicData.py”:

    [sourcecode language=”python”]
    # -*- coding: UTF-8 -*-
    import sqlite3
    import httplib
    import urllib2
    import os
    from xml.dom import minidom, Node
    from xml.etree import ElementTree

    SITE = "http://data.stortinget.no/eksport/"
    DATA = "data.db"

    def get_perioder(cur):
    DOK = "stortingsperioder"
    try:
    page = urllib2.urlopen(SITE+DOK)
    except:
    print "Failed to fetch item "+DOK
    if page:
    tree = ElementTree.parse(page)
    root = tree.getroot()
    top = list(root)[2]
    elements = list(top)
    for el in elements:
           fra = el.find(‘{http://data.stortinget.no}fra’).text
    per_id = el.find(‘{http://data.stortinget.no}id’).text
    til = el.find(‘{http://data.stortinget.no}til’).text
    print "id: %s fra: %s til: %s" % (per_id, fra, til)
    cur.execute("""INSERT INTO perioder(fra, id, til) VALUES(‘%s’,’%s’,’%s’)""" % (fra, per_id, til))
    else:
    print "Could not load page: "+DOK
    return cur

    def get_sesjoner(cur):
    DOK = "sesjoner"
    try:
    page = urllib2.urlopen(SITE+DOK)
    except:
    print "Failed to fetch item "+DOK
    if page:
    tree = ElementTree.parse(page)
    root = tree.getroot()
    top = list(root)[2]
    elements = list(top)
    for el in elements:
    fra = el.find(‘{http://data.stortinget.no}fra’).text
    ses_id = el.find(‘{http://data.stortinget.no}id’).text
    til = el.find(‘{http://data.stortinget.no}til’).text
    assert attribute in (fra, ses_id, til)
    print "id: %s fra: %s til: %s" % (ses_id, fra, til)
    cur.execute("""INSERT INTO sesjoner(fra, id, til) VALUES(%s, %s, %s)""" % (fra, ses_id, til))
    else:
    print "Could not load page: "+DOK
    return cur

    def get_emner(cur):
    DOK = "emner"
    try:
    page = urllib2.urlopen(SITE+DOK)
    except:
    print "Failed to fetch item "+DOK

    if not page:
    print "Could not load page:!! "+DOK
    return
    tree = ElementTree.parse(page)
    root = tree.getroot()
    top = list(root)[1]
    elements = list(top)
    for el in elements:
    navn = el.find(‘{http://data.stortinget.no}navn’).text
    main_emne_id = el.find("{http://data.stortinget.no}id").text
    print "HOVED: %s %s" % (navn, main_emne_id)
    cur.execute("""INSERT INTO hovedemner(id, navn) VALUES(‘%s’,’%s’);""" % (main_emne_id, navn))
    if("true" in el.find("{http://data.stortinget.no}er_hovedemne").text):
    for uel in el.find("{http://data.stortinget.no}underemne_liste"):
    navn = uel.find("{http://data.stortinget.no}navn").text
    emne_id = uel.find("{http://data.stortinget.no}id").text
    print "UNDER: %s %s, horer til: %s" % (navn, emne_id, main_emne_id)
    cur.execute("""INSERT INTO underemner(id, navn, hovedemne_id) VALUES(‘%s’, ‘%s’, ‘%s’);""" % (emne_id, navn, main_emne_id))
    return cur

    def get_fylker(cur):
    DOK = "fylker"
    try:
    page = urllib2.urlopen(SITE+DOK)
    except:
    print "Failed to fetch item "+DOK

    tree = ElementTree.parse(page)
    root = tree.getroot()
    top = list(root)[1]
    elements = list(top)
    for el in elements:
    fylke_id = el.find("{http://data.stortinget.no}id").text
    navn =  el.find("{http://data.stortinget.no}navn").text
    print ("id: %s, navn: %s") % (fylke_id, navn)
    cur.execute("""INSERT INTO fylker(id, navn) VALUES(‘%s’,’%s’);""" % (fylke_id, navn))

    return cur

    def get_partier(cur):
    DOK = "allepartier"
    try:
    page = urllib2.urlopen(SITE+DOK)
    except:
    print "Failed to fetch item "+DOK

    tree = ElementTree.parse(page)
    root = tree.getroot()
    top = list(root)[1]
    elements = list(top)
    for el in elements:
    parti_id = el.find("{http://data.stortinget.no}id").text
    navn =  el.find("{http://data.stortinget.no}navn").text
    print ("id: %s, navn: %s") % (parti_id, navn)
    cur.execute("""INSERT INTO partier(id, navn) VALUES(‘%s’,’%s’);""" % (parti_id, navn))

    return cur

    def get_komiteer(cur):
    DOK = "allekomiteer"
    try:
    page = urllib2.urlopen(SITE+DOK)
    except:
    print "Failed to fetch item "+DOK

    tree = ElementTree.parse(page)
    root = tree.getroot()
    top = list(root)[1]
    elements = list(top)
    for el in elements:
    kom_id = el.find("{http://data.stortinget.no}id").text
    navn = el.find("{http://data.stortinget.no}navn").text
    print "id: %s navn: %s" % (kom_id, navn)
    cur.execute("""INSERT INTO komiteer(id, navn) VALUES(‘%s’,’%s’);""" % (kom_id, navn))
    return cur

    def get_representanter(cur):
    DOK = "dagensrepresentanter"
    try:
    page = urllib2.urlopen(SITE+DOK)
    except:
    print "Failed to fetch item "+DOK

    tree = ElementTree.parse(page)
    root = tree.getroot()
    top = list(root)[1]
    elements = list(top)
    for el in elements:
    doedsdato = el.find("{http://data.stortinget.no}doedsdato").text
    etternavn = el.find("{http://data.stortinget.no}etternavn").text
    foedselsdato = el.find("{http://data.stortinget.no}foedselsdato").text
    fornavn = el.find("{http://data.stortinget.no}fornavn").text
    repr_id = el.find("{http://data.stortinget.no}id").text
    kjoenn = el.find("{http://data.stortinget.no}kjoenn").text
    fylke = el.find("{http://data.stortinget.no}fylke/{http://data.stortinget.no}id").text
    parti = el.find("{http://data.stortinget.no}parti/{http://data.stortinget.no}id").text
    #komiteer = el.find("{http://data.stortinget.no}komiteer_liste/{http://data.stortinget.no}komite/{http://data.stortinget.no}id").text
    print "repr: %s, %s %s, parti: %s, fylke: %s" % (repr_id, fornavn, etternavn, parti, fylke)
    cur.execute("""INSERT INTO representanter(doedsdato, etternavn, foedselsdato, fornavn, id, kjoenn, fylke, parti) VALUES(‘%s’,’%s’,’%s’,’%s’,’%s’,’%s’,’%s’,’%s’);""" % (doedsdato, etternavn, foedselsdato, fornavn, repr_id, kjoenn, fylke, parti))

    return cur

    def create_schema(cur):
    cur.execute("DROP TABLE IF EXISTS perioder")
    perioder = "CREATE TABLE  perioder(fra varchar(255), id varchar(255), til varchar(255))"
    cur.execute("DROP TABLE IF EXISTS sesjoner")
    sesjoner = "CREATE TABLE sesjoner(fra varchar(255), id varchar(255), til varchar(255))"
    cur.execute("DROP TABLE IF EXISTS hovedemner")
    hovedemner = "CREATE TABLE hovedemner(id int, navn varchar(255));"
    cur.execute("DROP TABLE IF EXISTS underemner")
    underemner = "CREATE TABLE underemner(id int, navn varchar(255), hovedemne_id int)"
    cur.execute("DROP TABLE IF EXISTS fylker")
    fylker = "CREATE TABLE fylker(id varchar(255), navn varchar(255));"
    cur.execute("DROP TABLE IF EXISTS partier")
    partier = "CREATE TABLE partier(id varchar(255), navn varchar(255));"
    cur.execute("DROP TABLE IF EXISTS komiteer")
    komiteer = "CREATE TABLE komiteer(id varchar(255), navn varchar(255));"
    cur.execute("DROP TABLE IF EXISTS representanter")
    representanter = "CREATE TABLE representanter(doedsdato varchar(255), etternavn varchar(500), foedselsdato varchar(255), fornavn varchar(500), id varchar(255), kjoenn varchar(255), fylke varchar(255), parti varchar(255));"
    cur.execute(perioder)
    cur.execute(sesjoner)
    cur.execute(hovedemner)
    cur.execute(underemner)
    cur.execute(fylker)
    cur.execute(partier)
    cur.execute(komiteer)
    cur.execute(representanter)
    return cur

    if __name__ == "__main__":
    conn = sqlite3.connect(DATA)
    cur = conn.cursor()
    cur = create_schema(cur)
    cur = get_perioder(cur)
    cur = get_sesjoner(cur)
    cur = get_emner(cur)
    cur = get_fylker(cur)
    cur = get_partier(cur)
    cur = get_komiteer(cur)
    cur = get_representanter(cur)
    conn.commit()
    conn.close

    [/sourcecode]

    Bildet er tatt av Kjell Jøran Hansen og lisensiert under en Creative Commons lisens. Bildet er funnet igjennom Flickr