up demo
This commit is contained in:
parent
2157091778
commit
afe83a9a3c
8 changed files with 1433 additions and 8 deletions
|
@ -3,6 +3,9 @@ Demo resource for the OpenEventDatabase.
|
|||
"""
|
||||
|
||||
import falcon
|
||||
import requests
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from oedb.utils.logging import logger
|
||||
|
||||
class DemoResource:
|
||||
|
@ -67,6 +70,11 @@ class DemoResource:
|
|||
<li><a href="/event" target="_blank">/event - Get Events</a></li>
|
||||
<li><a href="/stats" target="_blank">/stats - Database Statistics</a></li>
|
||||
</ul>
|
||||
<h3>Demo Pages:</h3>
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
<p><a href="/demo/add" class="add-event-btn" style="display: block; text-align: center; margin-top: 15px; padding: 8px; background-color: #0078ff; color: white; border-radius: 4px; font-weight: bold;">+ Add New Event</a></p>
|
||||
<p><a href="https://source.cipherbliss.com/tykayn/oedb-backend" target="_blank">Source Code on Cipherbliss</a></p>
|
||||
</div>
|
||||
|
@ -138,15 +146,41 @@ class DemoResource:
|
|||
// Create popup content
|
||||
let popupContent = '<div class="event-popup">';
|
||||
popupContent += `<h3>${properties.label || 'Event'}</h3>`;
|
||||
popupContent += `<p><strong>Date:</strong> ${properties.when || 'Unknown'}</p>`;
|
||||
|
||||
if (properties.what) {
|
||||
popupContent += `<p><strong>Type:</strong> ${properties.what}</p>`;
|
||||
// Display all properties
|
||||
popupContent += '<div style="max-height: 300px; overflow-y: auto;">';
|
||||
popupContent += '<table style="width: 100%; border-collapse: collapse;">';
|
||||
|
||||
// Sort properties alphabetically for better organization
|
||||
const sortedKeys = Object.keys(properties).sort();
|
||||
|
||||
for (const key of sortedKeys) {
|
||||
// Skip the label as it's already displayed as the title
|
||||
if (key === 'label') continue;
|
||||
|
||||
const value = properties[key];
|
||||
let displayValue;
|
||||
|
||||
// Format the value based on its type
|
||||
if (value === null || value === undefined) {
|
||||
displayValue = '<em>null</em>';
|
||||
} else if (typeof value === 'object') {
|
||||
displayValue = `<pre style="margin: 0; white-space: pre-wrap;">${JSON.stringify(value, null, 2)}</pre>`;
|
||||
} else if (typeof value === 'string' && value.startsWith('http')) {
|
||||
displayValue = `<a href="${value}" target="_blank">${value}</a>`;
|
||||
} else {
|
||||
displayValue = String(value);
|
||||
}
|
||||
|
||||
popupContent += `
|
||||
<tr style="border-bottom: 1px solid #eee;">
|
||||
<td style="padding: 4px; font-weight: bold; vertical-align: top;">${key}:</td>
|
||||
<td style="padding: 4px;">${displayValue}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
if (properties.source) {
|
||||
popupContent += `<p><a href="${properties.source}" target="_blank">More Information</a></p>`;
|
||||
}
|
||||
popupContent += '</table>';
|
||||
popupContent += '</div>';
|
||||
|
||||
popupContent += '</div>';
|
||||
|
||||
|
@ -197,6 +231,709 @@ class DemoResource:
|
|||
logger.error(f"Error processing GET request to /demo: {e}")
|
||||
resp.status = falcon.HTTP_500
|
||||
resp.text = f"Error: {str(e)}"
|
||||
|
||||
def on_get_by_what(self, req, resp):
|
||||
"""
|
||||
Handle GET requests to the /demo/by-what endpoint.
|
||||
Returns an HTML page with links to events organized by their "what" type.
|
||||
|
||||
Args:
|
||||
req: The request object.
|
||||
resp: The response object.
|
||||
"""
|
||||
logger.info("Processing GET request to /demo/by-what")
|
||||
|
||||
try:
|
||||
# Set content type to HTML
|
||||
resp.content_type = 'text/html'
|
||||
|
||||
# Fetch events from the API
|
||||
response = requests.get('http://localhost/event?limit=1000')
|
||||
events_data = response.json()
|
||||
|
||||
# Group events by "what" type
|
||||
events_by_what = defaultdict(list)
|
||||
|
||||
if events_data.get('features'):
|
||||
for feature in events_data['features']:
|
||||
properties = feature.get('properties', {})
|
||||
what = properties.get('what', 'Unknown')
|
||||
events_by_what[what].append({
|
||||
'id': properties.get('id'),
|
||||
'label': properties.get('label', 'Unnamed Event'),
|
||||
'coordinates': feature.get('geometry', {}).get('coordinates', [0, 0])
|
||||
})
|
||||
|
||||
# Create HTML response
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Events by Type - OpenEventDatabase</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { color: #333; }
|
||||
h2 {
|
||||
color: #0078ff;
|
||||
margin-top: 30px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
ul { padding-left: 20px; }
|
||||
li { margin-bottom: 8px; }
|
||||
a { color: #0078ff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.nav {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.nav a {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.event-count {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="/demo">Demo Map</a>
|
||||
<a href="/demo/map-by-what">Map by Event Type</a>
|
||||
</div>
|
||||
|
||||
<h1>Events by Type</h1>
|
||||
<p>This page lists all events from the OpenEventDatabase organized by their type.</p>
|
||||
"""
|
||||
|
||||
# Add event types and their events
|
||||
if events_by_what:
|
||||
# Sort event types alphabetically
|
||||
sorted_what_types = sorted(events_by_what.keys())
|
||||
|
||||
# Add quick navigation
|
||||
html += "<h2>Quick Navigation</h2><ul>"
|
||||
for what_type in sorted_what_types:
|
||||
event_count = len(events_by_what[what_type])
|
||||
html += f'<li><a href="#what-{what_type.replace(" ", "-")}">{what_type}</a> <span class="event-count">({event_count} events)</span></li>'
|
||||
html += "</ul>"
|
||||
|
||||
# Add sections for each event type
|
||||
for what_type in sorted_what_types:
|
||||
events = events_by_what[what_type]
|
||||
html += f'<h2 id="what-{what_type.replace(" ", "-")}">{what_type} <span class="event-count">({len(events)} events)</span></h2>'
|
||||
html += "<ul>"
|
||||
|
||||
# Sort events by label
|
||||
sorted_events = sorted(events, key=lambda x: x.get('label', ''))
|
||||
|
||||
for event in sorted_events:
|
||||
event_id = event.get('id')
|
||||
event_label = event.get('label', 'Unnamed Event')
|
||||
coordinates = event.get('coordinates', [0, 0])
|
||||
|
||||
html += f'<li><a href="/event/{event_id}" target="_blank">{event_label}</a> '
|
||||
html += f'<small>[<a href="https://www.openstreetmap.org/?mlat={coordinates[1]}&mlon={coordinates[0]}&zoom=15" target="_blank">map</a>]</small></li>'
|
||||
|
||||
html += "</ul>"
|
||||
else:
|
||||
html += "<p>No events found in the database.</p>"
|
||||
|
||||
html += """
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Set the response body and status
|
||||
resp.text = html
|
||||
resp.status = falcon.HTTP_200
|
||||
logger.success("Successfully processed GET request to /demo/by-what")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing GET request to /demo/by-what: {e}")
|
||||
resp.status = falcon.HTTP_500
|
||||
resp.text = f"Error: {str(e)}"
|
||||
|
||||
def on_get_map_by_what(self, req, resp):
|
||||
"""
|
||||
Handle GET requests to the /demo/map-by-what endpoint.
|
||||
Returns an HTML page with a MapLibre map showing events filtered by "what" type.
|
||||
|
||||
Args:
|
||||
req: The request object.
|
||||
resp: The response object.
|
||||
"""
|
||||
logger.info("Processing GET request to /demo/map-by-what")
|
||||
|
||||
try:
|
||||
# Set content type to HTML
|
||||
resp.content_type = 'text/html'
|
||||
|
||||
# Create HTML response with MapLibre map and filtering controls
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Map by Event Type - 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: 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: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
max-width: 300px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.filter-overlay {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
max-width: 300px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
h2, h3 { margin-top: 0; }
|
||||
ul { padding-left: 20px; }
|
||||
a { color: #0078ff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.event-popup { max-width: 300px; }
|
||||
|
||||
.filter-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-item input {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.filter-item label {
|
||||
cursor: pointer;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter-count {
|
||||
color: #666;
|
||||
font-size: 0.8em;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.color-dot {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.nav a {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #0078ff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="map-overlay">
|
||||
<h2>Map by Event Type</h2>
|
||||
<div class="nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="/demo">Demo Map</a>
|
||||
<a href="/demo/by-what">Events by Type</a>
|
||||
</div>
|
||||
<p>This map shows events from the OpenEventDatabase filtered by their type.</p>
|
||||
<p>Use the filter panel on the right to show/hide different event types.</p>
|
||||
<div id="event-info">
|
||||
<p>Loading events...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-overlay">
|
||||
<h3>Filter by Event Type</h3>
|
||||
<div>
|
||||
<button id="select-all">Select All</button>
|
||||
<button id="deselect-all">Deselect All</button>
|
||||
</div>
|
||||
<ul id="filter-list" class="filter-list">
|
||||
<li>Loading event types...</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Initialize the map
|
||||
const map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://demotiles.maplibre.org/style.json',
|
||||
center: [2.3522, 48.8566], // Default center (Paris)
|
||||
zoom: 4
|
||||
});
|
||||
|
||||
// Add navigation controls
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
|
||||
// Store all events and their types
|
||||
let allEvents = null;
|
||||
let eventTypes = new Set();
|
||||
let eventsByType = {};
|
||||
let markersByType = {};
|
||||
let colorsByType = {};
|
||||
|
||||
// Generate a color for each event type
|
||||
function getColorForType(type, index) {
|
||||
// Predefined colors for better visual distinction
|
||||
const colors = [
|
||||
'#FF5722', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5',
|
||||
'#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50',
|
||||
'#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800'
|
||||
];
|
||||
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
|
||||
// Fetch events when the map is loaded
|
||||
map.on('load', function() {
|
||||
fetchEvents();
|
||||
});
|
||||
|
||||
// Function to fetch events from the API
|
||||
function fetchEvents() {
|
||||
// Update event info
|
||||
document.getElementById('event-info').innerHTML = '<p>Loading events...</p>';
|
||||
|
||||
// Fetch events from the API - using limit=1000 to get more events
|
||||
fetch('/event?limit=1000')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.features && data.features.length > 0) {
|
||||
// Store all events
|
||||
allEvents = data;
|
||||
|
||||
// Process events by type
|
||||
processEventsByType(data);
|
||||
|
||||
// Create filter UI
|
||||
createFilterUI();
|
||||
|
||||
// Add all events to the map initially
|
||||
addAllEventsToMap();
|
||||
|
||||
// Fit map to events bounds
|
||||
fitMapToBounds(data);
|
||||
|
||||
// Update event info
|
||||
document.getElementById('event-info').innerHTML =
|
||||
`<p>Found ${data.features.length} events across ${eventTypes.size} different types.</p>`;
|
||||
} else {
|
||||
document.getElementById('event-info').innerHTML = '<p>No events found.</p>';
|
||||
document.getElementById('filter-list').innerHTML = '<li>No event types available.</li>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching events:', error);
|
||||
document.getElementById('event-info').innerHTML =
|
||||
`<p>Error loading events: ${error.message}</p>`;
|
||||
});
|
||||
}
|
||||
|
||||
// Process events by their "what" type
|
||||
function processEventsByType(data) {
|
||||
eventTypes = new Set();
|
||||
eventsByType = {};
|
||||
|
||||
// Group events by their "what" type
|
||||
data.features.forEach(feature => {
|
||||
const properties = feature.properties;
|
||||
const what = properties.what || 'Unknown';
|
||||
|
||||
// Add to set of event types
|
||||
eventTypes.add(what);
|
||||
|
||||
// Add to events by type
|
||||
if (!eventsByType[what]) {
|
||||
eventsByType[what] = [];
|
||||
}
|
||||
eventsByType[what].push(feature);
|
||||
});
|
||||
|
||||
// Assign colors to each type
|
||||
let index = 0;
|
||||
eventTypes.forEach(type => {
|
||||
colorsByType[type] = getColorForType(type, index);
|
||||
index++;
|
||||
});
|
||||
}
|
||||
|
||||
// Create the filter UI
|
||||
function createFilterUI() {
|
||||
const filterList = document.getElementById('filter-list');
|
||||
filterList.innerHTML = '';
|
||||
|
||||
// Sort event types alphabetically
|
||||
const sortedTypes = Array.from(eventTypes).sort();
|
||||
|
||||
// Create a checkbox for each event type
|
||||
sortedTypes.forEach(type => {
|
||||
const count = eventsByType[type].length;
|
||||
const color = colorsByType[type];
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.className = 'filter-item';
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = `filter-${type}`;
|
||||
checkbox.checked = true;
|
||||
checkbox.addEventListener('change', () => {
|
||||
toggleEventType(type, checkbox.checked);
|
||||
});
|
||||
|
||||
const colorDot = document.createElement('span');
|
||||
colorDot.className = 'color-dot';
|
||||
colorDot.style.backgroundColor = color;
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.htmlFor = `filter-${type}`;
|
||||
label.appendChild(colorDot);
|
||||
label.appendChild(document.createTextNode(type));
|
||||
|
||||
const countSpan = document.createElement('span');
|
||||
countSpan.className = 'filter-count';
|
||||
countSpan.textContent = `(${count})`;
|
||||
label.appendChild(countSpan);
|
||||
|
||||
li.appendChild(checkbox);
|
||||
li.appendChild(label);
|
||||
filterList.appendChild(li);
|
||||
});
|
||||
|
||||
// Add event listeners for select/deselect all buttons
|
||||
document.getElementById('select-all').addEventListener('click', selectAllEventTypes);
|
||||
document.getElementById('deselect-all').addEventListener('click', deselectAllEventTypes);
|
||||
}
|
||||
|
||||
// Add all events to the map
|
||||
function addAllEventsToMap() {
|
||||
// Clear existing markers
|
||||
clearAllMarkers();
|
||||
|
||||
// Add markers for each event type
|
||||
Object.keys(eventsByType).forEach(type => {
|
||||
addEventsOfTypeToMap(type);
|
||||
});
|
||||
}
|
||||
|
||||
// Add events of a specific type to the map
|
||||
function addEventsOfTypeToMap(type) {
|
||||
if (!markersByType[type]) {
|
||||
markersByType[type] = [];
|
||||
}
|
||||
|
||||
const events = eventsByType[type];
|
||||
const color = colorsByType[type];
|
||||
|
||||
events.forEach(feature => {
|
||||
const coordinates = feature.geometry.coordinates.slice();
|
||||
const properties = feature.properties;
|
||||
|
||||
// Create popup content
|
||||
let popupContent = '<div class="event-popup">';
|
||||
popupContent += `<h3>${properties.label || 'Event'}</h3>`;
|
||||
popupContent += `<p><strong>Type:</strong> ${type}</p>`;
|
||||
|
||||
// Display all properties
|
||||
popupContent += '<div style="max-height: 300px; overflow-y: auto;">';
|
||||
popupContent += '<table style="width: 100%; border-collapse: collapse;">';
|
||||
|
||||
// Sort properties alphabetically for better organization
|
||||
const sortedKeys = Object.keys(properties).sort();
|
||||
|
||||
for (const key of sortedKeys) {
|
||||
// Skip the label as it's already displayed as the title
|
||||
if (key === 'label') continue;
|
||||
|
||||
const value = properties[key];
|
||||
let displayValue;
|
||||
|
||||
// Format the value based on its type
|
||||
if (value === null || value === undefined) {
|
||||
displayValue = '<em>null</em>';
|
||||
} else if (typeof value === 'object') {
|
||||
displayValue = `<pre style="margin: 0; white-space: pre-wrap;">${JSON.stringify(value, null, 2)}</pre>`;
|
||||
} else if (typeof value === 'string' && value.startsWith('http')) {
|
||||
displayValue = `<a href="${value}" target="_blank">${value}</a>`;
|
||||
} else {
|
||||
displayValue = String(value);
|
||||
}
|
||||
|
||||
popupContent += `
|
||||
<tr style="border-bottom: 1px solid #eee;">
|
||||
<td style="padding: 4px; font-weight: bold; vertical-align: top;">${key}:</td>
|
||||
<td style="padding: 4px;">${displayValue}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
popupContent += '</table>';
|
||||
popupContent += '</div>';
|
||||
|
||||
popupContent += '</div>';
|
||||
|
||||
// Create popup
|
||||
const popup = new maplibregl.Popup({
|
||||
closeButton: true,
|
||||
closeOnClick: true
|
||||
}).setHTML(popupContent);
|
||||
|
||||
// Add marker with popup
|
||||
const marker = new maplibregl.Marker({
|
||||
color: color
|
||||
})
|
||||
.setLngLat(coordinates)
|
||||
.setPopup(popup)
|
||||
.addTo(map);
|
||||
|
||||
// Store marker reference
|
||||
markersByType[type].push(marker);
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle visibility of events by type
|
||||
function toggleEventType(type, visible) {
|
||||
if (!markersByType[type]) return;
|
||||
|
||||
markersByType[type].forEach(marker => {
|
||||
if (visible) {
|
||||
marker.addTo(map);
|
||||
} else {
|
||||
marker.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Select all event types
|
||||
function selectAllEventTypes() {
|
||||
const checkboxes = document.querySelectorAll('#filter-list input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = true;
|
||||
const type = checkbox.id.replace('filter-', '');
|
||||
toggleEventType(type, true);
|
||||
});
|
||||
}
|
||||
|
||||
// Deselect all event types
|
||||
function deselectAllEventTypes() {
|
||||
const checkboxes = document.querySelectorAll('#filter-list input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
const type = checkbox.id.replace('filter-', '');
|
||||
toggleEventType(type, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Clear all markers from the map
|
||||
function clearAllMarkers() {
|
||||
Object.keys(markersByType).forEach(type => {
|
||||
if (markersByType[type]) {
|
||||
markersByType[type].forEach(marker => marker.remove());
|
||||
}
|
||||
});
|
||||
markersByType = {};
|
||||
}
|
||||
|
||||
// Function to fit map to events bounds
|
||||
function fitMapToBounds(geojson) {
|
||||
if (geojson.features.length === 0) return;
|
||||
|
||||
// Create a bounds object
|
||||
const bounds = new maplibregl.LngLatBounds();
|
||||
|
||||
// Extend bounds with each feature
|
||||
geojson.features.forEach(feature => {
|
||||
bounds.extend(feature.geometry.coordinates);
|
||||
});
|
||||
|
||||
// Fit map to bounds with padding
|
||||
map.fitBounds(bounds, {
|
||||
padding: 50,
|
||||
maxZoom: 12
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Set the response body and status
|
||||
resp.text = html
|
||||
resp.status = falcon.HTTP_200
|
||||
logger.success("Successfully processed GET request to /demo/map-by-what")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing GET request to /demo/map-by-what: {e}")
|
||||
resp.status = falcon.HTTP_500
|
||||
resp.text = f"Error: {str(e)}"
|
||||
events_by_what = defaultdict(list)
|
||||
|
||||
if events_data.get('features'):
|
||||
for feature in events_data['features']:
|
||||
properties = feature.get('properties', {})
|
||||
what = properties.get('what', 'Unknown')
|
||||
events_by_what[what].append({
|
||||
'id': properties.get('id'),
|
||||
'label': properties.get('label', 'Unnamed Event'),
|
||||
'coordinates': feature.get('geometry', {}).get('coordinates', [0, 0])
|
||||
})
|
||||
|
||||
# Create HTML response
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Events by Type - OpenEventDatabase</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 { color: #333; }
|
||||
h2 {
|
||||
color: #0078ff;
|
||||
margin-top: 30px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
ul { padding-left: 20px; }
|
||||
li { margin-bottom: 8px; }
|
||||
a { color: #0078ff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.nav {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.nav a {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.event-count {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="/demo">Demo Map</a>
|
||||
<a href="/demo/map-by-what">Map by Event Type</a>
|
||||
</div>
|
||||
|
||||
<h1>Events by Type</h1>
|
||||
<p>This page lists all events from the OpenEventDatabase organized by their type.</p>
|
||||
"""
|
||||
|
||||
# Add event types and their events
|
||||
if events_by_what:
|
||||
# Sort event types alphabetically
|
||||
sorted_what_types = sorted(events_by_what.keys())
|
||||
|
||||
# Add quick navigation
|
||||
html += "<h2>Quick Navigation</h2><ul>"
|
||||
for what_type in sorted_what_types:
|
||||
event_count = len(events_by_what[what_type])
|
||||
html += f'<li><a href="#what-{what_type.replace(" ", "-")}">{what_type}</a> <span class="event-count">({event_count} events)</span></li>'
|
||||
html += "</ul>"
|
||||
|
||||
# Add sections for each event type
|
||||
for what_type in sorted_what_types:
|
||||
events = events_by_what[what_type]
|
||||
html += f'<h2 id="what-{what_type.replace(" ", "-")}">{what_type} <span class="event-count">({len(events)} events)</span></h2>'
|
||||
html += "<ul>"
|
||||
|
||||
# Sort events by label
|
||||
sorted_events = sorted(events, key=lambda x: x.get('label', ''))
|
||||
|
||||
for event in sorted_events:
|
||||
event_id = event.get('id')
|
||||
event_label = event.get('label', 'Unnamed Event')
|
||||
coordinates = event.get('coordinates', [0, 0])
|
||||
|
||||
html += f'<li><a href="/event/{event_id}" target="_blank">{event_label}</a> '
|
||||
html += f'<small>[<a href="https://www.openstreetmap.org/?mlat={coordinates[1]}&mlon={coordinates[0]}&zoom=15" target="_blank">map</a>]</small></li>'
|
||||
|
||||
html += "</ul>"
|
||||
else:
|
||||
html += "<p>No events found in the database.</p>"
|
||||
|
||||
html += """
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Set the response body and status
|
||||
resp.text = html
|
||||
resp.status = falcon.HTTP_200
|
||||
logger.success("Successfully processed GET request to /demo/by-what")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing GET request to /demo/by-what: {e}")
|
||||
resp.status = falcon.HTTP_500
|
||||
resp.text = f"Error: {str(e)}"
|
||||
|
||||
# Create a global instance of DemoResource
|
||||
demo = DemoResource()
|
Loading…
Add table
Add a link
Reference in a new issue