You are on page 1of 19

Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

Projects

Dress for the Weather


A resource to use the OpenWeatherMap API

Python

Step 1 What you will make

In this resource you’re going to write a program that will allow a user to type in a city
they’re travelling to, and the date they’ll be arriving. The program will then use open
data (https://en.wikipedia.org/wiki/Open_data) from
OpenWeatherMap (http://openweathermap.org/api) to �nd out what the weather
is like in that city at that time, and advise the user on what they should wear when
they arrive.

What you will learn

By creating the Dress for the Weather project you will learn:

How weather data can be stored in text �les


How to manipulate JSON �les and objects
How to use Python to access open data APIs
How to manipulate dictionaries in Python

This resource covers elements from the following strands of the Raspberry Pi Digital
Making Curriculum (https://www.raspberrypi.org/curriculum/):

Apply abstraction and decomposition to solve more complex


problems (https://www.raspberrypi.org/curriculum/programming
/developer)

1 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

Step 2 What you will need

Just a Raspberry Pi with the latest Raspbian (https://www.raspberrypi.org


/downloads/) installed.

Step 3 Dress for the weather

In this resource you’re going to write a program that will allow a user to type in a city
they’re travelling to, and the date they’ll be arriving. The program will then use open
data (https://en.wikipedia.org/wiki/Open_data) to �nd out what the weather is
like in that city at that time, and advise the user on what they should wear when they
arrive.

Step 4 Getting it all ready

The �rst thing you’ll need to do is to get access to the weather forecast data. We can
get the data from a site called OpenWeatherMap (http://openweathermap.org
/api).

Navigate to OpenWeatherMap (http://home.openweathermap.org/users


/sign_up) to sign up for a free account:

Once signed in, you can see your API key on the
dashboard (http://home.openweathermap.org/):

2 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

You’ll need a place to save your �les in for this project. In your home directory,
create a new directory called dress-for-the-weather . You can do this with
the File Explorer, or by opening a terminal window and typing:

mkdir dress-for-the-weather

Now, open up a new Python 3 �le in your favourite editor; for instance, you can
navigate to Menu > Programming > Python3. Create a new �le (File > New
Window) and save this as weather.py in the new directory.

You’re going to need a few modules to complete this project, so you can import
them by writing the following lines of code at the top of your �le:

from requests import get


from datetime import datetime, timedelta
from json import loads
from pprint import pprint

Next, get your API key for OpenWeatherMap and declare it as a variable in your
program:

KEY = 'paste your key in here'

Step 5 Getting a city list

The OpenWeatherMap API requires you to use a city ID to identify the di�erent
cities all around the world. They provide a list of city IDs in the following linked
�le (http://bulk.openweathermap.org/sample/city.list.json.gz). Download the
3 of 19 �le and place it in the same directory as your code. 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

The �le is compressed, so if you want to read the data you’ll need to decompress
it. Open a terminal and go to the dress-for-the-weather directory:

cd dress-for-the-weather

Now check the �le is there by listing the directory’s contents:

ls

You should see the weather.py �le and a city.list.json.gz �le.

Now you can decompress the �le:

gunzip city.list.json.gz

If you list the directory contents again, you should see a new �le called
city.list.json . Don’t try and open the �le; it’s really big, and could crash
your computer.

Step 6 Understanding JSON

To get the city that the person is travelling to, you �rst need to understand the
contents of the �le you have just downloaded. You can explore the �le using the shell.

Save and run your weather.py �le ( ctrl+s and then F5 in IDLE). In the shell,
type the following command:

cities = open('city.list.json').read()

This has loaded all the lines of the �le into a huge string called cities .

To have a look at some of the items in the list, you can type the following in the
shell:

cities[0:130]

You should see something like this:

4 of 19 >>> cities[0:130] 11/25/18, 9:23 PM


Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

'[\n {\n "id": 707860,\n "name": "Hurzuf",\n "country":


"UA",\n "coord": {\n "lon": 34.283333,\n "lat":
44.549999\n '

This is the city Hurzuf in Ukraine (https://www.google.co.uk/maps/place


/Hurzuf/@44.5472927,34.2739755,14z
/data=!3m1!4b1!4m2!3m1!1s0x4094ca9c3582ba57:0xe2355b74466a46cc),
and you can see its id is 707860.

Step 7 Getting the city ID

Now that you understand the nature of the JSON �le, you can use it to �nd the
id of any city in there. You can write a new function to do this, so switch back
over to your weather.py �le and add the following:

def get_city_id():

The �rst thing to do is to load all of those json objects, convert each one to a
dictionary, and then add them all to a huge list called data :

def get_city_id():
with open('city.list.json') as f:
data = loads(f.read())

Next, you need to ask the user where they’re travelling to. Just in case their city is
not in the list, we’ll set a variable called city_id to False as well:

def get_city_id():
with open('city.list.json') as f:
data = loads(f.read())
city = input('Which is the closest city to the place you are
travelling to?' )
city_id = False

Now your program needs to iterate over every dictionary in that list, and see if the
city the user typed in is there:

def get_city_id():

5 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

with open('city.list.json') as f:
data = loads(f.read())
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

Run your code, and then move over into the shell to test it:

get_city_id()

Do you notice anything strange when you test the code with the city
Peterborough?

>>> get_city_id()
Which is the closest city to the place you are travelling to?
Peterborough
5091002

The ID 5091002 is di�erent to the ID 2640354 you found earlier! Why could this
be? You can alter your code so that it prints the country name, to try and debug
the code:

def get_city_id():
with open('city.list.json') as f:
data = loads(f.read())
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
6 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

GB
CA
AU
AU
US
5091002

So there’s a Peterborough in Canada, two in Australia, and another one in the


United States, as well as the one in Great Britain. Your code is going to need to
handle this problem by asking the user if the country is correct:

def get_city_id():
with open('city.list.json') as f:
data = loads(f.read())
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

If the city hasn’t been found then the city_id will still be set to False , so you
can tell the user their city wasn’t found and exit the program. The �nished
function should look like this:

def get_city_id():
with open('city.list.json') as f:
data = loads(f.read())
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:
7 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

print('Sorry, that location is not available')


exit()

return city_id

Step 8 Getting a 5-day forecast in your browser

Now that you can get the correct city ID, you have everything you need to get the
weather for that location from the OpenWeatherMap API. You can get the data using
a simple web request. The request you need to make must include the city_id and
your key, placed where the # symbols are in the example below.

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

For instance, to get the weather forecast for Peterborough, you could simply copy
and paste the web address below in the URL bar of your browser, replacing the fake
key at the end with your actual key.

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

You should get back something that looks like this:

This might look a little confusing, but with Python you can easily examine the data.

Step 9 Getting a 5-day forecast with Python

The get method that you have already imported from the requests module is
all you need to access data from the web. Start by de�ning a new function that
takes the city_id as an argument:

8 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

def get_weather_data(city_id):

Then you can use string formatting to compose the URL:

def get_weather_data(city_id):
weather_data = get('http://api.openweathermap.org/data/2.5
/forecast?id={}&APPID={}'.format(city_id, KEY))

Here the curly brackets {} within the URL are replaced with whatever is in the
brackets after .format .

The data that your program downloads is just a long string. You can convert it to
JSON easily enough, though, and return it:

def get_weather_data(city_id):
weather_data = get('http://api.openweathermap.org/data/2.5
/forecast?id={}&APPID={}'.format(city_id, KEY))
return weather_data.json()

Step 10 Looking at the data

To use the data you’ve just downloaded, you’ll need to understand its nature.

Save and run your code, then move into the shell and type the following:

weather = get_weather_data('2640354')

This get the weather data for Peterborough.

To look at the data, you can type the following into the shell:

weather

That’s quite a big dictionary and very confusing to look at. Fortunately, you’ve
imported the pretty print method called pprint that will help make more sense
of the data:

pprint(weather)
9 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

Here’s what the �rst few lines should look like:

>>> 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}},

The dictionary has a key in it called list on line 9. You can look at this section of
the dictionary by typing the following:

pprint(weather['list'])

That’s still pretty big, so have a look at the zero-th item in the list:

pprint(weather['list'][0])

That should be a little smaller, and look something like this:

10 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

{'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}}

It will be di�erent for you, as you are accessing the weather forecast at a di�erent
time.

What you have here is a dictionary containing weather data. This dictionary has a
few keys in it, but for now the most important one is dt_txt . You can look at this
by typing the following:

weather['list'][0]['dt_txt']

Now have a look at the next item in 'list' :

weather['list'][1]['dt_txt']

You should see that the two strings returned are dates and times that are three
hours apart. That is what list contains: a list of predicted weather data for 5
days, each three hours apart. So to know what the user should wear, you’re going
to need the time and date they’re getting to the city.

Step 11 Dress for the weather

11 of 19 In this continuation from the previous worksheet (worksheet.md), you will learn 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

how to customise the data from


OpenWeatherMap (http://openweathermap.org/api) to better suit your user’s
needs, and output what the user needs to wear.

Step 12 Getting the arrival date and time

The date and time from the JSON �le is in a speci�c format, YYYY-MM-DD
HH:00:00 , so you need to ask the user what day and hour they intend to arrive at
the city, and then convert it into this format.

Start by de�ning a new function:

def get_arrival():

Now you can use the datetime method you imported to get the current date
and time:

def get_arrival():
today = datetime.now()

To have the user choose a date, you need to give them a range of dates to choose
from. As this is a 5-day forecast, it will range from the date today up to the date in
4 days’ time. To get the date in 4 days’ time, you can use the timedelta
method:

max_day = today + timedelta(days = 4)

Next, you need to �nd out when the user plans on arriving at their destination,
and give them a choice of dates. The strftime method will let you print out
speci�c dates in a month:

print('What day of the month do you plan to arrive at your


destination?')
print(today.strftime('%d'), '-', max_day.strftime('%d'))
day = input()

Now the same needs to be done for the time the user is planning on arriving. The
forecasts are only once every 3 hours, starting at 00:00:00. To calculate the time
here, you can use the modulus ( % ) operator, which will get the remainder from a
12 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

division:

print('What hour do you plan to arrive?')


print('0 - 24')
hour = int(input())
hour = hour - hour % 3

To �nish o�, the date and time they are arriving can be converted to the same
format that’s used in the JSON �le:

arrival = today.strftime('%Y') + '-' + today.strftime('%m') + '-' +


day + ' ' + str(hour) + ':00:00'

With the arrival date and time returned, the complete function should look like
this:

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

Test the function by running the code and then typing the following into the shell:

get_arrival()

Step 13 Retrieving the forecast for the required date and time

Now that you have the date and time of arrival, you can query the dictionary for the
13 of 19 correct forecast. 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

Start by de�ning a new function that takes the weather_data and the
arrival as arguments:

def get_forecast(arrival, weather_data):

Now you can iterate over the weather_data['list] to �nd the entry that has
the correct arrival time:

def get_forecast(arrival, weather_data):


for forecast in weather_data['list']:
if forecast['dt_txt'] == arrival:
return forecast

Step 14 Testing so far

To test your code so far, save and run your code, then type the following lines into
the shell and answer the questions:

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

You should see the forecast for the correct city and the date/time displayed on your
screen.

Step 15 Making the forecast readable

Even with pretty print the dictionary looks pretty messy. You could use the data
structure as it is, but it would be fairly easy to make mistakes and introduce errors
into your code. You’re better o� trying to create a new data structure to hold just the
weather data you need.

De�ne a new function that takes forecast as an argument and create an empty
dictionary to hold the new data:
14 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

def get_readable_forecast(forecast):
weather = {}

The �rst item we want is the cloudiness. This is stored in forecast['clouds']


['all'] :

weather['cloudiness'] = forecast['clouds']['all']

Next, you want the temperature; it’s stored in forecast['main']['temp'] ,


but is a string. You need to type cast this to a �oat:

weather['temperature'] = float(forecast['main']['temp'])

The humidity is the same, but needs to be type cast to an integer as it is always a
whole number:

weather['humidity'] = int(forecast['main']['humidity'])

Next is the rain. This one is a little awkward; if there’s no rain that day, the
dictionary will be empty, which will cause you problems. Using conditional
selection, you can check if the dictionary contains the key '3h' . If it does, you
can use the data. If not, you can set the rain to 0 .

if '3h' in forecast['rain']:
weather['rain'] = float(forecast['rain']['3h'])
else:
weather['rain'] = 0.0

Finish o� by adding in the description and the wind speed:

weather['description'] = forecast['weather'][0]['description']
weather['wind'] = float(forecast['wind']['speed'])

Then return the newly created weather dictionary. The whole function should
look like this:

def get_readable_forecast(forecast):
weather = {}
weather['cloudiness'] = forecast['clouds']['all']
15 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

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

Step 16 Testing and understanding the data

Save and run the code again, then type the following into the 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)

You should get something like this:

{'cloudiness': 36,
'description': 'scattered clouds',
'humidity': 97,
'rain': 0.0,
'temperature': 283.99,
'wind': 2.76}

cloudiness is the % cloud cover.


description is a short description of the weather.
humidity is the % humidity.
rain is the mm of rainfall in the last 3 hours
temperature is the temperature in Kelvin. This is the same scale as Celsius but
with 273 added.
wind is the wind speed in kilometres per hour.

16 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

Step 17 Choosing what to wear

To �nish o�, your program will advise the user on what to wear. The function is going
to contain a lot of conditional selection, but shouldn’t need too much explaining. It’s
worth noting that the rain values have been divided by 3 to get the hourly rainfall.

If you want to change the values to suit your own particular feelings about what to
wear in di�erent conditions, then feel free - you can be as creative as you want.

def get_clothes(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:
17 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

print("The air will be quite calm, so no need to worry about


wind")

Step 18 Finishing o�

The very last function will tie all the others together, and can be called at the bottom
of your 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()

Test it out on di�erent locations and times.

Step 19 What next?

The OpenWeatherMap (http://openweathermap.org/api) also provides a 16-


day forecast. Check out the API and see if you can alter your program to allow for
16 days.

None of the data inputs have been validated. This means a user could easily type
in a city name and forget the capital letter at the start, or type YES instead of y
when checking if the city is correct. They might even enter numbers outside of
the range of acceptable dates! You can guarantee that if a user can break your
software, then they will. Why not alter your code to make it more robust?

Why not integrate your program with a little Minecraft code? You could then add
graphical elements to the program to display the di�erent weathers in Minecraft.

18 of 19 11/25/18, 9:23 PM
Dress for the Weather | Raspberry Pi Projects https://projects.raspberrypi.org/en/projects/dress-...

Published by Raspberry Pi Foundation (https://www.raspberrypi.org) under a Creative Commons


license (https://creativecommons.org/licenses/by-sa/4.0/).
View project & license on GitHub (https://github.com/RaspberryPiLearning/dress-for-the-weather)

19 of 19 11/25/18, 9:23 PM

You might also like