up static routes

This commit is contained in:
Tykayn 2025-09-21 16:57:24 +02:00 committed by tykayn
parent 9cca0e4998
commit 1048f4af45
14 changed files with 2959 additions and 1076 deletions

View file

@ -0,0 +1,512 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Report Traffic Jam - OpenEventDatabase</title>
<script src="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.css" rel="stylesheet" />
<link rel="stylesheet" href="/static/demo_styles.css">
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
<script src="/static/demo_auth.js"></script>
</head>
<body>
<div class="container">
<div class="nav-links">
<a href="/demo">&larr; Back to Map</a>
<a href="/">API Information</a>
<a href="/event">View Events</a>
<a href="/demo/view-events">View Saved Events</a>
</div>
<h1>Report Road Issue</h1>
<!-- Hidden OAuth2 configuration for the JavaScript module -->
<input type="hidden" id="osmClientId" value="{{ client_id }}">
<input type="hidden" id="osmClientSecret" value="{{ client_secret }}">
<input type="hidden" id="osmRedirectUri" value="{{ client_redirect }}">
<!-- Authentication section will be rendered by JavaScript or server-side -->
<div id="auth-section">
{% if is_authenticated %}
<div class="auth-info">
<div>
<p>Logged in as <strong>{{ osm_username }}</strong></p>
<p><a href="https://www.openstreetmap.org/user/{{ osm_username }}" target="_blank">View OSM Profile</a></p>
<input type="hidden" id="osmUsername" value="{{ osm_username }}">
<input type="hidden" id="osmUserId" value="{{ osm_user_id }}">
</div>
</div>
{% else %}
<p>Authenticate with your OpenStreetMap account to include your username in the traffic report.</p>
<a href="https://www.openstreetmap.org/oauth2/authorize?client_id={{ client_id }}&redirect_uri={{ client_redirect }}&response_type=code&scope={{ client_authorizations }}" class="osm-login-btn">
<span class="osm-logo"></span>
Login with OpenStreetMap
</a>
{% endif %}
<script>
// Replace server-side auth section with JavaScript-rendered version if available
document.addEventListener('DOMContentLoaded', function() {
if (window.osmAuth) {
const clientId = document.getElementById('osmClientId').value;
const redirectUri = document.getElementById('osmRedirectUri').value;
const authSection = document.getElementById('auth-section');
// Only replace if osmAuth is loaded and has renderAuthSection method
if (osmAuth.renderAuthSection) {
authSection.innerHTML = osmAuth.renderAuthSection(clientId, redirectUri);
}
}
});
</script>
</div>
<h3>Select Issue Type</h3>
<div class="issue-buttons">
<div class="issue-button pothole" onclick="fillForm('pothole')">
<i class="fas fa-dot-circle"></i>
Pothole
</div>
<div class="issue-button obstacle" onclick="fillForm('obstacle')">
<i class="fas fa-exclamation-triangle"></i>
Obstacle
</div>
<div class="issue-button vehicle" onclick="fillForm('vehicle')">
<i class="fas fa-car"></i>
Vehicle on Side
</div>
<div class="issue-button danger" onclick="fillForm('danger')">
<i class="fas fa-skull-crossbones"></i>
Danger
</div>
</div>
<button id="geolocateBtn" class="geolocation-btn">
<span id="geolocateSpinner" class="loading" style="display: none;"></span>
Get My Current Location
</button>
<form id="trafficForm">
<div class="form-group">
<label for="label" class="required">Issue Description</label>
<input type="text" id="label" name="label" placeholder="e.g., Large pothole on Highway A1" required>
</div>
<input type="hidden" id="issueType" name="issueType" value="traffic.jam">
<div class="form-row">
<div class="form-group">
<label for="severity" class="required">Severity</label>
<select id="severity" name="severity" required>
<option value="low">Low (Minor issue)</option>
<option value="medium" selected>Medium (Moderate issue)</option>
<option value="high">High (Severe issue)</option>
</select>
</div>
<div class="form-group">
<label for="cause">Additional Details</label>
<input type="text" id="cause" name="cause" placeholder="e.g., Size, specific location details">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="start" class="required">Report Time</label>
<input type="datetime-local" id="start" name="start" required value="">
</div>
<div class="form-group">
<label for="stop" class="required">Estimated Clear Time</label>
<input type="datetime-local" id="stop" name="stop" required value="">
</div>
</div>
<div class="form-group">
<label for="where">Road/Location Name</label>
<input type="text" id="where" name="where" placeholder="e.g., Highway A1, Main Street">
</div>
<div class="form-group">
<label class="required">Location</label>
<div id="map"></div>
<div class="note">Click on the map to set the issue location or use the "Get My Current Location" button</div>
</div>
<button type="submit">Report Issue</button>
</form>
<div id="result"></div>
<a href="/demo/view-events" class="view-saved-events">
<i class="fas fa-map-marked-alt"></i> View All Saved Events on Map
</a>
</div>
<script>
// Set default date values (current time and +1 hour)
function setDefaultDates() {
const now = new Date();
const nowISO = now.toISOString().slice(0, 16); // Format: YYYY-MM-DDThh:mm
// Set start time to current time
document.getElementById('start').value = nowISO;
// Set end time to current time + 1 hour
const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000);
document.getElementById('stop').value = oneHourLater.toISOString().slice(0, 16);
}
// Call function to set default dates
setDefaultDates();
// Initialize the map
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
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
});
// Add marker on map click
map.on('click', function(e) {
marker.setLngLat(e.lngLat).addTo(map);
});
// Function to fill the form based on issue type
function fillForm(issueType) {
const labelInput = document.getElementById('label');
const issueTypeInput = document.getElementById('issueType');
const severitySelect = document.getElementById('severity');
// Save current marker position if it exists
let currentLngLat = marker.getLngLat ? marker.getLngLat() : null;
// Remove existing marker from the map
marker.remove();
// Set marker color based on issue type
let markerColor = '#ff3860'; // Default red color
switch(issueType) {
case 'pothole':
labelInput.value = 'Nid de poule';
issueTypeInput.value = 'road.hazard.pothole';
severitySelect.value = 'medium';
markerColor = '#ff9800';
break;
case 'obstacle':
labelInput.value = 'Obstacle';
issueTypeInput.value = 'road.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';
severitySelect.value = 'low';
markerColor = '#2196f3';
break;
case 'danger':
labelInput.value = 'Danger non classé';
issueTypeInput.value = 'road.hazard.danger';
severitySelect.value = 'high';
markerColor = '#9c27b0';
break;
default:
labelInput.value = 'Bouchon';
issueTypeInput.value = 'traffic.jam';
severitySelect.value = 'medium';
markerColor = '#ff3860';
}
// Create a new marker with the selected color
marker = new maplibregl.Marker({
draggable: true,
color: markerColor
});
// If there was a previous marker position, set the new marker at that position
if (currentLngLat) {
marker.setLngLat(currentLngLat).addTo(map);
}
}
// Handle geolocation button click
document.getElementById('geolocateBtn').addEventListener('click', function() {
// Show loading spinner
document.getElementById('geolocateSpinner').style.display = 'inline-block';
this.disabled = true;
// Check if geolocation is available
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
// Success callback
function(position) {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
// Set marker at current location
marker.setLngLat([lng, lat]).addTo(map);
// Center map on current location
map.flyTo({
center: [lng, lat],
zoom: 14
});
// Hide loading spinner
document.getElementById('geolocateSpinner').style.display = 'none';
document.getElementById('geolocateBtn').disabled = false;
// Show success message
showResult('Current location detected successfully', 'success');
// Try to get address using reverse geocoding
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
.then(response => response.json())
.then(data => {
if (data && data.address) {
// Extract road name or location
let location = '';
if (data.address.road) {
location = data.address.road;
if (data.address.city) {
location += `, ${data.address.city}`;
}
} else if (data.address.suburb) {
location = data.address.suburb;
if (data.address.city) {
location += `, ${data.address.city}`;
}
}
if (location) {
document.getElementById('where').value = location;
}
}
})
.catch(error => {
console.error('Error getting address:', error);
});
},
// Error callback
function(error) {
// Hide loading spinner
document.getElementById('geolocateSpinner').style.display = 'none';
document.getElementById('geolocateBtn').disabled = false;
// Show error message
let errorMsg = 'Unable to get your location. ';
switch(error.code) {
case error.PERMISSION_DENIED:
errorMsg += 'You denied the request for geolocation.';
break;
case error.POSITION_UNAVAILABLE:
errorMsg += 'Location information is unavailable.';
break;
case error.TIMEOUT:
errorMsg += 'The request to get your location timed out.';
break;
case error.UNKNOWN_ERROR:
errorMsg += 'An unknown error occurred.';
break;
}
showResult(errorMsg, 'error');
},
// Options
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
} else {
// Hide loading spinner
document.getElementById('geolocateSpinner').style.display = 'none';
document.getElementById('geolocateBtn').disabled = false;
// Show error message
showResult('Geolocation is not supported by your browser', 'error');
}
});
// Handle form submission
document.getElementById('trafficForm').addEventListener('submit', function(e) {
e.preventDefault();
// Get form values
const label = document.getElementById('label').value;
const issueType = document.getElementById('issueType').value;
const severity = document.getElementById('severity').value;
const cause = document.getElementById('cause').value;
const start = document.getElementById('start').value;
const stop = document.getElementById('stop').value;
const where = document.getElementById('where').value;
// Check if marker is set
if (!marker.getLngLat()) {
showResult('Please set a location by clicking on the map or using the geolocation button', 'error');
return;
}
// Get marker coordinates
const lngLat = marker.getLngLat();
// Create event object
const event = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [lngLat.lng, lngLat.lat]
},
properties: {
label: label,
type: 'unscheduled', // Road issues are typically unscheduled
what: issueType, // Category for the issue
'issue:severity': severity, // Custom property for severity
start: start,
stop: stop
}
};
// Add optional properties if provided
if (cause) {
event.properties['issue:details'] = cause;
}
if (where) {
event.properties.where = where;
}
// Add OSM username if authenticated - check both DOM element and osmAuth object
let osmUsernameValue = '';
// First check the DOM element (for backward compatibility)
const osmUsername = document.getElementById('osmUsername');
if (osmUsername && osmUsername.value) {
osmUsernameValue = osmUsername.value;
}
// Then check the osmAuth object (preferred method)
if (window.osmAuth && osmAuth.isUserAuthenticated()) {
osmUsernameValue = osmAuth.getUsername();
}
// Add the username to the event properties if available
if (osmUsernameValue) {
event.properties['reporter:osm'] = osmUsernameValue;
console.log(`Including OSM username in report: ${osmUsernameValue}`);
}
// Save event to localStorage
saveEventToLocalStorage(event);
// Submit event to API
fetch('/event', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(event)
})
.then(response => {
if (response.ok) {
return response.json();
} else {
return response.text().then(text => {
throw new Error(text || response.statusText);
});
}
})
.then(data => {
// Update the event in localStorage with the server-assigned ID
if (data.id) {
updateEventInLocalStorage(event, data.id);
}
showResult(`Issue reported successfully with ID: ${data.id}`, 'success');
// Add links to view the event
const resultElement = document.getElementById('result');
resultElement.innerHTML += `
<p>
<a href="/event/${data.id}" target="_blank">View Report on Server</a> |
<a href="/demo/view-events" target="_blank">View Saved Reports</a> |
<a href="/demo">Back to Map</a>
</p>`;
// Reset form
document.getElementById('trafficForm').reset();
// Set default dates again
setDefaultDates();
// Remove marker
marker.remove();
})
.catch(error => {
showResult(`Error reporting issue: ${error.message}`, 'error');
});
});
// Function to save event to localStorage
function saveEventToLocalStorage(event) {
// Get existing events from localStorage
let savedEvents = JSON.parse(localStorage.getItem('oedb_events') || '[]');
// Add timestamp to event for sorting
event.timestamp = new Date().toISOString();
// Add event to array
savedEvents.push(event);
// Save back to localStorage
localStorage.setItem('oedb_events', JSON.stringify(savedEvents));
console.log('Event saved to localStorage:', event);
}
// Function to update event in localStorage with server ID
function updateEventInLocalStorage(event, serverId) {
// Get existing events from localStorage
let savedEvents = JSON.parse(localStorage.getItem('oedb_events') || '[]');
// Find the event by its timestamp (assuming it was just added)
const eventIndex = savedEvents.findIndex(e =>
e.timestamp === event.timestamp &&
e.geometry.coordinates[0] === event.geometry.coordinates[0] &&
e.geometry.coordinates[1] === event.geometry.coordinates[1]);
if (eventIndex !== -1) {
// Add server ID to the event
savedEvents[eventIndex].properties.id = serverId;
// Save back to localStorage
localStorage.setItem('oedb_events', JSON.stringify(savedEvents));
console.log('Event updated in localStorage with server ID:', serverId);
}
}
// Show result message
function showResult(message, type) {
const resultElement = document.getElementById('result');
resultElement.textContent = message;
resultElement.className = type;
resultElement.style.display = 'block';
// Scroll to result
resultElement.scrollIntoView({ behavior: 'smooth' });
}
</script>
</body>
</html>

View file

@ -0,0 +1,284 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>View Saved Events - OpenEventDatabase</title>
<script src="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.css" rel="stylesheet" />
<link rel="stylesheet" href="/static/demo/demo_styles.css">
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
<script src="/static/demo/demo_auth.js"></script>
<style>
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<!-- Hidden OAuth2 configuration for the JavaScript module -->
<input type="hidden" id="osmClientId" value="{{ client_id }}">
<input type="hidden" id="osmClientSecret" value="{{ client_secret }}">
<input type="hidden" id="osmRedirectUri" value="{{ client_redirect }}">
<div class="map-overlay">
<h2>Your Saved Events</h2>
<div class="nav-links">
<a href="/demo">&larr; Back to Map</a>
<a href="/demo/traffic">Report New Issue</a>
</div>
<!-- Authentication section -->
<div id="auth-section" class="auth-section">
<h3>OpenStreetMap Authentication</h3>
<p>Authenticate with your OpenStreetMap account to include your username in reports.</p>
<a href="https://www.openstreetmap.org/oauth2/authorize?client_id={{ client_id }}&redirect_uri={{ client_redirect }}&response_type=code&scope=read_prefs" class="osm-login-btn">
<span class="osm-logo"></span>
Login with OpenStreetMap
</a>
<script>
// Replace server-side auth section with JavaScript-rendered version if available
document.addEventListener('DOMContentLoaded', function() {
if (window.osmAuth) {
const clientId = document.getElementById('osmClientId').value;
const redirectUri = document.getElementById('osmRedirectUri').value;
const authSection = document.getElementById('auth-section');
// Only replace if osmAuth is loaded and has renderAuthSection method
if (osmAuth.renderAuthSection) {
authSection.innerHTML = osmAuth.renderAuthSection(clientId, redirectUri);
}
}
});
</script>
</div>
<div id="event-count"></div>
<div id="event-list" class="event-list"></div>
<div class="controls">
<button id="refresh-btn">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<button id="clear-btn" class="danger">
<i class="fas fa-trash-alt"></i> Clear All
</button>
</div>
</div>
<script>
// Initialize the map
const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
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: '© <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors'
}));
// Store markers for later reference
let markers = [];
// Load events when the map is loaded
map.on('load', function() {
loadEvents();
});
// Function to load events from localStorage
function loadEvents() {
// Clear existing markers
markers.forEach(marker => marker.remove());
markers = [];
// Get events from localStorage
const savedEvents = JSON.parse(localStorage.getItem('oedb_events') || '[]');
// Update event count
document.getElementById('event-count').textContent =
`${savedEvents.length} event${savedEvents.length !== 1 ? 's' : ''} found`;
// Clear event list
const eventList = document.getElementById('event-list');
eventList.innerHTML = '';
if (savedEvents.length === 0) {
// Show message if no events
eventList.innerHTML = `
<div class="no-events">
<i class="fas fa-info-circle"></i>
<p>No saved events found.</p>
<p>Report road issues using the <a href="/demo/traffic">Report Issue</a> page.</p>
</div>
`;
return;
}
// Sort events by timestamp (newest first)
savedEvents.sort((a, b) => {
return new Date(b.timestamp) - new Date(a.timestamp);
});
// Create bounds object for fitting map to events
const bounds = new maplibregl.LngLatBounds();
// Add each event to the map and list
savedEvents.forEach((event, index) => {
// Get event properties
const properties = event.properties;
const coordinates = event.geometry.coordinates;
const label = properties.label || 'Unnamed Event';
const what = properties.what || 'unknown';
const timestamp = event.timestamp ? new Date(event.timestamp) : new Date();
const formattedDate = timestamp.toLocaleString();
// Determine event type and color
let eventType = 'other';
let markerColor = '#777';
if (what.includes('pothole')) {
eventType = 'pothole';
markerColor = '#ff9800';
} else if (what.includes('obstacle')) {
eventType = 'obstacle';
markerColor = '#f44336';
} else if (what.includes('vehicle')) {
eventType = 'vehicle';
markerColor = '#2196f3';
} else if (what.includes('danger')) {
eventType = 'danger';
markerColor = '#9c27b0';
} else if (what.includes('traffic')) {
eventType = 'traffic';
markerColor = '#ff3860';
}
// Create marker
const marker = new maplibregl.Marker({
color: markerColor
})
.setLngLat(coordinates)
.addTo(map);
// Store marker reference
markers.push(marker);
// Extend bounds with event coordinates
bounds.extend(coordinates);
// Create popup content
let popupContent = `<div class="popup-content">
<h3>${label}</h3>
<table>`;
// Add event properties to popup
for (const key in properties) {
if (key === 'label') continue; // Skip label as it's already in the title
let displayValue = properties[key];
let displayKey = key;
// Format key for display
displayKey = displayKey.replace(/:/g, ' ').replace(/([A-Z])/g, ' $1');
displayKey = displayKey.charAt(0).toUpperCase() + displayKey.slice(1);
// Format value for display
if (key === 'start' || key === 'stop') {
try {
displayValue = new Date(displayValue).toLocaleString();
} catch (e) {
// Keep original value if date parsing fails
}
}
popupContent += `
<tr>
<td>${displayKey}</td>
<td>${displayValue}</td>
</tr>`;
}
// Add timestamp to popup
popupContent += `
<tr>
<td>Reported</td>
<td>${formattedDate}</td>
</tr>`;
popupContent += `</table></div>`;
// Create popup
const popup = new maplibregl.Popup({
closeButton: true,
closeOnClick: true
}).setHTML(popupContent);
// Attach popup to marker
marker.setPopup(popup);
// Add event to list
const eventItem = document.createElement('div');
eventItem.className = 'event-item';
eventItem.innerHTML = `
<span class="event-type ${eventType}">${eventType}</span>
<h3>${label}</h3>
<p>${properties.where || 'Unknown location'}</p>
<p class="event-date">${formattedDate}</p>
`;
// Add click event to list item
eventItem.addEventListener('click', () => {
// Fly to event location
map.flyTo({
center: coordinates,
zoom: 14
});
// Open popup
marker.togglePopup();
});
// Add to list
eventList.appendChild(eventItem);
});
// Fit map to bounds if there are events
if (!bounds.isEmpty()) {
map.fitBounds(bounds, {
padding: 50,
maxZoom: 12
});
}
}
// Handle refresh button click
document.getElementById('refresh-btn').addEventListener('click', function() {
loadEvents();
});
// Handle clear button click
document.getElementById('clear-btn').addEventListener('click', function() {
if (confirm('Are you sure you want to clear all saved events? This cannot be undone.')) {
// Clear localStorage
localStorage.removeItem('oedb_events');
// Reload events
loadEvents();
}
});
</script>
</body>
</html>