fix(map): group collocated user markers
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { parseUserLocation, projectWorldPoint } from "./user-location-map";
|
||||
import { groupMapLocations, parseUserLocation, projectWorldPoint } from "./user-location-map";
|
||||
|
||||
describe("user location map", () => {
|
||||
it("extracts a valid approximate location from cached IP intelligence", () => {
|
||||
@@ -24,6 +24,28 @@ describe("user location map", () => {
|
||||
expect(parseUserLocation({ location: { city: "Unknown" } })).toBeNull();
|
||||
});
|
||||
|
||||
it("groups users sharing approximate coordinates without hiding their identities", () => {
|
||||
const groups = groupMapLocations([
|
||||
{ userId: "one", nickname: "Dani (Steve)", latitude: 37.4056, longitude: -122.0775 },
|
||||
{ userId: "two", nickname: "Alex (AlexMC)", latitude: 37.4057, longitude: -122.0774 },
|
||||
{ userId: "three", nickname: "Sam (Notch)", latitude: 51.5, longitude: -0.12 },
|
||||
]);
|
||||
|
||||
expect(groups).toHaveLength(2);
|
||||
expect(groups[0]).toMatchObject({ count: 2, nicknames: ["Alex (AlexMC)", "Dani (Steve)"] });
|
||||
expect(groups[0]?.locations.map((location) => location.userId)).toEqual(["one", "two"]);
|
||||
expect(groups[1]).toMatchObject({ count: 1, nicknames: ["Sam (Notch)"] });
|
||||
});
|
||||
|
||||
it("normalizes signed zero and the antimeridian before grouping", () => {
|
||||
const groups = groupMapLocations([
|
||||
{ nickname: "West", latitude: -0.004, longitude: 180 },
|
||||
{ nickname: "East", latitude: 0.004, longitude: -180 },
|
||||
]);
|
||||
expect(groups).toHaveLength(1);
|
||||
expect(groups[0]).toMatchObject({ count: 2, key: "0:-180", latitude: 0, longitude: -180 });
|
||||
});
|
||||
|
||||
it("projects longitude and latitude into an equirectangular SVG", () => {
|
||||
expect(projectWorldPoint(0, 0, 800, 400)).toEqual({ x: 400, y: 200 });
|
||||
expect(projectWorldPoint(90, 180, 800, 400)).toEqual({ x: 800, y: 0 });
|
||||
|
||||
@@ -33,6 +33,32 @@ export function parseUserLocation(value: unknown): ParsedUserLocation | null {
|
||||
return { latitude, longitude, label: label || "Approximate location unavailable" };
|
||||
}
|
||||
|
||||
export function groupMapLocations<T extends {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
nickname: string;
|
||||
}>(locations: T[]) {
|
||||
const grouped = new Map<string, { latitude: number; longitude: number; locations: T[] }>();
|
||||
for (const location of locations) {
|
||||
const roundedLatitude = Number(location.latitude.toFixed(2));
|
||||
const latitude = roundedLatitude === 0 ? 0 : roundedLatitude;
|
||||
const roundedLongitude = Number(location.longitude.toFixed(2));
|
||||
const longitude = Math.abs(roundedLongitude) === 180 ? -180 : roundedLongitude;
|
||||
const key = `${latitude}:${longitude}`;
|
||||
const group = grouped.get(key);
|
||||
if (group) group.locations.push(location);
|
||||
else grouped.set(key, { latitude, longitude, locations: [location] });
|
||||
}
|
||||
return [...grouped.entries()].map(([key, group]) => ({
|
||||
key,
|
||||
latitude: group.latitude,
|
||||
longitude: group.longitude,
|
||||
count: group.locations.length,
|
||||
nicknames: [...group.locations.map((location) => location.nickname)].sort((left, right) => left.localeCompare(right)),
|
||||
locations: group.locations,
|
||||
}));
|
||||
}
|
||||
|
||||
export function projectWorldPoint(latitude: number, longitude: number, width: number, height: number) {
|
||||
return {
|
||||
x: ((longitude + 180) / 360) * width,
|
||||
|
||||
Reference in New Issue
Block a user