|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Introduction
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. BackgroundFirst, 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 DesignOur goal is to display a map with, optionally, some markers and a path. Since the map will be rendered as an image ( It’s now time to think about the classes that will be the foundations of our WebControl. First, we have a static map (
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 marker is defined by the following properties:
A path is defined by the following properties:
A coordinate is defined by the following properties:
Making the Code – Coordinate Class (GCoordinate)It’s now time for some code. We'll start our control with the coordinate class ( /// <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 Secondly, let's have a look at the serialization methods: the Output: {latitude},{longitude} Finally, we have a Making the Code – Marker Class (GMarker)A marker will be outputted like this on the map: First, we have an enumerator with available colors: #region Enumerators
/// <summary>
/// Available Google Static Marker colors
/// </summary>
public enum GMarkerColor
{
Red,
Blue,
Green
}
#endregion
Then, the marker class ( /// <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 The most interesting code is the serialization part: Serialization output: {coordinate},{color}{letter} Note that the letter is optional, so we only output it if it’s not Making the Code – Path Class (GPath)The last step before building the static map class is the path class ( First, we have an enumerator with available color types (simple RBG, or RGB with Alpha channel): #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 ( /// <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 ( The serialization part ( 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
/// <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 ( [ToolboxData("<{0}:GStaticMap runat="server"></{0}:GStaticMap>")]
public class GStaticMap : System.Web.UI.WebControls.Image
{
..........
}
We declare 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
/// <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 Since we are making a WebControl, you'll notice that most of them have a 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 ( The last interesting thing in this part is about the #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 #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 Serialization output: http://maps.google.com/staticmap?parameters. #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 #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 /// <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 Using the WebControl in an ASPX PageIf 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 <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 | ||||||||||||||||||||