75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
/**
|
|
* Privacy utilities for rMaps: location fuzzing, distance calculations, formatting.
|
|
*/
|
|
|
|
import type { PrecisionLevel } from "./map-sync";
|
|
|
|
/** Noise radius in meters per precision level */
|
|
const PRECISION_RADIUS: Record<PrecisionLevel, number> = {
|
|
exact: 0,
|
|
building: 50,
|
|
area: 500,
|
|
approximate: 5000,
|
|
};
|
|
|
|
/**
|
|
* Add random noise to coordinates based on precision level.
|
|
* Returns original coords if precision is "exact".
|
|
*/
|
|
export function fuzzLocation(lat: number, lng: number, precision: PrecisionLevel): { latitude: number; longitude: number } {
|
|
const radius = PRECISION_RADIUS[precision];
|
|
if (radius === 0) return { latitude: lat, longitude: lng };
|
|
|
|
// Random angle and distance within radius
|
|
const angle = Math.random() * 2 * Math.PI;
|
|
const dist = Math.random() * radius;
|
|
|
|
// Approximate meters to degrees
|
|
const dLat = (dist * Math.cos(angle)) / 111320;
|
|
const dLng = (dist * Math.sin(angle)) / (111320 * Math.cos(lat * (Math.PI / 180)));
|
|
|
|
return {
|
|
latitude: lat + dLat,
|
|
longitude: lng + dLng,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Haversine distance between two points in meters.
|
|
*/
|
|
export function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
|
|
const R = 6371000; // Earth radius in meters
|
|
const toRad = (d: number) => d * (Math.PI / 180);
|
|
|
|
const dLat = toRad(lat2 - lat1);
|
|
const dLng = toRad(lng2 - lng1);
|
|
const a =
|
|
Math.sin(dLat / 2) ** 2 +
|
|
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2;
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
|
|
return R * c;
|
|
}
|
|
|
|
/**
|
|
* Format a distance in meters to a human-readable string.
|
|
*/
|
|
export function formatDistance(meters: number): string {
|
|
if (meters < 50) return "nearby";
|
|
if (meters < 1000) return `${Math.round(meters)}m`;
|
|
if (meters < 10000) return `${(meters / 1000).toFixed(1)}km`;
|
|
return `${Math.round(meters / 1000)}km`;
|
|
}
|
|
|
|
/**
|
|
* Format seconds to a human-readable duration.
|
|
*/
|
|
export function formatTime(seconds: number): string {
|
|
if (seconds < 60) return `${Math.round(seconds)}s`;
|
|
const m = Math.floor(seconds / 60);
|
|
if (m < 60) return `${m} min`;
|
|
const h = Math.floor(m / 60);
|
|
const rm = m % 60;
|
|
return rm > 0 ? `${h}h ${rm}m` : `${h}h`;
|
|
}
|