From 8a6923c3b3d279d856c6254202abb672f2da231b Mon Sep 17 00:00:00 2001 From: you Date: Sat, 21 Mar 2026 00:25:55 +0000 Subject: [PATCH] Fix hash size labels and add label overlap prevention - Show '?' with grey background for nodes with null hash_size instead of '1B' - Add collision detection to offset overlapping repeater labels - Draw callout lines from offset labels back to true position - Re-deconflict labels on zoom change --- public/index.html | 2 +- public/map.js | 102 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/public/index.html b/public/index.html index 82e7e3b6..cb6dea5b 100644 --- a/public/index.html +++ b/public/index.html @@ -83,7 +83,7 @@ - + diff --git a/public/map.js b/public/map.js index 9c47b374..a037eb2d 100644 --- a/public/map.js +++ b/public/map.js @@ -61,10 +61,10 @@ } function makeRepeaterLabelIcon(node) { - var hashSize = node.hash_size || 1; var s = ROLE_STYLE['repeater'] || ROLE_STYLE.companion; - var label = hashSize + 'B'; - var html = '
' + label + '
'; + var label = node.hash_size ? node.hash_size + 'B' : '?'; + var bgColor = node.hash_size ? s.color : '#999'; + var html = '
' + label + '
'; return L.divIcon({ html: html, className: 'meshcore-marker meshcore-label-marker', @@ -144,6 +144,10 @@ userHasMoved = true; }); + map.on('zoomend', () => { + if (filters.hashLabels && !_renderingMarkers) renderMarkers(); + }); + markerLayer = L.layerGroup().addTo(map); routeLayer = L.layerGroup().addTo(map); @@ -367,7 +371,55 @@ map.fitBounds(bounds, { padding: [40, 40], maxZoom: 12 }); } + var _renderingMarkers = false; + var _lastDeconflictZoom = null; + + function deconflictLabels(labelMarkers, map) { + const placed = []; + const LABEL_W = 32; + const LABEL_H = 22; + const PAD = 4; + + 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 + ); + + 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 }; + + 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 }; + if (!overlaps(tryBox)) { + bestPt = tryPt; + box = tryBox; + break; + } + } + } + + placed.push(box); + m.adjustedLatLng = map.layerPointToLatLng(bestPt); + m.offset = Math.sqrt(Math.pow(bestPt.x - pt.x, 2) + Math.pow(bestPt.y - pt.y, 2)); + } + } + function renderMarkers() { + if (_renderingMarkers) return; + _renderingMarkers = true; + try { _renderMarkersInner(); } finally { _renderingMarkers = false; } + } + + function _renderMarkersInner() { markerLayer.clearLayers(); const filtered = nodes.filter(n => { @@ -376,15 +428,43 @@ return true; }); - for (const node of filtered) { - const icon = (node.role === 'repeater' && filters.hashLabels) ? makeRepeaterLabelIcon(node) : makeMarkerIcon(node.role || 'companion'); - const marker = L.marker([node.lat, node.lon], { - icon, - alt: `${node.name || 'Unknown'} (${node.role || 'node'})`, - }); + const labelMarkers = []; - marker.bindPopup(buildPopup(node), { maxWidth: 280 }); - markerLayer.addLayer(marker); + for (const node of filtered) { + const useLabel = node.role === 'repeater' && filters.hashLabels; + const icon = useLabel ? makeRepeaterLabelIcon(node) : makeMarkerIcon(node.role || 'companion'); + const latLng = L.latLng(node.lat, node.lon); + + if (useLabel) { + labelMarkers.push({ latLng, node, icon }); + } else { + const marker = L.marker(latLng, { + icon, + alt: `${node.name || 'Unknown'} (${node.role || 'node'})`, + }); + marker.bindPopup(buildPopup(node), { maxWidth: 280 }); + markerLayer.addLayer(marker); + } + } + + if (labelMarkers.length > 0) { + deconflictLabels(labelMarkers, map); + for (const m of labelMarkers) { + const pos = m.adjustedLatLng || m.latLng; + const marker = L.marker(pos, { + icon: m.icon, + alt: `${m.node.name || 'Unknown'} (${m.node.role || 'node'})`, + }); + marker.bindPopup(buildPopup(m.node), { maxWidth: 280 }); + markerLayer.addLayer(marker); + + if (m.offset > 15) { + const line = L.polyline([m.latLng, pos], { + color: '#999', weight: 1, dashArray: '3,3', opacity: 0.6 + }); + markerLayer.addLayer(line); + } + } } // Add observer markers