Crime-Detect / static /js /map.js
Noumankhan2005's picture
Update static/js/map.js
984614d verified
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 = `
<div class="analysis-card loading">
<h3>Analyzing Location...</h3>
<p>Latitude: ${latitude.toFixed(4)}, Longitude: ${longitude.toFixed(4)}</p>
<div class="loader"></div>
</div>`;
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 = `
<div class="analysis-card error">
<h3>Analysis Failed</h3>
<p>Could not fetch crime prediction data.</p>
<p>${error.message}</p>
</div>`;
}
}
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 = `
<div class="analysis-card">
<h3>Crime Risk Analysis</h3>
<p><b>Location:</b> ${latitude.toFixed(4)}, ${longitude.toFixed(4)}</p>
<p><b>Predicted Crime Intensity:</b> ${prediction.toFixed(2)}</p>
<p><b>Risk Level:</b> <span class="risk-level-${risk_level.toLowerCase()}">${risk_level}</span></p>
<p><b>Confidence:</b> ${confidence}</p>
<p><b>Explanation:</b> ${explanation}</p>
</div>
<div class="analysis-card">
<h4>Historical Data (12 Mo.)</h4>
<p>Total Crimes: ${historical_insights.total_crimes}</p>
<div class="chart-container">
<h5>Top Crime Types</h5>
<canvas id="crimeTypeChart"></canvas>
</div>
<div class="chart-container">
<h5>Crimes per Month</h5>
<canvas id="crimeTrendChart"></canvas>
</div>
</div>`;
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';
});