Henry Zhu
Henry Zhu

Reputation: 2628

Mapbox GL JS: Clustering circle not rendered properly upon zooming out

I am building a website that renders circles on a map. If the ["feature-state", "value] property is >= 1, the circle is green and if the ["feature-state", "value"] property is 0 or null, the circle is red (the default value is 0). When my map is zoomed in enough, the circles are rendered properly (all circles with ["feature-state", "value] >= 1 are rendered green). However, when I zoom out, the cluster circle that contains children features/circles with a ["feature-state", "value], the circle is rendered as red instead of green. Is there any way to ensure that the clusters are rendered as green if the sum of their children's ["feature-state", "value] is >= 1?

Below is my rendering code:

map.addSource('cities', {
    "type": "geojson",
    "data": "cities.geojson",
    "cluster": true,
    "clusterMaxZoom": 14,
    "clusterRadius": 80
});

map.addLayer({
    "id": "cities",
    "type": "circle",
    "source": "cities",
    "paint": {
        "circle-color": [ 
        "case",
            ["==", ["feature-state", "value"], null], "#ff4d4d",
            [">=", ["feature-state", "value"], 1], "#33cc33",
            "#ff4d4d"
        ],
        "circle-radius": [ 
            "case",
                ["==", ["feature-state", "value"], null], 9,
                [">=", ["feature-state", "value"], 1], 12,
                6
            ],
        "circle-opacity" : [
            "case",
                ["==", ["feature-state", "value"], null], 0.7,
                [">=", ["feature-state", "value"], 1], 1,
                0.7
        ]
    },
});

Below is how I set the "feature-state":

map.setFeatureState({source: "cities", id : 124312}, {value: 1});

Here is a screenshot of the map when correctly zoomed in: enter image description here

Here is a screenshot of the map when zoomed out (the red circle in the area marked with a white marker should be rendered green): enter image description here

Upvotes: 1

Views: 1346

Answers (1)

Manish
Manish

Reputation: 5213

This can be done with clusterProperties option of the source object which takes custom expression to aggregate properties for a cluster point.

Note: clusterProperties option does not support feature-state aggregation and can only work with properties. I'll explain it in steps below and there's also a codepen example attached at bottom.

  1. First, define an expression that aggregates feature's properties that form a cluster.
{
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 80,
  clusterProperties: {
    /*
      get the property numUser, then cast it to number or 0,
      then sum it, then store the sum as cluster's numUser property
    */
    numUsers: ["+", ["number", ["get", "numUsers"], 0]] 
  },
  type: "geojson",
  data: geojson,
}
  1. Update style expressions to use feature properties instead of feature-state:
paint: {
  "circle-color": [
    "case",
    ["==", ["get", "numUsers"], null], "#ff4d4d",
    [">=", ["get", "numUsers"], 1], "#33cc33",
    "#ff4d4d"
  ],
  "circle-radius": [
    "case",
    ["==", ["get", "numUsers"], null], 9,
    [">=", ["get", "numUsers"], 1], 12,
    6
  ],
  "circle-opacity": [
    "case",
    ["==", ["get", "numUsers"], null], 0.7,
    [">=", ["get", "numUsers"], 1], 1,
    0.7
  ]
}
  1. Update GeoJSON properties:
// Update after 2 seconds
setTimeout(() => {
  const newGeojson = {
    ...geojson,
    features: geojson.features.map(feature => {
      if ([0, 1].includes(feature.id)) { // update features selectively
        feature.properties.numUsers = 1;
      }
      return feature;
    })
  };
  map.getSource('geom').setData(newGeojson);
}, 2000);

Here's a working codepen: https://codepen.io/manishraj/full/wvwmNKR

There's a related example here, by mapbox: https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/

Upvotes: 1

Related Questions