document.addEventListener('DOMContentLoaded', function() { // --- Global Variables --- let map; let currentMarker; let crimeTypeChartInstance = null; let crimeTrendChartInstance = null; // --- DOM Elements --- const liveMapTab = document.getElementById('liveMapTab'); const modelAnalyticsTab = document.getElementById('modelAnalyticsTab'); const liveMapContent = document.getElementById('liveMapContent'); const modelAnalyticsContent = document.getElementById('modelAnalyticsContent'); const analysisCardContainer = document.getElementById('analysis-card-container'); const mapElement = document.getElementById('map'); // --- Tab Switching Logic --- function showContent(element) { element.style.display = 'flex'; void element.offsetWidth; // Trigger reflow for transition element.classList.remove('hidden'); element.classList.add('active'); } function hideContent(element) { element.classList.add('hidden'); element.classList.remove('active'); setTimeout(() => { if (!element.classList.contains('active')) { element.style.display = 'none'; } }, 500); // Match CSS transition duration } liveMapTab.addEventListener('click', () => { if (!liveMapTab.classList.contains('active')) { liveMapTab.classList.add('active'); modelAnalyticsTab.classList.remove('active'); hideContent(modelAnalyticsContent); showContent(liveMapContent); if (map) { map.resize(); } } }); modelAnalyticsTab.addEventListener('click', () => { if (!modelAnalyticsTab.classList.contains('active')) { modelAnalyticsTab.classList.add('active'); liveMapTab.classList.remove('active'); hideContent(liveMapContent); showContent(modelAnalyticsContent); // Notify charts.js that its tab is active document.dispatchEvent(new Event('analyticsTabActivated')); } }); // --- Map Initialization --- function initializeMap() { if (!mapElement) return; mapboxgl.accessToken = 'pk.eyJ1IjoibmF1bWFua2hhbmtoYW4iLCJhIjoiY21qZ3RuejdyMDRuMzNmc2xvemZsY2ZueiJ9.om5voCTqgCN2xFZ6_CVDsg'; // Replace with your token if needed map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/dark-v11', center: [-87.6298, 41.8781], // Chicago zoom: 12, pitch: 55, bearing: -20, }); map.addControl(new mapboxgl.NavigationControl(), 'top-right'); map.on('load', () => { const layers = map.getStyle().layers; const labelLayerId = layers.find(layer => layer.type === 'symbol' && layer.layout['text-field'])?.id; map.addLayer({ 'id': '3d-buildings', 'source': 'composite', 'source-layer': 'building', 'filter': ['==', 'extrude', 'true'], 'type': 'fill-extrusion', 'minzoom': 15, 'paint': { 'fill-extrusion-color': '#aaa', 'fill-extrusion-height': ['interpolate', ['linear'], ['zoom'], 15, 0, 15.05, ['get', 'height']], 'fill-extrusion-base': ['interpolate', ['linear'], ['zoom'], 15, 0, 15.05, ['get', 'min_height']], 'fill-extrusion-opacity': 0.6 } }, labelLayerId); }); map.on('click', handleMapClick); } // --- Map and Prediction Logic --- function handleMapClick(e) { if (currentMarker) { currentMarker.remove(); } currentMarker = new mapboxgl.Marker({ color: '#FF0000' }) .setLngLat(e.lngLat) .addTo(map); map.flyTo({ center: e.lngLat, zoom: 15 }); fetchAndDisplayAnalysis(e.lngLat.lat, e.lngLat.lng); } async function fetchAndDisplayAnalysis(latitude, longitude) { if (!analysisCardContainer) return; analysisCardContainer.innerHTML = `

Analyzing Location...

Latitude: ${latitude.toFixed(4)}, Longitude: ${longitude.toFixed(4)}

`; try { const response = await fetch('/predict_crime', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ latitude, longitude }) }); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); renderAnalysisCard(data, latitude, longitude); } catch (error) { console.error('Error fetching crime prediction:', error); analysisCardContainer.innerHTML = `

Analysis Failed

Could not fetch crime prediction data.

${error.message}

`; } } function renderAnalysisCard(data, latitude, longitude) { const { prediction, risk_level, historical_insights } = data; const confidence = Math.max(0, 100 - (prediction * 10)).toFixed(0) + '% Confidence (in safety)'; let explanation = 'Low predicted crime intensity. This area appears relatively safe.'; if (risk_level === "High") { explanation = 'High predicted crime intensity. Consider exercising caution.'; } else if (risk_level === "Moderate") { explanation = 'Moderate predicted crime intensity. Be aware of your surroundings.'; } analysisCardContainer.innerHTML = `

Crime Risk Analysis

Location: ${latitude.toFixed(4)}, ${longitude.toFixed(4)}

Predicted Crime Intensity: ${prediction.toFixed(2)}

Risk Level: ${risk_level}

Confidence: ${confidence}

Explanation: ${explanation}

Historical Data (12 Mo.)

Total Crimes: ${historical_insights.total_crimes}

Top Crime Types
Crimes per Month
`; renderAnalysisCharts(historical_insights); } function renderAnalysisCharts({ crime_counts, crime_trends }) { // Destroy previous charts to prevent conflicts if (crimeTypeChartInstance) crimeTypeChartInstance.destroy(); if (crimeTrendChartInstance) crimeTrendChartInstance.destroy(); const crimeTypeCtx = document.getElementById('crimeTypeChart'); if (crimeTypeCtx && crime_counts && Object.keys(crime_counts).length > 0) { crimeTypeChartInstance = new Chart(crimeTypeCtx, createChartConfig('pie', crime_counts)); } const crimeTrendCtx = document.getElementById('crimeTrendChart'); if (crimeTrendCtx && crime_trends && Object.keys(crime_trends).length > 0) { crimeTrendChartInstance = new Chart(crimeTrendCtx, createChartConfig('line', crime_trends)); } } function createChartConfig(type, data) { const labels = Object.keys(data); const values = Object.values(data); const baseOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: 'var(--text-light)' } } }, scales: { x: { ticks: { color: 'var(--text-light)' }, grid: { color: 'rgba(255, 255, 255, 0.1)' } }, y: { ticks: { color: 'var(--text-light)' }, grid: { color: 'rgba(255, 255, 255, 0.1)' } } } }; if (type === 'pie') { return { type: 'pie', data: { labels, datasets: [{ data: values, backgroundColor: ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#C9CBCF', '#7A7A7A', '#2ECC71', '#E74C3C'], borderColor: 'var(--bg-dark)', }] }, options: { ...baseOptions, plugins: { legend: { position: 'right', ...baseOptions.plugins.legend } } } }; } if (type === 'line') { return { type: 'line', data: { labels, datasets: [{ label: 'Number of Crimes', data: values, borderColor: '#4BC0C0', backgroundColor: 'rgba(75, 192, 192, 0.2)', fill: true, tension: 0.3 }] }, options: { ...baseOptions, plugins: { legend: { display: false } } } }; } } // --- Initial Setup --- initializeMap(); liveMapContent.style.display = 'flex'; modelAnalyticsContent.style.display = 'none'; });