Free Weather Forecast

I noticed on the National Weather Service’s website they now allow you to grab the forecast by REST, whereas before they only supported SOAP requests. This makes grabbing the forecast much simpler than it was before.

If you use SOAP there are many methods to grab the data, but if you use REST there are only 2. With REST you can either use DWMLgen which lets you get a little more specific information or NDFDgenByDay which is a little simpler and lets you pick either 12 hour or 24 hour increments (so you can get each day broken in half as in day and night or the full day). The response is sent back as XML for either method so you can format the data how you like.

For the example we are doing, we are going to keep it simple and use a single location, NDFDgenByDay and a 24 hourly period. We will also set it up so that we send the longitude and latitude for Austin, TX, the current date as the start date and request 7 days worth of data.

You’ll want to get the longitude of the location you want the forecast for. The simplest way is to use Google Maps and type in your city and state and search. Then put your cursor slightly below the green arrow, basically as soon as it is not linking to the arrow. Now double click and the map may shift slightly. Now click on the Link to this page text and you will get a box and copy whats in the first textfield. You’ll get a huge URL like the one below.

http://maps.google.com/maps?ie=UTF8&oe=utf-8&client=firefox-a&q=Austin,+TX,+USA&ll=30.270928,-97.742615&spn=0.432311,0.6427&z=11

What we want is the longitude and latitude. In that long URL you’ll see &ll= and after it will be the longitude and latitude separated by a comma. And in this case it is 30.270928,-97.742615.

So let’s make the request and write it to a file. Here is the PHP to do that.

<?php
//custom function to write a file
simple_write_file($filename, $data)
{
//write file
$fp = @fopen($filename, 'w'); //suppress error
if ($fp)
{
flock($fp, LOCK_EX);//lock file for writing
fwrite($fp, $data);
flock($fp, LOCK_UN);//release write lock
fclose($fp);
$output = '';
}
else
$output = 'Failed to open file.';
return $output;
}
$parameters = array('lat' => 30.270928, 'lon' => -97.742615, 'startDate' => gmdate('Y-m-d'), 'numDays' => 7, 'format' => urlencode('24 hourly'));
$data = file_get_contents('http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php?&lat='.$parameters['lat'].'&lon='.$parameters['lon'].'&format='.$parameters['format'].'&startDate='.$parameters['startDate'].'&numDays='.$parameters['numDays']);
simple_write_file('forecast.xml', $data);//NOTE: this will write the error to the file if it encounters one, but this is just for testing purposes
?>

The NWS recommends to make a new request 15 minutes after the hour and to cache the data for an hour. So you’ll want to write the code to cache it, but for this example we can just check the file and see if it’s good and then play with that file without having to make requests each time. Now here is some PHP code using SimpleXML to parse through the data in that file.

<?php
$output = '';
//php5
if (file_exists('forecast.xml'))
{
$today = date('l');
$today_index = -1;
$xmlstr = file_get_contents('forecast.xml');
$xml = simplexml_load_string($xmlstr);
$xml_weather = $xml->data->parameters;//shorthand
$count = count($xml_weather->temperature->value);
$output .= '<ul id="weather_forecast">';
for($x = 0; $x < $count; $x++)
{
$output .= '<li>';
//convert time to timestamp
$timestamp = strtotime($xml->data->{'time-layout'}->{'start-valid-time'}[$x]);
$day = date('l', $timestamp);
//get the day of the week
$output .= '<strong class="day_of_week">';
if ($today == $day)
{
$output .= 'Today';
$today_index = $x + 1;
}
else if ($today_index == $x)
$output .= 'Tomorrow';
else
$output .= $day;
$output .= '</strong>';
//store it for shorthand multiple use
$condition = $xml_weather->weather->{'weather-conditions'}[$x]->attributes()->{'weather-summary'};
//icon image
$output .= ' ';
$output .= '<img src="';
$output .= basename($xml_weather-/>{'conditions-icon'}->{'icon-link'}[$x]);//get the filename, we are going to host the icons on our server
$output .= '" alt="' . htmlentities($condition, ENT_QUOTES) . '" />';//alt text is the condition
//conditions
$output .= ' ';
$output .= '<strong class="condition">';
$output .= $condition;
$output .= '</strong>';
//NOTE: 7th day low will usually be null, so dont bother showing the high
if ($x < 6)
{
//high and low
$output .= ' ';
$output .= '<span class="temperature">';
$output .= '<span class="temperature_high">' . $xml_weather->temperature[0]->value[$x] . '&deg;</span>';//high
$output .= ' / ';
$output .= '<span class="temperature_low">' . $xml_weather->temperature[1]->value[$x] . '&deg;</span>';//low
$output .= '';
}
//NOTE: precipitation is broken into 12hr periods and the 7th day's evening precipitation will usually be null
$output .= '';
}
$output .= '</ul>';
$output .= '<p class="credits">Weather by <a href="' . $xml->head->source->credit . '">NOAA</a></p>';
}
echo $output;
?>

The benefits to the NWS service are: you can make a request for up to 200 locations in one call, you can get weather from the past or several days ahead and use it on a commercial site.

Now combine this code with code I did before on getting current conditions and you one step closer to building your own portal. As I mentioned before, you can’t claim to have copyright on the data, but it is public domain information.

Tags: , , , ,

Comments -49-0 of 14 to “Free Weather Forecast”

  1. -49 · not quite working says:

    I’ve been going through this code and am having a hell of a time getting it to work. I’ve found one missing ‘}’, but can’t seem to figure out where else it is breaking. Any chance there is something else missing?
    Cheers,
    Sam

  2. -48 · blogger says:

    I had a space in the opening php tag, “<? php” rather than “<?php“. I must of typed it in WordPress wrong after I HTML formatted my code, it’s fixed now.

  3. -47 · G says:

    I’m not sure how you can get multiple locations from the HTTP GET approach, I thought that was only available via soap? If it is possible can you provide an example of the url query string needed to do so? thanks

  4. -46 · blogger says:

    The only way to get multiple locations from the HTTP GET approach is to do multiple requests. Really all its going to do is call file_get_contents() multiple times with whatever URLs you need. Then you can store each result in its own file. I reccommend to have a CRON job setup to hit the page that does those multiple requests. Then the page that the user sees only deals with parsing through the files already cached.

  5. -45 · G says:

    Yea thats how I thought it was going to be.

    My other question is… I’m noticing at around 1600 PST, the current day is null… or at least the data is off somehow… using 24 hourly… I’m going to look at it a little more closely when it happens today.

    Also, instead of saving the data from ndfdBrowserClientByDay to its own xml file, what is the downside to parsing the data directly from nws source? I currently have it set up this way and the way you’ve demonstrated in this tutorial. It seems more object-oriented when dealing with multiple locations to pass the lat/long variables to the url and parse the data the same way everytime, rather than saving {x} number of xml files for each location… like if i had 50 locations, that’s 50 files that need to be cronned,cached and its a pain to add new locs, when i could just make a request directly to ndfdBrowserClientByDay and to add new locs i would just add the lat/lon to my array.

  6. -44 · G says:

    hmmm, this is messed up. There is a bug in the data association with the 24-hour format. I sent message to the webmaster at noaa.gov. I’ll past it here so you can see what I mean.

    ————————————————————–

    I am currently working with http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php

    The problem coming up is that after 1600 PST, the current day’s weather forecast is no longer available in the 24-hour format.
    This is understandable, however the bug is in the corresponding data to the dates.

    Lets say one uses the follow params: http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php?&lat=33.8303&lon=-116.544&format=24+hourly&startDate=2009-02-26&numDays=2

    After 1600 PST on the start day, the dates are changed to:

    k-p24h-n2-1
    2009-02-27T06:00:00-08:00
    2009-02-28T06:00:00-08:00
    2009-02-28T06:00:00-08:00
    2009-03-01T06:00:00-08:00

    This is reasonable. Two start and end times as requested, the dates adjusted because daytime data for the 26th is now no longer applicable (although based on a 12 hour cycle instead of the requested 24). However, the parameters do not match up with the time-layout and a nil value is provided instead of the data that would correctly match the dates…

    −
    Daily Maximum Temperature
    75

    So if my start date is the 26th in a 24 hour format, but instead I’m getting the 28th and 29th, then the data should correspond with those dates, instead a nil value is placed for a date that should have data available to it. Or if the 26th is nil then that should be the date the nil value corresponds to, not the 29th. Writing code to work with this requires defying predicate logic because the associative data cannot be quantified without accounting for associative error.

    Please let me know if this is correctable or something I must account for permanently or something logical that I’m failing to see.

  7. -43 · G says:

    hmmm, the xml tags pasted in with all of that were formatted… but im sure you get my drift. What do you think?

  8. -42 · G says:

    I had contacted the webmaster for MDL/NWS/NOAA about the issue in my previous comment and here’s the deal:

    The param ‘startDate’ => date(‘Y-m-d’)

    needs to be in GMT, so it should be changed to gmdate(‘Y-m-d’), otherwise the bug will show.

  9. -41 · blogger says:

    The NWS asks that you cache their data for 15minutes and all the APIs out there I’ve seen ask you to do this. That way you don’t make a request everytime someone visits the page. If noone cached data from APIs, all the servers out there would be under a much bigger load. In my example I kept the code that makes the request separate from parsing the data, so you could put them in separate PHP files. Also I you could probably do 10 or 15 requests in a batch (maybe more), so you wouldn’t need 50 CRON jobs. Another benefit is that if it takes awhile to grab the data (in case their servers or slow for some reason that day), the user doesn’t have to wait if you keep that process separate from the page they view.

    It can be a pain to manage all the files though, but you might make a database or another function that names the files dynamically.

  10. -40 · Jeff says:

    With a couple of tweaks I was able to make it work great.

    I’m having a problem with the line:
    $condition = $xml_weather->weather->{‘weather-conditions’}[$x]->attributes()->{‘weather-summary’};

    It won’t populate $condition with any of the “Partly Cloudy”, “Clear”, etc phrases. I checked the xml and they are in the weather-summary attribute.

    Any ideas?

  11. -39 · blogger says:

    It should work, I’m using that same line and it’s pulling it up.

  12. -38 · Glenn says:

    I am with Jeff – I keep getting errors with Simple_XML:

    PHP 5.29
    Simple_XML
    $Revision: 1.151.2.22.2.46 $

    Fatal error: Call to a member function attributes() on a non-object

    This does not seem to be the case with the following version:
    PHP 5.2.11
    Simple_XML
    $Revision: 272374 $

  13. -37 · Glenn says:

    So it may have been that the data just was not there. Turns out we were requesting data a full 2 weeks out and the service didn’t return a weather forecast. We also found a couple areas where the service failed to respond with a forecast for no reason. Added a test for null and it works fine once again.

    // Having already checked to see if the date was within 7 days.

    // Now just check to see if data exists.
    $v1 = $xml_weather->weather->{‘weather-conditions’}[$i];
    if (is_null($v1)) {
    // No data
    }

  14. -36 · blogger says:

    They have changed the structure of the XML files a bit. The code on this page is kind of old, so it may not reflect the latest changes they have made.

Leave a Comment

Comments are reviewed before publishing to prevent spam.