Is there any better way to get source marker on "popupopen" event in leaflet? - leaflet

So this is how I do it now:
map.on('popupopen', ({ popup }) => {
if (popup instanceof L.Popup) {
const marker = popup._source as L.Marker;
}
});
I really don't like accessing private variables in leaflet. I still have not found in leaflet api clean method to get marker that is binded to active popup.

Better way is to emit new event on marker popupopen and access it from wherever you want.
popupopen: () => {
map.fire('someevent', { somemarker });
},
map.on({
'someevent': (event) => {} // <- event has marker
});

Related

How to update Leaflet.markercluster Icon on Event `spiderfied`

I'm using the Leaflet.markercluster plugin (https://github.com/Leaflet/Leaflet.markercluster) and
I'm struggling to update the markerClusterGroup Icon when the event spiderfied is fired.
Code:
// Initiate markers w/ a markerClusterGroup and the default icon
var markers = L.markerClusterGroup({
showCoverageOnHover: false,
});
// Set marker, bind it to Popup and add the markers as an layer to the map
markers.addLayer(L.marker(...))
.bindPopup(
L.popup({offset: L.point(0,0)}).setContent(
`...`
).openPopup()));
map.addLayer(markers);
// Try to update the default markerClusterGroup icon when a markerClusterGroup is spiderfied
markers.on('spiderfied', function (a) {
console.log('spiderfied');
markers.options = {
showCoverageOnHover: false,
iconCreateFunction: function() {
return L.divIcon({ html: '<b>' + 'Test' + '</b>' });
}
};
markers.refreshClusters();
});
What am I doing wrong here? Any advices?
Thanks in advance!
I've tried to get: https://github.com/Leaflet/Leaflet.markercluster#refreshing-the-clusters-icon implemented
I've done research on internet

How can i stop this onClick event from rerendering the entire treemap echart i have?

How can i stop this onClick event from rerendering the entire treemap echart i have?
I have basically a echarts treemap https://echarts.apache.org/examples/en/editor.html?c=treemap-disk as a functional component in react. I need to be able to apply filters and "grey out" certain tree nodes that dont fit the criteria. This functionality works currently but it rerenders the echart so that the user must restart from the top level and clicktheir way through all the way to the bottom level. How can i avoid the rerendering? This is a similar example i have where clicking the node displays data but also rerenders the chart losing where the node was in the map.
const onChartClick = params => {
if (params.treePathInfo.length === 9) {
setDrawerData(params);
}
};
useEffect(() => {
props.setDrawerData(drawerData);
}, [drawerData]);
const onEvents = {
click: onChartClick,
}; ```
you can try to put your chart on useMemo it works for me :
const [dataLoaded, setdataLoaded] = useState(true);
const onChartClick = params => {
if (params.treePathInfo.length === 9) {
setDrawerData(params);
}
};
useEffect(() => {
props.setDrawerData(drawerData);
setdataLoaded(false)
}, [drawerData]);
const onEvents = {
click: onChartClick,
};
const MemoChart = useMemo(() => <Charts
option={option}
onEvents={onEvents}
/>, [dataLoaded]);

Mapbox-gl popup.on('open') not firing

Using Mapbox GL Javascript Web
My popups are opening but the 'open' event isn't firing. I read that this was fixed a while back so is there something I'm doing wrong here:
this.map.on('click', 'listings', (e: any) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const detailURI = e.features[0].properties.detailURI;
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(title)
.addTo(this.map)
.on('open', () => {
console.log('Popup opened'); // <--- Not firing
// Add a click listener to the custom button with dynamic URI
document.getElementById('popup-detail-button')
.addEventListener('click', () => {
console.log(`Clicked with link: ${detailURI}`);
});
});
});
If I do it like this:
this.map.on('click', 'listings', (e: any) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const detailURI = e.features[0].properties.detailURI;
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(this.returnPopupHTML(image))
.addTo(this.map);
// Add a click listener
document.getElementById('popup-detail-button')
.addEventListener('click', () => {
console.log(`Clicked with link: ${detailURI}`); // <-- Only works if closing popup before opening another one
});
});
The click listener on the button works but if I don't close a popup before opening another one then the event doesn't fire. This is something that users frequently do: they open a popup and then scroll over and open another one without closing the first. So what I'm really trying to do here is ensure whenever a popup is opened and it's custom button is clicked - the event is registered with the correct URI.
Thanks
I'm sure you figured it out by now. I'm just posting this for anyone else that comes across the situation. But I was having the same problem. Initially I was trying to do this.
const popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(
this.generatePointAndMarkerPopupHtml(layerClicked[0]),
)
.addTo(this.map);
popup.on('open', e => {
console.log('it is open');
});
What's weird is that when I used 'close' the console.log would work but not with 'open'.
What finally worked was your format, but putting the .on event listener before the .addTo(map):
const layerClicked: mapboxgl.MapboxGeoJSONFeature[] = this.map.queryRenderedFeatures(
e.point,
{
layers: this.currentPointAndMarkerLayerIds,
},
);
const popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(
this.generatePointAndMarkerPopupHtml(layerClicked[0]),
)
.on('open', e => {
console.log('It is open');
})
.addTo(this.map);
Thanks for setting me in the right direction!
EDIT
If you wanted to add an event listener I just do this.
const popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(
this.generatePointAndMarkerPopupHtml(layerClicked[0]),
)
.on('open', e => {
if (document.getElementById('drive-time')) {
document
.getElementById('drive-time')
.addEventListener('click', e => {
console.log(e);
});
}
})
.addTo(this.map);
I check if the document element exists or else it will create an error for in the developer console for me.

Mapbox GL JS: Style is not done loading

I have a map wher we can classically switch from one style to another, streets to satellite for example.
I want to be informed that the style is loaded to then add a layer.
According to the doc, I tried to wait that the style being loaded to add a layer based on a GEOJson dataset.
That works perfectly when the page is loaded which fires map.on('load') but I get an error when I just change the style, so when adding layer from map.on('styledataloading'), and I even get memory problems in Firefox.
My code is:
mapboxgl.accessToken = 'pk.token';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v10',
center: [5,45.5],
zoom: 7
});
map.on('load', function () {
loadRegionMask();
});
map.on('styledataloading', function (styledata) {
if (map.isStyleLoaded()) {
loadRegionMask();
}
});
$('#typeMap').on('click', function switchLayer(layer) {
var layerId = layer.target.control.id;
switch (layerId) {
case 'streets':
map.setStyle('mapbox://styles/mapbox/' + layerId + '-v10');
break;
case 'satellite':
map.setStyle('mapbox://styles/mapbox/satellite-streets-v9');
break;
}
});
function loadJSON(callback) {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', 'regions.json', true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
callback(xobj.responseText);
}
};
xobj.send(null);
}
function loadRegionMask() {
loadJSON(function(response) {
var geoPoints_JSON = JSON.parse(response);
map.addSource("region-boundaries", {
'type': 'geojson',
'data': geoPoints_JSON,
});
map.addLayer({
'id': 'region-fill',
'type': 'fill',
'source': "region-boundaries",
'layout': {},
'paint': {
'fill-color': '#C4633F',
'fill-opacity': 0.5
},
"filter": ["==", "$type", "Polygon"]
});
});
}
And the error is:
Uncaught Error: Style is not done loading
at t._checkLoaded (mapbox-gl.js:308)
at t.addSource (mapbox-gl.js:308)
at e.addSource (mapbox-gl.js:390)
at map.js:92 (map.addSource("region-boundaries",...)
at XMLHttpRequest.xobj.onreadystatechange (map.js:63)
Why do I get this error whereas I call loadRegionMask() after testing that the style is loaded?
1. Listen styledata event to solve your problem
You may need to listen styledata event in your project, since this is the only standard event mentioned in mapbox-gl-js documents, see https://docs.mapbox.com/mapbox-gl-js/api/#map.event:styledata.
You can use it in this way:
map.on('styledata', function() {
addLayer();
});
2. Reasons why you shouldn't use other methods mentioned above
setTimeout may work but is not a recommend way to solve the problem, and you would got unexpected result if your render work is heavy;
style.load is a private event in mapbox, as discussed in issue https://github.com/mapbox/mapbox-gl-js/issues/7579, so we shouldn't listen to it apparently;
.isStyleLoaded() works but can't be called all the time until style is full loaded, you need a listener rather than a judgement method;
Ok, this mapbox issue sucks, but I have a solution
myMap.on('styledata', () => {
const waiting = () => {
if (!myMap.isStyleLoaded()) {
setTimeout(waiting, 200);
} else {
loadMyLayers();
}
};
waiting();
});
I mix both solutions.
I was facing a similar issue and ended up with this solution:
I created a small function that would check if the style was done loading:
// Check if the Mapbox-GL style is loaded.
function checkIfMapboxStyleIsLoaded() {
if (map.isStyleLoaded()) {
return true; // When it is safe to manipulate layers
} else {
return false; // When it is not safe to manipulate layers
}
}
Then whenever I swap or otherwise modify layers in the app I use the function like this:
function swapLayer() {
var check = checkIfMapboxStyleIsLoaded();
if (!check) {
// It's not safe to manipulate layers yet, so wait 200ms and then check again
setTimeout(function() {
swapLayer();
}, 200);
return;
}
// Whew, now it's safe to manipulate layers!
the rest of the swapLayer logic goes here...
}
Use the style.load event. It will trigger once each time a new style loads.
map.on('style.load', function() {
addLayer();
});
My working example:
when I change style
map.setStyle()
I get error Uncaught Error: Style is not done loading
This solved my problem
Do not use map.on("load", loadTiles);
instead use
map.on('styledata', function() {
addLayer();
});
when you change style, map.setStyle(), you must wait for setStyle() finished, then to add other layers.
so far map.setStyle('xxx', callback) Does not allowed. To wait until callback, work around is use map.on("styledata"
map.on("load" not work, if you change map.setStyle(). you will get error: Uncaught Error: Style is not done loading
The current style event structure is broken (at least as of Mapbox GL v1.3.0). If you check map.isStyleLoaded() in the styledata event handler, it always resolves to false:
map.on('styledata', function (e) {
if (map.isStyleLoaded()){
// This never happens...
}
}
My solution is to create a new event called "style_finally_loaded" that gets fired only once, and only when the style has actually loaded:
var checking_style_status = false;
map.on('styledata', function (e) {
if (checking_style_status){
// If already checking style status, bail out
// (important because styledata event may fire multiple times)
return;
} else {
checking_style_status = true;
check_style_status();
}
});
function check_style_status() {
if (map.isStyleLoaded()) {
checking_style_status = false;
map._container.trigger('map_style_finally_loaded');
} else {
// If not yet loaded, repeat check after delay:
setTimeout(function() {check_style_status();}, 200);
return;
}
}
I had the same problem, when adding real estate markers to the map. For the first time addding the markers I wait till the map turns idle. After it was added once I save this in realEstateWasInitialLoaded and just add it afterwards without any waiting. But make sure to reset realEstateWasInitialLoaded to false when changing the base map or something similar.
checkIfRealEstateLayerCanBeAddedAndAdd() {
/* The map must exist and real estates must be ready */
if (this.map && this.realEstates) {
this.map.once('idle', () => {
if (!this.realEstateWasInitialLoaded) {
this.addRealEstatesLayer();
this.realEstateWasInitialLoaded = true
}
})
if(this.realEstateWasInitialLoaded) {
this.addRealEstatesLayer();
}
}
},
I ended up with :
map.once("idle", ()=>{ ... some function here});
In case you have a bunch of stuff you want to do , i would do something like this =>
add them to an array which looks like [{func: function, param: params}], then you have another function which does this:
executeActions(actions) {
actions.forEach((action) => {
action.func(action.params);
});
And at the end you have
this.map.once("idle", () => {
this.executeActions(actionsArray);
});
I have created simple solution. Give 1 second for mapbox to load the style after you set the style and you can draw the layer
map.setStyle(styleUrl);
setTimeout(function(){
reDrawMapSourceAndLayer(); /// your function layer
}, 1000);
when you use map.on('styledataloading') it will trigger couple of time when you changes the style
map.on('styledataloading', () => {
const waiting = () => {
if (!myMap.isStyleLoaded()) {
setTimeout(waiting, 200);
} else {
loadMyLayers();
}
};
waiting();
});

Detecting right click position on angular leaflet map

I have a mobile page showing a map using angular-leaflet-directive 0.7.11, and have declared my required events like so:
$scope.map = {
events: [
'mousedown',
'contextmenu'
],
...
}
$scope.$on('leafletDirectiveMap.mousedown', function (event) {
debugger;
});
Where the debugger statement is, the event variable contains no information about where the map was clicked. The same event format was provided by the directive when the contextmenu event is triggered.
In fact, if I inspect the entire event variable, it is just an Object, not an Event:
Are the docs wrong? Is the example missing something? How can I obtain the X/Y or Lat/Lng for the particular position that I have right-clicked (tap-hold)?
You need to use the 'leafletEvent'. Try this:
myApp.controller('YourController', ['$scope', 'leafletEvent', function($scope) {
$scope.$on('leafletDirectiveMap.mousedown', function (event, leafletEvent) {
leafletData.getMap().then(function(map) {
map.on('click', function(e) {
console.log('e');
console.log(e);
console.log('e.latlng:');
console.log(e.latlng); // L.LatLng {lat: 19.642587534013046, lng: -4.5703125}
});
});
});
}]);