feat: 添加 2D/3D 地图切换与坐标偏移功能

- 新增 WorldMap 组件,支持 2D 世界地图视图
- 主页添加 2D/3D 视图切换按钮
- 实现确定性坐标偏移算法,分散同城用户位置
- 更新 heatmap 和 register API 使用坐标偏移
This commit is contained in:
richarjiang
2026-03-15 14:19:36 +08:00
parent 7db59c9290
commit 8d094ad5cc
5 changed files with 513 additions and 102 deletions

72
lib/geo/offset.ts Normal file
View File

@@ -0,0 +1,72 @@
/**
* Generate a deterministic offset based on a unique identifier.
* This ensures the same ID always gets the same offset,
* allowing multiple users in the same city to be spread out
* while maintaining consistent positions.
*
* @param id - Unique identifier (e.g., claw ID)
* @param maxOffsetKm - Maximum offset in kilometers (default: 15km)
* @returns Object with lat and lng offsets in degrees
*/
export function generateDeterministicOffset(
id: string,
maxOffsetKm: number = 15
): { latOffset: number; lngOffset: number } {
// Simple hash function to convert string to number
let hash = 0;
for (let i = 0; i < id.length; i++) {
const char = id.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
// Use the hash to generate two pseudo-random values between -1 and 1
// Using simple modulo arithmetic for determinism
const seed1 = Math.abs(hash % 10000) / 10000; // 0 to 1
const seed2 = Math.abs((hash >> 8) % 10000) / 10000; // 0 to 1
// Convert to -1 to 1 range with better distribution
const factor1 = (seed1 * 2 - 1);
const factor2 = (seed2 * 2 - 1);
// Convert km to degrees (approximate)
// 1 degree latitude ≈ 111km
// 1 degree longitude varies by latitude (cos(lat) * 111km)
// We use a rough average here
const kmPerDegree = 111;
const maxOffsetDegrees = maxOffsetKm / kmPerDegree;
return {
latOffset: factor1 * maxOffsetDegrees,
lngOffset: factor2 * maxOffsetDegrees,
};
}
/**
* Apply deterministic offset to coordinates.
* This spreads out users in the same city while keeping
* each user's position consistent.
*
* @param lat - Original latitude
* @param lng - Original longitude
* @param id - Unique identifier for deterministic offset
* @param maxOffsetKm - Maximum offset in kilometers
* @returns New coordinates with offset applied
*/
export function applyDeterministicOffset(
lat: number,
lng: number,
id: string,
maxOffsetKm: number = 15
): { lat: number; lng: number } {
const { latOffset, lngOffset } = generateDeterministicOffset(id, maxOffsetKm);
// Adjust longitude offset based on latitude
// (longitude degrees get smaller towards poles)
const lngCorrection = Math.cos((lat * Math.PI) / 180);
return {
lat: lat + latOffset,
lng: lng + lngOffset * lngCorrection,
};
}