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