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)
filterMarkersyou are referring to check boxes input type, not radio button.