Introduction
There are many projects where interactive maps are needed to display information. Integration with maps, for example, Google maps, can be used in some cases. However, when it comes to displaying a large amount of information from various map layers – which in turn can physically be based on various data sources – the best solution is to use your own map server.
This article will present an example of a WEB application with interactive maps, and includes development of WMS server and features for map display on the client side. MapAround will be used as a GIS engine to implement WMS. This OpenSource library is written in C#. It is provided under GPLv3 license (can also be obtained through a commercial license) and incorporates all the features necessary to develop own GIS applications. Map display will be based on JavaScript library Leaflet. This library is also provided by OpenSource under its own license similar to GPL and includes very extensive features – supports a set of data access standards, such as WMS, WMTS, and others. This library also has an expanded architecture and works efficiently across all modern browsers.
Using the code
MapAround-based WMS
What is WMS? WMS (Web Map Server) is a communication protocol for data transmission between the server providing map data and a client using these data for display (and for other operations, see the protocol specification). The protocol itself has a very simple set of commands. They are only three types of commands - receiving information about the server, receiving the image of a given map area, and receiving information about objects on the map. We need not go into much detail since MapAround already has this protocol implemented. It is only left to solve how to "raise" our WMS. Using APS.NET, or to be exact, ASP WebHandler, would be the easiest solution.
Loading map status
The first step in processing a WMS request is to initialize the map status, i.e., the list of layers, map coloring styles, and conformity of the layers with data sources. Of course, all these operations can be encoded directly into the body of the handler. However, MapAround has a special tool called MapWorkspace, which is used to save and load map status. It would be unreasonable not to use this tool. Workspace in MapAround is an ordinary XML document that contains descriptions of map layers. The design style of each layer and the data source assigned to the layer are described. By the way, MapAround has no rigid binding to map data sources. Moreover, layers can be loaded both from file formats and from DBMSs. Unfortunately, the free version of the library has no supporting tools to create and edit Workspace. Despite this, however, creating this document will not be a difficult process. An example of Workspace can be found in a demo project
Thus, reading the Workspace will be the first step in processing a request.
public void ProcessRequest(HttpContext context)
{
httpContext = context;
var WorkspaceFilePath = System.IO.Path.Combine(httpContext.Server.MapPath("~"), "workspace.xml");
MapFolder = System.IO.Path.Combine(httpContext.Server.MapPath("~"), "..", "..", "data");
....
mapWs = new MapWorkspace();
mapWs.XmlRepresentation = File.ReadAllText(WorkspaceFilePath);
mapWs.Map.RenderingSettings.AntiAliasGeometry = true;
mapWs.Map.RenderingSettings.AntiAliasText = true;
Binding layers to data sources
Inside the Workspace, each layer must be assigned to the appropriate data source with parameters for accessing it. However, the MapAround itself knows nothing about them, so one needs to make the final binding manually.
foreach (LayerBase l in mapWs.Map.Layers)
{
FeatureLayer fl = l as FeatureLayer;
if (fl != null)
{
fl.DataSourceNeeded += LayerDataSourceNeeded;
fl.DataSourceReadyToRelease += LayerDataSourceReadyToRelease;
if (!fl.AreFeaturesAutoLoadable)
fl.LoadFeatures();
}
}
Data source request and release methods will be called for the layer whenever the need to render the layer arises.
private void LayerDataSourceNeeded(object sender, MapAround.Mapping.FeatureDataSourceEventArgs e)
{
FeatureLayer l = sender as FeatureLayer;
string featuresFilePath = string.Empty;
switch (l.DataProviderRegName)
{
case "MapAround.DataProviders.ShapeFileSpatialDataProvider":
l.AreFeaturesAutoLoadable = true;
ShapeFileSpatialDataProvider shapeP = new ShapeFileSpatialDataProvider();
shapeP.AttributesEncoding = Encoding.UTF8;
shapeP.FileName = GetFeaturesFilePath(l.DataProviderParameters["file_name"]);
shapeP.ProcessAttributes = true;
e.Provider = shapeP;
break;
default:
throw new Exception("Map data provider not found: \"" + l.DataProviderRegName + "\"");
}
}
private void LayerDataSourceReadyToRelease(object sender, MapAround.Mapping.FeatureDataSourceEventArgs e)
{
FeatureLayer l = sender as FeatureLayer;
switch (l.DataProviderRegName)
{
case "MapAround.DataProviders.ShapeFileSpatialDataProvider":
break;
default:
throw new Exception("Map data provider not found: \"" + l.DataProviderRegName + "\"");
}
}
Processing WMS request
The next step is to directly process WMS request. For this purpose, MapAround has the WMSServer class, which implements all the necessary actions.
IMapServer server = new WMSServer(new WmsServiceDescription("MapAround Demo", ""));
server.ImageQuality = 95;
server.Map = mapWs.Map;
server.GutterSize = 180;
server.BeforeRenderNewImage += server_BeforeRenderNewImage;
WMSServer can also be configured to use data caching mechanism. When rendering large maps, this can increase performance considerably.
FileTileCacheAccessor tileCacheAccessor =
new FileTileCacheAccessor(Path.Combine(HttpContext.Current.Server.MapPath("~"), "map\\cache"));
tileCacheAccessor.Prefix = TileCacheKeyPrefix;
server.TileCacheAccessor = tileCacheAccessor;
The BeforeRenderNewImage event is called each time whenever WMSServer starts rendering a map on request. When it is processed, we should recalculate the scale factor of the map (the number of pixels per unit of the map) and initiate loading of layer data.
private void server_BeforeRenderNewImage(object sender, RenderNewImageEventArgs e)
{
BoundingRectangle bbox = QueryStringDataExtractor.GetBBox(httpContext.Request.QueryString["BBOX"]);
Size displaySize = QueryStringDataExtractor.GetDisplaySize(httpContext.Request.QueryString["WIDTH"], httpContext.Request.QueryString["HEIGHT"]);
double mapScale = displaySize.Width / bbox.Width;
mapWs.Map.LoadFeatures(mapScale, mapWs.Map.MapViewBoxFromPresentationViewBox(e.BboxWithGutters));
mapWs.Map.LoadRasters(mapScale, mapWs.Map.MapViewBoxFromPresentationViewBox(e.BboxWithGutters));
}
The scale factor is used to determine the list of layers that need to be rendered for the current map zoom level. This parameter is specified for each layer separately.
Next, we need to record the output of WMSServer in the handler stream.
string mime = string.Empty;
context.Response.Clear();
server.GetResponse(context.Request.Params, context.Response.OutputStream, out mime);
context.Response.ContentType = mime;
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetMaxAge(new TimeSpan(24, 0, 0));
context.Response.End();
Leaflet-based client
After the server side – for formation of maps – has been completed, we must implement a foreground for map display on the user side. JavaScript library Leaflet, already possessing all the necessary features, will be used for this purpose. Thus, all actions aimed at creating a page for display come down to only connecting and setting up the library.
<div id="map2" class="leaflet-container leaflet-fade-anim" tabindex="0">
</div>
<script type="text/javascript">
var nexrad = new L.TileLayer.WMS("/map/WmsHandler.ashx", {
layers: "layer1,layer2,layer3", format: 'image/png',
transparent: true
});
var map2 = new L.Map('map2', {
center: new L.LatLng(56.30,44), zoom: 5, layers: [ nexrad], maxZoom: 28,
minZoom: 0,
zoomControl: true
});
</script>
As you can see from the code above, is a very simple task to connect the library.
That's it! Our application is ready!
History
- 18th September, 2013 - initial article version