I have an interactive map in which I display several hundred points.
The map is based on the tutorial "stores locator" and "Create and style clusters" from Mapbox or MapLibreGl.
I managed to create filters to filter by feature, in this case, the region. When a region is clicked, the corresponding markers are displayed. It works well. I first thought of checkboxes, but I think that complicates the use cases. So this time I'm trying with radio buttons.
Here is a sample of my code:
var map = new maplibregl.Map({
container: 'map', // container id
style: {
'version': 8,
'sources': {
'raster-tiles': {
'type': 'raster',
'tiles': [
"https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png",
"https://b.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png",
"https://c.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png",
"https://d.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png"
],
'tileSize': 256,
'attribution':
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
}
},
"glyphs": "https://fonts.openmaptiles.org/{fontstack}/{range}.pbf",
'layers': [
{
'id': 'simple-tiles',
'type': 'raster',
'source': 'raster-tiles',
'minzoom': 0,
'maxzoom': 22
}
]
},
center: [2.3514998, 48.85661], // starting position
zoom: 5, // starting zoom
cooperativeGestures: true
});
var stores =
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
3.2803257,
45.5400916
]
},
"properties": {
"category": "Maisons sports et santé",
"name": "Sport Santé Plus",
"region": "Region one",
"address": "16 Route d'Issoire, 63500, Parentignat",
"phone": "Tel : 06 88 91 08 49",
"email": "email : [email protected]",
"website": "<a target='_blank' href='https://url.de.m.mimecastprotect.com/s/Z3NrCoZ34XsZ4V2vWF1fgCrWPXS?domain=sportsanteplus.com'>Site web</a>"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
3.2803257,
45.5400916
]
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
5.4919297,
47.079275
]
},
"properties": {
"category": "Maison sport et santé",
"name": "Maison Sport Santé Sociale Grad Dole",
"region": "Region two",
"address": "65 Rue de Crissey 39100 Dole",
"phone": "Tel : 07 82 09 22 73",
"email": "email : [email protected]",
"website": ""
}
}
]
};
stores.features.forEach((store, i) => {
store.properties.id = i;
});
map.on('load', () => {
map.addSource('stores', {
'type': 'geojson',
'data': stores,
'cluster': true,
'clusterMaxZoom': 14, // Max zoom to cluster points on
'clusterRadius': 50
});
// addMarkers();
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'stores',
filter: ['has', 'point_count'],
paint: {
'circle-color': [
'step',
['get', 'point_count'],
'#525CA3',
100,
'#525CA3',
750,
'#525CA3'
],
'circle-radius': [
'step',
['get', 'point_count'],
20,
100,
30,
750,
40
]
}
});
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'stores',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['Open Sans Bold'],
'text-size': 12,
"icon-image": "{marker-symbol}",
"icon-size": 0.20
},
paint: {
"text-color": "#ffffff"
}
});
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'stores',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#525CA3',
'circle-radius': 15,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff'
}
});
buildLocationList(stores);
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['clusters']
});
const clusterId = features[0].properties.cluster_id;
map.getSource('stores').getClusterExpansionZoom(
clusterId,
(err, zoom) => {
if (err) return;
map.easeTo({
center: features[0].geometry.coordinates,
zoom: zoom
});
}
);
});
map.on('click', 'unclustered-point', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const category = e.features[0].properties.category;
const region = e.features[0].properties.region;
const name = e.features[0].properties.name;
const address = e.features[0].properties.address;
const phone = e.features[0].properties.phone;
const email = e.features[0].properties.email;
const website = e.features[0].properties.website;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
for (const marker of stores.features) {
const activeItem = document.getElementsByClassName('active');
// e.stopPropagation();
if (activeItem[0]) {
activeItem[0].classList.remove('active');
}
const listing = document.getElementById(
`listing-${e.features[0].properties.id}`
);
listing.classList.add('active');
listing.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
};
new maplibregl.Popup({ closeOnClick: true })
.setLngLat(coordinates)
.setHTML(`<h3>${category}</h3><h4>${name}</h4><p style="margin:0">${region}</p><p style="margin:0">${address}</p><p style="margin:0">${phone}</p><p style="margin:0">${email}</p><p style="margin:0">${website}</p>`)
.addTo(map);
});
map.on('mouseenter', 'clusters', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'clusters', () => {
map.getCanvas().style.cursor = '';
});
});
function buildLocationList(stores) {
for (const store of stores.features) {
const listings = document.getElementById('listings');
const listing = listings.appendChild(document.createElement('div'));
listing.id = `listing-${store.properties.id}`;
listing.className = 'item';
const link = listing.appendChild(document.createElement('a'));
link.href = 'javascript:void(0)';
link.className = 'title';
link.id = `link-${store.properties.id}`;
link.innerHTML = `<h3 style="margin:0 0 10px">${store.properties.name}</h3>`;
const details = listing.appendChild(document.createElement('div'));
if(store.properties.category) {
details.innerHTML += `<h4 style="margin:0 0 10px">${store.properties.category }</h4>`
}
if(store.properties.region) {
details.innerHTML += `<p style="margin:0">${store.properties.region }</p>`
}
if(store.properties.address) {
details.innerHTML += `<p style="margin:0">${store.properties.address }</p>`
}
if(store.properties.phone) {
details.innerHTML += `<p style="margin:0">${store.properties.phone }</p>`
}
if(store.properties.email) {
details.innerHTML += `<p style="margin:0">${store.properties.email }</p>`
}
if(store.properties.website) {
details.innerHTML += `<p style="margin:0">${store.properties.website }</p>`
}
link.addEventListener('click', function () {
for (const feature of stores.features) {
if (this.id === `link-${feature.properties.id}`) {
flyToStore(feature);
createPopUp(feature);
}
}
const activeItem = document.getElementsByClassName('active');
if (activeItem[0]) {
activeItem[0].classList.remove('active');
}
this.parentNode.classList.add('active');
});
}
}
function flyToStore(currentFeature) {
map.flyTo({
center: currentFeature.geometry.coordinates,
zoom: 15
});
}
function createPopUp(currentFeature) {
const popUps = document.getElementsByClassName('maplibregl-popup');
if (popUps[0]) popUps[0].remove();
const popup = new maplibregl.Popup({ closeOnClick: true })
.setLngLat(currentFeature.geometry.coordinates)
.setHTML(
`'<h3>${category}</h3><h4>${name}</h4><p style="margin:0">${region}</p><p style="margin:0">${address}</p><p style="margin:0">${phone}</p><p style="margin:0">${email}</p><p style="margin:0">${website}</p>`
)
.addTo(map);
}
const nav = new maplibregl.NavigationControl({
visualizePitch: true
});
map.addControl(nav, 'bottom-right');
function getLabel(id) {
return $("#"+id).next("label").html();
}
function getSelectedRegions() {
const selectedRegions = [];
const regionRadios = document.querySelectorAll('input[type=radio]:checked');
regionRadios.forEach(radio => selectedRegions.push(getLabel(radio.id)));
return selectedRegions;
}
function filterMarkers() {
const selectedRegions = getSelectedRegions();
const mapSource = map.getSource('stores');
if (selectedRegions.length === 0) {
setAllMarkersVisible(mapSource); // Ensure to implement this function
return;
}
mapSource.setData({
...stores,
features: stores.features.filter(feature => selectedRegions.includes(feature.properties.region))
});
}
function setAllMarkersVisible(mapSource) {
mapSource.setData(stores);
}
And in my html :
<div>
<div class="sidebar">
<div class="heading font-body text-title-4 font-title text-primary1">
<p>Hospitals</p>
</div>
<div id="listings" class="listings font-body"></div>
</div>
<div id="map" class="map pad2"></div>
<div class="map-overlay top">
<div class="map-overlay-inner">
<nav id="nav-filter">
<fieldset>
<legend>Région</legend>
<div>
<input id="all" type="radio" name="radio"/>
<label for="all">All</label>
</div>
<div>
<input id="region1" type="radio" name="radio" onclick="filterMarkers()"/>
<label for="region1">Region one</label>
</div>
<div>
<input id="region2" type="radio" name="radio" onclick="filterMarkers()"/>
<label for="region2">Region two</label>
</div>
<div>
<input id="region3" type="radio" onclick="filterMarkers()"/>
<label for="region3">Region three</label>
</div>
</fieldset>
</nav>
</div>
</div>
</div>
When clicking on the radio buttons, with the flyTo function, I recenter the map on the region concerned and display the markers of this region only.
document.getElementById('region one').addEventListener('click', () => {
map.flyTo({
center: [5.041869163513184, 47.32464599609375],
zoom: 6
});
})
document.getElementById('region two').addEventListener('click', () => {
map.flyTo({
center: [4.6604809, 45.2968119],
zoom: 6
});
})
Although the code is redundant, as I have about ten regions, it works. My goal is that when I click on the "all" button, the map returns to its initial position and displays all the markers, just like when the page loads.
As the click-on function, filterMarkers()
allows you to filter the markers, I thought about adding on the input "all" : setAllMarkersVisible()
, but it doesn't work. I also can't find a way to display all the markers via a function.
Is there a simple way, through the different functions present, or with additional code, to reset the map ? (restore all markers and fitBounds)
filterMarkers
you are referring to check boxes input type, not radio button.