After a lot of persuasion and, frankly, nagging from all of us, my wife has finally started to share her rather awesome recipes and household hints and tips on her own web site. As she has lots of readers from outside Canada she asked me to put together a feature on her site showing the current weather in Calgary, partly so that her readers can identify with her and partly to prove that it really does get as cold here as we tell people!
I looked around the web for a free weather site (she’ll probably upgrade when the site gets more visitors) and found one with decent reviews at APIXU (https://www.apixu.com/). They have some weather widgets but, like all of the other weather sites, none were quite the right size and none displayed quite what she wanted. The API looked quite good though, so I decided to give the site a try.
In the terms and conditions APIXU mention that they have a 10,000 calls per month limit for the free account. It sounds like a lot, but for a total number of page loads and refreshes it’s not that many, and it wouldn’t look too good if the total number of calls was exceeded during the month and then the site showed an empty box for the rest of the month. The obvious thing to do, at least to begin with, was to download the data periodically, cache it on the local disk, and then refer to that cached data from the website. I described this in my previous article Cache Third Party Data - It's Just Good Manners! so I set up a cron job to retrieve the data every 30 minutes.
The next thing to do was to write the code to turn the JSON weather data into HTML, with the following requirements:
- The JSON data must be loaded from a local directory.
- Each day’s weather details or forecast has an associated image, provided as a URI. The URIs are on the APIXU site but they also provide downloadable lists of weather condition descriptions and images at https://www.apixu.com/doc/credit.aspx.
– If we’re caching the weather we should probably cache the image and description data. That sounds like a couple more cron jobs.
- We don’t need to customize the condition descriptions yet, but we may want to later.
- The CMS is written in PHP, so the weather code should probably be in PHP too. It will be run every time a page is loaded, and as I mentioned in my previous article the Python version on my host is a little old.
The main problem with this list is the last item – I’d never actually written any PHP before. Still, with the usual software developer’s attitude of "how hard can it be" I dug through a couple of books and web sites and picked up enough PHP to do the following:
- Load the weather file from the JSON file.
- Convert the text into JSON data.
- Change the URIs for the weather condition images to point to the local drive.
- Create the HTML to display the current temperature and conditions, and the forecast for the rest of today, tomorrow and the day after.
Adding this to the CMS as a custom PHP script displayed the following:
There is actually a lot more weather data provided by the API – wind speed and direction, barometric pressure and more. Initial versions of the script displayed it all but then my client used the word dreaded by all UI developers - "cluttered". Now everything else has been moved to a tooltip:
I’ve cut off the forecast in both images as it goes on quite a way, but it follows the same style as the "Current" display.
So, for a few hours’ work and by taking the time to learn a little PHP, my wife’s site gets a new weather pane and I get to shamelessly steal, or rather re-use it for my own site. I think that’s a reasonable use of a couple of afternoons. If you'd like to see the PHP script that does this, it's at the end of this article.
If you’d like to see the code in action, as well as taking the chance to pick up some good recipes, take a look at the Calgary Cooking Mom site.
PHP Script To Display The Weather
<?php
$weather_json_path = "downloads/apis/weather/forecast_cgy.json";
$weather_icon_supplied_path_prefix = "//cdn.apixu.com/";
$weather_icon_local_path_prefix = "downloads/apis/weather/apixureference/";
// Read the weather data from the local file system.
if (!file_exists($weather_json_path)) {
echo "<p>The weather data file does not exist.</p>";
return;
}
$weather_json_file = fopen($weather_json_path, "r");
$weather_json = fread($weather_json_file, filesize($weather_json_path));
fclose($weather_json_file);
$weather = json_decode($weather_json);
// Local functions
function convert_web_path_to_local_path($supplied_path_prefix, $local_path_prefix, $supplied_path): string
{
return str_replace($supplied_path_prefix, $local_path_prefix, $supplied_path);
}
// Produce the HTML
// Set the styles for the local HTML - use a unique class for this area.
echo "<style>" .
".sp_weather {width: 100%; text-align: center;}" .
"p strong.sp_weather {font-size: 200%; width: 100%; text-align: center;}" .
"img.sp_weather {float: center;}" .
"</style>";
echo "<h4>Current:</h4>";
// Current weather
$weather_icon_local_path = convert_web_path_to_local_path($weather_icon_supplied_path_prefix,
$weather_icon_local_path_prefix,
$weather->current->condition->icon);
echo "<div class='sp_weather'>";
$current_tooltip = "Wind: " . $weather->current->wind_kph . "km/h / " . $weather->current->wind_mph . "mph " . $weather->current->wind_dir . "\n" .
"Precipitation: " . $weather->current->precip_mm . "mm / " . $weather->current->precip_in . "in\n" .
"Pressure: " . $weather->current->pressure_mb . "mb / " . $weather->current->pressure_in . "in\n" .
"Humidity: " . $weather->current->humidity . "%\n" .
"Cloud cover: " . $weather->current->cloud . "%";
echo "<p><figure><img src='" . $weather_icon_local_path .
"' title='" . $current_tooltip . "'>" .
"<figcaption>" . $weather->current->condition->text .
"</figcaption></figure></p>";
echo "<p class='sp_weather'><strong class='sp_weather'>" . $weather->current->temp_c . "C / " . $weather->current->temp_f . "F</strong></p>";
echo "<p class='sp_weather'>Feels like: " . $weather->current->feelslike_c . "C / " . $weather->current->feelslike_f . "F</p>";
echo "</div>";
// Forecast days
echo "<h4>Forecast:</h4>";
$forecast_date_format = "Y-m-d";
foreach ($weather->forecast->forecastday as $day) {
$weather_icon_local_path = convert_web_path_to_local_path($weather_icon_supplied_path_prefix,
$weather_icon_local_path_prefix,
$day->day->condition->icon);
$forecast_date = date_create_from_format($forecast_date_format, $day->date);
if (is_null($forecast_date)) {
echo "<p><strong>Date: '" . $day->date . "'</strong></p>";
} else {
echo "<p><strong>" . $forecast_date->format("D d M") . "</strong></p>";
}
echo "<div class='sp_weather'>";
$current_tooltip = "Temperature (ave): " . $day->day->avgtemp_c . "C / " . $day->day->avgtemp_f . "\n" .
"Wind (max): " . $day->day->maxwind_kph . "km/h / " . $day->day->maxwind_mph . "mph\n" .
"Precipitation: " . $day->day->totalprecip_mm . "mm / " . $day->day->totalprecip_in . "in\n" .
"Humidity: " . $day->day->avghumidity . "%";
echo "<p><figure><img src='" . $weather_icon_local_path .
"' title='" . $current_tooltip . "'>" .
"<figcaption>" . $day->day->condition->text .
"</figcaption></figure></p>";
echo "<p>Temperature (max): " . $day->day->maxtemp_c . "C / " . $day->day->maxtemp_f . "F</p>";
echo "<p>Temperature (min): " . $day->day->mintemp_c . "C / " . $day->day->mintemp_f . "F</p>";
echo "</div>";
}
$date_format = "Y-m-d H:i";
$date = date_create_from_format($date_format, $weather->current->last_updated);
if (is_null($date)) {
echo "<p><small>Last updated returned: '" . $weather->current->last_updated . "'</small></p>";
} else {
echo "<p><small>Last updated: " . $date->format("D d M H:i") . "</small></p>";
}
echo "<p><small>Weather data provided by <a href='https://www.apixu.com/'>APIXU</a>.</small></p>";
echo "<p><small>Weather code developed by <cite><a href='https://www.softwarepragmatism.com/'>Jason Ross at Software Pragmatism</a></cite>.</small></p>";
?>