Hello!
As we can see here https://www.mapbox.com/mapbox-gl-js/example/custom-marker-icons/
we could create a custom Marker and add it to map.
e.g. new mapboxgl.Marker(...).setLngLat(...).addTo(map);
I'm trying to find the way how to add marker to the layer and organize into the Cluster.
Smth like https://www.mapbox.com/mapbox-gl-js/example/cluster/ but with custom Markers:
Hi @D1M! We do not support clustering on Marker
s, only on symbol
layers. This is a feature we may support in the future but have no immediate plans to add support.
You _can_ use custom icons with a symbol
layer via the new Map#addImage
method (going out in the next release) or by adding the icons to your sprite in Studio.
@lucaswoj thanks, looking forward to see such feature in future releases of mapbox-gl!
Let's keep this issue open to track the feature 😄
The Android and iOS analogue of this issue is being tracked in mapbox/mapbox-gl-native#5815.
+1 I too would like to have custom markers that work with clustering.
+1 would really love to have this feature
this is possible now, with https://github.com/mapbox/supercluster. Was originally built for leaflet maps, but can be adapted to GL marker.
that's amazing, you guys are the best !!
How to use this cluster plugin with custom html marker there is not much documentation about it ?? ;(
@DarkKnight1992 you are correct that there is no documentation specifically combining supercluster with GL custom markers. The former does provide GeoJSON as output, though, which you can figure out how to adapt as an input for the latter.
@peterqliu but yo can't use custom marker as geojson in Mapbox Gl if i am not wrong ? Since you can't add a source and get a source layer very are using ajax for getting the getting and iteration for the former geojson created for mapbox leaflet to create new markers in Mapbox Gl. Is that the correct way of adding custom markers or mapbox gl provides some kind of api like loadurl() in mapbox leaflet ?
but yo can't use custom marker as geojson in Mapbox Gl if i am not wrong ?
@DarkKnight1992 see the first link in the original post above for an example of how to do this
that's how its been done currently
Implemented in https://github.com/alex3165/react-mapbox-gl
But, as you can see in the demo : http://alex3165.github.io/react-mapbox-gl/, when we rotate/pitch the map, the cluster are messed up.
Because it's not sending the real viewport to supercluster, it's based on the current axis-aligned rectangular bounding box (==> https://github.com/mapbox/mapbox-gl-js/issues/2112)
const bounds = map.getBounds();
const zoom = map.getZoom();
const newPoints = supercluster.getClusters(
[bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()],
Math.round(zoom)
);
Does supercluster have a way to get the real viewport ?
How it is done in mapbox-gl ? Because the cluster demo (https://www.mapbox.com/mapbox-gl-js/example/custom-marker-icons/) works fine with rotation/pich, and as far I know, it use supercluster internally.
Does supercluster have a way to get the real viewport ?
@Wykks you can adapt unproject() on the four corners, for this purpose.
@peterqliu ran into another problem today and paying very big price for using Mapbox Gl if there is no workaround for this problem. As i know mapbox-gl uses webgl to render images but a vast amount of our user base may not have webgl support. Can we render raster images instead of webgl? is that a possibilty ? If not then it's really been a huge waste of time using mapbox gl :(
Whoa true, totally missed that, thanks !
@DarkKnight1992 Yep, as far I know mapbox-gl require webgl (hence the suffix -gl), you can use this plugin to test if mapbox-gl is supported : https://github.com/mapbox/mapbox-gl-supported
@Wykks thanks. We didn't wanted to use other plugin for checking the webgl support and only a small script did the trick. But in the end i was really disappointed as we had to load to different libraries for doing the same thing. Shouldn't there be better work around for this ? Because loading a two different libraries mean using two different sets of code as a library and the actual code for the map. which goes against the basic rule of programming - "Never write code again fro the same result".
There is documentation for checking webgl support
Any improvement on this one?
I managed to add custom icon by generating them with the addImage but I get a "SpriteAtlas out of space"...
So I tought that we could have manage our marker manually and add them to a layer and the clustering would work out of the box without having to rebuild everything... it suck that it don't... I have over 500 markers on my map and each of them have an icon with their logos... so I have to add over 500 custom icon and without clustering it's a mess...
Why not, adding the possibility to pass a custom icon url in the GeoJSON to display???
+1 for marker clustering support
Also, Is there an event triggered when a symbol disappears to be part of a cluster? Using this code https://www.mapbox.com/mapbox-gl-js/example/cluster/
I have a map with markers. On click on a marker, the icon changes to an "active" style and a popup shows up.
Since I want to use clustering, I've changed all my markers to be symbols. The only way I found to have an "active" icon on click is by adding a layer on top of all the symbols with just this point using a different icon. However when zooming out this symbol doesn't get clustered when the "default" one is resulting in the "active" icon displaying on top of the cluster...
Thanks
+1 For marker clustering support
Hi Guys ,
please work around on this.This is most important feature.
I am still using leaflet cluster on mapboxgl to make it working but this is very important feature.
+1
Also after this, a custom icon url would be perfect!
For anyone still wanting this feature. The current workaround I'm using is to display both markers and clusters on a map. Using queryRenderedFeatures
to grab the unclustered points (which are hidden using the opacity prop) and using a unique id attribute/property to show/hide the markers on events such as render/load.
+1 really useful
For ReactJS lover, here is a solution for clustering (using supercluster) in mapbox-gl:
https://github.com/thuanmb/react-mapbox-gl-cluster
Any news regarding this?
+1
+1 "musthave" feature
Here's an example of clustering using HTML markers:
https://jsfiddle.net/iaezzy/xpvt214o/309931/
.. but the recommended way is to use 'Symbols' which I realized and moved to after I figured I could use any external image with symbols. There's really no reason to use HTML markers over symbols.
@3zzy You're example is incorrect (try to rotate / pitch), you should use unproject (see comments above).
There are valid reasons to use HTML marker when you need very custom icons (like d3 generated icons). But yeah, before thinking about using HTML markers, one should check if it's doable using mapbox-gl style system.
There are valid reasons to use HTML marker when you need very custom icons (like d3 generated icons).
@Wykks Could you do something like https://www.mapbox.com/mapbox-gl-js/example/add-image-generated/.
Interested to hear the reasons HTML Markers are needed over symbols. Personally I know animated markers (via css or gif) is a big one. Accessibility could be mitigated with https://github.com/mapbox/mapbox-gl-accessibility.
+1
@andrewharvey stuff like that http://bl.ocks.org/gisminister/10001728
You can't use addImage
to do this, because you can't generate everything in this case.
And yep, animated markers is an issue with symbols.
+1
@3zzy Thanks. This is the best clusters workaround for using HTML markers and popups
@andrewharvey
the reasons HTML Markers are needed over symbols
I want to display the users avatars on markers. In my case it is also reasonable to move the clustering to the server side. Could the vector source mapbox-gl-js functionality be used to manage the Marker
's? Doesn't the usage of something like a https://github.com/nextzen/d3-vector-tiles overlay over the mapbox map sounds too sick?
Oh, found the querySourceFeatures
method, could be really easy to integrate with d3 overlay.
@andrewharvey
the reasons HTML Markers are needed over symbols
In my app, users should be able to choose an option once they've located a marker, and the options will depend on the status of the place that the marker refers to. So far I haven't found another way.
@D1M @lucaswoj @1ec5 @realph @DarkKnight1992
i implemented cluster on react-native-map in mobile app but my requirement is like below , i dont know it is possible in react-native-map or not. Please help me team
Thank you for your kind cooperation.
First part show cluster that i implemented very well.
second part when press on cluster icon it show hidden marker that is almost on same location that i don't know how to implement please see image
Please if any plugin in react-native
What exactly is the best option right now when needing custom cluster icons as well as custom marker icons? Coming from leaflet, it supports this pretty well with DivIcons, I'm not sure how to implement this in mapbox though. Halp!
Hello tmcintire,thanks for your response. So you said it is not implement in react-native map.please reply.
I'm not using the react implementations of this. None of them expose nearly all of the api pieces that we use. We have a complex map with lots of layers and lots of custom markers. So we had to ditch trying to use react for this
Is there any solution to do this functionality in react-native-map?
Clustering seems unnecessarily complicated. When can we expect to do this in a simple way using the Marker object?
+1
+1 can't wait for this one
我在做的产品是全美房源地图,因为layers-symbol不能实现我的业务需求,所以只能使用marker来实现自定义内容和样式。之前我使用的Google map js开发的产品,现在替换到mapbox-GL-,目前我只能像以前一样继续使用supercluseter作为替代方案,产品效果也很不错,https://github.com/mapbox/supercluster
所以,希望mapbox尽快实现cluster和marker的完美结合。
This functionality is also needed in my project. We need the combination of marker cluster and custom marker icon in our project.
I have an idea — how about implementing this externally with existing tools like this:
querySourceFeatures
on every integer zoom change and synchronize the results with the DOM markers on the map. Since the number of cluster is usually relatively low, performance should be fine.I could try doing a proof of concept. Does this sound like something that would fit use cases here, or is there anything I'm not taking into account?
I already done this before, but if you do this, there a limitation : you cannot get the leaves of the clusters. And that's a big issue if you need that. So it's ok as long as you don't need theses informations do draw/interact with the cluster.
@Wykks why not? You can keep track of cluster ids in the DOM marker objects, and then use the new cluster API methods like getClusterChildren.
Oh well, when I played with this, the new api (https://github.com/mapbox/mapbox-gl-js/pull/6829) wasn't there :smile:
That's cool, I should try to do this in ngx-mapbox-gl :+1:
@mourner that sounds good. It doesn't need to be in the Mapbox GL core but it'd be great to have an example to get us started 👍
@malwoodsantoro @mzdraper this could be a useful example to add to our docs
Any news on this feature?
@mourner @Wykks Did you guys achieved the expected result? If yes, can you please do a jsfiddle for the help of the community.
+1
Is anyone working on this? I don't know if I should keep waiting for this or look for another solution. Thanks!
I tried the solution someone mentioned that consists of adding a transparent circle layer on the map, enabling clustering on it, the generating html markers after the map finished moving. Works great. I made sure every circle had a unique ID and created a local cache for the markers which is indexed by the ID. Tested with 20,000 markers and worked great. Likely can handle a lot more. A good code sample or plug in might be sufficient.
@rbrundritt would you be able to share an example of your solution? Trying to accomplish something similar in a React project. Struggling with displaying only those HTML markers that are currently not in a cluster.
Unfortunately my sample is built into a heavily modified version of the Mapbox GL control and it would likely be more work understanding the modified version than the clustering logic. The basic approach is as follows:
Thanks for your detailed answer. Makes sense. Your advice to use moveend
event totally works after actually moving the map. But how do you get queryRenderedFeatures
from the layer on initial load? This is what I'm having trouble with.
What I've done is wait for the load event of the map to fire, then load my source and layer. Then following that call the same logic I have in the moveend event. If there is a decent amount of data in the source, the clustering logic would likely still be running and the initial query for rendered data will be blank. To address this, use the sourcechange event and have it run the same logic as moveend. This will fire when the source data updates which will address this issue and will also allow you to update the source data and have it automatically reflected in your HTML marker layer.
@rbrundritt Thank you for all the info! It would be really cool if mapbox-gl supported clustering with html markers out of the box.
+1
I believe this feature (clustering of custom html markers) is long overdue.
+1 for clustering of markers. Honestly, I was surprised to learn that this is not possible yet. It just makes so much sense and is a feature many people are used to these days.
I don't work for Mapbox, so guessing here, but suspect this was left out as the focus appears to be on native render (i.e. symbol layer) which supports much larger data sets. The native functionality supports clustering. HTML markers I think were added more so for those moving from legacy apps or who render a few points but need full HTML/CSS support.
From first reading this, it might seem to some it is not possible to have clusters/markers with custom icons/HTML popups on mapbox GL which absolutely is possible.
Don't let yourself be fooled! It is absolutely possible and not too hard. Here are some pointers.
1) use exclusively geoJSON. Add a property to hold your icon name.
2) make the layer with the unclustered items a symbol layer
type: 'symbol',
layout: {
'icon-image':'{yourIconPropertyName}'
}
3) add your custom icons with map.loadImage()
4) react to mouse clicks onto markers and create and open popups
```
map.on(
'click',
'unclustered-points',
function (e) {
var coordinates = e.features[0].geometry.coordinates.slice()
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(
JSON.stringify(e.features[0].properties) // just an example ;)
)
.addTo(map)
}
)
```
Dear Mapbox, I suggest you add an example showcasing that.
@jonaseberle I think you missed the ask for this task. The ask is for the ability to cluster HTML markers. Clustering symbols and displaying popups with HTML content is already well known, and not what people are asking for here. Symbols are great for rendering large data set, but is not nearly as customizable as HTML markers where you have access to the full CSS stack. Here is an example where HTML markers are used to represent clusters. Each cluster marker is a pie chart where each slice of the pie is fully interactive.
Ah, I see. Thank you for the example @rbrundritt . Sorry to having derailed that issue.
It is just that the first post mentions an example where the marker is just an image - which is exactly possible with a symbol geoJSON layer.
Hey, heads up that I'm working on an official example at the moment — will aim for something like on the screenshot above. Stay tuned in the nearest week!
The new example with HTML clusters and property aggregation is live — check it out! Thanks to everyone who participated in the thread, and let me know if there's anything we can improve / clarify. https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/
Excellent work!! Although, I think the only thing missing is when we click on the marker it should zoom in the map. Is there any way I can achieve this functionality?
@smitraval The normal symbol clustering demo does this https://docs.mapbox.com/mapbox-gl-js/example/cluster/
// inspect a cluster on click
map.on('click', 'clusters', function (e) {
var features = map.queryRenderedFeatures(e.point, { layers: ['clusters'] });
var clusterId = features[0].properties.cluster_id;
map.getSource('earthquakes').getClusterExpansionZoom(clusterId, function (err, zoom) {
if (err)
return;
map.easeTo({
center: features[0].geometry.coordinates,
zoom: zoom
});
});
});
@andrewharvey Thank you very much for your quick reply. I will try the same and update the final result here for future reference.
Hi @andrewharvey , Using your example I have successfully created marker clusters with images, However I am still stuck at a point where I need to replace un-clustered points with custom html div. Here is my code
`map.on('load', function () {
// Add a new source from our GeoJSON data and set the
// 'cluster' option to true. GL-JS will add the point_count property to your source data.
map.addSource("hotels", {
type: "geojson",
// Point to GeoJSON data. This example visualizes all M1.0+ earthquakes
// from 12/22/15 to 1/21/16 as logged by USGS' Earthquake hazards program.
data: hotelGeo,//"https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
cluster: true,
//clusterMaxZoom: 14, // Max zoom to cluster points on
//clusterRadius: 40 // Radius of each cluster when clustering points (defaults to 50)
});
map.addLayer({
id: "clusters",
type: "circle",
source: "hotels",
filter: ["has", "point_count"],
paint: {
// Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step)
// with three steps to implement three types of circles:
// * Blue, 20px circles when point count is less than 100
// * Yellow, 30px circles when point count is between 100 and 750
// * Pink, 40px circles when point count is greater than or equal to 750
//"circle-color": "blue",
"circle-radius": 0
}
});
// map.addLayer({
// id: "cluster-count",
// type: "symbol",
// source: "hotels",
// filter: ["has", "point_count"],
// layout: {
// "text-field": "{point_count_abbreviated}",
// "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
// "text-size": 0
// }
// });
map.addLayer({
id: "unclustered-point",
type: "circle",
source: "hotels",
filter: ["!=", "cluster", true],
paint: {
"circle-color": "red",
"circle-radius": 4,
"circle-stroke-width": 1,
"circle-stroke-color": "#fff"
}
});
// objects for caching and keeping track of HTML marker objects (for performance)
var markers = {};
var markersOnScreen = {};
var coordinates = {};
function updateMarkers() {
var newMarkers = {};
var features = map.querySourceFeatures('hotels');
// for every cluster on the screen, create an HTML marker for it (if we didn't yet),
// and add it to the map if it's not there already
for (var i = 0; i < features.length; i++) {
if (!features[i]) continue;
var coords = features[i].geometry.coordinates;
var props = features[i].properties;
if (!props.cluster) continue;
var id = props.cluster_id;
coordinates[id] = coords;
// console.log("check id", id)
var marker = markers[id];
if (!marker) {
/*** For cluster */
var el = document.createElement('div');
var features = map.queryRenderedFeatures({ layers: ['clusters'] });
if (!features[i]) continue;
var point_count = features[i].properties.point_count_abbreviated;
el.className = 'marker';
el.id = id;
el.style.backgroundImage = 'url("' + Marker + '")';
el.style.width = '41px';
el.style.height = '48px';
el.innerHTML = "<span style='width: 41px;line-height: 42px;text-align:center;display:block'>" + point_count + "</span>"
// console.log("check counts", point_count);
marker = markers[id] = new mapboxgl.Marker({ element: el }).setLngLat(coords);
el.addEventListener('click', () => {
var features = map.queryRenderedFeatures({ layers: ['clusters'] });
if (!features[0]) return;
var clusterId = features[0].properties.cluster_id;
map.getSource('hotels').getClusterExpansionZoom(clusterId, function (err, zoom) {
if (err)
return;
// map.easeTo({
// center: coordinates[el.id],
// zoom: zoom
// });
map.flyTo({
center: coordinates[el.id],
zoom: zoom,
speed: .75
});
//console.log("check coords", coordinates)
// for every marker we've added previously, remove those that are no longer visible
//map.off("zoom");
});
}
);
}
newMarkers[id] = marker;
if (!markersOnScreen[id])
marker.addTo(map);
}
//console.log("check coords", coordinates)
// for every marker we've added previously, remove those that are no longer visible
for (id in markersOnScreen) {
//console.log("in for loop", newMarkers[id])
if (!newMarkers[id])
markersOnScreen[id].remove();
}
markersOnScreen = newMarkers;
}
// after the GeoJSON data is loaded, update markers on the screen and do so on every map move/moveend
map.on('data', function (e) {
if (e.sourceId !== 'hotels' || !e.isSourceLoaded) return;
map.on('zoom', updateMarkers);
map.on('moveend', updateMarkers);
updateMarkers();
});
map.on('mouseenter', 'clusters', function () {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'clusters', function () {
map.getCanvas().style.cursor = '';
});
});`
See those red points in map? I need to change them with HTML div. Any help would be appreciated.
@smitraval I've simplified your code and it now works for custom markers and clusters. And at the end of the updateMarkers function, there's a simple loop that removes unused markers and clusters :)
map.addSource("addresses", {
type: "geojson",
data: geojson, //"https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
cluster: true,
clusterMaxZoom: 13, // Max zoom to cluster points on
clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
});
map.addLayer({
id: "clusters",
type: "circle",
source: "addresses",
filter: ["has", "point_count"],
paint: {
"circle-radius": 0
}
});
// Store IDs and cluster/marker HTMLElements
const markers = new Map();
function updateMarkers(){
const features = map.querySourceFeatures('addresses');
const keepMarkers = [];
for (let i = 0; i < features.length; i++) {
const coords = features[ i ].geometry.coordinates;
const props = features[ i ].properties;
const featureID = features[ i ].id;
const clusterID = props.cluster_id || null;
if (props.cluster && markers.has('cluster_'+clusterID)) {
//Cluster marker is already on screen
keepMarkers.push('cluster_'+clusterID);
} else if (props.cluster) {
//This feature is clustered, create an icon for it and use props.point_count for its count
var el = document.createElement('div');
el.className = 'mapCluster';
el.style.width = '60px';
el.style.height = '60px';
el.style.textAlign = 'center';
el.style.color = 'white';
el.style.background = '#16d3f9';
el.style.borderRadius = '50%';
el.innerText = props.point_count;
const marker = new mapboxgl.Marker(el).setLngLat(coords);
marker.addTo(map);
keepMarkers.push('cluster_'+featureID);
markers.set('cluster_'+clusterID,el);
} else if (markers.has(featureID)) {
//Feature marker is already on screen
keepMarkers.push(featureID);
} else {
//Feature is not clustered and has not been created, create an icon for it
const el = new Image();
el.style.backgroundImage = 'url(https://placekitten.com/g/50/50)';
el.className = 'mapMarker';
el.style.width = '50px';
el.style.height = '50px';
el.style.borderRadius = '50%';
el.dataset.type = props.type;
const marker = new mapboxgl.Marker(el).setLngLat(coords);
marker.addTo(map);
keepMarkers.push(featureID);
markers.set(featureID,el);
}
}
//Let's clean-up any old markers. Loop through all markers
markers.forEach((value,key,map) => {
//If marker exists but is not in the keep array
if (keepMarkers.indexOf(key) === -1) {
console.log('deleting key: '+key);
//Remove it from the page
value.remove();
//Remove it from markers map
map.delete(key);
}
});
};`
map.on('data', function (e) {
if (e.sourceId !== 'addresses' || !e.isSourceLoaded) return;
map.on('moveend', updateMarkers); // moveend also fires on zoomend
updateMarkers();
});
Console output showing markers and clusters getting removed. They are removed when they go off screen, but also when they switch from a marker to a cluster and vice versa.
I have just read this thread and if I understand it correctly it is simply not possible to easily create a cluster of HTML markers. You either need to add them as a GeoJSON layer and render them using images. Or you can add them to a map directly as markers but you need to manage the hiding logic manually. Am I right or am I missing something?
I create my markers purely by using HTML and CSS. I want to add them to a cluster which would deal with the hiding logic and show the number of hidden markers (just like Leaflet plugins do it) without me having to implement anything. Is it possible or not?
@NeonCreativeStudios, thanks for your efforts! While running your example code, I am seeing the clustering work beautifully, but the max zoom seems to be ignored, and it is not unclustering into individual markers when zooming far enough.
@mvtenney I am not sure if you encountered the same problem as me but unclustering was very unreliable in my application so I had to add the following line to onMoveEnd
callback:
map.once('idle', () => updateMarkers());
The only problem is that it waits not only for the zooming animation to end but also for map tiles loading so markers will appear after some time. However, AFAIK there is no animationend
event in Mapbox GL JS and so I find this to be the only reliable solution.
Most helpful comment
The new example with HTML clusters and property aggregation is live — check it out! Thanks to everyone who participated in the thread, and let me know if there's anything we can improve / clarify. https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/