diff --git a/public/index.html b/public/index.html index cb6dea5b..586a6c9a 100644 --- a/public/index.html +++ b/public/index.html @@ -83,7 +83,7 @@ - + diff --git a/public/map.js b/public/map.js index a037eb2d..e56ea535 100644 --- a/public/map.js +++ b/public/map.js @@ -62,9 +62,12 @@ function makeRepeaterLabelIcon(node) { var s = ROLE_STYLE['repeater'] || ROLE_STYLE.companion; - var label = node.hash_size ? node.hash_size + 'B' : '?'; - var bgColor = node.hash_size ? s.color : '#999'; - var html = '
' + label + '
'; + var hs = node.hash_size || 1; + // Show the short mesh hash ID (first N bytes of pubkey, uppercased) + var shortHash = node.public_key ? node.public_key.slice(0, hs * 2).toUpperCase() : '??'; + var bgColor = node.hash_size ? s.color : '#888'; + var html = '
' + + shortHash + '
'; return L.divIcon({ html: html, className: 'meshcore-marker meshcore-label-marker', @@ -374,31 +377,39 @@ var _renderingMarkers = false; var _lastDeconflictZoom = null; - function deconflictLabels(labelMarkers, map) { + function deconflictLabels(labelMarkers, mapRef) { const placed = []; - const LABEL_W = 32; - const LABEL_H = 22; - const PAD = 4; + const LABEL_W = 38; + const LABEL_H = 24; + const PAD = 6; - const overlaps = (b) => placed.some(p => - b.x < p.x + p.w + PAD && b.x + b.w + PAD > p.x && - b.y < p.y + p.h + PAD && b.y + b.h + PAD > p.y - ); + const overlaps = function(b) { + return placed.some(function(p) { + return b.x < p.x + p.w + PAD && b.x + b.w + PAD > p.x && + b.y < p.y + p.h + PAD && b.y + b.h + PAD > p.y; + }); + }; - for (const m of labelMarkers) { - const pt = map.latLngToLayerPoint(m.latLng); - let bestPt = pt; - let box = { x: pt.x - LABEL_W / 2, y: pt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H }; + // Generate spiral offsets — 5 rings, 8 directions each, up to 120px out + var offsets = []; + for (var ring = 1; ring <= 6; ring++) { + var dist = ring * 28; + for (var angle = 0; angle < 360; angle += 45) { + var rad = angle * Math.PI / 180; + offsets.push([Math.round(Math.cos(rad) * dist), Math.round(Math.sin(rad) * dist)]); + } + } + + for (var i = 0; i < labelMarkers.length; i++) { + var m = labelMarkers[i]; + var pt = mapRef.latLngToLayerPoint(m.latLng); + var bestPt = pt; + var box = { x: pt.x - LABEL_W / 2, y: pt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H }; if (overlaps(box)) { - const offsets = [ - [0, -25], [0, 25], [-30, 0], [30, 0], - [-25, -20], [25, -20], [-25, 20], [25, 20], - [0, -45], [0, 45], [-50, 0], [50, 0] - ]; - for (const [dx, dy] of offsets) { - const tryPt = L.point(pt.x + dx, pt.y + dy); - const tryBox = { x: tryPt.x - LABEL_W / 2, y: tryPt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H }; + for (var j = 0; j < offsets.length; j++) { + var tryPt = L.point(pt.x + offsets[j][0], pt.y + offsets[j][1]); + var tryBox = { x: tryPt.x - LABEL_W / 2, y: tryPt.y - LABEL_H / 2, w: LABEL_W, h: LABEL_H }; if (!overlaps(tryBox)) { bestPt = tryPt; box = tryBox; @@ -408,7 +419,7 @@ } placed.push(box); - m.adjustedLatLng = map.layerPointToLatLng(bestPt); + m.adjustedLatLng = mapRef.layerPointToLatLng(bestPt); m.offset = Math.sqrt(Math.pow(bestPt.x - pt.x, 2) + Math.pow(bestPt.y - pt.y, 2)); } } diff --git a/server.js b/server.js index f04c5a94..b0599311 100644 --- a/server.js +++ b/server.js @@ -973,20 +973,45 @@ app.get('/api/nodes', (req, res) => { counts[r + 's'] = db.db.prepare(`SELECT COUNT(*) as count FROM nodes WHERE role = ?`).get(r).count; } - // Compute hash_size for each node from latest ADVERT packets + // Compute hash_size for each node from ADVERT path byte or path hop lengths const hashSizeMap = new Map(); + // Pass 1: from ADVERT packets (most authoritative — path byte bits 7-6) for (const p of pktStore.packets) { - if (p.payload_type === 4 && p.decoded_json) { + if (p.payload_type === 4 && p.raw_hex) { try { - const d = JSON.parse(p.decoded_json); + const d = JSON.parse(p.decoded_json || '{}'); const pk = d.pubKey || d.public_key; - if (pk && p.raw_hex && !hashSizeMap.has(pk)) { + if (pk && !hashSizeMap.has(pk)) { const pathByte = parseInt(p.raw_hex.slice(2, 4), 16); hashSizeMap.set(pk, ((pathByte >> 6) & 0x3) + 1); } } catch {} } } + // Pass 2: for nodes without ADVERTs, derive from path hop lengths in any packet + // Path hops are hex strings; their length / 2 = hash_size in bytes + for (const p of pktStore.packets) { + if (p.path_json) { + try { + const hops = JSON.parse(p.path_json); + if (hops.length > 0) { + const hopLen = hops[0].length / 2; // hex chars / 2 = bytes + if (hopLen >= 1 && hopLen <= 4) { + // The path byte tells us hash_size for ALL nodes in this packet's mesh + const pathByte = p.raw_hex ? parseInt(p.raw_hex.slice(2, 4), 16) : -1; + const hs = pathByte >= 0 ? ((pathByte >> 6) & 0x3) + 1 : hopLen; + // We can't map hops to pubkeys directly (hops are truncated hashes) + // but we know the packet's source node uses this hash_size + if (p.decoded_json) { + const d = JSON.parse(p.decoded_json); + const pk = d.pubKey || d.public_key; + if (pk && !hashSizeMap.has(pk)) hashSizeMap.set(pk, hs); + } + } + } + } catch {} + } + } for (const node of nodes) { node.hash_size = hashSizeMap.get(node.public_key) || null; }