sync: 2026-08-21 16:58:10

This commit is contained in:
2026-08-21 16:58:10 +02:00
parent a2953151a2
commit 15d473a83c

View File

@@ -648,57 +648,33 @@
MARKER STYLES
===================================================== */
/* Wrapper that holds both the label and the pin circle */
.poi-pin-wrapper {
position: relative;
cursor: pointer;
width: 34px;
height: 34px;
}
/* Permanent name label shown above each pin */
.pin-label {
position: absolute;
bottom: 38px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.92);
border: none;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
color: #202124;
font-size: 12px;
font-weight: 500;
padding: 2px 7px;
white-space: nowrap;
cursor: pointer;
}
/* Custom circular pin that reflects the POI type */
.poi-pin-inner {
width: 34px;
height: 34px;
/*
* Simple coloured dot marker.
* Kept intentionally minimal so MapLibre's internal
* transform-based positioning stays stable during
* zoom / pan animations on vector-tile maps.
*/
.poi-dot {
width: 28px;
height: 28px;
border-radius: 50%;
border: 3px solid #fff;
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
line-height: 1;
color: white;
border: 2px solid white;
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.35);
font-size: 14px;
cursor: pointer;
/* Prevent any CSS transition from fighting MapLibre's
per-frame transform updates during camera moves. */
will-change: transform;
}
.poi-dot.selected {
box-shadow: 0 0 0 4px rgba(26,115,232,0.35), 0 2px 8px rgba(0,0,0,0.5);
}
/* User location marker (blue dot) */
/* User location marker (blue pulsing dot) */
.user-loc-marker {
cursor: default;
}
@@ -1020,17 +996,50 @@
);
let markers = [];
/** All search-result POI markers currently on the map. */
let poiMarkers = [];
/** The single marker representing the user's current selection (click or search). */
let selectedMarker = null;
/** Popup attached to the selected marker (reverse-geocode result). */
let selectedPopup = null;
/** Blue dot showing the user's GPS position. */
let userLocationMarker = null;
let zoomMessageTimer = null;
/* =========================================================
MARKER FACTORY
========================================================= */
/**
* Build a simple coloured dot element for a POI marker.
* Deliberately kept flat (no nested absolutely-positioned
* children, no SVG, no filters) so that MapLibre's per-frame
* transform updates stay rock-stable during zoom / pan on
* vector-tile maps.
*/
function createDotElement(color, icon) {
const el = document.createElement("div");
el.className = "poi-dot";
el.style.background = color;
el.textContent = icon || "";
el.title = "";
return el;
}
/**
* Plain red dot for map-click selections / fallbacks.
*/
function createDefaultDot() {
return createDotElement("#ea4335", "");
}
/* =========================================================
SEARCH
========================================================= */
@@ -1125,168 +1134,77 @@
resultsCard.innerHTML = "";
const bounds = [];
const lngLats = [];
data.forEach(place => {
const result =
document.createElement(
"div"
);
result.className =
"result";
// Skip results with missing/invalid coordinates
if (
typeof place.lat !== "number" ||
typeof place.lon !== "number" ||
!isFinite(place.lat) ||
!isFinite(place.lon) ||
Math.abs(place.lat) > 90 ||
Math.abs(place.lon) > 180
)
return;
// --- Results list item ---
const result = document.createElement("div");
result.className = "result";
result.innerHTML = `
<div class="result-icon">
${getIcon(place)}
</div>
<div class="result-icon">${getIcon(place)}</div>
<div class="result-text">
<div class="result-name">
${escapeHtml(
getName(place)
)}
</div>
<div class="result-address">
${escapeHtml(
getShortAddress(place)
)}
</div>
<div class="result-type">
${escapeHtml(
formatType(
getType(place)
)
)}
</div>
<div class="result-name">${escapeHtml(getName(place))}</div>
<div class="result-address">${escapeHtml(getShortAddress(place))}</div>
<div class="result-type">${escapeHtml(formatType(getType(place)))}</div>
</div>
`;
result.addEventListener("click", () => {
selectPlace(place);
resultsCard.style.display = "none";
});
result.addEventListener(
"click",
() => {
resultsCard.appendChild(result);
selectPlace(
place
);
resultsCard.style.display =
"none";
}
// --- Map marker (simple dot, centered on coordinate) ---
const dotEl = createDotElement(
getPoiColor(place),
getIcon(place)
);
dotEl.title = getName(place);
const marker = new maplibregl.Marker({
element: dotEl,
anchor: "center"
}).setLngLat([place.lon, place.lat]).addTo(map);
resultsCard.appendChild(
result
);
// Build the marker element with label + pin
const el =
document.createElement(
"div"
);
el.className =
"poi-pin-wrapper";
el.innerHTML = `
<div class="pin-label">
${escapeHtml(
getName(place)
)}
</div>
<div
class="poi-pin-inner"
style="background:${getPoiColor(place)};"
>
${getIcon(place)}
</div>
`;
const marker =
new maplibregl.Marker({
element: el,
anchor: "center"
})
.setLngLat([
place.lon,
place.lat
])
.addTo(map);
marker.getElement().addEventListener(
"click",
() => {
selectPlace(
place
);
resultsCard.style.display =
"none";
}
);
markers.push(marker);
bounds.push([
place.lat,
place.lon
]);
// Stop propagation so clicking a POI pin does NOT
// also trigger the map's reverse-geocode handler.
marker.getElement().addEventListener("click", e => {
e.stopPropagation();
highlightPoiMarker(marker);
selectPlace(place);
resultsCard.style.display = "none";
});
poiMarkers.push(marker);
lngLats.push([place.lon, place.lat]);
});
if (bounds.length === 1) {
const zoom = getZoomForPlace(data[0]);
// Fly / fit to results
if (lngLats.length === 1) {
map.flyTo({
center: [
bounds[0][1],
bounds[0][0]
],
zoom: zoom,
center: lngLats[0],
zoom: getZoomForPlace(data[0]),
duration: 700
});
}
else {
const lons =
bounds.map(b => b[1]);
const lats =
bounds.map(b => b[0]);
map.fitBounds([
[
Math.min(...lons),
Math.min(...lats)
],
[
Math.max(...lons),
Math.max(...lats)
]
], {
padding: 80
});
} else {
const sw = [Math.min(...lngLats.map(p => p[0])), Math.min(...lngLats.map(p => p[1]))];
const ne = [Math.max(...lngLats.map(p => p[0])), Math.max(...lngLats.map(p => p[1]))];
map.fitBounds([sw, ne], { padding: 80 });
}
}
@@ -1430,9 +1348,7 @@
let url =
"/search?q=" +
encodeURIComponent(
query + ", "
);
encodeURIComponent(query);
if (viewbox) {
@@ -1515,40 +1431,46 @@
}
/**
* Highlight a POI marker by adding the "selected" class.
* Called when the user clicks on an existing search-result pin.
*/
function highlightPoiMarker(marker) {
// Remove highlight from all other POI markers
poiMarkers.forEach(m => {
m.getElement().classList.remove("selected");
});
marker.getElement().classList.add("selected");
}
function selectPlace(place) {
placeCard.style.display =
"block";
resultsCard.style.display =
"none";
placeCard.style.display = "block";
resultsCard.style.display = "none";
clearSelection();
// Coloured dot for known POIs, red default otherwise
const isKnownPoi = place.kind === "poi" || place.kind === "address";
const dotEl = isKnownPoi
? createDotElement(getPoiColor(place), getIcon(place))
: createDefaultDot();
dotEl.title = getName(place);
selectedMarker =
new maplibregl.Marker()
.setLngLat([
place.lon,
place.lat
])
.addTo(map);
selectedMarker = new maplibregl.Marker({
element: dotEl,
anchor: "center"
}).setLngLat([place.lon, place.lat]).addTo(map);
const zoom = getZoomForPlace(place);
map.flyTo({
center: [
place.lon,
place.lat
],
center: [place.lon, place.lat],
zoom: zoom,
duration: 700
});
renderPlace(place);
}
@@ -1625,14 +1547,10 @@
clearSelection();
selectedMarker =
new maplibregl.Marker()
.setLngLat([
lon,
lat
])
.addTo(map);
selectedMarker = new maplibregl.Marker({
element: createDefaultDot(),
anchor: "center"
}).setLngLat([lon, lat]).addTo(map);
try {
@@ -2529,14 +2447,8 @@
function clearMarkers() {
markers.forEach(
marker =>
marker.remove()
);
markers = [];
poiMarkers.forEach(marker => marker.remove());
poiMarkers = [];
clearSelection();
}