Custom Traffic Maps with Yahoo Traffic RSS,
MSN Virtual Earth Maps, and
Remote Scripting [AJAX=NOT!]

by Peter A. Bromberg, Ph.D.

Peter Bromberg

"Rudeness is the weak man's imitation of strength" -- Eric Hoffer

Last year (2004) I went to San Diego Tech-Ed, and I've always remembered one of the coolest apps that one developer put together for the Pocket PC. He got some traffic data from the local provider (CALTRAN or whatever it is in Southern CA) and plotted it in colors over a map route of his trip into San Diego. There were three color codes - green, yellow, and red. Anyone who is familiar with San Diego traffic on a weekday morning can guess what color most of the markers were! Aside from B.J. Holtgrew's wild taxi ride over the border into Tijuana chasing their runaway VSTO promotional blimp, most San Diegans are pretty much resigned to the traffic crunch.



I always wanted to provide something similar as a sort of public service and developer tutorial, but wasn't able to find the traffic data, until recently. It seems that Yahoo has resurrected their traffic data and now provides reports on most major cities in RSS format. Here's a sample URL:

http://maps.yahoo.com/traffic.rss?csz=32801&mag=3&minsev=1

The csz parameter above is a zip code (or a location such as city state) ; the mag = number of miles range to show, and the minsev parameter is the minimum severity of accidents and incidents to show. You can click on the link if you want to see the actual RSS XML Document that it returns. If you do, note carefully that the "link" element in each "item" aggregate has a URL-style delimited string of information that includes the latitude and longitude coordinates of the actual incident.

"Well," I thought - "RSS means I can handle this traffic stuff as easy as falling off an overpass!". If you've followed any of my previous articles dealing with RSS you may remember that a key factor is the ability to use the DataSet's built-in ReadXml method to consume the entire RSS feed programmatically in one fell swoop! That's right, you just point the ReadXml method at your "preconstructed" Yahoo traffic URL and querystring, and Presto! -- you have yourself a DataSet. As our friend and contributor Dr. Dotnetsky would probably say, "That's slicker'n snot on a doorknob!" The only thing remaining is to figure out how to parse out what you need. In the case of Yahoo Traffic, we don't even need any Regex, because we can String.Split our way into Traffic Heaven.

The other interesting thing that came about recently is the rollout of MSN Virtual Earth, the API, and the Developer help pages and site. Google Maps is great, Virtual Earth could be even better. Why? A very simple Javascript-based API, lots of good features, and in some cases, the maps and aerial photo tiles are better than Google's. Plus, you don't need a license key. The commercial license requires only that you include the MSN widgets on your map, which eventually will be used to serve ads. If you want to see what it all looks like, visit the MSN Virtual Earth site here.

As a side note, I should probably mention that my MVP buddy and co - founder of this site, Robbe Morris and I decided we'd put the complete (17 Million + location records) optimized US Census TIGER geolocator database online so that we could start to provide geocoding services. We now have our database in full operation, with a complete .NET WebService interface, so if you have data that you need to be geocoded or you have a business idea you'd like to explore for a possible joint venture, be sure to let us know. We'll provide trial license keys to the GeocoderService upon request. Our geocoder service returns latitude / longitude coordinates of any parsed address in the United States with six digits of precision - tight enough to plot a geolocation point 300 feet or less from your house!

But, I digress. Let's get into the code!

The final piece is Remote Scripting. And of all the offerings out there, Jason Diamond's "MyAjax.Net" is in my opinion, the best. Why? Simple. The code is elegant and mature, its only about 450 lines, which means you can actually INCLUDE 100% of it right inside a UserControl (which I do here), and above all, it maintains and roundtrips that stateful ASP.NET page and the controls in it, which is so important to the concept of Remote Scripting with ASP.NET, without relying on external URL's or HTTPHandlers. The version I use here is something around version 6 and I've changed the namespace "back to" RemoteScripting, for reasons I don't feel it's necessary to repeat. However, Jason's latest version is currently up to number 10, and most of the additions have not been "fluff". I've also included a second solution download using the newest "10" version, with the Remote Scripting class in it's normal "habitat" - outside of the control, and with no changes whatsoever to his distributed code. The whole concept here is that if you select some stuff and press a button, you shouldn't have to have an annoying page reload. We should be able to go get our "stuff" (in this case a real DataTable from our DataSet I referred to above) and work with it in the client-side state of the page, updating the DOM and using the Virtual Earth MAP controls. You see Jason's marvelous piece of work, which already has several contributors, here. One thing developers need to think about with all this Remote Scripting / Ajax stuff is that its easy to "go overboard". Think about the user experience, how your application will behave online, and use Remote Scripting judiciously where appropriate.

Before we get into the actual code, if you are curious, you may wish to take a quick look at my live sample here.

If you looked at the above, you realize that the visible UI is very simple; we have a textbox for zip code, and some dropdowns for the minimum severity level of the incidents (accidents) to show, miles range to show, the Virtual Earth Map initial zoom factor, and a button to make it all go. Since this stuff is trivial for most developers, I'll skip to the goodies - what happens when you press the "MAP!" button.

In the very top of the User Control, I add a "helper" variable that allows me to identify the UniqueID of the control:

Page.RegisterStartupScript("ctrlId","<script>var ctrlId='"+this.ClientID +"_';</script>");

Then I have the server-side C# RemoteScripting.Method attributed ("Ajax.Method") method that is called when the button is pressed:

[Method]
  public DataTable  PopulateZipData(string zipCode,int miles,int severity)
  {
  string url1="http://maps.yahoo.com/traffic.rss?csz=";
   string url2 ="&mag=";
     string url3 ="&minsev=";
   DataTable dt2=new DataTable() ;
    // make the call and do map
    DataSet ds = new DataSet();
    string fullUrl =url1 +this.txtZip.Text +url2 + miles.ToString() 
     + url3 + severity.ToString() ;    
    ds.ReadXml(fullUrl) ;
    DataTable tbl = ds.Tables[2];
    dt2.Columns.Add("latitude");
    dt2.Columns.Add("longitude") ;
    dt2.Columns.Add("title");
    dt2.Columns.Add("description");
    dt2.Columns.Add("severity") ;
    DataRow row1=null;
    foreach(DataRow row in tbl.Rows )
    {
     row1 = dt2.NewRow() ;
     row1["title"] =row["title"];
     row1["description"] = row["description"];
     row1["severity"] = row["severity"];
     string[] gotRow = row["link"].ToString().Split('&') ;
     foreach (string s in gotRow)
     {
      if(s.IndexOf("mlt=")>-1)
      {
       string[] mltStr = s.Split('=') ;
       row1["latitude"] = mltStr[1];
      }
      if(s.IndexOf("mln=")>-1)
      {
       string[] mlnStr = s.Split('=') ;
       row1["longitude"] = mlnStr[1];
      } 
     }
     dt2.Rows.Add(row1);
    }
   Session["mapds"]=dt2;
   return dt2;
  }

I'm pretty sure I don't need to explain much about the above; as mentioned it populates a DataSet from the concatenated Yahoo Traffic URL to the RSS feed, and then it creates a new Datatable and does some more parsing and string splitting to get "latitude" Longitude" "title", "description" and "severity" columns with a row for each incident that the Yahoo feed brings back. Then, I store it in Session just in case I want to do something else with it such as transfer to a different page. I also return the DataTable from the method.

Incidentally, you can still use the XmlReader overload of DataSet's ReadXml method if you are behind a proxy / firewall such as ISA Server. Here's how:

// proxy code:
HttpWebRequest rqst =
(HttpWebRequest)WebRequest.Create(fullUrl);
WebProxy loProxy = new WebProxy("yourISAServer:8080",true);
loProxy.Credentials = new NetworkCredential("username","passs");
rqst.Proxy = loProxy;
HttpWebResponse rsp = (HttpWebResponse)rqst.GetResponse ();
XmlTextReader rdr = new XmlTextReader(rsp.GetResponseStream());
ds.ReadXml(rdr) ;

The rest of the server-side codebehind for the control is Jason Diamond's "myAjax.net" code, virtually untouched, except with the namespace changed "back" to RemoteScripting.

Now, we switch on over to the client side, where our callback method receives the Datatable:

<script>  
function PopulateWithDataTable() {
//alert(ctrlId);
var zip = document.getElementById(ctrlId+'txtZip').value;
 if(zip.length <5) return; // need 5 digit zip code dood.
 
 var slM =document.getElementById('slMiles');
 var slMiles=slM.options[slM.selectedIndex].value;
 var slS=document.getElementById('slSeverity');
 var slSeverity = slS.options[slS.selectedIndex].value;   
 // show the user a message while we make the out-of-band call...
 document.getElementById(ctrlId+"lblZipInfo").innerText = "Getting Data...";         
 VETraffic.Control.PopulateZipData(zip, slMiles,slSeverity,DoTableCallBack);    
} 

function DoTableCallBack(result) {
// here is our callback where we process our return data in the "result" object
var table=result.value; // yup, it gave us a DataTable in script.          
/*
    VE_MapControl(Latitude,  Longitude, Zoom, MapStyle, PositionType, 
        Left, Top, Width, Height); 
*/    
     if (table==null) 
     {
      document.getElementById(ctrlId+"lblZipInfo").innerText = 
         "No Data for selection.";  
     return null;     
     }    
var width = screen.width
var height = screen.height
var zoom = document.getElementById("slZoom");
var zoomlevel = zoom.options[zoom.selectedIndex].value;     
map = new VE_MapControl(table.Rows[0].latitude, table.Rows[0].longitude, zoomlevel, 
              'r', "absolute", 0, 280, width, height);              
            // add a location marker  
var descr= '';
var sevr='';
var titl='';
var popstr="";
for(var i=0;i<table.Rows.length;i++)
{         
descr=table.Rows[i].description ;
sevr= table.Rows[i].severity;
titl=table.Rows[i].title;
map.AddPushpin('pin'+i, table.Rows[i].latitude, table.Rows[i].longitude, 
    80, 110,'pin2',"<div id=pinmarker"+i+" title='"+ titl 
        + descr + "'>" + sevr + "</div>");}   
document.getElementById(ctrlId+"Panel1").appendChild(map.element);
document.getElementById(ctrlId+"lblZipInfo").innerText = "Done.";           
 } 
</script>

As can be seen above, the RemoteScripting infrastructure has marshalled the server-side call back into a javascript object that comes back into the page, looking, feeling and acting just like a server - side DataTable. The rest of the code is simply to use the Virtual Earth API to create a map, and add the "pushpin" location marker objects. The HTML "title" attribute of the div tag is used to show the mouseover message describing the Title and Description fields of the traffic item, and the visible text of the pushpin is the Severity column. So, it makes for a very compact UI on a zoomable, pannable map.

I've seen others do stuff like this and use up incredible amounts of javascript to show custom - styled javascript popups with timers and such. I assure you, this is completely unnecessary. I believe "less is more", and the title attribute, which is present in most HTML elements, will work just fine.

I'm already using this before I leave for work every morning, and it has been very helpful to me. I encourage you to create your own. Please read the Virtual Earth Terms of Service, as you are required to use the commercial format (with MS's "widgets" on the map) for commercial purposes. Then, you hopefully won't see stuff like this:

I have two solutions you can download today. First is the original i present in the article, and the second is the identical solution, but compiled with Diamond's My Ajax.Net version 10 (the newest) library, with his "Ajax" Remote Scripting class outside of the control.

 



Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.
Article Discussion: