fix: improve performance for realtime map
This commit is contained in:
@@ -106,17 +106,77 @@ function buildRealtimeBadgeDetailsFilter(input: {
|
|||||||
return buildRealtimeLocationFilter(input.locations);
|
return buildRealtimeLocationFilter(input.locations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CoordinatePoint {
|
||||||
|
country: string;
|
||||||
|
city: string;
|
||||||
|
long: number;
|
||||||
|
lat: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function mergeByRadius(
|
||||||
|
points: CoordinatePoint[],
|
||||||
|
radius: number
|
||||||
|
): CoordinatePoint[] {
|
||||||
|
// Highest-count points become cluster centers; nearby points get absorbed into them
|
||||||
|
const sorted = [...points].sort((a, b) => b.count - a.count);
|
||||||
|
const absorbed = new Uint8Array(sorted.length);
|
||||||
|
const clusters: CoordinatePoint[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < sorted.length; i++) {
|
||||||
|
if (absorbed[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const seed = sorted[i];
|
||||||
|
if (!seed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const center: CoordinatePoint = { ...seed };
|
||||||
|
for (let j = i + 1; j < sorted.length; j++) {
|
||||||
|
if (absorbed[j]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const other = sorted[j];
|
||||||
|
if (!other) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const dlat = other.lat - center.lat;
|
||||||
|
const dlong = other.long - center.long;
|
||||||
|
if (Math.sqrt(dlat * dlat + dlong * dlong) <= radius) {
|
||||||
|
center.count += other.count;
|
||||||
|
absorbed[j] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clusters.push(center);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adaptiveCluster(
|
||||||
|
points: CoordinatePoint[],
|
||||||
|
target: number
|
||||||
|
): CoordinatePoint[] {
|
||||||
|
if (points.length <= target) {
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand merge radius until we hit the target (~55km → ~111km → ~333km → ~1110km)
|
||||||
|
for (const radius of [0.5, 1, 3, 10]) {
|
||||||
|
const clustered = mergeByRadius(points, radius);
|
||||||
|
if (clustered.length <= target) {
|
||||||
|
return clustered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return points.slice(0, target);
|
||||||
|
}
|
||||||
|
|
||||||
export const realtimeRouter = createTRPCRouter({
|
export const realtimeRouter = createTRPCRouter({
|
||||||
coordinates: protectedProcedure
|
coordinates: protectedProcedure
|
||||||
.input(z.object({ projectId: z.string() }))
|
.input(z.object({ projectId: z.string() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const res = await chQuery<{
|
const res = await chQuery<CoordinatePoint>(
|
||||||
city: string;
|
|
||||||
country: string;
|
|
||||||
long: number;
|
|
||||||
lat: number;
|
|
||||||
count: number;
|
|
||||||
}>(
|
|
||||||
`SELECT
|
`SELECT
|
||||||
country,
|
country,
|
||||||
city,
|
city,
|
||||||
@@ -129,13 +189,11 @@ export const realtimeRouter = createTRPCRouter({
|
|||||||
AND longitude IS NOT NULL
|
AND longitude IS NOT NULL
|
||||||
AND latitude IS NOT NULL
|
AND latitude IS NOT NULL
|
||||||
GROUP BY country, city, longitude, latitude
|
GROUP BY country, city, longitude, latitude
|
||||||
ORDER BY count DESC`
|
ORDER BY count DESC
|
||||||
|
LIMIT 5000`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.forEach((item) => {
|
return adaptiveCluster(res, 500);
|
||||||
console.log(item.country, item.city, item.long, item.lat);
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}),
|
}),
|
||||||
mapBadgeDetails: protectedProcedure
|
mapBadgeDetails: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|||||||
Reference in New Issue
Block a user