How to get selected layers in control.layers? - leaflet

Is there a way to select all selected layers in the control.layers with leaflet api?
I can do it with the help of jquery like this :
$('.leaflet-control-layers-selector:checked')
But maybe there is an api?
Thanks

There is no API for that but you could easily create one yourself:
// Add method to layer control class
L.Control.Layers.include({
getActiveOverlays: function () {
// Create array for holding active layers
var active = [];
// Iterate all layers in control
this._layers.forEach(function (obj) {
// Check if it's an overlay and added to the map
if (obj.overlay && this._map.hasLayer(obj.layer)) {
// Push layer to active array
active.push(obj.layer);
}
});
// Return array
return active;
}
});
var control = new L.Control.Layers(...),
active = control.getActiveOverlays();

Based on iH8's answer
L.Control.Layers.include({
getOverlays: function() {
// create hash to hold all layers
var control, layers;
layers = {};
control = this;
// loop thru all layers in control
control._layers.forEach(function(obj) {
var layerName;
// check if layer is an overlay
if (obj.overlay) {
// get name of overlay
layerName = obj.name;
// store whether it's present on the map or not
return layers[layerName] = control._map.hasLayer(obj.layer);
}
});
return layers;
}
});
Now you can use
var control = new L.Control.Layers(...)
control.getOverlays(); // { Truck 1: true, Truck 2: false, Truck 3: false }
I find this a little more useful because
all the layers are included
the key is the name of the layer
if the layer is showing, it has a value of of true, else false

Related

How to select which layer to show on a map?

If I had an array of layers added to my map, i.e.:
for (i = 0; i < myoptionsArray.length; i++) {
lyr = L.tileLayer.wms(url, {optionsArray[i]});
layer.push(lyr);
lyr.addTo(mymap);
}
how can I select programmatically which layer[i] to show? I can't find any available function in Leaflet docs...
Add your layer to a featureGroup when you create it. A great idea is to add a name to your layer so it will be simpler to get it after :
var group = new L.featureGroup();
for (i = 0; i < myoptionsArray.length; i++) {
lyr = L.tileLayer.wms(url, {optionsArray[i]});
layer.push(lyr);
layer.name = 'My_layer ...';
lyr.addTo(group);
}
mymap.addLayer(group);
In this example, for me, each iteration provide a layer. You add it to your group and wait the end of the loop to add it to the map.
To show or hide you will need this function :
function showHideTile(tileToShowOrHide)
{
group.eachLayer(function(layer) {
layer.eachLayer(function(yourLayer) {
//Do your test here
if (yourLayer == tileToShowOrHide) {
//To add the layer to your map
map.addLayer(yourLayer);
} else {
//To remove the layer
map.removeLayer(yourLayer);
}
//You can also send an array to this function
//With the layer name and what you want to do
//Ex : tile1 hide
})
})
}
Not the best way but it will give you something to start with.
I did this way.
1- load the layers into the group:
var group = new L.featureGroup();
for (i = 0; i < myoptionsArray.length; i++) {
lyr = L.tileLayer.wms(url, {optionsArray[i]});
layer.push(lyr);
lyr.addTo(group);
}
mymap.addLayer(group);
2 - Then I added a function to show the layer I need:
function showLayer(i) {
layer[i].bringToFront();
}

Leaflet Trigger Event on Clustered Marker by external element

I just starting to learn about Leaflet.js for my upcoming project.
What i am trying to accomplish:
I need to make a list of marker which displayed on the map, and when the list item is being hovered (or mouseover) it will show where the position on the map (for single marker, it should change its color. For Clustered marker, it should display Coverage Line like how it behave when we hover it.. and perhaps change its color too if possible).
The map should not be changed as well as the zoom level, to put it simply, i need to highlight the marker/ Cluster on the map.
What i have accomplished now : I am able to do it on Single Marker.
what i super frustrated about : I failed to find a way to make it happen on Clustered Marker.
I use global var object to store any created marker.
function updateMapMarkerResult(data) {
markers.clearLayers();
for (var i = 0; i < data.length; i++) {
var a = data[i];
var myIcon = L.divIcon({
className: 'prop-div-icon',
html: a.Description
});
var marker = L.marker(new L.LatLng(a.Latitude, a.Longitude), {
icon: myIcon
}, {
title: a.Name
});
marker.bindPopup('<div><div class="row"><h5>Name : ' + a.Name + '</h5></div><div class="row">Lat : ' + a.Latitude + '</div><div class="row">Lng : ' + a.Longitude + '</div>' + '</div>');
marker.on('mouseover', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon");
this._icon.classList.add("prop-div-icon-shadow");
}
});
marker.on('mouseout', function(e) {
if (this._icon != null) {
this._icon.classList.remove("prop-div-icon-shadow");
this._icon.classList.add("prop-div-icon");
}
});
markersRef[a.LocId] = marker; // <-- Store Reference
markers.addLayer(marker);
updateMapListResult(a, i + 1);
}
map.addLayer(markers);
}
But i don't know which object or property to get the Clustered Marker reference.
And i trigger the marker event by my global variable (which only works on single marker).
...
li.addEventListener("mouseover", function(e) {
jQuery(this).addClass("btn-info");
markersRef[this.getAttribute('marker')].fire('mouseover'); // --> Trigger Marker Event "mouseover"
// TODO : Trigger ClusteredMarker Event "mouseover"
});
...
This is my current https://jsfiddle.net/oryza_anggara/2gze75L6/, any lead could be a very big help. Thank you.
Note: the only js lib i'm familiar is JQuery, i have no knowledge for others such as Angular.js
You are probably looking for markers.getVisibleParent(marker) method, to retrieve the containing cluster in case your marker is clustered.
Unfortunately, it is then not enough to fire your event on that cluster. The coverage display functionality is set on the Cluster Group, not on its individual clusters. Therefore you need to fire your event on that group:
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// In case the marker is hidden in a cluster, have the clusterGroup
// show the regular coverage polygon.
markers.fire(eventName, {
layer: visibleLayer
});
} else {
marker.fire(eventName);
}
}
var marker = markersRef[this.getAttribute('marker')];
_fireEventOnMarkerOrVisibleParentCluster(marker, 'mouseover');
Updated JSFiddle: https://jsfiddle.net/2gze75L6/5/
That being said, I think another interesting UI, instead of showing the regular coverage polygon that you get when "manually" hovering a cluster, would be to spiderfy the cluster and highlight your marker. Not very easy to implement, but the result seems nice to me. Here is a quick try, it would probably need more work to make it bullet proof:
Demo: https://jsfiddle.net/2gze75L6/6/
function _fireEventOnMarkerOrVisibleParentCluster(marker, eventName) {
if (eventName === 'mouseover') {
var visibleLayer = markers.getVisibleParent(marker);
if (visibleLayer instanceof L.MarkerCluster) {
// We want to show a marker that is currently hidden in a cluster.
// Make sure it will get highlighted once revealed.
markers.once('spiderfied', function() {
marker.fire(eventName);
});
// Now spiderfy its containing cluster to reveal it.
// This will automatically unspiderfy other clusters.
visibleLayer.spiderfy();
} else {
// The marker is already visible, unspiderfy other clusters if
// they do not contain the marker.
_unspiderfyPreviousClusterIfNotParentOf(marker);
marker.fire(eventName);
}
} else {
// For mouseout, marker should be unclustered already, unless
// the next mouseover happened before?
marker.fire(eventName);
}
}
function _unspiderfyPreviousClusterIfNotParentOf(marker) {
// Check if there is a currently spiderfied cluster.
// If so and it does not contain the marker, unspiderfy it.
var spiderfiedCluster = markers._spiderfied;
if (
spiderfiedCluster
&& !_clusterContainsMarker(spiderfiedCluster, marker)
) {
spiderfiedCluster.unspiderfy();
}
}
function _clusterContainsMarker(cluster, marker) {
var currentLayer = marker;
while (currentLayer && currentLayer !== cluster) {
currentLayer = currentLayer.__parent;
}
// Say if we found a cluster or nothing.
return !!currentLayer;
}

Getting the bounds of loaded tile of leaflet

Using leaflet is there any way I can get the bounds (NorthEast, SouthWest) of a loaded tile? I want to request the server to load the markers only for a particular tile which is loaded, so that when user is panning/dragging the map he can easily see the new markers on new area.
What you really want to do is a subclass of L.GridLayer. This will allow fine control over loaded/unloaded tiles, and is the best way to use the private L.GridLayer._tileCoordsToBounds() method.
With some basic handling of loaded markers, it should look like:
L.GridLayer.MarkerLoader = L.GridLayer.extend({
onAdd: function(map){
// Add a LayerGroup to the map, to hold the markers
this._markerGroup = L.layerGroup().addTo(map);
L.GridLayer.prototype.onAdd.call(this, map);
// Create a tilekey index of markers
this._markerIndex = {};
},
onRemove: function(map){
this._markergroup.removeFrom(map);
L.GridLayer.prototype.onRemove.call(this, map);
};
createTile: function(coords, done) {
var tileBounds = this._tileCoordsToBounds(coords);
var tileKey = this._tileCoordsToKey(coords);
var url = ...; // Compute right url using tileBounds & coords.z
fetch(url).then(function(res){
if (!key in this._markerIndex) { this._markerIndex[key] = []; }
// unpack marker data from result, instantiate markers
// Loop as appropiate
this._markerGroup.addLayer(marker);
this._markerIndex[key] = marker;
done(); // Signal that the tile has been loaded successfully
});
},
_removeTile: function (key) {
for (var i in this._markerIndex[key]) {
this._markerGroup.remove(this._markerIndex[key][i]);
}
L.GridLayer.prototype._removeTile.call(this, key);
}
});
Please note that zooming might be a source of bugs and graphical glitches (markers being removed because a tile unloads, before the markers at the new zoom level are loaded). Beware of that.

Leaflet: How to toggle GeoJSON feature properties from a single collection?

I have a single GeoJSON object that contains over 2000+ features and each feature is part of one category (i.e. "Electrical", "Military", etc). There are a total of about 38 categories.
Here's the schema example of my collection:
{"type":"Feature","properties":{"category":"Electrical","Name":"Plant No 1"},"geometry":{"type":"Point","coordinates":[81.73828125,62.59334083012024]}},{"type":"Feature","properties":{"category":"Electrical","Name":"Plane No 2"},"geometry":{"type":"Point","coordinates":[94.5703125,58.722598828043374]}},{"type":"Feature","properties":{"category":"Military","Name":"Base 1"},"geometry":{"type":"Point","coordinates":[104.4140625,62.91523303947614]}}
Here's my L.geoJson function that iterates through the
collection:
var allPoints = L.geoJson(myCollection, {
onEachFeature: function(feature, layer){
layer.bindPopup(L.Util.template(popTemplate, feature.properties));
},
"stylel": function(feature){
return { "color": legend[feature.properties.category]
}
}}).addTo(map);
How can I assign each category property to my L.control function so the user can toggle on/off the various categories from the collection? I could do this if I made each category a dataset and an individual geoJSOn layer, but that's too much work to do all 38 categories.
My attempt:
L.control.layers({
'Street Map': L.mapbox.tileLayer('mapbox.streets').addTo(map)
},{
'Electrical': myCollection[feature.properties.category["Electrical"]],
'Military': myCollection[feature.properties.category["Military"]]
});
Is there a better way to do this? Thanks!
You can simply assign your layers within the onEachFeature function. You could even automate the creation of Layer Groups for each category.
Result:
var categories = {},
category;
function onEachFeature(feature, layer) {
layer.bindPopup(L.Util.template(popTemplate, feature.properties));
category = feature.properties.category;
// Initialize the category array if not already set.
if (typeof categories[category] === "undefined") {
categories[category] = [];
}
categories[category].push(layer);
}
// Use function onEachFeature in your L.geoJson initialization.
var overlays = {},
categoryName,
categoryArray;
for (categoryName in categories) {
categoryArray = categories[categoryName];
overlays[categoryName] = L.layerGroup(categoryArray);
}
L.control.layers(basemaps, overlays).addTo(map);
EDIT: replaced overlays to be a mapping instead of an array.
Iterate your GeoJSON collection and create multiple L.GeoJSON layers, one per category and add them as overlays to your L.Control.Layers instance.
var controlLayers = L.control.layers({
'Street Map': L.mapbox.tileLayer('mapbox.streets').addTo(map)
}).addTo(map);
// Object to store category layers
var overlays = {};
// Iterate the collection
collection.features.forEach(function (feature) {
var category = feature.properties.category;
// Check if there's already an overlay for this category
if (!overlays[category]) {
// Create and store new layer in overlays object
overlays[category] = new L.GeoJSON(null, {
'onEachFeature': function () {},
'style': function () {}
});
// Add layer/title to control
controlLayers.addOverlay(overlays[category], category);
}
// Add feature to corresponding layer
overlays[category].addData(feature);
});
Here's an example on Plunker: http://plnkr.co/edit/Zv2xwv?p=preview

Filtering mapbox markcluster

I am trying to filter mapbox markers with custom icons in a markerclustergroup. I can't seem to get the filters working on the markerclustergroup. Here is the relevant part of the code:
var filters = document.getElementById(‘filters’);
var checkboxes = document.getElementsByClassName(‘filter’);
var markers = L.mapbox.featureLayer()
.setGeoJSON(geojson);
var markercluster = new L.MarkerClusterGroup();
markers.on(‘layeradd’, function(e) {
// Create custom markers
var marker = e.layer,
feature = marker.feature;
marker.setIcon(L.icon(feature.properties.icon));
function change() {
// Find all checkboxes that are checked and build a list of their values
var on = [];
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) on.push(checkboxes[i].value);
}
// The filter function takes a GeoJSON feature object
// and returns true to show it or false to hide it.
markers.setFilter(function (f) {
// check each marker’s symbol to see if its value is in the list
// of symbols that should be on, stored in the ‘on’ array
return on.indexOf(f.properties[‘marker-symbol’]) !== -1;
});
return false;
}
// When the form is touched, re-filter markers
filters.onchange = change;
// Initially filter the markers
change();
markercluster.addLayer(markers);
map.addLayer(markercluster);
Here's a working example of filtering marker cluster groups