Use MetPy to help answer your kid’s science questions

Being a good dad I try and answer my kids science questions, but sometimes it’s really tough.

There is an awesome Python library called MetPy that can help with some of those challenging science and weather questions.

In this blog I’d like to introduce the MetPy library and show how to use it to solve questions like:

  • Can they make snow when it’s above freezing ?
  • How much thinner is the air in places like Denver?
  • How can you figure out what the wind chill is?

Getting Started with MetPy

To install MetPy:

pip install metpy

One of nice things about MetPy is that it manages the scientific units, so variables  are defined with their units. Below is a Python example where the units module is used for a simple temperature conversion. The temperature today is defined as 40 degF. The to() method can be used to convert the temperature to degC.

>>>> from metpy.units import units
>>> tempToday = [40] * units.degF
>>> tempToday.to(units.degC)

Quantity([4.44444444], 'degree_Celsius')

You can also do some interesting mixing of units in math calculations, for example you can add 6 feet and 4 meters:

 >>> print( [6]*units.feet + [4] * units.m)
[19.123359580052494] foot

MetPy has a very large selection of thermodynamic and weather related functions. In the next sections I will show how to use some of the these functions.

How can they make snow when it’s above freezing ?

Ski resort can make snow by forcing water and pressurized air through a “snow gun”. Making snow can be an expensive operation but it allows ski resorts to extend their season.

snowgun

When you get a weather forecast the temperature is given as the ambient or dry bulb temperature. The wet bulb temperature takes the dry air temperature and relative humidity into account. The wet bulb temperature is always below the outside temperature. To start  making snow  a wet bulb temperature of -2.5°C or 27.5°F is required.

Metpy has a number of functions that can used to find humidity and wet bulb temperatures.  The wet_bulb_temperature function will find the wet bulb temperature using the pressure, dry temperature and dew point.

Below is an example where the temperature is below freezing, but it’s not possible to make snow because the wet bulb temperature is only -0.6°C  (not the required  -2.5°C).

>>> import metpy.calc
>>> 
>>> pressure = [101] * units.kPa
>>> temperature = [0.5] * units.degC
>>> dewpoint = [-2.5] * units.degC
>>> 
>>> metpy.calc.wet_bulb_temperature(pressure, temperature, dewpoint)

Quantity(-0.6491265444587265, 'degree_Celsius')

Knowing that -2.5°C (27.5°F) is the wet bulb temperature upper limit for snow making, the relative_humidity_wet_psychrometric function can be used to create a curve of humidity and dry temperature points where it is possible to make snow.

The code below iterates between -10 and 10 deg °C getting humidity values at a wet bulb temperature of -2.5°C.


#
# Find when you can make snow
#
import matplotlib.pyplot as plt
import metpy.calc
from metpy.units import units

print("Get temps vs. humidity")
print("-------------------")

plt_temp = []
plt_hum = []
for temp in range (-10,11): # Check dry temperatures between -10 - 10 C
    dry_temp = [temp] * units.degC
# Get the relative humidity
    rel_humid = metpy.calc.relative_humidity_wet_psychrometric(dry_temp, wet_temp,pres)
# Strip the humidity units for charting, and make a percent (0-100)
    the_humid = rel_humid.to_tuple()[0] * 100
    if (the_humid  0) : # Get valid points
        plt_temp.append(temp) # append a valid temp
        plt_hum.append(the_humid) # append a valid humidity
    print (temp, the_humid)

fig, ax = plt.subplots()
ax.plot(plt_temp, plt_hum )
ax.set(xlabel='Temperature (C)', ylabel='Humidity (%)',
title='When you can make Snow')
ax.grid()
#fig.savefig("makesnow.png")
plt.show()

makesnow

From the data we can see that it is possible to make snow when the temperature is above freezing and the humidity is low.

How much thinner is the air … in Denver?

We all know that the air is thinner when we’re up in an airplane, but how much thinner is it in Denver or  Mexico City compared to New York City ?

Using the height_to_pressure_std function it is possible to get a pressure value based on altitude. The to() method can be used to convert the pressure to standard atmospheres.

>>> import metpy.calc
>>> 
>>> New_York_alt = [33]*units.ft
>>> metpy.calc.height_to_pressure_std(New_York_alt).to(units.atm)
Quantity([0.99880745], 'standard_atmosphere')

>>> Denver_alt = [5280] * units.ft
>>> metpy.calc.height_to_pressure_std(Denver_alt).to(units.atm)
Quantity([0.82328412], 'standard_atmosphere')

>>> Mexico_city_alt = [7350]*units.ft
>>> metpy.calc.height_to_pressure_std(Mexico_city_alt).to(units.atm)
Quantity([0.76132418], 'standard_atmosphere')

Relative to New York City the air is about 18% thinner in Denver and 24% thinner in Mexico City.

Using the height_to_pressure_std function it is possible to create a chart of atmospheric pressure between sea level and the top of Mt. Everest (29,000 ft). At the top of Mt. Everest the air is 70% thinner than at sea level !!


#
# How much does the air thin as you climb ?
#
import matplotlib.pyplot as plt
import metpy.calc
from metpy.units import units
print("Get height vs. Atm Pressure")
print("---------------------------")
# create some plot variables
plt_ht = []
plt_press = []

# Check Atmospheric Pressure from sea level to Mt. Everest (29,000 ft) heights
for temp in range (0,30000,1000): # Check dry temperatures between -10 - 10 C
    height = [temp] * units.feet
    pressure = metpy.calc.height_to_pressure_std(height)
    atm = pressure.to(units.atm)
    print (height, atm)
    plt_ht.append (height.to_tuple()[0]) # put the value into plt list
    plt_press.append (atm.to_tuple()[0]) # put the value into plt list

fig, ax = plt.subplots()
ax.plot(plt_ht, plt_press )
ax.set(xlabel='Mountain Height (ft)', ylabel='Pressure (atm)',
title='How does the Pressure change Mountain Climbing?')
ax.grid()
fig.savefig("height_vs_press.png")
plt.show()

height_vs_press

How can I figure out the Wind Chill ?

The MetPy windchill function will return a “feels like” temperature based on a wind speed and ambient temperature. For example an outside ambient temperature of 40 deg °F with a wind of 20 mph feel like 28 deg °F.

>>> import metpy.calc
>>>
>>> temp = [40] * units.degF
>>> wind = [20] * units.mph
>>> metpy.calc.windchill(temp, wind, face_level_winds=True)
Quantity([28.42928573], 'degree_Fahrenheit')

When the temperature starts going below -20 °C parents should be keep a close eye on their kids for frost bite. Below is a code example that shows a curve of -20 °C based on ambient temperature and wind.


#
# Wind Chill
#
import matplotlib.pyplot as plt
import metpy.calc
from metpy.units import units

# Create some plotting variables
plt_temp = []
plt_speed = []

for temp in range (0,-46,-1): # Check dry temperatures between -10 - 10 C
    the_temp = [temp] * units.degC
    for wind in range(1,61,1):
        the_wind = [wind] * units.kph
        windchill =
        metpy.calc.windchill(the_temp,the_wind,face_level_winds=True)
# Select points with a wind chill around -20
        if (windchill.to_tuple()[0]) = -20.1) :
            plt_temp.append(temp)
            plt_speed.append(wind)

fig, ax = plt.subplots()
ax.fill_between(plt_temp, plt_speed, label="-20 C - Wind Chill", color="red" )

ax.set(xlabel='Temperature (C)', ylabel='Wind Speed (kph)',
title='When is the Wind Chill -20C ?')
ax.grid()
fig.savefig("windchill.png")
plt.show()

windchill

Summary

MetPy didn’t solve all my kids questions but the Metpy library is an excellent for science questions around water and weather.

If you have a budding chemist or chemical engineer in your house try taking a look at the Python Thermo library.

Pi Appliance

My goal was to make a Pi kitchen appliance that shows me the key things that I want to see and music I want to listen to while I’m getting my morning coffee. For this project I used a Rasp Pi with 2.8″ TFT touchscreen. These screens start at a round $15.

People’s morning interests will vary so in this blog I just wanted to highlight some of the issues that I needed to worked through. For me the main stumbling blocks were:

  • Hiding the top Rasp Pi menu bar
  • Creating a GUI that uses the full screen
  • Getting weather data
  • scraping web pages to extract what I need

Getting Started

There are some great Raspberry Pi TFT screens that come with buttons and cases. You will need to look at the documentation that comes with your screen, but a good reference is: https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pitft_case

For my project I simply used some of my kids Lego blocks.

pi_kitch2

Remove the Pi Menu Bar

The Pi TFT screen isn’t super large, so I wanted to remove the Pi menu bar and run my application at full size.

tft_w_menu

To remove the menu bar tweek two files. First:

sudo nano /etc/xdg/lxsession/LXDE-pi/autostart

Comment out the line  (with #) :

@lxpanel --profile LXDE

Then do the same for:

nano /home/pi/.config/lxsession/LXDE-pi/autostart

After this reboot the system.

Create a Full Size App

There are a multitude of choices for a screen layout. I was looking for lines of text, with maybe the bottom line used for buttons. I found that 7 lines was a reasonable fit. To remove the Python Tkinter title I positioned the top of the screen above the physical screen position (-30 instead of 0).


# My Kitchen Appliance App
#
import urllib.request as urllib2
import tkinter as Tkinter
from tkinter.ttk import *

from tkinter.font import Font
from tkinter import messagebox
top = Tkinter.Tk()
top.title("My Kitchen Appliance")
top.geometry("320x240+-5+-30") # set screen size, left (-5) and top (-30)
top.resizable(False, False)
top.details_expanded = False

#Define the buttons
myfont = Font(family="Times New Roman Bold",size= 12) # Should try a few more sizes

tft_rows = 7 # try 7 rows of buttons
tftbutton = ['' for i in range(tft_rows)]
for i in range(tft_rows):
    tftbutton[i] = Tkinter.Button(top, text = "Line " + str(i+1), fg = "blue", bg = "white", anchor="w", width= 35, height= 1,font=myfont).grid(row=(i+1),column=1) # a buttpn arra

top.mainloop()

The Python GUI will look like this:

tft_7bttns

Get Weather Data

There are a number of good weather API’s. I used OpenWeather because I can use it in variety of apps like Node-Red. OpenWeather has a free user API but you should login and get an appid.

A Python example to get some current weather data for a city:


# get Open Weather (REST API) data
import requests

# api-endpoint

URL = "https://openweathermap.org/data/2.5/weather?q="
mycity = "burlington,CA"
myappid = "&appid=b6907d289e10d714a6e88b30761fae22"
# sending get request and saving the response as response object
fullURL = URL + mycity + myappid
r = requests.get(fullURL)

# extracting data in json format
data = r.json()

print (data)

# Check out the structure
#for index, value in enumerate(data):
# print(index, value)

# Show some weather data
print (data['weather'][0]['description'])
print (data['weather'][0]['main'])
print (str(int(data['main']['temp'])) + " C")
# convert wind speed from meters/sec to kph
print (str((data['wind']['speed'] * 3.6)) + " kph")

This code will give output such as:

$Python3 burlweather.py
{'coord': {'lon': -79.8, 'lat': 43.32}, 'weather': [{'id': 803, 'main': 
'Clouds', 'description': 'broken clouds', 'icon': '04n'}], 'base': 
'stations', 'main': {'temp': 5.81, 'pressure': 1014, 'humidity': 93, 
'temp_min': 3.33, 'temp_max': 7.78}, 'visibility': 24140, 'wind': 
{'speed': 2.1, 'deg': 50}, 'clouds': {'all': 75}, 'dt': 1574816701,
 'sys': {'type': 1, 'id': 818, 'country': 'CA', 'sunrise': 1574771158, 
'sunset': 1574804839}, 'timezone': -18000, 'id': 5911592, 'name': 'Burlington', 'cod': 200}
broken clouds
Clouds
5 C
7 kph

Scraping Web Pages

I wasn’t able to find an API for all the things I was after, so I need to scrape web pages. The Python Beautiful Soup library is a great for finding and grabbing stuff on web pages. To install it:

$ apt-get install python-bs4 (for Python 2)

$ apt-get install python3-bs4 (for Python 3)

I had an example where I wanted to find the ski lifts and runs open. I had the Web page but I needed to search the ugly HTML code.

ski_bs0

ski_bs

In the HTML code I found that the lift and run information is contained in a <p class=“open value” tag. Beautiful Soup allows you to make searches based on attributes. The results can be HTML code or the .text property will return the results as simple text (no HTML code).

The following Python code would search my URL and extract the number of lifts open:


$ python
Python 3.7.4
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib.request as urllib2
>>> from bs4 import BeautifulSoup
>>> theurl = 'https://www.onthesnow.ca/ontario/blue-mountain/skireport.html'
>>> page = urllib2.urlopen(theurl)
>>> soup = BeautifulSoup(page, 'html.parser')
>>> liftopen = soup.find("p", attrs={"class":"open value"})
>>> liftopen.text
'2 of 11'

Final Comments

There are a ton of different “Pi Appliance” applications that could be done. I hope that some of these hints that I’ve documented are helpful.

pi_kitch1