Monday, June 4, 2012

Leaflet is a framework for making online maps. Its design follows that of Google Maps API, which is sometimes a good thing (all coordinates are LatLng, reprojection is automatic) and sometimes not so great (many advanced features are lacking).

Leaflet is meant to be lightweight: the library is under 30 KB in size, which is great when it's only one of 200 files being downloaded as your site loads. But it also has a cost: features are lacking and some missing features are effectively bugs.

I'm working on our first significantly-complex program based on Leaflet. It's significantly more complex than "center an OSM map on my city, allow pan and zoom" So I'll focus on the individual bugs and the fixes I had to produce for them. These are for Leaflet 3, since at the time the project started Leaflet 4 was not yet considered in a stable condition, and a lot of them are The Way Google Does It so it's natural that they'd fall into some of the same issues as I have with Google Maps API.

NO GOOGLE MAPS

Leaflet doesn't support Google Maps, Bing Maps, nor any other commercial tile provider. There are community plugins to add L.TileLayer.Google and L.TileLayer.Bing However, the Google one simply doesn't work in IE at all. The Bing one works, though.

https://gist.github.com/1286658

LAYER STACKING / TOGGLING VISIBILITY / LAYER IDS

Layers do not support an explicit stacking order. The order in which they are added to the map, is the order in which they will be drawn, so be sure to add your basemap first, then the parks, then the trails in the parks, then the buildings which will block the parks.

However, layers also don't support having their visibility toggled. To turn off a layer, remove it from the map with map.removeLayer() But, turning the layer on again with map.addLayer() messes with your layer stacking: the park boundaries now appear over top of the buildings and trails, obscuring them completely.

The obvious "easy way out" is to not turn the layer off, but instead set its opacity to 0. This has two issues, though: a) the tiles are still being downloaded and sucking up bandwidth and time, and b) I had all sorts of strange bugs with the layer still appearing partially, pieces of tiles appearing over top of the other layers.

So, in the end, the only realistic way to go on, is to write your own function to toggle the visibility of a single layer. But wait, there's more: Leaflet doesn't give layers a unique ID that you can use to identify a layer, nor a getLayerByName() sort of function, nor does it have a method for getting the current visibility status, which isn't surprising since it never occured to them that someone may want to turn off a layer. So before you can toggle a layer's visibility (e.g. from a checkbox that has the layer's name as its value), you need to be able to identify the layer and also store its visibility state.

The finished code looks like this. The overlay maps are stored in a list (not a dictionary / assocarray, that doesn't retain its order) so we can add them in a reliable stacking sequence. I add the extra options "id" so we can uniquely identify a layer, and "visibility" so the visibility can be fetched and saved.

var PHOTOBASE = new L.TileLayer.WMS("http://server.com/wms", { layers:'aerial_2011', format:'image/jpeg', reuseTiles:true, subdomains:SUBDOMAINS });
var MAPBASE   = new L.TileLayer.WMS("http://server.com/wms", { layers:'topomap', format:'image/jpeg', reuseTiles:true, subdomains:SUBDOMAINS });
var OVERLAYS  = [];
OVERLAYS[OVERLAYS.length] = new L.TileLayer.WMS("http://server.com/wms", { id:'parks', visibility:true, layers:'parkbounds2012', format:'image/png', transparent:'TRUE', reuseTiles:true  });
OVERLAYS[OVERLAYS.length] = new L.TileLayer.WMS("http://server.com/wms", { id:'trails', visibility:true, layers:'trails_hike,trails_equestrian', format:'image/png', transparent:'TRUE', reuseTiles:true  });
OVERLAYS[OVERLAYS.length] = new L.TileLayer.WMS("http://server.com/wms", { id:'buildings', visibility:true, layers:'buildings_solid', format:'image/png', transparent:'TRUE', reuseTiles:true  });
function toggleLayer(which,status) {
    // go over the layers...
    for (var i=0, l=OVERLAYS.length; i<l; i++) {
        var layer = OVERLAYS[i];
        // if this is the matching layer, toggle its visiblity
        if (layer.options.id == which) {
            layer.options.visibility = status;
        }

        // regardless of whether this is the desired layer, remove it and re-add it so we can maintain the stacking order
        MAP.removeLayer(layer);
        if (layer.options.visibility) MAP.addLayer(layer);
    }
} 
 
function selectBasemap(which) {
    switch (which) {
        case 'photo':
            MAP.removeLayer(MAPBASE);
            MAP.removeLayer(PHOTOBASE);
            MAP.addLayer(PHOTOBASE,true);
            break;
        case 'map':
            MAP.removeLayer(MAPBASE);
            MAP.removeLayer(PHOTOBASE);
            MAP.addLayer(MAPBASE,true);
            break;
    }
}

Now that we have basic layer toggling, time to get cracking on programming my map app!

No comments:

Post a Comment