Leaflet Trigger Event on Clustered Marker by external element - leaflet

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

Related

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

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...

checking duplicate markers leaflet geoman

Im trying to check each geojson feature if it is a Marker. If it is I want to remove the placed layer then init drawing marker again.
If it is not the same position, I will just add it to the feature layer.
The problem is with eachLayer it always returns true because it loops trough all layers and it always return true because marker is added to feature. So it always repeats.
features.eachLayer(layer => {
if(layer.pm._shape === 'Marker') {
if(e.layer._latlng !== layer._latlng) { //This is never true, should be true if the placed marker is not placed over an existing features marker
features.addLayer(e.layer);
} else if(e.layer._latlng === layer._latlng) { //this elseif is always true for some reason and will loop
map.removeLayer(e.layer)
DrawUtil.addMarker(map, isSnapping); //Alias for pm.enableDraw.marker
features.addLayer(e.layer);
}
}
})
Here is fiddle, my bad forgot to add vital code.
https://jsfiddle.net/2ftmy0bu/2/
Change your code to:
// listen to when a new layer is created
map.on('pm:create', function(e) {
//should only place one marker each time
// check if the layer with the same latlng exists
var eq = features.getLayers().find(function(layer){
if(layer instanceof L.Marker) {
return layer.getLatLng().equals(e.layer.getLatLng())
}else{
return false;
}
}) !== undefined;
if(!eq) {
console.log('not equal')
features.addLayer(e.layer);
map.pm.disableDraw('Marker')
//if marker is placed on the map and it is not placed on same place as another marker
} else if(eq) {
console.log('equal')
//if marker is placed on the map over another marker, remove marker, then init draw marker again.
map.removeLayer(e.layer);
map.pm.enableDraw('Marker', {
snappable: true,
snapDistance: 20,
finishOn: 'click' || 'mousedown',
});
// TODO: I think you don't want this
// features.addLayer(e.layer);
}
});
https://jsfiddle.net/falkedesign/c6Lf758j/

leaflet routing.control update when marker is moved

I am using leaflet and routing.control to show a route. I have it working fine, but I would like one of the markers to move with the users location using watch.position. But for now I a just trying to move the marker when I click a button. Again this works fine but when the marker moves I would like the route to update automatically. Its possible if you drag the marker so surely its possible when marker is moved in a different way? I can it if I remove the control and add a new one but this flickers too much. Any advice?
The code for the routing.control is
myroute = L.Routing.control({
waypoints: [
L.latLng(window.my_lat, window.my_lng),
L.latLng(window.job_p_lat, window.job_p_lng)
],show: true, units: 'imperial',
router: L.Routing.mapbox('API KEY HERE'),
createMarker: function(i, wp, nWps) {
if (i === 0 || i === nWps + 1) {
return mymarker = L.marker(wp.latLng, {
icon: redIcon
});
} else {
return job_start = L.marker(wp.latLng, {
icon: greenIcon
});
}
}
}).addTo(map);
and the code for moving the marker is
function movemarker() {
var lat = "52.410490";
var lng = "-1.575950";
var newLatLng = new L.LatLng(lat, lng);
mymarker.setLatLng(newLatLng);
// I assume I call something here?
}
Sorted, I did it with this, which removes first point and replaces it with new data
myroutewithout.spliceWaypoints(0, 1, newLatLng);

Efficient way of accessing large data sets to display on Leaflet

I have a global database of objects (points on a map) that I would like to show on a Leaflet map. Instead of loading all the database (simply too big) at once and creating the objects on a Leaflet LayerGroup, is there a more efficient way to go about querying data, perhaps as each map tile loads, or am I looking at creating a custom solution for this?
You could watch the 'moveend' event on map with map.on('moveend',loadstuff), make an ajax call inside loadstuff() to grab markers inside the current map.getBounds(), and then add/remove markers(I would assume you have some sort of global identifier for each of them) whether they are inside or outside the current view.
There's a standard and efficient way around doing what snkashis said. Create a tiled geoJSON service, and use the leaflet-tilelayer-geojson plugin.
Then all the code you would need browser-side is (from the Github page):
var geojsonURL = 'http://polymaps.appspot.com/state/{z}/{x}/{y}.json';
var geojsonTileLayer = new L.TileLayer.GeoJSON(geojsonURL, {
clipTiles: true,
unique: function (feature) {
return feature.id;
}
}, {
style: style,
onEachFeature: function (feature, layer) {
if (feature.properties) {
var popupString = '<div class="popup">';
for (var k in feature.properties) {
var v = feature.properties[k];
popupString += k + ': ' + v + '<br />';
}
popupString += '</div>';
layer.bindPopup(popupString);
}
if (!(layer instanceof L.Point)) {
layer.on('mouseover', function () {
layer.setStyle(hoverStyle);
});
layer.on('mouseout', function () {
layer.setStyle(style);
});
}
}
}
);
map.addLayer(geojsonTileLayer);

How to show different popups on click and on mouseover?

The SelectFeature method in Control class provides a way of adding and removing popups on the Vector layer by listening to events featureselected and featureunselected respectively. Below shows a sample code that I obtained from an example in the openlayers website:
// create the layer with listeners to create and destroy popups
var vector = new OpenLayers.Layer.Vector("Points",{
eventListeners:{
'featureselected':function(evt){
var feature = evt.feature;
var popup = new OpenLayers.Popup.FramedCloud("popup",
OpenLayers.LonLat.fromString(feature.geometry.toShortString()),
null,
"<div style='font-size:.8em'>Feature: " + feature.id +"<br>Foo: </div>",
null,
true
);
feature.popup = popup;
map.addPopup(popup);
},
'featureunselected':function(evt){
var feature = evt.feature;
map.removePopup(feature.popup);
feature.popup.destroy();
feature.popup = null;
}
}
});
vector.addFeatures(features);
// create the select feature control
var selector = new OpenLayers.Control.SelectFeature(vector,{
hover:true, # this line
autoActivate:true
});
The code above will allow a popup to be shown upon mouseover on the Geometry object (icon or marker on the map). If the line hover:true is removed, the popup will be shown only upon a mouse click on the Geometry object.
What I want, is to be able to display one type of popup (example, an image plus a title) upon mouseover and another type (example, detailed description) upon a mouse click. I am not sure how this could be done. Some help would be much appreciated. Thanks.
Also, there another way, it's rather hack than correct usage of API, but seems to work. You can overwrite over and out callbacks.
var selectControl = new OpenLayers.Control.SelectFeature(vectorLayer, {
callbacks: {
over: function(feat) {
console.log('Show popup type 1');
},
out: function(feat) {
console.log('Hide popup type 1');
}
},
eventListeners: {
featurehighlighted: function(feat) {
console.log('Show popup type 2');
},
featureunhighlighted: function(feat) {
console.log('Hide popup type 2');
}
}
});
Here's working example: http://jsfiddle.net/eW8DV/1/
Take a look on select control's source to understand details.