By: Ron Gowen

Consuming an XML weather feed from the National Weather Service

Introduction:

This tutorial will show you how to consume an XML weather feed from the National Weather Service and display the results.

It is only appropriate to minimize the number of hits to the server that is providing the feed. In order to do that we will store the results of the http call in a text file and replace it at predetermined intervals.

Let us begin:

First we will create 2 variables one for our path and one for our path including filename:

<cfset savePath = expandPath(".") & '\ws'/>
<cfset savePath_2 = savePath & '\' & 'temp.xml'/>

“temp.xml” will be the file we will create later that will contain the xml feed.

“ws\” will be the directory in which our XML file will be saved. We will test for the existence of this directory and if it is not there we’ll create it.

<cfif directoryExists(savepath) eq 'no'>
    <cfdirectory action="create" directory="#savepath#" />
</cfif>

Explanation: “directoryExists” is a built in ColdFusion Function. We are passing the absolute path of the directory we want to test for. The function will return yes or no. If no is returned we will use the cfdirectory tag to create the directory.

Then we’ll query the directory that will contain our file to see if the xml file is there and if so test it’s age. If the age of the file is past our predetermined age (12 hours in this case) we’ll delete the file. The documentation from the national weather service says they update their feeds every 45 minutes so you could change the time frame to be less if so desired.

<cfdirectory action="list" directory="#savePath#\" name="theSOAP" filter="temp.xml">

<cfif NOT theSOAP.recordCount EQ 0
       AND dateCompare(theSOAP.dateLastModified, dateAdd("h", -12, now())) EQ -1>
         <cffile action="delete"
                    file="#savePath_2#">
</cfif>

Explanation: Again we use the cfdirectory tag, but this time we’ll set the action attribute to list and set the filter attribute to ‘temp.xml’ to ensure we only select the file we want. The tag will return a query object of the results. We then test for a return from the query ‘recordcount’ and test the age of the returned file using the “dateLastModified” column that is returned by the cfdirectory tag’s list action. We compare the “dateLastModified” value against the current date/time via the built in function “dateCompare”.

Gathering Our Data:
To build our query string as required by the national weather service we need several things: our start date formatted like below with “-“ as delimiters, the longitude and latitude for the area you want the info about and the number of days you want to include in the forecast (3 in this case).

These pages can be helpful in constructing your URL and examining the documentation:
http://www.weather.gov/xml/
http://www.weather.gov/forecasts/xml/SOAP_server/ndfdXML.htm

<cfset startDate = dateFormat(now(),'yyyy-mm-dd') />
<cfsetting requesttimeout="40">
<cfif NOT fileexists(savePath_2)>
<cfhttp
url="http://weather.gov/forecasts/xml/SOAP_server/ndfdSOAPclientByDay.php?lat=33.6&lon=112.282&format=24+hourly&startDate=#startdate#&numDays=3&Submit=Submit" method="get"
path="#savepath#"
file="temp.xml" timeout="40" >
</cfif>
</cfsetting>

Explanation: We test for the existence of our file, if it is not there we create it using the cfhttp tag.

Then we read the file:

<cffile
action= "read"
file= "#savePath_2#"
variable= "theXML" >

Convert the string to an XML object and store it in the variable “theXML”

<cfset theXML = XMLParse(theXML) />

Now we want to extract the data that we would like to display on our page. We will use the ColdFusion XMLSearch Function that receives an Xpath expression as one of its parameters, the other being the XML object.

<cfset maxTemps = xmlSearch(theXML, '//temperature[@type="maximum"]/value') />
<cfset lowTemps = xmlSearch(theXML, '//temperature[@type="minimum"]/value') />
<cfset pics = xmlSearch(theXML, '//icon-link') />
<cfset overall = xmlSearch(theXML, '//weather-conditions[attribute::weather-summary]') />
<cfset theDate = xmlSearch(theXML, '//time-layout') />

Xpath and or XMLSearch() is very handy for getting straight to the Node or data that we wish to use without having to use endless dot notation to transverse the hierarchy of the XML document.

The xmlSearch() function requires 2 arguments – the XML to traverse and the xpath expression,
and in turn the function returns an array containing the results. Let’s look at the xpath we have used here,

Our first expression is //temperature[@type="maximum"]/value' if we tear down this expression we can say that “//temperature” will return all elements named “temperature” within the document object no matter where they are found within the XML. We would like to be even more specific than that so we add “[@type="maximum"]/.” this says with attributes named “type” that equal “maximum”. Then we add “/value”. The expression in it’s entirety says all nodes named value nested inside nodes named temperature with an attribute named type that equals maximum. The rest should be fairly self-explanatory.

There is a short but sweet tutorial on xpath at: http://www.w3schools.com/xpath/default.asp

Now that we have our 5 arrays containing the data we want we will pop the data into an array of structures to make it easier to understand and display.

<cfset the_weather = arraynew(1) />
<cfloop from=
"1" to="3" index="i">
    <cfset the_weather[
i] = structNew() />
    <cfset the_weather[i].tDay =
dayofweekasstring(dayofweek(dateformat(left(theDate[1].XmlChildren[i*2].XmlText,10),'yyyy-mm-dd'))) />
    <cfset the_weather[i].pics = pics[i].XmlText />
    <cfset the_weather[i].cond = overall[i].XmlAttributes[
'weather-summary'] />
    <cfset the_weather[i].maxTemps = maxtemps[
i + 1].xmlText />
    <cfset the_weather[i].lowTemps = lowtemps[i].xmlText />
</cfloop>

Now if we dump our array

<cfdump var="#the_weather#" />

Image Dump

of course you can display the data as you wish but here is one alternative:

<table cellpadding="0" cellspacing="0" style="border:1px solid #444; height:118px; margin:0px;" border="0" class='weather' >
    <tr>
        <td colspan=
"3" style="text-align:center; font-weight:bold;" >Sun City Weather</td>
    </tr>
    <tr>

    <cfoutput>
        <cfloop from="1" to="3" index="i">
        <td>
        <table cellpadding=
"0" cellspacing="0" border='0' style="border-top:1px solid ##444; width:75px;">
            <tr>
                <td colspan=
"2" style="text-align:center; font-size:10px; font-weight:bold; height:12px; background-color:##66FFFF; border-bottom:1px solid ##000;">#the_weather[i].tDay#</td>
            </tr>
            <tr>
                <td colspan=
"2" style="text-align:center; font-size:10px; height:24px;">#the_weather[i].cond#</td>
            </tr>
            <tr>
                <td colspan=
"2" style="text-align:center;"><img src="#the_weather[i].pics#" width="50px" height="50px;" /></td>
            </tr>
            <tr>
                <td style=
"text-align:center;">
                <table cellpadding=
"0" cellspacing="0">
                    <tr>
                        <td style=
"padding:0px 6px 0px 6px; font-size:10px; font-weight:bold;">High</td>
                        <td style=
"padding:0px 6px 0px 6px; font-size:10px; font-weight:bold;">Low</td>
                    </tr>
                    <tr>
                        <td style=
"text-align:center; font-size:10px;">#the_weather[i].maxTemps#</td>
                        <td style=
"text-align:center; font-size:10px;">#the_weather[i].lowTemps#</td>
                    </tr>
                </table>
                </td>
            </tr>
        </table>
        </td>
        
</cfloop>
        </cfoutput>

    </tr>
</table>

About This Tutorial
Author: Ron Gowen
Skill Level: Intermediate 
 
 
 
Platforms Tested: CFMX
Total Views: 88,376
Submission Date: March 31, 2006
Last Update Date: June 05, 2009
All Tutorials By This Autor: 1
Discuss This Tutorial
  • Sorry I just noticed your comment today. I just ran my code several times and it worked fine. I would suggest that when you get the error to dump your parsed XML. and see if the nodes in question are there. If you would like send your code along to me and I will take a look.

  • I've been using this successfully for about six months. On 10/22, the National Weather Service made some updates to NDFD XML, and I've had this problem ever since (see below). An error occured while Parsing an XML document. The markup in the document following the root element must be well-formed. The error occurred in denver_forecast.cfm: line 66 64 : 65 : 66 : 67 : 68 : If you go to the URL and manually save the XML file it works fine, but not automatically. Any idea what's going on here?

  • Im sorry Shen, I am not able to reproduce your error. My best guess would be that you are missing the line of code that creates the "overall" array.

  • Dear Ron Thank you for this great learning sample. I am experiencing a problem when I run this sample. I used the link http://www.weather.gov/forecasts/xml/SOAP_server/ndfdXML.htm create the xml. I got the following error. "overall variable can not be found" I can not found the weather-summarty type in the xml file. thank you for all your help Shen

  • Hi, is any service avaliable for India to get the results, if so plz let us know thanks

  • I am really not sure if Italy's version of the National weather service provides any web services. I think this is the address: http://www.meteoam.it/ but I dont read italian and I dont see any XML or RSS logos, but you may be able to find a pay(ugh) service if you spend some time googlin. Thanks and Good Luck

  • Thanks for the great tuorial. Do you have a webservice for Italy? Thanks

Advertisement


Website Designed and Developed by Pablo Varando.