Displaying pushpins in Bing Maps Silverlight control using GeoRSS feeds

In this post I will briefly explain how to add pushpins in the Bing Maps control using a GeoRSS feed. Although there are many ways of adding pushpins (or any geographic shape for that matter) on a Bing Maps backdrop, I have found this by far the easiest and most flexible.

So assuming you have a spatial table (in this example the table is stored in a SQL Server 2008 database, but any database could be used) containing the points you want to plot. A sample of the table is shown below:

I wont go into any details on how to create a Silverlight project and implement Bing Maps, there are plenty of good examples around. You can start here or here.  Instead, we will first focus on how to create the GeoRSS handler to retrieve the shape data from the table. To do this, right-click on your Web project and select Add->New Item->Generic Handler and name it GeoRSSHandler.ashx.

Replace the default contents in the GeoRssHandler.ashx file with the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration;
using System.Data.SqlClient;
namespace GeoRSSAndBing.Web
{
///
/// Summary description for GeoRSSHandler
///
public class GeoRSSHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string connStr = ConfigurationManager.ConnectionStrings["spatialDB"].ToString();
context.Response.ContentType = "text/xml";
string type = context.Request["type"];
//Build the GeoRSS feed
System.Text.StringBuilder rssOutput = new System.Text.StringBuilder("");
rssOutput.AppendLine("");
rssOutput.AppendLine("");
rssOutput.AppendLine("");
rssOutput.AppendLine("POIS");
rssOutput.AppendLine("");
rssOutput.AppendLine("" + System.DateTime.Now + "");
SqlConnection conn = new SqlConnection(connStr);
conn.Open();
try
{
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandType = System.Data.CommandType.Text;
if (type == "POI")
{
//Change this statement depending on your settings
cmd.CommandText = "select Eva_Event_ID,EVA_EVENT_TYPE, EVA_EVENT_DESCRIPTION, shape.AsGml() as GeomGml from pois_wgs84";
}
//TODO: Add logic to handle lines and polygons
SqlDataReader sqlGeomRdr= cmd.ExecuteReader();
while (sqlGeomRdr.Read())
{
//Create an element for this row
rssOutput.AppendLine("");
//Set title and description
if (type == "POI")
{
rssOutput.AppendLine("" + sqlGeomRdr["EVA_EVENT_ID"].ToString() + "");
rssOutput.AppendLine("");
rssOutput.AppendLine("");
}
//TODO: Add logic to handle lines and polygons
rssOutput.AppendLine("");
//Get the geography instance GML from column 2
string gml= sqlGeomRdr["GeomGml"].ToString();
//Append the gml: prefix to all the elements due to VE parsing behavior
if (type == "POI")
{
gml = gml.Replace("<Point", "<gml:Point");
gml = gml.Replace("pos", "gml:pos");
gml = gml.Replace("</Point", "</gml:Point");
}
//TODO: Add logic to handle lines and polygons
Add the elements to the output XML
rssOutput.AppendLine(gml);
//Close and elements
rssOutput.AppendLine("");
rssOutput.AppendLine("");
}
rssOutput.Append("");
context.Response.Write(rssOutput.ToString());
}
catch (Exception ex)
{
throw ex;
}
finally
{
conn.Close();
}
}

Next, add a [Show Locations] button in your XAML such as:

<Button Cursor=”Hand” Width=”195″ Height=”25″ HorizontalAlignment=”Left” Content=”Show Locations” x:Name=”b1″ Margin=”2,10,0,1″ Click=”b1_Click”></Button> 
 
 The implementation of the Onclick event in the code behind will call the handler we just created asynchronously i.e.:
 
 private void b1_Click(object sender, RoutedEventArgs e)
 {
   Uri url = new Uri(“../GeoRSSHandler.ashx?type=POI”, UriKind.Relative);  WebClient client = new WebClient();  
    client.DownloadStringCompleted +=new DownloadStringCompletedEventHandler(client_DownloadStringCompleted); 
   client.DownloadStringAsync(url);
 } 

Finally, the downloadString Event handler code is the bit that draws the pushpins on the map:

 void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)

{ if (e.Error == null)

{ StringReader stream = new StringReader(e.Result); 

 XmlReader reader = XmlReader.Create(stream); 

 string gmlURI = http://www.opengis.net/gml&#8221;;map.Children.Clear();

 while (reader.Read()){

   if (reader.NodeType == XmlNodeType.Element)  {

    if (reader.NamespaceURI == gmlURI && reader.Name == reader.Prefix + “:pos”)

   {

      string[] loc = reader.ReadInnerXml().Split(” “.ToCharArray()); 

      double lat = Double.Parse(loc[1]); 

      double lon = double.Parse(loc[0]); 

      Pushpin p = new Pushpin();p.Location =new Location(lat, lon);map.Children.Add(p);
  }}}}} Next, compile and run your application. Assuming you set the initial Bing Map extent correctly, (i.e. around the area where your points are), clicking on the [Show Locations] button should display your points on Bing Maps using the Default pushpin.

The great thing with this approach, is that you can easily add logic to either add other shape types such as polygons or lines, or retrieve data from other databases by simply amending the code in the GeoRSS handler.

Silverlight interface for Mapserver GIS

Yes, such a beast does exist! And I am proud to say that Gaiocorp (ok, a bit of not-so grey advertising) has developed it and released is as an open source project in CodePlex (http://slmapviewer.codeplex.com). Lovingly called SLMapViewer, it is a reusable Silverlight map control for use with MapServer, the popular open source WebGIS. It was initially developed as  the the mapping component for some of Gaiocorp’s products and was based on the MapFlash Viewer Framework’s concept  developed by Paolo Corti. Since then it has changed almost beyond recognition, supporting a large number of ‘GIS-like’ functions, different ‘view modes’ to be used as either through a normal browser or in a street kiosk, and extendable helper classes so developers can implement their own business logic for layer display. SLMapviewer can be used as a standalone map viewer or be embedded it and thus spatially enable, ASP.NET or Silverlight applications.

Passing values between the Silverlight Bing Map Control and aspx pages

 
… or how to easily embed Bing Maps into your existing ASP.NET application. So lets assume you have your fully working ASP.NET app which contains Datagrids dataforms, etc and you now want to add that little extra spatial glitz by embedding Bing Maps. Assume also that your data contains street addresses or Latitude/Longitude pairs. The most common thing that you would want to do is to be able to zoom to the map to a point which -say- represents the location of the selected record in a datagrid and/or click along a pushpin on the map which represents a record in your database and display its attributes in the aspx page.
 
The technique I propose here is -hopefully- simple and easy to implement. Note that this is just a generic approach. Depending on your data and business logic you should be able to change it accordingly. 
It can be broadly described using the following steps:
To Zoom to the Map by clicking a button on the ASPX page:
  1. When clicking the button a hidden field in the aspx page is populated with the Lat/Lon values of the currently selected Gridview/DataForm record  
  2. The Update panel that contains the Silverlight control gets updated.
  3. This will cause the Silverlight control to get refreshed and pass through the Page_Load event
  4. The Page_Loaded event reads the value of the hidden field in the aspx page, converts them into a Bing Maps Location object and zooms the map to that location.
To display the attributes of a location by clicking on the map
  1. User clicks on the map
  2. The MouseLeftButton down event is fired (either on the map or a layer) which populates the hidden Textbox in the aspx page.
  3. The Textbox is populated with either the ID of the record or the Lat/Lon values of the click point.
  4.  The OnTextChanged() event on the textbox is fired which calls the relevant business logic function to display the attributes.
To implement the above: 
In your asp.net page make sure you enclose the Bing Maps control in an update panel as below (Note: The following code uses the Silverlight 2 control. Hasn’t been tested yet with Silverlight 3 proposed object tag but it should work.
 
 <asp:UpdatePanel ID=”upnlSL” runat=”server” UpdateMode=”Conditional”> 
<ContentTemplate>
  <div id=”BingMapInterface” style=”display:none; width:0px; position:absolute;top:-800px; “>
  <asp:TextBox ID=”txtBingMapRequests” runat=”server” Width=”100″ onblur=”javascript:Changed(this);” OnTextChanged=”txtBingMapRequests_OnTextChanged” AutoPostBack=”true”></asp:TextBox>
 </div></ContentTemplate></asp:UpdatePanel>  
 Notice that the whole update panel has been enclosed in a <div> tag with the display attribute set to none so it won’t show on the page. Also note that the txtBingMapRequests, the textbox that will receive the Lat/Lon coordinates has an onblur() event as well as a OnTextChanged event. This will ensure that a postback on the page will occur as soon as the textbox value is changed.
The onblur() javascript event is:
<script type=”text/javascript”> 

function Changed(textControl) {“upnlSL”, null);}  

</script>  
The OnTextChanged event is implemented as: 
 protected void txtBingMapRequests_OnTextChanged(object sender, EventArgs e){ 
 string  s = txtBingMapRequests.Text; 
  upnlBingMap.Update();
}
 By causing the BingMaps update panel to update the Page_Loaded function in our Silverlight project will fire which can be similar to:
void  Page_Loaded(object sender, RoutedEventArgs e)
{
//Get the value of the txtBingMapRequest textbox
HtmlElement txtRequest = HtmlPage.Document.GetElementById(“txtBingMapRequests”); 
if (txtRequest.GetProperty(“value”).ToString() != string.Empty)
{
try
{string[] coords = txtRequest.GetProperty(“value”).ToString().Split(‘;’); 
if (coords.Length == 2) {
string lon = coords[0];
string lat = coords[1]; 
Location poiLoc = new Location(Convert.ToDouble(lon), Convert.ToDouble(lat));map.SetView(poiLoc, 18);
}
}
catch (Exception){}
}
Note that we assume that the value of the textbox is a string which holds the Lat/Lon coords separated by a ‘;’
 
We can implement the reverse in a similar way so when you click on the map the aspx page gets updated with the selected location/record.