|
|||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis 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:
Setup and configurationMaps 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 stuffIncluded 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 ColoradoCounties.aspx
This example makes use of the client callback and overlay data binding advanced features of the The code snippet below creates a <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
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. 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: function GMarker_InfoWindowClose()
{
var pLineId = this.id + "Boundary";
var pLine = this.map.getOverlayById(pLineId);
if( pLine )
this.map.removeOverlay(pLine);
}
ColoradoCounties.aspx.csThis 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 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 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 ( 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 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
This example makes use of the client callback and overlay data binding advanced features of the <wcp:GMap runat="server" id="gMap" Width="750px" Height="525px"
EnableClientCallBacks="True" OnMarkerClick="gMap_MarkerClick"/>
Again, note the StateQuarters.aspx.csWhen the page loads the 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 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 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__. Cache[gm.Id] = gmInfoWindow;
}
return gmInfoWindow;
}
return String.Empty;
}
Handling a large number of markersOne 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
This is the most complex example but possibly the most useful in terms of implementation in your own 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 <wcp:GMap runat="server" id="gMap" Width="750px" Height="525px"
EnableClientCallBacks="True" OnMarkerClick="gMap_MarkerClick"
OnMoveEnd="gMap_MoveEnd" />
AccorHotels.aspx.csThe 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 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. gmInfoWindow = gm.OpenInfoWindowHtml(HttpUtility.HtmlDecode(
GXslt.Transform(ah, path)));
Cache[gm.Id] = gmInfoWindow;
}
return gmInfoWindow;
}
return String.Empty;
}
Things start getting interesting in the protected string gMap_MoveEnd(object s, GPointEventArgs pea)
{
GOverlays newMarkers = new GOverlays();
Next, we get the current viewing bounds of our RectangleF currBounds = gMap.BoundsLatLng.ToRectangleF();
RectangleF prevBounds = PreviousBounds;
Now we loop through all of the Hotels and find out if the current 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 = 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 ConclusionIn this article, we covered the advanced usage of my .NET In Part 3, I will discuss how I designed the | ||||||||||||||||||||||||||||||||