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:
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″.
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.
<?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.
<?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.
<?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