Recently, I had a chance to play with Mapbox. I helped playmaps to sort out some issues with icons. At that time, I have spent some time to select an icon from a click. However, haven’t find any good example at that point. For this reason, I have decided to share the way how I manage to achieve selcting an icon from a click.
Table of Contents
Mapbox
If you are reading this, I believe you are already aware of what Mapbox is. And, Mapbox GL JS is one of the client libraries to use Mapbox
Mapbox GL JS is a JavaScript library that uses WebGL to render interactive maps from vector tiles and Mapbox styles.
Mapbox GL JS API Reference
Here, you’ll find many examples to start with Mapbox GL JS api
Playmaps
Playmaps® is the first children’s brand in the world to create road map play-mats in one of the healthiest and most sustainable material ever. You can explore your neighbourhood as your mat. Type in your address, Drag & Drop icons of your favourite places, to boost the sense of belonging. PlayMaps® gives that special feeling of playing on a unique product made especially for you. Basically, you can create your personalized playmaps mat. Meanwhile, Playmaps is using Mapbox under the hood.
Select an Icon
Icons will appear as symbols in a map based on whatever icons have been set in Mapbox studio. To select one of those icon, I am taking following few steps
- Get the map canvas
- Attach an event listener to canvas mouse-move event
- The event handler will
queryRenderedFeatures
based on X and Y axis of the mouse- Get all symbols in the map based on returned features from the above query
- The last one in the array will be considered as a selected icon
- Clicked the selected symbols
The following code will allow to click on an icon and it’ll show the text in the popup.
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Select icons in a map</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.3.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.3.0/mapbox-gl.css' rel='stylesheet' />
<style>
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id='map'></div>
<script>
function initPage(mapboxgl) {
var isDraggable = false;
var selectedSymbol = {};
mapboxgl.accessToken = 'pk.eyJ1IjoicGxheW1hcHMiLCJhIjoiY2pxdW91Mmt3MGRiajQ0bXF0bjNkZGlzNiJ9.D8WVzHCcxCgZYCkB7M-XWQ';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-65.017, -16.457],
zoom: 7
});
let findInCanvas = (e) => {
var layerX = e.layerX;
var layerY = e.layerY;
var features = map.queryRenderedFeatures([layerX, layerY], {});
if (features && features.length > 0) {
var symbols = getSymbolsWithPoint(features);
if (symbols.length > 0) {
if (selectedSymbol !== symbols[(symbols.length - 1)]) {
isDraggable = true;
if (canvas.style.cursor == '' || canvas.style.cursor == 'auto') {
canvas.style.cursor = 'pointer';
}
selectedSymbol = symbols[(symbols.length - 1)];
map.on('click', symbolSelected);
}
}
else
clearSelectedSymbol();
}
else
clearSelectedSymbol();
};
let clearSelectedSymbol = () => {
if (isDraggable) {
isDraggable = false;
selectedSymbol = null;
canvas.style.cursor = 'auto';
map.off('click', symbolSelected);
}
};
let symbolSelected = (e) => {
if (selectedSymbol == null) {
return;
}
map.off('click', symbolSelected);
canvas.removeEventListener('mousemove', findInCanvas);
addPopup(selectedSymbol);
selectedSymbol = null;
map.on('click', symbolSelected);
canvas.addEventListener('mousemove', findInCanvas);
}
let createSource = (feature) => {
return {
type: "geojson",
data: feature
}
}
let createLayer = (sourceName, feature) => {
return {
"id": sourceName,
"source": sourceName,
"type": "symbol",
"layout": feature.layout
};
}
function getSymbolsWithPoint(array) {
var symbols = [];
for (var i = array.length - 1; i > - 1; i--) {
if (array[i].layer.type === "symbol"
&& (array[i].geometry.type === "Point"
|| array[i].geometry.type === "Polygon"))
symbols.push(array[i]);
}
return symbols;
}
function addPopup(selectedFeature) {
var pointId = "point" + selectedFeature.id + "." + Math.random() * 10;
if (!map.getSource(pointId)) {
map.addSource(pointId, createSource(selectedFeature));
map.addLayer(createLayer(pointId, selectedFeature.layer));
map.on('click', pointId, (e) => {
var popup = new mapboxgl.Popup({ closeButton: true })
.setLngLat(e.lngLat)
.setText(selectedFeature.layer["source-layer"] + " layer")
.addTo(map);
if (e.originalEvent.stopPropagation) {
e.originalEvent.stopPropagation();
}
e.preventDefault();
return false;
});
}
}
var canvas = map.getCanvas();
canvas.addEventListener('mousemove', findInCanvas);
};
initPage(mapboxgl);
</script>
</body>
</html>
I would like to explain a few methods from the above example, findInCanvas
, getSymbolsWithPoint
and addPopup
.
findInCanvas
has been attached to mousemove
event of the map canvas. Whenever mouse point to any icon, this listener retrieves the feature. The getSymbolsWithPoint
method filter and return the symbol mouse is pointing to. addPopup
method add a new geojson source and a layer into the map. This method also adds a mapboxgl.Popup
Please check the JSFiddle here for full sample. I would love to hear your feedback. That’s all for today