All checks were successful
Deploy production / deploy (push) Successful in 1m13s
46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
// Tiny screen-space clustering for the task map (a few hundred pins at most, so O(n²) is fine
|
|
// and avoids a plugin). Points are in projected pixels at the current zoom.
|
|
|
|
export type ScreenPoint = { x: number; y: number; weight: number };
|
|
|
|
/**
|
|
* Greedy clustering: the heaviest unassigned point (largest scale) takes every unassigned
|
|
* point within `radius` px. Returns groups in that order; radius 0 keeps every point alone.
|
|
*/
|
|
export function clusterPoints<T extends ScreenPoint>(points: T[], radius: number): T[][] {
|
|
const order = [...points].sort((a, b) => b.weight - a.weight);
|
|
const used = new Set<T>();
|
|
const groups: T[][] = [];
|
|
const r2 = radius * radius;
|
|
for (const p of order) {
|
|
if (used.has(p)) continue;
|
|
used.add(p);
|
|
const group = [p];
|
|
if (radius > 0)
|
|
for (const q of order) {
|
|
if (used.has(q)) continue;
|
|
const dx = q.x - p.x;
|
|
const dy = q.y - p.y;
|
|
if (dx * dx + dy * dy <= r2) {
|
|
used.add(q);
|
|
group.push(q);
|
|
}
|
|
}
|
|
groups.push(group);
|
|
}
|
|
return groups;
|
|
}
|
|
|
|
/** Offsets (px) that fan out `n` pins sharing one coordinate in a small ring. */
|
|
export function ringOffsets(n: number): { dx: number; dy: number }[] {
|
|
if (n <= 1) return [{ dx: 0, dy: 0 }];
|
|
const r = 16 + n * 3;
|
|
return Array.from({ length: n }, (_, i) => {
|
|
const a = (2 * Math.PI * i) / n - Math.PI / 2;
|
|
return { dx: Math.round(Math.cos(a) * r), dy: Math.round(Math.sin(a) * r) };
|
|
});
|
|
}
|
|
|
|
/** Rounded lat/lon key, so tasks on the same centroid can be fanned out. */
|
|
export const coordKey = (lat: number, lon: number) => `${lat.toFixed(4)},${lon.toFixed(4)}`;
|