improve link visibility and network-scoped viable pairs

This commit is contained in:
Ben
2026-03-04 23:10:35 +00:00
parent 0632ec3351
commit 8d42910fca
4 changed files with 65 additions and 17 deletions
+13 -4
View File
@@ -155,11 +155,20 @@ export async function getLastNPackets(n: number, network?: string) {
export const MIN_LINK_OBSERVATIONS = 5;
/** Returns only confirmed viable link pairs — compact for sending in initial WebSocket state. */
export async function getViableLinkPairs(): Promise<[string, string][]> {
export async function getViableLinkPairs(network?: string): Promise<[string, string][]> {
const params: unknown[] = [MIN_LINK_OBSERVATIONS];
const networkFilter = network ? 'AND a.network = $2 AND b.network = $2' : '';
if (network) params.push(network);
const res = await pool.query<{ node_a_id: string; node_b_id: string }>(
`SELECT node_a_id, node_b_id FROM node_links
WHERE (itm_viable = true OR force_viable = true) AND observed_count >= $1`,
[MIN_LINK_OBSERVATIONS],
`SELECT nl.node_a_id, nl.node_b_id
FROM node_links nl
JOIN nodes a ON a.node_id = nl.node_a_id
JOIN nodes b ON b.node_id = nl.node_b_id
WHERE (nl.itm_viable = true OR nl.force_viable = true)
AND nl.observed_count >= $1
${networkFilter}`,
params,
);
return res.rows.map((r) => [r.node_a_id, r.node_b_id]);
}
+1 -1
View File
@@ -54,7 +54,7 @@ export function initWebSocketServer(httpServer: Server): WebSocketServer {
// Send initial state: known nodes + last 5 minutes of packets
try {
const [nodes, packets, viablePairs] = await Promise.all([
getNodes(network), getLastNPackets(10, network), getViableLinkPairs(),
getNodes(network), getLastNPackets(10, network), getViableLinkPairs(network),
]);
const initMsg: WSMessage = {
type: 'initial_state',
+1
View File
@@ -137,6 +137,7 @@ export const App: React.FC = () => {
showClientNodes={filters.clientNodes}
showLinks={filters.links}
viablePairsArr={viablePairsArr}
linkMetrics={linkMetrics}
packetPath={packetPath}
betaPath={betaPacketPath}
showBetaPaths={filters.betaPaths || pinnedPacketId !== null}
+50 -12
View File
@@ -3,6 +3,7 @@ import { MapContainer, TileLayer, useMap, Pane, Polygon, Polyline } from 'react-
import type { LatLngExpression, Map as LeafletMap, Polyline as LeafletPolyline } from 'leaflet';
import type { MeshNode, PacketArc } from '../../hooks/useNodes.js';
import type { NodeCoverage } from '../../hooks/useCoverage.js';
import type { LinkMetrics } from '../../utils/pathing.js';
import { NodeMarker } from './NodeMarker.js';
import { PacketArcLayer } from './PacketArcLayer.js';
import { NodeSearch } from './NodeSearch.js';
@@ -88,6 +89,7 @@ interface MapViewProps {
showClientNodes: boolean;
showLinks: boolean;
viablePairsArr: [string, string][];
linkMetrics: Map<string, LinkMetrics>;
packetPath: [number, number][] | null;
betaPath: [number, number][] | null;
showBetaPaths: boolean;
@@ -101,7 +103,7 @@ const DEFAULT_ZOOM = 11;
export const MapView: React.FC<MapViewProps> = ({
nodes, arcs, activeNodes, coverage, showPackets, showCoverage, showClientNodes,
showLinks, viablePairsArr, packetPath, betaPath, showBetaPaths, pathOpacity, onMapReady,
showLinks, viablePairsArr, linkMetrics, packetPath, betaPath, showBetaPaths, pathOpacity, onMapReady,
}) => {
const [map, setMap] = useState<LeafletMap | null>(null);
@@ -181,19 +183,49 @@ export const MapView: React.FC<MapViewProps> = ({
return m;
}, [coverage]);
const linkKey = (a: string, b: string) => (a < b ? `${a}:${b}` : `${b}:${a}`);
const linkColor = (pathLossDb: number | null | undefined) => {
if (pathLossDb == null) return '#fbbf24';
if (pathLossDb <= 120) return '#22c55e';
if (pathLossDb <= 135) return '#fbbf24';
return '#ef4444';
};
// Resolve viable link pairs to lat/lon polyline positions
const linkLines = useMemo(() => {
if (!showLinks || viablePairsArr.length === 0) return [];
const lines: [number, number][][] = [];
const lines: Array<{
key: string;
positions: [number, number][];
observedCount: number;
pathLossDb: number | null | undefined;
}> = [];
for (const [aId, bId] of viablePairsArr) {
const a = nodes.get(aId);
const b = nodes.get(bId);
if (hasCoords(a) && hasCoords(b)) {
lines.push([[a.lat, a.lon], [b.lat, b.lon]]);
if (
hasCoords(a)
&& hasCoords(b)
&& (Date.now() - new Date(a.last_seen).getTime()) < FOURTEEN_DAYS_MS
&& (Date.now() - new Date(b.last_seen).getTime()) < FOURTEEN_DAYS_MS
&& !a.name?.includes('🚫')
&& !b.name?.includes('🚫')
&& (a.role === undefined || a.role === 2)
&& (b.role === undefined || b.role === 2)
) {
const key = linkKey(aId, bId);
const metrics = linkMetrics.get(key);
lines.push({
key,
positions: [[a.lat, a.lon], [b.lat, b.lon]],
observedCount: metrics?.observed_count ?? 0,
pathLossDb: metrics?.itm_path_loss_db,
});
}
}
return lines;
}, [showLinks, viablePairsArr, nodes]);
}, [showLinks, viablePairsArr, nodes, linkMetrics]);
return (
<div className="map-area">
@@ -240,18 +272,24 @@ export const MapView: React.FC<MapViewProps> = ({
{/* Confirmed link lines — ITM-viable node pairs */}
{showLinks && linkLines.length > 0 && (
<Pane name="linksPane" style={{ zIndex: 400 }}>
{linkLines.map((positions, i) => (
{linkLines.map((line) => {
const obs = Math.max(1, line.observedCount);
const strength = Math.log10(obs + 1);
const opacity = Math.min(0.85, 0.35 + strength * 0.22);
const weight = Math.min(3.2, 1.0 + strength * 1.1);
return (
<Polyline
key={i}
positions={positions}
key={line.key}
positions={line.positions}
pathOptions={{
color: '#fbbf24',
weight: 1.5,
opacity: 0.55,
color: linkColor(line.pathLossDb),
weight,
opacity,
}}
interactive={false}
/>
))}
);
})}
</Pane>
)}