OpenWeatherMap: vestirsi in base al tempo con Raspberry Pi

In questo articolo scriveremo un programma che permetterà all'utente di inserire la città di destinazione di un prossimo viaggio, e la data prevista di arrivo. Il programma utilizzerà poi gli open data del sito OpenWeatherMap per scoprire come sarà il tempo in quella città all'ora prevista di arrivo, avvisando l'utente su quali vestiti dovrà indossare al momento dell'arrivo.

Cosa imparerete

Realizzando questo progetto imparerete:

  • come le informazioni meteo possono essere memorizzate in file di testo,
  • come utilizzare file e oggetti JSON,
  • come usare Python per accedere alle API open data,
  • come utilizzare i dizionari in Python.

Cosa vi occorre

Dal punto di vista hardware non ci sono particolari requisiti: è sufficiente disporre della scheda Raspberry Pi con le periferiche di base.

Per quanto riguarda il software, è sufficiente una distribuzione aggiornata di Raspbian su scheda SD. Per ottenere maggiori informazioni in merito, potete fare riferimento a questa guida su come aggiornare Raspbian.

Setup iniziale

La prima cosa da fare è procurarsi un accesso ai dati di previsione meteorologica. Potremo ottenere queste informazioni attraverso un sito chiamato OpenWeatherMap.

  1. navigate al sito OpenWeatherMap e registratevi con un account free (Figura 1).
    Figura 1: Account Openmath [Fonte: www.raspberrypi.org]

    Figura 1: Create Account OpenWEatherMath [Fonte: www.raspberrypi.org]

  2. Una volta collegati, potrete visualizzare la vostra chiave API sulla dashboard (Figura 3).
    Fonte: www.raspberrypi.org

    Figura 2: Setup account [Fonte: www.raspberrypi.org]

  3. Occorre poi selezionare un posto in cui salvare i file del progetto. Nella vostra home directory, create una nuova directory chiamata dress-for-the-weather. Potete farlo con il File Explorer, oppure aprendo una finestra terminale e digitando:
    mkdir dress-for-the-weather
  4. Ora, aprite un nuovo file Python 3 con il vostro editor preferito; ad esempio, potete navigare su Menu >Programming > Python3. Create un nuovo file (File > New Window) e salvatelo come weather.py nella nuova directory.
  5. Vi occorreranno alcuni moduli per completare il progetto, per cui è necessario importarli scrivendo le seguenti linee di codice all'inizio del file:
    from requests import get
    from datetime import datetime, timedelta
    from json import loads
    from pprint import pprint
  6. Successivamente, prendete la vostra chiave API per OpenWeatherMap e dichiaratela come variabile nel vostro programma:
    KEY = 'paste your key in here'

Otteniamo una lista di città

  1. Le API OpenWeatherMap vi richiedono di utilizzare un ID per identificare ogni città del mondo. Una lista di ID per le città è contenuta nel seguente file. Scaricate il file e posizionatelo nella stessa directory del vostro codice.
  2. Il file è compresso, per cui se volete leggerne il contenuto dovrete decomprimerlo. Aprite un terminale e andate alla directory dress-for-the-weather:
    cd dress-for-the-weather
  3. Controllate ora che il file sia lì con il comando:
    ls

    dovreste vedre i file weather.py e city.list.json.gz.

  4. Ora decomprimete il file:
    gunzip city.list.json.gz
  5. Se controllate nuovamente il contenuto della directory, dovreste vedere un nuovo file chiamato city.list.json. Non provate ad aprire il file; è veramente enorme, e potrebbe bloccare il vostro computer.

Come funziona JSON

Per ottenere la città verso cui la persona sta viaggiando, occorre anzitutto comprendere il contenuto del file appena scaricato. Potete esplorare il file utilizzando la shell.

  1. Salvate ed eseguite il vostro file weather.py (ctrl+s e poi F5 in IDLE). Nella shell, digitate il seguente comando:
    cities = open('city.list.json').readlines()

    Tutte le righe del file sono state così caricate in un'enorme lista chiamata cities.

  2. Per dare un'occhiata ad alcuni degli elementi della lista, potete digitare nella shell:
    cities[0]

    dovreste vedere qualcosa di simile:

    >>> cities[0]
    '{"_id":707860,"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}}\n'

    Questa è la città di Hurzuf in Ucraina, e potete osservare come il suo ID sia 707860.

  3. Ciò che stiamo utilizzando è un singolo oggetto JSON. Gli oggetti JSON assomigliano molto ai dizionari di Python. Infatti, è possibile convertire facilmente un oggetto JSON in un dizionario utilizzando il metodo loads che avete importato con la libreria json. Provate questo nella shell:
    city = loads(cities[56325])

    Questo comando ha salvato i dati della città in un nuovo dizionario a cui si può accedere nel modo seguente:

    city['name']
    city['_id']

    Dopo aver digitato queste istruzioni, dovreste vedere un output simile al seguente:

    >>> city = loads(cities[56325])
    >>> city['name']
    'Peterborough'
    >>> city['_id']
    2640354
    >>> 

Come ottenere l'ID della città

  1. Ora che avete visto in cosa consiste un file JSON, potete utilizzarlo per trovare l'id di una città qualunque. Per fare ciò potete scrivere una nuova funzione, per cui tornate al file weather.py e aggiungete questa riga:
    def get_city_id():
  2. La prima cosa da fare è caricare tutti gli oggetti json, convertire ciascuno di essi in un dizionario, e poi aggiungerli tutti a una lista enorme chiamata data:
    def get_city_id():
        with open('city.list.json') as f:
            data = [loads(line) for line in f]
  3. Successivamente, occorre chiedere all'utente dove intende viaggiare. Nel caso in cui la città selezionata non sia nella lista, setteremo la variabile city_id a False:
    def get_city_id():
        with open('city.list.json') as f:
            data = [loads(line) for line in f]
        city = input('Which is the closest city to the place you are travelling to?' )
        city_id = False
  4. Il programma deve ora iterare su ogni dizionario della lista, e vedere se la città (city) selezionata dall'utente è presente nella lista:
    def get_city_id():
        with open('city.list.json') as f:
            data = [loads(line) for line in f]
        city = input('Which is the closest city to the place you are travelling to?' )
        city_id = False
        for item in data:
            if item['name'] == city:
                city_id = item['_id']
        return city_id
  5. Eseguite il codice, e poi passate alla shell per testarlo:
    get_city_id()
  6. Notate qualcosa di strano quando testate il codice con la città di Peterborough?
    >>> get_city_id()
    Which is the closest city to the place you are travelling to? Peterborough
    5091002
  7. L'ID 5091002 è diverso dall'ID 2640354 che avete trovato in precedenza! Com'è possibile? Potete modificare il vostro codice in modo tale che stampi il nome del paese (country), per eseguire e debuggare il codice:
    def get_city_id():
        with open('city.list.json') as f:
            data = [loads(line) for line in f]
        city = input('Which is the closest city to the place you are travelling to? ')
        city_id = False
        for item in data:
            if item['name'] == city:
                city_id = item['_id']
                print(item['country'])
        return city_id
    >>> get_city_id()
    Which is the closest city to the place you are travelling to? Peterborough
    GB
    CA
    AU
    AU
    US
    5091002
  8. Allora, c'è una Peterborough in Canada, due in Australia, e un'altra negli Stati Uniti, oltre a quella in Gran Bretagna. Il vostro codice dovrà gestire questo problema chiedendo all'utente se il country è corretto:
    def get_city_id():
        with open('city.list.json') as f:
            data = [loads(line) for line in f]
        city = input('Which is the closest city to the place you are travelling to?' )
        city_id = False
        for item in data:
            if item['name'] == city:
                answer = input('Is this in ' + item['country'])
                if answer == 'y':
                    city_id = item['_id']
                    break
        return city_id
  9. Se la città non è stata trovata, city_id sarà ancora False, per cui potete comunicare all'utente che la città non è stata trovata e uscire dal programma. La funzione aggiornata dovrà ora apparire così:
    def get_city_id():
        with open('city.list.json') as f:
            data = [loads(line) for line in f]
        city = input('Which is the closest city to the place you are travelling to?' )
        city_id = False
        for item in data:
            if item['name'] == city:
                answer = input('Is this in ' + item['country'])
                if answer == 'y':
                    city_id = item['_id']
                    break
    
        if not city_id:
            print('Sorry, that location is not available')
            exit()
    
        return city_id

Una previsione di 5 giorni nel browser

Ora che disponiamo del corretto ID, abbiamo tutto ciò che serve per ottenere il meteo relativo a quella città dalle API OpenWeatherMap. Possiamo ottenere i dati tramite una semplice richiesta web. La richiesta deve includere il city_id e la vostra chiave, collocate tra i caratteri # nell'esempio seguente.

http://api.openweathermap.org/data/2.5/forecast?id=######&APPID=################

Ad esempio, per ottenere le informazioni meteo relative alla città di Peterborough, potete copiare e incollare l'indirizzo web qui sotto nella barra degli indirizzi del vostro browser, sostituendo la chiave fake con la vostra chiave reale.

http://api.openweathermap.org/data/2.5/forecast?id=2640354&APPID=123456789abcdefghijklmnopqrstuvw

Dovreste ottenere qualcosa di simile all'output di figura 3.

Figura 3: Output di comando [Fonte: www.raspberrypi.org]

Figura 3: Output di comando [Fonte: www.raspberrypi.org]

Questi dati potrebbero sembrare un pò confusi, ma con l'aiuto di Python i dati stessi possono essere facilmente analizzati.

Una previsione di 5 giorni con Python

  1. Il metodo get che avete già importato dal modulo requests è tutto quanto serve per accedere ai dati dal web. Cominciamo a definire una nuova funzione che prende city_id come argomento:
    def get_weather_data(city_id):
  2. Utilizziamo poi la formattazione di stringhe per comporre l'URL:
    def get_weather_data(city_id):
        weather_data = get('http://api.openweathermap.org/data/2.5/forecast?id={}&APPID={}'.format(city_id, KEY))

    le parentesi graffe {} all'interno dell'URL vengono sostituite con quanto incluso tra parentesi dopo .format.

  3. Le informazioni che il programma scarica sono contenute in una lunga stringa. Possiamo tuttavia convertirla facilmente in JSON:
    def get_weather_data(city_id):
        weather_data = get('http://api.openweathermap.org/data/2.5/forecast?id={}&APPID={}'.format(city_id, KEY))

Osserviamo i dati

Per utilizzare i dati appena scaricati, occorre capire come sono strutturati.

  1. Salvate ed eseguite il codice, poi spostatevi nella shell e digitate:
    weather = get_weather_data('2640354')

    Questo otterrà i dati meteo di Peterborough.

  2. Per osservare il contenuto dei dati, potete digitare nella shell quanto segue:
    weather
  3. E' un dizionario abbastanza grande e piuttosto complesso da esaminare. Fortunatamente, avete importato il metodo pprint che renderà i dati maggiormente comprensibili:
    pprint(weather)

    questo è un esempio di come dovrebbe comparire le prime righe:

    >>> weather = get_weather_data("2640354")
    >>> pprint(weather)
    {'city': {'coord': {'lat': 52.573639, 'lon': -0.24777},
              'country': 'GB',
              'id': 2640354,
              'name': 'Peterborough',
              'population': 0,
              'sys': {'population': 0}},
     'cnt': 37,
     'cod': '200',
     'list': [{'clouds': {'all': 8},
               'dt': 1457697600,
               'dt_txt': '2016-03-11 12:00:00',
               'main': {'grnd_level': 1034.12,
                        'humidity': 100,
                        'pressure': 1034.12,
                        'sea_level': 1042.16,
                        'temp': 284.67,
                        'temp_kf': 4.43,
                        'temp_max': 284.67,
                        'temp_min': 280.241},
               'rain': {},
               'sys': {'pod': 'd'},
               'weather': [{'description': 'clear sky',
                            'icon': '02d',
                            'id': 800,
                            'main': 'Clear'}],
               'wind': {'deg': 145.004, 'speed': 2.47}},
  4. Il dizionario contiene alla linea 9 una chiave chiamata list. Potete osservare questa sezione del dizionario digitando quanto segue:
    pprint(weather['list'])
  5. L'output è ancora piuttosto grande, per cui osserviamo l'item di indice 0 della lista:
    pprint(weather['list'][0])
  6. Ora l'output dovrebbe essere leggermente più ridotto, qualcosa di simile al seguente:
    {'clouds': {'all': 8},
     'dt': 1457697600,
     'dt_txt': '2016-03-11 12:00:00',
     'main': {'grnd_level': 1034.12,
              'humidity': 100,
              'pressure': 1034.12,
              'sea_level': 1042.16,
              'temp': 284.67,
              'temp_kf': 4.43,
              'temp_max': 284.67,
              'temp_min': 280.241},
     'rain': {},
     'sys': {'pod': 'd'},
     'weather': [{'description': 'clear sky',
                  'icon': '02d',
                  'id': 800,
                  'main': 'Clear'}],
     'wind': {'deg': 145.004, 'speed': 2.47}}

    Per voi l'output effettivo sarà differente, in quanto state accedendo alle previsioni meteo in un'ora differente.

  7. Ciò che avete ora è un dizionario contenete le informazioni meteo. Questo dizionario contiene alcune chiavi, ma per ora la più importante è dt_txt. Potete osservarla digitando quanto segue:
    weather['list'][0]['dt_txt']
  8. Diamo ora un'occhiata all'elemento successivo della lista 'list':
    weather['list'][1]['dt_txt']
  9. Dovreste osservare come le due stringhe restituite contengono date e ore distanti tre ore l'una dall'altra. Questo è esattamente ciò che list contiene: una lista di informazioni meteo predette per 5 giorni, ognuna separata di tre ore dall'altra. Per sapere quindi quali vestiti l'utente dovrebbe indossare, occorrerà conoscere la data e l'orario di arrivo nella città.

Otteniamo data e ora di arrivo

La data e l'ora sono contenute nel file JSON nel formato YYYY-MM-DD HH:00:00, per cui occorre anzitutto chiedere all'utente in quale giorno e a quale ora intende arrivare nella città, e poi convertire i valori nel formato richiesto.

  1. Definiamo una nuova funzione:
    def get_arrival():
  2. Possiamo ora usare il metodo datetime importato per ottenere la data e l'ora corrente:
    def get_arrival():
        today = datetime.now()
  3. Affinchè l'utente possa scegliere una data, occorre presentare un range di date dal quale scegliere. Poichè si tratta di una previsione di 5 giorni, il range sarà compreso tra la data odierna e quella avanti di 4 giorni. Per ottenere la data avanti di 4 giorni, si può utilizzare il metodo timedelta:
        max_day = today + timedelta(days = 4)
  4. Ora, occorre sapere in quale giorno l'utente intende arrivare a destinazione, presentandogli le date da cui scegliere:
        print('What day of the month do you plan to arrive at your destination?')
        print(today.strftime('%d'), '-', max_day.strftime('%d'))
        day = input()
  5. La stessa cosa deve essere fatta per l'ora a cui l'utente intende arrivare a destinazione. Le previsioni sono una ogni 3 ore, a partire dall'ora 00:00:00. Per calcolare l'ora, possiamo usare l'operatore modulo (%), che restituisce il resto di una divisione:
        print('What hour do you plan to arrive?')
        print('0 - 24')
        hour = int(input())
        hour = hour - hour % 3
  6. Per completare, la data e l'ora di arrivo possono essere convertite nello stesso formato usato dal file JSON:
        arrival = today.strftime('%Y') + '-' + today.strftime('%m') + '-' + day + ' ' + str(hour) + ':00:00'
  7. Aggiungendo la data e l'ora di arrivo, la funzione completa dovrebbe apparire come segue:
    def get_arrival():
        today = datetime.now()
        max_day = today + timedelta(days = 4)
        print('What day of the month do you plan to arrive at your destination?')
        print(today.strftime('%d'), '-', max_day.strftime('%d'))
        day = input()
        print('What hour do you plan to arrive?')
        print('0 - 24')
        hour = int(input())
        hour = hour - hour % 3
        arrival = today.strftime('%Y') + '-' + today.strftime('%m') + '-' + day + ' ' + str(hour) + ':00:00'
        return arrival
  8. Testiamo la funzione lanciando il codice e digitando quanto segue nella shell:
    get_arrival()

Otteniamo la previsione

Ora che abbiamo la data e ora di arrivo, possiamo interrogare il dizionario per ottenere la previsione corretta.

  1. cominciamo a definire una nuova funzione che riceve gli argomenti weather_data e arrival:
    def get_forecast(arrival, weather_data):
  2. possiamo ora iterare sulla lista weather_data['list] per trovare l'elemento che ha la corretta ora di arrivo:
    def get_forecast(arrival, weather_data):
        for forecast in weather_data['list']:
            if forecast['dt_txt'] == arrival:
                return forecast

Testiamo il codice

Per testare il codice finora prodotto, salviamo ed eseguiamo il codice, e digitiamo poi le seguenti linee nella shell rispondendo alle domande:

city_id = get_city_id()
weather_data = get_weather_data(city_id)
arrival = get_arrival()
forecast = get_forecast(arrival, weather_data)
pprint(forecast)

Dovreste vedere sullo schermo le previsioni corrette per la città, insieme alla data e ora di arrivo.

Rendiamo la previsione leggibile

Nonostante sia stato utilizzato il metodo pretty print, il dizionario è ancora abbastanza confuso. Si potrebbe utilizzare la struttura dati così come è, ma la probabilità di commettere errori sarebbe abbastanza elevata. E' quindi meglio creare una nuova struttura dati, adatta a contenere solo i dati meteo strettamente necessari.

  1. Definiamo una nuova funzione che prende forecast come argomento e crea un dizionario vuoto per contenere i nuovi dati:
    def get_readable_forecast(forecast):
        weather = {}
  2. Il primo elemento che ci occorre è la nuvolosità ("cloudiness"). Questa è memorizzata in forecast['clouds']['all']:
        weather['cloudiness'] = forecast['clouds']['all']
  3. Poi ci occorre la temperatura; questa è memorizzata in forecast['main']['temp'], ma è una stringa. Dobbiamo fare una conversione di tipo da stringa a float:
       weather['temperature'] = float(forecast['main']['temp'])
  4. Stessa cosa per l'umidità, ma questa deve essere convertita in un intero:
        weather['humidity'] = int(forecast['main']['humidity'])
  5. Il dato successivo è la pioggia. Questa è un pò particolare; se in quel giorno non piove, il dizionario sarà vuoto, che potrebbe causare qualche problema al programma. Usando la selezione condizionale, possiamo controllare se il dizionario contiene la chiave '3h'. Se è così, possiamo utilizzare i dati. In caso contrario, possiamo impostare la piovosità a 0:
        if '3h' in forecast['rain']:
            weather['rain'] = float(forecast['rain']['3h'])
        else:
            weather['rain'] = 0.0
  6. Finiamo aggiungendo la descrizione e la velocità del vento:
        weather['description'] = forecast['weather'][0]['description']
        weather['wind'] = float(forecast['wind']['speed'])
  7. La funzione completa dovrebbe assomigliare a quanto segue:
    def get_readable_forecast(forecast):
        weather = {}
        weather['cloudiness'] = forecast['clouds']['all']
        weather['temperature'] = float(forecast['main']['temp'])
        weather['humidity'] = int(forecast['main']['humidity'])
        if '3h' in forecast['rain']:
            weather['rain'] = float(forecast['rain']['3h'])
        else:
            weather['rain'] = 0.0
        weather['description'] = forecast['weather'][0]['description']
        weather['wind'] = float(forecast['wind']['speed'])
        return weather

Test e analisi dei dati

Salvate ed eseguite nuovamente il codice, poi digitate quanto segue nella shell:

city_id = get_city_id()
weather_data = get_weather_data(city_id)
arrival = get_arrival()
forecast = get_forecast(arrival, weather_data)
weather = get_readable_forecast(forecast)
pprint(weather)

Dovreste ottenere qualcosa di simile:

{'cloudiness': 36,
 'description': 'scattered clouds',
 'humidity': 97,
 'rain': 0.0,
 'temperature': 283.99,
 'wind': 2.76}
  • cloudiness è la precentuale di cielo coperto (nuvolosità).
  • description è una breve descrizione del tempo.
  • humidity è la precentuale di umidità.
  • rain sono i mm di pioggia nelle ultime 3 ore.
  • temperature è la temperatura in gradi Kelvin.
  • wind è la velocità del vento in chilometri orari.

Scegliamo cosa indossare

Per completare il progetto, il programma suggerirà all'utente cosa indossare. La funzione conterrà numerose istruzioni condizionali, ma non dovrebbe richiedere troppe spiegazioni. Occorre solo precisare che i valori di piovosità sono stati divisi per 3 in modo tale da ottenere la piovosità oraria.

Se volete modificare i valori in modo tale da adeguarli alle vostre preferenze particolari su cosa indossare nelle varie condizioni meteorologiche, sentitevi liberi di farlo: potete essere creativi come e quando volete.

s(weather):
    print('The overall description for the weather at that time is {}'.format(weather['description']))
    if weather['cloudiness'] < 10:
        print('It should be sunny, so a hat or sunglasses might be needed')
    if weather['rain'] == 0:
        print("It's not going to rain, so no umbrella is needed")
    elif weather['rain']/3 < 2.5:
        print("There'll be light rain, so consider a hood or umbrella")
    elif weather['rain']/3 < 7.6:
        print("There'll be moderate rain, so an umbrella is probably needed")
    elif weather['rain']/3 < 50:
        print("There'll be heavy rain, so you'll need an umbrella and a waterproof top")
    elif weather['rain']/3 > 50:
        print("There'll be violent rain, so wear a life-jacket")
    if weather['temperature'] < 273:
        print("It's going to be freezing, so take a heavy coat")
    elif weather['temperature'] < 283:
        print("It's going to be cold, so a coat or thick jumper might be sensible")
    elif weather['temperature'] < 293:
        print("It's not too cold, but you might consider taking a light jumper")
    elif weather['temperature'] < 303:
        print("Shorts and T-shirt weather :)")
    if weather['wind'] > 30:
        print("There'll be wind, so a jacket might be useful")
    elif weather['wind'] > 10:
        print("There'll be a light breeze, so maybe long sleeves might be useful")
    else:
        print("The air will be quite calm, so no need to worry about wind")

Per finire

Ecco l'ultimissima funzione, che lega tutto assieme, e che può essere chiamata alla fine del vostro script:

def main():
    city_id = get_city_id()
    weather_data = get_weather_data(city_id)
    arrival = get_arrival()
    forecast = get_forecast(arrival, weather_data)
    weather = get_readable_forecast(forecast)
    get_clothes(weather)

main()

Provatela inserendo diversi valori di città e diversi valori di tempo.

Link

Link all'articolo originale: https://www.raspberrypi.org/learning/dress-for-the-weather/

 

Scarica subito una copia gratis

2 Commenti

  1. Avatar photo Maurizio 18 Giugno 2016
  2. Avatar photo Marco Rando 27 Marzo 2020

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend