diff --git a/dve.css b/dve.css index 64e9cdd..27874c9 100644 --- a/dve.css +++ b/dve.css @@ -57,66 +57,6 @@ html, body { top: auto; bottom: 10px; } -.layer-selector { - padding: 0; - min-width: 190px; -} -.layer-selector-button { - width: 100%; - max-width: 280px; - border: 0; - background: transparent; - padding: 6px 24px 6px 8px; - text-align: left; - font: inherit; - cursor: pointer; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.layer-selector-button::after { - content: "▾"; - position: absolute; - right: 8px; -} -.layer-selector-menu { - display: none; - min-width: 260px; - max-width: 360px; - max-height: 65vh; - overflow: auto; - border-top: 1px solid rgba(0,0,0,0.15); -} -.layer-selector.open .layer-selector-menu { - display: block; -} -.layer-group-title { - padding: 8px 8px 4px; - font-weight: bold; - color: #555; -} -.layer-option, -.layer-overlay-option { - display: block; - box-sizing: border-box; - width: 100%; - border: 0; - background: transparent; - padding: 5px 8px 5px 18px; - text-align: left; - font: inherit; - cursor: pointer; -} -.layer-option:hover, -.layer-overlay-option:hover { - background: rgba(0,0,0,0.08); -} -.layer-option.active { - font-weight: bold; -} -.layer-overlay-option input { - margin-right: 6px; -} .crosshair { position: absolute; diff --git a/dve.js b/dve.js index 0738ebe..4c99de8 100644 --- a/dve.js +++ b/dve.js @@ -1,18 +1,12 @@ const json_files = [ "tileconfig/osm.json", "tileconfig/mapycom.json", - "tileconfig/praha.json", - "tileconfig/others.json" + "tileconfig/praha.json" ]; const center = [50.08804, 14.42076]; const zoom = 14; const mapCounts = [1, 2, 4, 6, 9]; -const layerCategories = { - classic: "Klasické", - ortho: "Ortomapy", - overlay: "Overlaye" -}; let syncing = false; let urlUpdateTimer = null; @@ -20,151 +14,18 @@ let activeMouseMap = null; let activeMousePoint = null; let activeMouseLatLng = null; -let TILE_SERVERS = {}; +let TILE_SERVERS = []; let maps = []; -let baseLayers = []; -let overlayLayers = []; +let layers = []; let crosshairs = []; -function getLayerCategory(name, config) { - const category = (config.category || "").toString().toLowerCase(); - const lowerName = name.toLowerCase(); - - if (["overlay", "overlays", "prekryv", "překryv"].includes(category)) return "overlay"; - if (["ortho", "ortomap", "orthomap", "orthophoto", "ortofoto", "ortomapa"].includes(category)) return "ortho"; - if (["classic", "base", "normal", "klasicke", "klasické"].includes(category)) return "classic"; - - if (lowerName.includes("overlay")) return "overlay"; - if (lowerName.includes("orto")) return "ortho"; - - return "classic"; -} - -function getLayerNamesByCategory(category) { - return Object.keys(TILE_SERVERS).filter(name => getLayerCategory(name, TILE_SERVERS[name]) === category); -} - -function getBaseLayerNames() { - return Object.keys(TILE_SERVERS).filter(name => getLayerCategory(name, TILE_SERVERS[name]) !== "overlay"); -} - -function getValidBaseLayerName(name) { - if (name && TILE_SERVERS[name] && getLayerCategory(name, TILE_SERVERS[name]) !== "overlay") return name; - - return getBaseLayerNames()[0] || Object.keys(TILE_SERVERS)[0]; -} - -function getValidOverlayNames(names) { - const selected = []; - - names.forEach(name => { - if ( - !selected.includes(name) && - TILE_SERVERS[name] && - getLayerCategory(name, TILE_SERVERS[name]) === "overlay" - ) { - selected.push(name); - } - }); - - return selected; -} - -function closeLayerSelectors(except) { - document.querySelectorAll(".layer-selector.open").forEach(selector => { - if (selector !== except) selector.classList.remove("open"); - }); -} - -function updateLayerSelectorLabel(selector) { - const button = selector.querySelector(".layer-selector-button"); - const state = getLayerSelectorState(selector.dataset.index); - button.textContent = state.base + (state.overlays.length ? " +" + state.overlays.length : ""); - - selector.querySelectorAll(".layer-option").forEach(option => { - option.classList.toggle("active", option.dataset.layer === state.base); - }); -} - -function addLayerGroup(menu, title, names, overlay, selector) { - if (!names.length) return; - - const groupTitle = document.createElement("div"); - groupTitle.className = "layer-group-title"; - groupTitle.textContent = title; - menu.appendChild(groupTitle); - - names.forEach(name => { - if (overlay) { - const label = document.createElement("label"); - const input = document.createElement("input"); - label.className = "layer-overlay-option"; - input.type = "checkbox"; - input.dataset.layer = name; - input.checked = selector._initialOverlays.includes(name); - label.appendChild(input); - label.appendChild(document.createTextNode(name)); - menu.appendChild(label); - - input.addEventListener("change", e => { - const index = parseInt(selector.dataset.index, 10); - setOverlayLayer(index, name, e.target.checked); - updateLayerSelectorLabel(selector); - writeUrlState(maps[index]); - }); - } else { - const option = document.createElement("button"); - option.type = "button"; - option.className = "layer-option"; - option.dataset.layer = name; - option.textContent = name; - menu.appendChild(option); - - option.addEventListener("click", () => { - const index = parseInt(selector.dataset.index, 10); - selector.dataset.baseLayer = name; - setBaseLayer(index, name); - updateLayerSelectorLabel(selector); - selector.classList.remove("open"); - writeUrlState(maps[index]); - }); - } - }); -} - -function fillLayerSelector(selector, index, state) { - const baseLayer = getValidBaseLayerName(state.base); - const overlays = getValidOverlayNames(state.overlays || []); - - selector.innerHTML = ""; - selector.dataset.index = index; - selector.dataset.baseLayer = baseLayer; - selector._initialOverlays = overlays; - - const button = document.createElement("button"); - button.type = "button"; - button.className = "layer-selector-button"; - selector.appendChild(button); - - const menu = document.createElement("div"); - menu.className = "layer-selector-menu"; - selector.appendChild(menu); - - addLayerGroup(menu, layerCategories.classic, getLayerNamesByCategory("classic"), false, selector); - addLayerGroup(menu, layerCategories.ortho, getLayerNamesByCategory("ortho"), false, selector); - addLayerGroup(menu, layerCategories.overlay, getLayerNamesByCategory("overlay"), true, selector); - - ["click", "mousedown", "dblclick", "wheel", "touchstart"].forEach(type => { - selector.addEventListener(type, e => e.stopPropagation()); - }); - - button.addEventListener("click", () => { - const open = !selector.classList.contains("open"); - closeLayerSelectors(selector); - selector.classList.toggle("open", open); - }); - - updateLayerSelectorLabel(selector); +function fillDropdown(select) { + for (const name in TILE_SERVERS) { + const opt = document.createElement("option"); + opt.value = name; + opt.textContent = name; + select.appendChild(opt); + } } function readUrlState() { @@ -196,31 +57,23 @@ function readUrlState() { } for (let i = 1; i <= state.count; i++) { - state.layers.push({ - base: params.get("layer" + i), - overlays: params.getAll("overlay" + i) - }); + const layer = params.get("layer" + i); + + if (layer) state.layers.push(layer); } return state; } -function getLayerSelectorState(index) { - const selector = document.getElementById("select" + (parseInt(index, 10) + 1)); - const overlays = []; +function getSelectedLayers(count) { + const selected = []; - selector.querySelectorAll(".layer-overlay-option input:checked").forEach(input => { - overlays.push(input.dataset.layer); - }); + for (let i = 1; i <= count; i++) { + const select = document.getElementById("select" + i); + if (select && select.value) selected.push(select.value); + } - return { - base: selector.dataset.baseLayer, - overlays - }; -} - -function addUrlParam(parts, key, value) { - if (value) parts.push(key + "=" + encodeURIComponent(value)); + return selected; } function writeUrlState(map) { @@ -229,18 +82,15 @@ function writeUrlState(map) { urlUpdateTimer = setTimeout(() => { const c = map.getCenter(); const count = parseInt(document.getElementById("mapCountSelector").value, 10); - const parts = []; let hash = "#map=" + map.getZoom() + "/" + c.lat.toFixed(5) + "/" + c.lng.toFixed(5); - parts.push("count=" + count); + hash += "&count=" + count; - for (let i = 0; i < count; i++) { - const state = getLayerSelectorState(i); - addUrlParam(parts, "layer" + (i + 1), state.base); - state.overlays.forEach(name => addUrlParam(parts, "overlay" + (i + 1), name)); - } + const selected = getSelectedLayers(count); + selected.forEach((name, index) => { + hash += "&layer" + (index + 1) + "=" + encodeURIComponent(name); + }); - hash += "&" + parts.join("&"); history.replaceState(null, "", hash); }, 100); } @@ -259,14 +109,15 @@ function sync(source) { updateCrosshairs(); } -function createLayer(config, zIndex) { +function setLayer(map, config, currentLayer) { + if (currentLayer) map.removeLayer(currentLayer); + if (config.type === "xyz") { return L.tileLayer(config.url, { maxZoom: 19, tileSize: 256, - zIndex, referrerPolicy: "strict-origin-when-cross-origin" - }); + }).addTo(map); } if (config.type === "wms") { @@ -276,40 +127,8 @@ function createLayer(config, zIndex) { transparent: config.transparent ?? true, version: config.version || "1.3.0", attribution: config.attribution || "", - zIndex, referrerPolicy: "strict-origin-when-cross-origin" - }); - } -} - -function setLayer(map, config, currentLayer, zIndex) { - if (currentLayer) map.removeLayer(currentLayer); - - return createLayer(config, zIndex).addTo(map); -} - -function bringOverlayLayersToFront(index) { - Object.values(overlayLayers[index]).forEach(layer => { - layer.setZIndex(100); - layer.bringToFront(); - }); -} - -function setBaseLayer(index, name) { - baseLayers[index] = setLayer(maps[index], TILE_SERVERS[name], baseLayers[index], 1); - bringOverlayLayersToFront(index); -} - -function setOverlayLayer(index, name, enabled) { - if (enabled) { - if (!overlayLayers[index][name]) { - overlayLayers[index][name] = createLayer(TILE_SERVERS[name], 100).addTo(maps[index]); - } - - overlayLayers[index][name].bringToFront(); - } else if (overlayLayers[index][name]) { - maps[index].removeLayer(overlayLayers[index][name]); - delete overlayLayers[index][name]; + }).addTo(map); } } @@ -413,11 +232,13 @@ function init() { const mapCountSelector = document.getElementById("mapCountSelector"); fillMapCountDropdown(mapCountSelector); - document.addEventListener("click", () => closeLayerSelectors()); - for (let i = 1; i <= 9; i++) { - const selector = document.getElementById("select" + i); - fillLayerSelector(selector, i - 1, urlState.layers[i - 1] || {}); + const select = document.getElementById("select" + i); + fillDropdown(select); + + if (urlState.layers[i - 1] && TILE_SERVERS[urlState.layers[i - 1]]) { + select.value = urlState.layers[i - 1]; + } const map = L.map("map" + i, { center: urlState.center, zoom: urlState.zoom, zoomControl: false }); map.on("move", () => sync(map)); @@ -426,14 +247,17 @@ function init() { L.control.zoom({ position: "topright" }).addTo(map); maps.push(map); - baseLayers.push(null); - overlayLayers.push({}); + layers.push(null); crosshairs.push(createCrosshair(map)); initCrosshair(map); - const layerState = getLayerSelectorState(i - 1); - setBaseLayer(i - 1, layerState.base); - layerState.overlays.forEach(name => setOverlayLayer(i - 1, name, true)); + layers[i - 1] = setLayer(map, TILE_SERVERS[select.value], layers[i - 1]); + + select.addEventListener("change", e => { + const key = e.target.value; + layers[i - 1] = setLayer(map, TILE_SERVERS[key], layers[i - 1]); + writeUrlState(map); + }); } mapCountSelector.value = urlState.count; diff --git a/index.html b/index.html index 04ec9a8..e535ec3 100644 --- a/index.html +++ b/index.html @@ -31,39 +31,39 @@