User:DukeEgr93/WeatherClock

From PrattWiki
Jump to navigation Jump to search

The Weather Clock is a project I decided to build after having a fun conversation with ENS Chris Coughlin about an art project he was doing involving controllable LEDs, IR communications, and arduinos. The thought that came to mind was to use a controllable LED ring to indicate weather conditions for the next 24 hours. I want to try to put two pieces of information on the ring, so I thought hue might be good for the temperature and either saturation or brightness or both for the chance of rain.

Preamble

For development purposes, I am writing everything using Spyder based on Python 3.6. The following are the lists of imports I have at the top of my code:

import json
from pprint import pprint
import pickle
import requests
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import colorsys

Getting Weather Data

There are several sites that provide APIs for getting weather data. Depending on your use case and tempo, you may have to pay. For this project, I don't anticipating updating the actual weather data more than once an hour, if even that often, so I usually come in free. I looked at both:

and ended up with Weather Underground for two reasons: it would provide an hourly forecast for free, and it accepted my API key first.

To get the data from the web site, I am using the Requests library. Here's all the code it takes to get an hourly forecast data strem from Weather Underground:

response = requests.get("http://api.wunderground.com/api/KEY_GOES_HERE/hourly/q/NC/Durham.json")

Note: I am not showing you my key. If you want one, you will need to get your own :) The data set that this returns has a massive amount of information - it returns hourly forecasts for the next 36 hours, and each hour has a data list that looks like the one presented below (if you click Expand).

{'FCTTIME': {'UTCDATE': '',
                                  'age': '',
                                  'ampm': 'AM',
                                  'civil': '12:00 AM',
                                  'epoch': '1498795200',
                                  'hour': '0',
                                  'hour_padded': '00',
                                  'isdst': '1',
                                  'mday': '30',
                                  'mday_padded': '30',
                                  'min': '00',
                                  'min_unpadded': '0',
                                  'mon': '6',
                                  'mon_abbrev': 'Jun',
                                  'mon_padded': '06',
                                  'month_name': 'June',
                                  'month_name_abbrev': 'Jun',
                                  'pretty': '12:00 AM EDT on June 30, 2017',
                                  'sec': '0',
                                  'tz': '',
                                  'weekday_name': 'Friday',
                                  'weekday_name_abbrev': 'Fri',
                                  'weekday_name_night': 'Friday Night',
                                  'weekday_name_night_unlang': 'Friday Night',
                                  'weekday_name_unlang': 'Friday',
                                  'yday': '180',
                                  'year': '2017'},
                      'condition': 'Clear',
                      'dewpoint': {'english': '63', 'metric': '17'},
                      'fctcode': '1',
                      'feelslike': {'english': '71', 'metric': '22'},
                      'heatindex': {'english': '-9999', 'metric': '-9999'},
                      'humidity': '77',
                      'icon': 'clear',
                      'icon_url': 'http://icons.wxug.com/i/c/k/nt_clear.gif',
                      'mslp': {'english': '30.15', 'metric': '1021'},
                      'pop': '4',
                      'qpf': {'english': '0.0', 'metric': '0'},
                      'sky': '28',
                      'snow': {'english': '0.0', 'metric': '0'},
                      'temp': {'english': '71', 'metric': '22'},
                      'uvi': '0',
                      'wdir': {'degrees': '188', 'dir': 'S'},
                      'windchill': {'english': '-9999', 'metric': '-9999'},
                      'wspd': {'english': '5', 'metric': '8'},
                      'wx': 'Mostly Clear'}

Saving and Using Weather Data

Since there are a limited number of calls per hour and day that you are allowed to do for free, during the development phase I thought it would be smart to save a data set and then use it rather than requesting a new one every time I ran the code. To do that, I used pickle:

import pickle
filehandler = open("WeatherData", 'wb')
pickle.dump(response, filehandler)

Note the wb part is important as the file needs to be opened for binary. Without it, the process did not work correctly. From that point on, if I wanted to load the variable, assuming pickle is already imported, I could put

filehandler = open("WeatherData", 'rb')
response = pickle.load(filehandler)

Regardless of which method I used to obtain data - requesting a new set or loading an old one, I included the line

data = response.json()

to pull the JavaScript Object Notation information out of the response object.

Storing Temperatures

This next code is going to horrify people who know Python. I am sure there is a better way to go about this. Regardless - here's how I pull 24 hours worth of time and temperature information out of the data set:

mytemps = []
mytempsnum = []
myhours = []
for i in range(0,24):
    print("Temperature ", i, ": ", data["hourly_forecast"][i]["temp"]["english"])
    mytemps.append(1)
    mytempsnum.append(1)
    myhours.append(1)
    mytemps[i] = data["hourly_forecast"][i]["temp"]["english"]
    mytempsnum[i] = int(mytemps[i])
    myhours[i] = i

This basically starts three empty arrays, then loops through 24 hours. Each loop, it will print out information about the (relative) hour and temperature, extend each array by one element, then place the relevant information in that newly created spot within the array. I can get rid of the append parts once I decide absolutely and for sure how much information will eventually be stored - I am just not sure at the moment if I am going to use 24 the whole time or not.

Graphing the Wedges

Ring produced from weather information starting at Noon of 28JUN2017 in Durham, NC. The temperature for the next hour of the day is straight up...not sure if it will stay that way

Since I don't have the light ring yet - and since I will need to model things first before interfacing with it once it does arrive - I wrote some code to generate the 24 light wedges. This code mainly uses the Polygon command. The general idea is I figured out the angles and radii for 200 coordinates of a patch that subtends 1/26th of a circle. I then use those base values and adjust the angles for each of the 24 different wedges - once I have those, I can use coordinate transformation to get the x and y coordinates for the wedge.

At the moment, I am picking colors using an HSV scheme. I decided the low temperature for the day would be displayed in dark blue and as the temperature warmed up, the colors would go through purple until finally the hottest temperature would be pure red. This leads to a range of hues that go from about 60% (pure blue) to 100% (pure red).

Here's the code that does all that:

theta1 = np.linspace(0, 2 * np.pi / 26, 100)
theta2 = np.linspace(2 * np.pi / 26, 0, 100)
thetar = np.concatenate((theta1, theta2), axis=0).reshape(200,1)

r1 = 0*theta1+1
r2 = 0*theta1+0.8
rr = np.concatenate((r1, r2), axis=0).reshape(200,1)

ax = plt.axes()
ax.cla()

for k in range(0,24):
    thetark = np.pi/2 - 2*np.pi/2/26 + thetar - 2*np.pi*(k/24)
    xr = rr*np.cos(thetark)
    yr = rr*np.sin(thetark)
    coords = np.concatenate((xr, yr), axis=1)
    MyH = 0.6+0.4*(mytempsnum[k]-min(mytempsnum))/(max(mytempsnum)-min(mytempsnum))
    ax.add_patch(Polygon(coords, closed=True, facecolor=colorsys.hsv_to_rgb(MyH, 1, 1)))

ax.axis('equal')

Codes

I know there's a much much better way to do this, but for the time being, this will serve as a simple way for me to keep track of evolutions of the code...

Version 0.1 - And so it begins!

This version includes code to get the weather information, extract and store what's necessary, and graph the ring with colors based on the temperatures. Note that you would need your own key to get it to work with Weather Underground. Click the Expand link to see the code.

# -*- coding: utf-8 -*-
"""
Created on Wed Jun 28 10:18:34 2017

@author: DukeEgr93
"""
import json
import pickle
import requests
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import colorsys

# Using new weather data
response = requests.get("http://api.wunderground.com/api/YOUR_CODE_HERE/hourly/q/NC/Durham.json")
print(response.status_code)

# Using a saved file
#filehandler = open("WeatherData", 'rb')
#response = pickle.load(filehandler)

data = response.json()

mytemps = []
mytempsnum = []
myhours = []
for i in range(0,24):
    print("Temperature ", i, ": ", data["hourly_forecast"][i]["temp"]["english"])
    mytemps.append(1)
    mytempsnum.append(1)
    myhours.append(1)
    mytemps[i] = data["hourly_forecast"][i]["temp"]["english"]
    mytempsnum[i] = int(mytemps[i])
    myhours[i] = i
    
theta1 = np.linspace(0, 2 * np.pi / 26, 100)
theta2 = np.linspace(2 * np.pi / 26, 0, 100)
thetar = np.concatenate((theta1, theta2), axis=0).reshape(200,1)

r1 = 0*theta1+1
r2 = 0*theta1+0.8
rr = np.concatenate((r1, r2), axis=0).reshape(200,1)

ax = plt.axes()
ax.cla()

for k in range(0,24):
    thetark = np.pi/2 - 2*np.pi/2/26 + thetar - 2*np.pi*(k/24)
    xr = rr*np.cos(thetark)
    yr = rr*np.sin(thetark)
    coords = np.concatenate((xr, yr), axis=1)
    MyH = 0.6+0.4*(mytempsnum[k]-min(mytempsnum))/(max(mytempsnum)-min(mytempsnum))
    ax.add_patch(Polygon(coords, closed=True, facecolor=colorsys.hsv_to_rgb(MyH, 1, 1)))

ax.axis('equal')