Recently I was able to take a deeper look at the problem described above and actually come to a solution that was good enough for my use case. There are several solutions possible to overcome this problem.
1) Using another cluster solution in combination with Mapbox eg Supercluster
2) Fork the Mapbox repro and add a method that invokes the getClusterLeaves()
method from the native SDK
Solution 1
The first solution uses the Supercluster npm package and this solution is entirely based on the information found on this page. I'll share the code I ended up with but for more information, I would like to encourage you to read the resource linked.
//Initialize supercluster, these values worked great for me
const cluster = new Supercluster({ radius: 40, maxZoom: 16 });
cluster.load(collection.features);
The collection variable used in the load method of Supercluster should represent a FeatureCollection value. Which you can create in several ways. The one that worked well for me was the one below.
const collection = MapboxGL.geoUtils.makeFeatureCollection(groupFeatures);
The groupFeatures variable can be an array consisting of Features to display on the map. In order for easy access, I've stored the cluster variable in my state before moving on. I like to keep things separate so it is easy for me to see which portion of the code is responsible for what. So my Render method looks something like below.
<MapboxGL.MapView
ref={c => (this._map = c)}
onRegionDidChange={this.updateClusters}
zoomEnabled
style={[{ flex: 1 }]}>
//This is where we render the clusters
{this.renderPoints()}
</MapboxGL.MapView>
The next step is to get the actual clusters using supercluster in order to do that we need the bounds of the map we're displaying and the current zoom level (using Math.round since decimal values could result in errors within supercluster).
const bounds = await this._map.getVisibleBounds();
const zoom = Math.round(await this._map.getZoom());
const westLng = bounds[1][0];
const southLat = bounds[1][1];
const eastLng = bounds[0][0];
const northLat = bounds[0][1];
Now we're able to retrieve the actual clusters doing something like such:
const sc = this.state.superCluster;
const clusters = sc.getClusters(
[westLng, southLat, eastLng, northLat],
zoom
)
this.setState({clusters: clusters})
And now we are able to render the clusters in the Mapbox Shapesource
renderPoints = () => {
const { clusters } = this.state;
return (
<MapboxGL.ShapeSource
id="symbolLocationSource"
hitbox={{ width: 18, height: 18 }}
onPress={this.onMarkerSelected}
shape={{ type: "FeatureCollection", features: superClusterClusters }}
>
<MapboxGL.SymbolLayer
id="pointCount"
minZoomLevel={6}
style={mapStyles.clusterCount}
/>
<MapboxGL.CircleLayer
id="clusteredPoints"
minZoomLevel={6}
belowLayerID="pointCount"
filter={[">", "point_count", 1]}
style={mapStyles.clusteredPoints}
/>
<MapboxGL.SymbolLayer
id="symbolLocationSymbols"
minZoomLevel={6}
filter={["!", ["has", "point_count"]]}
style={mapStyles.icon}
/>
</MapboxGL.ShapeSource>
);
}
So now for the last piece of the puzzle, we are able to get the actual leaves of a cluster. In the last part of the code above I've already added the onMarkerPressed method for the onPress. This is were we obtain the leaves of the cluster.
onMarkerSelected = event => {
const point = event.nativeEvent.payload;
const { name, cluster } = point.properties;
if (cluster) {
const sc = this.state.superCluster;
const points = sc
.getLeaves(point.properties.cluster_id, Infinity)
//This will output an array with all leaves of the selected cluster
console.log(points);
}
}
Solution 2
So the solution mentioned above is the solution I chose for my own situation. So I didn't explore the solution of forking the Mapbox repro and adding functionality to it. I however found some pointers to get you started following the link here and here