794 lines
No EOL
38 KiB
Python
794 lines
No EOL
38 KiB
Python
"""
|
|
Traffic jam reporting resource for the OpenEventDatabase.
|
|
"""
|
|
|
|
import falcon
|
|
import os
|
|
import requests
|
|
from oedb.utils.logging import logger
|
|
from oedb.utils.db import load_env_from_file
|
|
|
|
class DemoTrafficResource:
|
|
"""
|
|
Resource for the traffic jam reporting page.
|
|
Handles the /demo/traffic endpoint.
|
|
"""
|
|
|
|
def on_get(self, req, resp):
|
|
"""
|
|
Handle GET requests to the /demo/traffic endpoint.
|
|
Returns an HTML page with a form for reporting traffic jams.
|
|
|
|
Args:
|
|
req: The request object.
|
|
resp: The response object.
|
|
"""
|
|
logger.info("Processing GET request to /demo/traffic")
|
|
|
|
try:
|
|
# Set content type to HTML
|
|
resp.content_type = 'text/html'
|
|
|
|
# Load environment variables from .env file
|
|
load_env_from_file()
|
|
|
|
# Get OAuth2 configuration parameters
|
|
client_id = os.getenv("CLIENT_ID", "")
|
|
client_secret = os.getenv("CLIENT_SECRET", "")
|
|
client_authorizations = os.getenv("CLIENT_AUTORIZATIONS", "read_prefs")
|
|
client_redirect = os.getenv("CLIENT_REDIRECT", "")
|
|
|
|
# Check if we have an authorization code in the query parameters
|
|
auth_code = req.params.get('code', None)
|
|
auth_state = req.params.get('state', None)
|
|
|
|
# Variables to track authentication state
|
|
is_authenticated = False
|
|
osm_username = ""
|
|
osm_user_id = ""
|
|
|
|
# If we have an authorization code, exchange it for an access token
|
|
if auth_code:
|
|
logger.info(f"Received authorization code: {auth_code}")
|
|
|
|
try:
|
|
# Exchange authorization code for access token
|
|
token_url = "https://www.openstreetmap.org/oauth2/token"
|
|
token_data = {
|
|
"grant_type": "authorization_code",
|
|
"code": auth_code,
|
|
"redirect_uri": client_redirect,
|
|
"client_id": client_id,
|
|
"client_secret": client_secret
|
|
}
|
|
|
|
token_response = requests.post(token_url, data=token_data)
|
|
token_response.raise_for_status()
|
|
token_info = token_response.json()
|
|
|
|
access_token = token_info.get("access_token")
|
|
|
|
if access_token:
|
|
# Use access token to get user information
|
|
user_url = "https://api.openstreetmap.org/api/0.6/user/details.json"
|
|
headers = {"Authorization": f"Bearer {access_token}"}
|
|
|
|
user_response = requests.get(user_url, headers=headers)
|
|
user_response.raise_for_status()
|
|
user_info = user_response.json()
|
|
|
|
# Extract user information
|
|
user = user_info.get("user", {})
|
|
osm_username = user.get("display_name", "")
|
|
osm_user_id = user.get("id", "")
|
|
|
|
if osm_username:
|
|
is_authenticated = True
|
|
logger.info(f"User authenticated: {osm_username} (ID: {osm_user_id})")
|
|
else:
|
|
logger.error("Failed to get OSM username from user details")
|
|
else:
|
|
logger.error("Failed to get access token from token response")
|
|
except Exception as e:
|
|
logger.error(f"Error during OAuth2 token exchange: {e}")
|
|
|
|
# Create HTML response with form
|
|
# Start with the common HTML header
|
|
html_header = f"""
|
|
<!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" />
|
|
<style>
|
|
body {{
|
|
margin: 0;
|
|
padding: 20px;
|
|
font-family: Arial, sans-serif;
|
|
background-color: #f5f5f5;
|
|
}}
|
|
.container {{
|
|
max-width: 1000px;
|
|
margin: 0 auto;
|
|
background-color: white;
|
|
padding: 20px;
|
|
border-radius: 5px;
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
}}
|
|
h1 {{
|
|
margin-top: 0;
|
|
color: #333;
|
|
}}
|
|
.form-group {{
|
|
margin-bottom: 15px;
|
|
}}
|
|
label {{
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
}}
|
|
input[type="text"],
|
|
input[type="datetime-local"],
|
|
select,
|
|
textarea {{
|
|
width: 100%;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
box-sizing: border-box;
|
|
font-size: 14px;
|
|
}}
|
|
.required:after {{
|
|
content: " *";
|
|
color: red;
|
|
}}
|
|
.form-row {{
|
|
display: flex;
|
|
gap: 15px;
|
|
}}
|
|
.form-row .form-group {{
|
|
flex: 1;
|
|
}}
|
|
button {{
|
|
background-color: #0078ff;
|
|
color: white;
|
|
border: none;
|
|
padding: 10px 15px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
}}
|
|
button:hover {{
|
|
background-color: #0056b3;
|
|
}}
|
|
.note {{
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-top: 5px;
|
|
}}
|
|
#map {{
|
|
width: 100%;
|
|
height: 300px;
|
|
margin-bottom: 15px;
|
|
border-radius: 4px;
|
|
}}
|
|
#result {{
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
display: none;
|
|
}}
|
|
#result.success {{
|
|
background-color: #d4edda;
|
|
border: 1px solid #c3e6cb;
|
|
color: #155724;
|
|
}}
|
|
#result.error {{
|
|
background-color: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
color: #721c24;
|
|
}}
|
|
.nav-links {{
|
|
margin-bottom: 20px;
|
|
}}
|
|
.nav-links a {{
|
|
color: #0078ff;
|
|
text-decoration: none;
|
|
margin-right: 15px;
|
|
}}
|
|
.nav-links a:hover {{
|
|
text-decoration: underline;
|
|
}}
|
|
.geolocation-btn {{
|
|
background-color: #28a745;
|
|
margin-bottom: 10px;
|
|
}}
|
|
.geolocation-btn:hover {{
|
|
background-color: #218838;
|
|
}}
|
|
.loading {{
|
|
display: inline-block;
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 3px solid rgba(255,255,255,.3);
|
|
border-radius: 50%;
|
|
border-top-color: #fff;
|
|
animation: spin 1s ease-in-out infinite;
|
|
margin-right: 10px;
|
|
vertical-align: middle;
|
|
}}
|
|
@keyframes spin {{
|
|
to {{ transform: rotate(360deg); }}
|
|
}}
|
|
.auth-section {{
|
|
background-color: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid #e9ecef;
|
|
}}
|
|
.auth-section h3 {{
|
|
margin-top: 0;
|
|
margin-bottom: 10px;
|
|
}}
|
|
.auth-info {{
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}}
|
|
.auth-info img {{
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
}}
|
|
.osm-login-btn {{
|
|
background-color: #7ebc6f;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}}
|
|
.osm-login-btn:hover {{
|
|
background-color: #6ba75e;
|
|
}}
|
|
.osm-logo {{
|
|
width: 20px;
|
|
height: 20px;
|
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm-1.66 4.322c.944 0 1.71.766 1.71 1.71s-.766 1.71-1.71 1.71-1.71-.766-1.71-1.71.766-1.71 1.71-1.71zm6.482 13.356H4.322v-1.71h12.5v1.71zm0-3.322H4.322v-1.71h12.5v1.71zm0-3.322H4.322v-1.71h12.5v1.71z"/></svg>');
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
}}
|
|
.issue-buttons {{
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}}
|
|
.issue-button {{
|
|
flex: 1;
|
|
min-width: 120px;
|
|
text-align: center;
|
|
padding: 15px 10px;
|
|
border-radius: 5px;
|
|
font-weight: bold;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
}}
|
|
.issue-button i {{
|
|
font-size: 24px;
|
|
}}
|
|
.issue-button.pothole {{
|
|
background-color: #ff9800;
|
|
color: white;
|
|
}}
|
|
.issue-button.obstacle {{
|
|
background-color: #f44336;
|
|
color: white;
|
|
}}
|
|
.issue-button.vehicle {{
|
|
background-color: #2196f3;
|
|
color: white;
|
|
}}
|
|
.issue-button.danger {{
|
|
background-color: #9c27b0;
|
|
color: white;
|
|
}}
|
|
.issue-button:hover {{
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
}}
|
|
.view-saved-events {{
|
|
display: block;
|
|
text-align: center;
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
background-color: #f8f9fa;
|
|
border-radius: 5px;
|
|
text-decoration: none;
|
|
color: #333;
|
|
border: 1px solid #ddd;
|
|
}}
|
|
.view-saved-events:hover {{
|
|
background-color: #e9ecef;
|
|
}}
|
|
</style>
|
|
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="nav-links">
|
|
<a href="/demo">← 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>
|
|
|
|
<div class="auth-section">
|
|
<h3>OpenStreetMap Authentication</h3>
|
|
"""
|
|
|
|
# Add authentication section based on authentication status
|
|
if is_authenticated:
|
|
auth_section = f"""
|
|
<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:
|
|
auth_section = f"""
|
|
<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 button">
|
|
<span class="osm-logo"></span>
|
|
Login with OpenStreetMap
|
|
</a>
|
|
"""
|
|
|
|
# Add the rest of the HTML template
|
|
html_footer = """
|
|
</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');
|
|
|
|
switch(issueType) {
|
|
case 'pothole':
|
|
labelInput.value = 'Pothole in the road';
|
|
issueTypeInput.value = 'road.hazard.pothole';
|
|
severitySelect.value = 'medium';
|
|
marker.setColor('#ff9800');
|
|
break;
|
|
case 'obstacle':
|
|
labelInput.value = 'Obstacle on the road';
|
|
issueTypeInput.value = 'road.hazard.obstacle';
|
|
severitySelect.value = 'high';
|
|
marker.setColor('#f44336');
|
|
break;
|
|
case 'vehicle':
|
|
labelInput.value = 'Vehicle on the side of the road';
|
|
issueTypeInput.value = 'road.hazard.vehicle';
|
|
severitySelect.value = 'low';
|
|
marker.setColor('#2196f3');
|
|
break;
|
|
case 'danger':
|
|
labelInput.value = 'Dangerous situation on the road';
|
|
issueTypeInput.value = 'road.hazard.danger';
|
|
severitySelect.value = 'high';
|
|
marker.setColor('#9c27b0');
|
|
break;
|
|
default:
|
|
labelInput.value = '';
|
|
issueTypeInput.value = 'traffic.jam';
|
|
severitySelect.value = 'medium';
|
|
marker.setColor('#ff3860');
|
|
}
|
|
}
|
|
|
|
// 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
|
|
const osmUsername = document.getElementById('osmUsername');
|
|
if (osmUsername && osmUsername.value) {
|
|
event.properties['reporter:osm'] = osmUsername.value;
|
|
console.log(`Including OSM username in report: ${osmUsername.value}`);
|
|
}
|
|
|
|
// 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>
|
|
"""
|
|
|
|
# Concatenate the HTML parts to form the complete template
|
|
html = html_header + auth_section + html_footer
|
|
|
|
# Set the response body and status
|
|
resp.text = html
|
|
resp.status = falcon.HTTP_200
|
|
logger.success("Successfully processed GET request to /demo/traffic")
|
|
except Exception as e:
|
|
logger.error(f"Error processing GET request to /demo/traffic: {e}")
|
|
resp.status = falcon.HTTP_500
|
|
resp.text = f"Error: {str(e)}"
|
|
|
|
# Create a global instance of DemoTrafficResource
|
|
demo_traffic = DemoTrafficResource() |