how to hide the leaflet tooltip (visibility already depending on zoom level) when activating a layer? - leaflet

I am interested in showing the tooltip only for a specific zoom range (>=12) at any time. With the help of this other article I could manage to almost get there but I still have the problem that the tooltip come to appear when I activate the layer in my OverlayMaps-menu, disregarding the current zoom level.
In my leaflet map I have implemented a OverlayMaps-menu, in which almost all layers are deactivated from the start. For one of these deactivated layers, my aim is to show the tooltips for all features only from a zoom level of 12 or bigger. The code is as follows:
Declaration of the variable from a geoJSON object:
var vibro_offshore = L.geoJson (vbc_offshore, {
pointToLayer: function (feature, latlng) {
return new L.Circle (latlng, {
color:'#a13c02',
})
},
onEachFeature: enCadaVBC
});
In the onEachFeature function, beside some other code regarding popup, I also have defined the tooltips:
function enCadaVBC (feature, layer) {
//declaramos un tooltip
var tooltip = L.tooltip({
content: feature.properties.Location,
permanent: true,
direction:'right',
className:'tooltip'
});
//introducimos un tooltip
layer.bindTooltip(tooltip);
Finally, I have managed to govern the visibility of the tooltips depending on the zoom level with the code suggested in the article referred above:
var lastZoom;
map.on('zoomend', function() {
var zoom = map.getZoom();
console.log(zoom);
if (zoom < 12 && (!lastZoom || lastZoom >= 12)) {
map.eachLayer(function(l) {
if (l.getTooltip) {
var toolTip = l.getTooltip();
if (toolTip) {
this.map.closeTooltip(toolTip);
}
}
});
} else if (zoom >= 12 && (!lastZoom || lastZoom < 12)) {
map.eachLayer(function(l) {
if (l.getTooltip) {
var toolTip = l.getTooltip();
if (toolTip) {
console.log(toolTip);
this.map.addLayer(toolTip);
}
}
});
}
lastZoom = zoom;
})
All this works pretty good, but when I activate the layer via mouseclick on the OverlayMaps-menu, lets say when the zoom level is 9, all the tooltips suddenly appear. I want to avoid this.
I have tried to add a closeTooltip() command right after the bindTooltip-command, but this hasnt have any effect. A consol.log for Is Tooltipopen leads to an undefined...(?)
I also tried to relocate the layer.bindTooltip command into the if-loop, but here I do get some other error related to Mercator projection and latlng, which I do not really understand...

Related

In Leaflet, how to know when map.fitBounds actually changed anything?

I have a click event on a map feature that zooms into that feature when clicked by the user
Map starts like this:
When a user clicks on the map feature:
L.geoJson(geoJsonFeatureCollection, {
style,
onEachFeature
}).addTo(map)
function zoomToFeature (e) {
map.fitBounds(e.target.getBounds())
}
function onEachFeature (feature, layer) {
layer.on({
click: zoomToFeature
})
}
On a second click on the same map feature, after being already zoomed in, I'd like to forward the URL to another page (the info HTML page of that feature). But I can't distinguish both situations.
How can I know in map.fitBounds if the map bounds were already fitted, i.e., if the method actually did/zoomed/panned anything?
You can use the same calculation function from leaflet and check if it equals to the current map state:
function zoomToFeature (e) {
const bounds = e.target.getBounds();
const target = map._getBoundsCenterZoom(bounds);
if(target.zoom === map.getZoom() && map.getCenter().equals(target.center)){
// bounds already fitting
} else {
map.fitBounds(bounds);
}
}
Maybe you need to change the margin of equals. Default: .equals(target.center, 1.0E-9)

How does leaflet determine teh position of bound markers, and can I change this w/o using anchor for each feature?

PLease see this jsfiddle (scroll down in the JS window to below the geoJSON)
https://jsfiddle.net/n3zerj2q/1/
var prevLayer = null;
var map = L.map("map").setView([30, 0], 3);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var noStyle = {
fillColor: "#fff",
weight: 1,
opacity: 1,
color: 'red',
dashArray: '3',
fillOpacity: 0
};
var myGeojSonLayerGroup = L.geoJson(world, {
onEachFeature: onEachFeature2
}).addTo(map);
function onEachFeature2(feature, layer) {
if (feature.properties && feature.properties.name) {
layer.bindPopup('<span class="pcA">' + feature.properties.name + '</span>')
}
layer.setStyle(noStyle);
layer.on({
mouseover: function (e) {
e.target.openPopup();
if (prevLayer !== null) {
// Reset style
prevLayer.setStyle(styleA());
}
var thisLayer = e.target;
thisLayer.setStyle(styleX())
prevLayer = thisLayer;
}
});
}
function styleA() {
return {
fillColor: "#fff", fillOpacity: 0.0
};
}
function styleX() {
return {
fillColor: "pink", fillOpacity: 0.4
};
}
It simply displays a world map and as the user rolls over country a popup appears with the country name (as well as some color styling) - mostly this is positioned "sensibly" more or less in the middle of the country, but for some (esp bigger ones) it's way off - eg the USA, China, Russia, Argentina...
I'm not setting the position of these - leaflet is, somehow... Other than editing the (large) geoJSON to include anchor points for each country, I wonder if there's some way of tweaking whatever leaflet is doing? Why, for example, does it place Antarctica's where it does? Or China's? etc....
So when a Popup is about to be shown, it will check where, in this specific bit of code:
if (!latlng) {
if (source.getCenter) {
latlng = source.getCenter();
} else if (source.getLatLng) {
latlng = source.getLatLng();
} else if (source.getBounds) {
latlng = source.getBounds().getCenter();
} else {
throw new Error('Unable to get source layer LatLng.');
}
}
So for popups without an explicit latlng, the position will dpend on themethods available on the source layer - this means that for Rectangles and ImageOverlays that'll run getBounds().getCenter(), for Markers and Circles that'll run getLatLng(), and for Polylines and Polygons that'll run getCenter().
The getCenter() method from Polygon just delegates the work elsewhere:
return PolyUtil.polygonCenter(this._defaultShape(), this._map.options.crs);
...and, finally, the polygonCenter() function will calculate the polygon's centroid.
Note that the implementation only takes into account the first ring of the polygon:
if (!LineUtil.isFlat(latlngs)) {
console.warn('latlngs are not flat! Only the first ring will be used');
latlngs = latlngs[0];
}
This is due to the centroid algorithm only being able to handle "simple" polygons, with just an outer ring. Remember to read the OGC SFA specification for a refresher on (multi)polygon rings.

Toggle geojson layer interactivity

First of all, since it's my first question, I'm sorry if I make any mistakes.
I'd like to "deactivate" a Geojson Layer, composed by a feature collection with many polygons (or lines). I have seen how to create it with the option interactive = yes or interactive = no, but I can't find out how to toggle this dynamically.
Thanks in advance.
EDIT: last thing I tried was this:
var onEachFeature = function(feature, layer) {
function onmouseover(e) {
var layer = e.target;
var activeLayer = ___get_active_layergroup();
if (activeLayer === null){
layer.setStyle({
cursor: 'move'
});
} else if (activeLayer.hasLayer(layer)){
layer.setStyle({
cursor: 'pointer'
});
} else {
layer.setStyle({
cursor: 'move'
});
}
}
function onmouseout(e) {
}
return {
mouseover: onmouseover,
mouseout: onmouseout
}
}
And I put that function in the layer initialization.
___get_active_layergroup is accesible in the context.
EDIT2:
It works if instead of setStyle I use this:
var element = layer.getElement();
if (element.classList.contains("leaflet-interactive")) {
element.classList.remove("leaflet-interactive")
}
But I don't really like how it works. I can see the pointer while I'm moving the mouse blinking (because it takes a few milliseconds process the mouseover I think).

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;
}

Mapbox,leaflet: Increase marker size on Zoom

How can I increase the size of all markers when we zoom in the map?
I know we can use map.on('zoomend', function() {}); and change the icon size inside the function.But I have a lot of markers and looping through all of them and changing them individually doesn't seem like a good idea.
There is nothing wrong with looping through a set of markers on every zoomend event. Why doesn't it sound like a good idea?
An alternative to looping through markers is to extend the L.Marker class to do the work for you, something like:
L.Marker.Autoresizable = L.Marker.extend({
onAdd: {
map.on('zoomend', this._changeIcon, this);
},
onRemove: function(map) {
map.off('zoomend', this._changeIcon, this);
},
_changeIcon: function(ev) {
var zoom = this._map.getZoom();
if (zoom <= 10) {
this.setIcon(...);
} elseif (zoom > 10 && zoom <= 15) {
this.setIcon(...);
} else {
this.setIcon(...);
}
}
});
L.marker.autoresizable = function(latlng, options) {
return new L.Marker.Autoresizable(latlng, options);
}
In this case, the Leaflet code will implicitly loop through all the event listeners for the zoomend event, which is pretty much the same (performance-wise) as looping through the markers yourself.