/** * 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, }; }