Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Lat Lays Flat - Part 2: Advanced Google Maps .NET Control Usage

4.74/5 (46 votes)
17 Oct 200511 min read 3   7K  
Advanced usage of an ASP.NET server control wrapper for the Google Maps API.

Introduction

This is the second article in a three article series examining a custom ASP.NET server control I developed to make using the Google Maps API easier for .NET developers. This article assumes you have read Part 1 and are familiar with the Google Maps API. You may see references throughout the article to "my control, my GoogleMaps control, my GMap control, etc". I did not create the Google Maps API, I merely created a wrapper for ASP.NET using C#, XML, and XSL.

The main goal of this article is to show you just how powerful a .NET wrapper can be for the Google Maps API. Everything I will cover in this article could be done with other technologies like JavaScript, PHP, Java, etc. But aren't you glad you don't have to use any of those? This also uses an object oriented approach to Google Maps interaction.

The advanced features covered in this article include:

  • Overlay data binding – Bind your database lat/lng values directly to a GMap as overlays.
  • Client call backs – Run the server side code as a result of client side GMap events.
  • GMap postback state – Get some data about your GMap after a postback/callback.

Setup and configuration

Maps are pretty boring without some accompanying GIS data. The three advanced examples I will show you make use of approximately 5,000 data points. Included in the code download is the SQL script required to create the MS SQL Server database and tables used in the examples. Any database can be used, Access, MySQL, FireBird etc. The database included in the download is not an example of good DB design, just something to use for the article purposes. Data used in these examples is freely available (in raw form) from many Internet sites. If you'd like a copy of my massaged data, drop a line on the message board at the bottom of the article (and give me some feedback in the form of a rating).

The good stuff

Included in the downloadable code (see Download source files above) is a web project called TestWeb with three (3) advanced examples on how to use my GMap control in your applications. If you have downloaded the code from Part 1, you can unzip the code for Part 2 right on top of it. In the course of creating the advanced examples, some changes were made to the GMap control and the associated XSL and JavaScript files. None of these changes will break any of the basic examples from Part 1. If you have been experimenting with the control on your own, you can see the documented changes in the changes.txt file. You can find all the three examples here.

ColoradoCounties.aspx

__Show Me__

Image 1

This example makes use of the client callback and overlay data binding advanced features of the GMap control. If you're not familiar with client callbacks, check out my article on Asynchronous JavaScript and XML (AJAX) framework. For a refresher on data binding check out Mastering ASP.NET Data Binding.

The code snippet below creates a GMap control and turns on client callbacks by setting EnableClientCallBacks to True. The code also assigns the event handler gMap_MarkerClick() to the MarkerClick event of the GMap.

ASP.NET
<wcp:GMap runat="server" id="gMap" Width="750px" Height="525px" 
  EnableClientCallBacks="True" OnMarkerClick="gMap_MarkerClick"/>

Now, anytime a map marker is clicked, the server-side code found in gMap_MarkerClick() will be executed. Sweet! The table below shows the complete list of server-side events that can be implemented as client callbacks:

Event nameEvent arguments
GMap.ClickSender – The GMap clicked
GPointEventArgs – The GPoint where the GMap was clicked.
GMap.MarkerClickSender – A GMarker instance representing the marker clicked.
GPointEventArgs – The GPoint where the marker was clicked.
GMap.MoveStartSender – The GMap that was moved.
GPointEventArgs – The GPoint representing the center lat/lng coordinate of the GMap at the start of the move.
GMap.MoveEndSender – The GMap that was moved.
GPointEventArgs – The GPoint representing the center lat/lng coordinate of the GMap at the end of the move.
GMap.ZoomSender – The GMap that was zoomed.
GMapZoomEventArgs – The old and new zoom values.

Also included on the page are some client side events. The code below will open an info window on the map whenever a marker is clicked. The info window will contain the ID of the marker and the word "County". In ColoradoCounties.aspx.cs, we'll examine the code behind that assigns the ID to the marker.

JavaScript
function GMarker_Click()
{
  this.openInfoWindowHtml(this.id + " County");
}

The code below will execute whenever the info window is closed. The code searches the map for a polyline of the same name as the marker. If the polyline is found, it is removed from the map. This is the code that will clear the county boundary from the map:

JavaScript
function GMarker_InfoWindowClose()
{
  var pLineId = this.id + "Boundary";
  var pLine = this.map.getOverlayById(pLineId);
  if( pLine )
    this.map.removeOverlay(pLine);
}

ColoradoCounties.aspx.cs

This is where the magic happens. When the page loads, the code first centers the map over Colorado with an appropriate zoom level. Then the code queries the database to get the Name, Latitude, and Longitude of each county in Colorado (see Setup and configuration above for more information on the database). The data returned is in the form of a DataSet which is then assigned to the DataSource of GMap. Finally, we tell the GMap which database columns correspond to the MarkerId, Longitude, and Latitude for each county. When the GMap is rendered, it will add a marker using the database values for location and ID.

C#
gMap.CenterAndZoom(new GPoint(-105.5F, 39F), 10);
      
DataSet ds = GetCounties();

gMap.DataSource = ds;
gMap.DataMarkerIdField  = "CountyName";
gMap.DataLongitudeField = "Longitude";
gMap.DataLatitudeField  = "Latitude";
gMap.DataBind();

gMap.AddControl(new GSmallMapControl());

Remember when we assigned gMap_MarkerClick() to the MarkerClick event in the ASPX page? Now we'll examine the code associated with that event handler. The goal here is to read the lat/lng values from the database that represent the boundary of each county. We will take those values and translate them into a GPolyline that will show the boundary in red on the GMap. For performance reasons, we use caching to speed up client call backs.

C#
protected string gMap_MarkerClick(object s, GPointEventArgs pea)
{
  GMarker gm = s as GMarker;
  if( gm != null )
  {
    string gpTransform = Cache[gm.Id] as string;
    if( gpTransform == null )
    {

If the boundary isn't already cached we query the database for the lat/lng values. Because in our page load we used data binding to assign the county name to the marker ID, we use that same value (gm.Id) to query the database for the boundary values.

C#
GPolyline gp = GetBoundaryByCounty( gm.Id );
gp.Color = _BoundaryColor;
gp.Weight = _BoundaryWeight;
gp.Opacity = _BoundaryOpacity;

Finally, we use the XSL style sheet used to transform the GMap control into JavaScript to turn our new GPolyline boundary into JavaScript. That JavaScript is returned to the client where it is then executed using the JavaScript eval() function. Viola! We have a county boundary. __Show Me__.

C#
      string path = Server.MapPath("../Scripts/GMap.xsl");
      gpTransform = GXslt.Transform(gp, path, gMap.GetXsltArguments());
      Cache[gm.Id] = gpTransform;
    }
    return gpTransform;
  }
  return String.Empty;
}

StateQuarters.aspx

__Show Me__

Image 2

This example makes use of the client callback and overlay data binding advanced features of the GMap control. I re-engineered the US Mint 50 States Quarter Program using the GMap control. When you click the marker representing a state, you will see some state information along with a graphic of the state's quarter. The page itself is just some style sheet information and the GMap control.

ASP.NET
<wcp:GMap runat="server" id="gMap" Width="750px" Height="525px" 
  EnableClientCallBacks="True" OnMarkerClick="gMap_MarkerClick"/>

Again, note the EnableClientCallBacks and the OnMarkerClick event handler assignment.

StateQuarters.aspx.cs

When the page loads the GMap is centered on the United States. In a similar manner to the ColoradoCounties example, we query the database to get the two-letter state abbreviation and the latitude and longitude coordinates for each state. This data is bound to the GMap which results in a marker being added for each state. The Id of each marker is set to the abbreviation of the respective state.

C#
gMap.CenterAndZoom(new GPoint(-93.69141F, 40.11169F), 13);

DataSet ds = Cache["States"] as DataSet;
if( ds == null )
{
  ds = StateQuarter.GetStates();
  Cache["States"] = ds;
}
gMap.DataSource = ds;
gMap.DataMarkerIdField = "Abbreviation";
gMap.DataLongitudeField = "Longitude";
gMap.DataLatitudeField = "Latitude";
gMap.DataBind();

gMap.AddControl(new GSmallMapControl());

The code for the MarkerClick event takes the Id of the marker that was clicked and queries the database for the complete state information including: state name, state abbreviation, date of statehood, and the date the quarter was released.

C#
protected string gMap_MarkerClick(object s, GPointEventArgs pea)
{
  GMarker gm = s as GMarker;
  if( gm != null )
  {
    string gmInfoWindow = Cache[gm.Id] as string;
    if( gmInfoWindow == null )
    {
      StateQuarter sq = 
         StateQuarter.GetStateQuarterByAbbreviation( gm.Id );

This data is stored in a custom business object which is then transformed using the StateQuarter.xsl style sheet. The transformed HTML is passed to the GMarker.OpenInfoWindowHtml() method. This method creates the JavaScript that needs to be executed on the client side to open an info window over the marker that was clicked.

C#
string path = Server.MapPath("StateQuarter.xsl");
gmInfoWindow =
   gm.OpenInfoWindowHtml(GXslt.Transform(sq, path));

Finally, the info window JavaScript is cached for performance reasons and returned to the client which will actually display the info window. __Show Me__.

C#
      Cache[gm.Id] = gmInfoWindow;
    }
    return gmInfoWindow;
  }
  return String.Empty;
}

Handling a large number of markers

One of the biggest gripes I see posted regarding the Google Maps API is how slow maps become when adding a large number of markers. To most of those people I want to say "I'd ask Google for a refund of all the money you spent when you purchased the API". But seriously, JavaScript has its limits but we still need a way to represent a large number or markers.

There are a number of ways to do this including grouping markers within a certain proximity together at certain zoom levels. This can become a complex mathematical problem (for people much smarter than me to solve). The Accor Hotels example instead only loads the markers that are within the user's current viewing bounds, and removes them as the user pans to a different location. This doesn't completely solve the problem unless you restrict the user from zooming out. But the example below could be extended to only show the markers within the users viewing bounds and zoom level.

AccorHotels.aspx

__Show Me__

Image 3

This is the most complex example but possibly the most useful in terms of implementation in your own GMap applications. First, we'll start with the client side JavaScript. We implement three of the client side GMap events, GMap_AddOverlay(), GMap_RemoveOverlay(), GMap_MoveEnd(). Add/Remove Overlay are only implemented to give you a visual indication of when markers are added and removed as well as the number of markers visible within the current viewing bounds. In GMap_MoveEnd() whenever the user finishes panning the map, we loop through all of the overlays (or markers) and determine which ones are within the current bounds of the map. We remove any that are not within the viewing bounds.

JavaScript
function GMap_MoveEnd()
{
  var bounds = this.getBoundsLatLng();
  var numOverlays = this.overlays.length;
  for( var i=0; i<numOverlays; i++ )
  {
    var pnt = this.overlays[i].point;
    if( pnt.x < bounds.minX || pnt.y < bounds.minY ||
        pnt.x > bounds.maxX || pnt.y > bounds.maxY )
        this.removeOverlay(this.overlays[i]);
    numOverlays = this.overlays.length;
  }
}

The only other code on this page is the style sheet information, some labels, and the GMap control declaration. In this example we respond to the MoveEnd server-side event in addition to the MarkerClick event.

ASP.NET
<wcp:GMap runat="server" id="gMap" Width="750px" Height="525px"
  EnableClientCallBacks="True" OnMarkerClick="gMap_MarkerClick"
  OnMoveEnd="gMap_MoveEnd" />

AccorHotels.aspx.cs

The Page_Load() event for the Accor Hotels example is pretty boring. The only thing done here is to load a custom icon. A blue marker will be added to the map to represent a Motel 6 and the standard red marker will be added to represent a Red Roof Inn.

C#
private void Page_Load(object sender, System.EventArgs e)
{
  GIcon gi = new GIcon();
  gi.Id = "BlueMarker";
  gi.Image = new Uri(Global.BaseUri, 
                ResolveUrl("~/Advanced/blueMarker.png"));
  gMap.Icons.Add(gi);

  gMap.CenterAndZoom(new GPoint(-122.101944F, 37.401944F), 4);
  gMap.AddControl(new GSmallMapControl());
}

Whenever a marker is clicked, we use the Id of the marker to search for a custom business object that has the name of the hotel and the address of the hotel.

C#
protected string gMap_MarkerClick(object s, GPointEventArgs pea)
{
  GMarker gm = s as GMarker;
  if( gm != null )
  {
    string gmInfoWindow = Cache[gm.Id] as string;
    if( gmInfoWindow == null )
    {
      AccorHotel ah = GetHotelByName(gm.Id);

We then transform the business object and get the client side JavaScript needed to show the info window, just like the State Quarters example.

C#
      gmInfoWindow = gm.OpenInfoWindowHtml(HttpUtility.HtmlDecode( 
        GXslt.Transform(ah, path)));
      Cache[gm.Id] = gmInfoWindow;
    }
    return gmInfoWindow;
  }
  return String.Empty;
}

Things start getting interesting in the gMap_MoveEnd() event. This event loops through all of the hotels and adds markers to the map for every hotel that is visible in the current map bounds. First things first, we create a GOverlays object to temporarily store the new markers that will be added to the map.

C#
protected string gMap_MoveEnd(object s, GPointEventArgs pea)
{
  GOverlays newMarkers = new GOverlays();

Next, we get the current viewing bounds of our GMap from the property BoundsLatLng. BoundsLatLng is converted to a RectangleF struct. The reason we do this is to take advantage of all the functionalities built into RectangleF like Contains(), Intersect(), and Union(). We also get the BoundsLatLng from the previous call to gMap_MoveEnd() (saved to the Session). We do this so we don't add the same marker twice.

C#
RectangleF currBounds = gMap.BoundsLatLng.ToRectangleF();
RectangleF prevBounds = PreviousBounds;

Now we loop through all of the Hotels and find out if the current BoundsLatLng of the GMap contains the point at which the hotel is located. If so, and if the marker wasn't added previously, we add it to the list of new markers.

C#
foreach( object o in Hotels )
{
  AccorHotel ah = o as AccorHotel;
  PointF currPoint = ah.Marker.Point.ToPointF();
  if( currBounds.Contains(currPoint) )
  {
    if( !prevBounds.Contains(currPoint) )
      newMarkers.Add(ah.Marker);
  }
}

Finally, we update the PreviousBounds with the current BoundsLatLng, transform the list of new markers to JavaScript using the GMap.xsl style sheet, and return that JavaScript to the client where the markers will be added to the map.

C#
  PreviousBounds = currBounds;

  if( newMarkers.Count > 0 )
  {
    string path = Server.MapPath("../Scripts/GMap.xsl");
    string newOverlays = 
        GXslt.Transform(new GOverlaysWrapper(newMarkers), path, 
      gMap.GetXsltArguments());

    return newOverlays;
  }
  else
  {
    return String.Empty;
  }
}

I've encapsulated some of the functionalities in a few basic business classes, but the main thrust of this example is dynamically adding and removing markers based on the currently visible portion of the map. You can download the code to see the full implementation. The server-side MoveEnd handles the adding of the markers and the client-side MoveEnd handles the removal of the markers. __Show Me__.

Conclusion

In this article, we covered the advanced usage of my .NET GMap control. Now that you know how to use overlay data binding, client callbacks, and retrieve data about your GMap upon postback, the sky is the limit for the neat applications you can come up with.

In Part 3, I will discuss how I designed the GMap control and the reasoning for some of my design decisions (when XSL is involved there better be a damn good reason).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here