mirror of
https://github.com/gadgethd/ukmesh.git
synced 2026-10-22 05:52:29 +00:00
Harden map privacy and telemetry displays
This commit is contained in:
1 parent
ebb8fe55b4
commit
007d538da9
15 files changed
+1905
-119
No files matched your search
@@ -1,6 +1,9 @@
|
||||
import type { MeshNode } from '../hooks/useNodes.js';
|
||||
|
||||
export const MIN_LINK_OBSERVATIONS = 5; // must match backend db/index.ts
|
||||
export const HIDDEN_NODE_MASK_RADIUS_MILES = 1;
|
||||
export const HIDDEN_NODE_MASK_RADIUS_METERS = HIDDEN_NODE_MASK_RADIUS_MILES * 1609.344;
|
||||
const PROHIBITED_NODE_MARKER = '🚫';
|
||||
|
||||
export type LinkMetrics = {
|
||||
observed_count: number;
|
||||
@@ -25,19 +28,91 @@ export function hasCoords(node: MeshNode | null | undefined): node is MeshNode &
|
||||
return isValidMapCoord(node?.lat, node?.lon);
|
||||
}
|
||||
|
||||
export function isProhibitedMapNode(node: MeshNode | null | undefined): boolean {
|
||||
return Boolean(node?.name?.includes(PROHIBITED_NODE_MARKER));
|
||||
}
|
||||
|
||||
function roundCoord(value: number): number {
|
||||
return Math.round(value * 1_000_000) / 1_000_000;
|
||||
}
|
||||
|
||||
function hiddenCoordKey(lat: number, lon: number): string {
|
||||
return `${roundCoord(lat)},${roundCoord(lon)}`;
|
||||
}
|
||||
|
||||
function hashSeed(input: string): number {
|
||||
let hash = 2166136261;
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
hash ^= input.charCodeAt(i);
|
||||
hash = Math.imul(hash, 16777619);
|
||||
}
|
||||
return hash >>> 0;
|
||||
}
|
||||
|
||||
function seededUnitPair(seed: string): [number, number] {
|
||||
const distanceUnit = hashSeed(`${seed}:distance`) / 0xffffffff;
|
||||
const bearingUnit = hashSeed(`${seed}:bearing`) / 0xffffffff;
|
||||
return [distanceUnit, bearingUnit];
|
||||
}
|
||||
|
||||
function stablePointWithinMiles(
|
||||
lat: number,
|
||||
lon: number,
|
||||
seed: string,
|
||||
radiusMiles = HIDDEN_NODE_MASK_RADIUS_MILES,
|
||||
): [number, number] {
|
||||
const radiusKm = radiusMiles * 1.609344;
|
||||
const [distanceUnit, bearingUnit] = seededUnitPair(seed);
|
||||
const distanceKm = Math.sqrt(distanceUnit) * radiusKm;
|
||||
const bearing = bearingUnit * Math.PI * 2;
|
||||
const latRad = lat * (Math.PI / 180);
|
||||
const dLat = (distanceKm / 111) * Math.cos(bearing);
|
||||
const lonScale = Math.max(0.01, Math.cos(latRad));
|
||||
const dLon = (distanceKm / (111 * lonScale)) * Math.sin(bearing);
|
||||
return [lat + dLat, lon + dLon];
|
||||
}
|
||||
|
||||
export function buildHiddenCoordMask(nodes: Iterable<MeshNode>): Map<string, [number, number]> {
|
||||
const mask = new Map<string, [number, number]>();
|
||||
for (const node of nodes) {
|
||||
if (!hasCoords(node) || !isProhibitedMapNode(node)) continue;
|
||||
const activityKey = node.last_seen ?? 'unknown';
|
||||
const seed = `${node.node_id}|${activityKey}`;
|
||||
mask.set(hiddenCoordKey(node.lat, node.lon), stablePointWithinMiles(node.lat, node.lon, seed));
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
export function maskPoint(
|
||||
point: [number, number],
|
||||
hiddenCoordMask?: Map<string, [number, number]>,
|
||||
): [number, number] {
|
||||
if (!hiddenCoordMask || hiddenCoordMask.size < 1) return point;
|
||||
return hiddenCoordMask.get(hiddenCoordKey(point[0], point[1])) ?? point;
|
||||
}
|
||||
|
||||
export function maskNodePoint(
|
||||
node: MeshNode & { lat: number; lon: number },
|
||||
hiddenCoordMask?: Map<string, [number, number]>,
|
||||
): [number, number] {
|
||||
if (!isProhibitedMapNode(node)) return [node.lat, node.lon];
|
||||
return maskPoint([node.lat, node.lon], hiddenCoordMask);
|
||||
}
|
||||
|
||||
export function resolvePathWaypoints(
|
||||
pathHashes: string[],
|
||||
src: MeshNode | null,
|
||||
rx: MeshNode,
|
||||
src: (MeshNode & { lat: number; lon: number }) | null,
|
||||
rx: MeshNode & { lat: number; lon: number },
|
||||
allNodes: Map<string, MeshNode>,
|
||||
hiddenCoordMask?: Map<string, [number, number]>,
|
||||
): [number, number][] {
|
||||
const waypoints: [number, number][] = src ? [[src.lat!, src.lon!]] : [];
|
||||
const waypoints: [number, number][] = src ? [maskNodePoint(src, hiddenCoordMask)] : [];
|
||||
const N = pathHashes.length;
|
||||
|
||||
for (let i = 0; i < N; i++) {
|
||||
const prefix = pathHashes[i]!.toUpperCase();
|
||||
const candidates = Array.from(allNodes.values()).filter(
|
||||
(n) => hasCoords(n) && n.node_id.toUpperCase().startsWith(prefix),
|
||||
(n): n is MeshNode & { lat: number; lon: number } => hasCoords(n) && n.node_id.toUpperCase().startsWith(prefix),
|
||||
);
|
||||
if (candidates.length === 0) continue;
|
||||
|
||||
@@ -63,9 +138,9 @@ export function resolvePathWaypoints(
|
||||
});
|
||||
}
|
||||
}
|
||||
waypoints.push([best.lat!, best.lon!]);
|
||||
waypoints.push(maskNodePoint(best, hiddenCoordMask));
|
||||
}
|
||||
|
||||
waypoints.push([rx.lat!, rx.lon!]);
|
||||
waypoints.push(maskNodePoint(rx, hiddenCoordMask));
|
||||
return waypoints;
|
||||
}
|
||||
Reference in new issue
Block a user