diff --git a/static/js/map.js b/static/js/map.js
index 5fcbc98..4d716aa 100644
--- a/static/js/map.js
+++ b/static/js/map.js
@@ -3,7 +3,9 @@
let map;
let markerLayer;
let markerClusterGroup;
+let heatmapLayer;
let capturesData = [];
+let heatmapEnabled = false;
// Frequency color mapping
const FREQUENCY_COLORS = {
@@ -64,14 +66,33 @@ function setupMapControls() {
// Frequency filter
document.getElementById('frequency-filter').addEventListener('change', () => {
- renderMarkers();
+ if (heatmapEnabled) {
+ renderHeatmap();
+ } else {
+ renderMarkers();
+ }
});
- // Heatmap toggle (placeholder)
+ // Heatmap toggle
document.getElementById('heatmap-toggle').addEventListener('change', (e) => {
- if (e.target.checked) {
- alert('Heatmap view coming soon!');
- e.target.checked = false;
+ heatmapEnabled = e.target.checked;
+ if (heatmapEnabled) {
+ // Hide markers, show heatmap
+ map.removeLayer(markerClusterGroup);
+ map.removeLayer(markerLayer);
+ renderHeatmap();
+ } else {
+ // Hide heatmap, show markers
+ if (heatmapLayer) {
+ map.removeLayer(heatmapLayer);
+ }
+ // Re-add appropriate marker layer based on cluster toggle
+ const clusterEnabled = document.getElementById('cluster-toggle').checked;
+ if (clusterEnabled) {
+ map.addLayer(markerClusterGroup);
+ } else {
+ map.addLayer(markerLayer);
+ }
}
});
}
@@ -199,5 +220,57 @@ function updateStats() {
document.getElementById('unique-devices').textContent = uniqueDevices;
}
+function renderHeatmap() {
+ // Remove existing heatmap layer if it exists
+ if (heatmapLayer) {
+ map.removeLayer(heatmapLayer);
+ }
+
+ // Get frequency filter
+ const frequencyFilter = document.getElementById('frequency-filter').value;
+
+ // Filter captures
+ let filtered = capturesData;
+
+ if (frequencyFilter) {
+ const targetFreq = parseInt(frequencyFilter) * 1e6;
+ filtered = capturesData.filter(c => {
+ const freq = c.frequency / 1e6;
+ return Math.abs(freq - parseInt(frequencyFilter)) < 50;
+ });
+ }
+
+ // Convert captures to heatmap data format
+ // Format: [[lat, lng, intensity], ...]
+ const heatmapData = filtered.map(capture => {
+ // Use match confidence as intensity (default to 0.5 if no match)
+ const intensity = capture.match_confidence || 0.5;
+ return [capture.latitude, capture.longitude, intensity];
+ });
+
+ console.log(`Rendering heatmap with ${heatmapData.length} points`);
+
+ // Create heatmap layer
+ heatmapLayer = L.heatLayer(heatmapData, {
+ radius: 25, // Radius of each point
+ blur: 15, // Amount of blur
+ maxZoom: 17, // Zoom level where max intensity is reached
+ max: 1.0, // Maximum point intensity
+ gradient: { // Custom color gradient
+ 0.0: 'blue',
+ 0.3: 'cyan',
+ 0.5: 'lime',
+ 0.7: 'yellow',
+ 1.0: 'red'
+ }
+ });
+
+ // Add to map
+ map.addLayer(heatmapLayer);
+
+ // Update count
+ document.getElementById('total-captures').textContent = filtered.length;
+}
+
// Export for external use
window.reloadMapData = loadCaptures;
diff --git a/templates/index.html b/templates/index.html
index 8075e4e..675de81 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -280,6 +280,7 @@
+