Dan
Dan

Reputation: 5986

Update style of individual feature from single geoJSON source on Mapbox map, when clicked

I'm working with Mapbox GL JS to plot geoJSON data on a map using their external geoJSON example as a starting point. The geoJSON file contains lots of features which are plotted as individual markers on the same layer. I would like to highlight the clicked marker by changing its colour from red to blue. I have adapted the example to show a pop-up with the point id when clicked (just as a proof of concept that the markers can fire events when clicked), however, I'm struggling to find a way to change the styling of the individual clicked marker.

The code is currently as follows:

mapboxgl.accessToken = 'pk.eyJ1IjoiZGFuYnJhbWFsbCIsImEiOiJjbDB3ODFveHYxOG5rM2pubWpwZ2R1Y2xuIn0.yatzJHqBTjQ6F3DHASlriw';
    const map = new mapboxgl.Map({
        container: 'map', // container ID
        style: 'mapbox://styles/mapbox/satellite-v9', // style URL
        zoom: 7, // starting zoom
        center: [138.043, 35.201] // starting center
    });

    map.on('load', () => {
        map.addSource('earthquakes', {
            type: 'geojson',
            data: 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson'
        });

        map.addLayer({
            'id': 'earthquakes-layer',
            'type': 'circle',
            'source': 'earthquakes',
            'paint': {
                'circle-radius': 8,
                'circle-stroke-width': 2,
                'circle-color': 'red',
                'circle-stroke-color': 'white'
            }
        });
    });

    map.on('click', 'earthquakes-layer', (e) => {
        new mapboxgl.Popup()
        .setLngLat(e.lngLat)
        .setHTML('Id: ' + e.features[0].properties.id)
        .addTo(map);
    });

Here is a codepen: https://codepen.io/danb1/pen/BaYjOyx

Is it the case that it's actually not possible to use this approach, and instead each feature from the geoJSON file needs to be plotted as a separate layer? I'm struggling to find any examples of this and am not able to modify the geoJSON source — it has to come from one single file (rather than loading multiple geoJSON files separately on separate layers).

Upvotes: 0

Views: 1167

Answers (1)

Dan
Dan

Reputation: 5986

This is possible using feature-state. The first thing to do is to ensure the layer data contains ids for each feature (in the example the source data doesn't so we need to add generateId: true to the map.addSource method).

We then need to add mousemove and mouseleave events to the map to store the moused-over feature id (if there is one, i.e. if the mouse is hovering over a feature):

let hoveredEarthquakeId = null;

map.on('mousemove', 'earthquakes-layer', (e) => {
  map.getCanvas().style.cursor = 'pointer';

  if (e.features.length > 0) {
    map.setFeatureState(
      { source: 'earthquakes', id: e.features[0].id },
      { hover: true }
    );
    hoveredEarthquakeId = e.features[0].id;
  }
});

map.on('mouseleave', 'earthquakes-layer', () => {
  map.getCanvas().style.cursor = '';

  if (hoveredEarthquakeId !== null) {
    map.setFeatureState(
      { source: 'earthquakes', id: hoveredEarthquakeId },
      { hover: false }
    );
  }
  hoveredEarthquakeId = null;
}); 

Finally, in the layer properties, the colour setting of the circle needs to be updated to reflect the hover value stored against the feature:

'circle-color': [
  'case',
  ['boolean', ['feature-state', 'hover'], false],
  '#00f',
  '#f00'
],

The final thing can be seen in the modified pen. There is also a MapBox tutorial covering this kind of thing in a slightly more complicated way, which I hadn't come across until now: https://docs.mapbox.com/help/tutorials/create-interactive-hover-effects-with-mapbox-gl-js/.

Upvotes: 1

Related Questions