Search code examples
filtermapboxmaplibre-gl

MapLibreGL how to filter by features?


I'm looking for a helper to add filters to my map. I specify that I use MapLibreGL for my map but also base myself on Mapbox tutorials since they are applicable

I have managed to create a kind of store locator, with markers grouped by cluster.

So I have a mix of this tutorial and this one, and the result is okay.

I have the list of all my markers on the left, when I click on a list element, the map zooms in to the corresponding marker and displays a pop-up. And when I click on a marker this time, the list scrolls quickly to the corresponding item.

My map displays establishments with names, addresses, regions, telephone numbers, emails, etc. I would now like to create a filter block (checkbox) to allow me to filter by the "region" feature. There are about ten regions in total. The goal is that when clicking on a region, only the corresponding markers are displayed

I read this page dealing with filters, but I don't see how to implement it at all.

The pages "Filter symbols by toggling a list" and "filter-within-layer/" don't help me either.

I'm a bit of a beginner, in the sense that I have trouble appropriating the syntax and applying it to my code

As I followed the mapBox cluster tutorial, I already have three layers with filter: ['has', 'point_count'], so I don't understand how to add filters on "region" feature, as described here, in a similar question

   <div>
      <input id="region1" type="checkbox" />
      <label for="region1">Region One</label>
    </div>
    <div>
      <input id="region2" type="checkbox" />
      <label for="region2">Region 2</label>
    </div>
    function getSelectedRegions() {
     const selectedRegions = [];
     const regionCheckboxes = 
     document.querySelectorAll('input[type=checkbox]:checked');
      regionCheckboxes.forEach(checkbox => 
      selectedRegions.push(checkbox.id));
      return selectedRegions;
    }
 
    function filterMarkers() {
     const selectedRegions = getSelectedRegions();
     const mapSource = map.getSource('places');
     if (selectedRegions.length === 0) {
       setAllMarkersVisible(mapSource); 
     return;
    }
   mapSource.setData({
     ...stores,
      features: stores.features.filter(feature => 
      selectedRegions.includes(feature.properties.region))
    });
   }
 
   function setAllMarkersVisible(mapSource) {
     mapSource.setData(stores); 
   }

I have immplemented this code from https://www.devgem.io/posts/how-to-implement-region-based-filters-in-a-maplibregl-map-with-clusters-and-markers

That's exactly what I'm looking for. But when clicking on any checkbox, it clears all my markers, but doesn't filter by region.

Is there something wrong here, I would like that when clicking on the "region1" checkbox only the corresponding markers are displayed.

And here is the basic data for the creation of my map :

     map.on('load', () => {
          map.addSource('places', {
            'type': 'geojson',
            'data': stores,
            'cluster': true,
            'clusterMaxZoom': 14, // Max zoom to cluster points on
            'clusterRadius': 50
          });
  
          
          // addMarkers();
          map.addLayer({
          id: 'clusters',
          type: 'circle',
          source: 'places',
          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: 'places',
          filter: ['has', 'point_count'],
          layout: {
          'text-field': '{point_count_abbreviated}',
          'text-font': ['Open Sans Light'],
          'text-size': 12,
          "icon-image": "{marker-symbol}",
          "icon-size": 0.20
          },
          paint: {
      "text-color": "#ffffff"
    }
          });
  
   
          map.addLayer({
          id: 'unclustered-point',
          type: 'circle',
          source: 'places',
          filter: ['!', ['has', 'point_count']],
          paint: {
          'circle-color': '#525CA3',
          'circle-radius': 15,
          'circle-stroke-width': 1,
          'circle-stroke-color': '#fff'
          }
          });

Solution

  • In the end, the last code proposed was good. In fact it uses the ids of the checkboxes, which means that the text of the label must be identical. This is not suitable most of the time because you don't write the text of a label like you write ID names. So the solution is to target the labels instead, and there it works

    function getLabel(id) {
      return $("#"+id).next("label").html();
     }
    function getSelectedRegions() {
    
      const selectedRegions = [];
      const regionCheckboxes = 
      document.querySelectorAll('input[type=checkbox]:checked');
       regionCheckboxes.forEach(checkbox => 
       selectedRegions.push(getLabel(checkbox.id)));
       return selectedRegions; 
     }