Select your language

https://pixabay.com/de/users/clker-free-vector-images-3736/

Everybody talks about the weather. One reason more to keep track of it. The free OpenWeatherMap API and Kivy make it easy to make simple and clean App

Weather API

First, we need to get the weather data from which we build our app. A free and reliable source is the API from OpenWeaterMap. Private users get current weather data and forecasts for free. Only thing is that you need to register. You will receive an APPID which you need to authenticate your requests.

Now we are good to go.

Get the current weather

Before the actual programming starts, let's take a look at the data we are going to deal with. The official exaple on the OpenWeatherMap homepage shows you the current weather of a random city. Simply go to your browser and test it.

What is that request doing in detail. The first part of the data tells the server that you are requesting weather data.

api.openweathermap.org/data/2.5/weather?

The next part represents the city that your want to know the weather of. It is represented by a uniquie id

id=2172797

At the end you hand over your APPID

&appid=b6907d289e10d714a6e88b30761fae22

The request obviously returns a JSON dataset filled with a lot of information. It starts with the coordinates of the city, shows you the air pressuer, temperature etc. A detailed description of the contents can be found in the API specification.

Weather Forecast

Now we take the next example and check what comes back if we request the forecast data. It seemsl like we have data for the next 5 days in 3 hour steps.

The API call is almost similar only weather? isr replaced with 

forecast?

and we get a JSON file in return as well.

The GUI

The GUI will be fairly simple and follows the sketch below.

This here is the kv file derived from the sketch.

<MyRootLayout>:
    orientation: 'vertical'
    on_touch_down:
        self.ids.wdgt1.download_data()
        self.ids.wdgt1.show_current_weather()
        self.ids.wdgt1.show_forecast()
    WeatherWidget:
        id: wdgt1
        
<WeatherWidget>:
    Label:
        id: lbl1
        text: root.city + ' ' + root.country
        font_size: 50
        size_hint: 1, .25
        pos_hint: {'top':1, 'left':1}
    AsyncImage:
        id: img1
        source: root.image_source
        allow_stretch: True
        keep_ratio: True
        size_hint: .25, .25
        pos_hint: {'top':.75, 'left':1}
    Label:
        id: lbl2
        text: root.temperature + u' \xb0C'
        font_size: 50
        size_hint: .25, .25
        pos_hint: {'top':.75, 'x':.25}
    Label:
        id: lbl3
        text: root.humidity + ' %rH'
        font_size: 25
        size_hint: .25, .125
        pos_hint: {'top':.75, 'x':.5}
    Label:
        id: lbl4
        text: root.pressure + ' hPa'
        font_size: 25
        size_hint: .25, .125
        pos_hint: {'top':.75, 'x':.75}
    Label:
        id: lbl5
        text: root.wind_speed + ' m/s'
        font_size: 25
        size_hint: .25, .125
        pos_hint: {'top':.625, 'x':.5}
    Label:
        id: lbl6
        text: root.wind_direction + u' \xb0'
        font_size: 25
        size_hint: .25, .125
        pos_hint: {'top':.625, 'x':.75}
    ScrollView:
        size_hint: 1, .5
        pos_hint: {'top': .5, 'left': 1}
        id: scroll
        GridLayout:
            id: grid
            size_hint: 1, None
            height: self.minimum_height
            cols: 3
            row_force_default: True
            row_default_height: 40

Similar to the simple Widget for Temperature and Humidity, are all variables for the visualization bound by name binding and get updated automatically. The pyhton file is here.

# encoding: utf-8

from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, ObjectProperty, StringProperty
from kivy.uix.image import AsyncImage
from kivy.uix.label import Label
import urllib.request
import json
from time import gmtime, strftime

class WeatherWidget(RelativeLayout):

    city = StringProperty('--')
    country = StringProperty('--')
    temperature = StringProperty('--')
    humidity = StringProperty('--')
    pressure = StringProperty('--')
    image = StringProperty('--')
    wind_speed = StringProperty('--')
    wind_direction = StringProperty('--')
    last_update = StringProperty('--')
    notification = StringProperty('')
    image_source = StringProperty('')
    
    def get_http_to_json(self, url, file_name, *args, **kwargs):
        
        #open the url and decode the json data
        with urllib.request.urlopen(url) as f:
            data = json.loads(f.read().decode('utf-8'))

            #build the file name to save the file
            my_filename = file_name + '.json'
                
            #save the file
            with open(my_filename, 'w') as write_file:
                json.dump(data, write_file)

    def download_data(self, *args, **kwargs):
        '''download_data gets the weather data from openweathermap.org and writes it to
           json file'''
        
        now_url = 'http://api.openweathermap.org/data/2.5/weather?q=Nassau,bsde&APPID=YOURAPPIDCOMESHERE' 
        forecast_url = 'http://api.openweathermap.org/data/2.5/forecast?q=Nassau,bs&APPID=YOURAPPIDCOMESHERE'

        self.get_http_to_json(now_url, 'now')
        self.get_http_to_json(forecast_url, 'forecast')


    def show_current_weather(self, *args, **kwargs):

        #Read and store data for current weather
        try:          
            with open('now.json', 'r') as read_file:
                data=json.load(read_file)

            self.city = data.get('name','nn')
            self.country = data.get('sys').get('country','nn')
            self.temperature = str(data.get('main').get('temp'))
            self.humidity = str(data.get('main').get('humidity','nn'))
            self.pressure = str(data.get('main').get('pressure','nn'))
            self.image_source = 'http://openweathermap.org/img/w/' + data['weather'][0]['icon'] + '.png'
            self.wind_speed = str(data.get('wind').get('speed','nn'))
            self.wind_direction = str(data.get('wind').get('deg','nn'))
            self.last_update = str(gmtime(data.get('dt')))

        #if the file does not exist print an error message
        except FileNotFoundError as e:
            print(e)
            self.notification = 'Keine Wetterdaten vorhanden' 

        #Read and store data for forecast
        try:
            with open('forecast.json', 'r') as read_file:
                data=json.load(read_file)
                self.forecast=data

        #if the file does not exist print an error message
        except FileNotFoundError as e:
            print(e)
            self.notification = 'Keine Wetterdaten vorhanden'

    
    def show_forecast(self, *args, **kwargs):

            #first remove previous forecast 
            self.ids['grid'].clear_widgets()

            #add latest info
            for count in self.forecast['list']:

                #the time is formatted using strftime to display only the days name and hh:mm 
                self.ids['grid'].add_widget(Label(text=strftime('%a %H:%M',gmtime(count['dt'])), font_size='30sp'))
                #temperature will be rounded to two decimals
                self.ids['grid'].add_widget(Label(text='{:.0f}'.format(count['main']['temp'] - 273.15) + '°C', font_size='30sp'))
                self.ids['grid'].add_widget(AsyncImage(source='http://openweathermap.org/img/w/' + count['weather'][0]['icon'] + '.png'))

class MyRootLayout(BoxLayout):
    pass

class MyVisuApp(App):
    def build(self):
        return MyRootLayout()

if __name__ == '__main__':
    MyVisuApp().run()

The result

And here is the result.

 Source code on GitHub

You can find the latest code on GitHub