supercluster
supercluster copied to clipboard
Duplicate coordinates throwing `No cluster with the specified ID`
We are using Supercluster wrapped in https://github.com/novalabio/react-native-maps-super-cluster
On loading our map, we've experienced the No cluster with the specified ID
error when clicking on a cluster. This was happening within the following call to getLeaves()
, where clusteringEngine
is Supercluster.
clusteringEngine
.getLeaves(clusterId, 100)
.map(c => c.properties.item);
It turns out the error happened when a cluster had two points that shared exactly the same coordinates, leading to the below stack trace, where I log out from the getChildren()
function. As you can see, it thinks the child of the cluster is also a cluster at the same coordinates, and the child of that cluster is also apparently a cluster at the same coordinates, but that 'cluster' doesn't have any children IDs so the error is thrown.
I'm wondering:
- whether Supercluster is already meant to handle multiple points at the same coordinates, in which case is this a bug?; and
- whether in the event that
getChildren
cannot find the children for a cluster, an empty array should be returned rather than an error thrown. Or perhaps_appendLeaves
should setchildren
to an empty array if the error gets thrown, like this:
_appendLeaves: function (result, clusterId, limit, offset, skipped) {
try {
var children = this.getChildren(clusterId);
} catch(error){
if (error.message.includes('No cluster with the specified id')) {
children = []
} else {
throw error
}
}
...
Stack trace:
2018-10-19 21:27:40.887 [info][tid:com.facebook.react.JavaScript] ------------------------ get children
2018-10-19 21:27:40.887170+0100 Refill[68457:2682417] ------------------------ get children
2018-10-19 21:27:40.887 [info][tid:com.facebook.react.JavaScript] 'clusterId', 368017
2018-10-19 21:27:40.887494+0100 Refill[68457:2682417] 'clusterId', 368017
2018-10-19 21:27:40.888 [info][tid:com.facebook.react.JavaScript] '------------------------ ids', [ 11500 ]
2018-10-19 21:27:40.887822+0100 Refill[68457:2682417] '------------------------ ids', [ 11500 ]
2018-10-19 21:27:40.888 [info][tid:com.facebook.react.JavaScript] 'c.parentId', 368017
2018-10-19 21:27:40.888069+0100 Refill[68457:2682417] 'c.parentId', 368017
2018-10-19 21:27:40.888 [info][tid:com.facebook.react.JavaScript] push into children array
2018-10-19 21:27:40.888282+0100 Refill[68457:2682417] push into children array
2018-10-19 21:27:40.889 [info][tid:com.facebook.react.JavaScript] 'children', [ { type: 'Feature',
properties:
{ cluster: true,
cluster_id: 385970,
point_count: 2,
point_count_abbreviated: 2 },
geometry:
{ type: 'Point',
coordinates: [ -5.538370000000008, 50.11913999999999 ] } } ]
2018-10-19 21:27:40.888730+0100 Refill[68457:2682417] 'children', [ { type: 'Feature',
properties:
{ cluster: true,
cluster_id: 385970,
point_count: 2,
point_count_abbreviated: 2 },
geometry:
{ type: 'Point',
coordinates: [ -5.538370000000008, 50.11913999999999 ] } } ]
2018-10-19 21:27:40.889 [info][tid:com.facebook.react.JavaScript] ------------------------ get children
2018-10-19 21:27:40.888954+0100 Refill[68457:2682417] ------------------------ get children
2018-10-19 21:27:40.889 [info][tid:com.facebook.react.JavaScript] 'clusterId', 385970
2018-10-19 21:27:40.889217+0100 Refill[68457:2682417] 'clusterId', 385970
2018-10-19 21:27:40.889 [info][tid:com.facebook.react.JavaScript] '------------------------ ids', [ 12061 ]
2018-10-19 21:27:40.889467+0100 Refill[68457:2682417] '------------------------ ids', [ 12061 ]
2018-10-19 21:27:40.890 [info][tid:com.facebook.react.JavaScript] 'c.parentId', 385970
2018-10-19 21:27:40.889677+0100 Refill[68457:2682417] 'c.parentId', 385970
2018-10-19 21:27:40.890 [info][tid:com.facebook.react.JavaScript] push into children array
2018-10-19 21:27:40.889931+0100 Refill[68457:2682417] push into children array
2018-10-19 21:27:40.890 [info][tid:com.facebook.react.JavaScript] 'children', [ { type: 'Feature',
properties:
{ cluster: true,
cluster_id: 388691,
point_count: 2,
point_count_abbreviated: 2 },
geometry:
{ type: 'Point',
coordinates: [ -5.538370000000008, 50.11913999999999 ] } } ]
2018-10-19 21:27:40.890418+0100 Refill[68457:2682417] 'children', [ { type: 'Feature',
properties:
{ cluster: true,
cluster_id: 388691,
point_count: 2,
point_count_abbreviated: 2 },
geometry:
{ type: 'Point',
coordinates: [ -5.538370000000008, 50.11913999999999 ] } } ]
2018-10-19 21:27:40.891 [info][tid:com.facebook.react.JavaScript] ------------------------ get children
2018-10-19 21:27:40.890627+0100 Refill[68457:2682417] ------------------------ get children
2018-10-19 21:27:40.891 [info][tid:com.facebook.react.JavaScript] 'clusterId', 388691
2018-10-19 21:27:40.890848+0100 Refill[68457:2682417] 'clusterId', 388691
2018-10-19 21:27:40.891 [info][tid:com.facebook.react.JavaScript] '------------------------ ids', []
2018-10-19 21:27:40.891095+0100 Refill[68457:2682417] '------------------------ ids', []
2018-10-19 21:27:40.891 [info][tid:com.facebook.react.JavaScript] 'children', []
2018-10-19 21:27:40.891329+0100 Refill[68457:2682417] 'children', []
2018-10-19 21:27:42.898 [warn][tid:com.facebook.react.JavaScript] Possible Unhandled Promise Rejection (id: 0):
Error: No cluster with the specified id.
@ollieh-m thanks for the report! In theory, duplicate points shouldn't be a problem for the library. Would it be possible to set up a minimal reproducible test case so that I could investigate what's going on under the hood?
Closing because of not being able to reproduce this. Will reopen if there's a minimal reproducible use case.
I’m having the same problem and is clearly because of points with exactly the same coordinates inside the cluster. Initially I set the clusterMaxZoom
value to 99
because I wanted those clusters to return a zoom
value of 100
in getClusterExpansionZoom()
, but that doesn’t seem to work.
Playing with clusterMaxZoom
values it looks that there is a hard limit for the biggest zoom
level returned to be 31
(probably as a safety check for infinite recursion, or maybe because a zoom level bigger than 31
can’t be specified nowhere in Mapbox?), so clusterMaxZoom
has to have a maximum value of 30
to avoid the error. This way single-coordinate clusters can be recognised because they return a zoom value of 31
.
For the record, but it would be good to fix the getClusterExpansionZoom()
function to return a more descriptive error or else document this behavior.
(By the way, amazed that nobody else complained in more than two years!)
@djibarian thanks for bringing this up! Would you be able to set up a minimal test case for this so that I could promptly provide a fix?
We had the same problem (with a lot of duplicate coordinate), we set the cluster maxZoom to 30 and everything works.
@mourner How do I do that? Do you have a sandbox or playground I can set up?
@djibarian you could just use https://jsfiddle.net/ or https://jsbin.com/
To reproduce the error :
- Just click on the cluster with 2 points on the same place, this throws an error.
To fix it adjust the maxZoom to a value under 31
The code I used
import React, { useRef, useState } from 'react'
import GoogleMapReact from 'google-map-react'
import useSupercluster from 'use-supercluster'
function MainMap (props) {
const mapRef = useRef(null)
const [bounds, setBounds] = useState(null)
const [zoom, setZoom] = useState(10)
const Marker = ({ children }) => children
const points = [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
6.59874,
46.55043
]
},
properties: {
nickname: 'Doris'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
6.59874,
46.55043
]
},
properties: {
nickname: 'Doris'
}
}
]
const { clusters, supercluster } = useSupercluster({
points,
bounds,
zoom,
// Switch maxZoom to 30 to fix the error
options: { radius: 100, maxZoom: 31 }
})
const defaultProps = {
center: {
lat: 46.657505,
lng: 7.099246
},
zoom: 9
}
return (
<div style={{ height: '100vh', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ /* key: YourKey */ }}
defaultCenter={defaultProps.center}
defaultZoom={defaultProps.zoom}
onGoogleApiLoaded={({ map }) => {
console.log(map)
mapRef.current = map
}}
onChange={({ zoom, bounds }) => {
setZoom(zoom)
setBounds([
bounds.nw.lng,
bounds.se.lat,
bounds.se.lng,
bounds.nw.lat
])
}}
>
{clusters.map(cluster => {
const [longitude, latitude] = cluster.geometry.coordinates
const {
cluster: isCluster,
point_count: pointCount
} = cluster.properties
if (isCluster) {
return (
<Marker
key={`cluster-${cluster.id}`}
lat={latitude}
lng={longitude}
>
<div
style={{
width: `${10 + (pointCount / points.length) * 20}px`,
height: `${10 + (pointCount / points.length) * 20}px`,
color: '#fff',
background: '#1978c8',
borderRadius: '50%',
padding: '10px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
onClick={() => {
console.log(cluster)
console.log(cluster.id)
console.log(supercluster.getLeaves(cluster.id, Infinity))
}}
>
{pointCount}
</div>
</Marker>
)
} else {
return (
<Marker
key={cluster.id}
lat={latitude}
lng={longitude}
>
<button
style={{
color: '#fff',
background: '#1978c8',
borderRadius: '50%',
padding: '5px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
onClick={() => { console.log(cluster) }}>
</button>
</Marker>
)
}
})}
</GoogleMapReact>
</div>
)
}
export default MainMap
@ozaik would you mind turning this into a minimal (purely Supercluster without other libraries), live (a link to JSFiddle/JSBin) test case?
Hello!
I copied the example made by @ozaik into a codesandbox and adapted it so we can see the issue without any external library. Only supercluster here. Here's the link to the codesandbox: https://codesandbox.io/s/supercluster-no-cluster-with-the-specified-id-rtiqk?file=/src/index.js - I know it's not JSFiddle/JSbin, but I hope it's sufficient as a test case (as you don't need an account to mess with it either)
By the way. I'm having the same issue while using supercluster with the ol-supercluster: I get an apparently valid cluster_id (9) by scanning the output of getClusters
, but for some reason when I call getLeaves(cluster_id, Infinity)
I get that weird error. However, the maxZoom computed by OpenLayers is..28, so I don't think the issue is only the thing that affects if the error will happen or not here.
release 7.1.4 fixed it for me
Fantastic! So this might have been the same issue as #189. 🎉 Let's close this one for now then, unless anyone is still experiencing problems on the latest version.
@mourner Before closing the issue, could you please have checked the example I posted in the comment above?
For some weird scenarios, this issue still happens, even on version 7.1.4. Here's the updated codesandbox with the code still failing on 7.1.4: https://codesandbox.io/s/supercluster-no-cluster-with-the-specified-id-forked-r8jc6?file=/src/index.js
Thanks.
(My issue wasn't related to zoom level, but it did fix the exception thrown when iterating over clusters and trying to use getLeaves on a cluster)
Also having this issue with 7.1.5
with maxZoom > 30
On my case, if this helps someone was:
const [points, supercluster] = useClusterer(
debouncedDataToBeMerged ? debouncedDataToBeMerged : [],
SCREEN,
mapCoordsDebounced,
SUPERCLUSTER_OPTS
);
I have this hook that creates a supercluster instance, but forgot to add the dep into onClusterPress:
const onClusterPress = useCallback(
async (clusterId: number) => {
{
const regionToAnimate = supercluster.expandCluster(clusterId);
const zoom = supercluster.getClusterExpansionZoom(clusterId);
mapRef.current?.animateCamera({
center: {
...regionToAnimate,
},
zoom: zoom + 2,
});
}
},
- [],
+ [supercluster]
);