// GigLez - Map Visualization let map; let markerLayer; let markerClusterGroup; let capturesData = []; // Frequency color mapping const FREQUENCY_COLORS = { 315: '#10b981', // Green 433: '#3b82f6', // Blue 868: '#f59e0b', // Orange 915: '#ef4444', // Red default: '#6b7280' // Gray }; // Initialize map document.addEventListener('DOMContentLoaded', () => { initMap(); loadCaptures(); setupMapControls(); }); function initMap() { // Create map centered on US map = L.map('map').setView([39.8283, -98.5795], 4); // Add OpenStreetMap tiles L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19 }).addTo(map); // Create marker cluster group markerClusterGroup = L.markerClusterGroup({ maxClusterRadius: 50, spiderfyOnMaxZoom: true, showCoverageOnHover: false, zoomToBoundsOnClick: true }); // Create regular marker layer markerLayer = L.layerGroup(); // Add cluster group by default map.addLayer(markerClusterGroup); // Export map to window for access from other modules window.map = map; } function setupMapControls() { // Cluster toggle document.getElementById('cluster-toggle').addEventListener('change', (e) => { if (e.target.checked) { map.removeLayer(markerLayer); map.addLayer(markerClusterGroup); } else { map.removeLayer(markerClusterGroup); map.addLayer(markerLayer); } renderMarkers(); }); // Frequency filter document.getElementById('frequency-filter').addEventListener('change', () => { renderMarkers(); }); // Heatmap toggle (placeholder) document.getElementById('heatmap-toggle').addEventListener('change', (e) => { if (e.target.checked) { alert('Heatmap view coming soon!'); e.target.checked = false; } }); } async function loadCaptures() { try { const response = await fetch('/api/v1/query/captures?limit=1000'); if (!response.ok) { console.error('Failed to load captures'); return; } const data = await response.json(); capturesData = data.captures || []; renderMarkers(); updateStats(); } catch (error) { console.error('Error loading captures:', error); } } function renderMarkers() { // Clear existing markers markerClusterGroup.clearLayers(); markerLayer.clearLayers(); // 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; }); } // Create markers filtered.forEach(capture => { const marker = createMarker(capture); // Add to both layers (only one will be visible) markerClusterGroup.addLayer(marker); markerLayer.addLayer(marker); }); // Update count document.getElementById('total-captures').textContent = filtered.length; } function createMarker(capture) { // Determine color based on frequency const freqMHz = Math.round(capture.frequency / 1e6); let color = FREQUENCY_COLORS.default; for (const [freq, col] of Object.entries(FREQUENCY_COLORS)) { if (freq === 'default') continue; if (Math.abs(freqMHz - parseInt(freq)) < 50) { color = col; break; } } // Create custom icon const icon = L.divIcon({ className: 'custom-marker', html: `
`, iconSize: [12, 12], iconAnchor: [6, 6] }); // Create marker const marker = L.marker([capture.latitude, capture.longitude], { icon }); // Create popup const popupContent = createPopupContent(capture); marker.bindPopup(popupContent); return marker; } function createPopupContent(capture) { const freqMHz = (capture.frequency / 1e6).toFixed(2); const date = new Date(capture.captured_at).toLocaleDateString(); let deviceInfo = 'Unknown Device'; if (capture.device_name) { deviceInfo = `${capture.device_name}`; if (capture.match_confidence) { deviceInfo += ` (${(capture.match_confidence * 100).toFixed(0)}% confidence)`; } } return `Frequency: ${freqMHz} MHz
Protocol: ${capture.protocol || 'RAW'}
Captured: ${date}
Location: ${capture.latitude.toFixed(6)}, ${capture.longitude.toFixed(6)}
${capture.accuracy ? `Accuracy: ±${capture.accuracy.toFixed(1)}m
` : ''}