Clustering custom html markers with mapbox-gl-js - mapbox

I'm using the mapbox-gl-js API and I'm using it with react to create some custom markers as follows:
let div = document.createElement('div');
let marker = new mapboxgl.Marker(div, {
offset: [ -20, 80 ]
});
marker.setLngLat(person.geometry.coordinates);
render(
<MapPersonIcon />,
div,
() => {
marker.addTo(map);
}
);
This worked great. However I would now like to cluster these markers, producing the same affect as the functionality found with layers i.e.
https://www.mapbox.com/mapbox-gl-js/example/cluster/
Does anyone know whether this is possible (hopefully with custom clusters too) or whether it will be available in an upcoming release?

This feature is now in Mapbox GL js - https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/
Key takeaways:
When setting your data source using map.addSource, make sure you define cluster: true and clusterRadius: int, like so:
map.addSource( 'sourceName', {
type: "geojson",
data: {
type: 'FeatureCollection',
features: [JSON]
},
cluster: true,
clusterRadius: 80,
});
That will push mapbox to cluster your icons, but you need to tell mapbox what to do when it clusters those icons:
map.on( 'moveend', updateMarkers ); // moveend also considers zoomend
The business (trimmed down for relevance):
function updateMarkers(){
var features = map.querySourceFeatures( 'sourceName' );
for ( var i = 0; i < features.length; i++ ) {
var coords = features[ i ].geometry.coordinates;
var props = features[ i ].properties;
if ( props.cluster ){ // this property is only present when the feature is clustered
// generate your clustered icon using props.point_count
var el = document.createElement( 'div' );
el.classList.add( 'mapCluster' );
el.innerText = props.point_count;
marker = new mapboxgl.Marker( { element: el } ).setLngLat( coords );
} else { // feature is not clustered, create an icon for it
var el = new Image();
el.src = 'icon.png';
el.classList.add( 'mapMarker' );
el.dataset.type = props.type; // you can use custom data if you have assigned it in the GeoJSON data
marker = new mapboxgl.Marker( { element: el } ).setLngLat( coords );
}
marker.addTo( map );
}
NOTE: Don't copy paste this code, rather use it in conjunction with https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/ to get the whole picture. Hope this helps!

Answering own question:
At current it seems that this isn't possible as per mapbox's github:
If you would like to cluster your markers you will need to use mapbox's native maki icons (please see above example picture & URL) until a plugin is available for your custom HTML markers.

Related

Mapbox GL JS with Maki icons by Marker

I generated a list of Maki Icons I want to use via the original icon editor.
drawMarkers() {
let self = this;
const mapboxgl = require("mapbox-gl");
let data = this.draw.getAll();
data.features.forEach((feature) => {
if (feature.geometry.type == "Point") {
var icon = feature.properties.icon;
var title = feature.properties.title;
if (typeof title === "undefined") {
title = "Info";
} else {
var popup = new mapboxgl.Popup({ offset: 25 }) // add popups
.setHTML(`<h3>${title}</h3>`);
var marker = new mapboxgl.Marker({
color: '#333',
draggable: false,
scale: 1,
})
.setLngLat(feature.geometry.coordinates)
.setPopup(popup)
.addTo(self.map);
}
});
The Markers are showed correctly on the Mapbox.
The GeoJSON is like this:
"type":"FeatureCollection",
"features":[
{
"id":"c749de6a6eac6b1cfdda890e7c665e0d",
"type":"Feature",
"properties":{
"icon":"ferry",
"title":"This should show a Ferry icon",
"portColor":"#d9eb37"
},
"geometry":{
"coordinates":[
6.12,
22.44
],
"type":"Point"
}
},
I want the Maki Icons also added in the Marker, but I cannot find any documentation of how icons can be used inside the Mapbox Marker.
Who can help me out? I'm using the Mapbox GL JS for Web.
It depends on how you've created your map. You can either:
Create your own map style using the Maki icons you've generated. This is done using the Mapbox studio to create your custom map style, then adding it to your application.
Create custom markers that use the maki .svg files you've created. This can be done by passing a custom element to the new mapboxgl.Marker() function. So, instead of:
var marker = new mapboxgl.Marker({
color: '#333',
draggable: false,
scale: 1,
})
you would pass:
var marker = new mapboxgl.Marker(customElement)
where customElement uses the data from your icons variable.
I'm not sure if you're using plain JS here, but there's some examples on the mapbox docs of ways you can do this.
Because you've generated your own list of Maki icons, I'd suggest downloading them and maybe host them somewhere so that you can get away with creating your markers with <img src="link to hosted maki marker"/> or something of the sort

Here Maps not showing in Ionic tabs

I have a quite basic Here Map in an Ionic tap that is loaded in a Ionic tab with this JavaScript.
var platform = new H.service.Platform({
useCIT: true,
'app_id': $(component).data('appid'),
'app_code': $(component).data('appcode'),
useHTTPS: true
});
// Obtain the default map types from the platform object
var maptypes = platform.createDefaultLayers();
// Instantiate (and display) a map object:
var map = new H.Map(
document.getElementById('mapContainer'),
maptypes.normal.map,
{
zoom: 10,
center: { lng: $(component).data('long'),
lat: $(component).data('lat')
}
});
// Enable the event system on the map instance:
var mapEvents = new H.mapevents.MapEvents(map);
// Add event listeners:
map.addEventListener('tap', function(evt) {
// Log 'tap' and 'mouse' events:
console.log(evt.type, evt.currentPointer.type);
When adding this not in tab 1 the map is not showing. I tried and searched for several things but they only are for Google Maps. How can i get this working in the Ionic tabs?
Please try with following code snippet :
function moveMapToBerlin(map){
map.setCenter({lat:52.5159, lng:13.3777});
map.setZoom(14);
}
/**
* Boilerplate map initialization code starts below:
*/
//Step 1: initialize communication with the platform
// In your own code, replace variable window.apikey with your own apikey
var platform = new H.service.Platform({
apikey: window.apikey
});
var defaultLayers = platform.createDefaultLayers();
//Step 2: initialize a map - this map is centered over Europe
var map = new H.Map(document.getElementById('map'),
defaultLayers.vector.normal.map,{
center: {lat:50, lng:5},
zoom: 4,
pixelRatio: window.devicePixelRatio || 1
});
// add a resize listener to make sure that the map occupies the whole container
window.addEventListener('resize', () => map.getViewPort().resize());
//Step 3: make the map interactive
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Create the default UI components
var ui = H.ui.UI.createDefault(map, defaultLayers);
// Now use the map as required...
window.onload = function () {
moveMapToBerlin(map);
}

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 - Add a clusterGroup clickable with Layer Control

I'm still learning and I'm a bit stuck. I may be trying to do to much at once. I have a MapBox map working great with a clickable layer menu taken from examples on the MapBox site. I also have a MarkerClusterGroup which also works and is always visible on the map. Is there a way I could somehow have the MarkerClusterGroup clickable on/off just like layers identified in var overlays = { ...
Below is the code that I think needs the help:
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
Markers: L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
var clusterGroup = new L.MarkerClusterGroup();
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup);
});
Could be something as simple as misuse of brackets. Thanks in advance.
You have to include your Marker Cluster Group in your overlays object. For example you could instantiate it just before defining overlays, even if your Cluster Group is empty for now.
Then you fill it once it has downloaded its data.
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var clusterGroup = L.markerClusterGroup();
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
Markers: clusterGroup
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup); // use that line if you want to automatically add the cluster group to the map once it has downloaded its data.
});

Cannot set style on leaflet L.geoJSON layer

I am having a bit of trouble setting the style on a L.geoJSON based layer,my code looks like this:
var addProperties = function addProperties(prop,map)
{
//the API does not seem to support adding properties to an existing feature,
//the idea here is simple:
//(1) currentFeature.toGeoJson() needs to be called to obtain a json representation
//(2) set the properties on the geojson
//(3) create a new feature based on the geojson
//(4) remove res and add the new feature as res
var style = function style(feature){
var markerStyle = {
draggable: 'true',
icon: L.AwesomeMarkers.icon({
icon: 'link',
prefix: 'glyphicon',
markerColor: 'red',
spin: true
})
};
if(feature.geometry.type==='Point')
return markerStyle;
};
var onEachFeature = function onEachFeature(feature,layer){
console.log("Inside on each feature,checking to see if feature was passed to it ",feature);
layer.on('click',function(e){
//open display sidebar
console.log("Checking to see if setupTabs exists ",setupTabs);
setupTabs('#display-feature-tabs');
console.log("Checking to see if featureInfo exists ",featureInfo);
var featureInfoAPI =featureInfo('feature-properties');
featureInfoAPI.swap(feature.properties);
setTimeout(function() {
sidebar.show();
}, 100);
});
};
console.log("Inside add properties");
var geoJSON,feature;
if(res != null)
{
geoJSON = res.toGeoJSON();
geoJSON.properties = prop;
console.log(geoJSON);
feature = L.geoJson(geoJSON,{style:style,onEachFeature:onEachFeature});
console.log("The new feature that has been created ",feature);
removeFeature(map);
addFeature(feature);
feature.addTo(map);
}
};
I have also tried the style method as well,I am looking to add styles to the active layer for points(styles will also be added for lines and polylines by type).
To style point features, you should use pointToLayer option instead.