January 6, 2009

Pages


Search Site


Topics



Archives

Tweets

Tracking the storm… Redux

September 06 2008 by Adam

A couple of days ago I used Rays code for tracking hurricane distance from your local zip code to add tracking of Hurricane Hanna to my family blog. Then my father in law was kind enough to point out that it was wrong. Way wrong! It was reporting distances of over 3,000 miles, when we're really less than 300 miles from it.

Closer inspection of the code showed that Ray accidentally swapped the order of latitude and longitude assignments from the NHC feed; and wasn't accounting for Southern and Western hemispheres being represented as a negative number. Ray posted about some changes he made in a comment on his post to fix these problems (if only I had seen those before now!).

Ray is going to post his updated script soon, but mine is below.

In addition to the same fixes, I've added a touch of encapsulation (though I did remove the zip lookup stuff once I knew the lat/long for my zip — because this was easily the slowest part of the whole script) and setup caching with a forced refresh, and refreshing if the previous attempt had any problems. Here's my entire script:

<!--- catch previous hit found no data --->
<cfif structKeyExists(application, "hannaData") and findNoCase("check back later", application.hannaData)>
   <cfset application.hannaData = getHannaData() />
   <cfset application.hannaDataUpdated = now() />   
</cfif>
<!--- allow force cache refresh --->
<cfif structKeyExists(url, "updateHanna")>
   <cfset application.hannaData = getHannaData() />
   <cfset application.hannaDataUpdated = now() />   
</cfif>
<!--- catch first hit of application lifecycle --->
<cfif not structKeyExists(application, "hannaData") or not structKeyExists(application, "hannaData")>
   <cfset application.hannaData = getHannaData() />
   <cfset application.hannaDataUpdated = now() />   
</cfif>
<!--- catch cache outdated --->
<cfif structKeyExists(application, "hannaDataUpdated") and dateCompare(now(), application.hannaDataUpdated, "n") gt 10>
   <cfset application.hannaData = getHannaData() />
   <cfset application.hannaDataUpdated = now() />   
</cfif>

<cfoutput>
   #application.hannaData#<br/>
   <small><em>Updated: #timeFormat(application.hannaDataUpdated, "h:MM tt")#</em></small>
</cfoutput>

<cfscript>
/**
* Calculates the distance between two latitudes and longitudes.
* This funciton uses forumlae from Ed Williams Aviation Foundry website at http://williams.best.vwh.net/avform.htm.
*
* @param lat1     Latitude of the first point in degrees. (Required)
* @param lon1     Longitude of the first point in degrees. (Required)
* @param lat2     Latitude of the second point in degrees. (Required)
* @param lon2     Longitude of the second point in degrees. (Required)
* @param units     Unit to return distance in. Options are: km (kilometers), sm (statute miles), nm (nautical miles), or radians. (Required)
* @return Returns a number or an error string.
* @author Tom Nunamaker (&#116;&#111;&#109;&#64;&#116;&#111;&#115;&#104;&#111;&#112;&#46;&#99;&#111;&#109;)
* @version 1, May 14, 2002
*/
function LatLonDist(lat1,lon1,lat2,lon2,units)
{
   // Check to make sure latitutdes and longitudes are valid
   if(lat1 GT 90 OR lat1 LT -90 OR
      lon1 GT 180 OR lon1 LT -180 OR
      lat2 GT 90 OR lat2 LT -90 OR
      lon2 GT 280 OR lon2 LT -280) {
      Return ("Incorrect parameters");
   }

   lat1 = lat1 * pi()/180;
   lon1 = lon1 * pi()/180;
   lat2 = lat2 * pi()/180;
   lon2 = lon2 * pi()/180;
   UnitConverter = 1.150779448; //standard is statute miles
   if(units eq 'nm') {
      UnitConverter = 1.0;
   }

   if(units eq 'km') {
      UnitConverter = 1.852;
   }

   distance = 2*asin(sqr((sin((lat1-lat2)/2))^2 + cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2))^2)); //radians

   if(units neq 'radians'){
      distance = UnitConverter * 60 * distance * 180/pi();
   }

   Return (distance);
}
</cfscript>
<cffunction name="logit" output="false" returnType="void">
<cfargument name="str" type="string" required="true">
<cflog file="hurricane_hanna" text="#arguments.str#">
</cffunction>
<cffunction name="getHannaData" returntype="string" output="false">
   <cfset var dst = 0/>
   <cfset var long = 0/>
   <cfset var longdir = "" />
   <cfset var lat = 0/>
   <cfset var latdir = "" />
   <cfset var hannaXML = "http://www.nhc.noaa.gov/nhc_at3.xml">
   <cfset var results = "" />
   <cfset var match = "" />
   <cfset var regex = "" />
   <cfset var text = ""/>
   
   <cffeed source="#hannaXML#" query="results">
   
   <cfif not results.recordCount>
      <cfset logit("Error - no feed entries")>
      <cfreturn "Unable to track hurricane Hanna (no data provided), check back later." />
   </cfif>
   
   <!--- find "Public Advisory" --->
   <cfquery name="pa" dbtype="query" maxrows="1">
      select rsslink, content, title
      from results
      where title like '%HANNA Public Advisory Number%'
   </cfquery>
   
   <cfif not pa.recordCount>
      <cfset logit("Error - cound't find a matching entry")>
      <cfreturn "Couldn't find information on hurricane Hanna, check back later." />
   </cfif>
   
   <cfhttp url="#pa.rsslink#" result="results">
   <cfset text = results.fileContent>
   
   <!--- strip extra white space --->
   <cfset text = reReplace(text, "[\r\n]+", " ", "all")>
   
   <cfset regex = "CENTER OF TROPICAL STORM HANNA WAS LOCATED NEAR LATITUDE ([[:digit:]\.]+)[[:space:]]*([NORTH|SOUTH|EAST|WEST]+)...LONGITUDE ([[:digit:]\.]+)[[:space:]]*([NORTH|SOUTH|EAST|WEST]+)">
   
   <!--- now look for: THE CENTER OF TROPICAL STORM HANNA WAS LOCATED NEAR LATITUDE 19.1 NORTH...LONGITUDE 74.4 WEST --->
   <cfset match = reFind(regex, text, 1, true)>
   <cfif arrayLen(match.pos) is 5>
    <cfset lat = mid(text, match.pos[2], match.len[2])>
    <cfset latdir = mid(text, match.pos[3], match.len[3])>
    <cfset long = mid(text, match.pos[4], match.len[4])>
    <cfset longdir = mid(text, match.pos[5], match.len[5])>
    <cfif longdir eq "WEST"><cfset long = -1 * long /></cfif>
    <cfif latdir eq "SOUTH"><cfset lat = -1 * lat /></cfif>
   <cfelse>
    <cfset logit("Error - couldn't find my matches in the string")>
      <cfreturn "Couldn't find lat/long location info for hurricane Hanna, check back later." />
   </cfif>

   <cfset dst = latLonDist(lat,long,39.98,-75.82,"sm")>
   
   <cfset logit("distance:" & dst)>
   <cfset rtn = "" />
   <cfset rtn &= "<strong>Distance:</strong> " & numberFormat(dst,',9.99') & " miles<br /><small>As of: <a " />
   <cfset rtn &= "href='" />
   <cfset rtn &= pa.rsslink />
   <cfset rtn &= "'><strong>#replaceNoCase(pa.title, 'Tropical Storm HANNA Public ', '')#</strong></a><br />" />
   <cfset rtn &= "Hanna Lat: #lat# #latdir#<br />Hanna Long: #long# #longdir#</small>" />
   <cfreturn rtn />
</cffunction>

Posted in ColdFusion | 1 comments

1 comment:

  1. PaulH Says:

    that's actually fairly common as positions in geographic coords are usually expressed as lat/long (y/x).

Leave a Reply