feat: indicate clickable map features

This commit is contained in:
2026-07-24 03:51:25 +01:00
parent 2510bc583d
commit bf44014dc1
+60 -15
View File
@@ -111,6 +111,9 @@ let pendingCompassHeading;
let compassAnimationFrame; let compassAnimationFrame;
let deviceCompassFallbackTimer; let deviceCompassFallbackTimer;
let modalDrag; let modalDrag;
let featureHoverRequest;
let featureHoverRequestId = 0;
let featureHoverTimer;
const map = new ol.Map({ const map = new ol.Map({
target: "map", target: "map",
@@ -173,6 +176,8 @@ document.addEventListener("keydown", (event) => {
}); });
window.addEventListener("resize", keepFeatureModalInViewport); window.addEventListener("resize", keepFeatureModalInViewport);
map.on("singleclick", showFeatureInformation); map.on("singleclick", showFeatureInformation);
map.on("pointermove", updateFeatureCursor);
map.getViewport().addEventListener("pointerleave", clearFeatureCursor);
areaZoomInteraction.on("boxend", zoomToSelectedArea); areaZoomInteraction.on("boxend", zoomToSelectedArea);
areaZoomInteraction.on("boxcancel", () => { areaZoomInteraction.on("boxcancel", () => {
// A click or a very short drag is not a selection. Exit on the next frame so // A click or a very short drag is not a selection. Exit on the next frame so
@@ -450,6 +455,7 @@ function setAreaZoomMode(active) {
); );
if (active) { if (active) {
clearFeatureCursor();
closeFeatureModal(); closeFeatureModal();
} }
} }
@@ -655,6 +661,7 @@ function syncSelectedLayers() {
return layerName === "parcelas" || layerName === unqualifiedLayerName(DWELLING_PARCEL_LAYER_NAME); return layerName === "parcelas" || layerName === unqualifiedLayerName(DWELLING_PARCEL_LAYER_NAME);
}), }),
); );
clearFeatureCursor();
} }
function isInternalLayer(name) { function isInternalLayer(name) {
@@ -704,6 +711,51 @@ async function showFeatureInformation(event) {
return; return;
} }
try {
const feature = await getFeatureAtCoordinate(event.coordinate);
if (feature) {
openFeatureModal(feature);
}
} catch (error) {
console.error(error);
}
}
function updateFeatureCursor(event) {
if (event.dragging || areaZoomInteraction.getActive()) {
clearFeatureCursor();
return;
}
window.clearTimeout(featureHoverTimer);
const requestId = ++featureHoverRequestId;
const coordinate = [...event.coordinate];
featureHoverTimer = window.setTimeout(async () => {
featureHoverRequest?.abort();
featureHoverRequest = new AbortController();
try {
const feature = await getFeatureAtCoordinate(coordinate, featureHoverRequest.signal);
if (requestId === featureHoverRequestId) {
map.getViewport().style.cursor = feature ? "pointer" : "";
}
} catch (error) {
if (error.name !== "AbortError" && requestId === featureHoverRequestId) {
map.getViewport().style.cursor = "";
}
}
}, 120);
}
function clearFeatureCursor() {
window.clearTimeout(featureHoverTimer);
featureHoverRequestId += 1;
featureHoverRequest?.abort();
featureHoverRequest = undefined;
map.getViewport().style.cursor = "";
}
async function getFeatureAtCoordinate(coordinate, signal) {
const queryLayers = availableLayers const queryLayers = availableLayers
.map((layer) => layer.name) .map((layer) => layer.name)
.filter( .filter(
@@ -711,32 +763,25 @@ async function showFeatureInformation(event) {
); );
if (!queryLayers.length) { if (!queryLayers.length) {
return; return null;
} }
const url = wmsSource.getFeatureInfoUrl(event.coordinate, map.getView().getResolution(), "EPSG:3857", { const url = wmsSource.getFeatureInfoUrl(coordinate, map.getView().getResolution(), "EPSG:3857", {
INFO_FORMAT: "application/json", INFO_FORMAT: "application/json",
QUERY_LAYERS: queryLayers.join(","), QUERY_LAYERS: queryLayers.join(","),
FEATURE_COUNT: 1, FEATURE_COUNT: 1,
}); });
if (!url) { if (!url) {
return; return null;
} }
try { const response = await fetch(url, { signal });
const response = await fetch(url); if (!response.ok) {
if (!response.ok) { throw new Error(`Feature information failed with status ${response.status}.`);
throw new Error(`Feature information failed with status ${response.status}.`);
}
const feature = (await response.json()).features?.[0];
if (feature) {
openFeatureModal(feature);
}
} catch (error) {
console.error(error);
} }
return (await response.json()).features?.[0] || null;
} }
function openFeatureModal(feature) { function openFeatureModal(feature) {