The ArcGIS API for Microsoft Silverlight/WPF (and Windows Phone) includes an extensible framework for adding geographic data from a variety of sources. The core library (ESRI.ArcGIS.Client) contains the base classes for the framework and the implementation for the primary source of data, ArcGIS Server. In the initial version of the API, the framework was utilized to support the use of Bing Maps imagery. In version 2.0, the framework was leveraged to support other data sources, such as OpenStreetMap, WMS, and GeoRSS in the toolkit data sources assembly (ESRI.ArcGIS.Client.Toolkit.DataSources.dll) . You'll notice Google is missing from that list of data sources. This brings up a question, and the purpose for this blog post:
Can you use Google Maps imagery in an ArcGIS Silverlight/WPF/Windows Phone application?
There are two answers; one technical, one legal. First, technically it can be done. The Web Mercator projection and the tiling scheme used by Google map and image tiles is virtually the same as Bing, new ArcGIS Online services, and OpenStreetMap. The URL format to access Google tiles directly can be discovered. For example, you can use Fiddler to view requests from a legitimate Google Maps application. Some folks even discuss the URL format and structure (e.g. Code Project).
Now for the legal answer. In short, no. You cannot access Google Maps imagery outside of an interface (read: APIs) provided by Google. This is mentioned in an online FAQ which references an item in the terms of service. The last statement in the terms of service appears to suggest that direct access to map tiles outside of an API is possible via an explicit agreement. In an email conversation with Thor Mitchell, Product Manager for the Google Maps API, he clarified these terms by stating that such agreements are rare and "they are generally limited to embedded device partnerships such as in-car navigation systems and in-flight entertainment systems." Thor also reminded me that the URL format to access Google tiles is an undocumented interface, so it can change at any time. And Google maintains a team that identifies and contacts application developers that use Google tiles (and services) in an unsupported way. If you try, you will likely be asked to stop, followed by more punitive measures if ignored.
using System;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
namespace CustomLayers
{
public class GoogleStaticMapLayer : DynamicMapServiceLayer
{
private static ESRI.ArcGIS.Client.Projection.WebMercator mercator =
new ESRI.ArcGIS.Client.Projection.WebMercator();
private const double cornerCoordinate = 20037508.3427892;
private const int WKID = 102100;
public GoogleStaticMapLayer() : base(){ }
private Lod[] Lods { get; set;}
public override void Initialize()
{
this.FullExtent =
new ESRI.ArcGIS.Client.Geometry.Envelope(-cornerCoordinate, -cornerCoordinate, cornerCoordinate, cornerCoordinate)
{
SpatialReference = new SpatialReference(WKID)
};
this.SpatialReference = new SpatialReference(WKID);
Lods = new Lod[21];
double resolution = cornerCoordinate * 2 / 256;
for (int i = 0; i < Lods.Length; i++)
{
Lods[i] = new Lod() { Resolution = resolution };
resolution /= 2;
}
base.Initialize();
}
public override void GetUrl(ESRI.ArcGIS.Client.Geometry.Envelope extent, int width, int height,
DynamicMapServiceLayer.OnUrlComplete onComplete)
{
MapPoint geogCenterPoint = null;
string mapURL = null;
try
{
if (width > 640 || height > 640)
throw new Exception("Width or height greater than 640");
double currentResolution = extent.Width / width;
int currentLodIndex = 0;
int requestWidth = 0;
int requestHeight = 0;
for (int i = 0; i < Lods.Length; i++)
{
Lod lod = Lods[i];
currentLodIndex = i;
if ((int)lod.Resolution <= (int)currentResolution)
{
requestWidth = (int)(extent.Width / lod.Resolution);
requestHeight = (int)(extent.Height / lod.Resolution);
break;
}
}
if (requestWidth > 640 || requestHeight > 640)
throw new Exception("Request width or height greater than 640");
geogCenterPoint = mercator.ToGeographic(extent.GetCenter()) as MapPoint;
mapURL = string.Format
("http://maps.google.com/maps/api/staticmap?center={0},{1}&zoom={2}&size={3}x{4}&maptype=roadmap&sensor=false",
geogCenterPoint.Y, geogCenterPoint.X, currentLodIndex, requestWidth, requestHeight);
}
catch (Exception ex)
{
// Message box just for