Click here to Skip to main content
15,867,453 members
Articles / Web Development / ASP.NET
Article

Google's Static Map API WebControl

Rate me:
Please Sign up or sign in to vote.
4.87/5 (39 votes)
20 May 2008CPOL10 min read 134.4K   3.2K   155   23
Shows you how to build an image based WebControl displaying static maps with markers

Introduction

GStaticMap is an ASP.NET WebControl allowing you to build and show a static map image with optionally some markers, a path, etc. using Google’s Static Maps API.

Here is a quick sample, showing you what you can quickly and easily produce with it:

Image 1

This sample can be achieved with some lines of code in the code-behind, or directly in your ASPX page:

ASP.NET
<asp:GStaticMap ID="gsmTestMap" AutomaticZoomLevel="true" 
    AutomaticCenter="true" Width="320" Height="200" Type="RoadMap" 
    ZoomLevel="12" Key="put_yours_here" runat="server">
    <Center Latitude="45.759723" Longitude="4.842223"></Center>
    <Markers>
        <asp:GMarker Latitude="45.859723" Longitude="4.842223" Letter="a" 
            Color="Red"></asp:GMarker>
        <asp:GMarker Latitude="45.759723" Longitude="5.042223" Letter="b" 
            Color="Green"></asp:GMarker>
        <asp:GMarker Latitude="45.659723" Longitude="4.842223" Letter="c" 
            Color="Blue"></asp:GMarker>
        <asp:GMarker Latitude="45.759723" Longitude="4.642223" Color="Green">
        </asp:GMarker>
    </Markers>
    <Paths>
        <asp:GPath Color="blue" Opacity="255" Weight="5">
            <Points>
                <asp:GCoordinate Latitude="45.859723" Longitude="4.842223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.759723" Longitude="5.042223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.659723" Longitude="4.842223">
                </asp:GCoordinate>
            </Points>
        </asp:GPath>
        <asp:GPath Color="red" Opacity="128" Weight="5">
            <Points>
                <asp:GCoordinate Latitude="45.659723" Longitude="4.842223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.759723" Longitude="4.642223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.859723" Longitude="4.842223">
                </asp:GCoordinate>
            </Points>
        </asp:GPath>
    </Paths>
</asp:GStaticMap>

The generated URL is the following (I've added some spaces to wrap the URL and removed my API key):

http://maps.google.com/staticmap?size = 320x200 & maptype = roadmap 
& markers = 45.859723,4.842223,reda | 45.759723,5.042223,greenb | 
45.659723,4.842223,bluec | 45.759723,4.642223,green & path = rgba:0x0000ffff,weight:5 
| 45.859723,4.842223 | 45.759723,5.042223 | 45.659723,4.842223 & 
path = rgba:0xff000080,weight:5 | 45.659723,4.842223 | 45.759723,4.642223 
| 45.859723,4.842223 & key = put_yours_here.

Note that this WebControl is a sequel of my previous MapData WebControl, replacing it.

Background

First, you'll need to sign up for a free Google Maps API key if you don't have one. Connect with your Google account and put your domain name root (including the TCP/IP port).

Please note that I didn't provide my API key in the sample project, you'll have to put yours if you want to run the tests (just replace all "put_yours_here" occurrences with your key)

Classes Overview / Code Design

Our goal is to display a map with, optionally, some markers and a path. Since the map will be rendered as an image (img tag), there is no need to reinvent the wheel: we'll derive our GStaticMap WebControl from System.Web.UI.Image.

It’s now time to think about the classes that will be the foundations of our WebControl. First, we have a static map (GStaticMap class); this map will use:

  • A center based on a coordinate (GCoordinate class)
  • A collection of markers (GMarker class) themselves based on coordinates (GCoordinate class)
  • A collection of paths (GPath class), themselves based on a collection of coordinates (GCoordinate class)

Here is the class diagram (included in the sample project):

Image 2

The next step is to more deeply describe our classes. So:

A static map is defined by the following properties:

  • A width (integer, inherited from Image)
  • A height (integer, inherited from Image)
  • A center (using our GCoordinate class, optional when there is at least one marker)
  • A zoom level (integer, optional when there is at least one marker, from 1 to 19)
  • A rendering type (enumerator, optional: roadmap by default, or designed for mobile devices)
  • An image format (enumerator, optional: GIF, JPEG or PNG)

A marker is defined by the following properties:

  • A color (enumerator, optional: the Static Map API restricts them to red, by default, blue, or green)
  • An optional letter (character, optional, from A to Z)

A path is defined by the following properties:

  • A color (you can define it either with a known color from RGB values)
  • An optional opacity (integer, optional: from 0 to 255)

A coordinate is defined by the following properties:

  • A latitude (double)
  • A longitude (double)

Making the Code – Coordinate Class (GCoordinate)

It’s now time for some code.

We'll start our control with the coordinate class (GCoordinate). It’s a "small" class based on two doubles, constructors, and a query string serialization. Here is the class (tips and tricks are explained after):

C#
/// <summary>
/// Represents a Google Coordinate
/// </summary>
[Serializable]
[ToolboxData("<{0}:GCoordinate Latitude='' 
    Longitude='' runat="server"></{0}:GCoordinate>")]
public class GCoordinate
{
    #region Instance members
    /// <summary>
    /// Latitude
    /// </summary>
    private double _latitude;
    /// <summary>
    /// Longitude
    /// </summary>
    private double _longitude;
    #endregion
    #region Accessors
    /// <summary>
    /// Latitude
    /// </summary>
    [Bindable(true)]
    [Category("Coordinate")]
    [DefaultValue("")]
    [Localizable(false)]
    public double Latitude
    {
        get { return _latitude; }
        set { _latitude = value; }
    }
    /// <summary>
    /// Longitude
    /// </summary>
    [Bindable(true)]
    [Category("Coordinate")]
    [DefaultValue("")]
    [Localizable(false)]
    public double Longitude
    {
        get { return _longitude; }
        set { _longitude = value; }
    }
    #endregion
    #region Constructors
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="latitude"></param>
    /// <param name="longitude"></param>
    public GCoordinate()
    {
    }
    /// <summary>
    /// Constructor
    /// </summary>
    public GCoordinate(double latitude, double longitude)
    {
        SetCoordinate(latitude, longitude);
    }
    #endregion
    #region Utilities
    /// <summary>
    /// Serialize data as a query string part
    /// Format : {latitude,longitude}
    /// </summary>
    /// <returns></returns>
    internal virtual string SerializeAsQueryString()
    {
        return string.Format("{0},{1}", AngularValueToString(Latitude, 6), 
            AngularValueToString(Longitude, 6));
    }
    /// <summary>
    /// Format an angular value to be used by GStaticMap
    /// </summary>
    /// <param name="angularValue"></param>
    /// <param name="fixedLength"></param>
    /// <returns></returns>
    private string AngularValueToString(double angularValue, int precision)
    {
        return angularValue.ToString("F" + precision, 
            CultureInfo.CreateSpecificCulture("en-US"));
    }
    internal void SetCoordinate(double latitude, double longitude)
    {
        _latitude = latitude;
        _longitude = longitude;
    }
    #endregion
}

First, I've added the [Serializable] attribute before the class declaration, this is needed in order to store the class in the ViewState (as a serialized string).

Secondly, let's have a look at the serialization methods: the AngularValueToString method converts a double value to a formatted string (localized to en-US culture for the decimal point), and SerializeAsQueryString is an internal (to be accessed by other classes of the same assembly) method making use of AngularValueToString to output the coordinate as query string parameters (braces are not relevant).

Output: {latitude},{longitude}

Finally, we have a SetCoordinate method that will allow us to modify the latitude and longitude in a single call.

Making the Code – Marker Class (GMarker)

A marker will be outputted like this on the map: Image 3 (color and letter are customizable)

First, we have an enumerator with available colors:

C#
#region Enumerators

    /// <summary>
    /// Available Google Static Marker colors
    /// </summary>
    public enum GMarkerColor
    {
        Red,
        Blue,
        Green
    }
#endregion

Then, the marker class (GMarker) inherits from GCoordinate and extends it with marker’s color and letter (tips and tricks are explained after):

C#
/// <summary>
/// Represent a Google Marker
/// {latitude} (required) specifies a latitudinal value with precision to 
/// 6 decimal places.
/// {longitude} (required) specifies a longitudinal value with precision to 
/// 6 decimal places.
/// {color} (optional) specifies a color from the set {red,blue,green}.
/// {alpha-character} (optional) specifies a single lowercase alphabetic character 
/// from the set {a-z}.
/// </summary>
[Serializable]
[ToolboxData("<{0}:GMarker Latitude='' Longitude='' 
    Letter='a' Color='Red' runat="server">
    </{0}:GMarker>")]
public class GMarker : GCoordinate
{
    #region Instance members

    /// <summary>
    /// Marker color
    /// </summary>
    private MarkerColor _color;

    /// <summary>
    /// Marker letter (optional)
    /// </summary>
    private char? _letter;

    #endregion

    #region Accessors

    /// <summary>
    /// Marker color
    /// </summary>
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("Red")]
    [Localizable(false)]
    public MarkerColor Color
    {
        get { return _color; }
        set { _color = value; }
    }

    /// <summary>
    /// Marker letter (optional)
    /// </summary>
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("a")]
    [Localizable(false)]
    public char? Letter
    {
        get { return _letter; }
        set { _letter = value; }
    }

    #endregion

    #region Constructors

    public GMarker()
    {
    }

    public GMarker(double latitude, double longitude, MarkerColor color)
        : base(latitude, longitude)
    {
        _color = color;
    }

    public GMarker(double latitude, double longitude, MarkerColor color, char letter)
        : this(latitude, longitude, color)
    {
        _letter = letter;
    }

    #endregion

    #region Utilities

    /// <summary>
    /// Serialize data as a query string part
    /// Format : {latitude},{longitude},{color}{alpha-character}
    /// </summary>
    /// <returns></returns>
    internal override string SerializeAsQueryString()
    {
        return string.Format
            (
                "{0},{1}{2}",
                base.SerializeAsQueryString(),
                Enum.GetName(typeof(MarkerColor), Color).ToLower(),
                (Letter == null ? "" : Letter.ToString())
            );
    }

    #endregion
}

Once again, I added the [Serializable] attribute (always for the ViewState).

The most interesting code is the serialization part: SerializeAsQueryString is again with internal visibility (to be accessed by other classes of the same assembly) and outputs the marker as query string parameters with the help of its base class (for the coordinate part). It also shows how to retrieve the name of an enumerator value (yes, it’s verbose…) transformed to lower characters in order to respect the Google Static Map API (braces are not relevant).

Serialization output: {coordinate},{color}{letter}

Note that the letter is optional, so we only output it if it’s not null (thanks to the ternary operator, gracefully used here).

Making the Code – Path Class (GPath)

The last step before building the static map class is the path class (GPath).

First, we have an enumerator with available color types (simple RBG, or RGB with Alpha channel):

C#
#region Enumerators

    /// <summary>
    /// Available Google Static Marker colors
    /// </summary>
    public enum GPathColorType
    {
        RGB,
        RGBA
    }
#endregion

This class doesn't inherit from any class but makes use of the coordinate one (GCoordinate) to store the points (tips and tricks are explained after):

C#
/// <summary>
/// Represent a Google Path
/// </summary>
[Serializable]
[ToolboxData("<{0}:GPath runat="server"></{0}:GPath>")]
public class GPath
{
    #region Enumerators

    /// <summary>
    /// Available Google Static Marker colors
    /// </summary>
    protected enum PathColorType
    {
        RGB,
        RGBA
    }

    #endregion

    #region Instance members

    /// <summary>
    /// Path color (default blue)
    /// </summary>
    private System.Drawing.Color _color = System.Drawing.Color.Blue;

    /// <summary>
    /// Path thickness, in pixels
    /// </summary>
    private int _weight = 1;

    /// <summary>
    /// Path opacity from 0% to 100% (optional)
    /// </summary>
    private int? _opacity;

    /// <summary>
    /// List of points
    /// </summary>
    private List<GCoordinate> _points = new List<GCoordinate>();

    #endregion

    #region Accessors

    /// <summary>
    /// Path color
    /// </summary>
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("Red")]
    [Localizable(false)]
    public System.Drawing.Color Color
    {
        get { return _color; }
        set { _color = value; }
    }

    /// <summary>
    /// Path thickness, in pixels
    /// </summary>
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("1")]
    [Localizable(false)]
    public int Weight
    {
        get { return _weight; }
        set { _weight = value; }
    }

    /// <summary>
    /// Path opacity from 0 to 255 (optional)
    /// </summary>
    [Bindable(true)]
    [Category("Appearance")]
    [DefaultValue("1")]
    [Localizable(false)]
    public int? Opacity
    {
        get { return _opacity; }
        set { _opacity = value; }
    }

    /// <summary>
    /// Paths (optional)
    /// </summary>
    [
    DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
    PersistenceMode(PersistenceMode.InnerDefaultProperty),
    Editor(typeof(List<GCoordinate>), typeof(System.Drawing.Design.UITypeEditor)),
    ]
    public List<GCoordinate> Points
    {
        get { return _points; }
    }

    #endregion

    #region Constructors

    public GPath()
    {
    }

    public GPath(System.Drawing.Color color, int weight)
    {
        Color = color;
        Weight = weight;
    }

    public GPath(System.Drawing.Color color, int weight, int opacity)
        : this(color, weight)
    {
        Opacity = opacity;
    }

    #endregion

    #region Utilities

    /// <summary>
    /// Serialize data as a query string part
    /// Format : path=pathColorType:pathColorValue,weight:pathWeight
    /// |pathPoint1|pathPoint2|pathPoint3|...
    /// </summary>
    /// <returns></returns>
    internal string SerializeAsQueryString()
    {
        // Checks

        // Get color type

        PathColorType pathColorType = (Opacity == null) ? PathColorType.RGB : 
            PathColorType.RGBA;

        // Get color value

        string colorValue = string.Format("{0:x8}", Color.ToArgb()).Substring(2, 6);
        if (Opacity != null)
        {
            colorValue += string.Format("{0:x2}", Opacity);
        }

        // Serialize path's header

        string serializedPath = string.Format
            (
                "path={0}:0x{1},weight:{2}|",
                (pathColorType == PathColorType.RGB) ? "rgb" : "rgba",
                colorValue,
                _weight
            );

        //Serialize path's points

        List<string> serializedPoints = new List<string>();
        foreach(GCoordinate coordinate in Points)
        {
            serializedPoints.Add(coordinate.SerializeAsQueryString());
        }

        // Returns serialized path

        return serializedPath + string.Join("|", serializedPoints.ToArray());
    }

    #endregion
}

As usual, this class is serializable ([Serializable] attribute) in order to be stored in the ViewState.

The serialization part (SerializeAsQueryString method) is again with internal visibility (to be accessed by other classes of the same assembly) and outputs the path as query string parameters with the help of the coordinate class (braces and brackets are not relevant).

Serialization output: path={rgb|rgba}:0x{ color}[{alpha}],weight:{weight}|{ coordinate}|{coordinate}...

Note that the opacity is optional and the color type will be output as "rgb" or "rgba" according to its value (note the use of the ternary operator).

Making the Code – Static Map Class (GStaticMap)

Here is the final part of our WebControl: the WebControl itself. I've sliced out the interesting parts in order to comment them step by step (you may open the whole code in your Visual Studio).

First, we have useful enumerators for the map type, the map’s image format and for the zoom levels bounds:

C#
#region Enumerators

    /// <summary>
    /// Available map types
    /// </summary>
    public enum GStaticMapType
    {
        Mobile,
        RoadMap
    }

    /// <summary>
    /// Available image formats
    /// </summary>
    public enum GStaticMapImageFormat
    {
        GIF,
        JPG,
        PNG32
    }

    /// <summary>
    /// Available zoom levels
    /// </summary>
    public enum GStaticMapZoomLevel : uint
    {
        Mini = 1,
        Maxi = 19
    }

#endregion

Here is the declaration of the static map class (GStaticMap), inheriting from System.Web.UI.WebControls.Image:

C#
[ToolboxData("<{0}:GStaticMap runat="server"></{0}:GStaticMap>")]
public class GStaticMap : System.Web.UI.WebControls.Image
{
    ..........
}

We declare private instance members of our class.

You'll notice that we have members relating to the automatic centering and automatic zooming features, which we will talk about in few minutes.

I also make use of generics for the markers and paths lists. The last member is the Google API Key, don't forget to get yours!

C#
#region Instance members

/// <summary>
/// Indicates if automatic center is enabled 
/// (available if at least one marker present)
/// </summary>
private bool _automaticCenter;

/// <summary>
/// Indicates if automatic zoom level is enabled 
/// (available if at least one marker present)
/// </summary>
private bool _automaticZoomLevel;

/// <summary>
///Center (required if markers or paths not present)
/// </summary>
private GCoordinate _center;

/// <summary>
/// ZoomLevel (required if markers or paths not present)
/// </summary>
private uint? _zoomLevel = null;

/// <summary>
/// Image format (optional)
/// </summary>
private MapImageFormat? _imageFormat = null;

/// <summary>
/// Map type (optional)
/// </summary>
private MapType? _type = null;

/// <summary>
/// List of markers
/// </summary>
private List<GMarker> _markers = new List<GMarker>();

/// <summary>
/// List of paths
/// </summary>
private List<GPath> _paths = new List<GPath>();

/// <summary>
/// Google Base API Key (required)
/// Go to http://code.google.com/apis/base/signup.html to get yours 
/// (it's free and doesn't require any subscription)
/// </summary>
private string _key;

#endregion

It’s now time to declare our public accessors.

Since we are making a WebControl, you'll notice that most of them have a Bindable attribute in order to become ASP.NET attributes (i.e. you can define them directly in the tag that you may declare in the ASPX page) and to be available in the design mode.

Don't worry if you don't see the Center, Markers and Paths here, a specific region will covers them since they are now accessible as children in the ASPX page.

Accessors are very interesting because they encapsulate the way they manage the value you pass or retrieve to them: have a look at the zoom level (ZoomLevel), and the setter (which becomes a method during the compilation) checks bounds with the help of the enumerator. By contrast, automatic center and automatic zoom accessors alter the return value through the getter (also transformed to a method by the compiler).

The last interesting thing in this part is about the ImageUrl accessor. I make use of the new keyword as a modifier in order to redefine the base class’ ImageUrl accessor. Here, I wanted ImageUrl to be readable but not settable (because it’s built by our class before rendering), so I only defined a getter returning the value of the base class accessor (base keyword):

C#
#region Accessors

/// <summary>
/// ZoomLevel (required if markers or paths not present)
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("MapZoomLevel.Mini")]
[Localizable(false)]
public uint? ZoomLevel
{
    get { return _zoomLevel; }

    set
    {
        // Check zoom bounds
        if (value < (uint)MapZoomLevel.Mini) { value = (uint)MapZoomLevel.Mini; }
        else if (value > (uint)MapZoomLevel.Maxi) { value = (uint)MapZoomLevel.Maxi; }
        // Apply new zoom
        _zoomLevel = value;
    }
}

/// <summary>
/// Image format (optional)
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("PNG")]
[Localizable(false)]
public MapImageFormat? ImageFormat
{
    get { return _imageFormat; }
    set { _imageFormat = value; }
}

/// <summary>
/// Map type (optional)
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("RoadMap")]
[Localizable(false)]
public MapType? Type
{
    get { return _type; }
    set { _type = value; }
}

/// <summary>
/// Google Base API Key (required)
/// Go to http://code.google.com/apis/base/signup.html to get yours 
/// (it's free and doesn't require any subscription)
/// </summary>
[Bindable(true)]
[Category("Google")]
[DefaultValue("")]
[Localizable(false)]
public string Key
{
    get { return _key; }
    set { _key = value; }
}

/// <summary>
/// Indicates if automatic center is enabled (available if at least one marker present)
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(false)]
public bool AutomaticCenter
{
    get { return _automaticCenter && IsAutomaticAvailable; }
    set { _automaticCenter = value; }
}

/// <summary>
/// Indicates if automatic zoom level is enabled 
/// (available if at least one marker present)
/// </summary>
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(false)]
public bool AutomaticZoomLevel
{
    get { return _automaticZoomLevel && IsAutomaticAvailable; }
    set { _automaticZoomLevel = value; }
}

/// <summary>
/// Redefine picture's ImageUrl to avoid any modification 
/// (since ImageUrl is built by the webcontrol)
/// </summary>
public new string ImageUrl
{
    get { return base.ImageUrl; }
}

/// <summary>
/// Indicates whether automatic center and levels are available
/// </summary>
public bool IsAutomaticAvailable
{
    get { return (Markers.Count > 0 || Paths.Count > 0); }
}

#endregion

Here are the accessors that you can use directly in the ASPX page, the DesignerSerializationVisibility attribute for each of them make (according to MSDN) the code generator produce code for the contents of the object, rather than for the object itself (so you can access them in the ASPX page):

C#
#region Childs

/// <summary>
/// Center (required if markers or paths not present)
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public GCoordinate Center
{
    get
    {
        if (_center == null)
        {
            _center = new GCoordinate();
        }
        return _center;
    }
}

/// <summary>
/// Markers (optional)
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<GMarker> Markers
{
    get { return _markers; }
}

/// <summary>
/// Paths (optional)
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<GPath> Paths
{
    get { return _paths; }
}

#endregion

Now, since we have all the needed members and attributes to complete our aim, it’s time to write the BuildUrl method that will produce the final URL, according to the Google Static Map API.

Serialization output: http://maps.google.com/staticmap?parameters.

C#
#region Utilities

/// <summary>
/// Build up the URL used to request the generated picture
/// Format : http://maps.google.com/staticmap?parameters
/// </summary>
/// <returns></returns>
private string BuildUrl()
{
    // Base URL (change if needed)
    string url = "http://maps.google.com/staticmap?";

    List<string> parameters = new List<string>();

    // Center (required if markers or paths not present)
    if (!AutomaticCenter)
    {
        parameters.Add("center=" + Center.SerializeAsQueryString());
    }

    // ZoomLevel (required if markers or paths not present)
    if (!AutomaticZoomLevel)
    {
        parameters.Add("zoom=" + ZoomLevel);
    }

    // Image size
    parameters.Add(string.Format("size={0}x{1}", 
        (int)Width.Value, (int)Height.Value));

    // Image format (optional)
    if (ImageFormat != null)
    {
        parameters.Add("format=" + Enum.GetName(typeof(MapImageFormat), 
            ImageFormat).ToLower());
    }

    // Map type (optional)
    if (Type != null)
    {
        parameters.Add("maptype=" + Enum.GetName(typeof(MapType), Type).ToLower());
    }

    // Markers (optional)
    if (Markers.Count > 0)
    {
        // Gather markers in a string list
        List<string> markers = new List<string>();
        foreach (GMarker poi in Markers)
        {
            markers.Add(poi.SerializeAsQueryString());
        }

        // Add them as a parameter
        parameters.Add(string.Format
            ("markers={0}", string.Join("|", markers.ToArray())));
    }

    // Paths (optional)
    if (Paths.Count > 0)
    {
        foreach (GPath path in Paths)
        {
            // Add them as a parameter
            parameters.Add(path.SerializeAsQueryString());
        }
    }

    // ZoomLevel (required if markers or paths not present)
    if (Key != string.Empty)
    {
        parameters.Add("key=" + Key);
    }

    // Build up and returns final URL
    return url + string.Join("&", parameters.ToArray());
}

#endregion

The next logical step is to set the ImageUrl accessor. The best moment to do this is just before the rendering, so we just have to overload the OnPreRender virtual method to achieve this:

C#
#region Methods

/// <summary>
/// Build the image url according to parameters and added points
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);


    // Set Image Url before rendering
    base.ImageUrl = BuildUrl();
}
#endregion

OK, our work is looking good now, but we have missed something… How will the state of our control be maintained after a postback? Obviously with the ViewState, so here is the way to do this: we must overload the LoadViewState and SaveViewState virtual methods to, respectively load ViewState values in the control and save the control values in the ViewState (you can also directly save or load some values on demand, by using accessors):

C#
/// <summary>
/// Load viewstate values
/// </summary>
/// <param name="savedState"></param>
protected override void LoadViewState(object savedState)
{
    base.LoadViewState(savedState);

    _center = (GCoordinate)ViewState["Center"];
    ZoomLevel = (uint)ViewState["ZoomLevel"];
    Type = (MapType)ViewState["MapType"];
    Markers.AddRange(new List<GMarker>((GMarker[])ViewState["Markers"]));
    Paths.AddRange(new List<GPath>((GPath[])ViewState["Paths"]));
    Key = (string)ViewState["Key"];
    AutomaticCenter = (bool)ViewState["AutomaticCenter"];
    AutomaticZoomLevel = (bool)ViewState["AutomaticZoomLevel"];
}

/// <summary>
/// Save viewstate values
/// </summary>
/// <returns></returns>
protected override object SaveViewState()
{
    ViewState["Center"] = _center;
    ViewState["ZoomLevel"] = ZoomLevel;
    ViewState["MapType"] = Type;
    ViewState["Markers"] = Markers.ToArray();
    ViewState["Paths"] = Paths.ToArray();
    ViewState["Key"] = Key;
    ViewState["AutomaticCenter"] = AutomaticCenter;
    ViewState["AutomaticZoomLevel"] = AutomaticZoomLevel;

    return base.SaveViewState();
}

The only tricky thing was to save and load the markers and paths list based on a generic class. It may be a better way to do this, but I convert the list into an array and get it back to a list during the ViewState loading phase.

Using the WebControl in an ASPX Page

If you're a Web Designer or not so close to ASP.NET programming, you can now widely use the WebControl directly in an ASPX page.

First, you need to register the WebControl in your page:

ASP.NET
<%@ Register TagPrefix="asp" Assembly="GStaticMapTest" 
    Namespace="Initia.Google.GStaticMap" %>

Then, you can show a simple map like this (don't forget runat="server" and the center coordinate, since there are no markers and paths):

ASP.NET
<asp:GStaticMap ID="gsmTestMap" AutomaticZoomLevel="true" AutomaticCenter="true" 
    Width="320" Height="200" Type="RoadMap" ZoomLevel="12" Key="put_yours_here" 
    runat="server">

    <Center Latitude="45.759723" Longitude="4.842223"></Center>

</asp:GStaticMap>

As needed, you may add one or several markers (the center is optional if you use automatic center):

ASP.NET
<asp:GStaticMap ID="gsmTestMap" AutomaticZoomLevel="true" AutomaticCenter="true" 
    Width="320" Height="200" Type="RoadMap" ZoomLevel="12" 
    Key="put_yours_here" runat="server">
    <Center Latitude="45.759723" Longitude="4.842223"></Center>
<Markers>
        <asp:GMarker Latitude="45.859723" Longitude="4.842223" 
            Letter="a" Color="Red">
        </asp:GMarker>
        <asp:GMarker Latitude="45.759723" Longitude="5.042223" 
            Letter="b" Color="Green">
        </asp:GMarker>
        <asp:GMarker Latitude="45.659723" Longitude="4.842223" 
            Letter="c" Color="Blue">
        </asp:GMarker>
        <asp:GMarker Latitude="45.759723" Longitude="4.642223" 
            Color="Green">
        </asp:GMarker>
    </Markers>
</asp:GStaticMap>

Finally, you may add one or several paths on your map:

ASP.NET
<asp:GStaticMap ID="gsmTestMap" AutomaticZoomLevel="true" AutomaticCenter="true" 
    Width="320" Height="200" Type="RoadMap" ZoomLevel="12" 
    Key="put_yours_here" runat="server">
    <Center Latitude="45.759723" Longitude="4.842223"></Center>
<Markers>
        <asp:GMarker Latitude="45.859723" 
            Longitude="4.842223" Letter="a" Color="Red">
        </asp:GMarker>
        <asp:GMarker Latitude="45.759723" 
            Longitude="5.042223" Letter="b" Color="Green">
        </asp:GMarker>
        <asp:GMarker Latitude="45.659723" 
            Longitude="4.842223" Letter="c" Color="Blue">
        </asp:GMarker>
        <asp:GMarker Latitude="45.759723" 
            Longitude="4.642223" Color="Green">
        </asp:GMarker>
    </Markers>
    <Paths>
        <asp:GPath Color="blue" Opacity="255" Weight="5">
            <Points>
                <asp:GCoordinate Latitude="45.859723" Longitude="4.842223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.759723" Longitude="5.042223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.659723" Longitude="4.842223">
                </asp:GCoordinate>
            </Points>
        </asp:GPath>
        <asp:GPath Color="red" Opacity="128" Weight="5">
            <Points>
                <asp:GCoordinate Latitude="45.659723" Longitude="4.842223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.759723" Longitude="4.642223">
                </asp:GCoordinate>
                <asp:GCoordinate Latitude="45.859723" Longitude="4.842223">
                </asp:GCoordinate>
            </Points>
        </asp:GPath>
    </Paths>
</asp:GStaticMap>

Using the WebControl in the Code-behind

If you love coding or have to programmatically manage static maps, you can use code-behind to fit your needs.

The sample project included with this article shows you how to do this, here is some explanation about the basics.

First, create an ASPX page and register the newly created WebControl:

ASP.NET
<%@ Register TagPrefix="asp" Assembly="GStaticMapTest" 
    Namespace="Initia.Google.GStaticMap" %>

Now, "instantiate" the WebControl (this reference is automatically added in the designer if the ASPX page is right) where you want the static map to render:

ASP.NET
<asp:GStaticMap ID="gsmTestMap" runat="server"></asp:GStaticMap>

At this point, you can set some attributes like automatic center, map type, width, height… and you must set the Google API Key (beware: Google will serve you 1000 times the same static map per day and per IP address, you should use cache to avoid problems if you have some traffic !)

To avoid prefixing GStaticMap components in the code-behind, use the following namespace:

C#
using Initia.Google.GStaticMap;

Then, you can show a simple map like this (don't forget the center coordinate, since there are no markers and paths):

C#
// Initialize map size
gsmTestMap.Width = int.Parse(tbMapWidth.Text);
gsmTestMap.Height = int.Parse(tbMapHeight.Text);

// Set map type to Mobile
gsmTestMap.Type = GStaticMapType.Mobile;

// Center the static map to my town: Lyon, FRANCE
gsmTestMap.Center.SetCoordinate(45.759722, 4.842222);
gsmTestMap.Key = "put_yours_here";

// Set the zoom level
gsmTestMap.ZoomLevel = 8;

As needed, you may add one or several markers (the center becomes optional if you use automatic center):

C#
gsmTestMap.Markers.Add(new GMarker(45.759722, 4.842222, GMarkerColor.Blue, 'd');

Finally, you may add one or several paths on your map like this:

VB
// Trace a path from Lyon, FRANCE (45° 45' 35? N, 4° 50' 32? E) to Paris, 
    FRANCE (48° 51' 23.68? N,
// 2° 21' 6.58? E)
GPath fromLyonToParis = new GPath(KnownColor.Red, 5, 255);
fromLyonToParis.Points.Add(new GCoordinate(45.759723, 4.842223));
fromLyonToParis.Points.Add(new GCoordinate(48.856578, 2.351828));
gsmTestMap.Paths.Add(fromLyonToParis);

Points of Interest

This WebControl now covers all features of discontinued MapData WebControl, using the official static maps API from Google.

With this article, you learned how to:

  • build a useful custom WebControl with attributes and children
  • manage the ViewState
  • use the Google Static Maps API

Known Limitations

  • Missing visual designer
  • Missing icon for the toolbox

Thanks to

MOMTeB1 for driving me to Google’s Static Map API!

History

  • 2008/04/14: First release
  • 2008/05/10: Major update
    • Center can be defined in the ASPX page
    • Markers can be defined in the ASPX page
    • A marker can be defined without letter, a point will appear instead (new Google Static Map API feature)
    • The path is now available in both ASPX page and code-behind (new Google Static Map API feature)
    • The image format is now available : GIF, PNG or JPEG (new Google Static Map API feature)
    • Classes are now in separate files instead of one "big" file

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Stambia
France France
I'm a 39 year old team deputy CTO, living and working near Lyon (France)

I started to write softwares in the end of 90's, when I was a teenager.

I acquired several but complementary skills, mainly (but not only) on Microsoft's technologies and platforms : assembly (x86, 68k), C, C++, .NET/C#, JavaScript/HTML/CSS, PHP, DBMS (MySQL, Oracle, SQL Server, etc.), etc.

During 2007-2012, I was particulary active on .NET, ASP.NET, C#, SQL Server and ORM (NHibernate) with a growing time spent on architecture, technical management, code review, etc.

Since 2013, I'm a full-time development team leader/manager at Everial, I stopped development at work but still love to develop some personnal programs during my spare time, mainly for domotic purposes. I use to play with Arduino, ESP8266/NodeCmu, Raspberry PI... running them with some C++ and .NET Core.

My hobbies are futsal, badminton, motorcycle, Formula One, domotic, gardening... and software developement.

Comments and Discussions

 
Question5 hands down! Pin
bgates197011-Apr-12 18:24
bgates197011-Apr-12 18:24 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey16-Feb-12 0:46
professionalManoj Kumar Choubey16-Feb-12 0:46 
QuestionHow to obtain the coordinate of 4 edges point? Pin
Tiramisung29-Oct-09 20:30
Tiramisung29-Oct-09 20:30 
QuestionLimit on Static Map markres? Pin
BaileyK3-Jun-09 13:35
BaileyK3-Jun-09 13:35 
AnswerRe: Limit on Static Map markres? Pin
Erichin3-Nov-09 19:30
Erichin3-Nov-09 19:30 
QuestionIts feasible to use for google map reporting functionality? Pin
AmitChampaneri15-Feb-09 20:24
AmitChampaneri15-Feb-09 20:24 
QuestionGoogle Static map is not working on mobile phones ! Pin
umit gunden17-Jun-08 3:10
umit gunden17-Jun-08 3:10 
AnswerRe: Google Static map is not working on mobile phones ! Pin
Florian DREVET17-Jun-08 4:00
Florian DREVET17-Jun-08 4:00 
GeneralRe: Google Static map is not working on mobile phones ! [modified] Pin
umit gunden17-Jun-08 19:57
umit gunden17-Jun-08 19:57 
QuestionRe: Google Static map is not working on mobile phones ! Pin
Florian DREVET18-Jun-08 2:07
Florian DREVET18-Jun-08 2:07 
AnswerRe: Google Static map is not working on mobile phones ! Pin
umit gunden24-Jun-08 4:17
umit gunden24-Jun-08 4:17 
GeneralRe: Google Static map is not working on mobile phones ! Pin
Florian DREVET24-Jun-08 7:52
Florian DREVET24-Jun-08 7:52 
AnswerRe: Google Static map is not working on mobile phones ! Pin
mbaocha27-Apr-09 19:03
mbaocha27-Apr-09 19:03 
QuestionSaving of Map info? Pin
Gears21-May-08 22:32
Gears21-May-08 22:32 
AnswerRe: Saving of Map info? Pin
Florian DREVET21-May-08 22:50
Florian DREVET21-May-08 22:50 
GeneralRe: Saving of Map info? Pin
Gears21-May-08 23:49
Gears21-May-08 23:49 
GeneralRe: Saving of Map info? Pin
Florian DREVET22-May-08 0:14
Florian DREVET22-May-08 0:14 
General[Message Removed] Pin
Mojtaba Vali21-May-08 20:32
Mojtaba Vali21-May-08 20:32 
AnswerRe: nice component Pin
Florian DREVET21-May-08 21:10
Florian DREVET21-May-08 21:10 
Generalbe very good to me Pin
iXiami8-May-08 15:56
iXiami8-May-08 15:56 
GeneralRe: be very good to me Pin
Florian DREVET15-May-08 12:40
Florian DREVET15-May-08 12:40 
GeneralStraightforward and well designed Pin
Dietmar Kurok22-Apr-08 21:16
Dietmar Kurok22-Apr-08 21:16 
GeneralRe: Straightforward and well designed Pin
Florian DREVET22-Apr-08 21:29
Florian DREVET22-Apr-08 21:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.