feat: add area zoom map control
This commit is contained in:
@@ -72,12 +72,21 @@ const parcelSearchLayer = new ol.layer.Vector({
|
||||
}),
|
||||
});
|
||||
|
||||
const areaZoomInteraction = new ol.interaction.DragBox({
|
||||
className: "ol-area-zoom-box",
|
||||
condition: ol.events.condition.primaryAction,
|
||||
minArea: 16,
|
||||
});
|
||||
areaZoomInteraction.setActive(false);
|
||||
let areaZoomButton;
|
||||
|
||||
const map = new ol.Map({
|
||||
target: "map",
|
||||
controls: ol.control
|
||||
.defaults.defaults({ rotate: false })
|
||||
.extend([
|
||||
createVisibleLayersExtentControl(),
|
||||
createAreaZoomControl(),
|
||||
new ol.control.Rotate({
|
||||
autoHide: true,
|
||||
label: createNorthPointerIcon(),
|
||||
@@ -90,6 +99,7 @@ const map = new ol.Map({
|
||||
zoom: 7,
|
||||
}),
|
||||
});
|
||||
map.addInteraction(areaZoomInteraction);
|
||||
|
||||
let availableLayers = [];
|
||||
let selectedLayers = [];
|
||||
@@ -108,12 +118,25 @@ document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
if (!els.featureModal.hidden) {
|
||||
closeFeatureModal();
|
||||
} else if (areaZoomInteraction.getActive()) {
|
||||
setAreaZoomMode(false);
|
||||
} else if (!els.appShell.classList.contains("sidebar-is-collapsed")) {
|
||||
setSidebarCollapsed(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
map.on("singleclick", showFeatureInformation);
|
||||
areaZoomInteraction.on("boxend", zoomToSelectedArea);
|
||||
areaZoomInteraction.on("boxcancel", () => {
|
||||
// A click or a very short drag is not a selection. Exit on the next frame so
|
||||
// cancelling a drag while disabling the interaction cannot recursively
|
||||
// trigger another cancellation.
|
||||
requestAnimationFrame(() => {
|
||||
if (areaZoomInteraction.getActive()) {
|
||||
setAreaZoomMode(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
loadLayers();
|
||||
|
||||
@@ -150,6 +173,57 @@ function createVisibleLayersExtentControl() {
|
||||
return new ol.control.Control({ element });
|
||||
}
|
||||
|
||||
function createAreaZoomControl() {
|
||||
const element = document.createElement("div");
|
||||
element.className = "ol-area-zoom ol-unselectable ol-control";
|
||||
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.title = "Zoom to area";
|
||||
button.setAttribute("aria-label", "Zoom to area");
|
||||
button.setAttribute("aria-pressed", "false");
|
||||
|
||||
const icon = document.createElement("img");
|
||||
icon.className = "area-zoom-icon";
|
||||
icon.src = "./assets/icons/area-zoom.svg";
|
||||
icon.alt = "";
|
||||
button.append(icon);
|
||||
|
||||
button.addEventListener("click", () => setAreaZoomMode(!areaZoomInteraction.getActive()));
|
||||
element.append(button);
|
||||
areaZoomButton = button;
|
||||
|
||||
return new ol.control.Control({ element });
|
||||
}
|
||||
|
||||
function setAreaZoomMode(active) {
|
||||
areaZoomInteraction.setActive(active);
|
||||
map.getViewport().classList.toggle("area-zoom-is-active", active);
|
||||
areaZoomButton?.setAttribute("aria-pressed", String(active));
|
||||
areaZoomButton?.setAttribute(
|
||||
"title",
|
||||
active ? "Exit area zoom (Esc)" : "Zoom to area",
|
||||
);
|
||||
|
||||
if (active) {
|
||||
closeFeatureModal();
|
||||
}
|
||||
}
|
||||
|
||||
function zoomToSelectedArea() {
|
||||
const extent = areaZoomInteraction.getGeometry()?.getExtent();
|
||||
|
||||
if (extent && extent.every(Number.isFinite)) {
|
||||
map.getView().fit(extent, {
|
||||
padding: [64, 64, 64, 64],
|
||||
duration: 500,
|
||||
maxZoom: 19,
|
||||
});
|
||||
}
|
||||
|
||||
setAreaZoomMode(false);
|
||||
}
|
||||
|
||||
async function searchParcela(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -328,6 +402,10 @@ function syncSelectedLayers() {
|
||||
}
|
||||
|
||||
async function showFeatureInformation(event) {
|
||||
if (areaZoomInteraction.getActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryLayers = availableLayers
|
||||
.map((layer) => layer.name)
|
||||
.filter(
|
||||
|
||||
+55
@@ -442,6 +442,56 @@ button:disabled {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.map-wrap .ol-area-zoom {
|
||||
top: 140px;
|
||||
right: 16px;
|
||||
left: auto;
|
||||
overflow: hidden;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--panel-border);
|
||||
border-radius: 14px;
|
||||
backdrop-filter: blur(16px);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.map-wrap .ol-area-zoom button {
|
||||
display: grid;
|
||||
width: 2.0625em;
|
||||
height: 2.0625em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
place-items: center;
|
||||
background: linear-gradient(135deg, var(--accent) 0%, #2b9a79 100%);
|
||||
color: #fffefb;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.map-wrap .ol-area-zoom button:hover,
|
||||
.map-wrap .ol-area-zoom button:focus,
|
||||
.map-wrap .ol-area-zoom button[aria-pressed="true"] {
|
||||
background: linear-gradient(135deg, var(--accent-strong) 0%, var(--accent) 100%);
|
||||
color: #fff;
|
||||
outline: 2px solid rgba(31, 122, 98, 0.35);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.area-zoom-icon {
|
||||
display: block;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.map-wrap .ol-viewport.area-zoom-is-active {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.map-wrap .ol-area-zoom-box {
|
||||
border-color: var(--accent);
|
||||
background: rgba(31, 122, 98, 0.16);
|
||||
}
|
||||
|
||||
/* OpenLayers hides this control while the view is north-up. */
|
||||
.map-wrap .ol-rotate {
|
||||
top: auto;
|
||||
@@ -497,6 +547,11 @@ button:disabled {
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.map-wrap .ol-area-zoom {
|
||||
top: 136px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.map-wrap .ol-rotate {
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
|
||||
Reference in New Issue
Block a user