André Graubner
André Graubner

Reputation: 11

Highlighting Fill-Extrude Features on Hover after tilting?

I want to highlight fill-extrusion features when hovered over them. The styling related to this is straight-forward using expressions and feature state, but I am having trouble retrieving the correct features.

There is code available online to change the feature state when hovered over, and it seems straight-forward enough, so I adapted it:

var hover_id = null;
const feature_state = { hover: true }

map.on('mousemove', '3d-buildings', (e) => {
  
    // Get features under cursor, following render order
    const features = map.queryRenderedFeatures(e.point);

    // Check that features are not empty
    if (features.length > 0) {

        // Clean up previously hovered feature
        if (hover_id) {
            map.removeFeatureState({source: "composite", sourceLayer: 'building', id: hover_id});
        }

        // Set feature state of the new hovered feature
        hover_id = features[0].id;
        map.setFeatureState({source: 'composite', sourceLayer: 'building', id: hover_id}, feature_state);

        console.log(hover_id)
  }
});

While this works well initially, it stops working as soon as I tilt the camera using the right mouse button. After tilting, the foremost element no longer gets selected (something else seems to get selected and an ID gets printed out, but nothing shows up on the map and no error is thrown). On a related note, the correct feature only gets selected after zooming in quite far - there is a large zoom range where the buildings already get rendered to the screen, but seem to not get picked up by queryRenderedFeatures. Is this expected behaviour?

Expected behaviour: map.queryRenderedFeatures(...)[0] selects the foremost feature, independent of the camera tilt.

What could be a possible reason for the camera tilt influencing the feature selection? Is this a bug or am I misusing the API?

Upvotes: 1

Views: 1519

Answers (2)

jscastro
jscastro

Reputation: 3780

I think the issue you’re facing has nothing to do with tilt but with the fact that you’re adding and removing the state instead of changing the value of the state. The state must be declared in the layer definition, change the color with a expression, and then you only need to change the value of the state.

Here you have a fiddle I have created to show how to change color of fill extrusions on mouse over/out

enter image description here

Relevant code is this:

        let mapConfig = {
          NYC: {
            origin: [-74.044514, 40.689259, 39],
            center: [-74.0137, 40.70346, 0],
            zoom: 16.2,
            pitch: 60,
            bearing: 35
          }
        }

        mapboxgl.accessToken = 'PUT YOUR TOKEN HERE';
        let point = mapConfig.NYC;
        var map = new mapboxgl.Map({
          style: 'mapbox://styles/mapbox/streets-v11',
          center: point.center,
          zoom: point.zoom,
          pitch: point.pitch,
          bearing: point.bearing,
          container: 'map',
          antialias: true,
          hash: true
        });

        map.on('style.load', function() {

          if (map.getSource('composite')) {
            map.addLayer({
              'id': '3d-buildings',
              'source': 'composite',
              'source-layer': 'building',
              'type': 'fill-extrusion',
              'minzoom': 14,
              'paint': {
                'fill-extrusion-color': [
                  'case',
                  ['boolean', ['feature-state', 'hover'], false],
                  '#ff0000',
                  '#ddd'
                ],
                'fill-extrusion-height': ["number", ["get", "height"], 5],
                'fill-extrusion-base': ["number", ["get", "min_height"], 0],
                'fill-extrusion-opacity': 1
              }
            }, 'road-label');
          }

          let fHover;

          map.on('mousemove', function(e) {
            //157001066
            var features = map.queryRenderedFeatures(e.point, {
              layers: ['3d-buildings']
            });
            if (features[0]) {
              mouseout();
              mouseover(features[0]);
            } else {
              mouseout();
            }

          });

          map.on('mouseout', function(e) {
            mouseout();
          });

          function mouseout() {
            if (!fHover) return;
            map.getCanvasContainer().style.cursor = 'default';
            map.setFeatureState({
              source: fHover.source,
              sourceLayer: fHover.sourceLayer,
              id: fHover.id
            }, {
              hover: false
            });

          }

          function mouseover(feature) {
            fHover = feature;
            map.getCanvasContainer().style.cursor = 'pointer';

            map.setFeatureState({
              source: fHover.source,
              sourceLayer: fHover.sourceLayer,
              id: fHover.id
            }, {
              hover: true
            });
          }


        });

If this answer solves your question, please mark it as answer accepted in that way it will also help other users to know it was the right solution.

Upvotes: 2

@jscastro This works fine: My requirement is I need to change the color of few buildings with lat and lng. I have achieved getting buildings id's from lat and lng by using below API

https://api.mapbox.com/v4/mapbox.mapbox-streets-v8,mapbox.mapbox-terrain-v2/tilequery/55.26365875255766,25.188400365955193.json?radius=30&limit=10&dedupe&access_token=.

I am facing one issue here, The colors are changing only after zoom level 17. I want to change the color on zoom level 15.

map.on("style.load", function () {
  if (map.getSource("composite")) {
    const layers = map.getStyle().layers;
    const labelLayerId = layers.find(
      (layer) => layer.type === "symbol" && layer.layout["text-field"]
    ).id;
    map.addLayer(
      {
        id: "3d-buildings",
        source: "composite",
        "source-layer": "building",
        filter: ["==", "extrude", "true"],
        type: "fill-extrusion",
        minzoom: 15,
        zoom: 15,
        pitch: 60,
        bearing: -60,
        layout: {
          // Make the layer visible by default.
          visibility: "visible",
        },
        paint: {
          "fill-extrusion-color": [
            "case",
            ["boolean", ["feature-state", "hover"], false],
            "#00ff00",
            "#AED0EC",
          ],
          "fill-extrusion-height": [
            "interpolate",
            ["linear"],
            ["zoom"],
            15,
            0,
            15.5,
            ["get", "height"],
          ],
          "fill-extrusion-base": [
            "interpolate",
            ["linear"],
            ["zoom"],
            15,
            0,
            15.05,
            ["get", "min_height"],
          ],
          "fill-extrusion-opacity": 1,
        },
      },
      labelLayerId
    );
  }
  map.getCanvasContainer().style.cursor = "pointer";
  map.setFeatureState(
    {
      source: "composite",
      sourceLayer: "building",
      id: "4411722601841895",
    },
    {
      hover: true,
    }
  );
  map.getCanvasContainer().style.cursor = "pointer";
  map.setFeatureState(
    {
      source: "composite",
      sourceLayer: "building",
      id: "1315660041727095",
    },
    {
      hover: true,
    }
  );
  map.getCanvasContainer().style.cursor = "pointer";
  map.setFeatureState(
    {
      source: "composite",
      minzoom: 15,
      sourceLayer: "building",
      id: "3957345234349675",
    },
    {
      hover: true,
    }
  );
  map.getCanvasContainer().style.cursor = "pointer";
  map.setFeatureState(
    {
      source: "composite",
      sourceLayer: "building",
      id: "5328485811",
    },
    {
      hover: true,
    }
  );
});

Upvotes: 1

Related Questions