up static routes
This commit is contained in:
parent
9cca0e4998
commit
1048f4af45
14 changed files with 2959 additions and 1076 deletions
|
@ -2,8 +2,11 @@
|
|||
View saved events resource for the OpenEventDatabase.
|
||||
"""
|
||||
|
||||
import os
|
||||
import falcon
|
||||
import jinja2
|
||||
from oedb.utils.logging import logger
|
||||
from oedb.utils.db import load_env_from_file
|
||||
|
||||
class DemoViewEventsResource:
|
||||
"""
|
||||
|
@ -11,6 +14,17 @@ class DemoViewEventsResource:
|
|||
Handles the /demo/view-events endpoint.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the resource with a Jinja2 environment.
|
||||
"""
|
||||
# Set up Jinja2 environment
|
||||
template_dir = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
self.jinja_env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(template_dir),
|
||||
autoescape=jinja2.select_autoescape(['html', 'xml'])
|
||||
)
|
||||
|
||||
def on_get(self, req, resp):
|
||||
"""
|
||||
Handle GET requests to the /demo/view-events endpoint.
|
||||
|
@ -26,390 +40,21 @@ class DemoViewEventsResource:
|
|||
# Set content type to HTML
|
||||
resp.content_type = 'text/html'
|
||||
|
||||
# Create HTML response with MapLibre map
|
||||
html = """
|
||||
<!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" />
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
#map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.map-overlay {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
max-width: 300px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.map-overlay h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.nav-links {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.nav-links a {
|
||||
color: #0078ff;
|
||||
text-decoration: none;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.nav-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.event-list {
|
||||
margin-top: 15px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.event-item {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.event-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.event-item h3 {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.event-item p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.event-item .event-date {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.event-item .event-type {
|
||||
display: inline-block;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.event-type.pothole {
|
||||
background-color: #ff9800;
|
||||
}
|
||||
.event-type.obstacle {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.event-type.vehicle {
|
||||
background-color: #2196f3;
|
||||
}
|
||||
.event-type.danger {
|
||||
background-color: #9c27b0;
|
||||
}
|
||||
.event-type.traffic {
|
||||
background-color: #ff3860;
|
||||
}
|
||||
.event-type.other {
|
||||
background-color: #777;
|
||||
}
|
||||
.no-events {
|
||||
padding: 15px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
.controls {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.controls button {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.controls button:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.controls button.danger {
|
||||
color: #dc3545;
|
||||
}
|
||||
.popup-content {
|
||||
max-width: 300px;
|
||||
}
|
||||
.popup-content h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.popup-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.popup-content table td {
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.popup-content table td:first-child {
|
||||
font-weight: bold;
|
||||
width: 40%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="map-overlay">
|
||||
<h2>Your Saved Events</h2>
|
||||
|
||||
<div class="nav-links">
|
||||
<a href="/demo">← Back to Map</a>
|
||||
<a href="/demo/traffic">Report New Issue</a>
|
||||
</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>
|
||||
"""
|
||||
# Load environment variables from .env file for OAuth2 configuration
|
||||
load_env_from_file()
|
||||
|
||||
# Get OAuth2 configuration parameters
|
||||
client_id = os.getenv("CLIENT_ID", "")
|
||||
client_secret = os.getenv("CLIENT_SECRET", "")
|
||||
client_redirect = os.getenv("CLIENT_REDIRECT", "")
|
||||
|
||||
# Load and render the template with the appropriate variables
|
||||
template = self.jinja_env.get_template('view_events.html')
|
||||
html = template.render(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
client_redirect=client_redirect
|
||||
)
|
||||
|
||||
# Set the response body and status
|
||||
resp.text = html
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue