Parse Weather Forecast Data (from the NDFD) in PHP

Published: February 19th, 2009 by:

In a previous article, I covered 5 Sources of Free Weather Data for your Site, but did not provide any actual code to use the data. Starting with this article, I will post instructions on how to handle this data as well as sample code. For this article, we will start with the National Digital Forecast Database (NDFD) Simple Object Access Protocol (SOAP) Web Service.


Where to Find the Data

See weather.gov/xml for full details and available data. We will be using the 24 Hourly format, which gives us temperatures, cloud cover %, weather type (rain, snow, etc), hazardous conditions, and even weather icons.

Code to Fetch the Data

Below is some sample code that will fetch the data. The lat/lon coordinates need to be changed for your desired location on lines 10-11. Currently, it is set for Chicago, IL.

<?php

/* http://sourceforge.net/projects/nusoap/ */
require('../includes/nusoap/nusoap.php');

$parameters = array('product'	=> 'glance',
					'numDays'   => 5,
					'format'    => '24 hourly',
					'latitude'  => 41.879535,
					'longitude'	=> -87.624333);

try
{
	/* create the nuSOAP object */
	$c = new nusoap_client('http://www.weather.gov/forecasts/xml/DWMLgen/wsdl/ndfdXML.wsdl', 'wsdl');
	
	/* make the request */
	$result = $c->call('NDFDgen', $parameters);
}
catch (Exception $ex)
{
	/* nuSOAP throws an exception is there was a problem fetching the data */
	echo 'failed';
}

header('Content-type: text/xml');
echo $result;

/* ?> */

This is by no means a production sample. You are going to want to cache the results somehow so you aren’t querying the NDFD every time you need this information. It is only updated every hour at most, so there is no need to query it more frequent than that.

Understanding the XML Output

It took me a great deal of staring and reading to figure out how this XML output is organized. Have a look at this sample output, as I will refer to this specific example to explain the sections. I recommend loading the sample XML file in Firefox or any other web browser that allows you to collapse and expand the XML tree.

First, collapse the “head” area by clicking on the minus next to the “head” tag. Everything in there is pretty self-explanatory. Our focus is going to be in the “data” tag. Next, collapse all tags inside the “data” tag, except for the “parameters” tag, but collapse everything inside the “parameters” tag so we can see everything in there w/o scrolling. Here is what your view should look like:

ndfd_01

Now we are at a point where we can see how this is all grouped together. As we can see, the “parameters” section shows the products that are available, which are temperature (minimum), temperature (maximum), cloud-amount, weather, conditions-icon, and hazards. Each of them is associated with a time layout because most products are on a different time scale. For example, max temps are during the afternoon & once a day, min temps are at night & once a day, the conditions icons are at several points throughout the next several days, etc. It is separated out this way because not all products have a different time scale – some are the same, like “weather” and “conditions-icon” in our example.

Time to match each product to their time layout. We see that the maximum temps have the time layout of “k-p24h-n7-1″, so we go up to the time layouts and find one with the layout-key of “k-p24h-n7-1″.

ndfd_02

We have to code a way to merge all data sets with their corresponding time layout. This may look complicated, but I will show you a somewhat easy way to parse this in PHP.

Run This Example

<?php
/************************************/
/* some functions we will use later */
/************************************/

/**
 * get the string values of the object items
 * @param mixed $object the XML object to extract the string values from
 * @return array
 */
function get_values($object)
{
	$arr = array();
	foreach ($object as $field => $value)
	{
		$arr[] = (string)$value;
	}
	
	return $arr;
}

/*****************************/
/* actual script starts here */
/*****************************/

/* load our example from http://phpstarter.net/samples/348/ndfd_forecast.xml */
$xml_data = file_get_contents('ndfd_forecast.xml');

/* parse the XML data into a giant data object */
try
{
	$xml = new SimpleXMLElement($xml_data);
}
catch (Exception $ex)
{
	/* the XML was probably invalid */
	die('Failed to parse the XML');
}

/* all time layouts go in here with the layout-key as the array keys */
$times = array();

/* loop through the time-layouts in the XML */
foreach ($xml->xpath('//time-layout') as $field => $value)
{
	/* get the layout-key to make it the array key value */
	$layout = (string)$value->{'layout-key'};
	
	
	$times[$layout] = array('start' => get_values($value->xpath('start-valid-time')), 
							'end' => get_values($value->xpath('end-valid-time')));
}

header('Content-type: text/plain');
var_dump($times);


/**
 * Why do I comment out the PHP closing tag?
 * See: http://phpstarter.net/2009/01/omit-the-php-closing-tag/
 */
/* ?> */

Now we have all the time layouts in a way we can manage it. The next task is to match it up to the data to their respective time layouts. This next example includes the code to organize the time layouts, and then organize the data to correspond with the times. Running the example below will show that the data is now neatly organized in a way that it can be easily used.

Run This Example

<?php
/************************************/
/* some functions we will use later */
/************************************/

/**
 * get the string values of the object items
 * @param mixed $object the XML object to extract the string values from
 * @return array
 */
function get_values($object)
{
	$arr = array();
	foreach ($object as $field => $value)
	{
		$arr[] = (string)$value;
	}
	
	return $arr;
}

/**
 * Combine a time layout with a product
 * 
 * @param string $data_xpath the xpath to the data set
 * @param string $time_xpath the xpath to the time layout
 * @param array $times the big assoc array of all the time layouts
 */
function merge_times_data($data_xpath, $time_xpath, $times, $xml)
{
	$data = get_values($xml->xpath($data_xpath));
	$time_layout = $xml->xpath($time_xpath);
	if (!$time_layout) return false;
	$time_layout = (string)$time_layout[0]['time-layout'];
	$data = array_combine($times[$time_layout]['start'], $data);
	
	return $data;
}

/**
 * get the time layouts in an array form
 * 
 * @param string $xml
 * @return mixed
 */
function get_time_labels($xml)
{
	$data = $xml->xpath("//start-valid-time");
	$times = array();
	
	foreach ($data as $field => $value)
	{
		if ((string)$value['period-name'])
		{
			$index = (string)$value;
			$times[$index] = (string)$value['period-name'];
		}
	}
	
	ksort($times);
	
	return $times;
}

/*****************************/
/* actual script starts here */
/*****************************/

/* load our example from http://phpstarter.net/samples/348/ndfd_forecast.xml */
$xml_data = file_get_contents('ndfd_forecast.xml');

/* parse the XML data into a giant data object */
try
{
	$xml = new SimpleXMLElement($xml_data);
}
catch (Exception $ex)
{
	/* the XML was probably invalid */
	die('Failed to parse the XML');
}

/* all time layouts go in here with the layout-key as the array keys */
$times = array();

/* loop through the time-layouts */
foreach ($xml->xpath('//time-layout') as $field => $value)
{
	/* get the layout-key to make it the array key value */
	$layout = (string)$value->{'layout-key'};
	
	
	$times[$layout] = array('start' => get_values($value->xpath('start-valid-time')), 
							'end' => get_values($value->xpath('end-valid-time')));
}

$forecast['max_temps'] = merge_times_data("//parameters/temperature[@type='maximum']/value", 
	"//parameters/temperature[@type='maximum']", $times, $xml);
$forecast['min_temps'] = merge_times_data("//parameters/temperature[@type='minimum']/value", 
	"//parameters/temperature[@type='minimum']", $times, $xml);
$forecast['temps'] = array_merge($forecast['min_temps'], $forecast['max_temps']);
$forecast['icons'] = merge_times_data("//parameters/conditions-icon/icon-link", 
	"//parameters/conditions-icon", $times, $xml);
$forecast['time_labels'] = get_time_labels($xml);

header('Content-type: text/plain');
var_dump($forecast);

/**
 * Why do I comment out the PHP closing tag?
 * See: http://phpstarter.net/2009/01/omit-the-php-closing-tag/
 */
/* ?> */

There is another function in that example that associates the times to the time labels that you see in the XML. Some of them has a parameter called “period-name”, which is the day of the week or holiday name, if applicable.

How to Use the Formatted Data

For this last example, I will show you in the simplest terms how to use this data. Remember, we are not pulling data from the NDFD live – this is using the XML from our earlier example, so the temps are for mid-winter in NW Indiana.

Run This Example

<?php

/**
 * pick up where we left off from the last example
 * no need to generate the data array again here
 */
$forecast = file_get_contents('data.txt');
$forecast = unserialize($forecast);

/**
 * Show the temperatures
 */

foreach ($forecast['max_temps'] as $field => $value)
{
	echo 'High temp for ' . $forecast['time_labels'][$field] . ': ' . $value . "<br />\n";
}

foreach ($forecast['min_temps'] as $field => $value)
{
	echo 'Low temp for ' . $forecast['time_labels'][$field] . ': ' . $value . "<br />\n";
}

/**
 * Why do I comment out the PHP closing tag?
 * See: http://phpstarter.net/2009/01/omit-the-php-closing-tag/
 */
/* ?> */

So there you have it – an XML parsing nightmare made easy. This method is in my opinion the best way to receive and parse this data from the National Weather Service. It may not be the best way for everyone, so if you have something better to add, please post a comment!

(Added 2009/02/20) Fetching Time-Series Data from the NDFD

Requesting the time-series data returns a whole bunch more information. As requested, here is an example on how to fetch it. Change the desired parameters to ‘true’. The other examples on how to parse the time layouts and formatting data works on this XML, too.

<?php

/* http://sourceforge.net/projects/nusoap/ */
require('../includes/nusoap/nusoap.php');

$parameters = array('product'	=> 'time-series',
					'latitude'  => 41.879535,
					'longitude'	=> -87.624333,
					'weatherParameters' => array(
					
	'maxt' => true,			'mint' => false,		'temp' => false,			'dew' => false,	
	'appt' => true,			'pop12' => false,		'qpf' => false,				'snow' => false,	
	'sky' => false,			'rh' => false,			'wspd' => false,			'wdir' => false,	
	'wx' => false,			'icons' => false,		'waveh' => false,			'incw34' => false,	
	'incw50' => false,		'incw64' => false,		'cumw34' => false,			'cumw50' => false,	
	'cumw64' => false,		'wgust' => false,		'conhazo' => false,			'ptornado' => false,	
	'phail' => false,		'ptstmwinds' => false,	'pxtornado' => false,		'pxhail' => false,	
	'pxtstmwinds' => false,	'ptotsvrtstm' => false,	'pxtotsvrtstm' => false,	'tmpabv14d' => false,	
	'tmpblw14d' => false,	'tmpabv30d' => false,	'tmpblw30d' => false,		'tmpabv90d' => false,	
	'tmpblw90d' => false,	'prcpabv14d' => false,	'prcpblw14d' => false,		'prcpabv30d' => false,	
	'prcpblw30d' => false,	'prcpabv90d' => false,	'prcpblw90d' => false,		'precipa_r' => false,	
	'sky_r' => false,		'td_r' => false,		'temp_r' => false,			'wdir_r' => false,	
	'wwa' => false,			'wspd_r' => false)
	
					);

try
{
	/* create the nuSOAP object */
	$c = new nusoap_client('http://www.weather.gov/forecasts/xml/DWMLgen/wsdl/ndfdXML.wsdl', 'wsdl');
	
	/* make the request */
	$result = $c->call('NDFDgen', $parameters);
}
catch (Exception $ex)
{
	/* nuSOAP throws an exception is there was a problem fetching the data */
	echo 'failed';
}

header('Content-type: text/xml');
echo $result;

/* ?> */

More tips can now be found here: More Examples with Parsing NDFD Data in PHP


36 Responses to “Parse Weather Forecast Data (from the NDFD) in PHP”

  • Brian Shin

    Andrew,

    Thanks for the work on the NDFD stuff. I’m looking to do this exact project, but with a different part of the NDFD.

    I’m working with this one;
    http://www.weather.gov/forecasts/xml/DWMLgen/wsdl/ndfdXML.wsdl

    and I can’t seem to build the parameters for the entire thing. It looks like it has an embedded array/complex type like this;

    -40.7561
    –111.901
    -time-series
    -2009-02-18T00:00:00
    -2009-02-18T00:00:00
    -
    -true
    -true
    -false
    -false
    -

    Anyhow, do you have any suggestions on how this could work using your example?

    Thanks,
    Brian

     

  • Andrew

    Hi Brian,

    I updated the post to show an example for the time-series data. I didn’t use it at first because I couldn’t get it to function correctly with the parameters, either. It turns out that the weatherParameters has to be in a sub-array inside the other parameters, as shown in the example. Hope this helps. :)

    I cleaned up some the first example as well…there were some parameters in there that were not needed.

     

  • Brian Shin

    Andrew,

    Thanks for the quick reply and update. This complex type has caused me so much headache. You are right with the array of the weatherParameters, so I tried;

    $weatherParameters = array(‘maxt’ => true,
    ‘mint’ => true,
    ‘temp’ => true);

    $parameters = array(‘product’ => ‘time-series’,
    ‘startTime’ => ’2009-02-18T00:00:00′,
    ‘endTime’ => ’2009-02-18T00:00:00′,
    ‘format’ => ’24 hourly’,
    ‘latitude’ => 40.7561,
    ‘longitude’ => -111.901,
    $weatherParameters);

    but that doesn’t work. I don’t know if the boolean is “true”, true, 1, or 0.

    Then I found a sample in the ndfdXMLclient.php file and they make a call like;

    $weatherParameters = array(‘weatherParameters’ => array(‘maxt’ => 1 == 1,’mint’ => 1 == 1,’temp’ => 1 == 1));

    $parameters = array(‘product’ => ‘time-series’,
    ‘startTime’ => ’2009-02-20T00:00:00′,
    ‘endTime’ => ’2009-02-20T00:00:00′,
    ‘latitude’ => 40.7561,
    ‘longitude’ => -111.901,
    ‘weatherParameters’ => $weatherParameters);

    but when I run this, I don’t think what I get back is right. I’m not all that good with PHP yet so I’m not sure what I’m doing wrong. You can see at test on my web site took, http://www.vikingcomputerconsulting.com/soap.html. It’s the first link. The items 1 through 3 are me trying to get your examples to work ;-) (Which you can see don’t work either) lol!

    Thanks for your help,
    Brian

     

  • Andrew

    What you saw in the ndfdXMLclient.php file was the testing for the presence of the $_GET variables. If you run the example hosted on their servers here, you see that all form values are sent in the URL, so the receiving PHP script is testing for those values in the URL.

    For our purposes, your first example is close…here is what it should be:

    $weatherParameters = array('maxt' => true,
    'mint' => true,
    'temp' => true);
    
    $parameters = array('product' => 'time-series',
    'format' => '24 hourly',
    'latitude' => 40.7561,
    'longitude' => -111.901,
    'weatherParameters' => $weatherParameters);
    

    All I did was add “‘weatherParameters’ => ” to the last line and removed the time limits.

     

  • Sam

    Howdy Andrew,
    I’ve been jumping around the web looking for hints and tips on parsing this monster of an xml file I get from noaa when I request (and cache) the NDFDgen() function. So far high and low temperatures have been easy to parse, but i can’t figure out how to get the correct icons, snow reports, etc to correspond with the correct date because there are at times at least 40 of each!
    Here is what I’ve got parsing the xml thus far. I am a novice to php and am learning by fire at the moment. If there are any thoughts you could offer they would be greatly appreciated!
    Cheers,
    Sam

    data->parameters;
    			$count = count($xml_weather->temperature->value);
    			for($i = 0; $i data->{'time-layout'}->{'start-valid-time'}[$i]);
    				$weekdays[] = date('l', $timestamp);
    				$high = $xml_weather->temperature[0]->value[$i];
    				$highs[] = $high;				
    			}
    			for($i = 0; $i temperature[1]->value[$i];
    			$lows[] = $low;
    			}
    						
    			
    			$credit = $xml->head->source->credit;
    			
    		} else {
    		    exit('Failed to open forecast.xml.');
    		}
    		
    		echo $weekdays[1]."";
    		echo 'High: '.$highs[1]. '&deg;';
    		echo 'Low: '.$lows[1]. '&deg;';
    		echo $condition;
    		echo ''.$credit;
    		?>
    

     

  • Andrew

    Hi Sam,

    Yes, with some of the products using different time intervals, it can make it annoying to put, let’s say the temperature and the icons together. For example, on this forecast page, I got the temps and images to line up with some extra parsing not described in this article. Is that what you are looking to do?

     

  • Sam

    Andrew,
    Thanks so much for the quick response. That is EXACTLY what I am trying to do! Any guidance you might be able to offer would be greatly appreciated!
    Cheers,
    Sam

     

  • Sam

    In fact chasingweather.com is doing a lot of what I hope to create!

     

  • Andrew

    Sam: I will post an article on Tuesday that will cover the topic you talked about as well as more examples with working with NDFD data.

     

  • Sam

    Sounds good Andrew. I very much look forward to it. Any fodder you could throw at me to chew on in the meantime would be great, but if you would rather wait, I’ll be waiting anxiously :)

    Sam

     

  • Robert

    Why is it I am always getting this error referring to:
    header(‘Content-type: text/html’);

    Warning: Cannot modify header information – headers already sent by (output started at

     

  • Andrew

    Robert – You are outputting something before the line that has header(‘Content-type: text/xml’);. I’m guessing that you added some echo statements in the code for debugging purposes, or you have some whitespace before the opening PHP tag.

     

  • Sean

    I have this working in ASP.net, but I feel like the icons must be wrong because it sometimes shows nighttime icons for the day highs….  Am I somehow not matching these up right?
    Here’s the page I’m referring to:  http://www.boatpittsburgh.com
    You can use the down arrow to expand the weather for the whole week… apparently on Monday and Tuesday it’s forecast to be a solar eclipse all day long… O.o

     

  • Andrew

    Sean-

    That’s interesting.  I had a similar issue because the arrays weren’t aligning properly, causing the days to be off, and the forecasts were flipped – showing day for night and night for day.  It’s hard to say why you are seeing night for both.  How are you processing the XML?  I assume you are doing it in ASP.NET, but can you post how you are getting the data from the XML to an object to use?

     

  • Sean

    Here’s some of the code… there’s a whole mess of it, and I dont’ want to clog your board with randomness, so I’ve stripped out that which I think is relevant…

    http://phpstarter.pastebin.com/hRZkriTE

     

  • Roch

    Hello.  I have a question about time layouts in an array form.  My data does not have “period-name’ fields.  How would I be able to get the date/days without it?  Thanks!

     

  • Andrew

    Roch -

    Some of the time layouts do not have name period-name.  What data types are you pulling? (temp, cloud cover, etc)

     

  • Roch

    Thanks for replying.  I’m pulling at a glance so its max/min temps, current temps, etc.  I need to just have the date print out before the weather info for each day.  I’ve selected 7 days.  Thanks.

     

  • Roch

    To make myself clearer….I need to organize the weather condition summary.  I’ve been trying the past few hours with no luck.  Can anyone help?  Looked at what someone did on another one of your pages but I didn’t understand it.  Please help!

     

  • Roch

    Has anyone used the weather condition summary with simplexml????  I can’t seem to get it to work.

     

  • Andrew

    Roch – do you have a link to what you have so far?

    Also consider posting your code at http://phpstarter.pastebin.com/ so people can have a look at it.

     

  • Harry

    Andrew,
    First and foremost I want to thank you for an excellent tutorial on this topic. Your examples and scripts are excellent and informative.  I  ran into a glitch that I just can’t seem to find.
    In attempting to do the final display of the data.txt with  max and min temps,  i get Invalid argument supplied for foreach() for the two foreach lines, usually meaning an empty array.
    Can you see what could be wrong?

    Here is the text file, outputed.

    Code pasted here: http://phpstarter.pastebin.com/FDhSWx9A

     

     

  • Andrew

    Harry -

    Are you loading the data.txt from this website, or are you parsing the XML each time?

    Also, do a vardump() with the two variables – should show you if it’s empty.

     

  • Harry

    Andrew,
    I am creating the files locally.
    The output i posted was created by your script.
    They all worked great until the last display one.
    I am reading directly from that file.

     

  • Andrew

    Harry -

    Do a var_dump() of the $forecast variable after it’s unserialized, and let me know what you find.

     

  • Harry

    Andrew
    Here is the script

    http://phpstarter.pastebin.com/0VpXCyhW
     
    bool(false)
    Warning: Invalid argument supplied for foreach() in /home/content/n/e/w/news1537/html/nusoap/wx_soap_dataDisplay.php on line 15

    Warning: Invalid argument supplied for foreach() in /home/content/n/e/w/news1537/html/nusoap/wx_soap_dataDisplay.php on line 20
     
    wx_soap_data.txt
    is the txt file in the same directory.
    contents as in previous message
     
    Harry

     

  • Harry

    wx_soap_data.txt is created by
    var_dump($forecast) of the previous script

     

  • Harry

    ohh, that is probably confusing.
    the examples don’t have file names so it is difficult to reference them.
    comes from here var_dump($times)
    /* loop through the time-layouts in the XML */
    foreach ($xml->xpath(‘//time-layout’) as $field => $value)
    {
    /* get the layout-key to make it the array key value */
    $layout = (string)$value->{‘layout-key’};

    $times[$layout] = array(‘start’ => get_values($value->xpath(‘start-valid-time’)),
    ‘end’ => get_values($value->xpath(‘end-valid-time’)));
    }

    header(‘Content-type: text/plain’);
    var_dump($times)<—-
     
     

     

  • Roch

    Has anyone had their XML come back with no brackets?? That’s happening to me now and I don’t know how to fix it!

     

  • Andrew

    Harry – something is happening when you unserialize it I’m guessing.

    From the manual page: “In case the passed string is not unserializeable, FALSE is returned and E_NOTICE is issued.”

    Try enabling E_NOTICE in your error reporting to see if that is the issue.

     

    Roch – can you provide an example?

     

  • Roch

    Here’s a part of what I’m getting back:
     
    http://phpstarter.pastebin.com/jwXNgr3M

     

  • Harry

    Andrew,
    Have not figured out the issue …in a related idea using xpath
    i would like to extract the url of the icon-link and wrap  an img src tag around it.
    i can extract the icon link  but it is always wrapped in <icon-link></icon-link>
     
    Thanks
    Harry

     

  • Roch

    I still haven’t figured out why my brackets are gone when I get the xml back from the wsdl.  Has anyone had this happen???  I need to finish this and its been dragging on and on…..

     

  • Ron

    Andrew

    I have been reading your parsing of the NWS NDFD. I downloaded nuSOAP 0.9.5. I cannot find parse_data.php in your code examples. Am I missing something? I really to learn this. Thank you for any assistance.


     

     

     

  • Andrew

    Ron – the parse_data.php file is the second code example under the “Understanding the XML Output” section.  Click on the “Run this Example” link to see the sample output or copy the code from the box to try it yourself.

     

     

  • jkirby

    Andrew -

    I am not a PHP programmer (Java guy) but when I run your parse_data.php example locally using $xml_data = file_get_contents(‘http://phpstarter.net/samples/348/ndfd_forecast.xml’); I get a an out put like this:
     
    http://pastebin.com/JiRV284N

     

     

Leave a Reply





Wordpress doesn't like it when you post PHP code. Go save your code at pastebin, and post the link here.

About the Author

Andrew has been coding PHP applications since 2006, and has plenty of experience with PHP, MySQL, and Apache. He prefers Ubuntu Linux on his desktop and has plenty of experience at managing CentOS web servers. He is the owner of Wells IT Solutions LLC, and develops PHP applications full time for anyone that needs it as well as does desktop computer support locally in the local area. He spends most of his free time exploring new programming concepts and posting on The Webmaster Forums.