// Initialize the map const map = new maplibregl.Map({ container: 'map', style: 'https://tiles.openfreemap.org/styles/liberty', center: [2.3522, 48.8566], // Default center (Paris) zoom: 4 }); // Add navigation controls map.addControl(new maplibregl.NavigationControl()); // Add draw controls for polygon let drawnPolygon = null; let drawingMode = false; let points = []; let lineString = null; let polygonFill = null; // Add a button to toggle drawing mode const drawButton = document.createElement('button'); drawButton.textContent = 'Draw Polygon'; drawButton.style.position = 'absolute'; drawButton.style.top = '10px'; drawButton.style.right = '10px'; drawButton.style.zIndex = '1'; drawButton.style.padding = '5px 10px'; drawButton.style.backgroundColor = '#0078ff'; drawButton.style.color = 'white'; drawButton.style.border = 'none'; drawButton.style.borderRadius = '3px'; drawButton.style.cursor = 'pointer'; document.getElementById('map').appendChild(drawButton); drawButton.addEventListener('click', () => { drawingMode = !drawingMode; drawButton.textContent = drawingMode ? 'Cancel Drawing' : 'Draw Polygon'; if (!drawingMode) { // Clear the drawing points = []; if (lineString) { map.removeLayer('line-string'); map.removeSource('line-string'); lineString = null; } if (polygonFill) { map.removeLayer('polygon-fill'); map.removeSource('polygon-fill'); polygonFill = null; } } }); // Handle map click events for drawing map.on('click', (e) => { if (!drawingMode) return; const coords = [e.lngLat.lng, e.lngLat.lat]; points.push(coords); // If we have at least 3 points, create a polygon if (points.length >= 3) { const polygonCoords = [...points, points[0]]; // Close the polygon // Create or update the line string if (lineString) { map.removeLayer('line-string'); map.removeSource('line-string'); } lineString = { type: 'Feature', geometry: { type: 'LineString', coordinates: polygonCoords } }; map.addSource('line-string', { type: 'geojson', data: lineString }); map.addLayer({ id: 'line-string', type: 'line', source: 'line-string', paint: { 'line-color': '#0078ff', 'line-width': 2 } }); // Create or update the polygon fill if (polygonFill) { map.removeLayer('polygon-fill'); map.removeSource('polygon-fill'); } polygonFill = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [polygonCoords] } }; map.addSource('polygon-fill', { type: 'geojson', data: polygonFill }); map.addLayer({ id: 'polygon-fill', type: 'fill', source: 'polygon-fill', paint: { 'fill-color': '#0078ff', 'fill-opacity': 0.2 } }); // Store the drawn polygon for search drawnPolygon = { type: 'Polygon', coordinates: [polygonCoords] }; } }); // Handle custom date range selection document.getElementById('when').addEventListener('change', function() { const customDateGroup = document.getElementById('customDateGroup'); const customDateEndGroup = document.getElementById('customDateEndGroup'); if (this.value === 'custom') { customDateGroup.style.display = 'block'; customDateEndGroup.style.display = 'block'; } else { customDateGroup.style.display = 'none'; customDateEndGroup.style.display = 'none'; } }); // Handle form submission document.getElementById('searchForm').addEventListener('submit', function(e) { e.preventDefault(); // Show loading message const resultElement = document.getElementById('result'); resultElement.textContent = 'Searching...'; resultElement.className = ''; resultElement.style.display = 'block'; // Get form values const formData = new FormData(this); const params = new URLSearchParams(); // Add form fields to params for (const [key, value] of formData.entries()) { if (value) { params.append(key, value); } } // Handle custom date range if (formData.get('when') === 'custom') { params.delete('when'); } else { params.delete('start'); params.delete('stop'); } // Prepare the request let url = '/event/search'; let method = 'POST'; let body = null; // If we have a drawn polygon, use it for the search if (drawnPolygon) { body = JSON.stringify({ geometry: drawnPolygon }); } else if (formData.get('near') || formData.get('bbox')) { // If we have near or bbox parameters, use GET request url = '/event?' + params.toString(); method = 'GET'; } else { // Default to a simple point search in Paris if no spatial filter is provided body = JSON.stringify({ geometry: { type: 'Point', coordinates: [2.3522, 48.8566] } }); } // Make the request fetch(url + (method === 'GET' ? '' : '?' + params.toString()), { method: method, headers: { 'Content-Type': 'application/json' }, body: method === 'POST' ? body : null }) .then(response => { if (response.ok) { return response.json(); } else { return response.text().then(text => { throw new Error(text || response.statusText); }); } }) .then(data => { // Show success message resultElement.textContent = `Found ${data.features ? data.features.length : 0} events`; resultElement.className = 'success'; // Display results displayResults(data); }) .catch(error => { // Show error message resultElement.textContent = `Error: ${error.message}`; resultElement.className = 'error'; // Hide results container document.getElementById('resultsContainer').style.display = 'none'; }); }); // Function to display search results function displayResults(data) { // Show results container document.getElementById('resultsContainer').style.display = 'block'; // Initialize results map const resultsMap = new maplibregl.Map({ container: 'resultsMap', style: 'https://tiles.openfreemap.org/styles/liberty', center: [2.3522, 48.8566], // Default center (Paris) zoom: 4 }); // Add navigation controls to results map resultsMap.addControl(new maplibregl.NavigationControl()); // Add events to the map resultsMap.on('load', function() { // Add events as a source resultsMap.addSource('events', { type: 'geojson', data: data }); // Add a circle layer for events resultsMap.addLayer({ id: 'events-circle', type: 'circle', source: 'events', paint: { 'circle-radius': 8, 'circle-color': '#FF5722', 'circle-stroke-width': 2, 'circle-stroke-color': '#FFFFFF' } }); // Add popups for events if (data.features) { data.features.forEach(feature => { const coordinates = feature.geometry.coordinates.slice(); const properties = feature.properties; // Create popup content let popupContent = '
'; popupContent += `

${properties.label || 'Event'}

`; // Display key properties if (properties.what) { popupContent += `

Type: ${properties.what}

`; } if (properties.where) { popupContent += `

Where: ${properties.where}

`; } if (properties.start) { popupContent += `

Start: ${properties.start}

`; } if (properties.stop) { popupContent += `

End: ${properties.stop}

`; } // Add link to view full event popupContent += `

View Event

`; popupContent += '
'; // Create popup const popup = new maplibregl.Popup({ closeButton: true, closeOnClick: true }).setHTML(popupContent); // Add marker with popup new maplibregl.Marker({ color: '#FF5722' }) .setLngLat(coordinates) .setPopup(popup) .addTo(resultsMap); }); // Fit map to events bounds if (data.features.length > 0) { const bounds = new maplibregl.LngLatBounds(); data.features.forEach(feature => { bounds.extend(feature.geometry.coordinates); }); resultsMap.fitBounds(bounds, { padding: 50, maxZoom: 12 }); } } }); // Populate table with results const tableBody = document.getElementById('resultsTable').getElementsByTagName('tbody')[0]; tableBody.innerHTML = ''; if (data.features) { data.features.forEach(feature => { const properties = feature.properties; const row = tableBody.insertRow(); row.insertCell(0).textContent = properties.id || ''; row.insertCell(1).textContent = properties.label || ''; row.insertCell(2).textContent = properties.type || ''; row.insertCell(3).textContent = properties.what || ''; row.insertCell(4).textContent = properties.where || ''; row.insertCell(5).textContent = properties.start || ''; row.insertCell(6).textContent = properties.stop || ''; }); } // Store the data for download window.searchResults = data; } // Handle tab switching document.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', () => { // Remove active class from all buttons and content document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); // Add active class to clicked button and corresponding content button.classList.add('active'); document.getElementById(button.dataset.tab).classList.add('active'); }); }); // Handle CSV download document.getElementById('downloadCsv').addEventListener('click', () => { if (!window.searchResults || !window.searchResults.features) { alert('No search results to download'); return; } // Convert GeoJSON to CSV let csv = 'id,label,type,what,where,start,stop,longitude,latitude\n'; window.searchResults.features.forEach(feature => { const p = feature.properties; const coords = feature.geometry.coordinates; csv += `"${p.id || ''}","${p.label || ''}","${p.type || ''}","${p.what || ''}","${p.where || ''}","${p.start || ''}","${p.stop || ''}",${coords[0]},${coords[1]}\n`; }); // Create download link const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'search_results.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }); // Handle JSON download document.getElementById('downloadJson').addEventListener('click', () => { if (!window.searchResults) { alert('No search results to download'); return; } // Create download link const blob = new Blob([JSON.stringify(window.searchResults, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'search_results.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); });