// Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
//
// This file is part of SharpMap.
// SharpMap is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// SharpMap is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
// You should have received a copy of the GNU Lesser General Public License
// along with SharpMap; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using GeoAPI.Geometries;
using SharpMap.Api;
namespace SharpMap.Layers
{
///
/// Web Map Service layer
///
///
/// The WmsLayer is currently very basic and doesn't support automatic fetching of the WMS Service Description.
/// Instead you would have to add the nessesary parameters to the URL,
/// and the WmsLayer will set the remaining BoundingBox property and proper requests that changes between the requests.
/// See the example below.
///
///
/// The following example creates a map with a WMS layer the Demis WMS Server
///
/// myMap = new SharpMap.Map(new System.Drawing.Size(500,250);
/// string wmsUrl = "http://www2.demis.nl/mapserver/request.asp";
/// SharpMap.Layers.WmsLayer myLayer = new SharpMap.Layers.WmsLayer("Demis WMS", myLayer);
/// myLayer.AddLayer("Bathymetry");
/// myLayer.AddLayer("Countries");
/// myLayer.AddLayer("Topography");
/// myLayer.AddLayer("Hillshading");
/// myLayer.SetImageFormat(layWms.OutputFormats[0]);
/// myLayer.SpatialReferenceSystem = "EPSG:4326";
/// myMap.Layers.Add(myLayer);
/// myMap.Center = new SharpMap.Geometries.Point(0, 0);
/// myMap.Zoom = 360;
/// myMap.MaximumZoom = 360;
/// myMap.MinimumZoom = 0.1;
///
///
public class WmsLayer : SharpMap.Layers.Layer
{
private SharpMap.Web.Wms.Client wmsClient;
private string mimeType = "";
///
/// Initializes a new layer, and downloads and parses the service description
///
/// In and ASP.NET application the service description is automatically cached for 24 hours when not specified
public WmsLayer()
: this("", "", new TimeSpan(24, 0, 0))
{
}
///
/// Initializes a new layer, and downloads and parses the service description
///
/// In and ASP.NET application the service description is automatically cached for 24 hours when not specified
/// Layername
/// Url of WMS server
public WmsLayer(string layername, string url)
: this(layername, url, new TimeSpan(24, 0, 0))
{
}
///
/// Initializes a new layer, and downloads and parses the service description
///
/// Layername
/// Url of WMS server
/// Time for caching Service Description (ASP.NET only)
public WmsLayer(string layername, string url, TimeSpan cachetime)
: this(layername, url, cachetime, null)
{
}
///
/// Initializes a new layer, and downloads and parses the service description
///
/// In and ASP.NET application the service description is automatically cached for 24 hours when not specified
/// Layername
/// Url of WMS server
/// Proxy
public WmsLayer(string layername, string url, System.Net.WebProxy proxy)
: this(layername, url, new TimeSpan(24,0,0), proxy)
{
}
///
/// Initializes a new layer, and downloads and parses the service description
///
/// Layername
/// Url of WMS server
/// Time for caching Service Description (ASP.NET only)
/// Proxy
public WmsLayer(string layername, string url, TimeSpan cachetime, System.Net.WebProxy proxy)
{
this.proxy = proxy;
timeOut = 10000;
this.Name = layername;
this.Cachetime = cachetime;
this.Url = url; // Also does the initialization if an non-empty url was given
}
private void Initialize()
{
continueOnError = true;
if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Cache["SharpMap_WmsClient_" + url] != null)
{
wmsClient = (SharpMap.Web.Wms.Client)System.Web.HttpContext.Current.Cache["SharpMap_WmsClient_" + url];
}
else
{
wmsClient = new SharpMap.Web.Wms.Client(url, proxy);
if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.Cache.Insert("SharpMap_WmsClient_" + url, wmsClient, null,
System.Web.Caching.Cache.NoAbsoluteExpiration, cachetime);
}
//Set default mimetype - We prefer compressed formats
if (OutputFormats.Contains("image/jpeg")) mimeType = "image/jpeg";
else if (OutputFormats.Contains("image/png")) mimeType = "image/png";
else if (OutputFormats.Contains("image/gif")) mimeType = "image/gif";
else //None of the default formats supported - Look for the first supported output format
{
bool formatSupported = false;
foreach (System.Drawing.Imaging.ImageCodecInfo encoder in System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders())
if (OutputFormats.Contains(encoder.MimeType.ToLower()))
{
formatSupported = true;
mimeType = encoder.MimeType;
break;
}
if (!formatSupported)
throw new ArgumentException("GDI+ doesn't not support any of the mimetypes supported by this WMS service");
}
layerList = new Collection();
stylesList = new Collection();
}
private TimeSpan cachetime;
public virtual TimeSpan Cachetime
{
get { return cachetime; }
set { cachetime = value; }
}
private string url;
public virtual string Url
{
get { return url; }
set
{
url = value;
if (url != string.Empty)
{
Initialize();
}
}
}
public virtual string MimeType
{
get { return mimeType; }
set { mimeType = value; }
}
private IList layerList;
///
/// Gets the list of enabled layers
///
public virtual IList LayerList
{
get { return layerList; }
set { layerList = value; }
}
///
/// Adds a layer to WMS request
///
/// Layer names are case sensitive.
/// Name of layer
/// Throws an exception is an unknown layer is added
public virtual void AddLayer(string name)
{
if(!LayerExists(wmsClient.Layer,name))
throw new ArgumentException("Cannot add WMS Layer - Unknown layername");
layerList.Add(name);
}
///
/// Recursive method for checking whether a layername exists
///
///
///
///
private bool LayerExists(SharpMap.Web.Wms.Client.WmsServerLayer layer, string name)
{
if(name == layer.Name) return true;
foreach (SharpMap.Web.Wms.Client.WmsServerLayer childlayer in layer.ChildLayers)
if (LayerExists(childlayer,name)) return true;
return false;
}
///
/// Removes a layer from the layer list
///
/// Name of layer to remove
public virtual void RemoveLayer(string name)
{
layerList.Remove(name);
}
///
/// Removes the layer at the specified index
///
///
public virtual void RemoveLayerAt(int index)
{
layerList.RemoveAt(index);
}
///
/// Removes all layers
///
public virtual void RemoveAllLayers()
{
layerList.Clear();
}
private Collection stylesList;
///
/// Gets the list of enabled styles
///
public virtual Collection StylesList
{
get { return stylesList; }
}
///
/// Adds a style to the style collection
///
/// Name of style
/// Throws an exception is an unknown layer is added
public virtual void AddStyle(string name)
{
if (!StyleExists(wmsClient.Layer, name))
throw new ArgumentException("Cannot add WMS Layer - Unknown layername");
stylesList.Add(name);
}
///
/// Recursive method for checking whether a layername exists
///
/// layer
/// name of style
/// True of style exists
private bool StyleExists(SharpMap.Web.Wms.Client.WmsServerLayer layer, string name)
{
foreach(SharpMap.Web.Wms.Client.WmsLayerStyle style in layer.Style)
if (name == style.Name) return true;
foreach (SharpMap.Web.Wms.Client.WmsServerLayer childlayer in layer.ChildLayers)
if (StyleExists(childlayer, name)) return true;
return false;
}
///
/// Removes a style from the collection
///
/// Name of style
public virtual void RemoveStyle(string name)
{
stylesList.Remove(name);
}
///
/// Removes a style at specified index
///
/// Index
public virtual void RemoveStyleAt(int index)
{
stylesList.RemoveAt(index);
}
///
/// Removes all styles from the list
///
public virtual void RemoveAllStyles()
{
stylesList.Clear();
}
///
/// Sets the image type to use when requesting images from the WMS server
///
///
/// See the property for a list of available mime types supported by the WMS server
///
/// Throws an exception if either the mime type isn't offered by the WMS
/// or GDI+ doesn't support this mime type.
/// Mime type of image format
public virtual void SetImageFormat(string mimeType)
{
if (!OutputFormats.Contains(mimeType))
throw new ArgumentException("WMS service doesn't not offer mimetype '" + mimeType + "'");
//Check whether SharpMap supports the specified mimetype
bool formatSupported = false;
foreach (System.Drawing.Imaging.ImageCodecInfo encoder in System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders())
if (encoder.MimeType.ToLower() == mimeType.ToLower())
{
formatSupported = true;
break;
}
if (!formatSupported)
throw new ArgumentException("GDI+ doesn't not support mimetype '" + mimeType + "'");
this.mimeType = mimeType;
}
///
/// Gets the hiarchial list of available WMS layers from this service
///
public virtual SharpMap.Web.Wms.Client.WmsServerLayer RootLayer
{
get { return wmsClient.Layer; }
}
///
/// Gets the list of available formats
///
public virtual Collection OutputFormats
{
get { return wmsClient.GetMapOutputFormats; }
}
private string spatialReferenceSystem;
///
/// Gets or sets the spatial reference used for the WMS server request
///
public virtual string SpatialReferenceSystem
{
get { return spatialReferenceSystem; }
set { spatialReferenceSystem = value; }
}
///
/// Gets the service description from this server
///
public virtual SharpMap.Web.Wms.Capabilities.WmsServiceDescription ServiceDescription
{
get { return wmsClient.ServiceDescription; }
}
///
/// Gets the WMS Server version of this service
///
public virtual string Version
{
get { return wmsClient.WmsVersion; }
}
private ImageAttributes imageAttributes;
///
/// When specified, applies image attributes at image (fx. make WMS layer semi-transparent)
///
///
/// You can make the WMS layer semi-transparent by settings a up a ColorMatrix,
/// or scale/translate the colors in any other way you like.
///
/// Setting the WMS layer to be semi-transparent.
///
/// float[][] colorMatrixElements = {
/// new float[] {1, 0, 0, 0, 0},
/// new float[] {0, 1, 0, 0, 0},
/// new float[] {0, 0, 1, 0, 0},
/// new float[] {0, 0, 0, 0.5, 0},
/// new float[] {0, 0, 0, 0, 1}};
/// ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
/// ImageAttributes imageAttributes = new ImageAttributes();
/// imageAttributes.SetColorMatrix(
/// colorMatrix,
/// ColorMatrixFlag.Default,
/// ColorAdjustType.Bitmap);
/// myWmsLayer.ImageAttributes = imageAttributes;
///
///
///
public virtual ImageAttributes ImageAttributes
{
get { return imageAttributes; }
set { imageAttributes = value; }
}
#region ILayer Members
///
/// Renders the layer
///
/// Graphics object reference
/// Map which is rendered
public override void OnRender(System.Drawing.Graphics g, IMap map)
{
SharpMap.Web.Wms.Client.WmsOnlineResource resource = GetPreferredMethod();
Uri myUri = new Uri(GetRequestUrl(map.Envelope, map.Size));
System.Net.WebRequest myWebRequest = System.Net.WebRequest.Create(myUri);
myWebRequest.Method = resource.Type;
myWebRequest.Timeout = timeOut;
if (credentials != null)
myWebRequest.Credentials = credentials;
else
myWebRequest.Credentials = System.Net.CredentialCache.DefaultCredentials;
if (proxy != null)
myWebRequest.Proxy = proxy;
try
{
System.Net.HttpWebResponse myWebResponse = (System.Net.HttpWebResponse)myWebRequest.GetResponse();
System.IO.Stream dataStream = myWebResponse.GetResponseStream();
if (myWebResponse.ContentType.StartsWith("image"))
{
System.Drawing.Image img = System.Drawing.Image.FromStream(myWebResponse.GetResponseStream());
if (imageAttributes != null)
g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height), 0, 0,
img.Width, img.Height, GraphicsUnit.Pixel, this.ImageAttributes);
else
g.DrawImageUnscaled(img, 0, 0,map.Size.Width,map.Size.Height);
}
dataStream.Close();
myWebResponse.Close();
}
catch (System.Net.WebException webEx)
{
if (!continueOnError)
throw (new SharpMap.Rendering.Exceptions.RenderException("There was a problem connecting to the WMS server when rendering layer '" + this.Name + "'", webEx));
else
//Write out a trace warning instead of throwing an error to help debugging WMS problems
System.Diagnostics.Trace.Write("There was a problem connecting to the WMS server when rendering layer '" + this.Name + "': " + webEx.Message);
}
catch (System.Exception ex)
{
if (!continueOnError)
throw (new SharpMap.Rendering.Exceptions.RenderException("There was a problem rendering layer '" + this.Name + "'", ex));
else
//Write out a trace warning instead of throwing an error to help debugging WMS problems
System.Diagnostics.Trace.Write("There was a problem connecting to the WMS server when rendering layer '" + this.Name + "': " + ex.Message);
}
}
///
/// Gets the URL for a map request base on current settings, the image size and boundingbox
///
/// Area the WMS request should cover
/// Size of image
/// URL for WMS request
public virtual string GetRequestUrl(IEnvelope box, System.Drawing.Size size)
{
SharpMap.Web.Wms.Client.WmsOnlineResource resource = GetPreferredMethod();
System.Text.StringBuilder strReq = new StringBuilder(resource.OnlineResource);
if(!resource.OnlineResource.Contains("?"))
strReq.Append("?");
if (!strReq.ToString().EndsWith("&") && !strReq.ToString().EndsWith("?"))
strReq.Append("&");
strReq.AppendFormat(SharpMap.Map.numberFormat_EnUS, "REQUEST=GetMap&BBOX={0},{1},{2},{3}",
box.MinX, box.MinY, box.MaxX, box.MaxY);
strReq.AppendFormat("&WIDTH={0}&Height={1}", size.Width, size.Height);
strReq.Append("&Layers=");
if (layerList != null && layerList.Count > 0)
{
foreach (string layer in layerList)
strReq.AppendFormat("{0},", layer);
strReq.Remove(strReq.Length - 1, 1);
}
strReq.AppendFormat("&FORMAT={0}", mimeType);
if (spatialReferenceSystem == string.Empty)
throw new ApplicationException("Spatial reference system not set");
if(wmsClient.WmsVersion=="1.3.0")
strReq.AppendFormat("&CRS={0}", spatialReferenceSystem);
else
strReq.AppendFormat("&SRS={0}", spatialReferenceSystem);
strReq.AppendFormat("&VERSION={0}", wmsClient.WmsVersion);
strReq.Append("&Styles=");
if (stylesList != null && stylesList.Count > 0)
{
foreach (string style in stylesList)
strReq.AppendFormat("{0},", style);
strReq.Remove(strReq.Length - 1, 1);
}
return strReq.ToString();
}
///
/// Returns the extent of the layer
///
/// Bounding box corresponding to the extent of the features in the layer
public override IEnvelope Envelope
{
get
{
return RootLayer.LatLonBoundingBox;
}
}
private Boolean continueOnError;
///
/// Specifies whether to throw an exception if the Wms request failed, or to just skip rendering the layer
///
public virtual Boolean ContinueOnError
{
get { return continueOnError; }
set { continueOnError = value; }
}
///
/// Returns the type of the layer
///
//public override SharpMap.Layers.Layertype LayerType
//{
// get { return SharpMap.Layers.Layertype.Wms; }
//}
#endregion
private SharpMap.Web.Wms.Client.WmsOnlineResource GetPreferredMethod()
{
//We prefer posting. Seek for supported post method
for (int i = 0; i < wmsClient.GetMapRequests.Length; i++)
if (wmsClient.GetMapRequests[i].Type.ToLower() == "post")
{
return wmsClient.GetMapRequests[i];
}
//Next we prefer the 'get' method
for (int i = 0; i < wmsClient.GetMapRequests.Length; i++)
{
if (wmsClient.GetMapRequests[i].Type.ToLower() == "get")
{
return wmsClient.GetMapRequests[i];
}
}
return wmsClient.GetMapRequests[0];
}
private System.Net.ICredentials credentials;
///
/// Provides the base authentication interface for retrieving credentials for Web client authentication.
///
public virtual System.Net.ICredentials Credentials
{
get { return credentials; }
set { credentials = value; }
}
private System.Net.WebProxy proxy;
///
/// Gets or sets the proxy used for requesting a webresource
///
public virtual System.Net.WebProxy Proxy
{
get { return proxy; }
set { proxy = value; }
}
private int timeOut;
///
/// Timeout of webrequest in milliseconds. Defaults to 10 seconds
///
public virtual int TimeOut
{
get { return timeOut; }
set { timeOut = value; }
}
#region ICloneable Members
///
/// Clones the object
///
///
public override object Clone()
{
return new WmsLayer(Name, Url);
}
#endregion
public virtual string LayerTitle
{
get { return RootLayer.Name; }
}
}
}