Leaflet vs. OpenLayers - Round 2
When it comes to building a web-based GIS application, some teams prefer Blazor-based maps, while other prefer to keep the client side in 100% Javascript. We will have future posts on Blazor, but today we will be focusing on Javascript-based mapping frameworks.
In the Javascript mapping world, there are two major players - OpenLayers and Leaflet. Years back, we had a very popular blog post that helped our customers choose the right client-side Javascript library for your web-based mapping application. In today’s blog post, we will dig into examples of using both libraries with ThinkGeo WebAPI. We’ll also look at some Leaflet and OpenLayers code and show where they differ. And because your server-side WebAPI code is unchanged regardless of the client-side library, if you ever decide to move from Leaflet to Openlayers in the future, your upgrade will only need to address your client-side javascript.
In today’s post, we’ll be viewing some of the high points the ‘Basic Styling’ sample from our WebApi ‘HowDoI’ samples. If you want to see the full code and other samples, you can clone the samples from our Gitlab repo. Or, if you prefer to set up your own project from scratch, you also start with the QuickStart guide.
Code Sample
Step 1 - Add your Map to index.html. Whether you’re using Leaflet or OpenLayers, the first step is to add a <div> with an id value of ‘map’. Later, our Leaflet and OpenLayers code will reference this div by the id. You’ll also want to add a reference to thinkgeo.js and thinkgeo.leaflet.js or thinkgeo.openlayers.js depending on the type of project.
<div id="map"></div>
<script src="Scripts/thinkgeo.js"></script>
<script src="Scripts/thinkgeo.leaflet.js"></script>
// or
<script src="Scripts/thinkgeo.openlayers.js"></script>
Step 2 - Initialize your leaflet or openlayers map. In our samples, you can find this code in the thinkgeo.js file in both the OpenLayers and Leaflet projects. As we discussed in the original blog post, you’ll notice that the Leaflet code is more concise, but does lack some of the customizations allowed by OpenLayers.
The Leaflet code to initialize our map:
// Initialize the map.
var map = L.map('map').setView([40.14711, -95.81131], 4);
The openlayers code to initialize our map:
// Initialize the map.
var map = new ol.Map({
target: 'map',
renderer: 'webgl',
loadTilesWhileAnimating: true,
loadTilesWhileInteracting: true,
controls: ol.control.defaults({ attribution: false }).extend(
[new ol.control.Attribution({
collapsible: false
})]),
view: new ol.View({
center: ol.proj.transform([-95.81131, 40.14711], 'EPSG:4326', 'EPSG:3857'),
zoom: 4
})
});
Step 3 - Interactions between the javascript map and the WebAPI Controller. In the sample, our controller code is all in the BasicStylingController.cs. This same controller is used on both the OpenLayers and Leaflet samples.
The /BasicStyling/{styleid} route is then used to render our server-side layers on the maps. The Leaflet code to initialize our default-style layer is:
// Add dynamic tile layer to the map, it can be refreshed dynamically
var shapeLayer = L.dynamicLayer(L.Util.getRootPath() + '/BasicStyling/PredefinedStyles/{z}/{x}/{y}/' + accessId, { unloadInvisibleTiles: true, reuseTiles: false }).addTo(map);
And in OpenLayers, the code looks like:
// Add dynamic tile layer to the map, it can be refreshed dynamically
var xyzSource = new ol.source.XYZ({
url: getRootPath() + '/BasicStyling/PredefinedStyles/{z}/{x}/{y}/' + accessId,
maxZoom: 19,
attributions: [new ol.Attribution({
html: 'ThinkGeo | © OpenStreetMap contributors ODbL'
})]
});
Step 4 - The WebAPI Controller(BasicStylingController.cs) responds to all client-side requests to the /BasicStyling/* endpoints. This controller then oversses the building and styling of all the layers shown on the Leaflet or OpenLayers map.
The snippet below shows the implementation for the /BasicStyling/{styleid} route:
[Route("{styleId}/{z}/{x}/{y}/{accessId}")]
[HttpGet]
public IActionResult GetDynamicLayerTile(string styleId, int z, int x, int y, string accessId)
{
// Create the LayerOverlay for displaying the map with different styles.
LayerOverlay layerOverlay = GetStyleOverlay(styleId, accessId);
return DrawTileImage(layerOverlay, z, x, y);
}
In the code above, you’ll notice the familiar LayerOverlay that’s also used in our Desktop and Mobile products. Then, the DrawTileImage() function is called that uses the GeoCanvas and WebApiExtentHelper classes to generate a tile to return to the client:
/// Draw the map and return the image back to client in an IActionResult.
private IActionResult DrawTileImage(LayerOverlay layerOverlay, int z, int x, int y)
{
using (GeoImage image = new GeoImage(256, 256))
{
GeoCanvas geoCanvas = GeoCanvas.CreateDefaultGeoCanvas();
RectangleShape boundingBox = WebApiExtentHelper.GetBoundingBoxForXyz(x, y, z, GeographyUnit.Meter);
geoCanvas.BeginDrawing(image, boundingBox, GeographyUnit.Meter);
layerOverlay.Draw(geoCanvas);
geoCanvas.EndDrawing();
byte[] imageBytes = image.GetImageBytes(GeoImageFormat.Png);
return File(imageBytes, "image/png");
}
}
Finally, the other notable class in the server-side codebase is the LayerBuilder.cs. This class uses all the familiar ThinkGeo styling classes like ShapeFileFeatureLayer, PointStyle and LineStyle to build and style all the map tiles that are returned back to the client. An example of creating a triangle PointStyle on the ThinkGeo headquarters is below:
///
/// Gets layer for exhibition symbol point style by style id and access id.
///
/// style id
/// access id
///
internal static Layer GetSymbolPointLayer(string styleId, string accessId)
{
ShapeFileFeatureLayer symblePointLayer = new ShapeFileFeatureLayer(Path.Combine(baseDirectory, "ThinkGeoLocation.shp"));
var colors = GeoColor.GetColorsInHueFamily(GeoColors.Red, 10);
symblePointLayer.ZoomLevelSet.ZoomLevel01.DefaultPointStyle = PointStyle.CreateSimplePointStyle(PointSymbolType.Triangle, colors[1], GeoColors.Blue, 1, 20);
symblePointLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
// Refresh styles if the user has saved styles.
Dictionary savedStyle = GetSavedStyleByAccessId(styleId, accessId);
if (savedStyle != null) UpdateSymbolPointStyle(symblePointLayer, savedStyle);
return symblePointLayer;
}
More Information
I hope you enjoyed today’s post. If you have a topic that you’d like to see covered in this blog, please let us know at sales@thinkgeo.com. If you want to check out the sample above, sign up for a free evaluation and clone the sample from our gitlab repo.