import Spiderfy from 'https://cdn.jsdelivr.net/npm/@nazka/map-gl-js-spiderfy@1.2.10/dist/index.modern.js'; const MAP_SELECTOR = '[data-eventmanager-map="1"]'; const escapeHtml = (value) => { const stringValue = String(value ?? ''); return stringValue .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }; const parseItems = (container) => { const dataElementId = container.dataset.mapDataId || ''; if (!dataElementId) { return []; } const payload = document.getElementById(dataElementId); if (!payload) { return []; } try { const parsed = JSON.parse(payload.textContent || '[]'); if (!Array.isArray(parsed)) { return []; } const unique = new Map(); parsed.forEach((item) => { if (!item || !['event', 'location', 'organisation'].includes(item.type)) { return; } const id = Number(item.id || 0); const latitude = Number(item.latitude); const longitude = Number(item.longitude); if (!Number.isFinite(id) || id <= 0 || !Number.isFinite(latitude) || !Number.isFinite(longitude)) { return; } unique.set(`${item.type}:${id}`, { type: item.type, id, title: String(item.title || ''), latitude, longitude, extra: item.extra && typeof item.extra === 'object' ? item.extra : {}, }); }); return Array.from(unique.values()); } catch (error) { return []; } }; const popupHtmlFor = (item) => { if (item.type === 'event') { const locationTitle = escapeHtml(item.extra.locationTitle || ''); const startDate = escapeHtml(item.extra.startDate || ''); return [ `${escapeHtml(item.title)}`, locationTitle ? `
${locationTitle}
` : '', startDate ? `
${startDate}
` : '', ].join(''); } return `${escapeHtml(item.title)}`; }; const toFeature = (item) => ({ type: 'Feature', geometry: { type: 'Point', coordinates: [item.longitude, item.latitude], }, properties: { id: item.id, type: item.type, title: item.title, popupHtml: popupHtmlFor(item), }, }); const addSimpleMarker = (map, item) => { const popup = new maplibregl.Popup({ offset: 20 }).setHTML(popupHtmlFor(item)); new maplibregl.Marker() .setLngLat([item.longitude, item.latitude]) .setPopup(popup) .addTo(map); }; const initEventLayers = (map, eventItems) => { const sourceId = 'eventmanager-events-source'; const clusterLayerId = 'eventmanager-events-clusters'; const clusterCountLayerId = 'eventmanager-events-cluster-count'; const unclusteredLayerId = 'eventmanager-events-unclustered'; const features = eventItems.map(toFeature); map.addSource(sourceId, { type: 'geojson', data: { type: 'FeatureCollection', features, }, cluster: true, clusterMaxZoom: 22, clusterRadius: 22, }); map.addLayer({ id: clusterLayerId, type: 'circle', source: sourceId, filter: ['has', 'point_count'], paint: { 'circle-radius': 18, 'circle-opacity': 0.8, }, }); map.addLayer({ id: clusterCountLayerId, type: 'symbol', source: sourceId, filter: ['has', 'point_count'], layout: { 'text-field': ['get', 'point_count'], 'text-size': 12, }, }); map.addLayer({ id: unclusteredLayerId, type: 'circle', source: sourceId, filter: ['!', ['has', 'point_count']], paint: { 'circle-radius': 9, 'circle-stroke-width': 2, }, }); const openEventPopup = (feature, coordinatesOverride = null) => { const popupHtml = feature?.properties?.popupHtml || ''; const rawCoords = coordinatesOverride || feature?.geometry?.coordinates || null; if (!Array.isArray(rawCoords) || rawCoords.length !== 2) { return; } const [lng, lat] = rawCoords.map((coord) => Number(coord)); if (!Number.isFinite(lng) || !Number.isFinite(lat)) { return; } new maplibregl.Popup({ offset: 15 }) .setLngLat([lng, lat]) .setHTML(String(popupHtml)) .addTo(map); }; map.on('click', unclusteredLayerId, (event) => { const feature = event.features && event.features[0] ? event.features[0] : null; if (!feature) { return; } openEventPopup(feature, event.lngLat ? [event.lngLat.lng, event.lngLat.lat] : null); }); map.on('mouseenter', unclusteredLayerId, () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', unclusteredLayerId, () => { map.getCanvas().style.cursor = ''; }); if (typeof Spiderfy === 'function') { const spiderfy = new Spiderfy(map, { closeOnLeafClick: true, onLeafClick: (feature, spiderEvent) => { const clickCoordinates = spiderEvent?.lngLat ? [spiderEvent.lngLat.lng, spiderEvent.lngLat.lat] : null; openEventPopup(feature, clickCoordinates); }, }); spiderfy.applyTo(clusterLayerId); } else { map.on('click', clusterLayerId, (event) => { const feature = event.features && event.features[0] ? event.features[0] : null; if (!feature) { return; } const clusterId = feature.properties?.cluster_id; if (undefined === clusterId || null === clusterId) { return; } const source = map.getSource(sourceId); if (!source || typeof source.getClusterExpansionZoom !== 'function') { return; } source.getClusterExpansionZoom(clusterId, (error, zoom) => { if (error) { return; } map.easeTo({ center: event.lngLat, zoom, }); }); }); } map.on('mouseenter', clusterLayerId, () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', clusterLayerId, () => { map.getCanvas().style.cursor = ''; }); }; const initSingleMap = (container) => { if (container.dataset.mapInitialized === '1') { return; } const items = parseItems(container); if (!items.length || typeof maplibregl === 'undefined') { return; } container.dataset.mapInitialized = '1'; if (typeof pmtiles !== 'undefined' && !window.__eventmanagerPmtilesRegistered) { const protocol = new pmtiles.Protocol(); maplibregl.addProtocol('pmtiles', protocol.tile); window.__eventmanagerPmtilesRegistered = true; } const style = container.dataset.mapStyle || 'https://maps.mummert.media/metadaten/world-light.json'; const map = new maplibregl.Map({ container, style, zoom: 12, pitch: 0, bearing: 0, center: [items[0].longitude, items[0].latitude], }); map.addControl(new maplibregl.NavigationControl({ visualizePitch: true })); const fitBounds = (coordinates) => { const points = coordinates.filter((coord) => Array.isArray(coord) && coord.length === 2); if (!points.length) { return; } if (1 === points.length) { map.setCenter(points[0]); map.setZoom(14); return; } const bounds = new maplibregl.LngLatBounds(); points.forEach((coord) => bounds.extend(coord)); map.fitBounds(bounds, { padding: 60, maxZoom: 15, }); }; map.on('load', () => { const nonEventItems = items.filter((item) => item.type !== 'event'); const eventItems = items.filter((item) => item.type === 'event'); const allCoordinates = items.map((item) => [item.longitude, item.latitude]); nonEventItems.forEach((item) => { addSimpleMarker(map, item); }); if (eventItems.length) { initEventLayers(map, eventItems); } fitBounds(allCoordinates); }); }; const bootstrapMapModules = () => { const modules = document.querySelectorAll(MAP_SELECTOR); modules.forEach((container) => initSingleMap(container)); }; if ('loading' === document.readyState) { document.addEventListener('DOMContentLoaded', bootstrapMapModules, { once: true }); } else { bootstrapMapModules(); }