Search code examples
openlayers

How to show the amount of features if a cluster is being showed


In case the point is part of a cluster (i.e. it has more than one feature), I want to show the quantity of features. I used to do it like this with a VectorLayer:

const clusterSource = new Cluster({
  distance: 50,
  minDistance: 10,
  source: source,
});

const noClusterStyle = new Style({
  image: new CircleStyle({
    radius: 5,
    fill: new Fill({
      color: '#ffcc33',
    }),
  }),
});

const clusterStyle = new Style({
  text: new Text({
    fill: new Fill({
      color: '#fff',
    }),
  }),
})

const vectorLayer = new VectorLayer({
  source: clusterSource,
  style: function (feature) {
    const size = feature.get('features').length;
    if (size > 1) {
      clusterStyle.getText().setText(size.toString());
      return clusterStyle;
    }
    return noClusterStyle;
  },
});

Now I want to use a WebGLVectorLayer instead of a VectorLayer, however, its style must be a FlatStyleLike.

I tried the following:

import WebGLVectorLayer from 'ol/layer/WebGLVector';

const generateTextLabel = (featuresAmount) => {
  return (
    `<svg width="10" height="10" version="1.1" xmlns="http://www.w3.org/2000/svg">
      <text x="0" y="18" fill="white" font-size="16">${featuresAmount}</text>
    </svg>`
  )
}

const flatLikeStyle = [
  {
    style: {
      'shape-fill-color': 'black',
      'shape-points': 4,
      'shape-radius': 10,
    }
  },
  {
    filter: ['>', ['get', 'features'], 1],
    style: {
      'icon-src': 'data:image/svg+xml;utf8,' + generateTextLabel(0),
    },
  },
]

const webglVectorLayer = new WebGLVectorLayer({
  source: clusterSource,
  style: flatLikeStyle
})

But I don't know how to make the filter work with the size of features. I don't know how to pass the amount of features to the generateTextLabel() function either.


Solution

  • To obtain the length of an array you would need to save that as a feature property when the cluster is created. Also you cannot have more than one value for icon-src, you would need to create a sprite containing all possible values

    const canvas = document.createElement('canvas');
    const max = 200;
    const size = 20;
    canvas.width = max * size;
    canvas.heigth = size;
    const context = canvas.getContext('2d');
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.font = '10px sans-serif';
    const match = [];
    for (let i = 0; i < max; ++i) {
      context.fillStyle = '#3399CC';
      context.fillRect(i * size, 0, size, size);
      context.fillStyle = 'white';
      context.fillText(`${i + 1}`, i * size + size / 2, size / 2);
      match.push(i, [i * size, 0]);
    }
    const sprite = canvas.toDataURL();
    
    const source = new VectorSource({
      features: features,
    });
    
    const clusterSource = new Cluster({
      distance: parseInt(distanceInput.value, 10),
      minDistance: parseInt(minDistanceInput.value, 10),
      source: source,
    });
    
    clusterSource.on('addfeature', (e) => {
      e.feature.set('size', e.feature.get('features').length);
    });
    
    const clusters = new WebGLVectorLayer({
      source: clusterSource,
      style: {
        'icon-src': sprite,
        'icon-offset': ['match', ['get', 'size'], ...match, [0, 0]],
        'icon-size': [20, 20],
      },
    });
    

    Working example https://stackblitz.com/edit/js-hfsqdrec?file=package.json,index.html,index.js