User:DukeEgr93/WeatherClock
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.
Contents
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
from matplotlib.collections import PatchCollection
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:
{'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
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')