93 lines
2.6 KiB
JavaScript
93 lines
2.6 KiB
JavaScript
const clusterCount = point => {
|
|
const count = Number(point?.count)
|
|
return Number.isFinite(count) && count > 0 ? count : 1
|
|
}
|
|
|
|
const urgentCount = point => {
|
|
const count = Number(point?.urgent_count)
|
|
return Number.isFinite(count) && count > 0 ? count : 0
|
|
}
|
|
|
|
export const clusterDiameterPixels = count => {
|
|
if (count >= 100_000) return 60
|
|
if (count >= 10_000) return 56
|
|
if (count >= 1_000) return 52
|
|
return 44
|
|
}
|
|
|
|
export const clusterSizeClass = count => {
|
|
if (count >= 100_000) return "is-xxl"
|
|
if (count >= 10_000) return "is-xl"
|
|
if (count >= 1_000) return "is-lg"
|
|
return "is-md"
|
|
}
|
|
|
|
const mergeDistance = (left, right, gap) =>
|
|
(clusterDiameterPixels(left.count) + clusterDiameterPixels(right.count)) / 2 + gap
|
|
|
|
const coordinatesFromPixel = (map, pixel) => {
|
|
const coordinates = map.unproject([pixel.x, pixel.y])
|
|
|
|
return {
|
|
longitude: Number(coordinates.lng ?? coordinates[0]),
|
|
latitude: Number(coordinates.lat ?? coordinates[1])
|
|
}
|
|
}
|
|
|
|
export const mergeNearbyMapClusters = (map, points, options = {}) => {
|
|
const gap = Number.isFinite(options.gap) ? Math.max(options.gap, 0) : 8
|
|
|
|
const groups = points.map(point => {
|
|
const pixel = map.project([point.longitude, point.latitude])
|
|
|
|
return {
|
|
points: [point],
|
|
x: pixel.x,
|
|
y: pixel.y,
|
|
count: clusterCount(point),
|
|
urgent_count: urgentCount(point)
|
|
}
|
|
})
|
|
|
|
let merged = true
|
|
|
|
while (merged) {
|
|
merged = false
|
|
|
|
outer:
|
|
for (let leftIndex = 0; leftIndex < groups.length; leftIndex += 1) {
|
|
for (let rightIndex = leftIndex + 1; rightIndex < groups.length; rightIndex += 1) {
|
|
const left = groups[leftIndex]
|
|
const right = groups[rightIndex]
|
|
const distance = Math.hypot(left.x - right.x, left.y - right.y)
|
|
|
|
if (distance >= mergeDistance(left, right, gap)) continue
|
|
|
|
const count = left.count + right.count
|
|
left.x = (left.x * left.count + right.x * right.count) / count
|
|
left.y = (left.y * left.count + right.y * right.count) / count
|
|
left.count = count
|
|
left.urgent_count += right.urgent_count
|
|
left.points.push(...right.points)
|
|
groups.splice(rightIndex, 1)
|
|
merged = true
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
|
|
return groups.map(group => {
|
|
const coordinates = coordinatesFromPixel(map, group)
|
|
const firstId = group.points[0]?.id || "anonymous"
|
|
|
|
return {
|
|
type: "cluster",
|
|
id: `visual-cluster-${group.points.length}-${firstId}`,
|
|
count: group.count,
|
|
urgent_count: group.urgent_count,
|
|
longitude: coordinates.longitude,
|
|
latitude: coordinates.latitude
|
|
}
|
|
})
|
|
}
|