From f18383fb9eac2da40a5f616d66376f6d53dfc10a Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sun, 21 Sep 2025 19:18:43 +0200 Subject: [PATCH] up source events --- oedb/resources/demo/demo_main.py | 42 ++- oedb/resources/demo/static/demo_styles.css | 92 +++++ oedb/resources/demo/templates/traffic.html | 382 +++++++++++++++++++-- 3 files changed, 489 insertions(+), 27 deletions(-) diff --git a/oedb/resources/demo/demo_main.py b/oedb/resources/demo/demo_main.py index 519164f..ba24e4c 100644 --- a/oedb/resources/demo/demo_main.py +++ b/oedb/resources/demo/demo_main.py @@ -152,10 +152,11 @@ class DemoMainResource:
  • Search Music Events
  • Search Sport Events
  • -

    + Add New Event

    +

    + Traffic event

    +

    + Any Event

    - + sources

    @@ -279,8 +280,8 @@ class DemoMainResource: // Function to fetch events from the API function fetchEvents() { - // Fetch events from the API - using default behavior to get currently active events - fetch('/event') + // Fetch events from the API - using the external API endpoint + fetch('https://api.openeventdatabase.org/event?') .then(response => response.json()) .then(data => { if (data.features && data.features.length > 0) { @@ -349,6 +350,10 @@ class DemoMainResource: displayValue = `
    ${JSON.stringify(value, null, 2)}
    `; } else if (typeof value === 'string' && value.startsWith('http')) { displayValue = `${value}`; + } else if (typeof value === 'string' && (key === 'start' || key === 'stop' || key.includes('date') || key.includes('time'))) { + // For date fields, show both the original date and the relative time + const relativeTime = getRelativeTimeString(value); + displayValue = `${value} (il y a ${relativeTime})`; } else { displayValue = String(value); } @@ -425,6 +430,35 @@ class DemoMainResource: }); } + // Function to calculate relative time (e.g., "2 hours 30 minutes ago") + function getRelativeTimeString(dateString) { + if (!dateString) return ''; + + // Parse the date string + const date = new Date(dateString); + if (isNaN(date.getTime())) return dateString; // Return original if invalid + + // Calculate time difference in milliseconds + const now = new Date(); + const diffMs = now - date; + + // Convert to hours and minutes + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); + + // Format the relative time string + let relativeTime = ''; + if (diffHours > 0) { + relativeTime += `${diffHours} heure${diffHours > 1 ? 's' : ''}`; + } + if (diffMinutes > 0 || diffHours === 0) { + if (diffHours > 0) relativeTime += ' '; + relativeTime += `${diffMinutes} minute${diffMinutes > 1 ? 's' : ''}`; + } + + return relativeTime || "à l instant"; + } + // Function to fit map to events bounds function fitMapToBounds(geojson) { if (geojson.features.length === 0) return; diff --git a/oedb/resources/demo/static/demo_styles.css b/oedb/resources/demo/static/demo_styles.css index b19b586..2669fc9 100644 --- a/oedb/resources/demo/static/demo_styles.css +++ b/oedb/resources/demo/static/demo_styles.css @@ -418,4 +418,96 @@ button:hover { .osm-login-btn{ padding: 1rem 2rem; border-radius: 5px; +} + +#report_issue_button{ + position: fixed; + bottom: 0.5rem; + right: 0.5rem; +} + +/* Tab styles */ +.tabs { + display: flex; + border-bottom: 1px solid #ddd; + margin-bottom: 20px; +} + +.tab-item { + padding: 10px 20px; + cursor: pointer; + border: 1px solid transparent; + border-bottom: none; + margin-right: 5px; + border-radius: 5px 5px 0 0; + background-color: #f8f9fa; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.tab-item i { + font-size: 16px; +} + +.tab-item:hover { + background-color: #e9ecef; +} + +.tab-item.active { + background-color: white; + border-color: #ddd; + border-bottom-color: white; + margin-bottom: -1px; + font-weight: bold; +} + +.tab-content { + margin-bottom: 20px; +} + +.tab-pane { + display: none; +} + +.tab-pane.active { + display: block; +} + +/* Family-based colors for issue buttons */ +.issue-button.road { + background-color: #2196f3; /* Blue for road issues */ + color: white; +} + +.issue-button.rail { + background-color: #673ab7; /* Purple for rail issues */ + color: white; +} + +.issue-button.weather { + background-color: #ff9800; /* Orange for weather issues */ + color: white; +} + +.issue-button.emergency { + background-color: #f44336; /* Red for emergency issues */ + color: white; +} + +/* Disabled button styles */ +button:disabled, +button[disabled] { + background-color: #cccccc !important; + color: #666666 !important; + cursor: not-allowed; + opacity: 0.7; +} + +/* Invalid form field styles */ +input:invalid, +select:invalid { + border-color: #f44336; + background-color: #fff8f8; } \ No newline at end of file diff --git a/oedb/resources/demo/templates/traffic.html b/oedb/resources/demo/templates/traffic.html index 39e74a0..651a1c0 100644 --- a/oedb/resources/demo/templates/traffic.html +++ b/oedb/resources/demo/templates/traffic.html @@ -63,22 +63,111 @@

    Select Issue Type

    -
    -
    - - Pothole + + +
    +
    + Road
    -
    - - Obstacle +
    + Rail
    -
    - - Vehicle on Side +
    + Weather
    -
    - - Danger +
    + Emergency +
    +
    + + +
    + +
    +
    +
    + + Pothole +
    +
    + + Obstacle +
    +
    + + Vehicle on Side +
    +
    + + Danger +
    +
    + + Accident +
    +
    + + Flooded Road +
    +
    + + Roadwork +
    +
    + + Black Traffic +
    +
    +
    + + +
    +
    +
    + + Unattended Luggage +
    +
    + + Delay +
    +
    + + Major Delay +
    +
    +
    + + +
    +
    +
    + + Flood Alert +
    +
    + + Thunderstorm +
    +
    + + Fog Warning +
    +
    +
    + + +
    +
    +
    + + Emergency Alert +
    +
    + + Daylight Saving +
    +
    @@ -134,7 +223,7 @@
    Click on the map to set the issue location or use the "Get My Current Location" button
    - +
    @@ -161,6 +250,49 @@ // Call function to set default dates setDefaultDates(); + // Tab switching functionality + document.addEventListener('DOMContentLoaded', function() { + // Get all tab items + const tabItems = document.querySelectorAll('.tab-item'); + + // Add click event listener to each tab item + tabItems.forEach(tab => { + tab.addEventListener('click', function() { + // Remove active class from all tab items + tabItems.forEach(item => item.classList.remove('active')); + + // Add active class to clicked tab item + this.classList.add('active'); + + // Get the tab name from data-tab attribute + const tabName = this.getAttribute('data-tab'); + + // Get all tab panes + const tabPanes = document.querySelectorAll('.tab-pane'); + + // Remove active class from all tab panes + tabPanes.forEach(pane => pane.classList.remove('active')); + + // Add active class to the corresponding tab pane + document.getElementById(tabName + '-tab').classList.add('active'); + + // Save active tab to localStorage + localStorage.setItem('activeTab', tabName); + }); + }); + + // Restore active tab from localStorage + const activeTab = localStorage.getItem('activeTab'); + if (activeTab) { + // Find the tab item with the saved tab name + const tabItem = document.querySelector(`.tab-item[data-tab="${activeTab}"]`); + if (tabItem) { + // Trigger click event on the tab item + tabItem.click(); + } + } + }); + // Initialize the map const map = new maplibregl.Map({ container: 'map', @@ -168,15 +300,73 @@ center: [2.2137, 46.2276], // Default center (center of metropolitan France) zoom: 5 }); - + // Add navigation controls map.addControl(new maplibregl.NavigationControl()); - + // Add marker for issue location let marker = new maplibregl.Marker({ draggable: true, color: '#ff3860' // Red color for traffic jam }); + + // Store existing traffic event markers + let existingMarkers = []; + + // Function to fetch and display existing traffic events + function fetchExistingTrafficEvents() { + // Clear existing markers first + existingMarkers.forEach(marker => marker.remove()); + existingMarkers = []; + + // Fetch traffic events from the API + fetch('https://api.openeventdatabase.org/event?what=traffic') + .then(response => { + if (response.ok) { + return response.json(); + } else { + throw new Error('Failed to fetch existing traffic events'); + } + }) + .then(data => { + if (data && data.features && Array.isArray(data.features)) { + // Add markers for each event + data.features.forEach(event => { + if (event.geometry && event.geometry.type === 'Point') { + const coords = event.geometry.coordinates; + // Create a gray marker for existing events + const eventMarker = new maplibregl.Marker({ + color: '#888888' // Gray color for existing events + }) + .setLngLat(coords) + .addTo(map); + + // Add popup with event details + const popup = new maplibregl.Popup({ offset: 25 }) + .setHTML(` +

    ${event.properties.label || 'Traffic Event'}

    +

    Type: ${event.properties.what || 'Unknown'}

    +

    Start: ${event.properties.start || 'Unknown'}

    +

    End: ${event.properties.stop || 'Unknown'}

    + `); + + eventMarker.setPopup(popup); + + // Store marker reference for later removal + existingMarkers.push(eventMarker); + } + }); + + console.log(`Loaded ${existingMarkers.length} existing traffic events`); + } + }) + .catch(error => { + console.error('Error fetching traffic events:', error); + }); + } + + // Fetch existing events when the map loads + map.on('load', fetchExistingTrafficEvents); // Add marker on map click map.on('click', function(e) { @@ -201,28 +391,100 @@ switch(issueType) { case 'pothole': labelInput.value = 'Nid de poule'; - issueTypeInput.value = 'road.hazard.pothole'; + issueTypeInput.value = 'traffic.hazard.pothole'; severitySelect.value = 'medium'; markerColor = '#ff9800'; break; case 'obstacle': labelInput.value = 'Obstacle'; - issueTypeInput.value = 'road.hazard.obstacle'; + issueTypeInput.value = 'traffic.hazard.obstacle'; severitySelect.value = 'high'; markerColor = '#f44336'; break; case 'vehicle': labelInput.value = 'Véhicule sur le bas côté de la route'; - issueTypeInput.value = 'road.hazard.vehicle'; + issueTypeInput.value = 'traffic.hazard.vehicle'; severitySelect.value = 'low'; markerColor = '#2196f3'; break; case 'danger': labelInput.value = 'Danger non classé'; - issueTypeInput.value = 'road.hazard.danger'; + issueTypeInput.value = 'traffic.hazard.danger'; severitySelect.value = 'high'; markerColor = '#9c27b0'; break; + case 'emergency_alert': + labelInput.value = 'Alerte d\'urgence (SAIP)'; + issueTypeInput.value = 'alert.emergency'; + severitySelect.value = 'high'; + markerColor = '#e91e63'; // Pink + break; + case 'daylight_saving': + labelInput.value = 'Période d\'heure d\'été'; + issueTypeInput.value = 'time.daylight.summer'; + severitySelect.value = 'low'; + markerColor = '#ffc107'; // Amber + break; + case 'accident': + labelInput.value = 'Accident de la route'; + issueTypeInput.value = 'traffic.accident'; + severitySelect.value = 'high'; + markerColor = '#d32f2f'; // Dark red + break; + case 'flooded_road': + labelInput.value = 'Route inondée'; + issueTypeInput.value = 'traffic.closed.flood'; + severitySelect.value = 'high'; + markerColor = '#1976d2'; // Blue + break; + case 'black_traffic': + labelInput.value = 'Période noire bison futé vers la province'; + issueTypeInput.value = 'traffic.forecast.black.out'; + severitySelect.value = 'high'; + markerColor = '#212121'; // Black + break; + case 'roadwork': + labelInput.value = 'Travaux'; + issueTypeInput.value = 'traffic.roadwork'; + severitySelect.value = 'medium'; + markerColor = '#ff5722'; // Deep orange + break; + case 'flood_danger': + labelInput.value = 'Vigilance rouge inondation'; + issueTypeInput.value = 'weather.danger.flood'; + severitySelect.value = 'high'; + markerColor = '#b71c1c'; // Dark red + break; + case 'thunderstorm_alert': + labelInput.value = 'Vigilance orange orages'; + issueTypeInput.value = 'weather.alert.thunderstorm'; + severitySelect.value = 'medium'; + markerColor = '#ff9800'; // Orange + break; + case 'fog_warning': + labelInput.value = 'Vigilance jaune brouillard'; + issueTypeInput.value = 'weather.warning.fog'; + severitySelect.value = 'low'; + markerColor = '#ffeb3b'; // Yellow + break; + case 'unattended_luggage': + labelInput.value = 'Bagage abandonné'; + issueTypeInput.value = 'public_transport.incident.unattended_luggage'; + severitySelect.value = 'medium'; + markerColor = '#673ab7'; // Deep purple + break; + case 'transport_delay': + labelInput.value = 'Retard'; + issueTypeInput.value = 'public_transport.delay'; + severitySelect.value = 'low'; + markerColor = '#ffc107'; // Amber + break; + case 'major_transport_delay': + labelInput.value = 'Retard important'; + issueTypeInput.value = 'public_transport.delay.major'; + severitySelect.value = 'medium'; + markerColor = '#ff5722'; // Deep orange + break; default: labelInput.value = 'Bouchon'; issueTypeInput.value = 'traffic.jam'; @@ -240,6 +502,9 @@ if (currentLngLat) { marker.setLngLat(currentLngLat).addTo(map); } + + // Validate form after filling in values + validateForm(); } // Handle geolocation button click @@ -272,6 +537,9 @@ // Show success message showResult('Current location detected successfully', 'success'); + // Validate form after setting marker + validateForm(); + // Try to get address using reverse geocoding fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`) .then(response => response.json()) @@ -341,10 +609,75 @@ } }); + // Form validation function + function validateForm() { + // Get all required form fields + const requiredFields = document.querySelectorAll('#trafficForm [required]'); + const submitButton = document.getElementById('report_issue_button'); + + // Check if all required fields are filled + let isValid = true; + + // Check each required field + requiredFields.forEach(field => { + if (!field.value.trim()) { + isValid = false; + } + }); + + // Check if marker is set + if (!marker || !marker.getLngLat()) { + isValid = false; + } + + // Enable or disable submit button based on form validity + submitButton.disabled = !isValid; + + // Add or remove disabled class + if (isValid) { + submitButton.classList.remove('disabled'); + } else { + submitButton.classList.add('disabled'); + } + + return isValid; + } + + // Add event listeners to form fields to trigger validation + document.addEventListener('DOMContentLoaded', function() { + // Get all form fields + const formFields = document.querySelectorAll('#trafficForm input, #trafficForm select'); + + // Add input event listener to each field + formFields.forEach(field => { + field.addEventListener('input', validateForm); + }); + + // Add change event listener to each field + formFields.forEach(field => { + field.addEventListener('change', validateForm); + }); + + // Validate form on page load + validateForm(); + }); + + // Update validation when marker is set + map.on('click', function() { + // Validate form after marker is set + setTimeout(validateForm, 100); + }); + // Handle form submission document.getElementById('trafficForm').addEventListener('submit', function(e) { e.preventDefault(); + // Validate form before submission + if (!validateForm()) { + showResult('Please fill in all required fields and set a location on the map', 'error'); + return; + } + // Get form values const label = document.getElementById('label').value; const issueType = document.getElementById('issueType').value; @@ -354,7 +687,7 @@ const stop = document.getElementById('stop').value; const where = document.getElementById('where').value; - // Check if marker is set + // Check if marker is set (redundant but kept for safety) if (!marker.getLngLat()) { showResult('Please set a location by clicking on the map or using the geolocation button', 'error'); return; @@ -413,7 +746,7 @@ saveEventToLocalStorage(event); // Submit event to API - fetch('/event', { + fetch('https://api.openeventdatabase.org/event', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -441,7 +774,7 @@ const resultElement = document.getElementById('result'); resultElement.innerHTML += `

    - View Report on Server | + View Report on Server | View Saved Reports | Back to Map

    `; @@ -452,6 +785,9 @@ setDefaultDates(); // Remove marker marker.remove(); + + // Refresh the map to show the new event along with existing ones + fetchExistingTrafficEvents(); }) .catch(error => { showResult(`Error reporting issue: ${error.message}`, 'error');