Files

258 lines
8.6 KiB
JavaScript

let demoData = [];
let map, markers = [], polylines = [], pins = [], pinMode = false;
let currentTheme = 'light';
// Initialize
function initMap() {
map = L.map('map').setView([34.0522, -118.2437], 12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
loadDemoData();
setupDragAndDrop();
}
async function loadDemoData() {
try {
const res = await fetch('/api/demo-data');
demoData = await res.json();
visualizeData(demoData);
} catch (err) {
console.error('Demo data load failed:', err);
// Fallback
demoData = [
{ lat: 34.0522, lng: -118.2437, time: '2025-11-05T09:00:00' },
{ lat: 34.0740, lng: -118.2706, time: '2025-11-05T10:15:00' },
{ lat: 34.0195, lng: -118.2913, time: '2025-11-05T14:30:00' }
];
visualizeData(demoData);
}
}
function visualizeData(data) {
// Clear map
markers.forEach(m => map.removeLayer(m));
polylines.forEach(p => map.removeLayer(p));
markers = []; polylines = [];
data.forEach(point => {
const marker = L.marker([point.lat, point.lng]).addTo(map)
.bindPopup(`Lat: ${point.lat.toFixed(4)}, Lng: ${point.lng.toFixed(4)}<br>Time: ${point.time}`);
markers.push(marker);
});
if (data.length > 1) {
const path = data.map(p => [p.lat, p.lng]);
L.polyline(path, {color: 'blue', weight: 3}).addTo(map);
}
updateTimeline();
updatePinsList();
}
// Fixed Upload Function (No Event Dependency, Strict Prevention)
async function uploadFile(e) {
if (e) {
e.preventDefault();
e.stopPropagation();
}
const fileInput = document.getElementById('fileUpload');
const file = fileInput.files[0];
if (!file) {
alert('Select a file!');
return;
}
const formData = new FormData();
formData.append('file', file);
const statusEl = document.getElementById('uploadStatus');
statusEl.textContent = 'Uploading...';
statusEl.style.color = '#007bff';
try {
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
credentials: 'same-origin' // Ensure no CORS issues
});
if (!res.ok) {
const errorText = await res.text();
throw new Error(`Server responded with ${res.status}: ${errorText}`);
}
const data = await res.json();
if (data.success) {
demoData = data.points;
visualizeData(demoData);
statusEl.textContent = `Loaded ${data.points.length} points!`;
statusEl.style.color = 'green';
runVisitDetection();
fileInput.value = ''; // Reset input
} else {
throw new Error(data.error || 'Unknown upload error');
}
} catch (err) {
console.error('Upload failed:', err);
statusEl.textContent = `Error: ${err.message}`;
statusEl.style.color = 'red';
}
}
// Drag & Drop Setup (Full Map Coverage)
function setupDragAndDrop() {
const sidebarDrop = document.querySelector('.section:nth-child(2) > div'); // Sidebar drop area
const mapEl = document.getElementById('map');
[mapEl, sidebarDrop].forEach(el => {
el.addEventListener('dragover', handleDragOver);
el.addEventListener('dragleave', handleDragLeave);
el.addEventListener('drop', handleDrop);
});
}
function handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = 'copy';
e.currentTarget.style.background = '#e3f2fd'; // Visual feedback
}
function handleDragLeave(e) {
e.currentTarget.style.background = '';
}
function handleDrop(e) {
e.preventDefault();
e.stopPropagation();
e.currentTarget.style.background = '';
const files = e.dataTransfer.files;
if (files.length > 0) {
document.getElementById('fileUpload').files = files;
uploadFile();
}
}
// Rest of the functions (pins, analytics, etc.) remain the same as previous
function togglePinMode() {
pinMode = !pinMode;
if (pinMode) {
map.on('click', addPin);
alert('Pin mode on - click map to add pins!');
} else {
map.off('click', addPin);
alert('Pin mode off.');
}
}
async function addPin(e) {
const color = prompt('Pin color?') || 'red';
const comment = prompt('Comment?') || '';
let address = 'Loading...';
try {
const res = await fetch(`/api/reverse-geocode/${e.latlng.lat}/${e.latlng.lng}`);
const geoData = await res.json();
address = geoData.address || 'Unknown';
} catch {}
const pin = { id: Date.now(), lat: e.latlng.lat, lng: e.latlng.lng, color, comment, address, visible: true };
pins.push(pin);
L.circleMarker([pin.lat, pin.lng], { color: pin.color, fillColor: pin.color, fillOpacity: 0.7, radius: 8 })
.addTo(map)
.bindPopup(`Pin: ${pin.comment}<br>Address: ${address}<br>Coords: ${pin.lat.toFixed(4)}, ${pin.lng.toFixed(4)}`);
updatePinsList();
}
function updatePinsList() {
document.getElementById('pinsList').innerHTML = pins.map(p => `
<div class="pin-item" style="display: flex; align-items: center; padding: 10px; border-bottom: 1px solid #eee;">
<div class="pin-color" style="width: 20px; height: 20px; border-radius: 50%; background: ${p.color}; margin-right: 10px;"></div>
${p.comment || 'Pin'} - ${p.address} (${p.lat.toFixed(4)}, ${p.lng.toFixed(4)})
<button class="btn btn-secondary" style="margin-left: auto; padding: 5px 10px;" onclick="togglePinVisibility(${p.id})">Toggle</button>
</div>
`).join('');
}
function togglePinVisibility(id) {
const pin = pins.find(p => p.id == id);
if (pin) pin.visible = !pin.visible;
updatePinsList();
}
function runVisitDetection() {
if (demoData.length === 0) return alert('Load data first!');
const visits = demoData.filter((_, i) => i % 2 === 0);
visits.forEach(v => L.circle([v.lat, v.lng], {radius: 100, color: 'green', fillOpacity: 0.3}).addTo(map));
document.getElementById('analyticsInfo').innerHTML = `Detected ${visits.length} visits.`;
}
function analyzeTrips() {
if (demoData.length < 2) return alert('Need 2+ points!');
let totalDist = 0;
for (let i = 1; i < demoData.length; i++) {
const dLat = demoData[i].lat - demoData[i-1].lat;
const dLng = demoData[i].lng - demoData[i-1].lng;
totalDist += Math.sqrt(dLat * dLat + dLng * dLng) * 111; // km
}
const avgDist = totalDist / (demoData.length - 1);
const mode = avgDist > 5 ? 'Driving' : avgDist > 1 ? 'Cycling' : 'Walking';
document.getElementById('analyticsInfo').innerHTML = `Total: ${totalDist.toFixed(2)} km | Mode: ${mode}`;
// Recolor path
polylines.forEach(p => map.removeLayer(p));
const color = { Driving: 'red', Cycling: 'orange', Walking: 'green' }[mode];
const path = demoData.map(p => [p.lat, p.lng]);
L.polyline(path, {color, weight: 4}).addTo(map);
}
function updateTimeline() {
document.getElementById('timeline').innerHTML = demoData.map(p =>
`<div style="padding: 5px; border-left: 3px solid blue; margin: 5px 0;">${new Date(p.time).toLocaleString()}: [${p.lat.toFixed(4)}, ${p.lng.toFixed(4)}]</div>`
).join('');
}
function applyFilters() {
const date = document.getElementById('dateFilter').value;
const search = document.getElementById('locationFilter').value.toLowerCase();
markers.forEach(marker => {
const content = marker.getPopup().getContent().toLowerCase();
const show = (!date || content.includes(date)) && (!search || content.includes(search));
if (show) marker.addTo(map); else map.removeLayer(marker);
});
}
function clearFilters() {
location.reload();
}
function exportPins() {
document.getElementById('exportContent').textContent = JSON.stringify({ pins, data: demoData }, null, 2);
document.getElementById('export').style.display = 'block';
document.getElementById('overlay').style.display = 'block';
}
function closeExport() {
document.getElementById('export').style.display = 'none';
document.getElementById('overlay').style.display = 'none';
}
function copyToClipboard() {
navigator.clipboard.writeText(document.getElementById('exportContent').textContent).then(() => alert('Copied!'));
}
function toggleTheme() {
document.body.classList.toggle('dark-theme');
}
// Prevent form submit on upload button (extra safety)
document.getElementById('uploadForm').addEventListener('submit', e => e.preventDefault());
initMap();