Building a Webpart to Display a Virtual Earth Map

Welcome to this installment of the .NET Nuts & Bolts column. The focus of this article will be on building an ASP.NET webpart that will display coordinates on a Virtual Earth-created map. It will involve using the IWebPart Interface, an HTTP handler, and the Virtual Earth API.

Necessary Components and Setup

The items you will read about in this article came about as a result of some project work that I've been doing for the Indianapolis Colts. I've been helping to lead efforts to build and launch myColts.net, a social networking site for Colts fans. I got in to a discussion at the Microsoft MVP summit around the idea of a mashup, which is about using multiple online services to create a new one; that inspired me to play around with Virtual Earth.

The webpart display in the example relies on the native functionality contained within ASP.NET. For the purposes of this article, I'm not going to go in to all of the background setup that took place to get the web site set up to display webparts. Rather, I'm going to assume that the readership already has a site setup that will display a webpart. Worst case, you could just as easily toss the webpart portion and put the code in a page instead.

Microsoft Virtual Earth

Microsoft Virtual EarthTM is a set of services that can be used for mapping, location, and search functionality. It can be used for mapping and creating a visualization experience. It contains bird's eye, 3D, and other types of imagery. Microsoft uses it to power their Windows Live Local online local search and mapping tool. There is an Interactive SDK that is available along with a number of articles, forums, and blogs. I used this site to grab example code for how to display a virtual earth map, which consists of some simple JavaScript to include from http://dev.virtualearth.net/mapcontrol/v4/mapcontrol.js. You'll include the base JavaScript along with additional JavaScript to load your display.

To produce a map using Virtual Earth, you need address data to feed in to create the map. There is a geography-related RSS format that is widely accepted and can be used. In this example, you'll display a map of the location of the top groups on myColts.net. For this, I used the zip code of the group locations and got the latitude and longitudinal data from a geo-targeting data provider. Here is a data sample with a few records.

<rss version=\"2.0\" xmlns:geo=\"http://www.w3.org/2003/01/geo/
     wgs84_pos#\">
<channel>
   <title>Group Addresses</title>
   <item>
      <title>Help</title>
      <description>Category: Computers &amp; Internet,
                             Members: 2136,
                             New Members: 58</description>
      <geo:lat>39.8512</geo:lat>
      <geo:long>-86.2651</geo:long>
   </item>
   <item>
      <title>ColtsDrive</title>
      <description>Category: Recreation &amp; Sports, Members: 95,
                             New Members: 2</description>
      <geo:lat>39.8884</geo:lat>
      <geo:long>-86.3109</geo:long>
   </item>
   <item>
      <title>Tailgaters</title>
      <description>Category: Recreation &amp; Sports, Members: 38,
                             New Members: 1</description>
      <geo:lat>44.8139</geo:lat>
      <geo:long>-93.9194</geo:long>
   </item>
   <!--Repeat data cut-->
</channel>
</rss>

Http Handler

The data feed you will use for Virtual Earth is based on an RSS feed. To create the RSS feed within myColts.net, you use an HttpHandler. A custom HttpHandler allows you to have requests with a specific filename extension assigned to the custom handler. The custom extension in this case was arbitrarily set up as .geodata and registered as such in the web.config. Please refer to my prior article, "Use Custom HTTP Handlers in Your ASP.NET Applications," for more information on handlers. When you build your display control, you'll have it request a file with a .georss file extension on it, which in turn triggers the HttpHandler to dynamically build the RSS output necessary to feed Virtual Earth.

The following entry was put in the web.config to register the handler.

<add verb="*" path="*.georss"
              type="MyColts.Net.Handler.GeoData"
              validate="false"/>

The following example code should give you an idea of how I went about producing the RSS feed that is required. You'll have to adjust the part of the code where I retrieve the data to match your own. You could just as easily feed the static XML to make it easy for testing purposes. In this example, I'm using a strongly typed dataset to retrieve data from the database. I then create an XML output. I could have used the native capability of SQL Server to create XML output, but decided just to use a base query for those who may not have SQL Server. Kindly replace the data retrieval with whatever data source you choose.

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml;

namespace MyColts.Net.Handler
{
   /// <summary>
   /// Http Handler to provide access to geotargeting data.
   /// </summary>
   public class GeoData : IHttpHandler
   {
      /// <summary>Indicator if the handler can be
      /// reused.</summary>
      public bool IsReusable { get { return true; } }

      /// <summary>
      /// Constructor
      /// </summary>
      public GeoData()
      {
      }

      /// <summary>
      /// Process the request for the gallery image.
      /// </summary>
      /// <param name="context"></param>
      public void ProcessRequest(HttpContext context)
      {
         // Send back a list of addresses for groups
         System.Text.StringBuilder rssOutput =
            new System.Text.StringBuilder(
            "<channel><title>Group Addresses</title>");
         try
         {
            using (TopGroupsTableAdapter gta =
                   new TopGroupsTableAdapter())
            {
               using (TopGroupsDataTable dt =
                  gta.GetAddressByTop50()
               {
                  double latitude = 0;
                  double longitude = 0;
                  foreach (TopGroupsRow groupRow in dt.Rows)
                  {
                     try
                     {
                        latitude = groupRow.fl_Latitude;
                        longitude = groupRow.fl_Longitude;
                     }
                     catch
                     {
                        // Default to Indianapolis
                        latitude = 39.76833;
                        longitude = -86.15806;
                     }

                     // Add the group
                     rssOutput.Append("<item>")
                              .Append("<title>")
                              .Append(groupRow.vc_Name)
                              .Append("</title>")
                              .Append("<description>")
                              .Append("Category:")
                              .Append(groupRow.vc_CategoryName)
                              .Append(", Members: ")
                              .Append(groupRow.in_MemberCnt)
                              .Append(", New Members: ")
                              .Append(groupRow.in_NewMemberCnt)
                              .Append("</description>")
                              .Append("<geo:lat>")
                              .Append(latitude)
                              .Append("</geo:lat>")
                              .Append("<geo:long>")
                              .Append(longitude)
                              .Append("</geo:long>")
                              .Append("</item>");
                     }
                  }
               }
               rssOutput.Append("</channel>");

               XmlDocument document = new XmlDocument();
               document.LoadXml("<rss version='2.0'
                                 xmlns:geo='http://www.w3.org/2003/
                                 01/geo/wgs84_pos#'></rss>");
               XmlElement element = document.DocumentElement;
               element.InnerXml = rssOutput.ToString();

               context.Response.Write(document.DocumentElement.
                                      OuterXml);
            }
            catch
            {
               System.Text.StringBuilder emptyOutput =
                  new System.Text.StringBuilder
                  ("<?xml version='1.0' ?>")
                  .Append("<rss version='2.0'
                          xmlns:geo='http://www.w3.org/2003/01/geo/
                          wgs84_pos#'><channel>
                          <title>Group Addresses</title>
                          </channel></rss>");
               context.Response.Write(emptyOutput.ToString());
            }
         }
         context.Response.ContentType = "text/xml";
      }
   }
}

Building a Webpart to Display a Virtual Earth Map

Webpart Display

Building an ASP.NET webpart requires that you have webpart zones and other plumbing set up within your appliation. This is likely to be a topic of another article if enough people are interested, but for now you'll skip the background. The following sample control is a pretty basic webpart that doesn't do much more than get added to the display and render a map from the appropriate data source.

<%@ Control Language="C#"
            AutoEventWireup="true"
            CodeBehind="CompositeVirtualEarthMap.ascx.cs"
            Inherits="MyColts.Net.Composite.
                      CompositeVirtualEarthMap" %>
<script type="text/javascript"
        src="http://dev.virtualearth.net/mapcontrol/v4/
             mapcontrol.js"></script>
<script type="text/javascript">
   var map = null;
   var layerID = 1;

   function GetMap()
   {
      map = new VEMap('myMap');
      map.LoadMap();
      map.HideDashboard();
      AddMyLayer(VELayerType.GeoRSS);
   }

   function AddMyLayer(type)
   {
      if( layerID > 1 )
      {
         map.DeleteAllPushpins();
         map.DeleteLayer(layerID - 1);
      }
      var veLayerSpec = new VELayerSpecification();
      veLayerSpec.Type = type;
      veLayerSpec.ID = layerID;
      veLayerSpec.LayerSource = "./geoaddress.geodata?mode=" + mode;
      veLayerSpec.Method = 'get';
      map.DeleteAllPushpins();
      map.AddLayer(veLayerSpec);
      layerID++;
   }

   function ShowGroups()
   {
      AddMyLayer(VELayerType.GeoRSS);
   }
</script>
<table cellpadding="0"
       cellspacing="5"
       border="0" width="948px"
       style="border:1px solid #e6e5e5;">
   <tr>
      <td valign="top" style="width:698px">
         <table cellpadding="0"
                cellspacing="0"
                border="0"
                width="100%">
            <tr>
               <td>
                  <div id='myMap' width:100%; height:400px;"></div>
                  <a href="#" onclick="ShowGroups();">
                     Show Top 50 Groups Map</a>&nbsp;
               </td>
            </tr>
         </table>
      </td>
   </tr>
</table>

The following code is from the codebehind file associated with the item.

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

namespace MyColts.Net.Composite
{
   public partial class CompositeVirtualEarthMap :
      System.Web.UI.UserControl, IWebPart
   {
      #region IWebPart stuff
      public string Title
      {
         get { return "Group and Friend Virtual Earth Mashup"; }
         set { }

      }

      public string Subtitle
      {
         get { return ""; }
         set { }
      }

      public string Description
      {
         get { return ""; }
         set { }
      }

      public string TitleUrl
      {
         get { return ""; }
         set { }
      }

      public string TitleIconImageUrl
      {
         get { return ""; }
         set { }
      }

      public string CatalogIconImageUrl
      {
         get { return ""; }
         set { }
      }
      #endregion

      protected void Page_Load(object sender, EventArgs e)
      { this.Page.ClientScript.RegisterStartupScript
         (typeof(CompositeVirtualEarthMap), "LoadMapStartup",
          "window.onload = function () { GetMap(); }; \r\n", true);
      }
   }
}

Figure 1 below shows what the end product looks like, based on the current data.

[VirtualEarth.jpg]

Figure 1: Webpart Display with Map

Summary

This month's column covered how to build a webpart to display a map produced from Microsoft Virtual Earth. It used an HttpHandler to produce the RSS feed format to drive Virtual Earth. It can appear daunting at first, but hopefully, as you've seen, it is pretty straightforward when you break down the individual parts of the solution.

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, you could reach me at mstrawmyer@crowechizek.com.



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Corporate e-Learning technology has a long and diverse pedigree. As far back as the 1980s, companies were adopting computer-based training to supplement traditional classroom activities. More recently, rich web-based applications have added streaming audio and video, real-time collaboration and other new tools to the e-Learning mix. At the same time, the growing availability of informal learning tools--a category that includes everything from web searches to social media posts--are having a major impact on …

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds