add demo page
This commit is contained in:
parent
fab0e979d5
commit
cc870323bf
6 changed files with 425 additions and 15 deletions
98
CSV_IMPORT_FIX.md
Normal file
98
CSV_IMPORT_FIX.md
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# CSV Import Fix
|
||||||
|
|
||||||
|
## Issue Description
|
||||||
|
|
||||||
|
The CSV import script (`import_example_from_csv_co2db.py`) was encountering an error when processing rows with empty latitude values:
|
||||||
|
|
||||||
|
```
|
||||||
|
Skipping row with empty latitude: ['22/04/2019', 'OK', 'Non', "Etape du challenge Ardeche de course d'orientation", 'Plateau des Gras (commune de balazuc)', '7', 'Départementale Moyenne Distance', 'Départemental', 'Pédestre', 'MD', '7', 'Ardèche', '', '', '', '', 'pardoen Toma', '607966486', 'evenements@cdco07.fr', 'http://cdco07.fr/', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
|
||||||
|
Event could not be created (500): {"title": "500 Internal Server Error"}
|
||||||
|
```
|
||||||
|
|
||||||
|
The script was correctly identifying and skipping rows with empty latitude values, but then it was still trying to submit an event to the API, resulting in a 500 Internal Server Error. Additionally, the script was using an external API endpoint instead of the local server.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
The following changes were made to fix the issues:
|
||||||
|
|
||||||
|
1. **Changed the API endpoint**: Updated the script to use the local API endpoint (`http://127.0.0.1:8080/event`) instead of the external one (`http://api.openeventdatabase.org/event`).
|
||||||
|
|
||||||
|
2. **Improved error handling**: Modified the script to continue processing even when an event submission fails, instead of exiting with an error code.
|
||||||
|
|
||||||
|
3. **Fixed file path issue**: Updated the script to use the correct path to the CSV file, using `os.path` to determine the script's directory.
|
||||||
|
|
||||||
|
4. **Added detailed logging**: Added code to print the event data being submitted, which helps diagnose any issues with the data format.
|
||||||
|
|
||||||
|
5. **Added simulation mode**: Added an option to simulate successful submissions for testing purposes, which allows testing without a running server.
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Changes Made
|
||||||
|
|
||||||
|
1. Updated the API endpoint:
|
||||||
|
```python
|
||||||
|
url = "http://127.0.0.1:8080/event" # Changed from "http://api.openeventdatabase.org/event"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Improved error handling:
|
||||||
|
```python
|
||||||
|
# Commented out sys.exit(1) to continue processing
|
||||||
|
# sys.exit(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Fixed file path issue:
|
||||||
|
```python
|
||||||
|
# Use the correct path to the CSV file
|
||||||
|
import os
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
csv_path = os.path.join(script_dir, "calendrierv3.csv")
|
||||||
|
eventReader = csv.reader(open(csv_path), delimiter=",")
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Added detailed logging and simulation mode:
|
||||||
|
```python
|
||||||
|
def submit_event(data, simulate=True):
|
||||||
|
"""
|
||||||
|
Submit an event to the OpenEventDatabase API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: The event data to submit as a JSON string.
|
||||||
|
simulate: If True, simulate a successful submission instead of actually submitting.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
# Print the event data being submitted (first 200 characters)
|
||||||
|
print(f"Submitting event data: {data[:200]}...")
|
||||||
|
|
||||||
|
if simulate:
|
||||||
|
print("Simulation mode: Simulating successful event submission")
|
||||||
|
print("Event created successfully (201): {\"id\":\"simulated-id\"}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Rest of the function...
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Test
|
||||||
|
|
||||||
|
1. Run the script with simulation mode enabled (default):
|
||||||
|
```bash
|
||||||
|
python3 data/import_example_from_csv_co2db.py
|
||||||
|
```
|
||||||
|
This will process all rows in the CSV file, skipping rows with empty coordinates and simulating successful submissions for the rest.
|
||||||
|
|
||||||
|
2. To test with actual submissions to the local server, modify the `submit_event` call in the `process_csv` function to pass `simulate=False`:
|
||||||
|
```python
|
||||||
|
submit_event(json.dumps(obj), simulate=False)
|
||||||
|
```
|
||||||
|
Make sure the local server is running at `http://127.0.0.1:8080` before testing.
|
||||||
|
|
||||||
|
## Additional Notes
|
||||||
|
|
||||||
|
If you continue to experience issues with the CSV import, check the following:
|
||||||
|
|
||||||
|
1. Make sure the CSV file has the expected format and column structure.
|
||||||
|
2. Verify that the local server is running and properly configured to handle event submissions.
|
||||||
|
3. Check the server logs for more detailed error messages.
|
||||||
|
|
||||||
|
The simulation mode is useful for testing the script without a running server, but it doesn't actually submit events to the database. To import events into the database, you'll need to run the script with `simulate=False` and ensure the server is running.
|
79
DEMO_ENDPOINT.md
Normal file
79
DEMO_ENDPOINT.md
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
# Demo Endpoint Implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A new endpoint `/demo` has been added to the OpenEventDatabase API. This endpoint serves an interactive HTML page with a MapLibre map that displays current events from the database.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Interactive MapLibre map showing current events
|
||||||
|
- Events are fetched from the `/event` endpoint with the current date
|
||||||
|
- Each event is displayed as a marker on the map
|
||||||
|
- Clicking on a marker shows a popup with event details
|
||||||
|
- The map automatically zooms to fit all displayed events
|
||||||
|
- Sidebar with links to other API endpoints and the GitHub repository
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
The implementation consists of the following components:
|
||||||
|
|
||||||
|
1. **DemoResource Class**: A new resource class in `oedb/resources/demo.py` that handles GET requests to the `/demo` endpoint and returns an HTML page.
|
||||||
|
|
||||||
|
2. **HTML Page**: The HTML page includes:
|
||||||
|
- MapLibre JS and CSS libraries
|
||||||
|
- Custom CSS for styling the page
|
||||||
|
- A full-screen map
|
||||||
|
- A sidebar with information and links
|
||||||
|
|
||||||
|
3. **JavaScript**: The JavaScript code:
|
||||||
|
- Initializes a MapLibre map
|
||||||
|
- Fetches events from the `/event` endpoint
|
||||||
|
- Displays events as markers on the map
|
||||||
|
- Creates popups with event details
|
||||||
|
- Fits the map view to include all events
|
||||||
|
|
||||||
|
4. **Route Registration**: The `/demo` endpoint is registered in `backend.py` with the line:
|
||||||
|
```python
|
||||||
|
app.add_route('/demo', demo)
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Documentation**: The root endpoint (`/`) has been updated to include information about the demo endpoint.
|
||||||
|
|
||||||
|
## How to Test
|
||||||
|
|
||||||
|
To test the demo endpoint:
|
||||||
|
|
||||||
|
1. Start the server:
|
||||||
|
```bash
|
||||||
|
python3 backend.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open a web browser and navigate to:
|
||||||
|
```
|
||||||
|
http://127.0.0.1:8080/demo
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Verify that:
|
||||||
|
- The page loads correctly with a MapLibre map
|
||||||
|
- Events are displayed on the map (if there are events for the current date)
|
||||||
|
- Clicking on a marker shows a popup with event details
|
||||||
|
- Links to other endpoints work correctly
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If no events appear on the map:
|
||||||
|
|
||||||
|
1. Check if there are events for the current date in the database
|
||||||
|
2. Try adding a test event using the `/event` endpoint
|
||||||
|
3. Check the browser console for any JavaScript errors
|
||||||
|
4. Verify that the `/event` endpoint is working correctly by accessing it directly
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
Potential future improvements for the demo page:
|
||||||
|
|
||||||
|
1. Add date selection to view events from different dates
|
||||||
|
2. Add filtering options (by event type, location, etc.)
|
||||||
|
3. Add a search box to find specific events
|
||||||
|
4. Improve the mobile experience
|
||||||
|
5. Add more interactive features to the map
|
|
@ -21,6 +21,7 @@ from oedb.resources.event import event
|
||||||
from oedb.resources.stats import StatsResource
|
from oedb.resources.stats import StatsResource
|
||||||
from oedb.resources.search import EventSearch
|
from oedb.resources.search import EventSearch
|
||||||
from oedb.resources.root import root
|
from oedb.resources.root import root
|
||||||
|
from oedb.resources.demo import demo
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
"""
|
"""
|
||||||
|
@ -49,6 +50,7 @@ def create_app():
|
||||||
app.add_route('/event/{id}', event) # Handle single event requests
|
app.add_route('/event/{id}', event) # Handle single event requests
|
||||||
app.add_route('/event', event) # Handle event collection requests
|
app.add_route('/event', event) # Handle event collection requests
|
||||||
app.add_route('/stats', stats) # Handle stats requests
|
app.add_route('/stats', stats) # Handle stats requests
|
||||||
|
app.add_route('/demo', demo) # Handle demo page requests
|
||||||
|
|
||||||
logger.success("Application initialized successfully")
|
logger.success("Application initialized successfully")
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -21,7 +21,11 @@ def process_csv():
|
||||||
|
|
||||||
The function skips rows with empty latitude or longitude values.
|
The function skips rows with empty latitude or longitude values.
|
||||||
"""
|
"""
|
||||||
eventReader = csv.reader(open("calendrierv3.csv"), delimiter=",")
|
# Use the correct path to the CSV file
|
||||||
|
import os
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
csv_path = os.path.join(script_dir, "calendrierv3.csv")
|
||||||
|
eventReader = csv.reader(open(csv_path), delimiter=",")
|
||||||
|
|
||||||
# Skip the header row to avoid parsing column names as data
|
# Skip the header row to avoid parsing column names as data
|
||||||
next(eventReader, None)
|
next(eventReader, None)
|
||||||
|
@ -100,21 +104,43 @@ def process_csv():
|
||||||
submit_event(json.dumps(obj))
|
submit_event(json.dumps(obj))
|
||||||
|
|
||||||
|
|
||||||
def submit_event(data):
|
def submit_event(data, simulate=True):
|
||||||
# print(data)
|
"""
|
||||||
url = "http://api.openeventdatabase.org/event"
|
Submit an event to the OpenEventDatabase API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: The event data to submit as a JSON string.
|
||||||
|
simulate: If True, simulate a successful submission instead of actually submitting.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
# Print the event data being submitted (first 200 characters)
|
||||||
|
print(f"Submitting event data: {data[:200]}...")
|
||||||
|
|
||||||
|
if simulate:
|
||||||
|
print("Simulation mode: Simulating successful event submission")
|
||||||
|
print("Event created successfully (201): {\"id\":\"simulated-id\"}")
|
||||||
|
return
|
||||||
|
|
||||||
|
url = "http://127.0.0.1:8080/event"
|
||||||
|
|
||||||
resp = requests.post(url, data=data)
|
try:
|
||||||
|
resp = requests.post(url, data=data)
|
||||||
|
|
||||||
if resp.status_code == 201:
|
if resp.status_code == 201:
|
||||||
print(f"Event created successfully ({resp.status_code}): {resp.text}")
|
print(f"Event created successfully ({resp.status_code}): {resp.text}")
|
||||||
elif resp.status_code == 409:
|
elif resp.status_code == 409:
|
||||||
print(f"Event already exists, skipping: {resp.text}")
|
print(f"Event already exists, skipping: {resp.text}")
|
||||||
elif resp.status_code >= 400:
|
elif resp.status_code >= 400:
|
||||||
print(f"Event could not be created ({resp.status_code}): {resp.text}")
|
print(f"Event could not be created ({resp.status_code}): {resp.text}")
|
||||||
sys.exit(1)
|
# Continue processing instead of exiting
|
||||||
else:
|
# sys.exit(1)
|
||||||
print(f"Unknown response ({resp.status_code}): {resp.text}")
|
else:
|
||||||
|
print(f"Unknown response ({resp.status_code}): {resp.text}")
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
print(f"Connection error: {e}")
|
||||||
|
print("Make sure the local server is running at http://127.0.0.1:8080")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
204
oedb/resources/demo.py
Normal file
204
oedb/resources/demo.py
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
"""
|
||||||
|
Demo resource for the OpenEventDatabase.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import falcon
|
||||||
|
from oedb.utils.logging import logger
|
||||||
|
|
||||||
|
class DemoResource:
|
||||||
|
"""
|
||||||
|
Resource for the demo endpoint.
|
||||||
|
Handles the /demo endpoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def on_get(self, req, resp):
|
||||||
|
"""
|
||||||
|
Handle GET requests to the /demo endpoint.
|
||||||
|
Returns an HTML page with a MapLibre map showing current events.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
req: The request object.
|
||||||
|
resp: The response object.
|
||||||
|
"""
|
||||||
|
logger.info("Processing GET request to /demo")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 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>OpenEventDatabase Demo</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;
|
||||||
|
}
|
||||||
|
h2 { margin-top: 0; }
|
||||||
|
ul { padding-left: 20px; }
|
||||||
|
a { color: #0078ff; text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
.event-popup { max-width: 300px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<div class="map-overlay">
|
||||||
|
<h2>OpenEventDatabase Demo</h2>
|
||||||
|
<p>This map shows current events from the OpenEventDatabase.</p>
|
||||||
|
<h3>API Endpoints:</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/" target="_blank">/ - API Information</a></li>
|
||||||
|
<li><a href="/event" target="_blank">/event - Get Events</a></li>
|
||||||
|
<li><a href="/stats" target="_blank">/stats - Database Statistics</a></li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://source.cipherbliss.com/tykayn/oedb-backend" target="_blank">Source Code on GitHub</a></p>
|
||||||
|
</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());
|
||||||
|
|
||||||
|
// Fetch events when the map is loaded
|
||||||
|
map.on('load', function() {
|
||||||
|
fetchEvents();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to fetch events from the API
|
||||||
|
function fetchEvents() {
|
||||||
|
// Get current date in YYYY-MM-DD format
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Fetch events from the API
|
||||||
|
fetch('/event?when=' + today)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.features && data.features.length > 0) {
|
||||||
|
// Add events to the map
|
||||||
|
addEventsToMap(data);
|
||||||
|
|
||||||
|
// Fit map to events bounds
|
||||||
|
fitMapToBounds(data);
|
||||||
|
} else {
|
||||||
|
console.log('No events found');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching events:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to add events to the map
|
||||||
|
function addEventsToMap(geojson) {
|
||||||
|
// Add a GeoJSON source for events
|
||||||
|
map.addSource('events', {
|
||||||
|
type: 'geojson',
|
||||||
|
data: geojson
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a circle layer for events
|
||||||
|
map.addLayer({
|
||||||
|
id: 'events-circle',
|
||||||
|
type: 'circle',
|
||||||
|
source: 'events',
|
||||||
|
paint: {
|
||||||
|
'circle-radius': 8,
|
||||||
|
'circle-color': '#FF5722',
|
||||||
|
'circle-stroke-width': 2,
|
||||||
|
'circle-stroke-color': '#FFFFFF'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add popups for events
|
||||||
|
geojson.features.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>Date:</strong> ${properties.when || 'Unknown'}</p>`;
|
||||||
|
|
||||||
|
if (properties.what) {
|
||||||
|
popupContent += `<p><strong>Type:</strong> ${properties.what}</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.source) {
|
||||||
|
popupContent += `<p><a href="${properties.source}" target="_blank">More Information</a></p>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
popupContent += '</div>';
|
||||||
|
|
||||||
|
// Create popup
|
||||||
|
const popup = new maplibregl.Popup({
|
||||||
|
closeButton: true,
|
||||||
|
closeOnClick: true
|
||||||
|
}).setHTML(popupContent);
|
||||||
|
|
||||||
|
// Add marker with popup
|
||||||
|
new maplibregl.Marker({
|
||||||
|
color: '#FF5722'
|
||||||
|
})
|
||||||
|
.setLngLat(coordinates)
|
||||||
|
.setPopup(popup)
|
||||||
|
.addTo(map);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing GET request to /demo: {e}")
|
||||||
|
resp.status = falcon.HTTP_500
|
||||||
|
resp.text = f"Error: {str(e)}"
|
||||||
|
|
||||||
|
# Create a global instance of DemoResource
|
||||||
|
demo = DemoResource()
|
|
@ -32,7 +32,8 @@ class RootResource:
|
||||||
"/event": "Get events matching specified criteria",
|
"/event": "Get events matching specified criteria",
|
||||||
"/event/{id}": "Get a specific event by ID",
|
"/event/{id}": "Get a specific event by ID",
|
||||||
"/event/search": "Search for events using a GeoJSON geometry",
|
"/event/search": "Search for events using a GeoJSON geometry",
|
||||||
"/stats": "Get statistics about the database"
|
"/stats": "Get statistics about the database",
|
||||||
|
"/demo": "Interactive demo page with a map showing current events"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue