Click here to Skip to main content
15,896,912 members
Articles / Web Development / ASP.NET

Lat Lays Flat - Part 1 : A Google Maps .NET Control

Rate me:
Please Sign up or sign in to vote.
4.76/5 (69 votes)
17 Oct 20056 min read 912.9K   17.3K   258  
An ASP.NET server control wrapper for the Google Maps API.
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WCPierce Web Controls.
 *
 * The Initial Developer of the Original Code is William C. Pierce.
 * Portions created by the Initial Developer are Copyright (C) 2005
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Ren� Strauss
 *   JL Tejeda
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
using System;
using System.ComponentModel;
using System.Collections;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Text;
using System.Xml.Xsl;


[assembly:TagPrefix("WCPierce.Web.UI.WebControls", "wcp")]
namespace WCPierce.Web.UI.WebControls
{
  /// <summary>
  /// Creates a Google Map that the developer can interact with programatically
  /// using .Net languages
  ///
  /// 2005-09-08
  ///  + Corrected OnDataBinding, had my lat/lng values reversed
  /// 2005-09-10
  ///  + Added the ZoomLevel hidden input field for posting back current zoom level
  ///  + Cleaned up public Properties to use ViewState more consistently
  ///  + Removed DataGPointField, dunno what I was thinking
  /// 2005-09-21
  ///  + Corrected problem with use of GMap in a composite control.  Thanks to 
  ///    Ren� Strauss.
  /// 2005-09-28
  ///  + Cleaned up postback value handling.
  ///  + Fixed bug with numbers not rendering properly causing the GMap not to propertly
  ///    initialize.  Thanks to JL Tejeda.
  /// 2005-10-05
  ///  + Corrected the closing form tag hack in the Render method.  The control now
  ///    renders in a more proper manner.
  ///  + Added friendlyControlId to the GetXsltArguments
  /// 2005-10-12
  ///  + Added two new events ClientLoad and MapTypeChanged.  ClientLoad will fire after
  ///    the map has completely loaded.  This will give developers to take action based
  ///    on the Center, Span, Bounds, etc.  Map Type Changed will fire whenever the user
  ///    changes the Map Type from standard to satellite/hybrid, etc.
  ///  + Made some (hopefully) non breaking changes to the way events are handled to be more
  ///    closely inline with ASP.Net 2.0 ICallbackEventHandler style.
  ///  + Added another hidden field to track the Map Type upon postback.
  /// </summary>
  [ToolboxData("<{0}:GMap runat=server></{0}:GMap>")]
  public class GMap : WebControl, IPostBackEventHandler, IPostBackDataHandler
  {
    #region Member Variables

    /// <summary>
    /// This is a data representation of the GMap to be displayed.  Based very 
    /// loosely on Google's GVPage
    /// </summary>
    private GXPage _gxpage;

    /// <summary>
    /// Used to store any "method" operations on this GMap.  Builds the proper
    /// javascript to implement the method calls client side
    /// </summary>
    private StringBuilder initJs = new StringBuilder();

    private static readonly string _GoogleApiKey = "GoogleApiKey";
    private string _scriptPath = "http://maps.google.com/maps?file=api&v=1&key=";
    private string _scriptFolderPath = String.Empty;
    private string _googleApiKey = String.Empty;
    private string _friendlyUniqueId = String.Empty;
    private string _divId = String.Empty;
    private string _jsId = String.Empty;
    private const string _centerLatLngField = "_CenterLatLng";
    private GPoint _centerLatLng;
    private const string _spanLatLngField = "_SpanLatLng";
    private GSize _spanLatLng;
    private const string _boundsLatLngField = "_BoundsLatLng";
    private GBounds _boundsLatLng;
    private GMapType _supportedMapTypes = GMapType.G_MAP_TYPE & GMapType.G_SATELLITE_TYPE;
    private const string _zoomLevelField = "_ZoomLevel";
    private int _zoomLevel = 1;
    private object _dataSource = null;
    private const string _mapTypeField = "_MapType";

    #endregion

    #region Public Properties

    /// <summary>
    /// Initializes a new instance of the GMap control, based on the Div tag
    /// </summary>
    public GMap() : base(HtmlTextWriterTag.Div)
    { 
      _gxpage = new GXPage();
      _centerLatLng = new GPoint();
      _spanLatLng = new GSize();
      _boundsLatLng = new GBounds();
    }

    /// <summary>
    /// The path to the Google Maps javascript API
    /// </summary>
    public string GMapScriptPath
    {
      get { return _scriptPath + this.GoogleApiKey; }
    }

    /// <summary>
    /// The (relative) path to the developers script directory.  This method 
    /// first checks to see if the developer has already set the path.  If not
    /// the web.config file is checked via a call to Utilities.ScriptFolderPath
    /// </summary>
    public string ScriptFolderPath
    {
      get 
      { 
        if( _scriptFolderPath != String.Empty )
          return ResolveUrl(_scriptFolderPath); 
        else
          return ResolveUrl(Utilities.ScriptFolderPath);
      }
      set { _scriptFolderPath = value; }
    }

    /// <summary>
    /// The name of the accompanying Javascript file
    /// </summary>
    public static string ScriptName
    {
      get { return "GMapX.js"; }
    }

    /// <summary>
    /// The Google API key assigned by Google.  This method 
    /// first checks to see if the developer has already set the key.  If not
    /// the web.config file is checked.
    /// </summary>
    public string GoogleApiKey
    {
      get 
      { 
        if( _googleApiKey != String.Empty )
          return _googleApiKey; 

        string key = System.Configuration.ConfigurationSettings.AppSettings[GMap._GoogleApiKey];
        if( key == null )
          key = String.Empty;

        return key;
      }
      set { _googleApiKey = value; }
    }

    /// <summary>
    /// A strongly-typed ArrayList that holds map Overlays i.e. GMarker and GPolyline
    /// </summary>
    public GOverlays Overlays
    {
      get { return _gxpage.Overlays; }
    }

    /// <summary>
    /// A strongly-typed ArrayList that holds map Icons
    /// </summary>
    public GIcons Icons
    {
      get { return _gxpage.Icons; }
    }

    /// <summary>
    /// If true, enables Client Callbackes for GMap events.  --freakin' sweet feature--
    /// </summary>
    public bool EnableClientCallBacks
    {
      get 
      { 
        object o = this.ViewState["EnableClientCallBacks"];
        if (o != null)
        {
          return (bool)o;
        }
        return false;
      }
      set
      {
        this.ViewState["EnableClientCallBacks"] = value;
      }
    }

    /// <summary>
    /// The Center lat/lng coordinate of the map
    /// </summary>
    public GPoint CenterLatLng
    {
      get { return _centerLatLng; }
    }

    /// <summary>
    /// The Span distance of the map in lat/lng ticks
    /// </summary>
    public GSize SpanLatLng
    {
      get { return _spanLatLng; }
    }

    /// <summary>
    /// The bounding box of the map
    /// </summary>
    public GBounds BoundsLatLng
    {
      get { return _boundsLatLng; }
    }

    /// <summary>
    /// Enables dynamic dragging (enabled by default)
    /// </summary>
    public bool EnableDragging
    {
      get 
      { 
        object o = this.ViewState["EnableDragging"];
        if (o != null)
        {
          return (bool)o;
        }
        return true;
      }
      set
      {
        this.ViewState["EnableDragging"] = value;
      }
    }

    /// <summary>
    /// Enables the popup info windows on this map (enabled by default)
    /// </summary>
    public bool EnableInfoWindow
    {
      get 
      { 
        object o = this.ViewState["EnableInfoWindow"];
        if (o != null)
        {
          return (bool)o;
        }
        return true;
      }
      set
      {
        this.ViewState["EnableInfoWindow"] = value;
      }
    }

    /// <summary>
    /// Returns the integer zoom level of the map
    /// </summary>
    public int ZoomLevel
    {
      get { return _zoomLevel; }
      set { _zoomLevel = value; }
    }

    /// <summary>
    /// Returns the map type currently in use
    /// </summary>
    public GMapType MapType
    {
      get 
      { 
        object o = this.ViewState["MapType"];
        if (o != null)
        {
          return (GMapType)o;
        }
        return GMapType.G_MAP_TYPE;
      }
      set
      {
        this.ViewState["MapType"] = value;
      }
    }

    /// <summary>
    /// Returns the map types supported by this map (e.g., G_MAP_TYPE &amp; G_SATELLITE_TYPE)
    /// </summary>
    public GMapType SupportedMapTypes
    {
      get { return _supportedMapTypes; }
      set { _supportedMapTypes = value; }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    [Bindable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue((string) null)]
    public virtual object DataSource
    {
      get
      {
        return _dataSource;
      }
      set
      {
        if (((value != null) && !(value is IListSource)) && !(value is IEnumerable))
        {
          throw new ArgumentException("Invalid_DataSource_Type: " + this.ID);
        }
        _dataSource = value;
      }
    }

    /// <summary>
    /// For use with databinding
    /// </summary>
    public virtual string DataMarkerIdField
    {
      get 
      { 
        object o = this.ViewState["DataMarkerIdField"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataMarkerIdField"] = value;
      }
    }

    /// <summary>
    /// For use with databinding
    /// </summary>
    public virtual string DataIconIdField
    {
      get 
      { 
        object o = this.ViewState["DataIconIdField"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataIconIdField"] = value;
      }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    public virtual string DataLatitudeField
    {
      get 
      { 
        object o = this.ViewState["DataLatitudeField"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataLatitudeField"] = value;
      }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    public virtual string DataLongitudeField
    {
      get 
      { 
        object o = this.ViewState["DataLongitudeField"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataLongitudeField"] = value;
      }
    }

    /// <summary>
    /// For use with databinding.
    /// </summary>
    [DefaultValue("")]
    public virtual string DataMember
    {
      get
      {
        object o = this.ViewState["DataMember"];
        if (o != null)
        {
          return (string)o;
        }
        return string.Empty;
      }
      set
      {
        this.ViewState["DataMember"] = value;
      }
    }

    #endregion

    #region Public Methods

    /// <summary>
    /// Creates the proper Javascript code to center and zoom the map at the
    /// given LatLng/ZoomLevel.  This is added to the "iniJs" which is 
    /// rendered with the control.  Also useful for Client CallBacks
    /// </summary>
    /// <param name="LatLng">The Latitude/Longitude coordinate to center at</param>
    /// <param name="ZoomLevel">The Zoom Level to center to</param>
    /// <returns>A string of client side javascript to implement the centerAndZoom</returns>
    public string CenterAndZoom( GPoint LatLng, int ZoomLevel )
    {
      string str = String.Format(Utilities.UsCulture, "{0}.centerAndZoom(new GPoint({1}, {2}), {3});", this.JsId, LatLng.X, LatLng.Y, ZoomLevel);
      initJs.Append(str);
      return str;
    }

    /// <summary>
    /// Creates the proper Javascript code to zoom the map at the
    /// given ZoomLevel.  This is added to the "iniJs" which is 
    /// rendered with the control.  Also useful for Client CallBacks
    /// </summary>
    /// <param name="ZoomLevel">The Zoom Level to apply to the map</param>
    /// <returns>A string of client side javascript to implement the zoomTo</returns>
    public string ZoomTo(int ZoomLevel )
    {
      string str = String.Format(Utilities.UsCulture, "{0}.zoomTo({1});", this.JsId, ZoomLevel);
      initJs.Append(str);
      return str;
    }

    /// <summary>
    /// Creates the proper Javascript code to center the map at the
    /// given Latitude/Longitude coordinate.  This is added to the "iniJs" which
    /// is rendered with the control.  Also useful for Client CallBacks
    /// </summary>
    /// <param name="LatLng">The Latitude/Longitude coordinate to center at</param>
    /// <returns>A string of client side javascript to implement the centerAtLatLng</returns>
    public string CenterAtLatLng(GPoint LatLng)
    {
      string str = String.Format(Utilities.UsCulture, "{0}.centerAtLatLng(new GPoint({1},{2}));", this.JsId, LatLng.X, LatLng.Y);
      initJs.Append(str);
      return str;
    }

    /// <summary>
    /// Creates the proper Javascript code to center/pan the map to the
    /// given Latitude/Longitude coordinate.  This is added to the "iniJs" which
    /// is rendered with the control.  Also useful for Client CallBacks
    /// </summary>
    /// <param name="LatLng">The Latitude/Longitude coordinate to center/pan to</param>
    /// <returns>A string of client side javascript to implement the recenterOrPanToLatLng</returns>
    public string RecenterOrPanToLatLng(GPoint LatLng)
    {
      string str = String.Format(Utilities.UsCulture, "{0}.recenterOrPanToLatLng({1},{2});", this.JsId, LatLng.X, LatLng.Y);
      initJs.Append(str);
      return str;
    }

    /// <summary>
    ///  Displays the info window with the given HTML content at the given 
    ///  lat/lng point. htmlElem should be an HTML DOM element. If 
    ///  pixelOffset (GSize) is given, we offset the info window by that 
    ///  number of pixels, which lets users place info windows above markers
    ///  and other overlays. If onOpenFn is given, we call the function when
    ///  the window is displayed, and we call onCloseFn when the window is
    ///  closed.
    /// </summary>
    /// <param name="LatLng"></param>
    /// <param name="HtmlElem"></param>
    /// <returns></returns>
    public string OpenInfoWindow(GPoint LatLng, string HtmlElem)
    {
      string str = String.Format(Utilities.UsCulture, "{0}.openInfoWindow(new GPoint({1},{2}), {3});", this.JsId, LatLng.X, LatLng.Y, HtmlElem);
      initJs.Append(str);
      return str;
    }

    /// <summary>
    /// Like openInfoWindow, but takes an HTML string rather than an HTML DOM
    /// element
    /// </summary>
    /// <param name="LatLng"></param>
    /// <param name="HtmlStr"></param>
    /// <param name="PixelOffset"></param>
    /// <returns></returns>
    public string OpenInfoWindowHtml(GPoint LatLng, string HtmlStr, GSize PixelOffset)
    {
      string str = String.Format(Utilities.UsCulture, "{0}.openInfoWindowHtml(new GPoint({1},{2}), '{3}', new GSize({4},{5}));", this.JsId, LatLng.X, LatLng.Y, HtmlStr, PixelOffset.Width, PixelOffset.Height);
      initJs.Append(str);
      return str;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="LatLng"></param>
    /// <param name="HtmlStr"></param>
    /// <returns></returns>
    public string OpenInfoWindowHtml(GPoint LatLng, string HtmlStr)
    {
      return OpenInfoWindowHtml(LatLng, HtmlStr, new GSize(0, 0));
    }

    /// <summary>
    ///  Like openInfoWindow, but takes an XML element and the URI of an XSLT
    ///  document to produce the content of the info window. The first time a
    ///  URI is given, it is downloaded with GXmlHttp and subsequently cached.
    /// </summary>
    /// <param name="LatLng"></param>
    /// <param name="Xml"></param>
    /// <param name="XsltUri"></param>
    /// <param name="PixelOffset"></param>
    /// <returns></returns>
    public string OpenInfoWindowXslt(GPoint LatLng, string Xml, Uri XsltUri, GSize PixelOffset)
    {
      string str = String.Format(Utilities.UsCulture, "{0}.openInfoWindowXslt(new GPoint({1},{2}), GXml.parse('{3}'), '{4}', new GSize({5},{6}));", this.JsId, LatLng.X, LatLng.Y, Xml, XsltUri.AbsoluteUri, PixelOffset.Width, PixelOffset.Height);
      initJs.Append(str);
      return str;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="LatLng"></param>
    /// <param name="Xml"></param>
    /// <param name="XsltUri"></param>
    /// <returns></returns>
    public string OpenInfoWindowXslt(GPoint LatLng, string Xml, Uri XsltUri)
    {
      return OpenInfoWindowXslt(LatLng, Xml, XsltUri, new GSize(0, 0));
    }

    /// <summary>
    /// Shows a blowup of the map at the given lat/lng GPoint. We use a default
    /// zoom level of 1 and the current map type if the zoomLevel and mapType
    /// parameters are not given.
    /// </summary>
    /// <param name="LatLng"></param>
    /// <param name="ZoomLevel"></param>
    /// <param name="MapType"></param>
    /// <param name="PixelOffset"></param>
    /// <returns></returns>
    public string ShowMapBlowup(GPoint LatLng, int ZoomLevel, GMapType MapType, GSize PixelOffset)
    {
      string str = String.Format(Utilities.UsCulture, "{0}.showMapBlowup(new GPoint({1},{2}), {3}, {4}, new GSize({5},{6}));", this.JsId, LatLng.X, LatLng.Y, ZoomLevel, MapType, PixelOffset.Width, PixelOffset.Height);
      initJs.Append(str);
      return str;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="LatLng"></param>
    /// <returns></returns>
    public string ShowMapBlowup(GPoint LatLng)
    {
      return ShowMapBlowup(LatLng, 1, GMapType.G_MAP_TYPE, new GSize(0, 0));
    }

    /// <summary>
    /// Creates the proper Javascript code to clear all map overlays.  This is
    /// added to the "iniJs" which is rendered with the control.  Also useful
    /// for Client CallBacks
    /// </summary>
    /// <returns>A string of client side javascript to implement the clearOverlays</returns>
    public string ClearOverlays()
    {
      string str = String.Format(Utilities.UsCulture, "{0}.clearOverlays();", this.JsId);
      initJs.Append(str);
      return str;
    }

    /// <summary>
    /// Adds a GControl to the map
    /// </summary>
    /// <param name="Control">The GControl to add</param>
    public void AddControl(GControl Control)
    {
      _gxpage.Controls.Add(Control);
    }

    #endregion

    #region Private Properties

    /// <summary>
    /// A javascript friendly version of the controls unique id
    /// </summary>
    private string FriendlyUniqueId
    {
      get 
      { 
        if( _friendlyUniqueId == String.Empty )
          _friendlyUniqueId = this.UniqueID.Replace(":", "_");

        return _friendlyUniqueId;
      }
    }

    /// <summary>
    /// The unique id for the div panel of the GMap
    /// </summary>
    private string DivId
    {
      get 
      { 
        if( _divId == String.Empty )
          _divId = this.UniqueID + "_Div";

        return _divId;
      }
    }

    /// <summary>
    /// The unique javascript id for the GMap
    /// </summary>
    private string JsId
    {
      get 
      { 
        if( _jsId == String.Empty )
          _jsId = this.FriendlyUniqueId + "_Js";

        return _jsId;
      }
    }

    #endregion

    #region Overrides

    /// <summary>
    /// Creates the hidden input fields used to pass the map state back to the 
    /// server.  One field each for the Center, Span, and Bounds fields
    /// </summary>
    protected override void CreateChildControls()
    {   
      base.CreateChildControls();

      string id = this.ID;

      HtmlInputHidden centerLatLng = new HtmlInputHidden();
      centerLatLng.ID = id + _centerLatLngField;
      base.Controls.Add(centerLatLng);

      HtmlInputHidden spanLatLng = new HtmlInputHidden();
      spanLatLng.ID = id + _spanLatLngField;
      base.Controls.Add(spanLatLng);

      HtmlInputHidden boundsLatLng = new HtmlInputHidden();
      boundsLatLng.ID = id + _boundsLatLngField;
      base.Controls.Add(boundsLatLng);

      HtmlInputHidden zoomLevel = new HtmlInputHidden();
      zoomLevel.ID = id + _zoomLevelField;
      base.Controls.Add(zoomLevel);

      HtmlInputHidden mapType = new HtmlInputHidden();
      mapType.ID = id + _mapTypeField;
      base.Controls.Add(mapType);
    }

    /// <summary>
    /// I was attempting to properly create a custom control, but ended up doing
    /// a little hacking
    /// </summary>
    /// <param name="writer"></param>
    protected override void AddAttributesToRender(HtmlTextWriter writer)
    {
      if (this.Page != null)
      {
        this.Page.VerifyRenderingInServerForm(this);
      }

      writer.AddAttribute(HtmlTextWriterAttribute.Name, this.DivId);
      writer.AddAttribute(HtmlTextWriterAttribute.Id, this.DivId);

      // base.AddAttributesToRender automatically renders the Id field as the 
      // Id of the control unless the control Id is null.  We don't want the 
      // div tag's id to be the controls id.  We want it to be {controlId}_Div.  
      // So we need to do a little hack.
      string tId = base.ID;
      base.ID = null;
      base.AddAttributesToRender(writer);
      base.ID = tId;
    }

    /// <summary>
    /// XML ok, XSL bad.  Thanks to our GXPage and some XSL, the render method
    /// is nice and short.  See GMap.xsl for the full rendering goodness.
    /// </summary>
    /// <param name="output"></param>
    protected override void Render(HtmlTextWriter output)
    {
      base.Render(output);
     
      StringBuilder sb = new StringBuilder();

      // Transform our GXPage to javascript using our GMap.xsl stylesheet
      string path = Page.Server.MapPath(this.ScriptFolderPath + "/" + "GMap.xsl");
      XsltArgumentList xal = GetXsltArguments();
      sb.Append(GXslt.Transform(_gxpage, path, xal));

      // Register our script for output to the page.
      Page.RegisterStartupScript(this.UniqueID, sb.ToString());  
    }

    /// <summary>
    /// I overrode this method so that Developers weren't constantly
    /// reading/writing to the ViewState when accessing the map's GXPage.  
    /// Not sure if this is proper/recommended?
    /// </summary>
    /// <returns></returns>
    protected override object SaveViewState()
    {
      ViewState["GXPage"] = _gxpage;
      return base.SaveViewState();
    }

    /// <summary>
    /// I overrode this method so that Developers weren't constantly
    /// reading/writing to the ViewState when accessing the map's GXPage.  
    /// Not sure if this is proper/recommended?
    /// </summary>
    /// <param name="savedState"></param>
    protected override void LoadViewState(object savedState)
    {
      base.LoadViewState(savedState);
      _gxpage = ViewState["GXPage"] as GXPage;
    }

    /// <summary>
    /// Register our common scripts and do default PreRendering.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPreRender(EventArgs e)
    {
      base.OnPreRender(e);

      // Required for us to capture postback data
      Page.RegisterRequiresPostBack(this);

      // Register our standard scripts
      this._RegisterCommonScripts();     
    }

    /// <summary>
    /// Good old databinding.  This will check the bound DataSource for a Latitide 
    /// field and a Longitude field.  A marker will be added for each entry using 
    /// the default icon. --freakin' sweet feature--
    /// </summary>
    /// <param name="e"></param>
    protected override void OnDataBinding(EventArgs e)
    {
      base.OnDataBinding (e);
      _gxpage.Overlays.Clear();    
      IEnumerable ie = DataSourceHelper.GetResolvedDataSource(this.DataSource, this.DataMember);

      if( ie != null )
      {
        string latField = DataLatitudeField;
        string lngField = DataLongitudeField;
        string mIdField = DataMarkerIdField;
        string iIdField = DataIconIdField;

        foreach( object o in ie )
        {
          float lat = Convert.ToSingle(DataBinder.GetPropertyValue(o, latField), Utilities.UsCulture);
          float lng = Convert.ToSingle(DataBinder.GetPropertyValue(o, lngField), Utilities.UsCulture);
          GPoint gPoint = new GPoint(lng, lat);
          
          // Marker Id is not required.  If the developer didn't specify it, we just ignore it
          string mId = String.Empty;
          try
          {
            mId = DataBinder.GetPropertyValue(o, mIdField, "{0}");
          }
          catch( Exception ex )
          {
            if( !(ex is ArgumentNullException) )
              throw;
          }

          // IconId is not required.  If the developer didn't specify it, we just ignore it
          string iId = String.Empty;
          try
          {
            iId = DataBinder.GetPropertyValue(o, iIdField, "{0}");
          }
          catch( Exception ex )
          {
            if( !(ex is ArgumentNullException) )
              throw;
          }

          GMarker gm = new GMarker(gPoint, mId, iId);
          _gxpage.Overlays.Add(gm);
        }
      }
    }

    #endregion

    #region Helper Methods

    /// <summary>
    /// Add a reference to the JavaScript, but only once per page.  The order
    /// in which these scripts are registered is important
    /// </summary>
    private void _RegisterCommonScripts()
    {
      // Register the GoogleMaps API script
      if(!this.Page.IsClientScriptBlockRegistered("GMap"))
      {
        StringBuilder script = new StringBuilder();
        script.AppendFormat("<script type='text/javascript' src='{0}'></script>", this.GMapScriptPath);
        this.Page.RegisterClientScriptBlock("GMap", script.ToString());
      }
      // Register our GMapX script
      if(!this.Page.IsClientScriptBlockRegistered(GMap.ScriptName))
      {
        StringBuilder script = new StringBuilder();
        script.AppendFormat("<script type='text/javascript' src='{0}/{1}'></script>", this.ScriptFolderPath, GMap.ScriptName);
        this.Page.RegisterClientScriptBlock(GMap.ScriptName, script.ToString());
      }
      // Only register the CallBackObject if EnableClientCallBacks is true
      if( this.EnableClientCallBacks && !this.Page.IsClientScriptBlockRegistered(CallBackHelper.ScriptName))
      {
        StringBuilder script = new StringBuilder();
        script.AppendFormat("<script type='text/javascript' src='{0}/{1}'></script>", this.ScriptFolderPath, CallBackHelper.ScriptName);
        this.Page.RegisterClientScriptBlock(CallBackHelper.ScriptName, script.ToString());
      }
    }

    /// <summary>
    /// Helper function for getting the default Xsl arguments
    /// </summary>
    /// <returns></returns>
    public XsltArgumentList GetXsltArguments()
    {
      XsltArgumentList xal = new XsltArgumentList();
      xal.AddParam("jsId", string.Empty, this.JsId); 
      xal.AddParam("divId", string.Empty, this.DivId); 
      xal.AddParam("controlId", string.Empty, this.UniqueID); 
      xal.AddParam("enableClientCallBacks", string.Empty, this.EnableClientCallBacks); 
      xal.AddParam("initJs", string.Empty, initJs.ToString());
      xal.AddParam("enableDragging", string.Empty, this.EnableDragging);
      xal.AddParam("enableInfoWindow", string.Empty, this.EnableInfoWindow);
      xal.AddParam("zoomLevel", string.Empty, this.ZoomLevel);
      xal.AddParam("mapType", string.Empty, this.MapType.ToString());
      xal.AddParam("friendlyControlId", string.Empty, this.FriendlyUniqueId);

      return xal;
    }

    #endregion

    #region IPostBackEventHandler Members

    /// <summary>
    /// Generally used for responding to server side events.  We are using it 
    /// here to respond to client call backs.  Currently supported events are 
    /// Click, Zoom, Move Start, Move End and Marker Click.
    /// </summary>
    /// <param name="eventArgument">Events will be passed from the client in {event}|{argument1},{argument2}...{argumentN} form</param>
    public void RaisePostBackEvent(string eventArgument)
    {
      // If this isn't a call back we won't bother
      if( !CallBackHelper.IsCallBack )
        return;

      // Always wrap callback code in a try/catch block
      try
      {
        string[] ea = eventArgument.Split('|');
        string[] args = null;
        GPointEventArgs pea = null;
        string evt = ea[0];
        string retVal = String.Empty;
        switch( evt )
        {
          // GMap Click event sends the coordinates of the click as event argument
          case "GMap_Click":
            args = ea[1].Split(',');
            pea = new GPointEventArgs(float.Parse(args[0]), float.Parse(args[1]), this);
            retVal = this.OnClick(pea);
            break;
          // GMarker Click event sends the coordinates of the click as event argument
          case "GMarker_Click":
            args = ea[1].Split(',');
            GPoint gp = new GPoint(float.Parse(args[0]), float.Parse(args[1]));
            GMarker gm = new GMarker(gp, args[2]);
            pea = new GPointEventArgs(gp, gm);
            retVal = this.OnMarkerClick(pea);
            break;
          // GMap Move Start event sends the coordinates of the center of the 
          // map where the move started
          case "GMap_MoveStart":
            args = ea[1].Split(',');
            pea = new GPointEventArgs(float.Parse(args[0]), float.Parse(args[1]));
            retVal = this.OnMoveStart(pea);
            break;
          // GMap Move End event sends the coordinates of the center of the 
          // map where the move ended
          case "GMap_MoveEnd":
            args = ea[1].Split(',');
            pea = new GPointEventArgs(float.Parse(args[0]), float.Parse(args[1]));
            retVal = this.OnMoveEnd(pea);
            break;
          // GMap Zoom event sends the old and new zoom levels
          case "GMap_Zoom":
            args = ea[1].Split(',');
            GMapZoomEventArgs zea = new GMapZoomEventArgs(int.Parse(args[0]), int.Parse(args[1]));
            retVal = this.OnZoom(zea);
            break;
          // GMap Client Load event
          case "GMap_ClientLoad":
            retVal = this.OnClientLoad();
            break;
          // GMap Map Type Changed event
          case "GMap_MapTypeChanged":
            retVal = this.OnMapTypeChanged();
            break;
          // Default: we don't know what the client was trying to do
          default:
            throw new HttpException(String.Format("Invalid GMap Event Sender: {0} Event: {1}", this.ID, evt));
        }

        CallBackHelper.Write(retVal);
      }
      // Had some odd Thread Abort Exceptions going around.  Something to do with 
      // the default behavior of Response.End.  Just ignore these exceptions
      catch(Exception e)
      {
        if( !(e is System.Threading.ThreadAbortException) )
          CallBackHelper.HandleError(e);
      }
    }

    #endregion
    
    #region IPostBackDataHandler Members

    /// <summary>
    /// Not implemented
    /// </summary>
    public void RaisePostDataChangedEvent() { }

    /// <summary>
    /// After a postback, load the Center, Span, and Bounds data passed from the client
    /// </summary>
    /// <param name="postDataKey"></param>
    /// <param name="postCollection"></param>
    /// <returns></returns>
    public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
    {
      string uId = this.UniqueID;
      try { _centerLatLng = (GPoint)postCollection[uId + _centerLatLngField]; } catch { }
      try { _spanLatLng   = (GSize)postCollection[uId + _spanLatLngField]; } catch { }
      try { _boundsLatLng = (GBounds)postCollection[uId + _boundsLatLngField]; } catch { }
      try { _zoomLevel = Convert.ToInt32(postCollection[uId + _zoomLevelField], Utilities.UsCulture); } catch { }
      try { this.MapType = (GMapType)Utilities.StringToEnum(typeof(GMapType), postCollection[uId + _mapTypeField]); } catch { }

      return false;
    }

    #endregion

    #region Events

      #region EventPlumbing


      // Code required to raise events to developers /////  
      private static readonly object EventClick;
      private static readonly object EventMarkerClick;
      private static readonly object EventMoveStart;
      private static readonly object EventMoveEnd;
      private static readonly object EventZoom;
      private static readonly object EventClientLoad;
      private static readonly object EventMapTypeChanged;
     
      static GMap()
      {
        GMap.EventClick = new object();
        GMap.EventMarkerClick = new object();
        GMap.EventMoveStart = new object();
        GMap.EventMoveEnd = new object();
        GMap.EventZoom = new object();
        GMap.EventClientLoad = new object();
        GMap.EventMapTypeChanged = new object();
      }
      /////

      #endregion
  
      #region Click

      /// <summary>
      /// Allows developers to capture the click event
      /// </summary>
      public event GMapClickEventHandler Click
      {
        add { base.Events.AddHandler(GMap.EventClick, value); }
        remove { base.Events.RemoveHandler(GMap.EventClick, value); }
      }

      /// <summary>
      /// This event is raised whenever the GMap or a GMarker is clicked.  
      /// The sender will be either a GMap or GMarker.  The result of this event
      /// is a string of javascript the developer would like evaluated client side 
      /// using the eval() function
      /// </summary>
      /// <param name="pea">The point where the click occured</param>
      protected virtual string OnClick(GPointEventArgs pea)
      {
        GMapClickEventHandler eh = (GMapClickEventHandler)base.Events[GMap.EventClick];

        if(eh != null) 
        { 
          return eh(pea.Target, pea);
        }

        return String.Empty;
      }

      #endregion

      #region MarkerClick

      /// <summary>
      /// Allows developers to capture the click event
      /// </summary>
      public event GMapClickEventHandler MarkerClick
      {
        add { base.Events.AddHandler(GMap.EventMarkerClick, value); }
        remove { base.Events.RemoveHandler(GMap.EventMarkerClick, value); }
      }

      /// <summary>
      /// This event is raised whenever a GMarker is clicked.  
      /// The sender will be a new GMarker object.
      /// </summary>
      /// <param name="pea">The point where the click occured</param>
      protected virtual string OnMarkerClick(GPointEventArgs pea)
      {
        GMapClickEventHandler eh = (GMapClickEventHandler)base.Events[GMap.EventMarkerClick];

        if(eh != null) 
        { 
          return eh(pea.Target, pea);
        }
        
        return String.Empty;
      }

      #endregion

      #region MoveStart

      /// <summary>
      /// Allows developers to capture the MoveStart event
      /// </summary>
      public event GMapMoveEventHandler MoveStart
      {
        add { base.Events.AddHandler(GMap.EventMoveStart, value); }
        remove { base.Events.RemoveHandler(GMap.EventMoveStart, value); }
      }

      /// <summary>
      /// This event is raised whenever the users starts moving the GMap.  The 
      /// center lat/lng coordinate of the map at the start is passed as an 
      /// argument.  The result of this event is a string of javascript the 
      /// developer would like evaluated client side using the eval() function
      /// </summary>
      /// <param name="pea">The center lat/lng coordinate of the map before the 
      /// move started</param>
      protected virtual string OnMoveStart(GPointEventArgs pea)
      {
        GMapMoveEventHandler eh = (GMapMoveEventHandler)base.Events[GMap.EventMoveStart];
        if(eh != null) 
        { 
          return eh(this, pea);
        }

        return String.Empty;
      }

      #endregion

      #region MoveEnd

      /// <summary>
      /// Allows the developer to capture the MoveEnd event
      /// </summary>
      public event GMapMoveEventHandler MoveEnd
      {
        add { base.Events.AddHandler(GMap.EventMoveEnd, value); }
        remove { base.Events.RemoveHandler(GMap.EventMoveEnd, value); }
      }

      /// <summary>
      /// This event is raised whenever the users finishes moving the GMap.  The 
      /// center lat/lng coordinate of the map at the end is passed as an 
      /// argument.  The result of this event is a string of javascript the 
      /// developer would like evaluated client side using the eval() function
      /// </summary>
      /// <param name="pea">The center lat/lng coordinate of the map after the 
      /// move finished</param>
      protected virtual string OnMoveEnd(GPointEventArgs pea)
      {
        GMapMoveEventHandler eh = (GMapMoveEventHandler)base.Events[GMap.EventMoveEnd];
        if(eh != null) 
        { 
          return eh(this, pea);
        }
        
        return String.Empty;
      }

      #endregion

      #region Zoom

      /// <summary>
      /// Allows the developer to capture the Zoom event
      /// </summary>
      public event GMapZoomEventHandler Zoom
      {
        add { base.Events.AddHandler(GMap.EventZoom, value); }
        remove { base.Events.RemoveHandler(GMap.EventZoom, value); }
      }

      /// <summary>
      /// This event is raised whenever the user zooms the map.  The result of 
      /// this event is a string of javascript the developer would like evaluated 
      /// client side using the eval() function
      /// </summary>
      /// <param name="zea">The old and new zoom levels</param>
      protected virtual string OnZoom(GMapZoomEventArgs zea)
      {
        GMapZoomEventHandler eh = (GMapZoomEventHandler)base.Events[GMap.EventZoom];
        if(eh != null) 
        { 
          return eh(this, zea);
        }
        
        return String.Empty;
      }

      #endregion

      #region ClientLoad

      /// <summary>
      /// Allows the developer to take action after the Map has loaded on the client
      /// </summary>
      public event GMapEventHandler ClientLoad
      {
        add { base.Events.AddHandler(GMap.EventClientLoad, value); }
        remove { base.Events.RemoveHandler(GMap.EventClientLoad, value); }
      }

      /// <summary>
      /// This event is raised after the map has loaded on the client.  This gives the developer
      /// access to the Center, Bounds, and Span values and take the necessary action
      /// </summary>
      protected virtual string OnClientLoad()
      {
        GMapEventHandler eh = (GMapEventHandler)base.Events[GMap.EventClientLoad];
        if(eh != null) 
        { 
          return eh(this, null);
        }
        
        return String.Empty;
      }

      #endregion

      #region MapTypeChanged

      /// <summary>
      /// Allows the developer to take action whenver the Map Type has changed
      /// </summary>
      public event GMapEventHandler MapTypeChanged
      {
        add { base.Events.AddHandler(GMap.EventMapTypeChanged, value); }
        remove { base.Events.RemoveHandler(GMap.EventMapTypeChanged, value); }
      }

      /// <summary>
      /// This event is raised whenever the user changes the Map Type
      /// </summary>
      protected virtual string OnMapTypeChanged()
      {
        GMapEventHandler eh = (GMapEventHandler)base.Events[GMap.EventMapTypeChanged];
        if(eh != null) 
        { 
          return eh(this, null);
        }
        
        return String.Empty;
      }

      #endregion

    #endregion
  }

  #region Delegates

  /// <summary>
  /// Handler for the GMap_Click event
  /// </summary>
  public delegate string GMapClickEventHandler(object s, GPointEventArgs   pea);      

  /// <summary>
  /// Handler for the GMap_Move events
  /// </summary>
  public delegate string GMapMoveEventHandler (object s, GPointEventArgs   pea);

  /// <summary>
  /// Handler for the GMap_Zoom event
  /// </summary>
  public delegate string GMapZoomEventHandler (object s, GMapZoomEventArgs zea);

  /// <summary>
  /// Handler for the GMap_ClientLoad and GMap_MapTypeChanged events
  /// </summary>
  public delegate string GMapEventHandler (object s, EventArgs e);

  #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions