Layer Ordering in leaflet.js - gis

How can I force a new layer added to the map in Leaflet to be the first over the basemap?
I could not find a method to easily change the order of the layers, which is a very basic GIS feature. Am I missing something?

A Leaflet map consists of a collection of "Panes" whose view order is controlled using z-index. Each pane contains a collection of Layers The default pane display order is tiles->shadows->overlays->markers->popups. Like Etienne described, you can control the display order of Paths within the overlays pane by calling bringToFront() or bringToBack(). L.FeatureGroup also has these methods so you can change the order of groups of overlays at once if you need to.
If you want to change the display order of a whole pane then you just change the z-index of the pane using CSS.
If you want to add a new Map pane...well I'm not sure how to do that yet.
http://leafletjs.com/reference.html#map-panes
http://leafletjs.com/reference.html#featuregroup

According to Leaflet API, you can use bringToFront or bringToBack on any layers to brings that layer to the top or bottom of all path layers.
Etienne

For a bit more detail, Bobby Sudekum put together a fantastic demo showing manipulation of pane z-index. I use it as a starting point all the time.
Here's the key code:
var topPane = L.DomUtil.create('div', 'leaflet-top-pane', map.getPanes().mapPane);
var topLayer = L.mapbox.tileLayer('bobbysud.map-3inxc2p4').addTo(map);
topPane.appendChild(topLayer.getContainer());
topLayer.setZIndex(7);

Had to solve this recently, but stumbled upon this question.
Here is a solution that does not rely on CSS hacks and works with layer groups. It essentially removes and re-adds layers in the desired order.
I submit this as a better "best practice" than the current answer. It shows how to manage the layers and re-order them, which is also useful for other contexts. The current method uses the layer Title to identify which layer to re-order, but you can easily modify it to use an index or a reference to the actual layer object.
Improvements, comments, and edits are welcome and encouraged.
JS Fiddle: http://jsfiddle.net/ob1h4uLm/
Or scroll down and click "Run code snippet" and play with it. I set the initial zoom level to a point that should help illustrate the layerGroup overlap effect.
function LeafletHelper() {
// Create the map
var map = L.map('map').setView([39.5, -0.5], 4);
// Set up the OSM layer
var baseLayer = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
var baseLayers = {
"OSM tiles": baseLayer
};
this.map = map;
this.BaseLayers = {
"OSM tiles": baseLayer
};
this.LayersControl = L.control.layers(baseLayers).addTo(map);
this.Overlays = [];
this.AddOverlay = function (layerOptions, markers) {
var zIndex = this.Overlays.length;
var layerGroup = L.layerGroup(markers).addTo(map);
this.LayersControl.addOverlay(layerGroup, layerOptions.title);
this.Overlays.push({
zIndex: zIndex,
LeafletLayer: layerGroup,
Options: layerOptions,
InitialMarkers: markers,
Title: layerOptions.title
});
return layerGroup;
}
this.RemoveOverlays = function () {
for (var i = 0, len = this.Overlays.length; i < len; i++) {
var layer = this.Overlays[i].LeafletLayer;
this.map.removeLayer(layer);
this.LayersControl.removeLayer(layer);
}
this.Overlays = [];
}
this.SetZIndexByTitle = function (title, zIndex) {
var _this = this;
// remove overlays, order them, and re-add in order
var overlays = this.Overlays; // save reference
this.RemoveOverlays();
this.Overlays = overlays; // restore reference
// filter overlays and set zIndex (may be multiple if dup title)
overlays.forEach(function (item, idx, arr) {
if (item.Title === title) {
item.zIndex = zIndex;
}
});
// sort by zIndex ASC
overlays.sort(function (a, b) {
return a.zIndex - b.zIndex;
});
// re-add overlays to map and layers control
overlays.forEach(function (item, idx, arr) {
item.LeafletLayer.addTo(_this.map);
_this.LayersControl.addOverlay(item.LeafletLayer, item.Title);
});
}
}
window.helper = new LeafletHelper();
AddOverlays = function () {
// does not check for dups.. for simple example purposes only
helper.AddOverlay({
title: "Marker A"
}, [L.marker([36.83711, -2.464459]).bindPopup("Marker A")]);
helper.AddOverlay({
title: "Marker B"
}, [L.marker([36.83711, -3.464459]).bindPopup("Marker B")]);
helper.AddOverlay({
title: "Marker C"
}, [L.marker([36.83711, -4.464459]).bindPopup("Marker c")]);
helper.AddOverlay({
title: "Marker D"
}, [L.marker([36.83711, -5.464459]).bindPopup("Marker D")]);
}
AddOverlays();
var z = helper.Overlays.length;
ChangeZIndex = function () {
helper.SetZIndexByTitle(helper.Overlays[0].Title, z++);
}
ChangeZIndexAnim = function () {
StopAnim();
var stuff = ['A', 'B', 'C', 'D'];
var idx = 0;
var ms = 200;
window.tt = setInterval(function () {
var title = "Marker " + stuff[idx++ % stuff.length];
helper.SetZIndexByTitle(title, z++);
}, ms);
}
StopAnim = function () {
if (window.tt) clearInterval(window.tt);
}
#map {
height: 400px;
}
<link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.css">
<script type='text/javascript' src="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>
<div id="map"></div>
<input type='button' value='Remove overlays' onclick='helper.RemoveOverlays();' />
<input type='button' value='Add overlays' onclick='AddOverlays();' />
<input type='button' value='Move bottom marker to top' onclick='ChangeZIndex();' />
<input type='button' value='Change z Index (Animated)' onclick='ChangeZIndexAnim();' />
<input type='button' value='Stop animation' onclick='StopAnim();' />

I've found this fix (css):
.leaflet-map-pane {
z-index: 2 !important;
}
.leaflet-google-layer {
z-index: 1 !important;
}
found it here: https://gis.stackexchange.com/questions/44598/leaflet-google-map-baselayer-markers-not-visible

Related

Leaflet: Set HTML Block like L.ImageOverlay

Is it possible to set an layer like imageOverlay with html markup embed in Leaflet map? Maybe with a plugin?
I tried to set it with Popup, but I need more control about the X and Y coordinates. So it isn't a solution for my case.
If you read the Leaflet tutorials on how to extend Leaflet and make plugins, and have a look at the source code for L.ImageOverlay, the answer becomes quite easy:
L.HtmlBlockOverlay = L.ImageOverlay.extend({
initialize: function(bounds, options) {
return L.ImageOverlay.prototype.initialize.call(this, null, bounds, options);
},
_initImage: function() {
var block = this._image = L.DomUtil.create('div', 'leaflet-image-layer' +
(this._zoomAnimated ? ' leaflet-zoom-animated' : ''));
if (this.options.className) { L.DomUtil.addClass(block, this.options.className); }
block.innerHTML = this.options.html;
}
});
var blockOverlay = new L.HtmlBlockOverlay(bounds, {
html: 'Hello world!',
className: 'hello-world-box'
}).addTo(map);
See a working example.

Markers with on hover innerHTML

I am creating a Map datavisualisation with Leaflet js. And I am running into a (probably easy solved) problem. But I can't figure it out.
I have a div with an ID which is "zipcode"
<div id="zipcode></div>
And I am trying to change the text content of this div on a hover of a specific marker that has been plotted on the Map. I tried something like this:
marker.on('mouseover', function (e) {
this.openPopup();
document.getElementById("zipcode").innerHTML = rows[i]['postcode'];
});
The variable rows[i]['postcode'] is a value from a CSV which I load in with d3. This looks like:
d3.csv("data/data.csv", function(d) {
return {
client: d.Client,
postcode: d.Postcode,
plaats: d.Plaats,
totaal: d.Totaal,
budget: d.Budget,
besteed: d.Besteed,
percentage: d.Percentage,
latitude: d.Lat,
longitude: d.Long,
street: d.Street
};
}, function(error, rows) {
for(i=0; i<rows.length; i++) {
var latitude = rows[i]['latitude'];
var longitude = rows[i]['longitude'];
var percentage = rows[i]['percentage']/100;
var street = rows[i]['street'];
var cssIcon = L.divIcon({
// Specify a class name we can refer to in CSS.
className: 'css-icon',
// Set marker width and height
iconSize: [rows[i]['budget']/25+10, rows[i]['budget']/25+10]
});
var marker = L.marker([latitude,longitude], {icon: cssIcon, opacity: percentage}).addTo(map);
}
});
Unfortunatly I can't find a solution for this. Please help me!
You can store the data associated with the marker like this:
for(i=0; i<rows.length; i++) {
var latitude = rows[i]['latitude'];
var longitude = rows[i]['longitude'];
var percentage = rows[i]['percentage']/100;
var street = rows[i]['street'];
var cssIcon = L.divIcon({
// Specify a class name we can refer to in CSS.
className: 'css-icon',
// Set marker width and height
iconSize: [rows[i]['budget']/25+10, rows[i]['budget']/25+10]
});
var marker = L.marker([latitude,longitude], {icon: cssIcon, opacity: percentage}).addTo(map);
marker.myData = rows[i];//setting the data
}
Then in side your event you should be able to get the row data
marker.on('mouseover', function (e) {
this.openPopup();
document.getElementById("zipcode").innerHTML = this.myData.postcode;
});
Hope this helps!

Leaflet MarkerCluster with GeoJson

I am currently working on a Leaflet Project where I use external geojson files as data input. Since the json contains a lot of objects I would like to use the MarkerCluster plugin which I got from Mappbox:
<script src='https://api.tiles.mapbox.com/mapbox.js/plugins/leaflet-markercluster/v0.4.0/leaflet.markercluster.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox.js/plugins/leaflet-markercluster/v0.4.0/MarkerCluster.css' rel='stylesheet' />
<link href='https://api.tiles.mapbox.com/mapbox.js/plugins/leaflet-markercluster/v0.4.0/MarkerCluster.Default.css' rel='stylesheet' />
Displaying the json-layer without the clustering works just fine, but if i try to assign it to the cluster nothing is displayed.
var markersBar = L.markerClusterGroup();
var barLayer = new L.GeoJSON.AJAX("json/eat_drink/bar.geojson", {
pointToLayer: function(feature, latlng) {
var icon = L.icon({
iconSize: [27, 27],
iconAnchor: [13, 27],
popupAnchor: [1, -24],
iconUrl: 'icon/' + feature.properties.amenity + '.png'
});
return L.marker(latlng, {icon: icon})
},
onEachFeature: function(feature, layer) {
layer.bindPopup(feature.properties.name + ': ' + feature.properties.opening_hours);
}
});
markersBar.addLayer(barLayer);
console.log(markersBar);
map.addLayer(markersBar);
The console.log output lets me assume that there are no objects, but I don't get it why.
Object { options: Object, _featureGroup: Object, _leaflet_id: 24, _nonPointGroup: Object, _inZoomAnimation: 0, _needsClustering: Array[0], _needsRemoving: Array[0], _currentShownBounds: null, _queue: Array[0], _initHooksCalled: true }
What am I doing wrong?
Well it looks like you are using Leaflet-Ajax...so an async request is made to grab your geojson..and your immediate next line is markersBar.addLayer(barLayer);..which would contain nothing since the request is almost certainly not complete yet...
Instead, I believe you can use the loaded event provided in the documentation like
barLayer.on('data:loaded', function () {
markersBar.addLayer(barLayer);
console.log(markersBar);
map.addLayer(markersBar);
});
For anyone looking for a straight forward example for adding a marker cluster with geojson ajax to a map, binding pop-ups and adding to layer control:
// pop-up function
function popUp(f, l) {
var out = [];
if (f.properties) {
for (key in f.properties) {
out.push(key + ": " + f.properties[key]);
}
l.bindPopup(out.join("<br />"));
}
}
// add layer to map and layer control
function add_layer(layr, layr_name) {
map.addLayer(layr);
layerControl.addOverlay(layr, layr_name);
}
// fire ajax request
var points = new L.GeoJSON.AJAX("../data/points.geojson", { onEachFeature: popUp });
// create empty marker cluster group
var markers = L.markerClusterGroup()
// when geojson is loaded, add points to marker cluster group and add to map & layer control
points.on('data:loaded', function () {
markers.addLayer(points);
add_layer(markers, "Point Markers")
});

Google Maps KMZ file not rendering in IE8 and IE7

I have a web app with a map in it. I've added a nice little custom map control to turn on and off different layers on the map. Currently there are only two layers, and it all works nice and fine in most browsers.
Except for IE8+7. None of the layers are showing on the map when turned on. As far as I can tell the map is loading the kmz/kml files (when preserveViewport is set to false, the map moves to the right location) but they're just not appearing. One layer contains polylines, and the other contains markers. The code I use is below:
function someFunction() {
//code to initialise map etc goes here...
var layers = [];
//Create 1st layer
var exchangeslayer = new google.maps.KmlLayer('http://link.to.file/exchanges.kmz'
suppressInfoWindows: true,
preserveViewport: true
});
layers.push({name: "Exchanges", layer: exchangeslayer});
//Code to create second layer
var nyclayer = new google.maps.KmlLayer('http://www.nyc.gov/html/dot/downloads/misc/cityracks.kml'
suppressInfoWindows: true,
preserveViewport: false
});
layers.push({name: "NY City Tracks", layer: nyclayer});
addCustomLayerControls(layers);
}
function addCustomLayerControls(layers) {
//there is code here that would generate the divs for the custom map control
var container; //container is a div element created via javascript
for (var i = 0; i < layers.length; i++) {
this.addLayerLabelToContainer(layers[i], container);
}
//some more code
}
function addLayerLabelToContainer(layer, container) {
var map; //Assume I get a reference to the map
//some code here to make pretty labels for the map controls...
var layerLabel; // layerLabel is a div element created via javascript
google.maps.event.addDomListener(layerLabel, 'click', function() {
if(layer.layer.map == null) {
layer.layer.setMap(map);
} else {
layer.layer.setMap(null);
}
});
}
So as it turns out my problem related to CSS. One of my stylesheets was applying max-width: 100% to all img tags. This was playing havok with the map markers/polylines.
Its obvious now that I see it, but when you think the problem is to do with the javascript its not so obvious. As such, I'll leave this answer here for anyone else who makes the same mistake as me.
If you modify addLayerLabelToContainer() like this then it works in IE as expected. Verified it loads KMZ correctly in IE 8 and 9.
function addLayerLabelToContainer(layer, container) {
// var map; //Assume I get a reference to the map
//some code here to make pretty labels for the map controls...
var layerLabel; // layerLabel is a div element created via javascript
if(layer.layer.map == null) {
layer.layer.setMap(map);
} else {
layer.layer.setMap(null);
}
}
Don't need to invoke addDomListener(). Also note the API syntax:
addDomListener(instance:Object, eventName:string, handler:Function)
Also minor fix of syntax errors in someFunction as follows:
function someFunction() {
// var map; //assume map is initialised, I've just removed that code
var layers = [];
// see https://developers.google.com/maps/documentation/javascript/layers
//Create 1st layer
var exchangeslayer = new google.maps.KmlLayer(
'http://kml-samples.googlecode.com/svn/trunk/kml/kmz/simple/big.kmz',
{ suppressInfoWindows: true, preserveViewport: true
});
layers.push( {name: "Exchanges", layer: exchangeslayer} );
// ...
addCustomLayerControls(layers);
}

Google maps: Set different zoom levels for two different maps

I have two maps on a page, one is a map of the world, and the other is a closeup of the current place they picked on the map of the world. I would like to set different zoom min/max levels for each map but:
G_NORMAL_MAP.getMinimumResolution = function(){return 11};
Seems to set the same min/max for both maps, I can't set them to different levels.
I think the problem is probably elsewhere in your code - I'm not sure exactly how you're using that function.
Here is a method that will work. You can re-write it to have less duplication.
map1 = new GMap2(document.getElementById("map1"));
map1.addControl(new GLargeMapControl3D());
map1.addControl(new GMenuMapTypeControl());
var mt = map1.getMapTypes();
// Overwrite the getMinimumResolution() and getMaximumResolution() methods
for (var i=0; i<mt.length; i++) {
mt[i].getMinimumResolution = function() {return 7;}
mt[i].getMaximumResolution = function() {return 11;}
}
map1.setCenter(new GLatLng(40,-100), 8);
map2 = new GMap2(document.getElementById("map2"));
map2.addControl(new GLargeMapControl3D());
map2.addControl(new GMenuMapTypeControl());
var mt = map2.getMapTypes();
// Overwrite the getMinimumResolution() and getMaximumResolution() methods
for (var i=0; i<mt.length; i++) {
mt[i].getMinimumResolution = function() {return 2;}
mt[i].getMaximumResolution = function() {return 6;}
}
map2.setCenter(new GLatLng(40,-100), 4);
Do you need 2 different maps? You can use the Map2.showMapBlowup() function to show a subarea which will be a zoomed in section on the current map.
I'm sorry I don't know if you can actually do it with 2 different maps.
http://code.google.com/apis/maps/documentation/reference.html#GMap2.showMapBlowup
You could use a custom map type and copy the G_NORMAL_MAP members of using a library like Prototype.
var G_MY_MAP = Class.create(G_NORMAL_MAP, {
getMinimumResolution: function()
{
return 11;
}
});
Then on your second map:
secondMap.addMapType(G_MY_MAP);
secondMap.setMapType(G_MY_MAP);
No idea if this will work, just a brain storm.....