Skip to content

Instantly share code, notes, and snippets.

@clupprich
Created June 15, 2009 16:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clupprich/130208 to your computer and use it in GitHub Desktop.
Save clupprich/130208 to your computer and use it in GitHub Desktop.
WMSMapServiceLayer.cs
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