traffic with osm auth

This commit is contained in:
Tykayn 2025-09-21 12:27:00 +02:00 committed by tykayn
parent 6d755ee8dc
commit e274e91dcb
4 changed files with 649 additions and 5 deletions

View file

@ -1,2 +1,7 @@
DB_USER=cipherbliss DB_USER=cipherbliss
POSTGRES_PASSWORD=tralalahihou POSTGRES_PASSWORD=tralalahihou
CLIENT_ID=ziozioizo-sllkslk
CLIENT_SECRET=spposfdo-msmldflkds
CLIENT_AUTORIZATIONS=read_prefs
CLIENT_REDIRECT=https://oedb.cipherbliss.com:8080/demo/traffic

View file

@ -62,6 +62,7 @@ def create_app():
app.add_route('/demo/by-what', demo, suffix='by_what') # Handle events by type page app.add_route('/demo/by-what', demo, suffix='by_what') # Handle events by type page
app.add_route('/demo/map-by-what', demo, suffix='map_by_what') # Handle map by event type page app.add_route('/demo/map-by-what', demo, suffix='map_by_what') # Handle map by event type page
app.add_route('/demo/edit/{id}', demo, suffix='edit') # Handle event editing page app.add_route('/demo/edit/{id}', demo, suffix='edit') # Handle event editing page
app.add_route('/demo/traffic', demo, suffix='traffic') # Handle traffic jam reporting page
logger.success("Application initialized successfully") logger.success("Application initialized successfully")
return app return app

View file

@ -68,12 +68,49 @@ If no events appear on the map:
3. Check the browser console for any JavaScript errors 3. Check the browser console for any JavaScript errors
4. Verify that the `/event` endpoint is working correctly by accessing it directly 4. Verify that the `/event` endpoint is working correctly by accessing it directly
## Additional Demo Pages
The demo section includes several specialized pages:
1. **Main Demo Page** (`/demo`): Shows a map with all current events
2. **Search Page** (`/demo/search`): Provides advanced search functionality
3. **Events by Type** (`/demo/by-what`): Lists events grouped by their type
4. **Map by Event Type** (`/demo/map-by-what`): Shows events on a map with filtering by type
5. **Add Event** (`/demo/add`): Form for adding new events
6. **Edit Event** (`/demo/edit/{id}`): Form for editing existing events
7. **Traffic Jam Reporting** (`/demo/traffic`): Form for reporting traffic jams with geolocation
### Traffic Jam Reporting Page
The traffic jam reporting page (`/demo/traffic`) provides a specialized form for reporting traffic jams. Features include:
- Button to automatically detect the user's current location using browser geolocation
- Map for selecting the location of the traffic jam
- Form fields for traffic jam details (description, severity, cause, etc.)
- Automatic reverse geocoding to determine the road/location name
- Submission to the API as a traffic.jam event type
- OpenStreetMap OAuth2 authentication to include the reporter's OSM username in the event
#### OpenStreetMap Authentication
The traffic jam reporting page includes OAuth2 authentication with OpenStreetMap:
- Users can authenticate with their OpenStreetMap account
- After authentication, the user's OSM username and a link to their profile are displayed
- When submitting a traffic jam report, the OSM username is included in the event properties as `reporter:osm`
- OAuth2 configuration parameters are stored in the `.env` file:
- `CLIENT_ID`: The OAuth2 client ID for the application
- `CLIENT_SECRET`: The OAuth2 client secret for the application
- `CLIENT_AUTORIZATIONS`: The permissions requested (default: "read_prefs")
- `CLIENT_REDIRECT`: The redirect URL after authentication
## Future Improvements ## Future Improvements
Potential future improvements for the demo page: Potential future improvements for the demo pages:
1. Add date selection to view events from different dates 1. Add date selection to view events from different dates
2. Add filtering options (by event type, location, etc.) 2. Add more filtering options (by event type, location, etc.)
3. Add a search box to find specific events 3. Add a search box to find specific events
4. Improve the mobile experience 4. Improve the mobile experience
5. Add more interactive features to the map 5. Add more interactive features to the map
6. Expand the traffic reporting functionality to include other traffic-related events

View file

@ -5,8 +5,10 @@ Demo resource for the OpenEventDatabase.
import falcon import falcon
import requests import requests
import json import json
import os
from collections import defaultdict from collections import defaultdict
from oedb.utils.logging import logger from oedb.utils.logging import logger
from oedb.utils.db import load_env_from_file
class DemoResource: class DemoResource:
""" """
@ -36,7 +38,7 @@ class DemoResource:
resp.content_type = 'text/html' resp.content_type = 'text/html'
# Fetch the event data from the API # Fetch the event data from the API
response = requests.get(f'http://localhost/event/{id}') response = requests.get(f'http://api.openevent/event/{id}')
if response.status_code != 200: if response.status_code != 200:
resp.status = falcon.HTTP_404 resp.status = falcon.HTTP_404
@ -508,6 +510,7 @@ class DemoResource:
<li><a href="/demo/search" target="_blank">/demo/search - Advanced Search</a></li> <li><a href="/demo/search" target="_blank">/demo/search - Advanced Search</a></li>
<li><a href="/demo/by-what" target="_blank">/demo/by-what - Events by Type</a></li> <li><a href="/demo/by-what" target="_blank">/demo/by-what - Events by Type</a></li>
<li><a href="/demo/map-by-what" target="_blank">/demo/map-by-what - Map by Event Type</a></li> <li><a href="/demo/map-by-what" target="_blank">/demo/map-by-what - Map by Event Type</a></li>
<li><a href="/demo/traffic" target="_blank">/demo/traffic - Report Traffic Jam</a></li>
<li><a href="/event?what=music" target="_blank">Search Music Events</a></li> <li><a href="/event?what=music" target="_blank">Search Music Events</a></li>
<li><a href="/event?what=sport" target="_blank">Search Sport Events</a></li> <li><a href="/event?what=sport" target="_blank">Search Sport Events</a></li>
</ul> </ul>
@ -2303,5 +2306,603 @@ class DemoResource:
resp.status = falcon.HTTP_500 resp.status = falcon.HTTP_500
resp.text = f"Error: {str(e)}" resp.text = f"Error: {str(e)}"
def on_get_traffic(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;
}}
</style>
</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>
</div>
<h1>Report Traffic Jam</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>
<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">Traffic Jam Description</label>
<input type="text" id="label" name="label" placeholder="e.g., Heavy traffic on Highway A1" required value="bouchon">
</div>
<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 (Slow moving)</option>
<option value="medium" selected>Medium (Very slow)</option>
<option value="high">High (Standstill)</option>
</select>
</div>
<div class="form-group">
<label for="cause">Cause (if known)</label>
<input type="text" id="cause" name="cause" placeholder="e.g., Accident, Construction, Weather">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="start" class="required">Start Time</label>
<input type="datetime-local" id="start" name="start" required value="">
</div>
<div class="form-group">
<label for="stop" class="required">Estimated End 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 traffic jam location or use the "Get My Current Location" button</div>
</div>
<button type="submit">Report Traffic Jam</button>
</form>
<div id="result"></div>
</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 traffic jam 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);
});
// 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 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', // Traffic jams are typically unscheduled
what: 'traffic.jam', // Category for traffic jams
'traffic:severity': severity, // Custom property for severity
start: start,
stop: stop
}
};
// Add optional properties if provided
if (cause) {
event.properties['traffic:cause'] = 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}`);
}
// 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 => {
showResult(`Traffic jam reported successfully with ID: ${data.id}`, 'success');
// Add link to view the event
const resultElement = document.getElementById('result');
resultElement.innerHTML += `<p><a href="/event/${data.id}" target="_blank">View Report</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 traffic jam: ${error.message}`, 'error');
});
});
// 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 DemoResource # Create a global instance of DemoResource
demo = DemoResource() demo = DemoResource()