Displaying WMS Legends in Silverlight with a WCF Service

This may be re-inventing the wheel big time, but whilst working on an ArcGIS Silverlight app making heavy use of WMS Layers, I could not, for the life of me, find a way  to display the WMS legend in the ‘standard’ legend control. Now, there may be a version of API I missed, or plain dump-ness but I came up with my own solution, using the ‘raw’ GetLegendGraphic request. If not anything else, this post would at least show how to retrieve dynamic images at run time in Silverlight via WCF.

First thing we need to do is to create the WCF Service (called WMSService in this example). In the IWMSService.cs file add the LegendGraphic Operation Contract

IWMSService.cs
  1. [ServiceContract]
  2.     public interface IWMSService
  3.     {
  4.         [OperationContract]
  5.         Stream GetLegendGraphic(string url);
  6.     }

And in the WMSService.svc.cs the following code:

WMSService.svc.cs
  1. [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
  2.     public class WMSService : IWMSService
  3.     {
  4.        public Stream GetLegendGraphic(string url)
  5.         {
  6.             Stream str = null;
  7.             HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
  8.             HttpWebResponse response = request.GetResponse() as HttpWebResponse;
  9.             str = response.GetResponseStream();
  10.             return str;
  11.         }
  12.     }

Add the service reference to your Silverlight project and add the following function. This will create a StackPanel including the sub-layer name, a slider to change the layer’s opacity and the layer’s legend image . You can then add the StackPanel control onto your application as required.

Create WMS Legend Control
  1. private StackPanel createWMSLayerControl(WmsLayer wmsLyr, string subLayer)
  2.         {
  3.             StackPanel spV = new StackPanel();
  4.             StackPanel spH= new StackPanel();
  5.             spH.Orientation = Orientation.Horizontal;
  6.             TextBlock tb = new TextBlock();
  7.             tb.Padding = new Thickness(5);
  8.             tb.FontWeight = FontWeights.Bold;
  9.             tb.Text = wmsLyr.ID;
  10.             Slider sl = new Slider()
  11.             {
  12.                 Maximum = 1
  13.                ,Width=50
  14.             };
  15.             string[] subLyrs= new string[1];
  16.             subLyrs[0]=subLayer;
  17.             wmsLyr.Layers = subLyrs;
  18.             sl.Tag = wmsLyr;
  19.             sl.Value = wmsLyr.Opacity;
  20.             sl.ValueChanged+=new RoutedPropertyChangedEventHandler<double>(sl_ValueChanged);
  21.             spH.Children.Add(tb);
  22.             spH.Children.Add(sl);
  23.             spV.Children.Add(spH);
  24.             //Get Legend Graphic
  25.             WMSServiceClient service = new WMSServiceClient();
  26.             service.GetLegendGraphicCompleted += new EventHandler<GetLegendGraphicCompletedEventArgs>(service_GetLegendGraphicCompleted);
  27.             TextBlock tbl = new TextBlock();
  28.             tbl.Padding = new Thickness(5);
  29.             tbl.FontStyle = FontStyles.Italic;
  30.             tbl.Text = subLayer;
  31.             spV.Children.Add(tbl);
  32.             Image imgb = new Image();
  33.             spV.Children.Add(imgb);
  34.             string strGetLegend = wmsLyr.Url + “?REQUEST=GetLegendGraphic&VERSION=” + wmsLyr.Version + “&FORMAT=image/png&WIDTH=16&HEIGHT=16&LAYER=” + subLayer;
  35.             service.GetLegendGraphicAsync(strGetLegend, imgb);
  36.             return spV;
  37.         }

Note that the GetLegendGraphicAsync  includes a second argument (userState) as the Image control. The GetLegendGraphicCompleted is where the Image Source is set (as the result of the GetLegendGraphic request).

Set the Legend Graphic
  1. void service_GetLegendGraphicCompleted(object sender, GetLegendGraphicCompletedEventArgs e)
  2.         {
  3.             int lengthInBytes = Convert.ToInt32(e.Result.Length);
  4.             byte[] imageData = e.Result;
  5.             byte[] buffer = new byte[lengthInBytes];
  6.             System.IO.MemoryStream stream = new System.IO.MemoryStream(imageData, 0, imageData.Length, false, true);
  7.             System.Windows.Media.Imaging.BitmapImage bmp = new System.Windows.Media.Imaging.BitmapImage();
  8.             bmp.SetSource(stream);
  9.             Image imgb = (Image)e.UserState;
  10.             imgb.Source = bmp;
  11.         }

Finally, the sl_ValueChange will simply allow the user to change the layer’s opacity by dragging the slider.

Change layer’s opacity
  1. void sl_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
  2.         {
  3.             Slider sl = (Slider)sender;
  4.             WmsLayer lyr = (WmsLayer)sl.Tag;
  5.             lyr.Opacity = sl.Value;
  6.         }

As an example, assuming your WMS Layer is set as:

WMS Layer Example
  1. <esri:WmsLayer ID=”World Mineral Deposits”
  2.                          Url=”http://132.156.97.59/cgi-bin/worldmin_en-ca_ows&#8221;
  3.                          SkipGetCapabilities=”True” Layers=”GSC:WORLD_AgeRockDomain,GSC:WORLD_AgeDomains”
  4.                          Version=”1.1.0″ Visible=”True”
  5.                          Opacity=”0.7″ />

The legend for the first sub-layer (GSC:WORLD_AgeRockDomain) would look something like this:

wmsLegend

And this is basically it. As mentioned in the beginning of this post- this may be a complete overkill, so if you know how to do this the ‘ArcGIS way’ please do tell!!!