diff --git a/dve.css b/dve.css index 5876ad9..fb654cd 100644 --- a/dve.css +++ b/dve.css @@ -6,33 +6,13 @@ html, body { font-family: sans-serif; } #container { - display: grid; + display: flex; height: 100vh; width: 100vw; } -#container[data-count="2"] { - grid-template-columns: repeat(2, 1fr); - grid-template-rows: 1fr; -} -#container[data-count="4"] { - grid-template-columns: repeat(2, 1fr); - grid-template-rows: repeat(2, 1fr); -} -#container[data-count="6"] { - grid-template-columns: repeat(3, 1fr); - grid-template-rows: repeat(2, 1fr); -} -#container[data-count="9"] { - grid-template-columns: repeat(3, 1fr); - grid-template-rows: repeat(3, 1fr); -} .map-wrapper { + flex: 1; position: relative; - min-width: 0; - min-height: 0; -} -.map-wrapper.hidden { - display: none; } .map { height: 100%; @@ -48,36 +28,3 @@ html, body { border-radius: 4px; font-size: 14px; } -.layout-selector { - position: fixed; - top: auto; - bottom: 10px; -} - -.crosshair { - position: absolute; - display: none; - width: 24px; - height: 24px; - z-index: 999; - pointer-events: none; - transform: translate(-50%, -50%); -} -.crosshair::before, -.crosshair::after { - content: ""; - position: absolute; - background: #000; -} -.crosshair::before { - left: 11px; - top: 0; - width: 2px; - height: 24px; -} -.crosshair::after { - left: 0; - top: 11px; - width: 24px; - height: 2px; -} diff --git a/dve.js b/dve.js index 936bbcb..5f874af 100644 --- a/dve.js +++ b/dve.js @@ -6,18 +6,10 @@ const json_files = [ const center = [50.08804, 14.42076]; const zoom = 14; -const mapCounts = [2, 4, 6, 9]; let syncing = false; -let urlUpdateTimer = null; -let activeMouseMap = null; -let activeMousePoint = null; -let activeMouseLatLng = null; let TILE_SERVERS = []; -let maps = []; -let layers = []; -let crosshairs = []; function fillDropdown(select) { for (const name in TILE_SERVERS) { @@ -28,85 +20,11 @@ function fillDropdown(select) { } } -function readUrlState() { - const state = { - center, - zoom, - count: 2, - layers: [] - }; - - const match = window.location.hash.match(/^#map=([0-9.]+)\/(-?[0-9.]+)\/(-?[0-9.]+)/); - - if (match) { - const z = parseFloat(match[1]); - const lat = parseFloat(match[2]); - const lng = parseFloat(match[3]); - - if (Number.isFinite(lat) && Number.isFinite(lng) && Number.isFinite(z)) { - state.center = [lat, lng]; - state.zoom = z; - } - } - - const params = new URLSearchParams(window.location.hash.replace(/^#map=[^&]*/, "")); - const count = parseInt(params.get("count"), 10); - - if (mapCounts.includes(count)) { - state.count = count; - } - - for (let i = 1; i <= state.count; i++) { - const layer = params.get("layer" + i); - - if (layer) state.layers.push(layer); - } - - return state; -} - -function getSelectedLayers(count) { - const selected = []; - - for (let i = 1; i <= count; i++) { - const select = document.getElementById("select" + i); - if (select && select.value) selected.push(select.value); - } - - return selected; -} - -function writeUrlState(map) { - if (urlUpdateTimer) clearTimeout(urlUpdateTimer); - - urlUpdateTimer = setTimeout(() => { - const c = map.getCenter(); - const count = parseInt(document.getElementById("mapCountSelector").value, 10); - let hash = "#map=" + map.getZoom() + "/" + c.lat.toFixed(5) + "/" + c.lng.toFixed(5); - - hash += "&count=" + count; - - const selected = getSelectedLayers(count); - selected.forEach((name, index) => { - hash += "&layer" + (index + 1) + "=" + encodeURIComponent(name); - }); - - history.replaceState(null, "", hash); - }, 100); -} - -function sync(source) { +function sync(source, target) { if (syncing) return; syncing = true; - maps.forEach(target => { - if (target !== source) { - target.setView(source.getCenter(), source.getZoom(), { animate: false }); - } - }); + target.setView(source.getCenter(), source.getZoom(), { animate: false }); syncing = false; - - writeUrlState(source); - updateCrosshairs(); } function setLayer(map, config, currentLayer) { @@ -132,142 +50,41 @@ function setLayer(map, config, currentLayer) { } } -function fillMapCountDropdown(select) { - select.innerHTML = ""; - - mapCounts.forEach(count => { - const opt = document.createElement("option"); - opt.value = count; - opt.textContent = count + (count === 2 || count === 4 ? " mapy" : " map"); - select.appendChild(opt); - }); -} - -function setMapCount(count) { - const container = document.getElementById("container"); - container.dataset.count = count; - - for (let i = 1; i <= maps.length; i++) { - const wrapper = document.getElementById("map" + i).parentElement; - wrapper.classList.toggle("hidden", i > count); - } - - hideCrosshairs(); - - setTimeout(() => { - maps.forEach((map, index) => { - if (index < count) map.invalidateSize(false); - }); - }, 0); -} - -function isVisibleMap(map) { - return !map.getContainer().parentElement.classList.contains("hidden"); -} - -function createCrosshair(map) { - const crosshair = document.createElement("div"); - crosshair.className = "crosshair"; - map.getContainer().parentElement.appendChild(crosshair); - return crosshair; -} - -function hideCrosshairs() { - activeMouseMap = null; - activeMousePoint = null; - activeMouseLatLng = null; - - crosshairs.forEach(crosshair => { - crosshair.style.display = "none"; - }); -} - -function updateCrosshairs() { - if (!activeMouseMap || !activeMousePoint || !isVisibleMap(activeMouseMap)) return; - - activeMouseLatLng = activeMouseMap.containerPointToLatLng(activeMousePoint); - - maps.forEach((map, index) => { - const crosshair = crosshairs[index]; - - if (map === activeMouseMap || !isVisibleMap(map)) { - crosshair.style.display = "none"; - return; - } - - const point = map.latLngToContainerPoint(activeMouseLatLng); - const size = map.getSize(); - - if (point.x < 0 || point.y < 0 || point.x > size.x || point.y > size.y) { - crosshair.style.display = "none"; - return; - } - - crosshair.style.left = point.x + "px"; - crosshair.style.top = point.y + "px"; - crosshair.style.display = "block"; - }); -} - -function initCrosshair(map) { - const container = map.getContainer(); - - map.on("mousemove", e => { - activeMouseMap = map; - activeMousePoint = map.mouseEventToContainerPoint(e.originalEvent); - activeMouseLatLng = e.latlng; - updateCrosshairs(); - }); - - map.on("mouseout", e => { - if (!container.contains(e.originalEvent.relatedTarget)) hideCrosshairs(); - }); - - map.on("move", () => updateCrosshairs()); - map.on("zoom", () => updateCrosshairs()); -} - function init() { - const urlState = readUrlState(); - const mapCountSelector = document.getElementById("mapCountSelector"); - fillMapCountDropdown(mapCountSelector); + fillDropdown(document.getElementById("select1")); + fillDropdown(document.getElementById("select2")); - for (let i = 1; i <= 9; i++) { - const select = document.getElementById("select" + i); - fillDropdown(select); + // Create maps + const map1 = L.map("map1", { center, zoom, zoomControl: false }); + const map2 = L.map("map2", { center, zoom, zoomControl: false }); - if (urlState.layers[i - 1] && TILE_SERVERS[urlState.layers[i - 1]]) { - select.value = urlState.layers[i - 1]; - } + map1.on("move", () => sync(map1, map2)); + map2.on("move", () => sync(map2, map1)); - const map = L.map("map" + i, { center: urlState.center, zoom: urlState.zoom, zoomControl: false }); - map.on("move", () => sync(map)); - map.on("zoom", () => sync(map)); + map1.on("zoom", () => sync(map1, map2)); + map2.on("zoom", () => sync(map2, map1)); - L.control.zoom({ position: "topright" }).addTo(map); + L.control.zoom({ position: "topright" }).addTo(map1); + L.control.zoom({ position: "topright" }).addTo(map2); - maps.push(map); - layers.push(null); - crosshairs.push(createCrosshair(map)); - initCrosshair(map); + // Active tile layers + let layer1 = null; + let layer2 = null; - layers[i - 1] = setLayer(map, TILE_SERVERS[select.value], layers[i - 1]); + // Initialize with first server + layer1 = setLayer(map1, TILE_SERVERS[document.getElementById("select1").value], layer1); + layer2 = setLayer(map2, TILE_SERVERS[document.getElementById("select2").value], layer2); - 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; - mapCountSelector.addEventListener("change", e => { - setMapCount(parseInt(e.target.value, 10)); - writeUrlState(maps[0]); + // Dropdown change handlers + document.getElementById("select1").addEventListener("change", e => { + const key = e.target.value; + layer1 = setLayer(map1, TILE_SERVERS[key], layer1); }); - setMapCount(parseInt(mapCountSelector.value, 10)); - writeUrlState(maps[0]); + document.getElementById("select2").addEventListener("change", e => { + const key = e.target.value; + layer2 = setLayer(map2, TILE_SERVERS[key], layer2); + }); } async function loadTileConfigs(json_files) { diff --git a/index.html b/index.html index 5f1763b..df18083 100644 --- a/index.html +++ b/index.html @@ -29,7 +29,7 @@
-