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({
|
const map = new ol.Map({
|
||||||
target: "map",
|
target: "map",
|
||||||
controls: ol.control
|
controls: ol.control
|
||||||
.defaults.defaults({ rotate: false })
|
.defaults.defaults({ rotate: false })
|
||||||
.extend([
|
.extend([
|
||||||
createVisibleLayersExtentControl(),
|
createVisibleLayersExtentControl(),
|
||||||
|
createAreaZoomControl(),
|
||||||
new ol.control.Rotate({
|
new ol.control.Rotate({
|
||||||
autoHide: true,
|
autoHide: true,
|
||||||
label: createNorthPointerIcon(),
|
label: createNorthPointerIcon(),
|
||||||
@@ -90,6 +99,7 @@ const map = new ol.Map({
|
|||||||
zoom: 7,
|
zoom: 7,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
map.addInteraction(areaZoomInteraction);
|
||||||
|
|
||||||
let availableLayers = [];
|
let availableLayers = [];
|
||||||
let selectedLayers = [];
|
let selectedLayers = [];
|
||||||
@@ -108,12 +118,25 @@ document.addEventListener("keydown", (event) => {
|
|||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
if (!els.featureModal.hidden) {
|
if (!els.featureModal.hidden) {
|
||||||
closeFeatureModal();
|
closeFeatureModal();
|
||||||
|
} else if (areaZoomInteraction.getActive()) {
|
||||||
|
setAreaZoomMode(false);
|
||||||
} else if (!els.appShell.classList.contains("sidebar-is-collapsed")) {
|
} else if (!els.appShell.classList.contains("sidebar-is-collapsed")) {
|
||||||
setSidebarCollapsed(true);
|
setSidebarCollapsed(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
map.on("singleclick", showFeatureInformation);
|
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();
|
loadLayers();
|
||||||
|
|
||||||
@@ -150,6 +173,57 @@ function createVisibleLayersExtentControl() {
|
|||||||
return new ol.control.Control({ element });
|
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) {
|
async function searchParcela(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -328,6 +402,10 @@ function syncSelectedLayers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function showFeatureInformation(event) {
|
async function showFeatureInformation(event) {
|
||||||
|
if (areaZoomInteraction.getActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const queryLayers = availableLayers
|
const queryLayers = availableLayers
|
||||||
.map((layer) => layer.name)
|
.map((layer) => layer.name)
|
||||||
.filter(
|
.filter(
|
||||||
|
|||||||
+55
@@ -442,6 +442,56 @@ button:disabled {
|
|||||||
font-size: 1.3rem;
|
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. */
|
/* OpenLayers hides this control while the view is north-up. */
|
||||||
.map-wrap .ol-rotate {
|
.map-wrap .ol-rotate {
|
||||||
top: auto;
|
top: auto;
|
||||||
@@ -497,6 +547,11 @@ button:disabled {
|
|||||||
right: 12px;
|
right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-wrap .ol-area-zoom {
|
||||||
|
top: 136px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.map-wrap .ol-rotate {
|
.map-wrap .ol-rotate {
|
||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user