// Traffic.js - JavaScript for the traffic reporting page // Global variables let map; let marker; let geocoder; let currentPosition; let currentIssueType = null; let photoFiles = []; let panoramaxUploadUrl = ''; let panoramaxToken = ''; // Initialize the map when the page loads document.addEventListener('DOMContentLoaded', function() { initMap(); initTabs(); initForm(); // Get Panoramax configuration panoramaxUploadUrl = document.getElementById('panoramaxUploadUrl').value; panoramaxToken = document.getElementById('panoramaxToken').value; // Set up photo upload const photoInput = document.getElementById('photo'); if (photoInput) { photoInput.addEventListener('change', handlePhotoUpload); } // Set up form submission const reportForm = document.getElementById('reportForm'); if (reportForm) { reportForm.addEventListener('submit', submitReport); } }); // Initialize the map function initMap() { // Create the map map = new maplibregl.Map({ container: 'map', style: 'https://tiles.openfreemap.org/styles/liberty', center: [2.2137, 46.2276], // Default center (center of metropolitan France) zoom: 5 }); // Add navigation controls map.addControl(new maplibregl.NavigationControl()); // Add attribution control with OpenStreetMap attribution map.addControl(new maplibregl.AttributionControl({ customAttribution: '© OpenStreetMap contributors' })); // Try to get the user's current location if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( function(position) { currentPosition = [position.coords.longitude, position.coords.latitude]; // Center the map on the user's location map.flyTo({ center: currentPosition, zoom: 12 }); // Add a marker at the user's location marker = new maplibregl.Marker({ draggable: true }) .setLngLat(currentPosition) .addTo(map); // Update coordinates when marker is dragged marker.on('dragend', updateCoordinates); // Update the coordinates display updateCoordinates(); }, function(error) { console.error('Error getting location:', error); } ); } // Add click handler to the map map.on('click', function(e) { // If we don't have a marker yet, create one if (!marker) { marker = new maplibregl.Marker({ draggable: true }) .setLngLat(e.lngLat) .addTo(map); // Update coordinates when marker is dragged marker.on('dragend', updateCoordinates); } else { // Otherwise, move the existing marker marker.setLngLat(e.lngLat); } // Update the coordinates display updateCoordinates(); }); } // Initialize the tabs function initTabs() { const tabItems = document.querySelectorAll('.tab-item'); const tabPanes = document.querySelectorAll('.tab-pane'); tabItems.forEach(item => { item.addEventListener('click', function() { // Remove active class from all tabs tabItems.forEach(tab => tab.classList.remove('active')); tabPanes.forEach(pane => pane.classList.remove('active')); // Add active class to clicked tab this.classList.add('active'); // Show the corresponding tab content const tabId = this.getAttribute('data-tab'); document.getElementById(tabId + '-tab').classList.add('active'); }); }); } // Initialize the form function initForm() { // Set the current date and time as the default const now = new Date(); const dateTimeString = now.toISOString().slice(0, 16); const startTimeInput = document.getElementById('start'); if (startTimeInput) { startTimeInput.value = dateTimeString; } const stopTimeInput = document.getElementById('stop'); if (stopTimeInput) { // Set default end time to 1 hour from now const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000); stopTimeInput.value = oneHourLater.toISOString().slice(0, 16); } } // Update the coordinates display when the marker is moved function updateCoordinates() { if (!marker) return; const lngLat = marker.getLngLat(); currentPosition = [lngLat.lng, lngLat.lat]; // Update the coordinates display const coordinatesElement = document.getElementById('coordinates'); if (coordinatesElement) { coordinatesElement.textContent = `${lngLat.lat.toFixed(6)}, ${lngLat.lng.toFixed(6)}`; } // Update the hidden coordinates input const coordinatesInput = document.getElementById('coordinates-input'); if (coordinatesInput) { coordinatesInput.value = JSON.stringify({ type: 'Point', coordinates: [lngLat.lng, lngLat.lat] }); } } // Fill the form with predefined values based on the selected issue type function fillForm(issueType) { currentIssueType = issueType; // Get the form elements const whatInput = document.getElementById('what'); const labelInput = document.getElementById('label'); const descriptionInput = document.getElementById('description'); // Set default values based on the issue type switch (issueType) { case 'pothole': whatInput.value = 'road.hazard.pothole'; labelInput.value = 'Nid de poule'; descriptionInput.value = 'Nid de poule sur la chaussée'; break; case 'obstacle': whatInput.value = 'road.hazard.obstacle'; labelInput.value = 'Obstacle sur la route'; descriptionInput.value = 'Obstacle sur la chaussée'; break; case 'vehicle': whatInput.value = 'road.hazard.vehicle'; labelInput.value = 'Véhicule sur le bas côté'; descriptionInput.value = 'Véhicule arrêté sur le bas côté de la route'; break; case 'danger': whatInput.value = 'road.hazard.danger'; labelInput.value = 'Danger sur la route'; descriptionInput.value = 'Situation dangereuse sur la route'; break; case 'accident': whatInput.value = 'road.accident'; labelInput.value = 'Accident de la route'; descriptionInput.value = 'Accident de la circulation'; break; case 'flooded_road': whatInput.value = 'road.hazard.flood'; labelInput.value = 'Route inondée'; descriptionInput.value = 'Route inondée, circulation difficile'; break; case 'roadwork': whatInput.value = 'road.works'; labelInput.value = 'Travaux routiers'; descriptionInput.value = 'Travaux en cours sur la chaussée'; break; case 'traffic_jam': whatInput.value = 'road.traffic.jam'; labelInput.value = 'Embouteillage'; descriptionInput.value = 'Circulation dense, embouteillage'; break; // Add more cases for other issue types } // Scroll to the form document.getElementById('reportForm').scrollIntoView({ behavior: 'smooth' }); } // Handle photo upload function handlePhotoUpload(event) { const files = event.target.files; if (!files || files.length === 0) return; // Store the files for later upload photoFiles = Array.from(files); // Show preview of the photos const previewContainer = document.getElementById('photoPreview'); if (previewContainer) { previewContainer.innerHTML = ''; photoFiles.forEach(file => { const reader = new FileReader(); reader.onload = function(e) { const img = document.createElement('img'); img.src = e.target.result; img.className = 'photo-preview'; previewContainer.appendChild(img); }; reader.readAsDataURL(file); }); previewContainer.style.display = 'flex'; } } // Submit the report async function submitReport(event) { event.preventDefault(); // Show loading message const resultElement = document.getElementById('result'); resultElement.textContent = 'Submitting report...'; resultElement.className = ''; resultElement.style.display = 'block'; try { // Check if we have coordinates if (!currentPosition) { throw new Error('Please select a location on the map'); } // Get form values const formData = new FormData(event.target); const eventData = { type: formData.get('type') || 'unscheduled', what: formData.get('what'), label: formData.get('label'), description: formData.get('description'), start: new Date(formData.get('start')).toISOString(), stop: new Date(formData.get('stop')).toISOString(), geometry: { type: 'Point', coordinates: currentPosition } }; // Add username if authenticated const osmUsername = document.getElementById('osmUsername'); if (osmUsername && osmUsername.value) { eventData.source = { name: 'OpenStreetMap user', id: osmUsername.value }; } // Upload photos to Panoramax if available let photoUrls = []; if (photoFiles.length > 0 && panoramaxUploadUrl && panoramaxToken) { photoUrls = await uploadPhotos(photoFiles); if (photoUrls.length > 0) { eventData.media = photoUrls.map(url => ({ type: 'image', url: url })); } } // Submit the event to the API const response = await fetch('/event', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(eventData) }); if (!response.ok) { const errorText = await response.text(); throw new Error(errorText || response.statusText); } const data = await response.json(); // Show success message resultElement.textContent = `Report submitted successfully! Event ID: ${data.id}`; resultElement.className = 'success'; // Reset the form event.target.reset(); photoFiles = []; const previewContainer = document.getElementById('photoPreview'); if (previewContainer) { previewContainer.innerHTML = ''; previewContainer.style.display = 'none'; } // Initialize the form again initForm(); } catch (error) { // Show error message resultElement.textContent = `Error: ${error.message}`; resultElement.className = 'error'; } } // Upload photos to Panoramax async function uploadPhotos(files) { const urls = []; for (const file of files) { try { const uploadData = new FormData(); uploadData.append('file', file); const response = await fetch(panoramaxUploadUrl, { method: 'POST', headers: { 'Authorization': `Token ${panoramaxToken}` }, body: uploadData }); if (!response.ok) { console.error('Failed to upload photo:', await response.text()); continue; } const data = await response.json(); if (data.url) { urls.push(data.url); } } catch (error) { console.error('Error uploading photo:', error); } } return urls; }