New weather script for GeekTool

Update 7/29/09
The current, version 0.2.1, has the bug fix described in this post, so there’s no need to edit it. Just install it as is using, and the weathertext script will work fine.

I like having GeekTool display the current weather on my desktop, and I’ve written a script that gets the information from a NOAA website and formats it for GeekTool. Now there’s a new Python weather library, called pywapi, that simplifies the process of getting the data and extends it beyond just NOAA’s weather stations. I’ve rewritten my GeekTool script to use this new library.

The pywapi library is the creation of Eugene Kaznacheev. It provides a simple Pythonic interface to weather data supplied by

Google doesn’t have a weather page, per se, but it does provide weather info through an API. The Gismeteo service is in Russian, so I can’t give any advice on using it.

The current version of pywapi, version 0.2.0, has a bug that causes it to fail when accessing NOAA data. Fortunately, this bug is easy to fix before installation. I’ve described the bug and provided my fix on the pywapi Issues page—perhaps the fix will be rolled into the official pywapi distribution soon. Until then, follow these instructions to get a working version of pywapi:

Download the tarball to a convenient location and extract the code. Open the file and scroll to the bottom of the get_weather_from_noaa function. Change this code

for tag in data_structure:
    weather_data[tag] = current_observation.getElementsByTagName(tag)[0]

to this

for tag in data_structure:
        weather_data[tag] = current_observation.getElementsByTagName(tag)[0]
    except IndexError:

The original code only works if NOAA is returning every bit of information in the data_structure tuple. As it turns out, sometimes NOAA stations don’t report wind gust, wind chill, or heat index values. In those situations, the original code fails when the get_weather_from_noaa function is called. The revised code succeeds regardless of whether the data are reported or not.

After saving the edited file, execute

python build
sudo python install

from the command line, give your administrator password at the prompt, and the pywapi library will be installed in /Library/Python/2.5/site-packages directory.

To see what kind of data are available from the different services, open the examples directory in the pywapi distribution and try them out. Here’s, modified to return values for my town rather than New York:

1:  import pywapi
2:  import pprint
3:  pp = pprint.PrettyPrinter(indent=4)
5:  result = pywapi.get_weather_from_google('60566')
6:  pp.pprint(result)

The modification is on Line 5, where I’ve changed the argument to my zip code. The output looks like this

{   'current_conditions': {   'condition': u'Clear',
                              'humidity': u'Humidity: 29%',
                              'icon': u'/ig/images/weather/sunny.gif',
                              'temp_c': u'32',
                              'temp_f': u'89',
                              'wind_condition': u'Wind: SW at 3 mph'},
    'forecast_information': {   'city': u'Naperville, IL',
                                'current_date_time': u'2009-06-27 21:37:00 +0000',
                                'forecast_date': u'2009-06-27',
                                'latitude_e6': u'',
                                'longitude_e6': u'',
                                'postal_code': u'60566',
                                'unit_system': u'US'},
    'forecasts': [   {   'condition': u'Mostly Sunny',
                         'day_of_week': u'Sat',
                         'high': u'90',
                         'icon': u'/ig/images/weather/mostly_sunny.gif',
                         'low': u'65'},
                     {   'condition': u'Clear',
                         'day_of_week': u'Sun',
                         'high': u'83',
                         'icon': u'/ig/images/weather/sunny.gif',
                         'low': u'61'},
                     {   'condition': u'Thunderstorm',
                         'day_of_week': u'Mon',
                         'high': u'74',
                         'icon': u'/ig/images/weather/thunderstorm.gif',
                         'low': u'58'},
                     {   'condition': u'Mostly Sunny',
                         'day_of_week': u'Tue',
                         'high': u'74',
                         'icon': u'/ig/images/weather/mostly_sunny.gif',
                         'low': u'54'}]}

It’s a multi-level dictionary, from which you can pluck the current conditions and the forecast for the next few days.

Similarly, if I modify to

1:  import pywapi
2:  import pprint
3:  pp = pprint.PrettyPrinter(indent=4)
5:  result = pywapi.get_weather_from_yahoo('60566', '')
6:  pp.pprint(result)

in which the arguments in Line 5 have been changed to my zip code and an empty string to give the Naperville results in US customary units. Running this example script yields

{   'astronomy': {'sunrise': u'5:20 am', 'sunset': u'8:32 pm'},
    'atmosphere': {   'humidity': u'40',
                      'pressure': u'29.78',
                      'rising': u'2',
                      'visibility': u'10'},
    'condition': {   'code': u'34',
                     'date': u'Sat, 27 Jun 2009 5:03 pm CDT',
                     'temp': u'90',
                     'text': u'Fair',
                     'title': u'Conditions for Naperville, IL at 5:03 pm CDT'},
    'forecasts': [   {   'code': u'47',
                         'date': u'27 Jun 2009',
                         'high': u'88',
                         'low': u'69',
                         'text': u'Scattered Thunderstorms'},
                     {   'code': u'24',
                         'date': u'28 Jun 2009',
                         'high': u'82',
                         'low': u'61',
                         'text': u'Sunny/Wind'}],
    'geo': {'lat': u'41.76', 'long': u'-88.15'},
    'html_description': u'\n<img src=""/><br />\n<b>Current Conditions:</b><br />\nFair, 90 F<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Scattered Thunderstorms. High: 88 Low: 69<br />\nSun - Sunny/Wind. High: 82 Low: 61<br />\n<br />\n<a href="*">Full Forecast at Yahoo! Weather</a><BR/>\n(provided by The Weather Channel)<br/>\n',
    'link': u'*',
    'location': {'city': u'Naperville', 'country': u'US', 'region': u'IL'},
    'title': u'Yahoo! Weather - Naperville, IL',
    'units': {   'distance': u'mi',
                 'pressure': u'in',
                 'speed': u'mph',
                 'temperature': u'F'},
    'wind': {'chill': u'90', 'direction': u'180', 'speed': u'14'}}

which is another multi-level dictionary with similar information. Yahoo! gives sunrise and sunset times, which Google doesn’t, but Yahoo’s forecasts only include today and tomorrow.

Here’s after my modification:

1:  import pywapi
2:  import pprint
3:  pp = pprint.PrettyPrinter(indent=4)
5:  result = pywapi.get_weather_from_noaa('KARR')
6:  pp.pprint(result)

The argument in Line 5 is the code for the NOAA station at the Aurora airport west of Naperville. Finding the best NOAA station isn’t as easy as just entering your zip code, but if you go to this NOAA page, you can find it pretty quickly.

The output from is

{   'dewpoint_c': u'20.6',
    'dewpoint_f': u'69.1',
    'dewpoint_string': u'69.1 F (20.6 C)',
    'latitude': u'41.77',
    'location': u'Aurora Municipal Airport, IL',
    'longitude': u'-88.47',
    'ob_url': u'',
    'observation_time': u'Last Updated on Jun 27 2009, 8:52 pm CDT',
    'observation_time_rfc822': u'Sat, 27 Jun 2009 20:52:00 -0500',
    'pressure_in': u'29.77',
    'pressure_mb': u'1007.6',
    'pressure_string': u'1007.6 mb',
    'relative_humidity': u'87',
    'station_id': u'KARR',
    'suggested_pickup': u'15 minutes after the hour',
    'suggested_pickup_period': u'60',
    'temp_c': u'22.8',
    'temp_f': u'73.0',
    'temperature_string': u'73.0 F (22.8 C)',
    'two_day_history_url': u'',
    'weather': u'Thunderstorm in Vicinity Light Rain Fog/Mist',
    'wind_degrees': u'220',
    'wind_dir': u'Southwest',
    'wind_gust_mph': u'18.4',
    'wind_mph': u'10.4',
    'wind_string': u'from the Southwest at 10.4 gusting to 18.4 MPH (9 gusting to 16 KT)'}

which is a simple dictionary with only current conditions, no forecasts. Still, I like the NOAA data set because it includes, when appropriate, the wind gust speed and heat index, items that neither Google nor Yahoo! provide.

So here’s my new GeekTool weather script, called weathertext:

 1:  #!/usr/bin/python
 3:  import pywapi
 5:  # Get the current conditions for the given station.
 6:  noaa = pywapi.get_weather_from_noaa('KARR')
 8:  # This is the list of output lines.
 9:  out = []
11:  # Go through the dictionary and construct a list of the desired output lines.
12:  out.append('Last update:' + noaa['observation_time'].split(',')[1])
13:  try:
14:    gust = ', gusting to %s mph' % noaa['wind_gust_mph']
15:  except KeyError:
16:    gust = ''
17:  out.append('Wind: %s at %s mph%s' % ( noaa['wind_dir'], noaa['wind_mph'], gust))
18:  out.append('Relative Humidity: %s%%' % noaa['relative_humidity'])
19:  try:
20:    out.append('Wind Chill: %s F' % noaa['windchill_f'])
21:  except KeyError:
22:    pass
23:  try:
24:    out.append('Heat Index: %s F' % noaa['heat_index_f'])
25:  except KeyError:
26:    pass
27:  out.append('Temperature: %s F' % noaa['temp_f'])
29:  # Sometimes there's no heat index or windchill value (there should never be
30:  # both). If that's the case, add a blank line to the beginning so the output
31:  # is always 5 lines.
33:  if len(out) < 5:
34:    out.insert(0, '')
36:  print '\n'.join(out)

At the moment, it’s basically a rewrite of my previous GeekTool weather script, using the clean pywapi calls instead of a kludgy series of regular expressions to pull the desired information out of the NOAA results. I’ll probably add to it, gathering bits from the Google or Yahoo! results and mixing them into the output. I have GeekTool set up to display the output of weathertext—which I’ve made executable and keep in a “bin” directory in my home folder—in the lower left corner of my screen.

I have the temperature at the bottom because it’s the most important item and putting it at the bottom makes it the most likely to be visible.

Update 7/2/09
I’ve made some minor changes to weathertext since writing this post. Rather than updating the post every time I make a change, I’ve set up a GitHub repository where you can always download my latest version.