Click here to Skip to main content
Click here to Skip to main content

Google's Static Map API WebControl

, 21 May 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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

<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):

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):

/// <span class="code-SummaryComment"><summary></span>
/// Represents a Google Coordinate
/// <span class="code-SummaryComment"></summary></span>
[Serializable]
[ToolboxData("<{0}:GCoordinate Latitude='' 
    Longitude='' runat="server"></{0}:GCoordinate>")]
public class GCoordinate
{
    #region Instance members
    /// <span class="code-SummaryComment"><summary></span>
    /// Latitude
    /// <span class="code-SummaryComment"></summary></span>
    private double _latitude;
    /// <span class="code-SummaryComment"><summary></span>
    /// Longitude
    /// <span class="code-SummaryComment"></summary></span>
    private double _longitude;
    #endregion
    #region Accessors
    /// <span class="code-SummaryComment"><summary></span>
    /// Latitude
    /// <span class="code-SummaryComment"></summary></span>
    [Bindable(true)]
    [Category("Coordinate")]
    [DefaultValue("")]
    [Localizable(false)]
    public double Latitude
    {
        get { return _latitude; }
        set { _latitude = value; }
    }
    /// <span class="code-SummaryComment"><summary></span>
    /// Longitude
    /// <span class="code-SummaryComment"></summary></span>
    [Bindable(true)]
    [Category("Coordinate")]
    [DefaultValue("")]
    [Localizable(false)]
    public double Longitude
    {
        get { return _longitude; }
        set { _longitude = value; }
    }
    #endregion
    #region Constructors
    /// <span class="code-SummaryComment"><summary></span>
    /// Constructor
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="latitude"></param></span>
    /// <span class="code-SummaryComment"><param name="longitude"></param></span>
    public GCoordinate()
    {
    }
    /// <span class="code-SummaryComment"><summary></span>
    /// Constructor
    /// <span class="code-SummaryComment"></summary></span>
    public GCoordinate(double latitude, double longitude)
    {
        SetCoordinate(latitude, longitude);
    }
    #endregion
    #region Utilities
    /// <span class="code-SummaryComment"><summary></span>
    /// Serialize data as a query string part
    /// Format : {latitude,longitude}
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    internal virtual string SerializeAsQueryString()
    {
        return string.Format("{0},{1}", AngularValueToString(Latitude, 6), 
            AngularValueToString(Longitude, 6));
    }
    /// <span class="code-SummaryComment"><summary></span>
    /// Format an angular value to be used by GStaticMap
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="angularValue"></param></span>
    /// <span class="code-SummaryComment"><param name="fixedLength"></param></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    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: (color and letter are customizable)

First, we have an enumerator with available colors:

#region Enumerators

    /// <span class="code-SummaryComment"><summary></span>
    /// Available Google Static Marker colors
    /// <span class="code-SummaryComment"></summary></span>
    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):

/// <span class="code-SummaryComment"><summary></span>
/// 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}.
/// <span class="code-SummaryComment"></summary></span>
[Serializable]
[ToolboxData("<{0}:GMarker Latitude='' Longitude='' 
    Letter='a' Color='Red' runat="server">
    </{0}:GMarker>")]
public class GMarker : GCoordinate
{
    #region Instance members

    /// <span class="code-SummaryComment"><summary></span>
    /// Marker color
    /// <span class="code-SummaryComment"></summary></span>
    private MarkerColor _color;

    /// <span class="code-SummaryComment"><summary></span>
    /// Marker letter (optional)
    /// <span class="code-SummaryComment"></summary></span>
    private char? _letter;

    #endregion

    #region Accessors

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

    /// <span class="code-SummaryComment"><summary></span>
    /// Marker letter (optional)
    /// <span class="code-SummaryComment"></summary></span>
    [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

    /// <span class="code-SummaryComment"><summary></span>
    /// Serialize data as a query string part
    /// Format : {latitude},{longitude},{color}{alpha-character}
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    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):

#region Enumerators

    /// <span class="code-SummaryComment"><summary></span>
    /// Available Google Static Marker colors
    /// <span class="code-SummaryComment"></summary></span>
    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):

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

    /// <span class="code-SummaryComment"><summary></span>
    /// Available Google Static Marker colors
    /// <span class="code-SummaryComment"></summary></span>
    protected enum PathColorType
    {
        RGB,
        RGBA
    }

    #endregion

    #region Instance members

    /// <span class="code-SummaryComment"><summary></span>
    /// Path color (default blue)
    /// <span class="code-SummaryComment"></summary></span>
    private System.Drawing.Color _color = System.Drawing.Color.Blue;

    /// <span class="code-SummaryComment"><summary></span>
    /// Path thickness, in pixels
    /// <span class="code-SummaryComment"></summary></span>
    private int _weight = 1;

    /// <span class="code-SummaryComment"><summary></span>
    /// Path opacity from 0% to 100% (optional)
    /// <span class="code-SummaryComment"></summary></span>
    private int? _opacity;

    /// <span class="code-SummaryComment"><summary></span>
    /// List of points
    /// <span class="code-SummaryComment"></summary></span>
    private List<GCoordinate> _points = new List<GCoordinate>();

    #endregion

    #region Accessors

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

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

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

    /// <span class="code-SummaryComment"><summary></span>
    /// Paths (optional)
    /// <span class="code-SummaryComment"></summary></span>
    [
    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

    /// <span class="code-SummaryComment"><summary></span>
    /// Serialize data as a query string part
    /// Format : path=pathColorType:pathColorValue,weight:pathWeight
    /// |pathPoint1|pathPoint2|pathPoint3|...
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    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:

#region Enumerators

    /// <span class="code-SummaryComment"><summary></span>
    /// Available map types
    /// <span class="code-SummaryComment"></summary></span>
    public enum GStaticMapType
    {
        Mobile,
        RoadMap
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Available image formats
    /// <span class="code-SummaryComment"></summary></span>
    public enum GStaticMapImageFormat
    {
        GIF,
        JPG,
        PNG32
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Available zoom levels
    /// <span class="code-SummaryComment"></summary></span>
    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:

[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!

#region Instance members

/// <span class="code-SummaryComment"><summary></span>
/// Indicates if automatic center is enabled 
/// (available if at least one marker present)
/// <span class="code-SummaryComment"></summary></span>
private bool _automaticCenter;

/// <span class="code-SummaryComment"><summary></span>
/// Indicates if automatic zoom level is enabled 
/// (available if at least one marker present)
/// <span class="code-SummaryComment"></summary></span>
private bool _automaticZoomLevel;

/// <span class="code-SummaryComment"><summary></span>
///Center (required if markers or paths not present)
/// <span class="code-SummaryComment"></summary></span>
private GCoordinate _center;

/// <span class="code-SummaryComment"><summary></span>
/// ZoomLevel (required if markers or paths not present)
/// <span class="code-SummaryComment"></summary></span>
private uint? _zoomLevel = null;

/// <span class="code-SummaryComment"><summary></span>
/// Image format (optional)
/// <span class="code-SummaryComment"></summary></span>
private MapImageFormat? _imageFormat = null;

/// <span class="code-SummaryComment"><summary></span>
/// Map type (optional)
/// <span class="code-SummaryComment"></summary></span>
private MapType? _type = null;

/// <span class="code-SummaryComment"><summary></span>
/// List of markers
/// <span class="code-SummaryComment"></summary></span>
private List<GMarker> _markers = new List<GMarker>();

/// <span class="code-SummaryComment"><summary></span>
/// List of paths
/// <span class="code-SummaryComment"></summary></span>
private List<GPath> _paths = new List<GPath>();

/// <span class="code-SummaryComment"><summary></span>
/// 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)
/// <span class="code-SummaryComment"></summary></span>
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):

#region Accessors

/// <span class="code-SummaryComment"><summary></span>
/// ZoomLevel (required if markers or paths not present)
/// <span class="code-SummaryComment"></summary></span>
[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;
    }
}

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

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

/// <span class="code-SummaryComment"><summary></span>
/// 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)
/// <span class="code-SummaryComment"></summary></span>
[Bindable(true)]
[Category("Google")]
[DefaultValue("")]
[Localizable(false)]
public string Key
{
    get { return _key; }
    set { _key = value; }
}

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

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

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

/// <span class="code-SummaryComment"><summary></span>
/// Indicates whether automatic center and levels are available
/// <span class="code-SummaryComment"></summary></span>
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):

#region Childs

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

/// <span class="code-SummaryComment"><summary></span>
/// Markers (optional)
/// <span class="code-SummaryComment"></summary></span>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<GMarker> Markers
{
    get { return _markers; }
}

/// <span class="code-SummaryComment"><summary></span>
/// Paths (optional)
/// <span class="code-SummaryComment"></summary></span>
[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.

#region Utilities

/// <span class="code-SummaryComment"><summary></span>
/// Build up the URL used to request the generated picture
/// Format : http://maps.google.com/staticmap?parameters
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><returns></returns></span>
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:

#region Methods

/// <span class="code-SummaryComment"><summary></span>
/// Build the image url according to parameters and added points
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="e"></param></span>
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):

/// <span class="code-SummaryComment"><summary></span>
/// Load viewstate values
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="savedState"></param></span>
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"];
}

/// <span class="code-SummaryComment"><summary></span>
/// Save viewstate values
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><returns></returns></span>
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:

<%@ 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: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: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: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:

<%@ 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: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:

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):

// 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):

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:

// 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)

Share

About the Author

Florian DREVET
Other euroCSgroup - Cabinet Zulian ICS
France France
I'm 30 year old, living at Saint-Maurice-de-Gourdans (France, near Lyon)
 
Research and Development manager
 
My hobbies are motorcycle (I'm riding a GSXF 750), Formula One, diescast Formula One models (mainly 1/18 scale), gardening, video games, and... software developement.
 
I started to make softwares about 15 years ago as a hobby, including now about 11 years of profesional activity. I worked for :
- a web agency
- a medical ISV (Independent Software Vendor), mainly in blood tracability department
- an online computer components seller
- actually in an engineering (and scientific advisory) ISV
 
I acquired several but complementary skills during this time, mainly (but not only) on Microsoft's technologies and platforms : assembly (x86, 68k), C, C++, C#, JavaScript, PHP, DBMS (MySQL, Oracle, SQL Server, etc.), etc.
 
Since 2007, I'm particulary active on .NET, ASP.NET, C#, SQL Server and ORM (NHibernate) with a growing time spent on architecture, technical managment, code review, etc.

Comments and Discussions

 
Question5 hands down! Pinmemberbgates197011-Apr-12 19:24 
GeneralMy vote of 5 Pinmembermanoj kumar choubey16-Feb-12 1:46 
QuestionHow to obtain the coordinate of 4 edges point? PinmemberTiramisung29-Oct-09 21:30 
QuestionLimit on Static Map markres? PinmemberBaileyK3-Jun-09 14:35 
AnswerRe: Limit on Static Map markres? PinmemberErichin3-Nov-09 20:30 
QuestionIts feasible to use for google map reporting functionality? PinmemberAmitChampaneri15-Feb-09 21:24 
QuestionGoogle Static map is not working on mobile phones ! PinmemberMember 176258417-Jun-08 4:10 
AnswerRe: Google Static map is not working on mobile phones ! PinmemberFlorian DREVET17-Jun-08 5:00 
GeneralRe: Google Static map is not working on mobile phones ! [modified] PinmemberMember 176258417-Jun-08 20:57 
QuestionRe: Google Static map is not working on mobile phones ! PinmemberFlorian DREVET18-Jun-08 3:07 
AnswerRe: Google Static map is not working on mobile phones ! PinmemberMember 176258424-Jun-08 5:17 
GeneralRe: Google Static map is not working on mobile phones ! PinmemberFlorian DREVET24-Jun-08 8:52 
AnswerRe: Google Static map is not working on mobile phones ! Pinmembermbaocha27-Apr-09 20:03 
QuestionSaving of Map info? PinmemberGears21-May-08 23:32 
AnswerRe: Saving of Map info? PinmemberFlorian DREVET21-May-08 23:50 
GeneralRe: Saving of Map info? PinmemberGears22-May-08 0:49 
GeneralRe: Saving of Map info? PinmemberFlorian DREVET22-May-08 1:14 
General[Message Removed] PinmemberMojtaba Vali21-May-08 21:32 
Spam message removed
AnswerRe: nice component PinmemberFlorian DREVET21-May-08 22:10 
Generalbe very good to me PinmemberiXiami8-May-08 16:56 
GeneralRe: be very good to me PinmemberFlorian DREVET15-May-08 13:40 
GeneralStraightforward and well designed Pinmemberdkurok22-Apr-08 22:16 
GeneralRe: Straightforward and well designed PinmemberFlorian DREVET22-Apr-08 22:29 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 21 May 2008
Article Copyright 2008 by Florian DREVET
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid