1

I have an interactive map in which each marker is a location, and each radio button filters those locations by region. By default, the markers are grouped by clusters.

Tha map has :

  • an interactive list. When an item in the list is clicked, the map zooms in on the corresponding marker and vice versa.
  • A filter box where each radio button filters the map by region.

This is working great after help from @TomazicM.

What I'm trying to do now: I try to adapt the code of the tutorial Filter features within map view of Mapbox, which uses queryRenderedFeatures to restrict a list of features in a vector tile layer to only return those that are visible in the map view.

1- When targeting a region throught the filters (radio buttons), I want the list in the left sidebar to be updated by listing only the locations of the targeted region. So for example, if I click on radio button "region 1", only the locations of region 1 are listed on the left sidebar, and so on. When I click on "All", all locations are listed in the sidebar.

Here is my (broken) fiddle And here is the original code

What I tried

1 - I tried to adapt the Mapbox tutorial code on my small project, by simply replacing the names of the variables.

So I just added, outside the on(load) function :

  function normalize(string) {
        return string.trim().toLowerCase();
    }
    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;
    }
    let airports = [];

Inside the on(load)function I added :

 map.on('movestart', () => {
            map.setFilter(clustersId, ['has', 'region']);
        });
        map.on('moveend', () => {
            const features = map.queryRenderedFeatures({ layers: clustersId });
            if (features) {
                const uniqueFeatures = getUniqueFeatures(features, 'name');
                buildLocationList(uniqueFeatures);
                airports = uniqueFeatures;
            }
        });
        map.on('mousemove', clustersId, (e) => {
            map.getCanvas().style.cursor = 'pointer';
             const feature = e.features[0];
            popup
                .setLngLat(feature.geometry.coordinates)
                .setText(
                    `${feature.properties.name} (${feature.properties.region})`
                )
                .addTo(map);
        });
        map.on('mouseleave', clustersId, () => {
            map.getCanvas().style.cursor = '';
            popup.remove();
        })
7
  • So you want to display marker that is zoomed in when clicking on the side menu. How long does this marker stay displayed? What should happen to this marker if another link from side menu is clicked or another region selected?
    – TomazicM
    Commented Oct 10 at 13:37
  • 1
    Then I admit I don't understand what the question is about. Displaying linked marker when clicking on link in sidenav? Or something else? Please edit your question and clearly state what are you asking.
    – TomazicM
    Commented Oct 10 at 16:36
  • 1
    Your describe in length what you do not like in current solution, but for what you would like to have, it's only "To limit this behavior, I thought of applying the technique described in the tutorial". Question must be complete in itself. Leave out what you don't like in current solution and in details describe the desired behavior of the app.
    – TomazicM
    Commented Oct 10 at 17:20
  • 1
    I don't see "the technique described in the tutorial" described anywhere in your question. If you want answer, then please leave out all descriptions of what you don't like in your current code. Just leave plain description of desired behavior.
    – TomazicM
    Commented Oct 10 at 19:10
  • sorry TomazicM and thanks for your for your vigilance. I edited my question
    – blogob
    Commented Oct 11 at 7:22

1 Answer 1

2

If sidenav should show list of all markers of only selected region, regardless if they are visible or not, and if no region is selected, all markers are to be shown in the list, below is one way of how it can be done.

In the loop where layers are created for all regions, filtered markers for each region can be saved to regions object. Here is relevant part of the code:

map.on('load', () => {
  const regionCheckboxes = document.querySelectorAll('input[type=radio]');
  regionCheckboxes.forEach(checkbox => {
    var region = getLabel(checkbox.id);
    if (region != 'All') {
      var filteredFeatures = stores.features.filter(feature => (feature.properties.region == region));
      var filteredStores = turf.featureCollection(filteredFeatures);
      var bbox = turf.bbox(filteredStores);
      
      regions[checkbox.id].bbox = bbox;
      regions[checkbox.id].stores = filteredStores;

Function buildLocationList to create list of markers has to be modified so at each call list is first emptied. This is relevant part of the code:

function buildLocationList(stores) {
  const listings = document.getElementById('listings');
  listings.innerHTML = '';
  for (const store of stores.features) {
    const listing = listings.appendChild(document.createElement('div'));

In the filterMarkers function list creating function has to be called with the parameter that reflects current selection:

function filterMarkers(button) {
  const popUps = document.getElementsByClassName('maplibregl-popup');
  if (popUps[0]) popUps[0].remove();

  const selectedRegions = getSelectedRegions();
  if (selectedRegions[0] == 'All') {
    setAllMarkersVisible();
    buildLocationList(stores);
    return;
  }
  for (var regionId in regions) {
    if (regionId != button.id) map.setLayoutProperty('clusters_' + regionId, 'visibility', 'none');
    if (regionId != button.id) map.setLayoutProperty('unclustered-point_' + regionId, 'visibility', 'none');
  }

  map.setLayoutProperty('clusters_' + button.id, 'visibility', 'visible');
  map.setLayoutProperty('unclustered-point_' + button.id, 'visibility', 'visible');
  buildLocationList(regions[button.id].stores);
  
  map.fitBounds(regions[button.id].bbox,  {
    padding: {top: 50, bottom: 50, left: 50, right: 50}
  });
}

Here is working JSFiddle: https://jsfiddle.net/TomazicM/dmok7cbp/

1
  • Well, I had a good idea of where to modify the code, but in terms of variables, I wasn't there at all. I don't know how to thank you again!! gracie mille !
    – blogob
    Commented Oct 11 at 15:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.