Monday, October 05, 2009

Integrating weather from yr.no

UPDATED: there's a new implementation post here that uses MVC's built-in AJAX functionality.

a lot of places sell data, and often quite pricy too.. there are however a few that actually gives it away, i like that.

yr.no (the Norwegian Meteorological Institute and the NRK) exposes their weather data in xml format, both in norwegian as well as in english. I figured it was about time i tried it out since i really wanted to have some weather data on the discgolf site.

first, the basics:

the code i’m demonstrating is written in ASP.NET MVC, but it’s not anywhere near MVC-specific so you can easily take it all and run in your own solution.

for this example we’re gonna be using the following url:

http://www.yr.no/place/Sweden/Stockholm/J%c3%a4rva/forecast.xml

at yr.no it’s pretty easy to grab the url to the city of your choice (as long as they hold data for that city): just find it on their site and add forecast.xml in the end of the url.

so, after creating a partial view and setting some initial code up we get to consume the data from yr.no and output it to our site:

code:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DiscMaster.Web.Models.Course>" %>
<%@ Import Namespace="System.Xml" %>
<%
    System.Xml.XmlDocument doc = new XmlDocument();

    if (Cache[Server.UrlPathEncode(Model.CourseDetail.yr_weather)]!=null)
    {
        doc = (XmlDocument)Cache[Server.UrlPathEncode(Model.CourseDetail.yr_weather)];
    }
    else
    {
        try
        {
            System.Xml.XmlTextReader reader = new XmlTextReader(Server.UrlPathEncode(Model.CourseDetail.yr_weather));
            doc.Load(reader);
            Cache.Add(Server.UrlPathEncode(Model.CourseDetail.yr_weather), doc, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
        }
        catch (Exception ex)
        {
        
            throw;
        }
    }

    System.Xml.XmlNodeList nodelist = doc.SelectNodes("/weatherdata/forecast/tabular/time");
    for (int i = 0; i < 4; i++)
    {
     %>
     <p>
        <strong><%= DateTime.Parse(nodelist[i].Attributes["from"].Value).ToString("yyyy-mm-dd HH:MM") %> - <%= DateTime.Parse(nodelist[i].Attributes["to"].Value).ToString("HH:MM")%></strong>
        <br />
        <img src='<%= String.Format("/Image.ashx?src=/Content/Media/Icons/Weather/{0}.png&width=32", nodelist[i]["symbol"].Attributes["number"].Value.PadLeft(2,'0')) %>' />
        conditions: <%= nodelist[i]["symbol"].Attributes["name"].Value%>
        <br />
        temperature: <%= nodelist[i]["temperature"].Attributes["value"].Value%><sup>o</sup>C
        <br />
        precipitation: <%= nodelist[i]["precipitation"].Attributes["value"].Value%>
        <br />
        wind: <%= nodelist[i]["windSpeed"].Attributes["name"].Value%> - <%= nodelist[i]["windSpeed"].Attributes["mps"].Value%> mps <%= nodelist[i]["windDirection"].Attributes["code"].Value%>
    </p>
<%    }
     %>
<small>
    Weather forecast from <a href="http://www.yr.no" target="_blank">yr.no</a> delivered by the Norwegian Meteorological Institute and the NRK
</small>

view:

first nothing major really in that code.. the green-highlighted text in the example code is where the actual url to the xml data is entered.

a few minutes more and the code is more optimized to handle errors, linq and in the end i even had the time to throw in a little jQuery i found over at DynamicDrive called Featured Content Glider that i thought would do nicely for what i wanted this forecast to appear as..

so, now the final code:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DiscMaster.Web.Models.Course>" %>
<%@ Import Namespace="System.Xml" %>
<script type="text/javascript" src="/scripts/featuredcontentglider.js">
    /***********************************************
    * Featured Content Glider script- (c) Dynamic Drive DHTML code library (www.dynamicdrive.com)
    * Visit http://www.dynamicDrive.com for hundreds of DHTML scripts
    * This notice must stay intact for legal use
    ***********************************************/
</script>
<script type="text/javascript">

    featuredcontentglider.init({
        gliderid: "forecast", //ID of main glider container
        contentclass: "glidecontent", //Shared CSS class name of each glider content
        togglerid: "p-select", //ID of toggler container
        remotecontent: "", //Get gliding contents from external file on server? "filename" or "" to disable
        selected: 0, //Default selected content index (0=1st)
        persiststate: false, //Remember last content shown within browser session (true/false)?
        speed: 500, //Glide animation duration (in milliseconds)
        direction: "downup", //set direction of glide: "updown", "downup", "leftright", or "rightleft"
        autorotate: true, //Auto rotate contents (true/false)?
        autorotateconfig: [5000, 2] //if auto rotate enabled, set [milliseconds_btw_rotations, cycles_before_stopping]
    })

</script>
<div id="forecast" class="glidecontentwrapper">
<%
    // used to read the forecast xml
    System.Xml.XmlDocument doc = new XmlDocument();
    // our soon-to-be forecast node collection
    System.Xml.XmlNodeList nodelist = null;

    // determine if the forecast is already stored in the cache..
    if (Cache[Server.UrlPathEncode(Model.CourseDetail.yr_weather)]!=null)
    {
        // .. if so, fetch it
        doc = (XmlDocument)Cache[Server.UrlPathEncode(Model.CourseDetail.yr_weather)];
    }
    else
    {
        // .. and if not, try to consume the source using a reader..
        try
        {
            System.Xml.XmlTextReader reader = new XmlTextReader(Server.UrlPathEncode(Model.CourseDetail.yr_weather));
            doc.Load(reader);
            // add entry to cache
            Cache.Add(Server.UrlPathEncode(Model.CourseDetail.yr_weather), doc, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
        }
        catch (Exception ex)
        {
            // just in case something went wrong we allow this to be blank for now..
            throw;
        }
    }

    // if all is loaded ok and data is there, simple xpath query to get the forecast data..
    if (doc!=null)
    {
        if (doc.HasChildNodes)
        {
             nodelist = doc.SelectNodes("/weatherdata/forecast/tabular/time");
        }
    }

    if (nodelist!=null)
    {
        if (nodelist.Count>0)
        {
            // restruct the nodelist to a queryable list, then group by the day of the forecast, and iterate 'em..
            foreach (var day in nodelist.OfType<XmlNode>().GroupBy(n => DateTime.Parse(n.Attributes["from"].Value).ToString("yyyy-MM-dd")))
            {
                // iterate each forecast in the current group (day)
                foreach (XmlNode node in day)
                {
             %>
             <div class="glidecontent">
                 <p>
                    <strong><%= DateTime.Parse(node.Attributes["from"].Value).ToString("yyyy-MM-dd HH:MM") %> - <%= DateTime.Parse(node.Attributes["to"].Value).ToString("HH:MM")%></strong>
                    <br />
                    <img style="float: left;" src='<%= String.Format("/Image.ashx?src=/Content/Media/Icons/Weather/{0}.png&width=64", node["symbol"].Attributes["number"].Value.PadLeft(2,'0')) %>' />
                    conditions: <%= node["symbol"].Attributes["name"].Value%>
                    <br />
                    temperature: <%= node["temperature"].Attributes["value"].Value%><sup>o</sup>C
                    <br />
                    precipitation: <%= node["precipitation"].Attributes["value"].Value%>
                    <br />
                    wind: <%= node["windSpeed"].Attributes["name"].Value%> - <%= node["windSpeed"].Attributes["mps"].Value%> mps <%= node["windDirection"].Attributes["code"].Value%>
                </p>
             </div>
        <%    }
            }
        }
    }
             %>
</div>
<div id="p-select" class="glidecontenttoggler">
<a href="#" class="prev"><img border="0" title="previous forecast" src="/Image.ashx?src=/Content/Media/Icons/24x24/previous.png&width=32" /></a><br /><a href="#" class="next"><img border="0" title="next forecast" src="/Image.ashx?src=/Content/Media/Icons/24x24/next.png&width=32" /></a>
</div>
<div class="forecastfooter">
    <small>
        Weather forecast from <a href="http://www.yr.no" target="_blank">yr.no</a> delivered by<br />the Norwegian Meteorological Institute and the NRK
    </small>
</div>

and the final view:

final

that’s really all there is to it.. some tweaking of the css and jQuery part to be more what i wanted it to be, but none the less it’s a fairly simple example and could hopefully give you some ideas or lend you some help.

external sources to this article:

Regards,

P.

No comments: