rspace-online/modules/rmaps/components/map-privacy.ts

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`;
}