sync: 2026-08-21 16:58:10
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user