Map: pitch option, marker ordering and event popup improvements

This commit is contained in:
Jürgen Mummert
2026-02-26 21:32:20 +01:00
parent ba321fdc23
commit fc2508af22
7 changed files with 130 additions and 18 deletions
+52 -9
View File
@@ -118,6 +118,21 @@ const escapeHtml = (value) => {
.replace(/'/g, ''');
};
const decodeHtmlEntities = (value) => {
const stringValue = String(value ?? '');
if ('undefined' === typeof document || !document.createElement) {
return stringValue;
}
const textarea = document.createElement('textarea');
textarea.innerHTML = stringValue;
return textarea.value;
};
const sanitizeDisplayText = (value) => escapeHtml(decodeHtmlEntities(value));
const parseItems = (container) => {
const dataElementId = container.dataset.mapDataId || '';
@@ -406,17 +421,24 @@ const ensureMarkerImage = (map, color, prefix = 'eventmanager-marker', options =
const popupHtmlFor = (item) => {
if (item.type === 'event') {
const locationTitle = escapeHtml(item.extra.locationTitle || '');
const startDate = escapeHtml(item.extra.startDate || '');
const title = sanitizeDisplayText(item.title || '');
const locationTitle = sanitizeDisplayText(item.extra.locationTitle || '');
const startDate = sanitizeDisplayText(item.extra.startDate || '');
const detailUrl = String(item.extra.detailUrl || '').trim();
const hasDetailUrl = /^(https?:\/\/|\/)/i.test(detailUrl);
const detailLink = hasDetailUrl
? `<div><a href="${escapeHtml(detailUrl)}">mehr Infos</a></div>`
: '';
return [
`<strong>${escapeHtml(item.title)}</strong>`,
`<strong>${title}</strong>`,
locationTitle ? `<div>${locationTitle}</div>` : '',
startDate ? `<div>${startDate}</div>` : '',
detailLink,
].join('');
}
return `<strong>${escapeHtml(item.title)}</strong>`;
return `<strong>${sanitizeDisplayText(item.title)}</strong>`;
};
const toFeature = (item) => ({
@@ -432,6 +454,7 @@ const toFeature = (item) => ({
markerTagIds: Array.isArray(item.extra?.organizationTagIds) ? item.extra.organizationTagIds : [],
markerTagLabels: Array.isArray(item.extra?.organizationTagLabels) ? item.extra.organizationTagLabels : [],
title: item.title,
latitude: item.latitude,
popupHtml: popupHtmlFor(item),
},
});
@@ -445,7 +468,9 @@ const initOrganizationMarkers = (map, organizationItems, organizationColor) => {
};
}
const markerEntries = organizationItems.map((item) => {
const sortedOrganizationItems = [...organizationItems].sort((firstItem, secondItem) => secondItem.latitude - firstItem.latitude);
const markerEntries = sortedOrganizationItems.map((item) => {
const popupHtml = popupHtmlFor(item);
const marker = new maplibregl.Marker(organizationColor ? { color: organizationColor } : undefined)
.setLngLat([item.longitude, item.latitude]);
@@ -456,6 +481,12 @@ const initOrganizationMarkers = (map, organizationItems, organizationColor) => {
marker.addTo(map);
const markerElement = marker.getElement();
if (markerElement) {
markerElement.style.zIndex = String(Math.round((90 - item.latitude) * 1000));
}
return {
marker,
tagIds: normalizeTagIds(item.extra?.organizationTagIds),
@@ -776,6 +807,7 @@ const initLocationLayers = (map, locationItems, markerImageId) => {
'icon-anchor': 'bottom',
'icon-allow-overlap': true,
'icon-ignore-placement': true,
'symbol-sort-key': ['*', -1, ['to-number', ['get', 'latitude'], 0]],
},
paint: {
'icon-opacity': 1,
@@ -915,6 +947,7 @@ const initEventLayers = (map, eventItems, eventColor, markerImageId, clusterMark
'icon-anchor': 'bottom',
'icon-allow-overlap': true,
'icon-ignore-placement': true,
'symbol-sort-key': ['*', -1, ['to-number', ['get', 'latitude'], 0]],
},
paint: {
'icon-opacity': 1,
@@ -1132,8 +1165,12 @@ const initSingleMap = (container) => {
const customLat = parseCoordinate(container.dataset.mapCenterLat);
const customLng = parseCoordinate(container.dataset.mapCenterLng);
const customZoom = Number(container.dataset.mapCenterZoom);
const customPitch = Number(container.dataset.mapPitch);
const eventColor = normalizeHexColor(container.dataset.mapEventColor, DEFAULT_EVENT_COLOR);
const hasCustomCenter = null !== customLat && null !== customLng;
const initialPitch = Number.isFinite(customPitch)
? Math.max(0, Math.min(85, customPitch))
: 0;
let initialCenter = DEFAULT_CENTER;
let initialZoom = DEFAULT_ZOOM;
@@ -1165,7 +1202,7 @@ const initSingleMap = (container) => {
container,
style,
zoom: initialZoom,
pitch: 0,
pitch: initialPitch,
bearing: 0,
center: initialCenter,
transformRequest: (url, resourceType) => {
@@ -1227,9 +1264,15 @@ const initSingleMap = (container) => {
map.on('load', async () => {
applyDefaultProjection(map);
const organizationItems = items.filter((item) => item.type === 'organisation');
const locationItems = items.filter((item) => item.type === 'location');
const eventItems = items.filter((item) => item.type === 'event');
const organizationItems = items
.filter((item) => item.type === 'organisation')
.sort((firstItem, secondItem) => secondItem.latitude - firstItem.latitude);
const locationItems = items
.filter((item) => item.type === 'location')
.sort((firstItem, secondItem) => secondItem.latitude - firstItem.latitude);
const eventItems = items
.filter((item) => item.type === 'event')
.sort((firstItem, secondItem) => secondItem.latitude - firstItem.latitude);
const allCoordinates = items.map((item) => [item.longitude, item.latitude]);
const organizationColor = normalizeHexColor(
container.dataset.mapOrganizationColor,