Created
June 15, 2009 16:43
-
-
Save clupprich/130208 to your computer and use it in GitHub Desktop.
WMSMapServiceLayer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Net; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Documents; | |
using System.Windows.Ink; | |
using System.Windows.Input; | |
using System.Windows.Media; | |
using System.Windows.Media.Animation; | |
using System.Windows.Shapes; | |
using System.Text; | |
using ESRI.ArcGIS.Geometry; | |
using System.Xml.Linq; | |
using ESRI.ArcGIS; | |
using System.Xml; | |
using System.Diagnostics; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using System.Xml.XPath; | |
namespace ESRI.ArcGIS.DataSources.WMS | |
{ | |
/// <summary> | |
/// A WMS service layer for the ArcGIS API for Microsoft Silverlight/WPF | |
/// Original code from ESRI. | |
/// Modifications by Christoph Lupprich - clupprich (at) gmail (dot) com. | |
/// </summary> | |
public class WMSMapServiceLayer : DynamicMapServiceLayer | |
{ | |
List<string> _layers; | |
string _proxyUrl; | |
BoundingExtent _boundingExtent = new BoundingExtent(); | |
// Coordinate system WKIDs in WMS 1.3 where X,Y (Long,Lat) switched to Y,X (Lat,Long) | |
private int[,] LatLongCRSRanges = new int[,] { { 4001, 4999 }, | |
{2044, 2045}, {2081, 2083}, {2085, 2086}, {2093, 2093}, | |
{2096, 2098}, {2105, 2132}, {2169, 2170}, {2176, 2180}, | |
{2193, 2193}, {2200, 2200}, {2206, 2212}, {2319, 2319}, | |
{2320, 2462}, {2523, 2549}, {2551, 2735}, {2738, 2758}, | |
{2935, 2941}, {2953, 2953}, {3006, 3030}, {3034, 3035}, | |
{3058, 3059}, {3068, 3068}, {3114, 3118}, {3126, 3138}, | |
{3300, 3301}, {3328, 3335}, {3346, 3346}, {3350, 3352}, | |
{3366, 3366}, {3416, 3416}, {20004, 20032}, {20064, 20092}, | |
{21413, 21423}, {21473, 21483}, {21896, 21899}, {22171, 22177}, | |
{22181, 22187}, {22191, 22197}, {25884, 25884}, {27205, 27232}, | |
{27391, 27398}, {27492, 27492}, {28402, 28432}, {28462, 28492}, | |
{30161, 30179}, {30800, 30800}, {31251, 31259}, {31275, 31279}, | |
{31281, 31290}, {31466, 31700} }; | |
public WMSMapServiceLayer() | |
: base() | |
{ } | |
#region Public Properties | |
/// <summary> | |
/// Required. Gets or sets the URL to a WMS service endpoint. | |
/// For example, | |
/// http://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer, | |
/// http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi. | |
/// </summary> | |
/// <value>The URL.</value> | |
public string Url | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Required. Gets or sets the unique layer ids in a WMS service. | |
/// Each id is a string value. At least one layer id must be defined. | |
/// </summary> | |
/// <value>A string array of layer ids.</value> | |
[System.ComponentModel.TypeConverter(typeof(StringToStringArrayConverter))] | |
public string[] Layers | |
{ | |
get { return _layers.ToArray(); } | |
set | |
{ | |
_layers = new List<string>(value); | |
OnLayerChanged(); | |
} | |
} | |
/// <summary> | |
/// Optional. Gets or sets the URL to a proxy service that brokers Web requests between the Silverlight | |
/// client and a WMS service. Use a proxy service when the WMS service is not hosted on a site that provides | |
/// a cross domain policy file (clientaccesspolicy.xml or crossdomain.xml). You can also use a proxy to | |
/// convert png images to a bit-depth that supports transparency in Silverlight. | |
/// </summary> | |
/// <value>The proxy URL string.</value> | |
public string ProxyUrl | |
{ | |
get { return _proxyUrl; } | |
set | |
{ | |
_proxyUrl = value; | |
_proxyUrl += "?"; | |
/* | |
if (_proxyUrl.StartsWith("~")) | |
{ | |
_proxyUrl = _proxyUrl.Replace("~", string.Format("{0}://{1}:{2}", | |
Application.Current.Host.Source.Scheme, | |
Application.Current.Host.Source.Host, | |
Application.Current.Host.Source.Port)); | |
} | |
*/ | |
} | |
} | |
/// <summary> | |
/// Optional. Gets or sets the WMS version. If SkipGetCapabilities property is set to true, this value determines version requested. | |
/// If SkipGetCapabilities is false, this value determines version to retrieve. If no value specified, default value returned from | |
/// the site will be used. | |
/// </summary> | |
/// <value>The version string.</value> | |
public string Version | |
{ | |
get; | |
set; | |
} | |
/// <summary> | |
/// Optional. Gets or sets a value indicating whether to skip a request to get capabilities. | |
/// Default value is false. Set SkipGetCapabilities if the site hosting the WMS service does not provide a | |
/// cross domain policy file and you do not have a proxy page. In this case, you must set the WMS service version. | |
/// If true, the initial and full extent of the WMS Silverlight layer will not be defined. | |
/// </summary> | |
public bool SkipGetCapabilities | |
{ | |
get; | |
set; | |
} | |
#endregion | |
private bool initializing; | |
/// <summary> | |
/// Initializes this a WMS layer. Calls GetCapabilities if SkipGetCapabilities is false. | |
/// </summary> | |
public override void Initialize() | |
{ | |
if (initializing || IsInitialized) return; | |
initializing = true; | |
if (SkipGetCapabilities) | |
{ | |
base.Initialize(); | |
} | |
else | |
{ | |
string wmsUrl = string.Format("{0}{1}{2}{3}", | |
ProxyUrl, | |
Url, | |
"?service=WMS&request=GetCapabilities&version=", | |
Version); | |
WebClient client = new WebClient(); | |
client.DownloadStringCompleted += client_DownloadStringCompleted; | |
client.DownloadStringAsync(new Uri(wmsUrl)); | |
} | |
} | |
internal void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) | |
{ | |
if (!CheckForError(e)) | |
{ | |
// Will be set when capabilities processed | |
InitialExtent = new ESRI.ArcGIS.Geometry.Envelope(); | |
XmlDocument doc = new XmlDocument(); | |
doc.LoadXml(e.Result); | |
processDocument(doc); | |
// Define full extent when all included layers are processed | |
FullExtent = new Envelope(_boundingExtent.XMin, _boundingExtent.XMax, | |
_boundingExtent.YMin, _boundingExtent.YMax); | |
} | |
// Call initialize regardless of error | |
base.Initialize(); | |
} | |
#region Process Capabilities | |
private void processDocument(XmlDocument doc) | |
{ | |
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable); | |
nsManager.AddNamespace("wms", "http://www.opengis.net/wms"); | |
XmlNode capabilities = doc.SelectSingleNode("WMT_MS_Capabilities|wms:WMS_Capabilities", nsManager); | |
Version = capabilities.Attributes["version"].Value; | |
Debug.WriteLine(String.Format("WMS Version is {0}", Version)); | |
XmlNodeList layers = doc.SelectNodes("//Layer|//wms:Layer", nsManager); | |
foreach (XmlNode layer in layers) | |
{ | |
string name = layer.SelectSingleNode("Name|wms:Name", nsManager).InnerText; | |
if (!_layers.Contains(name)) | |
continue; | |
XmlNode bbox = layer.SelectSingleNode("BoundingBox|wms:BoundingBox", nsManager); | |
InitialExtent = getBoundingBox(bbox); | |
_boundingExtent.XMin = InitialExtent.XMin; | |
_boundingExtent.YMin = InitialExtent.YMin; | |
_boundingExtent.XMax = InitialExtent.XMax; | |
_boundingExtent.YMax = InitialExtent.YMax; | |
} | |
} | |
private Envelope getBoundingBox(XmlNode node) | |
{ | |
Envelope envelope = new Envelope(); | |
switch (Version) | |
{ | |
case "1.3.0": | |
envelope.SpatialReference = new SpatialReference(int.Parse(node.Attributes["CRS"].Value.Split(':')[1])); | |
envelope.XMin = Double.Parse(node.Attributes["minx"].Value, CultureInfo.InvariantCulture); | |
envelope.YMin = Double.Parse(node.Attributes["miny"].Value, CultureInfo.InvariantCulture); | |
envelope.XMax = Double.Parse(node.Attributes["maxx"].Value, CultureInfo.InvariantCulture); | |
envelope.YMax = Double.Parse(node.Attributes["maxy"].Value, CultureInfo.InvariantCulture); | |
break; | |
case "1.1.1": | |
envelope.SpatialReference = new SpatialReference(int.Parse(node.Attributes["SRS"].Value.Split(':')[1])); | |
envelope.XMin = Double.Parse(node.Attributes["minx"].Value, CultureInfo.InvariantCulture); | |
envelope.YMin = Double.Parse(node.Attributes["miny"].Value, CultureInfo.InvariantCulture); | |
envelope.XMax = Double.Parse(node.Attributes["maxx"].Value, CultureInfo.InvariantCulture); | |
envelope.YMax = Double.Parse(node.Attributes["maxy"].Value, CultureInfo.InvariantCulture); | |
break; | |
} | |
return envelope; | |
} | |
#endregion | |
private bool CheckForError(DownloadStringCompletedEventArgs e) | |
{ | |
if (e.Cancelled) | |
{ | |
InitializationFailure = new Exception("Request Cancelled"); | |
return true; | |
} | |
if (e.Error != null) | |
{ | |
Exception ex = e.Error; | |
if (ex is System.Security.SecurityException) | |
{ | |
ex = new System.Security.SecurityException( | |
@"A security exception occured while trying to connect to the WMS service. | |
Make sure you have a cross domain policy file available at the root for your server that allows for requests from this application. | |
If not, use a proxy page (handler) to broker communication.", | |
ex); | |
} | |
InitializationFailure = ex; | |
return true; | |
} | |
return false; | |
} | |
/// <summary> | |
/// Gets the URL. Override from DynamicMapServiceLayer | |
/// </summary> | |
/// <param name="extent">The extent.</param> | |
/// <param name="width">The width.</param> | |
/// <param name="height">The height.</param> | |
/// <param name="onComplete">OnUrlComplete delegate.</param> | |
/// <remarks> | |
/// The Map has a private method loadLayerInView which calls Layer.Draw. | |
/// The DynamicMapServiceLayer abstract class overrides the Draw method and calls | |
/// DynamicMapServiceLayer.GetUrl which must be implemented in a subclass. | |
/// The last parameter is the OnUrlComplete delegate, which is used to pass the appropriate values | |
/// (url, width, height, envelope) to the private DynamicMapServiceLayer.getUrlComplete method. | |
/// </remarks> | |
public override void GetUrl(ESRI.ArcGIS.Geometry.Envelope extent, int width, int height, | |
DynamicMapServiceLayer.OnUrlComplete onComplete) | |
{ | |
if (width == 0 && height == 0) | |
return; | |
int extentWKID = extent.SpatialReference.WKID; | |
StringBuilder mapURL = new StringBuilder(); | |
mapURL.Append(ProxyUrl + Url); | |
mapURL.Append("?service=WMS"); | |
mapURL.Append("&request=GetMap"); | |
mapURL.AppendFormat("&width={0}", width); | |
mapURL.AppendFormat("&height={0}", height); | |
mapURL.AppendFormat("&format={0}", "image/png"); | |
mapURL.AppendFormat("&layers={0}", String.Join(",", Layers)); | |
mapURL.Append("&styles="); | |
mapURL.AppendFormat("&bgcolor={0}", "0xFFFFFF"); | |
mapURL.AppendFormat("&transparent={0}", "true"); | |
mapURL.AppendFormat("&version={0}", Version); | |
switch (Version) | |
{ | |
case ("1.1.1"): | |
mapURL.AppendFormat("&SRS=EPSG:{0}", extentWKID); | |
mapURL.AppendFormat(CultureInfo.InvariantCulture, | |
"&bbox={0},{1},{2},{3}", extent.XMin, extent.YMin, extent.XMax, extent.YMax); | |
break; | |
case ("1.3"): | |
case ("1.3.0"): | |
mapURL.AppendFormat("&CRS=EPSG:{0}", extentWKID); | |
bool useLatLong = false; | |
int length = LatLongCRSRanges.Length / 2; | |
for (int count = 0; count < length; count++) | |
{ | |
if (extentWKID >= LatLongCRSRanges[count, 0] && extentWKID <= LatLongCRSRanges[count, 1]) | |
{ | |
useLatLong = true; | |
break; | |
} | |
} | |
if (useLatLong) | |
mapURL.AppendFormat(CultureInfo.InvariantCulture, "&bbox={0},{1},{2},{3}", extent.YMin, extent.XMin, extent.YMax, extent.XMax); | |
else | |
mapURL.AppendFormat(CultureInfo.InvariantCulture, "&bbox={0},{1},{2},{3}", extent.XMin, extent.YMin, extent.XMax, extent.YMax); | |
break; | |
} | |
onComplete(mapURL.ToString(), width, height, new ESRI.ArcGIS.Geometry.Envelope() | |
{ | |
XMin = extent.XMin, | |
YMin = extent.YMin, | |
XMax = extent.XMax, | |
YMax = extent.YMax | |
}); | |
} | |
internal class BoundingExtent | |
{ | |
double _xmin = double.MaxValue; | |
double _ymin = double.MaxValue; | |
double _xmax = double.MinValue; | |
double _ymax = double.MinValue; | |
public double XMin | |
{ | |
get { return _xmin; } | |
set { if (value < _xmin) _xmin = value; } | |
} | |
public double YMin | |
{ | |
get { return _ymin; } | |
set { if (value < _ymin) _ymin = value; } | |
} | |
public double XMax | |
{ | |
get { return _xmax; } | |
set { if (value > _xmax) _xmax = value; } | |
} | |
public double YMax | |
{ | |
get { return _ymax; } | |
set { if (value > _ymax) _ymax = value; } | |
} | |
} | |
/// <summary> | |
/// String To String Array Converter | |
/// </summary> | |
public sealed class StringToStringArrayConverter : System.ComponentModel.TypeConverter | |
{ | |
public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) | |
{ | |
return (sourceType == typeof(string)); | |
} | |
public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) | |
{ | |
if (value == null) | |
return null; | |
if (value is string) | |
{ | |
string[] values = (value as string).Replace(" ", "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | |
if (values.Length == 0) | |
return null; | |
return values; | |
} | |
throw new NotSupportedException("Cannot convert to string array"); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment