new mapboxgl.Map({
...
antialias: true,
bearing: 20, //initial degree rotation
bearingSnap: 45, //max degree rotation before map snap to normal
clickTolerance: 20, //the pixels that need to be swept to get a drag before being a click
cooperativeGestures: true, //the zoom will need the crtl button but doesn't stops double click to zoom
doubleClickZoom: false, //to disable double-click zoom
dragPan: false, //will disable the drag move map
dragRotate: false, //won't allow rotate nor pitch 3D
interactive: false, //will disable any movement or zoom
maxZoom, minZoom, maxPitch, minPitch: //for limits (max) or immediate(min)
pitchWithRotate: false //will disable pitch
})
We use projection to change the view of the map:
projection: "mercator" will create a continous map
projection: "albers" will give us a conic projection
projection: "equalEarth" will have rounded border
For the Dom HTML map data we:
mappe.getContainer() //will get the ENTIRE HTML mapboxgl map
mappe.getCanvasContainer() //will only get the .mapboxgl-canvas-container, used for layer styling
map1.getCanvas() //will get the .mapboxgl-canvas
We can get or set the current map bond coordinates:
//we get an object with the North-East and South-West coordinates
mappe.getBounds()
mappe.getBounds()._sw.lng
mappe.getBounds()._ne.lat
//to set it we start with the SOUTH-WEST
mappe.setMaxBounds( [
[mappe.getBounds()._sw.lng, frap.getBounds()._sw.lat] ,
[mappe.getBounds()._ne.lng, frap.getBounds()._ne.lat] ]
)
//if we set maxBounds in map, if not we get null
mappe.getMaxBounds()
//or get/set the Projection type and center
mappe.getProjection()
mappe.setProjection({
name: 'globe',
center: [35, 55],
parallels: [20, 60] //lowers the distortions inside the 2 parallels
});
Then min/max zoom and pitch:
//to not allow zoom away/further from 0/24
mappe.setMinZoom(7.25)/ mappe.getMinZoom()
mappe.setMaxZoom(10.25)/ mappe.getMaxZoom()
//min/max pitch can go from 0/85
mappe.setMinPitch(10)/ mappe.getMinPitch()
mappe.setMaxPitch(80)/ mappe.getMaxPitch()
//or just, without blocking the zoom
mappe.setZoom(10)
We can add navigation controls with:
//we first create it, then we add it with its position
const navigato = new mapboxgl.NavigationControl()
mappe.addControl(navigato, "bottom-left");
//when mouse gets out
mappe.on("mouseout", ()=>{
console.log( "MOUSE fuori" )
})
//only once when mouse gets into map
mappe.on("mouseover", ()=>{
console.log("MOUSE dentro")
})
//each frame of movement IN the map
mappe.on("mousemove" , ()=>{
console.log("MOUSE mosso")
})
We also have event listeners for when the map is dragged/moved:
//at the start/middle/end of the map movement
mappe.on("movestart", ()=>{
console.log("MAPPA comincia mossa")
})
mappe.on("move", ()=>{
console.log("MAPPA mossa")
})
mappe.on("moveend", ()=>{
console.log("MOUSE smesso mossa")
})
For the Event to trigger on the Layer elements we:
//we use the Layer ID, to include more layers we ["places", "more"]
mappe.on("mouseover", "places", ()=>{
console.log("MOUSE dentro")
})
We can start from the 2 phases on the click:
mappe.on("mousedown", ()=>{
console.log("mouse got down")
})
//like the mouseDown we can use the preclick
mappe.on("preclick", ()=>{
console.log("IS tis the first one")
})
mappe.on("mouseup", ()=>{
console.log("mouse went up")
})
We also have double click and right click:
mappe.on("dblclick", ()=>{
console.log("possible zoom")
})
//for context menu if right click on it
mappe.on("contextmenu", ()=>{
console.log("inspect button")
})
For each scroll of the mouse wheel:
mappe.on("wheel", ()=>{
console.log("la rotelina")
})
mappe.on("zoom",()=>{
console.log("on double click or control zoom")
})
we can also have events for data/style loads:
mappe.on("data", ()=>{
console.log("DATA somewhere")
})
mappe.on("styledata", ()=>{
console.log("lo stile lo abbiamo qui")
})
we can use it for Style:
mappe.getStyle()
mappe.setStyle("mapbox://styles/mapbox/streets-v11")
//will be false if triggered while the ("load") still not done
mappe.isStyleLoaded()
These will work for different Instances:
mappe.on("move", ()=>{
if( mappe.isMoving() ){
console.log("will work on Pitch, Zoom and rotate")
}
})
mappe.on("zoom", ()=>{
if( mappe.isZooming() ){
console.log("limited to zoom")
}
})
mappe.on("rotate", ()=>{
if( mappe.isRotating() ){
console.log("limited to Rotate")
}
})
We can then use its event features[0].properties:
//on click on map we can have map coordinates and canvas point position
mappe.on("click", (e)=>{
console.log(e.point) //{x: 572, y: 233}
console.log(e.lngLat+ e.lngLat.lng+ e.lngLat.lat) //{lng: -92.0058, lat: 45.0721}
})
Querying features of Mapbox layers
We can use queryRenderedFeatures() to get an array of GeoJSON Feature objectsfrom the layer we are on, using visible features that satisfy the query parameters (geometry):
Filter feature properties of a clicked query Point
mappe.on('click', (e)=>{
let band= mappe.queryRenderedFeatures(e.point, {
layers: ["circle"]
})
//we create an array of filter properties, like the source used for the layer
const limited = [
'properties',
'source',
'layer'
];
const finito = band.map((feat) => {
const tutto = {};
//we create limited properties to the new object, using the original[prop]
limited.forEach((prop) => {
tutto[prop] = feat[prop];
});
return tutto;
})
//we get returned an object that we need to stringify
console.log( JSON.stringify( finito, null, 2 ) )
//the string will have 2 empty spaces
})
On click() we filter the query specific features
We can include the Query Features in the event listener:
// this is the same as
// band= mappe.queryRenderedFeatures(e.point, { layers: ["state-fills"] })
frap.on("mousemove", "state-fills", (e)=>{
//and the e argument works only if the select layer is listened to
e.features[0].properties.STATE_NAME == band[0].properties.STATE_NAME
console.log( e.lngLat ) //will get the coordinates in the layer
console.log( e.point ) //will get the position IN THE MAP X/Y width/height
})
Coordinates and Pont position
We can also change the cursor if in a Layer using getCanvas():
//we can use feature.length or just E for the if statement
mappe.getCanvas().style.cursor = features.length ? 'pointer' : '';
An example with query features and layers:
Hover on Layer event
Paint layers and Hover query feature events
On load we:
//we need a variable for the current hovered
let hoveredStateId = null;
mappe.on('load', () => {
frap.addSource('stati', {
'type': 'geojson',
'data': 'https://docs.mapbox.com/mapbox-gl-js/assets/us_states.geojson'
});
//we separate line BORDERS from FILL
frap.addLayer({
'id': 'state-borders',
'type': 'line',
'source': 'stati',
'layout': {},
'paint': {
'line-color': '#c80815',
'line-width': 2
}
});
//because of the feature-state dependent fill-opacity expression
frap.addLayer({
'id': 'state-fills',
'type': 'fill',
'source': 'stati',
'layout': {},
'paint': {
'fill-color': '#627BC1',
'fill-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
1,
0.5
]
}
});
frap.on("mousemove", "state-fills", (e)=>{
//to update the hover effect on event
if (hoveredStateId ) {
frap.setFeatureState(
{ source: 'stati', id: hoveredStateId },
{ hover: false }
);
}
//we set ID on current hover ID
hoveredStateId = e.features[0].id;
//we set the current ID as the hover paint effect
frap.setFeatureState(
{ source: 'stati', id: hoveredStateId },
{ hover: true }
);
//we can also add Popups and stuff here
})
//we also add event for when out of the query
frap.on('mouseleave', 'state-fills', () => {
frap.getCanvas().style.cursor = '';
if (hoveredStateId !== null) {
frap.setFeatureState(
{ source: 'stati', id: hoveredStateId },
{ hover: false }
);
}
hoveredStateId = null;
pop.remove()
});
})
We can also mix queryRenderedFeatures() and Layers:
map6.on("load", (e)=>{
let canvas= map6.getCanvasContainer();
let start;
let current;
let box;
canvas.addEventListener('mousedown', mouseDown)
//on click down AND shift we start getting the position of the box
function mouseDown(e) {
if (!(e.shiftKey && e.button === 0)) return;
map6.dragPan.disable();
//we disable the drag to keep the map still and get start position mousePos()
start = mousePos(e);
//both to update and finish the box
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
function mousePos(e){
const rect = canvas.getBoundingClientRect();
//on canvas, clientXY distance from top-left 0/0 we return width/height box mapbox Object
return new mapboxgl.Point(
e.clientX - rect.left - canvas.clientLeft,
e.clientY - rect.top - canvas.clientTop
);
}
//on mousemove we have
function onMouseMove(e) {
//current moving XY canvas coordinates
current = mousePos(e);
// Append the box element if not yet
if (!box) {
box = document.createElement('div');
box.classList.add('boxdraw');
canvas.appendChild(box);
}
const minX = Math.min(start.x, current.x),
maxX = Math.max(start.x, current.x),
minY = Math.min(start.y, current.y),
maxY = Math.max(start.y, current.y);
//we translate the 0/0 boxdraw with the minXY and its width/height with maxXY
const pos = `translate(${minX}px, ${minY}px)`;
box.style.transform = pos;
box.style.width = maxX - minX + 'px';
box.style.height = maxY - minY + 'px';
}
//we create the diagonal query with the start and ending point position on canvas
function onMouseUp(e) {
finish( [start, mousePos(e)] );
}
//we remove the box events on mouse, so we dont stretch the box
function finish(bbox) {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
map6.dragPan.enable();
}
})
//being bbox 2 X/Y mapboxgl point objects forming the diagonal of a square
function finish(bbox) {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
if (box) {
//mapboxgl-canvas-container is parent of boxdraw, like mapboxgl-canvas
//we remove if present to just get the hover layer
box.parentNode.removeChild(box);
box = null;
}
if (bbox){
//we do the same as with points
const features = map3.queryRenderedFeatures(bbox, {
layers: ['counties']
});
//if number selected within the box exceeds
if (features.length >= 1000) {
return window.alert('Select a smaller number of features');
}
//between the selected on the layer we return the FIPS and use them in the filter
const fips = features.map((feature) => feature.properties.FIPS);
map3.setFilter('counties-highlighted', ['in', 'FIPS', ...fips]);
}
}
Instead of box on drag we use 2 click boxes
Custom 2 points box for query feature events
We will need 2 arrays, one for the markers on coordinates and another for the canvas points:
// Some code
map5.on("load", (e)=>{
let canvas= map5.getCanvasContainer();
let box;
let markers= []
let points= []
let start;
let endi;
map2.on("click", (e)=>{
//if we already have 2 points we delete the box and points
if( points.length== 2 ){
points= []
for( x of markers){
x.remove()
}
box.parentNode.removeChild(box);
box = null;
}
points.push( e.point )
let uno= new mapboxgl.Marker(pick)
.setLngLat( e.lngLat )
.addTo(map2)
markers.push( uno )
if( points.length == 2 ){
//when 2 points we use canvas for the box
start= points[0]
endi= points[1]
box = document.createElement('div');
box.classList.add('boxdraw');
canvas.appendChild(box);
const minX = Math.min(start.x, endi.x)
maxX = Math.max(start.x, endi.x),
minY = Math.min(start.y, endi.y),
maxY = Math.max(start.y, endi.y);
const pos = `translate(${minX}px, ${minY}px)`;
box.style.transform = pos;
box.style.width = maxX - minX + 'px';
box.style.height = maxY - minY + 'px';
finish([start, endi])
}
})
map2.on("move", (e)=>{
//after the box or points, any move will clear them
if(points.length> 0 ){
points= []
for( x of markers ){
x.remove()
}
if(box){
box.parentNode.removeChild(box);
box = null;
}
}
})
//In this example we used a done style with its layer pointarea
function finish(bbox){
const features = map2.queryRenderedFeatures(bbox, {
layers: ['pointarea']
});
console.log( features )
}
QuerySourceFeatures() returns an array of GeoJSON Feature objects from its source that satisfy the query parameter:
Instead of using an area in Features we use the source to filter
Filter features containing using its data
The source is similar but we use a different property:
map3.addSource('counties', {
'type': 'vector',
'url': 'mapbox://mapbox.82pkq93d'
});
map3.addLayer(
{
'id': 'counties',
'type': 'fill',
'source': 'counties',
'source-layer': 'original',
'paint': {
'fill-outline-color': 'rgba(0,0,0,0.1)',
'fill-color': 'rgba(0,0,0,0.1)'
}
},
// Place this layer under labels, roads and buildings.
'building'
);
map3.addLayer(
{
'id': 'counties-highlighted',
'type': 'fill',
'source': 'counties',
'source-layer': 'original',
'paint': {
'fill-outline-color': '#aa4896',
'fill-color': '#c80015',
'fill-opacity': 0.75
},
// Display none by adding a
// filter with an empty string.
'filter': ['in', 'COUNTY', '']
},
'building'
);
We use querySourceFeatures() on the layer:
//to get the filter effect we just need
map3.on('mousemove', 'counties', (e) => {
const feature = e.features[0];
map3.setFilter('counties-highlighted', [
'in',
'COUNTY',
feature.properties.COUNTY
]);
//To get the actual data from the filtered we need
const relatedCounties = map3.querySourceFeatures('counties', {
sourceLayer: 'original',
filter: ['in', 'COUNTY', feature.properties.COUNTY]
});
//we select complete features based on the selected property
//querySourceFeatures can return repeated features so we can filter them
const uniqueCounties = getUniqueFeatures(relatedCounties, 'FIPS');
//we can sum the values of the filtered properties
const populationSum = uniqueCounties.reduce((memo, feature) => {
return memo + feature.properties.population;
}, 0);
})
We use set() to not repeat features on getUniqueFeatures():
//we return a set of features
function getUniqueFeatures(features, comparatorProperty) {
const uniqueIds = new Set();
const uniqueFeatures = [];
for (const feature of features) {
const id = feature.properties[comparatorProperty];
if (!uniqueIds.has(id)) {
uniqueIds.add(id);
uniqueFeatures.push(feature);
}
}
return uniqueFeatures;
}
We can also create a 3D terrain map using mapbox-dem:
we can also exagerate the heigths
var map4 = new mapboxgl.Map({
...
pitch: 80,
bearing: 41,
style: 'mapbox://styles/mapbox/satellite-streets-v12'
})
map4.on('load', () => {
map4.addSource('mapbox-dem', {
'type': 'raster-dem',
'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
'tileSize': 512,
'maxzoom': 14
});
//exaggeration from 1 to 1000 and not on buildings
map4.setTerrain({
'source': 'mapbox-dem',
'exaggeration': 2
});
});