Integrating SharpMap with the Esri ArcGIS API for Silverlight
Note: The project described in this post also makes use of ArcGIS Server. If you intend to use the Esri ArcGIS API for Silverlight/WPF without ArcGIS Server, you should review the license agreement.
If you have spent much time with the Esri ArcGIS API for Silverlight, you know it provides a lot of capabilities and offers a flexible API for adding data to a map. Esri has provided some simple out of the box solutions for putting data on a map, such as ArcGISDynamicLayer and GraphicsLayer. These are simple to use and provide an easy way to visualize data on a map. However these may not always meet your needs, and you may need to extend the Esri Silverlight API.
The project I am currently working on required a query builder interface for allowing users to create on-the-fly relational and spatial queries for data stored in SQL Server 2008. The users wanted to be able to generate queries from available fields, join to other tables (lookups), filter by spatial criteria, etc. They did not want to have to query multiple data sources, or use different mechanisms to display results as a table or on a map. We could have used other methods to incorporate ArcGIS Server, such as a Server Object Extension, but we were looking for a lightweight solution that would allow us to integrate the mapping piece quickly and easily into our existing solution.
I was looking for other options and my co-worker, Bill Dollins, mentioned SharpMap to me. I was intrigued, but unsure of how it would integrate into our application and how well it would perform. I must say I was surprised at how well it has performed. Since the documentation is limited, I did have to stumble my way through certain things, but for the most part, SharpMap tied in nicely and did not require additional servers or complex configurations. We were able to run our queries, and pass the results to either a tabular view or through SharpMap for rendering just by adding references to the SharpMap projects, and adding a few supporting functions.
Our solution was to use SharpMap to render an image server side and send a bytestream back to the Silverlight client which then renders an image from the bytestream. We use SQL functions to limit the results to 2000 records per request. This improves performance and rendering more than 2000 lines on an image would not be easy to interpret visually. As the user zooms in, the data is requeried, and the image redrawn. In addition to providing a basic map display, we provide a query interface that allows the user to perform spatial or relational queries that can be viewed as a data table, or on the map.
We decided to derive from DynamicLayer instead of using a GraphicsLayer since we have over 90,000 lines that need to be drawn, and it would have been impractical to stream the geometries for 90,000 objects to the client.
Integrating SharpMap into the Esri Silverlight API requires creating a webservice on the backend that generates an image using SharpMap and data from SQL 2008, and a Silverlight class in the UI that derives from ESRI.ArcGIS.Client.DynamicLayer.

I downloaded the data for this sample from the GeoCommons website, then used Shape2Sql to import the data into SQL Server 2008. I also ran MakeValid against the dataset after it was in SQL. I highly recommend creating a spatial index on the SQL table to improve query performance.
You will also need to download the Esri Silverlight API and the SharpMap libraries. In this application, the work with SharpMap is being done on the server so it is not necessary to attempt to compile SharpMap against the Silverlight runtime.
For this sample application, I created a Silverlight Application in Visual Studio with a new ASP.NET Web Application Project as the Host for the application.
In the new web project, I added a new Windows Communication Foundation (WCF) Service called SharpMapService.
Updating the generated code my interface now looks like this:
using System.Drawing;
using System.ServiceModel;
namespace SharpMapSQL2008.Web
{
[ServiceContract]
public interface ISharpMapService
{
[OperationContract]
byte[] GetImage(Size imageSize, ESRI.ArcGIS.Client.Geometry.Envelope boundingBox);
}
}
I updated the web service code to look like this:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using SharpMap;
using SharpMap.Data.Providers;
using SharpMap.Geometries;
using SharpMap.Layers;
namespace SharpMapSQL2008.Web
{
public class SharpMapService : ISharpMapService
{
// Variables needed for provider.
private static readonly string mTableName = "WANationalForests";
private static readonly string mConnectionString = "Data Source=localhost\SQLEXPRESS;Initial Catalog=Map;Integrated Security=true";
private static readonly string mIDColumn = "ID";
private static readonly string mGeometryColumn = "geom";
private static readonly string mLabelColumn = "NAME";
private static readonly int mSrid = 4326;
public byte[] GetImage(Size imageSize, ESRI.ArcGIS.Client.Geometry.Envelope boundingBox)
{
SqlServer2008 provider;
MemoryStream memoryStream;
Image mapImage;
Map map;
VectorLayer vLayer;
LabelLayer labelLayer;
byte[] myBytes;
provider = new SqlServer2008(mConnectionString, mTableName, mGeometryColumn, mIDColumn);
provider.SRID = mSrid;
map = new Map(imageSize);
// Add vector data layer
vLayer = new VectorLayer(mTableName, provider);
vLayer.Style.EnableOutline = true;
vLayer.Style.Outline = new Pen(new SolidBrush(Color.Black));
vLayer.Style.Fill = new SolidBrush(Color.FromArgb(128, 0, 0, 255)); // blue
map.Layers.Add(vLayer);
// Add label layer
labelLayer = new LabelLayer("labels");
labelLayer.Enabled = true;
labelLayer.DataSource = provider;
labelLayer.LabelColumn = mLabelColumn;
labelLayer.Style.Font = new Font(FontFamily.GenericSerif, 11);
labelLayer.MaxVisible = 5; // Map zoom is calculated by taking MaxX - MinX from bounding box
labelLayer.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center;
labelLayer.Style.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Middle;
labelLayer.Style.Offset = new PointF(3, 3);
labelLayer.Style.Halo = new Pen(Color.White, 2);
labelLayer.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
labelLayer.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
labelLayer.SRID = 4326;
map.Layers.Add(labelLayer);
//Create sharpmap BoundingBox from ESRI Envelope input parameter
map.ZoomToBox(new BoundingBox(boundingBox.XMin, boundingBox.YMin, boundingBox.XMax, boundingBox.YMax));
mapImage = map.GetMap(); // generates image
memoryStream = new MemoryStream();
mapImage.Save(memoryStream, ImageFormat.Png); //saves image to memorystream
myBytes = memoryStream.ToArray();
// Cleanup
map.Dispose();
mapImage.Dispose();
memoryStream.Dispose();
return myBytes; //returns stream bytearray
}
}
}
The GetImage function takes two arguments. One is the size of the image to be drawn, and the other is the extent of the image you want drawn. When we create the Silverlight layer, you will be able to see how these are wired up. The first two lines in the GetImage function set up the provider that SharpMap will use for retrieving the data for the VectorLayer. A few lines are needed to define the VectorLayer rendering Style and a few more are needed for rendering the LabelLayer.
The MaxVisible property on the labels layer is a bit tricky to figure out. MaxVisible refers to the max zoom level at which the layer will be displayed. Zoom is determined by taking the boundingBox MaxX and subtracting the MinX value.
Something to keep in mind when working with these tools: You need to make sure that all the datasets have matching spatial references (in this case WGS84) or they will need to be reprojected to match the projection of your basemap. Another thing to keep in mind with SQL Server queries is that you must provide the matching spatial reference ID for your query geometry. Even if you use the same coordinate system as the data in your table, without the spatial reference id, you will not get anything back. Thus it is important to set the SRID property on the SQLServer2008 “provider” variable .
That’s really about it for the server side of things. On the client we have a couple of things to do. First we need to Add a Service Reference to the Silverlight Project that points to our SharpMapService. Visual Studio will create the WCF client, and update the ServiceReferences.ClientConfig file for you.
Next I created a layer that derives from ESRI.ArcGIS.Client.DynamicLayer. This is an abstract class that requires you to implement a GetSource function. This function will get called by the Esri Silverlight API when the user pans/zooms the map. This is where the call to the web service is made.
using System;
using System.IO;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using SharpMapSQL2008.SharpMap;
namespace SharpMapSQL2008
{
public class SharpMapLayer : ESRI.ArcGIS.Client.DynamicLayer
{
private SharpMap.SharpMapServiceClient mClient;
public override void Initialize()
{
if(mClient == null)
{
mClient = new SharpMap.SharpMapServiceClient();
}
base.Initialize();
}
protected override void GetSource(ESRI.ArcGIS.Client.Geometry.Envelope extent, int width, int height, ESRI.ArcGIS.Client.DynamicLayer.OnImageComplete onComplete)
{
MemoryStream stream;
EventHandler<GetImageCompletedEventArgs> handler;
Size imgSize; // Note this type is from the generated service reference code, and not System.Windows.Size
imgSize = new Size();
imgSize.width = width;
imgSize.height = height;
handler = null;
//I am using an inline handler so I have a reference to the image, width, height and extent variables
handler = delegate(object sender, GetImageCompletedEventArgs e)
{
BitmapImage bitmapImage;
if(e.Error == null)
{
bitmapImage = new BitmapImage();
stream = new MemoryStream(e.Result);
bitmapImage.SetSource(stream);
}
else
{
bitmapImage = null;
}
mClient.GetImageCompleted -= handler; //Unsubscribe handler
onComplete(bitmapImage, width, height, extent);
};
mClient.GetImageCompleted += handler; //Subscribe handler
mClient.GetImageAsync(imgSize, extent);
}
}
}
This is fairly straightforward. A call to the web service is made and the resultant byte array is assigned to a MemoryStream. From the MemoryStream, a BitmapImage is created. This image is passed back to the Esri Silverlight API via the call to the onComplete function. The API will then render the image on the map.
The last thing we need to do is update the XAML file to add our SharpMapLayer to the map. This requires two lines of code.
<UserControl x:Class="SharpMapSQL2008.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:esri="http://schemas.esri.com/arcgis/client/2009"
xmlns:sm="clr-namespace:SharpMapSQL2008" <!-- reference to our namespace -->
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<esri:Map x:Name="myMap">
<esri:Map.Extent>
<esri:Envelope XMin="-125" YMin="45" XMax="-117" YMax="49">
<esri:Envelope.SpatialReference>
<esri:SpatialReference WKID="4326"/>
</esri:Envelope.SpatialReference>
</esri:Envelope>
</esri:Map.Extent>
<esri:ArcGISTiledMapServiceLayer Url="http://services.arcgisonline.com/arcgis/rest/services/ESRI_StreetMap_World_2D/MapServer" />
<sm:SharpMapLayer /> <!-- Add our SharpMapLayer to the map -->
</esri:Map>
</Grid>
</UserControl>
As you can see it is not very difficult to implement a lightweight solution to getting data on a map, and does not require extensive coding. In addition, the performance of SharpMap for rendering images is very good.
I would like to says thanks to Morten Nielsen for all his contributions to the GIS developer community. The samples here use 3 technologies Morten has worked on: SharpMap, Shape2SQL and the Esri ArcGIS API for Silverlight.
This post was written by:
Sky
Senior Associate
For more information on this post, the technologies discussed here, or Zekiah’s geospatial technology services, please e-mail us at contact@zekiah.com