302 lines
No EOL
9.6 KiB
JavaScript
302 lines
No EOL
9.6 KiB
JavaScript
// Initialize the map
|
|
const map = new maplibregl.Map({
|
|
container: 'map',
|
|
style: 'https://tiles.openfreemap.org/styles/liberty',
|
|
center: [2.3522, 48.8566], // Default center (Paris)
|
|
zoom: 4
|
|
});
|
|
|
|
// 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" >OpenStreetMap</a> contributors'
|
|
}));
|
|
|
|
// 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 public API - using limit=1000 to get more events
|
|
fetch('https://api.openeventdatabase.org/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}" >${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
|
|
});
|
|
} |