map types
Signed-off-by: K1 <git@karoh.net>
This commit is contained in:
parent
e1b205d01c
commit
362e1fc88c
3 changed files with 275 additions and 52 deletions
60
dve.css
60
dve.css
|
|
@ -57,6 +57,66 @@ 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;
|
||||
|
|
|
|||
249
dve.js
249
dve.js
|
|
@ -7,6 +7,11 @@ const json_files = [
|
|||
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;
|
||||
|
|
@ -14,18 +19,151 @@ let activeMouseMap = null;
|
|||
let activeMousePoint = null;
|
||||
let activeMouseLatLng = null;
|
||||
|
||||
let TILE_SERVERS = [];
|
||||
let TILE_SERVERS = {};
|
||||
let maps = [];
|
||||
let layers = [];
|
||||
let baseLayers = [];
|
||||
let overlayLayers = [];
|
||||
let crosshairs = [];
|
||||
|
||||
function fillDropdown(select) {
|
||||
for (const name in TILE_SERVERS) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = name;
|
||||
opt.textContent = name;
|
||||
select.appendChild(opt);
|
||||
}
|
||||
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 readUrlState() {
|
||||
|
|
@ -57,23 +195,31 @@ function readUrlState() {
|
|||
}
|
||||
|
||||
for (let i = 1; i <= state.count; i++) {
|
||||
const layer = params.get("layer" + i);
|
||||
|
||||
if (layer) state.layers.push(layer);
|
||||
state.layers.push({
|
||||
base: params.get("layer" + i),
|
||||
overlays: params.getAll("overlay" + i)
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function getSelectedLayers(count) {
|
||||
const selected = [];
|
||||
function getLayerSelectorState(index) {
|
||||
const selector = document.getElementById("select" + (parseInt(index, 10) + 1));
|
||||
const overlays = [];
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const select = document.getElementById("select" + i);
|
||||
if (select && select.value) selected.push(select.value);
|
||||
}
|
||||
selector.querySelectorAll(".layer-overlay-option input:checked").forEach(input => {
|
||||
overlays.push(input.dataset.layer);
|
||||
});
|
||||
|
||||
return selected;
|
||||
return {
|
||||
base: selector.dataset.baseLayer,
|
||||
overlays
|
||||
};
|
||||
}
|
||||
|
||||
function addUrlParam(parts, key, value) {
|
||||
if (value) parts.push(key + "=" + encodeURIComponent(value));
|
||||
}
|
||||
|
||||
function writeUrlState(map) {
|
||||
|
|
@ -82,15 +228,18 @@ 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);
|
||||
|
||||
hash += "&count=" + count;
|
||||
parts.push("count=" + count);
|
||||
|
||||
const selected = getSelectedLayers(count);
|
||||
selected.forEach((name, index) => {
|
||||
hash += "&layer" + (index + 1) + "=" + encodeURIComponent(name);
|
||||
});
|
||||
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));
|
||||
}
|
||||
|
||||
hash += "&" + parts.join("&");
|
||||
history.replaceState(null, "", hash);
|
||||
}, 100);
|
||||
}
|
||||
|
|
@ -109,15 +258,13 @@ function sync(source) {
|
|||
updateCrosshairs();
|
||||
}
|
||||
|
||||
function setLayer(map, config, currentLayer) {
|
||||
if (currentLayer) map.removeLayer(currentLayer);
|
||||
|
||||
function createLayer(config) {
|
||||
if (config.type === "xyz") {
|
||||
return L.tileLayer(config.url, {
|
||||
maxZoom: 19,
|
||||
tileSize: 256,
|
||||
referrerPolicy: "strict-origin-when-cross-origin"
|
||||
}).addTo(map);
|
||||
});
|
||||
}
|
||||
|
||||
if (config.type === "wms") {
|
||||
|
|
@ -128,7 +275,28 @@ function setLayer(map, config, currentLayer) {
|
|||
version: config.version || "1.3.0",
|
||||
attribution: config.attribution || "",
|
||||
referrerPolicy: "strict-origin-when-cross-origin"
|
||||
}).addTo(map);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setLayer(map, config, currentLayer) {
|
||||
if (currentLayer) map.removeLayer(currentLayer);
|
||||
|
||||
return createLayer(config).addTo(map);
|
||||
}
|
||||
|
||||
function setBaseLayer(index, name) {
|
||||
baseLayers[index] = setLayer(maps[index], TILE_SERVERS[name], baseLayers[index]);
|
||||
}
|
||||
|
||||
function setOverlayLayer(index, name, enabled) {
|
||||
if (enabled) {
|
||||
if (!overlayLayers[index][name]) {
|
||||
overlayLayers[index][name] = createLayer(TILE_SERVERS[name]).addTo(maps[index]);
|
||||
}
|
||||
} else if (overlayLayers[index][name]) {
|
||||
maps[index].removeLayer(overlayLayers[index][name]);
|
||||
delete overlayLayers[index][name];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -232,13 +400,11 @@ function init() {
|
|||
const mapCountSelector = document.getElementById("mapCountSelector");
|
||||
fillMapCountDropdown(mapCountSelector);
|
||||
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
const select = document.getElementById("select" + i);
|
||||
fillDropdown(select);
|
||||
document.addEventListener("click", () => closeLayerSelectors());
|
||||
|
||||
if (urlState.layers[i - 1] && TILE_SERVERS[urlState.layers[i - 1]]) {
|
||||
select.value = urlState.layers[i - 1];
|
||||
}
|
||||
for (let i = 1; i <= 9; i++) {
|
||||
const selector = document.getElementById("select" + i);
|
||||
fillLayerSelector(selector, i - 1, urlState.layers[i - 1] || {});
|
||||
|
||||
const map = L.map("map" + i, { center: urlState.center, zoom: urlState.zoom, zoomControl: false });
|
||||
map.on("move", () => sync(map));
|
||||
|
|
@ -247,17 +413,14 @@ function init() {
|
|||
L.control.zoom({ position: "topright" }).addTo(map);
|
||||
|
||||
maps.push(map);
|
||||
layers.push(null);
|
||||
baseLayers.push(null);
|
||||
overlayLayers.push({});
|
||||
crosshairs.push(createCrosshair(map));
|
||||
initCrosshair(map);
|
||||
|
||||
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);
|
||||
});
|
||||
const layerState = getLayerSelectorState(i - 1);
|
||||
setBaseLayer(i - 1, layerState.base);
|
||||
layerState.overlays.forEach(name => setOverlayLayer(i - 1, name, true));
|
||||
}
|
||||
|
||||
mapCountSelector.value = urlState.count;
|
||||
|
|
|
|||
18
index.html
18
index.html
|
|
@ -31,39 +31,39 @@
|
|||
<body>
|
||||
<div id="container" data-count="2">
|
||||
<div class="map-wrapper">
|
||||
<select id="select1" class="selector"></select>
|
||||
<div id="select1" class="selector layer-selector"></div>
|
||||
<div id="map1" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper">
|
||||
<select id="select2" class="selector"></select>
|
||||
<div id="select2" class="selector layer-selector"></div>
|
||||
<div id="map2" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper hidden">
|
||||
<select id="select3" class="selector"></select>
|
||||
<div id="select3" class="selector layer-selector"></div>
|
||||
<div id="map3" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper hidden">
|
||||
<select id="select4" class="selector"></select>
|
||||
<div id="select4" class="selector layer-selector"></div>
|
||||
<div id="map4" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper hidden">
|
||||
<select id="select5" class="selector"></select>
|
||||
<div id="select5" class="selector layer-selector"></div>
|
||||
<div id="map5" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper hidden">
|
||||
<select id="select6" class="selector"></select>
|
||||
<div id="select6" class="selector layer-selector"></div>
|
||||
<div id="map6" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper hidden">
|
||||
<select id="select7" class="selector"></select>
|
||||
<div id="select7" class="selector layer-selector"></div>
|
||||
<div id="map7" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper hidden">
|
||||
<select id="select8" class="selector"></select>
|
||||
<div id="select8" class="selector layer-selector"></div>
|
||||
<div id="map8" class="map"></div>
|
||||
</div>
|
||||
<div class="map-wrapper hidden">
|
||||
<select id="select9" class="selector"></select>
|
||||
<div id="select9" class="selector layer-selector"></div>
|
||||
<div id="map9" class="map"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue