It’s been a while since I’ve worked on a Chrome extension, and it’s high time I changed that. To that end, I have published WeatherTab, an extension which replaces the New Tab page with weather information for today, and a forecast for the following four days.

Install on the Chrome Store

I’ve enjoyed writing this extension, and barring any major breakthroughs, I think it won’t really need any more updates. (Now that I’ve said that, of course…)

About WeatherTab

Since I’m done adding functionality, I want to talk about what it took to get it to this point. There were a few things I wanted this extension to have: 1) a simple presentation, 2) weather description and matching iconography, and 3) a large image to match the current weather and approximate location, similar to Yahoo’s weather app.

unnamed
Yahoo Weather

When I started building the extension, I found a free to use weather API on Mashape. (I had found Mashape back when I was building my Twitter bot.) The API actually serves as an intermediary, and it outputs Yahoo Weather data. I’ve since switched to using Yahoo’s API directly, but I’ll get to that.

Material Design

Since this was for Chrome, I thought it would make sense to built the interface using Material Design. Once upon a time, I had used Material Design Lite, Google’s own web implementation, but that has now been discontinued in favor of Material Components for the Web. (Man, does Google suck at naming things.) As it seems to be in a fairly constant state of flux, I decided to use something that’s been around a little longer (and more stable): Materialize, a third-party CSS and JavaScript framework based on Material Design.

Side note: it strikes me as odd that Google took so long to put out something official (and they’re still making changes to it), since they’re the ones that introduced Material Design in the first place. But I digress.

Weather icons

I originally wanted to use graphics not unlike the colored icons Google displays in its own app, but I was unable to find any free resources with more than 20 icons. The Yahoo Weather API has a total of 49 status codes, and I didn’t want to reuse icons across multiple weather descriptions.

I happened to come across a fantastic, although unfortunately no longer maintained, icon font called Weather Icons. Not only are there over 200 unique weather icons (more than enough), but the author actually created aliases to match commonly used weather APIs’ status codes.

Getting weather data

As I mentioned, I was using a free third-party API which returned Yahoo Weather data. I decided to forego that in favor of using Yahoo’s own API.

Yahoo has a unified interface for accessing all their data, which they call YQL (it stands for Yahoo Query Language). The syntax is nearly identical to SQL (in which, as a full stack developer, I have plenty of experience), so it was pretty easy to pick up.

Strangely enough, you can’t get a weather forecast directly using latitude and longitude coordinates. Instead, you have to use the Where On Earth ID. Here is a sample query getting the woeid for latitude and longitude (substituting actual values with “1,1”).

SELECT woeid FROM geo.places WHERE text="(1,1)")

Nesting this inside a weather forecast query gets the weather data for a precise location:

SELECT * FROM weather.forecast WHERE woeid IN (SELECT woeid FROM geo.places WHERE text="(1,1)")

In terms of API usage, this amounts to two calls in order to get a single dataset. That’s not really a problem, though, because Yahoo’s public API allows for up to 2,000 requests per IP in a single day.

I also wanted to allow the user to toggle the temperature scale between Fahrenheit and Celsius. This can easily be done by specifying the value of “F” or “C” in the u parameter.

SELECT * FROM weather.forecast WHERE woeid IN (SELECT woeid FROM geo.places WHERE text="(1,1)") AND u="C"

In order to pass the YQL query into the API, you need to call the url https://query.yahooapis.com/v1/public/yql. The query needs to be encoded as the value of the q parameter, and if you want JSON data in the response (XML is the default), you need to include the parameter format=json. So, putting it all together:

https://query.yahooapis.com/v1/public/yql?q=SELECT%20*%20FROM%20weather.forecast%20WHERE%20woeid%20IN%20%28SELECT%20woeid%20FROM%20geo.places%20WHERE%20text%3D%22%281%2C1%29%22%29%20AND%20u%3D%22C%22&format=json

Put that into a GET request and the JSON response will contain all the weather data your little heart desires: high and low, ten-day forecast, humidity, and more. Since I didn’t feel like hand-coding a ton of HTML after receiving the response, I pass the data into a Handlebars template where it gets rendered.

Using template literals

Fun fact: in the past, I would have plugged in latitude and longitude values with string concatination, like so:

'SELECT woeid FROM geo.places WHERE text="(' + lat + ',' + long + ')"'

But with ES2015, we can now use template literals. Enclose a string’s value with backticks (`) instead of a single or double quote, and you can use variables inside a dollar sign and curly braces, like so:

`SELECT woeid FROM geo.places WHERE text="(${lat},${long})"`

As you might imagine, browser support is… lacking. If this were a web application where I needed to worry about Internet Explorer or other older browsers, and I simply insisted on using template literals, I would need to use a transpiler like Babel to convert my beautiful modern JavaScript into something that an older browser would understand.

Fortunately, this code lives inside an extension written for Chrome, I don’t need to resort to using a transpiler, and this JavaScript executes sans problème as-is.

Getting an image from Flickr

The Yahoo Weather app, per its own description, has “stunning Flickr photos” that “match your location, time of day, and current conditions.” I think I’ve got a pretty close approximation of that, but I don’t know exactly how Yahoo is doing it. My solution, therefore, is Good Enough™.

I have two primary Flickr search queries in YQL, falling back to the second if the first one fails: first by latitude and longitude, then by region (here in the good ol’ U.S. of A. that means by state). The general query syntax is the same:

`SELECT * FROM flickr.photos.search WHERE api_key="xxx" AND group_id="1463451@N25" AND has_geo="true" [ location stuff goes here ] AND tags="${weather},${timeOfDay},${season}" AND tag_mode="all"`

To use the Flickr API, you have to register for a key. It’s free, and since you can figure it out by looking at the source I guess it really doesn’t matter if I cut it out here or not. Whatever.

The group ID belongs to Project Weather, a group Yahoo themselves put together back in 2010. All photos in the group are available for public use and nearly all are geotagged, making finding photos by location relatively easy.

Instead of searching for text on the photos (like name, description, things like that), I am doing a tag search. Each photo in Project Weather is tagged extensively, so I can search for weather conditions (sunny, cloudy, etc.), the general time of day (morning, afternoon, evening, or night), and season (spring, summer, fall, or winter). I am also specifying in the search that all tags must match.

The first search includes all three tag types: weather, time, and season. If that fails, the search includes weather and season. If that fails, the search just looks for weather conditions before moving on to the second query type.

My two queries differ in the section above where it says location stuff goes here. The first query does a radial search, starting at latitude and longitude, with a search radius of up to 20 miles (the maximum the Flickr search API will allow).

lat="${lat}" AND lon="${lon}" AND radius="20" AND radius_units="mi"

The second query, which is run if no image is found for weather and precise location, does a broader search by region code. The region is included in the data returned by the weather API call, but I can’t use it directly. I have to use what the Yahoo API refers to as a Place ID, so I need to get that value from another data table. (Since this query is a separate API call, I need to pass in the Flickr API key in the subquery as well.)

place_id IN (SELECT place_id FROM flickr.places WHERE query="${search.region}" AND api_key="xxx")

There is one final fallback query, which is only run if all of the location-based queries fail to return a match. It simply does a search for weather conditions, without regard to location:

`SELECT * FROM flickr.photos.search WHERE api_key="xxx" AND group_id="1463451@N25" AND tags="${weather}"`

As a result, there is the potential to run a total of seven queries against the Flickr API, all in the hopes of finding a relevant photo for the user’s location, time of day, season, and weather conditions. (The Yahoo Weather app doesn’t mention season in its description of “stunning Flickr photos,” but it seems important to me. I don’t want to show a sunny winter picture in the middle of summer, after all.) Fortunately, this is all part of Yahoo’s public API and falls under the umbrella of being capped at 2,000 requests per IP per day. I don’t think the extension will ever hit that cap. (And now that I’ve said that…)

Displaying the image

Thanks to the speed of Yahoo’s API, weather data is returned (and rendered) almost immediately. Depending on how many queries need to be processed before getting a relevant photo, it may be a few seconds before a photo can be displayed. For that reason, I am not making the user wait for a photo before showing weather information. So this is what the user sees once the weather is displayed:

chrome-extension-cfbkenlngdjcpeogjkdcmgbmghahonfn-index.html-5

However, once the photo is ready, just inserting it into position is jarring. Instead, I animate its visibility, utilizing a Material Design-like transition.

This is accomplished with the CSS property clip-path, which doesn’t have the greatest browser support. Fortunately, it has enough support in Chrome that I can use it to reveal a simple shape (in this case, a circle).

By default, the element that contains the image (as a background) has a style of clip-path: circle(0%), essentially rendering it invisible. Instead of inserting the image directly, I preload it using this code:

$('<img />').attr('src', img).on('load', function() {
  $(this).remove();
  $('.weather-container__bg').css('background-image', img).addClass('open');
});

The image created here is never attached to the document body, so it is never rendered, but the source is downloaded. Once the load event fires (meaning the browser has downloaded and cached the image), I can remove the original image and attach the source as a background image to the real element. Since the image has already been downloaded, it’s immediately available. Then I add the open class to the element, which has a style of clip-path: circle(100%). This, combined with the transition defined on the element, animates the image into view.