Using Weather Forecast XML from the National Weather Service

Back to Geek Stuff

For the weather information on the home page, the source data comes from:

Current conditions http://www.nws.noaa.gov/data/current_obs/KRDU.xml
Forecast noaaGet.asp handles the call to the SOAP (Simple Object Access Protocol) web service on the National Weather Service's site and displays the XML that is used to create the page.
National Hurricane Center storm information http://www.nhc.noaa.gov/index-at.xml

Key components:

/include/noaa.asp Include file containing functions that get the XML and perform the transformations. (see more)
/weather/forecastNoaa.asp This page displays the forecast information
/xslt/forecast.xslt This XSL stylesheet is used to take the forecast XML and create the HTML used in forecastNoaa.asp (see more)
/weather/storms.asp This page displays the latest storm information from the National Hurricane Center
/xslt/storminfo.xslt This XSL stylesheet is used to take the NHC XML and create the HTML used in storms.asp
/xslt/currentconditions.xslt This XSL stylesheet is used to take the current conditions XML and create the HTML displayed on the home page. (see more)

For the current conditions, I use this code:

function transformCurrent()
	' You need to enter the full path to a XML file in same secure authorized directory
	' as your Web virtual directory where this ASP page is loaded from and executed.
	xmlFile = "http://www.nws.noaa.gov/data/current_obs/KRDU.xml"
	xsltSheet = server.mappath("/xslt/currentconditions.xslt")
	
	Dim root
	Dim xmlDoc
	dim xsltDoc
	Dim child
	Dim indent
	
	indent=0
	
	Set xmlDoc = createXmlDomDocument(xmlDoc)
	set xsltDoc = createXMLDomDocument(xsltDoc)
	
	xmlDoc.async = False
	xmlDoc.validateOnParse=False
	xmlDoc.setProperty "ServerHTTPRequest", true 
	xmlDoc.load xmlFile
	'xmlDoc.open  "GET", xmlFile, False, "", "" 
	
	If xmlDoc.parseError.errorcode = 0 Then
		
	Else
	   Response.Write("<P>There was an error in : " & xmlFile &"</P><P>Line: " & _
					   xmlDoc.parseError.line & _
					   "<BR>Column: " & xmlDoc.parseError.linepos & "</P>" & _
					   xmlDoc.parseError.reason)
	End If

	xsltDoc.validateOnParse=False
	xsltDoc.load xsltSheet
	
	transformCurrent = xmlDoc.transformNode(xsltDoc)
	transformCurrent = replace(transformCurrent, "From the", "")

end function

Function createXmlDomDocument(xd)
   On Error Resume Next
   'Uncomment the following line to use MSXML 3.0
   Set xd = CreateObject("MSXML2.DOMDocument.3.0")
   'Uncomment the following line to use MSXML 4.0
   'Set xd = CreateObject("MSXML2.DOMDocument.4.0")
   'Uncomment the following line to use MSXML 5.0
   'Set xd = CreateObject("MSXML2.DOMDocument.5.0")
   If (IsObject(xd) = False) Then
       alert("DOM document not created. Check MSXML version used in createXmlDomDocument.")
   Else
       Set createXmlDomDocument = xd
   End If
End Function

function getCurrentConditionsXML(searchType)
  Set objInputXMLDoc = Server.CreateObject("Microsoft.XMLDOM")
  objInputXMLDoc.load Server.MapPath("noaarequest.xml")
  objInputXMLDoc.selectSingleNode("//product").Text = searchType
  '
  ' Post the SOAP message.
  '
  Set objXMLHTTP = Server.CreateObject("Microsoft.XMLHTTP")
  objXMLHTTP.open  "post", "http://www.nws.noaa.gov/forecasts/xml/SOAP_server/ndfdXMLserver.php", False
  objXMLHTTP.setRequestHeader "Content-Type", "text/xml"
  objXMLHTTP.setRequestHeader "SOAPAction", "NDFDgenResponse"
  objXMLHTTP.send objInputXMLDoc

  ' Dump the results into an XML document.

  Set objOutputXMLDoc = Server.CreateObject("Microsoft.XMLDOM")
  objOutputXMLDoc.loadXML objXMLHTTP.responseText

  ' Assign the node to the new document, and voila!  You have the XML from the SOAP document in it's own doc
  Set Node = objOutputXMLDoc.selectSingleNode("//xmlOut")

  ' Return XML text for xmlOut node to be used as input to forecast xslt

  getCurrentConditionsXML = Node.text

  
end function

function transformForecast()
dim forecastXML
dim xmlDoc
dim xsltDoc
dim xsltSheet 
	
	xsltSheet = server.mappath("/xslt/forecast.xslt")
	xmlFile = server.mappath("noaadump.xml")
	forecastXML = getCurrentConditionsXML("glance")
	
	' document with transform info
	set xsltDoc = createXMLDomDocument(xsltDoc)
	' document with XML from SOAP request
	set xmlDoc = createXMLDomDocument(xmlDoc)
		
	xsltDoc.validateOnParse = false
	xsltDoc.load xsltSheet

	' load the forecast information using the XML returned by the SOAP request
	xmlDoc.loadXML forecastXML

	transformForecast = xmlDoc.transformNode(xsltDoc)
	
end function
 

...the XSL transform used is getCurrentConditions.xslt below...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<xsl:element name="table">
<xsl:element name="tr">
<xsl:element name="td">
<xsl:attribute name="class">c5_bold</xsl:attribute>Weather at <xsl:value-of select="//location"/>
</xsl:element>
</xsl:element>
<xsl:element name="tr">
<xsl:element name="td">Current temperature: <xsl:value-of select="//temperature_string"/>
</xsl:element>
</xsl:element>
<xsl:element name="tr">
<xsl:element name="td">
<xsl:value-of select="//weather"/>
</xsl:element>
</xsl:element>
<xsl:element name="tr">
<xsl:element name="td">Relative humidity: <xsl:value-of select="//relative_humidity"/>%
</xsl:element>
</xsl:element>
<xsl:element name="tr">
<xsl:element name="td">Wind: <xsl:value-of select="//wind_string"/>
</xsl:element>
</xsl:element>
<xsl:element name="tr">
<xsl:element name="td">Heat Index: <xsl:value-of select="//heat_index_string"/>
</xsl:element>
</xsl:element>
<xsl:element name="tr">
<xsl:element name="td">Courtesy: <xsl:element name="a">
<xsl:attribute name="href"><xsl:value-of select="//credit_URL"/></xsl:attribute>
<xsl:attribute name="alt"><xsl:apply-templates select="//credit"/></xsl:attribute>
<xsl:value-of select="//credit"/>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:template>
<!--
<xsl:template match="/temperature_string">
Current temperature: <xsl:value-of select="."/>
</xsl:template>
<xsl:template match="/location">
Location: <xsl:value-of select="."/>
</xsl:template>-->
</xsl:stylesheet>

For the forecast, I use this xsl stylesheet, forecast.xslt. It generates most of the content of the forecast page from the results of the SOAP request above.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<!-- initialize keys -->
<xsl:key name="layout-key" match="data/time-layout" use="layout-key"/>
<xsl:key name="day-key" match="data/time-layout/start-valid-time" use="substring(., 1, 10)"/>
<!-- initialize variables -->
<!-- get first date from each of the first two time layout tables, assign to -->
<!-- date1 and date2 -->
<xsl:variable name="start-dates">
<xsl:apply-templates select="//data/time-layout[1]"/>
</xsl:variable>
<xsl:variable name="date1">
<xsl:value-of select="substring($start-dates, 1, 10)"/>
</xsl:variable>
<xsl:variable name="date2">
<xsl:value-of select="substring($start-dates, 26, 10)"/>
</xsl:variable>
<!-- if the first date in the first two layouts isn't the same day, then we will need to -->
<!-- offset the table to get the days to line up. We'll use the tackOn variable as our flag -->
<xsl:variable name="tackOn">
<xsl:choose>
<xsl:when test="$date1=$date2">false</xsl:when>
<xsl:otherwise>true</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- main template -->
<xsl:template match="/">
<!-- table of temperatures -->
<table cellspacing="0">
<tr>
<xsl:apply-templates select="//temperature"/>
</tr>
</table>
<!-- credit -->
<p class="white"> Courtesy: <xsl:element name="a">
<xsl:attribute name="class">whiteBold</xsl:attribute>
<xsl:attribute name="href"><xsl:value-of select="//head/source/credit"/></xsl:attribute>
<xsl:value-of select="//head/product/title"/>
</xsl:element>
</p>
<!-- graphic forecasts -->
<xsl:apply-templates select="//weather"/>
</xsl:template>
<!-- experimental templates -->
<xsl:template match="//time-layout">
<!--<xsl:value-of select="child::layout-key"/>-->
<xsl:apply-templates select="child::start-valid-time"/>
</xsl:template>
<!--<xsl:template match="//start-valid-time">
<xsl:if test="../layout-key='k-p24h-n7-1'">
<p>
<xsl:value-of select="name()"/>: <xsl:value-of select="."/>
</p>
</xsl:if>
</xsl:template>-->
<!-- grab dates for comparison in initial variables -->
<xsl:template match="//data/time-layout[1]">
<!-- grab first date -->
<p>
<xsl:value-of select="start-valid-time"/>
</p>
<!-- grab second date -->
<p>
<xsl:value-of select="following::start-valid-time"/>
</p>
</xsl:template>
<!-- template to build table of temperature data -->
<xsl:template match="temperature">
<xsl:variable name="times" select="key('layout-key', @time-layout)"/>
<td valign="top">
<table width="200" cellspacing="0">
<xsl:for-each select="value">
<!-- if tackOn is true, add an offset to the row counter to make row -->
<!-- colors align -->
<xsl:variable name="offset">
<xsl:choose>
<xsl:when test="$tackOn = 'true' and ../@type='maximum'">1</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- set the color for this row using the mod function to see if even row -->
<xsl:variable name="color">
<xsl:choose>
<xsl:when test="(position() + $offset) mod 2 = 0">#CCCCCC</xsl:when>
<xsl:otherwise>#FFFFFF</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- if tackOn is true (at night) and max temperature, prepend row to top -->
<xsl:choose>
<xsl:when test="$tackOn='true' and position() = 1 and ../@type='maximum'">
<tr>
<td colspan="2" bgcolor="#FFFFFF"> </td>
</tr>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
<!-- start row -->
<tr>
<xsl:element name="td">
<xsl:attribute name="width">100</xsl:attribute>
<xsl:attribute name="bgcolor"><xsl:value-of select="$color"/></xsl:attribute>
<!-- while iterating set counter to current position in temperature array -->
<xsl:variable name="counter" select="position()"/>
<!-- dereference the position in the array to variable this-time -->
<!-- (I tried to use position () here but it didn't work, probably a -->
<!-- context issue) -->
<xsl:variable name="this-time" select="$times/start-valid-time[$counter]"/>
<!-- display value of the day name -->
<xsl:value-of select="$this-time/@period-name"/>
</xsl:element>
<xsl:element name="td">
<xsl:attribute name="width">100</xsl:attribute>
<xsl:attribute name="bgcolor"><xsl:value-of select="$color"/></xsl:attribute>
<!-- the temperature -->
<xsl:value-of select="."/>
</xsl:element>
</tr>
<!-- if tackOn is true and minimum temperature, add row to bottom -->
<xsl:choose>
<xsl:when test="$tackOn='true' and position()=count(../value) and ../@type='minimum'">
<tr>
<xsl:element name="td">
<xsl:attribute name="colspan">2</xsl:attribute>
<xsl:attribute name="bgcolor">#CCCCCC</xsl:attribute> </xsl:element>
</tr>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:for-each>
</table>
</td>
</xsl:template>
<!-- template to make table of forecast graphics -->
<xsl:template match="weather">
<xsl:variable name="times" select="key('layout-key', @time-layout)"/>
<xsl:variable name="icons" select="//conditions-icon"/>
<table width="400" cellspacing="1">
<xsl:for-each select="weather-conditions">
<tr>
<!-- as above set the counter and deference times var to get -->
<!-- corresponding day and time value -->
<xsl:variable name="counter" select="position()"/>
<xsl:variable name="this-time" select="$times/start-valid-time[$counter]"/>
<td class="white">
<!-- pull the day name using the day key -->
<xsl:variable name="day" select="key('day-key', substring($this-time, 1, 10))"/>
<!-- <xsl:value-of select="substring($this-time, 1, 10)"/>-->
<xsl:value-of select="$day/@period-name"/>
<br/>
<xsl:value-of select="substring($this-time, 12, 5)"/>
</td>
<td class="white">
<!-- if we have a text forecast, iterate through -->
<!-- store in variable for image alt text -->
<xsl:variable name="textForecast">
<xsl:choose>
<xsl:when test="count(value) != 0">
<xsl:for-each select="value">
<xsl:value-of select="@coverage"/>
<xsl:text> </xsl:text>
<xsl:value-of select="@weather-type"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:when>
<!-- if we don't have a text forecast -->
<xsl:otherwise>No precipitation forecast</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- repeat because we get nice text format -->
<xsl:choose>
<xsl:when test="count(value) != 0">
<xsl:for-each select="value">
<xsl:value-of select="@coverage"/>
<xsl:text> </xsl:text>
<xsl:value-of select="@weather-type"/>
<br />
</xsl:for-each>
</xsl:when>
<!-- if we don't have a text forecast -->
<xsl:otherwise>No precipitation forecast</xsl:otherwise>
</xsl:choose>
</td>
<td>
<!-- do da image thang -->
<xsl:element name="img">
<xsl:attribute name="src"><xsl:value-of select="$icons/icon-link[$counter]"/> </xsl:attribute>
<xsl:attribute name="border">0</xsl:attribute>
<xsl:attribute name="alt"><xsl:value-of select="$textForecast"/></xsl:attribute>
</xsl:element>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>

If you want to use any of this code, you're welcome to do so. All I ask is that you credit me and this site (Brandt Barretto at http://www.barretto.org/). We appreciate your support!