diff --git a/index.html b/index.html index 5334e6b..eb87702 100644 --- a/index.html +++ b/index.html @@ -3,22 +3,12 @@ LiDAR App - + - + - +

- Obernkirchener Sandstein -

-
-
+
-
+ + +
+ + + + + + +
+ +
- +
-
- -

Info

- - -

Bitte wählen Sie eine Punktwolke aus.

-
-
-
-
+
+ +

Info

+ + +

Bitte wählen Sie eine Punktwolke aus.

+
+ +
diff --git a/main.js b/main.js index f1f33df..3cfe068 100644 --- a/main.js +++ b/main.js @@ -1,68 +1,68 @@ + import maplibregl from "maplibre-gl"; import proj4 from "proj4"; import * as THREE from "three"; -import { LidarControl } from "maplibre-gl-lidar"; +import { LidarControl } from "maplibre-gl-lidar"; import "maplibre-gl/dist/maplibre-gl.css"; import "maplibre-gl-lidar/style.css"; +// UTM Zone 32N für Koordinatentransformation registrieren proj4.defs("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs"); - -// Koordinaten des Obernkirchener Sandstein +// Koordinaten des Obernkirchener Sandstein (Kartenmittelpunkt) const center = [9.209116842757239, 52.26520546238239]; -// die Grundkarte +// Zustandsvariablen +let currentPointCloud = null; // aktuell geladene Potree-Punktwolke +let isVisible = true; // Punktwolke sichtbar? +let currentRenderer = "deckgl"; // Default-Renderer deck.gl" +let currentColorMode = "rgb"; // Farbmodus: "rgb" oder "elevation" +let currentPointSize = Number(document.getElementById("pointSizeSlider").value); +let currentPointcloudKey = null; // ausgewählte Punktwolke +let lidarControl = null; // Deck.gl LidarControl-Instanz +var quality = "medium"; // Qualitätsstufe + + +//-------------------------------- Karten-Initialisierung -------------------------------------------------- +// Grundkarte: OpenFreeMap hell const map = new maplibregl.Map({ - container: 'map', - style: "https://tiles.openfreemap.org/styles/bright", - center: center, - zoom: 17, - maxZoom: 24, + container: "map", + style: "https://tiles.openfreemap.org/styles/bright", + center: center, + zoom: 17, + maxZoom: 24, }); // Karten-Bedienelemente -map.addControl(new maplibregl.NavigationControl({ +map.addControl( + new maplibregl.NavigationControl({ visualizePitch: true, showZoom: true, - showCompass: true -})); - -let currentPointCloud = null; -let isVisible = true; -let currentRenderer='deckgl'; -let currentColorMode='rgb'; -let currentPointSize=Number(document.getElementById("pointSizeSlider").value); -let currentPointcloudKey = null; -let lidarControl=null; -var quality = 'medium'; + showCompass: true, + }), +); -// WICHTIG: Layer/Controls immer erst im 'load'-Event der Karte hinzufügen -map.on('load', () => { - lidarControl = new LidarControl({ - title: 'Mein LiDAR-Viewer', - collapsed: false, - pointSize: 2, - colorScheme: 'rgb', // 'elevation' oder 'rgb' - pointBudget: 3000000, - maxRequests: 32, - pickable: false, - }); +// Karten Load-Event: LidarControl anlegen, JSON-Datei laden +map.on("load", () => { + lidarControl = new LidarControl({ + title: "LiDAR-Viewer", + collapsed: false, + pointSize: 2, + colorScheme: "rgb", // 'elevation' oder 'rgb' + pointBudget: 3000000, + maxRequests: 32, + pickable: false, + }); - // COPC-Datei laden - const selectBox=document.querySelector('select[name="pointcloud"]'); - if (selectBox && selectBox.value){ - currentPointcloudKey=selectBox.value; - loadCurrentPointCloud(); - } - loadInfoJSON(); + loadInfoJSON(); }); //---------------Potree Eigenschaften-------------------------- var pointSize = document.getElementById("pointSizeSlider").value; const elRenderArea = document.getElementById("potree_render_area"); -const viewer = new Potree.Viewer(elRenderArea, {noDragAndDrop: true}); +const viewer = new Potree.Viewer(elRenderArea, { noDragAndDrop: true }); viewer.setEDLEnabled(false); viewer.setFOV(60); viewer.setMinNodeSize(pointSize); @@ -70,563 +70,613 @@ viewer.setBackground("none"); viewer.orbitControls.enabled = false; viewer.fpControls.enabled = false; viewer.deviceControls.enabled = false; -elRenderArea.style.display='none'; -viewer.setPointBudget(3000000) +elRenderArea.style.display = "none"; +viewer.setPointBudget(3000000); + +//-------------------------- LIDARCONTROL (DECK.GL) NEU AUFBAUEN ---------------------------------------- +// entfernt das bestehende LidarControl und erzeugt es mit den aktuellen Einstellungen neu function resetLidarControl() { - if (!lidarControl) return; - try { - map.removeControl(lidarControl); - } catch(e) {} - lidarControl = null; + if (!lidarControl) + return; + try { + map.removeControl(lidarControl); + } catch (e) {} + lidarControl = null; - lidarControl = new LidarControl({ - title: 'Mein LiDAR-Viewer', - collapsed: false, - pointSize: currentPointSize > 0 ? currentPointSize : 2, - colorScheme: currentColorMode === 'rgb' ? 'rgb' : 'elevation', - pointBudget: 3000000, - maxRequests: 32, - pickable: false - }); - map.addControl(lidarControl, 'top-right'); + lidarControl = new LidarControl({ + title: "LiDAR-Viewer", + collapsed: false, + pointSize: currentPointSize > 0 ? currentPointSize : 2, + colorScheme: currentColorMode === "rgb" ? "rgb" : "elevation", + pointBudget: 3000000, + maxRequests: 32, + pickable: false, + }); + map.addControl(lidarControl, "top-right"); } +//-------------- PUNKTWOLKEN-DATENQUELLEN (Pfade je Renderer-Format) - aktuell auf Server der Jade-HS -------------------- + // Punktwolkendaten im Potree-Format function getPointCloudFilesPOTREE() { - return { - first: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp1_${quality}/metadata.json`, - second: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp2_${quality}/metadata.json`, - third: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp3_${quality}/metadata.json`, - }; + return { + first: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp1_${quality}/metadata.json`, + second: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp2_${quality}/metadata.json`, + third: `http://ar2350.web-01.fbbgg.hs-woe.de/Punktwolken%20konvertiert%20potree%20Format/sp3_${quality}/metadata.json`, + }; } // Punktwolkendaten im COPC (Cloud Optimised Point Cloud)-Format für deck.gl function getPointCloudFilesCOPC() { - return { - first: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp1_${quality}.copc.laz`, - second: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp2_${quality}.copc.laz`, - third: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp3_${quality}.copc.laz`, - }; + return { + first: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp1_${quality}.copc.laz`, + second: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp2_${quality}.copc.laz`, + third: `http://ar2350.web-01.fbbgg.hs-woe.de/copc%20Daten/sp3_${quality}.copc.laz`, + }; } -function loadCurrentPointCloud() { - if (!currentPointcloudKey) return; - if (currentRenderer === 'deckgl') { - loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]); - } else { - loadPointCloud(getPointCloudFilesPOTREE()[currentPointcloudKey]); - } +//---------------------------- PUNKTWOLKEN LADEN (je nach aktivem Renderer) ------------------------------------- + +// wählt automatisch Potree- oder Deck.gl-Renderer und lädt die Punktwolke +function loadPointCloud() { + if (!currentPointcloudKey) + return; + if (currentRenderer === "deckgl") { + loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]); + } else { + loadPointCloudPotree(getPointCloudFilesPOTREE()[currentPointcloudKey]); + } } + +//---------------------- Punktwolken laden über den deck.gl-Renderer ---------------------- function loadDeckGL(path) { - if (!lidarControl || !path) return; - resetLidarControl(); - lidarControl.loadPointCloudStreaming(path); - setTimeout(() => applyDeckGLSettings(), 500); + if (!lidarControl || !path) + return; + resetLidarControl(); + lidarControl.loadPointCloudStreaming(path); + setTimeout(() => applyDeckGLSettings(), 500); } +// Punktgröße, Farbschema und Colormap auf LidarControl anwenden function applyDeckGLSettings() { - if (!lidarControl) return; - try { - lidarControl.setPointSize(currentPointSize > 0 ? currentPointSize : 2); - lidarControl.setColorScheme(currentColorMode === 'rgb' ? 'rgb' : 'elevation'); - lidarControl.setColormap('jet'); - //lidarControl.setPointBudget(3000000); - } catch(e) { console.warn("LidarControl API:", e); } + if (!lidarControl) + return; + try { + lidarControl.setPointSize(currentPointSize > 0 ? currentPointSize : 2); + lidarControl.setColorScheme(currentColorMode === "rgb" ? "rgb" : "elevation",); + lidarControl.setColormap("jet"); + } catch (e) {} } + +//------------------------- Renderer zwischen deck.gl und potree umstellen ----------------------------------- function switchRenderer(renderer) { - currentRenderer = renderer; - const slider = document.getElementById("pointSizeSlider"); - if (renderer === 'deckgl') { - elRenderArea.style.display = 'none'; - if (currentPointCloud) currentPointCloud.visible = false; - - currentPointSize = 2; - slider.min = 1; - slider.max = 10; - slider.value = 2; - if (lidarControl && currentPointcloudKey) loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]); - document.getElementById('rendererToggle').dataset.active = 'deckgl'; - document.getElementById('rendererLabel').textContent = 'Renderer: Deck.gl'; - } else { - resetLidarControl(); - elRenderArea.style.display = 'block'; - - currentPointSize = 0; - slider.min = 0; - slider.max = 1000; - slider.value = 0; - - if (currentPointcloudKey) loadPointCloud(getPointCloudFilesPOTREE()[currentPointcloudKey]); - document.getElementById('rendererToggle').dataset.active = 'potree'; - document.getElementById('rendererLabel').textContent = 'Renderer: Potree'; + currentRenderer = renderer; + const slider = document.getElementById("pointSizeSlider"); + if (renderer === "deckgl") { + elRenderArea.style.display = "none"; + if (currentPointCloud){ + currentPointCloud.visible = false; } - applyVisibility(); + currentPointSize = 2; + slider.min = 1; + slider.max = 10; + slider.value = 2; + if (lidarControl && currentPointcloudKey){ + loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]); + } + document.getElementById("rendererToggle").dataset.active = "deckgl"; + document.getElementById("rendererLabel").textContent = "Renderer: Deck.gl"; + } else { + resetLidarControl(); + elRenderArea.style.display = "block"; + + currentPointSize = 0; + slider.min = 0; + slider.max = 1000; + slider.value = 0; + + if (currentPointcloudKey){ + loadPointCloudPotree(getPointCloudFilesPOTREE()[currentPointcloudKey]); + } + document.getElementById("rendererToggle").dataset.active = "potree"; + document.getElementById("rendererLabel").textContent = "Renderer: Potree"; + } + applyVisibility(); } + +// Sichtbarkeit der Punktwolke je nach aktivem Renderer setzen function applyVisibility() { - - if (currentRenderer === 'potree' && currentPointCloud) { - currentPointCloud.visible = isVisible; - } - - if (currentRenderer === 'deckgl') { - - lidarControl.setPointSize(isVisible ? 2 : 0); - } + if (currentRenderer === "potree" && currentPointCloud) { + currentPointCloud.visible = isVisible; + } + + if (currentRenderer === "deckgl") { + // Trick: Punktgröße auf 0 setzen + lidarControl.setPointSize(isVisible ? 2 : 0); + } } - +// Farbmodus (rgb/elevation) auf die Potree-Punktwolke anwenden function applyColorModePotree() { - if (!currentPointCloud) return; - let mat = currentPointCloud.material; - mat.activeAttributeName = currentColorMode === 'rgb' ? "rgba" : "elevation"; - viewer.renderer.resetState(); - viewer.render(); - map.triggerRepaint(); + if (!currentPointCloud) + return; + let mat = currentPointCloud.material; + mat.activeAttributeName = currentColorMode === "rgb" ? "rgba" : "elevation"; + viewer.renderer.resetState(); + viewer.render(); + map.triggerRepaint(); } + +// Farbmodus je nach aktivem Renderer anwenden function applyColorMode() { - if (currentRenderer === 'potree') { applyColorModePotree(); } - else { applyDeckGLSettings(); } + if (currentRenderer === "potree") { + applyColorModePotree(); + } else { + applyDeckGLSettings(); + } } + +//-------------------- INFO-JSON laden (Texte/Bilder zu den Punktwolken) ----------------- function loadInfoJSON() { - fetch("info.json") - .then(r => r.json()) - .then(data => { pointCloudInfo = data; }) - .catch(e => console.error("Fehler beim Laden der JSON:", e)); + fetch("info.json").then((r) => r.json()).then((data) => { + pointCloudInfo = data; + }) + .catch((e) => console.error("Fehler beim Laden der JSON:", e)); } +//------------------------ Punktwolke im Potree Format laden ----------------------------- +function loadPointCloudPotree(path) { + viewer.scene.view.yaw = 0; + viewer.scene.view.pitch = 0; -//------------------------lädt Punktwolke im Potree Format----------------------------- -function loadPointCloud(path) { - viewer.scene.view.yaw = 0; - viewer.scene.view.pitch = 0; - - if (currentPointCloud) { - const index = viewer.scene.pointclouds.indexOf(currentPointCloud); - if (index !== -1) viewer.scene.pointclouds.splice(index, 1); - if (currentPointCloud.parent) currentPointCloud.parent.remove(currentPointCloud); - currentPointCloud = null; - viewer.render(); + // vorhandene Punktwolke aus der Szene entfernen + if (currentPointCloud) { + const index = viewer.scene.pointclouds.indexOf(currentPointCloud); + if (index !== -1) + viewer.scene.pointclouds.splice(index, 1); + if (currentPointCloud.parent){ + currentPointCloud.parent.remove(currentPointCloud); } + currentPointCloud = null; + viewer.render(); + } - if (!path || !isVisible) return; + if (!path || !isVisible) + return; - Potree.loadPointCloud(path, "punktwolke", function(e) { - currentPointCloud = e.pointcloud; - viewer.scene.addPointCloud(currentPointCloud); + Potree.loadPointCloud(path, "punktwolke", function (e) { + currentPointCloud = e.pointcloud; + viewer.scene.addPointCloud(currentPointCloud); - let material = currentPointCloud.material; - material.size = 1; - material.pointSizeType = Potree.PointSizeType.ADAPTIVE; - - // 1. Three.js zwingen, die Wolke sofort in der 3D-Welt zu platzieren - currentPointCloud.updateMatrixWorld(true); + let material = currentPointCloud.material; + material.size = 1; + material.pointSizeType = Potree.PointSizeType.ADAPTIVE; - // 2. POTREE WECKRUF - viewer.update(viewer.clock.getDelta(), Number.MAX_VALUE); - - // --- HIER IST DIE KORREKTUR --- - // Erzwinge den aktuell ausgewählten Farbmodus (z.B. elevation) - // auf das frisch generierte Material der neuen Punktwolke! - applyColorMode(); + // 1. Three.js zwingen, die Wolke sofort in der 3D-Welt zu platzieren + currentPointCloud.updateMatrixWorld(true); - // 3. Jetzt die Kamera berechnen und setzen - syncCamera(); + // 2. POTREE WECKRUF + viewer.update(viewer.clock.getDelta(), Number.MAX_VALUE); - // 4. Ein zweites Mal synchronisieren - requestAnimationFrame(() => { - syncCamera(); - }); + // --- HIER IST DIE KORREKTUR --- + // Erzwinge den aktuell ausgewählten Farbmodus (z.B. elevation) + // auf das frisch generierte Material der neuen Punktwolke! + applyColorMode(); + + // 3. Jetzt die Kamera berechnen und setzen + syncCamera(); + + // 4. Ein zweites Mal synchronisieren + requestAnimationFrame(() => { + syncCamera(); }); + }); } // Variable, um Endlosschleifen zu verhindern let isSyncing = false; -/* - Kamera von Potree und MapLibre syncronisieren -*/ +//------------------ Kamera von Potree und MapLibre syncronisieren ------------------------------------ function syncCamera() { + if (!currentPointCloud || isSyncing || currentRenderer !== "potree") + return; + isSyncing = true; + const transform = map.transform; - if (!currentPointCloud || isSyncing || currentRenderer!== 'potree') return; - isSyncing = true; + // 1. Dimensionen auf den Pixel genau angleichen + if (viewer.renderer) { + viewer.renderer.setSize(map.getCanvas().clientWidth, map.getCanvas().clientHeight,); + } - const transform = map.transform; - - // 1. Dimensionen auf den Pixel genau angleichen - if (viewer.renderer) { - viewer.renderer.setSize(map.getCanvas().clientWidth, map.getCanvas().clientHeight); - } + // 2. Mathematische Parameter aus dem MapLibre-Kern extrahieren + const pitch = (transform.pitch * Math.PI) / 180; + const bearing = (transform.bearing * Math.PI) / 180; + const mapCenter = map.getCenter(); + const [utmX, utmY] = proj4("EPSG:4326", "EPSG:25832", [mapCenter.lng, mapCenter.lat,]); - // 2. Mathematische Parameter aus dem MapLibre-Kern extrahieren - const pitch = transform.pitch * Math.PI / 180; - const bearing = transform.bearing * Math.PI / 180; + // MapLibres exakte mathematische Kameradistanz nutzen + const distanceInMeters = transform.cameraToCenterDistance / transform.pixelsPerMeter; + const target = new THREE.Vector3(utmX, utmY, 0); + const offset = new THREE.Vector3(0, -distanceInMeters * Math.sin(pitch), distanceInMeters * Math.cos(pitch),); + offset.applyAxisAngle(new THREE.Vector3(0, 0, 1), -bearing); + const cameraPosition = target.clone().add(offset); - const mapCenter = map.getCenter(); - const [utmX, utmY] = proj4("EPSG:4326", "EPSG:25832", [mapCenter.lng, mapCenter.lat]); + // 3. Potree-Kamera absolut starr setzen + viewer.scene.view.position.copy(cameraPosition); + viewer.scene.view.lookAt(target); + viewer.setFOV(transform.fov); - // WICHTIG: Nutze MapLibres exakte mathematische Kameradistanz - const distanceInMeters = transform.cameraToCenterDistance / transform.pixelsPerMeter; + // 4. Potree rendern + viewer.renderer.resetState(); + viewer.render(); - const target = new THREE.Vector3(utmX, utmY, 0); - const offset = new THREE.Vector3( - 0, - -distanceInMeters * Math.sin(pitch), - distanceInMeters * Math.cos(pitch) - ); - - offset.applyAxisAngle(new THREE.Vector3(0, 0, 1), -bearing); - const cameraPosition = target.clone().add(offset); - - // 3. Potree-Kamera absolut starr setzen - viewer.scene.view.position.copy(cameraPosition); - viewer.scene.view.lookAt(target); - - viewer.setFOV(transform.fov); - - // 4. Potree rendern - viewer.renderer.resetState(); - viewer.render(); - - isSyncing = false; + isSyncing = false; } -const originalRequestAnimationFrame = window.requestAnimationFrame; -const hookRenderLoop = () => { - // Erzwinge die Kamerasynchronisation bei jedem Karten-Update - syncCamera(); -}; -// Verwendet MapLibres internes Repaint-System, um absolut latenzfrei zu synchronisieren -map.on('movestart', () => { - map.getCanvasContainer().style.cursor = 'grabbing'; +// Kartenbewegungs-Events: Kamera-Synchronisation +map.on("movestart", () => { + map.getCanvasContainer().style.cursor = "grabbing"; }); -// bei jeder Kartenbwegeung die Kameras synchronisieren -map.on('zoom', syncCamera); -map.on('move', syncCamera); -map.on('rotate', syncCamera); -map.on('pitch', syncCamera); -map.on('draw', syncCamera); +// bei jeder Kartenbewegung die Kameras synchronisieren +map.on("zoom", syncCamera); +map.on("move", syncCamera); +map.on("rotate", syncCamera); +map.on("pitch", syncCamera); +map.on("draw", syncCamera); + +// Potree-Canvas soll keine Mausereignisse abfangen, nur die MapLibre-Canvas elRenderArea.style.pointerEvents = "none"; map.getCanvas().style.pointerEvents = "auto"; -/* - Hintergrundkarte ändern, Optionen: hell, dunkel, 3D-Gebäudemodelle, Satellit, Terrain, Satellit+Terrain -*/ +//---------------------- Hintergrundkarte ändern --------------------------------------------------- function changeBaseMap(newMap) { - var basemapStyle; - switch(newMap) { - case "openfree_dark": - basemapStyle = "https://tiles.openfreemap.org/styles/dark"; - break; - case "openfree_bright": - basemapStyle = "https://tiles.openfreemap.org/styles/bright"; - break; - case "openfree_liberty": - basemapStyle = "https://tiles.openfreemap.org/styles/liberty"; - break; - case "satellite": - basemapStyle = { - version: 8, - sources: { - "raster-tiles": { - type: "raster", - tiles: ["https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH"], - tileSize: 256, - attribution: "© MapTiler" - } - }, - layers: [{ - id: "satellite-layer", - type: "raster", - source: "raster-tiles" - }] - }; - break; - case "terrain": - basemapStyle = { - version: 8, - sources: { - topo: { - type: 'raster', - url: 'https://api.maptiler.com/maps/topo-v4/tiles.json?key=6mG881AthmTTWyLvFyjH', - tileSize: 256 - }, - terrainSource: { - type: 'raster-dem', - url: 'https://tiles.mapterhorn.com/tilejson.json' - }, - hillshadeSource: { - type: 'raster-dem', - url: 'https://tiles.mapterhorn.com/tilejson.json' - } - }, - layers: [{ - id: 'topo', - type: 'raster', - source: 'topo' - }, { - id: 'hills', - type: 'hillshade', - source: 'hillshadeSource', - layout: { - visibility: 'visible' - }, - paint: { - 'hillshade-shadow-color': '#473B24' - } - }], - terrain: { - source: 'terrainSource', - exaggeration: 1 - }, - sky: {} - }; - break; - case "satellite_terrain": - basemapStyle = { - version: 8, - sources: { - "raster-tiles": { - type: "raster", - tiles: ["https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH"], - tileSize: 256, - attribution: "© MapTiler" - }, - terrainSource: { - type: 'raster-dem', - url: 'https://tiles.mapterhorn.com/tilejson.json' - }, - hillshadeSource: { - type: 'raster-dem', - url: 'https://tiles.mapterhorn.com/tilejson.json' - } - }, - layers: [{ - id: 'raster-tiles', - type: 'raster', - source: 'raster-tiles' - }, { - id: 'hills', - type: 'hillshade', - source: 'hillshadeSource', - layout: { - visibility: 'visible' - }, - paint: { - 'hillshade-shadow-color': '#473B24' - } - }], - terrain: { - source: 'terrainSource', - exaggeration: 1 - }, - sky: {} - }; - break; - } - map.setStyle(basemapStyle); + var basemapStyle; + switch (newMap) { + // dunkle Karte + case "openfree_dark": + basemapStyle = "https://tiles.openfreemap.org/styles/dark"; + break; + + // helle Karte + case "openfree_bright": + basemapStyle = "https://tiles.openfreemap.org/styles/bright"; + break; + + // mit LOD1-GEbäudemodellen + case "openfree_liberty": + basemapStyle = "https://tiles.openfreemap.org/styles/liberty"; + break; + + // Satellitenbild + case "satellite": + basemapStyle = { + version: 8, + sources: { + "raster-tiles": { + type: "raster", + tiles: [ + "https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH", + ], + tileSize: 256, + attribution: "© MapTiler", + }, + }, + layers: [ + { + id: "satellite-layer", + type: "raster", + source: "raster-tiles", + }, + ], + }; + break; + + // Gelände + case "terrain": + basemapStyle = { + version: 8, + sources: { + topo: { + type: "raster", + url: "https://api.maptiler.com/maps/topo-v4/tiles.json?key=6mG881AthmTTWyLvFyjH", + tileSize: 256, + }, + terrainSource: { + type: "raster-dem", + url: "https://tiles.mapterhorn.com/tilejson.json", + }, + hillshadeSource: { + type: "raster-dem", + url: "https://tiles.mapterhorn.com/tilejson.json", + }, + }, + layers: [ + { + id: "topo", + type: "raster", + source: "topo", + }, + { + id: "hills", + type: "hillshade", + source: "hillshadeSource", + layout: { + visibility: "visible", + }, + paint: { + "hillshade-shadow-color": "#473B24", + }, + }, + ], + terrain: { + source: "terrainSource", + exaggeration: 1, + }, + sky: {}, + }; + break; + + // Gelände mit Satellitenbild + case "satellite_terrain": + basemapStyle = { + version: 8, + sources: { + "raster-tiles": { + type: "raster", + tiles: [ + "https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH", + ], + tileSize: 256, + attribution: "© MapTiler", + }, + terrainSource: { + type: "raster-dem", + url: "https://tiles.mapterhorn.com/tilejson.json", + }, + hillshadeSource: { + type: "raster-dem", + url: "https://tiles.mapterhorn.com/tilejson.json", + }, + }, + layers: [ + { + id: "raster-tiles", + type: "raster", + source: "raster-tiles", + }, + { + id: "hills", + type: "hillshade", + source: "hillshadeSource", + layout: { + visibility: "visible", + }, + paint: { + "hillshade-shadow-color": "#473B24", + }, + }, + ], + terrain: { + source: "terrainSource", + exaggeration: 1, + }, + sky: {}, + }; + break; + } + map.setStyle(basemapStyle); } -//------------------------------------------------------Action-Listener und Event-Handler--------------------------------------------------------------------------------- -// Punktwolke aus/einblenden -document.querySelector('#disable').addEventListener('click', function() { - isVisible = !isVisible; - //if (currentPointCloud) - // currentPointCloud.visible = isVisible; - applyVisibility(); - - if(isVisible) { - this.textContent = "Punktwolke ausblenden"; - this.classList.add("active-state"); - this.classList.remove("inactive-state"); - } else { - this.textContent = "Punktwolke anzeigen"; - this.classList.add("inactive-state"); - this.classList.remove("active-state"); - } +//----------------------------------Action-Listener und Event-Handler-------------------------------------- + +// Punktwolke über Button aus/einblenden +document.querySelector("#disable").addEventListener("click", function () { + isVisible = !isVisible; + applyVisibility(); + + if (isVisible) { + this.textContent = "Punktwolke ausblenden"; + this.classList.add("active-state"); + this.classList.remove("inactive-state"); + } else { + this.textContent = "Punktwolke anzeigen"; + this.classList.add("inactive-state"); + this.classList.remove("active-state"); + } }); // Koordinaten an der Mausposition darstellen (auf 5 Nachkommastellen begrenzt) -const coordinatesDiv = document.getElementById('coordinates'); -map.on('mousemove', (e) => { - coordinatesDiv.innerHTML = `Lon: ${e.lngLat.lng.toFixed(5)}° | Lat: ${e.lngLat.lat.toFixed(5)}°`; +const coordinatesDiv = document.getElementById("coordinates"); +map.on("mousemove", (e) => { + coordinatesDiv.innerHTML = `Lon: ${e.lngLat.lng.toFixed(5)}° | Lat: ${e.lngLat.lat.toFixed(5)}°`; }); // Hintergrundkarte ändern, wenn anderes Element im DropDown gewählt wird -document.querySelector('select[name="basemap"]').addEventListener('change', (e) => changeBaseMap(e.target.value)); - +document.querySelector('select[name="basemap"]').addEventListener("change", (e) => { + changeBaseMap(e.target.value) +}); // Punktwolke austauschen, wenn anderes Element im DropDown gewählt wird -document.querySelector('select[name="pointcloud"]').addEventListener('change', (e) => { - //loadPointCloud(getPointCloudFilesPOTREE()[e.target.value]); +document.querySelector('select[name="pointcloud"]').addEventListener("change", (e) => { currentPointcloudKey = e.target.value; - loadCurrentPointCloud(); + loadPointCloud(); }); // Kartenausschnitt auf Ursprung zurücksetzen -document.getElementById("location").addEventListener("click", () => map.flyTo({center, zoom: 17})); +document.getElementById("location").addEventListener("click", () => map.flyTo({ center, zoom: 17 })); -document.getElementById("pointSizeSlider").oninput = function() { - //viewer.setMinNodeSize(this.value); - currentPointSize = Number(this.value); -if (currentRenderer === 'potree') { +// Punktgröße mit Slider anpassen +document.getElementById("pointSizeSlider").oninput = function () { + currentPointSize = Number(this.value); + if (currentRenderer === "potree") { viewer.setMinNodeSize(currentPointSize); -} else if (lidarControl) { - try { lidarControl.setPointSize(currentPointSize); } catch(e) {} -} + } else if (lidarControl) { + try { + lidarControl.setPointSize(currentPointSize); + } catch (e) {} + } }; + +//--------------------------- Sidepannel ein/ausklappen ---------------------------------------------------- const closeButton = document.getElementById("closeSideBarButton"); const sidebar = document.getElementById("sidebar"); const openOuter = document.getElementById("openButtonOuter"); if (closeButton) { - closeButton.addEventListener("click", () => { - const rect = closeButton.getBoundingClientRect(); - sidebar.style.display = "none"; - openOuter.innerHTML = ""; - const openButton = document.getElementById("openSideBarButton"); - openButton.style.top = `${rect.top + window.scrollY - 2}px`; - openButton.addEventListener("click", () => { - sidebar.style.display = "flex"; - openOuter.innerHTML = ""; - }); + closeButton.addEventListener("click", () => { + const rect = closeButton.getBoundingClientRect(); + sidebar.style.display = "none"; + openOuter.innerHTML = ""; + const openButton = document.getElementById("openSideBarButton"); + openButton.style.top = `${rect.top + window.scrollY - 2}px`; + openButton.addEventListener("click", () => { + sidebar.style.display = "flex"; + openOuter.innerHTML = ""; }); + }); } -document.querySelectorAll('.qualityButtons').forEach(btn => { - btn.addEventListener('click', () => { - quality = btn.id; - //if (currentPointCloud) loadPointCloud(getPointCloudFiles()[document.getElementById("pointcloud").value]); - if (currentPointcloudKey) loadCurrentPointCloud(); - document.querySelectorAll('.qualityButtons').forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - }); +//------------------------- QUALITÄTS- UND FARB-BUTTONS ---------------------------------------------------- +// Qualitätsstufe wählen +document.querySelectorAll(".qualityButtons").forEach((btn) => { + btn.addEventListener("click", () => { + quality = btn.id; + if (currentPointcloudKey) + loadPointCloud(); + document.querySelectorAll(".qualityButtons").forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + }); }); -document.querySelectorAll('.colorButtons').forEach(btn => { - btn.addEventListener('click', () => { - //if (!currentPointCloud) return; // Abbrechen, falls noch keine Wolke geladen ist - - document.querySelectorAll('.colorButtons').forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - currentColorMode = btn.id; - applyColorMode(); - }); +// Farbmodus (rgb/elevation) wählen +document.querySelectorAll(".colorButtons").forEach((btn) => { + btn.addEventListener("click", () => { + document.querySelectorAll(".colorButtons").forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + currentColorMode = btn.id; + applyColorMode(); + }); }); // Sobald die Karte das allererste Mal stabil steht, Kamera abgleichen -map.once('idle', () => { - syncCamera(); +map.once("idle", () => { + syncCamera(); }); + +//--------------- INFO-PANEL (Texte & Bilder zu den Punktwolken) ------------------------------- let pointCloudInfo = {}; const infoPanel = document.getElementById("info-panel"); const infoPanelTitle = document.getElementById("info-panel-title"); const infoPanelText = document.getElementById("info-panel-text"); const openInfoPanel = document.getElementById("openInfoPanel"); const closeInfoPanel = document.getElementById("closeInfoPanel"); - let currentImageIndex = 0; - +// Info-Panel mit Titel, Text und Bild/Bildern der gewählten Punktwolke befüllen function updateInfoPanel() { - const key = document.querySelector('select[name="pointcloud"]').value; + const key = document.querySelector('select[name="pointcloud"]').value; + const info = pointCloudInfo[key]; + currentImageIndex = 0; - const info = pointCloudInfo[key]; - - currentImageIndex = 0; - - if (info) { - infoPanelTitle.textContent = info.title; - infoPanelText.textContent = info.text; - updateImage(info); - } else { - infoPanelTitle.textContent = "Info"; - infoPanelText.textContent = "Bitte wählen Sie eine Punktwolke aus."; - document.getElementById("info-panel-image").style.display = "none"; - document.getElementById("info-image-nav").style.display = "none"; - } + if (info) { + infoPanelTitle.textContent = info.title; + infoPanelText.textContent = info.text; + updateImage(info); + } else { + infoPanelTitle.textContent = "Info"; + infoPanelText.textContent = "Bitte wählen Sie eine Punktwolke aus."; + document.getElementById("info-panel-image").style.display = "none"; + document.getElementById("info-image-nav").style.display = "none"; + } } +// aktuelles Bild + Bildzähler im Info-Panel anzeigen function updateImage(info) { - const img = document.getElementById("info-panel-image"); - const nav = document.getElementById("info-image-nav"); - const counter = document.getElementById("info-image-counter"); + const img = document.getElementById("info-panel-image"); + const nav = document.getElementById("info-image-nav"); + const counter = document.getElementById("info-image-counter"); - img.src = info.images[currentImageIndex]; - img.style.display = "block"; + img.src = info.images[currentImageIndex]; + img.style.display = "block"; - if (info.images.length > 1) { - nav.style.display = "flex"; - counter.textContent = `${currentImageIndex + 1} / ${info.images.length}`; - } else { - nav.style.display = "none"; - } + if (info.images.length > 1) { + nav.style.display = "flex"; + counter.textContent = `${currentImageIndex + 1} / ${info.images.length}`; + } else { + nav.style.display = "none"; + } } +// vorheriges Bild im Info-Panel anzeigen document.getElementById("info-img-prev").addEventListener("click", () => { - const key = document.querySelector('select[name="pointcloud"]').value; - const info = pointCloudInfo[key]; - if (!info) return; - currentImageIndex = (currentImageIndex - 1 + info.images.length) % info.images.length; - updateImage(info); + const key = document.querySelector('select[name="pointcloud"]').value; + const info = pointCloudInfo[key]; + if (!info) + return; + currentImageIndex = (currentImageIndex - 1 + info.images.length) % info.images.length; + updateImage(info); }); +// nächstes Bild im Info-Panel anzeigen document.getElementById("info-img-next").addEventListener("click", () => { - const key = document.querySelector('select[name="pointcloud"]').value; - const info = pointCloudInfo[key]; - if (!info) return; - currentImageIndex = (currentImageIndex + 1) % info.images.length; - updateImage(info); + const key = document.querySelector('select[name="pointcloud"]').value; + const info = pointCloudInfo[key]; + if (!info) + return; + currentImageIndex = (currentImageIndex + 1) % info.images.length; + updateImage(info); }); +// Info-Panel öffnen openInfoPanel.addEventListener("click", () => { - infoPanel.classList.add("open"); - //openInfoPanel.style.display = "none"; + infoPanel.classList.add("open"); }); +// Info-Panel schließen closeInfoPanel.addEventListener("click", () => { - infoPanel.classList.remove("open"); - openInfoPanel.style.display = "flex"; + infoPanel.classList.remove("open"); + openInfoPanel.style.display = "flex"; }); -// Panel aktualisieren wenn Punktwolke gewechselt wird +// Info-Panel aktualisieren, wenn Punktwolke gewechselt wird document.querySelector('select[name="pointcloud"]').addEventListener("change", () => { updateInfoPanel(); -}); + }); -document.getElementById('rendererToggle').addEventListener('click', function() { - const next = currentRenderer === 'deckgl' ? 'potree' : 'deckgl'; +//Renderer Toggle Button +document.getElementById("rendererToggle").addEventListener("click", function () { + const next = currentRenderer === "deckgl" ? "potree" : "deckgl"; switchRenderer(next); -}); - + }); \ No newline at end of file diff --git a/style.css b/style.css index c5965fb..f798e0e 100644 --- a/style.css +++ b/style.css @@ -1,59 +1,60 @@ -html, body { +html, +body { height: 100%; margin: 0; } -body{ - margin:0; - overflow:hidden; - font-family: 'Inter', sans-serif; +body { + margin: 0; + overflow: hidden; + font-family: "Inter", sans-serif; background: var(--bg); color: var(--text); } -#main{ - width:100vw; - height:100vh; - position:relative; +#main { + width: 100vw; + height: 100vh; + position: relative; } -#header{ - position:absolute; - top:20px; - left:50%; - transform:translateX(-50%); - z-index:30; - padding:14px 28px; +#header { + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 30; + padding: 14px 28px; border-radius: 20px; - background: + background: linear-gradient( 135deg, rgba(200, 200, 200, 0.221) 10%, rgba(79, 79, 79, 0.756) 60%, rgba(47, 47, 47, 0.864) 100% ), - rgba(24,24,27,0.78); + rgba(24, 24, 27, 0.78); backdrop-filter: blur(16px); - color:white; - font-size:24px; - font-weight:600; + color: white; + font-size: 24px; + font-weight: 600; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.536); } -.logos{ - margin-top:20px; +.logos { + margin-top: 20px; margin-left: 12px; - opacity:0.8; + opacity: 0.8; } -.logos img{ - width:80px; +.logos img { + width: 80px; margin-right: 10px; } -.logos img:hover{ - transform:translateY(-1px); - box-shadow: 0 6px 18px rgba(0,0,0,0.12); +.logos img:hover { + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12); } #location-button { @@ -67,9 +68,11 @@ body{ align-items: center; justify-content: center; background-color: #f8f9fa; - border: none !important; - border-radius: 4px; - box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05); + border: none !important; + border-radius: 4px; + box-shadow: + 0 0 0 2px rgba(0, 0, 0, 0.1), + 0 4px 6px rgba(0, 0, 0, 0.05); cursor: pointer; box-sizing: border-box; } @@ -78,28 +81,28 @@ body{ background-color: #f4f4f4; } -#location{ - width:20px; - height:20px; - object-fit:contain; +#location { + width: 20px; + height: 20px; + object-fit: contain; margin-left: 1px; margin-top: 1px; cursor: pointer; } -#location:hover{ - transform:scale(1.05); - background:rgb(233, 232, 232); +#location:hover { + transform: scale(1.05); + background: rgb(233, 232, 232); } -#location-button:hover{ - background:rgb(233, 232, 232); +#location-button:hover { + background: rgb(233, 232, 232); } #sidebar { position: absolute; top: 48%; - transform:translateY(-50%); + transform: translateY(-50%); left: 20px; width: 210px; padding: 22px; @@ -107,13 +110,13 @@ body{ padding-top: 10px; background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(18px); - border: 1px solid rgba(255,255,255,0.3); + border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 24px; - box-shadow: 0 10px 30px rgba(0,0,0,0.15); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); z-index: 20; - display: flex; + display: flex; flex-direction: column; - gap:8px; + gap: 8px; } #main { @@ -126,9 +129,9 @@ body{ height: 100%; } -#potree_render_area{ - position:absolute; - top:0; +#potree_render_area { + position: absolute; + top: 0; left: 0; width: 100%; height: 100%; @@ -140,50 +143,49 @@ body{ background: transparent !important; } -select{ - appearance:none; - -webkit-appearance:none; - -moz-appearance:none; - width:100%; - padding:10px 16px; - border:none; - border-radius:14px; - background:rgba(255,255,255,0.92); - font-size:14px; - font-weight:500; - color:#0f172a; +select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 100%; + padding: 10px 16px; + border: none; + border-radius: 14px; + background: rgba(255, 255, 255, 0.92); + font-size: 14px; + font-weight: 500; + color: #0f172a; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); - cursor:pointer; - transition:0.2s; - + cursor: pointer; + transition: 0.2s; } -.selectWrapper{ - position:relative; - width:100%; +.selectWrapper { + position: relative; + width: 100%; } -.selectArrow{ - position:absolute; - right:14px; - top:48%; - transform:translateY(-50%); - pointer-events:none; - font-size:14px; - color:#000000; +.selectArrow { + position: absolute; + right: 14px; + top: 48%; + transform: translateY(-50%); + pointer-events: none; + font-size: 14px; + color: #000000; } -#disable{ - appearance:none; - -webkit-appearance:none; - -moz-appearance:none; - width:100%; - padding:10px 16px; - border:none; - border-radius:14px; - font-size:14px; - font-weight:500; - background: +#disable { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 100%; + padding: 10px 16px; + border: none; + border-radius: 14px; + font-size: 14px; + font-weight: 500; + background: linear-gradient( 135deg, rgba(18, 77, 216, 0.92) 10%, @@ -191,16 +193,16 @@ select{ rgba(6, 45, 135, 0.92) 100% ), rgba(6, 45, 135, 0.92); - color: white; + color: white; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); - cursor:pointer; - transition:0.2s; + cursor: pointer; + transition: 0.2s; margin-top: 8px; margin-bottom: 10px; } #disable.active-state { - background: + background: linear-gradient( 135deg, rgba(18, 77, 216, 0.92) 10%, @@ -208,37 +210,39 @@ select{ rgba(6, 45, 135, 0.92) 100% ), rgba(6, 45, 135, 0.92); - color: white; + color: white; } #disable.inactive-state { - background: rgba(255,255,255,0.92); - color: #0f172a; + background: rgba(255, 255, 255, 0.92); + color: #0f172a; } -select:hover{ - transform:translateY(-1px); - box-shadow: 0 6px 18px rgba(0,0,0,0.12); +select:hover { + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12); } -select:focus{ - outline:none; - box-shadow:0 0 0 4px rgba(37,99,235,0.15), 0 8px 24px rgba(0,0,0,0.12); +select:focus { + outline: none; + box-shadow: + 0 0 0 4px rgba(37, 99, 235, 0.15), + 0 8px 24px rgba(0, 0, 0, 0.12); } -#disable:hover{ - transform:translateY(-1px); - box-shadow: 0 6px 18px rgba(0,0,0,0.12); +#disable:hover { + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12); } -*{ +* { transition: background 0.2s, transform 0.01s, opacity 0.2s; } -footer{ +footer { z-index: 30; position: absolute; bottom: 8px; @@ -246,13 +250,13 @@ footer{ font-size: small; } -.sideBarText{ -font-size:80%; -margin:0; -margin-bottom: -2px; +.sideBarText { + font-size: 80%; + margin: 0; + margin-bottom: -2px; } -#pointSizeSlider{ +#pointSizeSlider { width: 100%; margin: 0; padding: 0; @@ -260,7 +264,7 @@ margin-bottom: -2px; margin-bottom: 8px; } -input[type="range"]{ +input[type="range"] { -webkit-appearance: none; appearance: none; width: 100%; @@ -272,12 +276,12 @@ input[type="range"]{ outline: none; } -input[type="range"]::-webkit-slider-thumb{ +input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; - width:16px; - height:16px; - border-radius:50%; - background: + width: 16px; + height: 16px; + border-radius: 50%; + background: linear-gradient( 135deg, rgba(18, 77, 216, 0.92) 10%, @@ -285,106 +289,108 @@ input[type="range"]::-webkit-slider-thumb{ rgba(6, 45, 135, 0.92) 100% ), rgba(6, 45, 135, 0.92); - cursor:pointer; + cursor: pointer; } -.slider-labels{ - display:flex; - justify-content:space-between; - font-size:0.5rem; - color:#868686; - margin-bottom:0px; - margin-top: -5px; +.slider-labels { + display: flex; + justify-content: space-between; + font-size: 0.5rem; + color: #868686; + margin-bottom: 0px; + margin-top: -5px; } -#title{ +#title { z-index: 30; position: absolute; top: 75px; left: 50%; - transform:translateX(-50%); + transform: translateX(-50%); font-size: small; color: #ffffff; text-shadow: 1px 1.5px #00000059; } -#coordinates{ +#coordinates { z-index: 30; position: absolute; bottom: 20px; left: 50%; - transform:translateX(-50%); + transform: translateX(-50%); font-size: smaller; color: #000000cf; border: none; border-radius: 14px; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); - padding:8px; + padding: 8px; border-radius: 15px; background-color: rgba(255, 255, 255, 0.751); backdrop-filter: blur(10px); } -.sideBarButtons{ - font-family:monospace; - border:none; - border-radius:10px; - background: +.sideBarButtons { + font-family: monospace; + border: none; + border-radius: 10px; + background: linear-gradient( 135deg, rgba(200, 200, 200, 0.221) 10%, rgba(79, 79, 79, 0.756) 60%, rgba(47, 47, 47, 0.864) 100% ), - rgba(24,24,27,0.78); - font-size:15px; - color:#ffffff; - box-shadow: 0 2px 10px rgba(0,0,0,0.08); - cursor:pointer; - transition:0.2s; + rgba(24, 24, 27, 0.78); + font-size: 15px; + color: #ffffff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + cursor: pointer; + transition: 0.2s; z-index: 30; } -#closeSideBarButton{ - align-self: flex-end; +#closeSideBarButton { + align-self: flex-end; right: 0%; - padding:5px 15px; -} - -#openSideBarButton{ -position: absolute; - left: 5px; - padding:5px 15px; + padding: 5px 15px; } -.buttonRow{ +#openSideBarButton { + position: absolute; + left: 5px; + padding: 5px 15px; +} + +.buttonRow { display: flex; width: 100%; border: none; border-radius: 10px; - overflow:hidden; + overflow: hidden; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); margin-bottom: 10px; - } - -.qualityButtons, .colorButtons{ - flex: 1; - margin: 0; - appearance:none; - -webkit-appearance:none; - -moz-appearance:none; - border:none; - background:rgba(255,255,255,0.92); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); - font-size:12px; - color:#0f172a; - padding:5px; - cursor:pointer; - transition:0.2s; } -.qualityButtons.active, .colorButtons.active{ - background: +.qualityButtons, +.colorButtons { + flex: 1; + margin: 0; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + border: none; + background: rgba(255, 255, 255, 0.92); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); + font-size: 12px; + color: #0f172a; + padding: 5px; + cursor: pointer; + transition: 0.2s; +} + +.qualityButtons.active, +.colorButtons.active { + background: linear-gradient( 135deg, rgba(18, 77, 216, 0.92) 10%, @@ -392,182 +398,185 @@ position: absolute; rgba(6, 45, 135, 0.92) 100% ), rgba(6, 45, 135, 0.92); - color: #ffffff; + color: #ffffff; } #potree_render_area { pointer-events: none; - } +} - #info-button-outer { - position: absolute; - top: 50%; - right: 20px; - transform: translateY(-50%); - z-index: 30; +#info-button-outer { + position: absolute; + top: 50%; + right: 20px; + transform: translateY(-50%); + z-index: 30; } #rendererToggle { - width: 100%; - padding: 10px 16px; - border: none; - border-radius: 14px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 5px 10px rgba(0,0,0,0.278); - transition: 0.2s; + width: 100%; + padding: 10px 16px; + border: none; + border-radius: 14px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); + transition: 0.2s; } #rendererToggle[data-active="deckgl"] { - background: linear-gradient( - 135deg, - rgba(18,77,216,0.92) 10%, - rgba(13,61,175,0.92) 60%, - rgba(6,45,135,0.92) 100%); - color: #ffffff; + background: linear-gradient( + 135deg, + rgba(18, 77, 216, 0.92) 10%, + rgba(13, 61, 175, 0.92) 60%, + rgba(6, 45, 135, 0.92) 100% + ); + color: #ffffff; } #rendererToggle[data-active="potree"] { - background: linear-gradient( - 135deg, - rgba(22,163,74,0.92) 10%, - rgba(15,118,54,0.92) 60%, - rgba(6,78,32,0.92) 100%); - color: #ffffff; + background: linear-gradient( + 135deg, + rgba(22, 163, 74, 0.92) 10%, + rgba(15, 118, 54, 0.92) 60%, + rgba(6, 78, 32, 0.92) 100% + ); + color: #ffffff; } #rendererToggle:hover { - transform: translateY(-1px); - box-shadow: 0 6px 18px rgba(0,0,0,0.12); + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12); } #rendererIcon { - font-size: 16px; + font-size: 16px; } #openInfoPanel { - margin-top: 12px; - border: none; - border-radius: 14px; - width: 100%; - padding: 10px 16px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - background: rgba(255,255,255,0.9); - box-shadow: 0 5px 10px rgba(0,0,0,0.278); - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - color: #1e293b; + margin-top: 12px; + border: none; + border-radius: 14px; + width: 100%; + padding: 10px 16px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + background: rgba(255, 255, 255, 0.9); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + color: #1e293b; } #openInfoPanel:hover { - background: rgb(233,232,232); - transform: translateY(-1px); + background: rgb(233, 232, 232); + transform: translateY(-1px); } #openInfoPanel img { - width: 16px; - height: 16px; - object-fit: contain; + width: 16px; + height: 16px; + object-fit: contain; } #info-panel { - display: none; - position: absolute; - top: 48%; - right: 20px; - transform: translateY(-50%); - width: 340px; - padding: 18px 22px; - background: rgba(255,255,255,0.9); - backdrop-filter: blur(18px); - border: 1px solid rgba(255,255,255,0.3); - border-radius: 24px; - box-shadow: 0 10px 30px rgba(0,0,0,0.15); - z-index: 20; - display: none; - flex-direction: column; - gap: 0; + display: none; + position: absolute; + top: 48%; + right: 20px; + transform: translateY(-50%); + width: 340px; + padding: 18px 22px; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(18px); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 24px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); + z-index: 20; + display: none; + flex-direction: column; + gap: 0; } #info-panel.open { - display: flex; + display: flex; } #closeInfoPanel { - align-self: flex-start; - padding: 5px 15px; - margin-bottom: 14px; + align-self: flex-start; + padding: 5px 15px; + margin-bottom: 14px; } #info-panel-title { - margin: 0 0 14px 0; - font-size: 14px; - font-weight: 600; - color: #0f172a; + margin: 0 0 14px 0; + font-size: 14px; + font-weight: 600; + color: #0f172a; } #info-panel-image { - width: 100%; - border-radius: 12px; - object-fit: cover; - max-height: 220px; - margin-bottom: 14px; + width: 100%; + border-radius: 12px; + object-fit: cover; + max-height: 220px; + margin-bottom: 14px; } #info-image-nav { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - margin-bottom: 14px; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin-bottom: 14px; } #info-panel-text { - margin: 0; - font-size: 13px; - line-height: 1.6; - color: #1e293b; + margin: 0; + font-size: 13px; + line-height: 1.6; + color: #1e293b; } -#info-img-prev, #info-img-next { - border: none; - background: rgba(255,255,255,0.9); - border-radius: 8px; - padding: 4px 10px; - cursor: pointer; - font-size: 16px; - box-shadow: 0 2px 6px rgba(0,0,0,0.15); +#info-img-prev, +#info-img-next { + border: none; + background: rgba(255, 255, 255, 0.9); + border-radius: 8px; + padding: 4px 10px; + cursor: pointer; + font-size: 16px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); } -#info-img-prev:hover, #info-img-next:hover { - background: rgb(233,232,232); +#info-img-prev:hover, +#info-img-next:hover { + background: rgb(233, 232, 232); } #info-image-counter { - font-size: 12px; - color: #555; + font-size: 12px; + color: #555; } .lidar-control-icon { - display: none !important; + display: none !important; } .maplibregl-ctrl:has(.lidar-control-icon), .maplibregl-ctrl-group:has(.lidar-control-icon), button:has(.lidar-control-icon) { - display: none !important; - visibility: hidden !important; - width: 0px !important; - height: 0px !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; + display: none !important; + visibility: hidden !important; + width: 0px !important; + height: 0px !important; + margin: 0 !important; + padding: 0 !important; + border: none !important; } -