worked on making map and base all API driven

This commit is contained in:
Pablo Revilla
2025-11-02 11:39:29 -08:00
parent 47a22911ca
commit ed33bfe540

View File

@@ -1,25 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mesh Node Density Map</title>
<!-- Leaflet CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
/>
<meta charset="utf-8" />
<title>Mesh Nodes Population Heatmap</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
body { margin: 0; background: #000; font-family: monospace; color: #fff; }
body { margin: 0; background: #000; }
#map { height: 100vh; width: 100%; }
#legend {
position: absolute; bottom: 10px; right: 10px;
background: rgba(0,0,0,0.8); padding: 10px 14px;
background: rgba(0,0,0,0.8);
color: white; padding: 10px 14px;
font-family: monospace; font-size: 13px;
border-radius: 5px; z-index: 1000;
box-shadow: 0 0 10px rgba(0,0,0,0.6);
font-size: 13px;
}
.legend-item { display: flex; align-items: center; margin-bottom: 5px; }
.legend-color { width: 18px; height: 18px; margin-right: 6px; border-radius: 3px; }
@@ -31,23 +26,30 @@
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.heat/dist/leaflet-heat.js"></script>
<script>
const map = L.map("map");
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "© OpenStreetMap"
}).addTo(map);
let heatLayer = null;
let nodeCoords = [];
let hoverTooltip = L.tooltip({
permanent: false,
direction: "top",
className: "node-tooltip"
});
// --- Legend ---
const legend = document.getElementById("legend");
const legendItems = [
{ color: "#0000ff", label: "Low" },
{ color: "#00ffff", label: "Moderate" },
{ color: "#8000ff", label: "Moderate" },
{ color: "#00ffff", label: "Elevated" },
{ color: "#00ff00", label: "High" },
{ color: "#ffff00", label: "Very High" },
{ color: "#ff8000", label: "High Red" },
{ color: "#ff0000", label: "Extreme Red" },
{ color: "#8B0000", label: "Dark Red" },
{ color: "#800080", label: "Purple" }
{ color: "#ff0000", label: "Extreme" }
];
legendItems.forEach(item => {
const div = document.createElement("div");
@@ -62,19 +64,50 @@ legendItems.forEach(item => {
legend.appendChild(div);
});
// Node count summary
const summaryDiv = document.createElement("div");
summaryDiv.id = "summary";
legend.appendChild(summaryDiv);
// --- Load nodes and create heatmap ---
async function loadNodes() {
try {
const res = await fetch("/api/nodes?days_active=3");
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
const data = await res.json();
const nodes = data.nodes || [];
// --- Tile layer ---
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "© OpenStreetMap"
}).addTo(map);
nodeCoords = [];
const heatPoints = [];
nodes.forEach(node => {
const lat = node.last_lat / 1e7;
const lng = node.last_long / 1e7;
if (lat && lng && !isNaN(lat) && !isNaN(lng)) {
nodeCoords.push([lat, lng]);
heatPoints.push([lat, lng, 1.0]); // equal weight per node
}
});
if (heatLayer) map.removeLayer(heatLayer);
heatLayer = L.heatLayer(heatPoints, {
radius: 18, // smaller circles
blur: 10, // slightly tighter glow
maxZoom: 15,
minOpacity: 0.4,
gradient: {
0.0: "#0000ff", // deep blue
0.2: "#8000ff", // purple
0.4: "#00ffff", // cyan
0.6: "#00ff00", // green
0.8: "#ffff00", // yellow
0.9: "#ff8000", // orange
1.0: "#ff0000" // red
}
}).addTo(map);
await setMapBoundsFromConfig();
} catch (err) {
console.error("Failed to load nodes:", err);
}
}
// --- Map bounds ---
async function setMapBounds() {
async function setMapBoundsFromConfig() {
try {
const res = await fetch("/api/config");
const config = await res.json();
@@ -82,84 +115,42 @@ async function setMapBounds() {
const topLon = parseFloat(config.site.map_top_left_lon);
const bottomLat = parseFloat(config.site.map_bottom_right_lat);
const bottomLon = parseFloat(config.site.map_bottom_right_lon);
if ([topLat, topLon, bottomLat, bottomLon].some(v => isNaN(v))) throw new Error("Invalid bounds");
map.fitBounds([[topLat, topLon], [bottomLat, bottomLon]]);
} catch {
map.setView([37.77, -122.42], 10); // fallback
}
}
// --- Load nodes and create heatmap ---
async function loadNodes() {
try {
const res = await fetch("/api/nodes?days_active=3");
const data = await res.json();
const nodes = data.nodes || [];
nodeCoords = [];
if (nodes.length === 0) {
summaryDiv.textContent = "No recent nodes found.";
if (heatLayer) map.removeLayer(heatLayer);
return;
if ([topLat, topLon, bottomLat, bottomLon].some(v => isNaN(v))) {
throw new Error("Map bounds contain NaN");
}
const heatPoints = [];
nodes.forEach(node => {
if (node.last_lat && node.last_long) {
const lat = node.last_lat / 1e7;
const lon = node.last_long / 1e7;
nodeCoords.push([lat, lon]);
heatPoints.push([lat, lon, 1.0]);
}
});
if (heatLayer) map.removeLayer(heatLayer);
heatLayer = L.heatLayer(heatPoints, {
radius: 35,
blur: 12,
maxZoom: 15,
minOpacity: 0.4,
gradient: {
0.0: "#0000ff", // deep blue
0.2: "#00ffff", // cyan
0.4: "#00ff00", // green
0.6: "#ffff00", // yellow
0.75: "#ff8000", // orange
0.85: "#ff0000", // bright red
0.95: "#8B0000", // dark red
1.0: "#800080" // purple
}
}).addTo(map);
summaryDiv.textContent = `Nodes: ${nodes.length}`;
map.fitBounds([[topLat, topLon], [bottomLat, bottomLon]]);
} catch (err) {
console.error("Failed to load nodes:", err);
summaryDiv.textContent = "Error loading nodes.";
console.error("Failed to load map bounds from config:", err);
map.setView([37.77, -122.42], 9);
}
}
// --- Hover tooltip ---
const hoverTooltip = L.tooltip({ permanent: false, direction: 'top', offset: [0, -10] });
// --- Count nearby nodes ---
function countNearbyNodes(latlng, radiusMeters) {
let count = 0;
for (const [lat, lon] of nodeCoords) {
if (map.distance(latlng, L.latLng(lat, lon)) <= radiusMeters) count++;
const latR = radiusMeters / 111320; // meters per degree lat
const lngR = radiusMeters / (111320 * Math.cos(latlng.lat * Math.PI / 180));
for (const [lat, lng] of nodeCoords) {
if (Math.abs(lat - latlng.lat) <= latR && Math.abs(lng - latlng.lng) <= lngR)
count++;
}
return count;
}
// --- Tooltip on hover ---
map.on("mousemove", e => {
if (!nodeCoords.length) return;
const zoom = map.getZoom();
const radiusMeters = 2000 / Math.pow(2, zoom - 10); // dynamic radius
const radiusMeters = 2000 / Math.pow(2, zoom - 10); // dynamic nearness by zoom
const count = countNearbyNodes(e.latlng, radiusMeters);
// Only show tooltip if count > 0
if (count > 0) {
hoverTooltip.setLatLng(e.latlng)
hoverTooltip
.setLatLng(e.latlng)
.setContent(`${count} nodes nearby (${radiusMeters.toFixed(0)}m radius)`)
.addTo(map);
} else {
@@ -167,13 +158,7 @@ map.on("mousemove", e => {
}
});
map.on("mouseout", () => map.closeTooltip(hoverTooltip));
// --- Run ---
(async () => {
await setMapBounds();
await loadNodes();
})();
loadNodes();
</script>
</body>
</html>