namespace GMap.NET.MapProviders
{
using System;
using System.Text;
using GMap.NET.Projections;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using GMap.NET.Internals;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
public abstract class BingMapProviderBase : GMapProvider, RoutingProvider, GeocodingProvider
{
public BingMapProviderBase()
{
MaxZoom = null;
RefererUrl = "http://www.bing.com/maps/";
Copyright = string.Format("©{0} Microsoft Corporation, ©{0} NAVTEQ, ©{0} Image courtesy of NASA", DateTime.Today.Year);
}
public string Version = "875";
///
/// Bing Maps Customer Identification, more info here
/// http://msdn.microsoft.com/en-us/library/bb924353.aspx
///
public string ClientKey = null;
///
/// Converts tile XY coordinates into a QuadKey at a specified level of detail.
///
/// Tile X coordinate.
/// Tile Y coordinate.
/// Level of detail, from 1 (lowest detail)
/// to 23 (highest detail).
/// A string containing the QuadKey.
internal string TileXYToQuadKey(long tileX, long tileY, int levelOfDetail)
{
StringBuilder quadKey = new StringBuilder();
for(int i = levelOfDetail; i > 0; i--)
{
char digit = '0';
int mask = 1 << (i - 1);
if((tileX & mask) != 0)
{
digit++;
}
if((tileY & mask) != 0)
{
digit++;
digit++;
}
quadKey.Append(digit);
}
return quadKey.ToString();
}
#region GMapProvider Members
public override Guid Id
{
get
{
throw new NotImplementedException();
}
}
public override string Name
{
get
{
throw new NotImplementedException();
}
}
public override PureProjection Projection
{
get
{
return MercatorProjection.Instance;
}
}
GMapProvider[] overlays;
public override GMapProvider[] Overlays
{
get
{
if(overlays == null)
{
overlays = new GMapProvider[] { this };
}
return overlays;
}
}
public override PureImage GetTileImage(GPoint pos, int zoom)
{
throw new NotImplementedException();
}
#endregion
public bool TryCorrectVersion = true;
static bool init = false;
public override void OnInitialized()
{
if(!init && TryCorrectVersion)
{
string url = @"http://www.bing.com/maps";
try
{
string html = GMaps.Instance.UseUrlCache ? Cache.Instance.GetContent(url, CacheType.UrlCache, TimeSpan.FromHours(8)) : string.Empty;
if(string.IsNullOrEmpty(html))
{
html = GetContentUsingHttp(url);
if(!string.IsNullOrEmpty(html))
{
if(GMaps.Instance.UseUrlCache)
{
Cache.Instance.SaveContent(url, CacheType.UrlCache, html);
}
}
}
if(!string.IsNullOrEmpty(html))
{
#region -- match versions --
Regex reg = new Regex("http://ecn.t(\\d*).tiles.virtualearth.net/tiles/r(\\d*)[?*]g=(\\d*)", RegexOptions.IgnoreCase);
Match mat = reg.Match(html);
if(mat.Success)
{
GroupCollection gc = mat.Groups;
int count = gc.Count;
if(count > 2)
{
string ver = gc[3].Value;
string old = GMapProviders.BingMap.Version;
if(ver != old)
{
GMapProviders.BingMap.Version = ver;
GMapProviders.BingSatelliteMap.Version = ver;
GMapProviders.BingHybridMap.Version = ver;
#if DEBUG
Debug.WriteLine("GMapProviders.BingMap.Version: " + ver + ", old: " + old + ", consider updating source");
if(Debugger.IsAttached)
{
Thread.Sleep(5555);
}
#endif
}
else
{
Debug.WriteLine("GMapProviders.BingMap.Version: " + ver + ", OK");
}
}
}
#endregion
}
init = true; // try it only once
}
catch(Exception ex)
{
Debug.WriteLine("TryCorrectBingVersions failed: " + ex.ToString());
}
}
}
protected override bool CheckTileImageHttpResponse(System.Net.HttpWebResponse response)
{
var pass = base.CheckTileImageHttpResponse(response);
if(pass)
{
var tileInfo = response.Headers.Get("X-VE-Tile-Info");
if(tileInfo != null)
{
return !tileInfo.Equals("no-tile");
}
}
return pass;
}
#region RoutingProvider
public MapRoute GetRoute(PointLatLng start, PointLatLng end, bool avoidHighways, bool walkingMode, int Zoom)
{
string tooltip;
int numLevels;
int zoomFactor;
MapRoute ret = null;
List points = GetRoutePoints(MakeRouteUrl(start, end, LanguageStr, avoidHighways, walkingMode), Zoom, out tooltip, out numLevels, out zoomFactor);
if(points != null)
{
ret = new MapRoute(points, tooltip);
}
return ret;
}
public MapRoute GetRoute(string start, string end, bool avoidHighways, bool walkingMode, int Zoom)
{
throw new NotImplementedException("check GetRoute(PointLatLng start...");
}
string MakeRouteUrl(PointLatLng start, PointLatLng end, string language, bool avoidHighways, bool walkingMode)
{
string addition = avoidHighways ? "&avoid=highways" : string.Empty;
string mode = walkingMode ? "Walking" : "Driving";
return string.Format(CultureInfo.InvariantCulture, RouteUrlFormatPointLatLng, mode, start.Lat, start.Lng, end.Lat, end.Lng, addition, ClientKey);
}
List GetRoutePoints(string url, int zoom, out string tooltipHtml, out int numLevel, out int zoomFactor)
{
List points = null;
tooltipHtml = string.Empty;
numLevel = -1;
zoomFactor = -1;
try
{
string urlEnd = url.Substring(url.IndexOf("Routes/"));
string route = GMaps.Instance.UseRouteCache ? Cache.Instance.GetContent(urlEnd, CacheType.RouteCache) : string.Empty;
if(string.IsNullOrEmpty(route))
{
route = GetContentUsingHttp(url);
if(!string.IsNullOrEmpty(route))
{
if(GMaps.Instance.UseRouteCache)
{
Cache.Instance.SaveContent(urlEnd, CacheType.RouteCache, route);
}
}
}
// parse values
if(!string.IsNullOrEmpty(route))
{
#region -- title --
int tooltipEnd = 0;
{
int x = route.IndexOf("") + 17;
if(x >= 17)
{
tooltipEnd = route.IndexOf("", x + 1);
if(tooltipEnd > 0)
{
int l = tooltipEnd - x;
if(l > 0)
{
//tooltipHtml = route.Substring(x, l).Replace(@"\x26#160;", " ");
tooltipHtml = route.Substring(x, l);
}
}
}
}
#endregion
#region -- points --
XmlDocument doc = new XmlDocument();
doc.LoadXml(route);
XmlNode xn = doc["Response"];
string statuscode = xn["StatusCode"].InnerText;
switch(statuscode)
{
case "200":
{
xn = xn["ResourceSets"]["ResourceSet"]["Resources"]["Route"]["RoutePath"]["Line"];
XmlNodeList xnl = xn.ChildNodes;
if(xnl.Count > 0)
{
points = new List();
foreach(XmlNode xno in xnl)
{
XmlNode latitude = xno["Latitude"];
XmlNode longitude = xno["Longitude"];
points.Add(new PointLatLng(double.Parse(latitude.InnerText, CultureInfo.InvariantCulture),
double.Parse(longitude.InnerText, CultureInfo.InvariantCulture)));
}
}
break;
}
// no status implementation on routes yet although when introduced these are the codes. Exception will be catched.
case "400":
throw new Exception("Bad Request, The request contained an error.");
case "401":
throw new Exception("Unauthorized, Access was denied. You may have entered your credentials incorrectly, or you might not have access to the requested resource or operation.");
case "403":
throw new Exception("Forbidden, The request is for something forbidden. Authorization will not help.");
case "404":
throw new Exception("Not Found, The requested resource was not found.");
case "500":
throw new Exception("Internal Server Error, Your request could not be completed because there was a problem with the service.");
case "501":
throw new Exception("Service Unavailable, There's a problem with the service right now. Please try again later.");
default:
points = null;
break; // unknown, for possible future error codes
}
#endregion
}
}
catch(Exception ex)
{
points = null;
Debug.WriteLine("GetRoutePoints: " + ex);
}
return points;
}
// example : http://dev.virtualearth.net/REST/V1/Routes/Driving?o=xml&wp.0=44.979035,-93.26493&wp.1=44.943828508257866,-93.09332862496376&optmz=distance&rpo=Points&key=[PROVIDEYOUROWNKEY!!]
static readonly string RouteUrlFormatPointLatLng = "http://dev.virtualearth.net/REST/V1/Routes/{0}?o=xml&wp.0={1},{2}&wp.1={3},{4}{5}&optmz=distance&rpo=Points&key={6}";
#endregion RoutingProvider
#region GeocodingProvider
public GeoCoderStatusCode GetPoints(string keywords, out List pointList)
{
return GetLatLngFromGeocoderUrl(MakeGeocoderUrl("q=" + keywords), out pointList);
}
public PointLatLng? GetPoint(string keywords, out GeoCoderStatusCode status)
{
List pointList;
status = GetPoints(keywords, out pointList);
return pointList != null && pointList.Count > 0 ? pointList[0] : (PointLatLng?)null;
}
public GeoCoderStatusCode GetPoints(Placemark placemark, out List pointList)
{
return GetLatLngFromGeocoderUrl(MakeGeocoderDetailedUrl(placemark), out pointList);
}
public PointLatLng? GetPoint(Placemark placemark, out GeoCoderStatusCode status)
{
List pointList;
status = GetLatLngFromGeocoderUrl(MakeGeocoderDetailedUrl(placemark), out pointList);
return pointList != null && pointList.Count > 0 ? pointList[0] : (PointLatLng?)null;
}
string MakeGeocoderDetailedUrl(Placemark placemark)
{
string parameters = string.Empty;
if(!AddFieldIfNotEmpty(ref parameters, "countryRegion", placemark.CountryNameCode))
AddFieldIfNotEmpty(ref parameters, "countryRegion", placemark.CountryName);
AddFieldIfNotEmpty(ref parameters, "adminDistrict", placemark.DistrictName);
AddFieldIfNotEmpty(ref parameters, "locality", placemark.LocalityName);
AddFieldIfNotEmpty(ref parameters, "postalCode", placemark.PostalCodeNumber);
if(!string.IsNullOrEmpty(placemark.HouseNo))
AddFieldIfNotEmpty(ref parameters, "addressLine", placemark.ThoroughfareName + " " + placemark.HouseNo);
else
AddFieldIfNotEmpty(ref parameters, "addressLine", placemark.ThoroughfareName);
return MakeGeocoderUrl(parameters);
}
bool AddFieldIfNotEmpty(ref string Input, string FieldName, string Value)
{
if(!string.IsNullOrEmpty(Value))
{
if(string.IsNullOrEmpty(Input))
Input = string.Empty;
else
Input = Input + "&";
Input = Input + FieldName + "=" + Value;
return true;
}
return false;
}
public GeoCoderStatusCode GetPlacemarks(PointLatLng location, out List placemarkList)
{
throw new NotImplementedException();
}
public Placemark? GetPlacemark(PointLatLng location, out GeoCoderStatusCode status)
{
throw new NotImplementedException();
}
string MakeGeocoderUrl(string keywords)
{
return string.Format(CultureInfo.InvariantCulture, GeocoderUrlFormat, keywords, ClientKey);
}
GeoCoderStatusCode GetLatLngFromGeocoderUrl(string url, out List pointList)
{
var status = GeoCoderStatusCode.Unknow;
pointList = null;
try
{
string urlEnd = url.Substring(url.IndexOf("Locations?"));
string geo = GMaps.Instance.UseGeocoderCache ? Cache.Instance.GetContent(urlEnd, CacheType.GeocoderCache) : string.Empty;
bool cache = false;
if(string.IsNullOrEmpty(geo))
{
geo = GetContentUsingHttp(url);
if(!string.IsNullOrEmpty(geo))
{
cache = true;
}
}
status = GeoCoderStatusCode.Unknow;
if(!string.IsNullOrEmpty(geo))
{
if(geo.StartsWith("();
xn = xn["ResourceSets"]["ResourceSet"]["Resources"];
XmlNodeList xnl = xn.ChildNodes;
foreach(XmlNode xno in xnl)
{
XmlNode latitude = xno["Point"]["Latitude"];
XmlNode longitude = xno["Point"]["Longitude"];
pointList.Add(new PointLatLng(Double.Parse(latitude.InnerText, CultureInfo.InvariantCulture),
Double.Parse(longitude.InnerText, CultureInfo.InvariantCulture)));
}
if(pointList.Count > 0)
{
status = GeoCoderStatusCode.G_GEO_SUCCESS;
if(cache && GMaps.Instance.UseGeocoderCache)
{
Cache.Instance.SaveContent(urlEnd, CacheType.GeocoderCache, geo);
}
break;
}
status = GeoCoderStatusCode.G_GEO_UNKNOWN_ADDRESS;
break;
}
case "400":
status = GeoCoderStatusCode.G_GEO_BAD_REQUEST;
break; // bad request, The request contained an error.
case "401":
status = GeoCoderStatusCode.G_GEO_BAD_KEY;
break; // Unauthorized, Access was denied. You may have entered your credentials incorrectly, or you might not have access to the requested resource or operation.
case "403":
status = GeoCoderStatusCode.G_GEO_BAD_REQUEST;
break; // Forbidden, The request is for something forbidden. Authorization will not help.
case "404":
status = GeoCoderStatusCode.G_GEO_UNKNOWN_ADDRESS;
break; // Not Found, The requested resource was not found.
case "500":
status = GeoCoderStatusCode.G_GEO_SERVER_ERROR;
break; // Internal Server Error, Your request could not be completed because there was a problem with the service.
case "501":
status = GeoCoderStatusCode.Unknow;
break; // Service Unavailable, There's a problem with the service right now. Please try again later.
default:
status = GeoCoderStatusCode.Unknow;
break; // unknown, for possible future error codes
}
}
}
}
catch(Exception ex)
{
status = GeoCoderStatusCode.ExceptionInCode;
Debug.WriteLine("GetLatLngFromGeocoderUrl: " + ex);
}
return status;
}
// http://dev.virtualearth.net/REST/v1/Locations/1%20Microsoft%20Way%20Redmond%20WA%2098052?o=xml&key=BingMapsKey
static readonly string GeocoderUrlFormat = "http://dev.virtualearth.net/REST/v1/Locations?{0}&o=xml&key={1}";
#endregion GeocodingProvider
}
///
/// BingMapProvider provider
///
public class BingMapProvider : BingMapProviderBase
{
public static readonly BingMapProvider Instance;
BingMapProvider()
{
}
static BingMapProvider()
{
Instance = new BingMapProvider();
}
#region GMapProvider Members
readonly Guid id = new Guid("D0CEB371-F10A-4E12-A2C1-DF617D6674A8");
public override Guid Id
{
get
{
return id;
}
}
readonly string name = "BingMap";
public override string Name
{
get
{
return name;
}
}
public override PureImage GetTileImage(GPoint pos, int zoom)
{
string url = MakeTileImageUrl(pos, zoom, LanguageStr);
return GetTileImageUsingHttp(url);
}
#endregion
string MakeTileImageUrl(GPoint pos, int zoom, string language)
{
string key = TileXYToQuadKey(pos.X, pos.Y, zoom);
return string.Format(UrlFormat, GetServerNum(pos, 4), key, Version, language, (!string.IsNullOrEmpty(ClientKey) ? "&key=" + ClientKey : string.Empty));
}
// http://ecn.t0.tiles.virtualearth.net/tiles/r120030?g=875&mkt=en-us&lbl=l1&stl=h&shading=hill&n=z
static readonly string UrlFormat = "http://ecn.t{0}.tiles.virtualearth.net/tiles/r{1}?g={2}&mkt={3}&lbl=l1&stl=h&shading=hill&n=z{4}";
}
}