Compare commits

...

2 Commits

Author SHA1 Message Date
ar2350 da29cbeb7e Kommentiert 2026-07-02 22:02:57 +02:00
ar2350 267952ef71 Finaler build 2026-06-16 21:22:41 +02:00
5 changed files with 866 additions and 819 deletions
+3 -3
View File
@@ -2,16 +2,16 @@
"first": { "first": {
"title": "Fläche im unteren Bereich", "title": "Fläche im unteren Bereich",
"text": "Dieser Bereich des Steinbruchs zeigt eine natürliche Oberfläche aus Obernkirchener Sandstein. Zwischen einzelnen Sandsteinblöcken haben sich Sträucher und andere Pflanzen angesiedelt. Der Sandstein entstand vor etwa 140 Millionen Jahren in einer küstennahen Landschaft der Unterkreide.", "text": "Dieser Bereich des Steinbruchs zeigt eine natürliche Oberfläche aus Obernkirchener Sandstein. Zwischen einzelnen Sandsteinblöcken haben sich Sträucher und andere Pflanzen angesiedelt. Der Sandstein entstand vor etwa 140 Millionen Jahren in einer küstennahen Landschaft der Unterkreide.",
"images": ["stand1_1.jpeg", "stand1_2.jpeg"] "images": ["/stand1_1.jpeg", "/stand1_2.jpeg"]
}, },
"second": { "second": {
"title": "Abbauwand", "title": "Abbauwand",
"text": "An dieser Felswand wird der Obernkirchener Sandstein abgebaut. Das Gestein ist für seine hohe Festigkeit und Witterungsbeständigkeit bekannt und wird seit Jahrhunderten als Bau- und Werkstein genutzt. Es findet sich unter anderem an zahlreichen historischen Gebäuden in Deutschland.", "text": "An dieser Felswand wird der Obernkirchener Sandstein abgebaut. Das Gestein ist für seine hohe Festigkeit und Witterungsbeständigkeit bekannt und wird seit Jahrhunderten als Bau- und Werkstein genutzt. Es findet sich unter anderem an zahlreichen historischen Gebäuden in Deutschland.",
"images": ["stand2_1.jpeg"] "images": ["/stand2_1.jpeg"]
}, },
"third": { "third": {
"title": "Dinosaurier-Spuren", "title": "Dinosaurier-Spuren",
"text": "Auf dieser öffentlich zugänglichen Fläche sind fossile Fußspuren von Dinosauriern erhalten geblieben. Die Spuren entstanden vor rund 140 Millionen Jahren in feuchtem Sand und wurden später durch weitere Sedimentschichten konserviert. Sie gehören zu den bekanntesten Fossilienfunden im Obernkirchener Sandstein.", "text": "Auf dieser öffentlich zugänglichen Fläche sind fossile Fußspuren von Dinosauriern erhalten geblieben. Die Spuren entstanden vor rund 140 Millionen Jahren in feuchtem Sand und wurden später durch weitere Sedimentschichten konserviert. Sie gehören zu den bekanntesten Fossilienfunden im Obernkirchener Sandstein.",
"images": ["stand3_1.jpeg", "stand3_2.jpeg"] "images": ["/stand3_1.jpeg", "/stand3_2.jpeg"]
} }
} }
+15 -27
View File
@@ -3,22 +3,12 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>LiDAR App</title> <title>LiDAR App</title>
<link <link href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" rel="stylesheet"/>
href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css"
rel="stylesheet"
/>
<link rel="stylesheet" type="text/css" href="./libs/potree/potree.css" /> <link rel="stylesheet" type="text/css" href="./libs/potree/potree.css" />
<link <link rel="stylesheet" type="text/css" href="./libs/jquery-ui/jquery-ui.min.css"/>
rel="stylesheet"
type="text/css"
href="./libs/jquery-ui/jquery-ui.min.css"
/>
<link rel="stylesheet" type="text/css" href="style.css" /> <link rel="stylesheet" type="text/css" href="style.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"/>
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
</head> </head>
<body> <body>
<header id="header"> <header id="header">
@@ -26,8 +16,7 @@
</header> </header>
<h1 id="title">- Obernkirchener Sandstein -</h1> <h1 id="title">- Obernkirchener Sandstein -</h1>
<div id="coordinates" title="Koordinaten an Mausposition"></div> <div id="coordinates" title="Koordinaten an Mausposition"></div>
<div id="openButtonOuter"> <div id="openButtonOuter"></div>
</div>
<div id="sideBarOuter"> <div id="sideBarOuter">
<aside id="sidebar"> <aside id="sidebar">
<button class="sideBarButtons" id="closeSideBarButton" title="Menü einklappen"><<</button> <button class="sideBarButtons" id="closeSideBarButton" title="Menü einklappen"><<</button>
@@ -69,13 +58,15 @@
<span>Klein</span> <span>Klein</span>
<span>Groß</span> <span>Groß</span>
</div> </div>
<input id="pointSizeSlider" type="range" min="1" max="10" value="2" step="1"> <input id="pointSizeSlider" type="range" min="1" max="10" value="2" step="1"/>
<p class="sideBarText">Renderer</p> <p class="sideBarText">Renderer</p>
<button id="rendererToggle" data-active="deckgl" title="Renderer wechseln"> <button id="rendererToggle" data-active="deckgl" title="Renderer wechseln">
<span id="rendererLabel">Renderer: Deck.gl</span> <span id="rendererLabel">Renderer: Deck.gl</span>
<span id="rendererIcon"></span> <span id="rendererIcon"></span>
</button> </button>
<button id="openInfoPanel" title="Informationen zur Punktwolke"><img src="info.png"/>Informationen</button> <button id="openInfoPanel" title="Informationen zur Punktwolke">
<img src="info.png" />Informationen
</button>
<div class="logos"> <div class="logos">
<a href="http://www.jade-hs.de"> <a href="http://www.jade-hs.de">
<img id="logoJadeHs" src="logoJadeHs.png" /> <img id="logoJadeHs" src="logoJadeHs.png" />
@@ -85,31 +76,28 @@
</a> </a>
</div> </div>
</aside> </aside>
</div> </div>
<main id="main"> <main id="main">
<div id="location-button"> <div id="location-button">
<img id="location" title="Standort zurücksetzen" src="standort.png" /> <img id="location" title="Standort zurücksetzen" src="standort.png" />
</div> </div>
<div id="map"></div> <div id="map"></div>
<div id="potree_render_area"></div> <div id="potree_render_area"></div>
<footer> <footer>&#169 Arne Zitting u. Hauke Kahrs</footer>
&#169 Arne Zitting u. Hauke Kahrs
</footer>
<div id="info-panel"> <div id="info-panel">
<div id="info-panel-inner"> <div id="info-panel-inner">
<button class="sideBarButtons" id="closeInfoPanel" title="Info einklappen">>></button> <button class="sideBarButtons" id="closeInfoPanel" title="Info einklappen">>></button>
<h3 id="info-panel-title">Info</h3> <h3 id="info-panel-title">Info</h3>
<img id="info-panel-image" src="" alt="Standortbild" style="display:none;" /> <img id="info-panel-image" src="" alt="Standortbild" style="display: none"/>
<div id="info-image-nav" style="display:none;"> <div id="info-image-nav" style="display: none">
<button id="info-img-prev">&#8592;</button> <button id="info-img-prev">&#8592;</button>
<span id="info-image-counter"></span> <span id="info-image-counter"></span>
<button id="info-img-next">&#8594;</button> <button id="info-img-next">&#8594;</button>
</div> </div>
<p id="info-panel-text">Bitte wählen Sie eine Punktwolke aus.</p> <p id="info-panel-text">Bitte wählen Sie eine Punktwolke aus.</p>
</div> </div>
</div> </div>
<div id="info-button-outer"> <div id="info-button-outer"></div>
</div>
</main> </main>
<script src="./libs/jquery/jquery-3.1.1.min.js"></script> <script src="./libs/jquery/jquery-3.1.1.min.js"></script>
<script src="./libs/other/BinaryHeap.js"></script> <script src="./libs/other/BinaryHeap.js"></script>
+264 -214
View File
@@ -1,3 +1,4 @@
import maplibregl from "maplibre-gl"; import maplibregl from "maplibre-gl";
import proj4 from "proj4"; import proj4 from "proj4";
import * as THREE from "three"; import * as THREE from "three";
@@ -5,15 +6,27 @@ import { LidarControl } from "maplibre-gl-lidar";
import "maplibre-gl/dist/maplibre-gl.css"; import "maplibre-gl/dist/maplibre-gl.css";
import "maplibre-gl-lidar/style.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"); proj4.defs("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs");
// Koordinaten des Obernkirchener Sandstein (Kartenmittelpunkt)
// Koordinaten des Obernkirchener Sandstein
const center = [9.209116842757239, 52.26520546238239]; 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({ const map = new maplibregl.Map({
container: 'map', container: "map",
style: "https://tiles.openfreemap.org/styles/bright", style: "https://tiles.openfreemap.org/styles/bright",
center: center, center: center,
zoom: 17, zoom: 17,
@@ -21,40 +34,27 @@ const map = new maplibregl.Map({
}); });
// Karten-Bedienelemente // Karten-Bedienelemente
map.addControl(new maplibregl.NavigationControl({ map.addControl(
new maplibregl.NavigationControl({
visualizePitch: true, visualizePitch: true,
showZoom: true, showZoom: true,
showCompass: 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';
// WICHTIG: Layer/Controls immer erst im 'load'-Event der Karte hinzufügen // Karten Load-Event: LidarControl anlegen, JSON-Datei laden
map.on('load', () => { map.on("load", () => {
lidarControl = new LidarControl({ lidarControl = new LidarControl({
title: 'Mein LiDAR-Viewer', title: "LiDAR-Viewer",
collapsed: false, collapsed: false,
pointSize: 2, pointSize: 2,
colorScheme: 'rgb', // 'elevation' oder 'rgb' colorScheme: "rgb", // 'elevation' oder 'rgb'
pointBudget: 3000000, pointBudget: 3000000,
maxRequests: 32, maxRequests: 32,
pickable: false, pickable: false,
}); });
// COPC-Datei laden
const selectBox=document.querySelector('select[name="pointcloud"]');
if (selectBox && selectBox.value){
currentPointcloudKey=selectBox.value;
loadCurrentPointCloud();
}
loadInfoJSON(); loadInfoJSON();
}); });
@@ -62,7 +62,7 @@ map.on('load', () => {
//---------------Potree Eigenschaften-------------------------- //---------------Potree Eigenschaften--------------------------
var pointSize = document.getElementById("pointSizeSlider").value; var pointSize = document.getElementById("pointSizeSlider").value;
const elRenderArea = document.getElementById("potree_render_area"); 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.setEDLEnabled(false);
viewer.setFOV(60); viewer.setFOV(60);
viewer.setMinNodeSize(pointSize); viewer.setMinNodeSize(pointSize);
@@ -70,29 +70,35 @@ viewer.setBackground("none");
viewer.orbitControls.enabled = false; viewer.orbitControls.enabled = false;
viewer.fpControls.enabled = false; viewer.fpControls.enabled = false;
viewer.deviceControls.enabled = false; viewer.deviceControls.enabled = false;
elRenderArea.style.display='none'; elRenderArea.style.display = "none";
viewer.setPointBudget(3000000) viewer.setPointBudget(3000000);
//-------------------------- LIDARCONTROL (DECK.GL) NEU AUFBAUEN ----------------------------------------
// entfernt das bestehende LidarControl und erzeugt es mit den aktuellen Einstellungen neu
function resetLidarControl() { function resetLidarControl() {
if (!lidarControl) return; if (!lidarControl)
return;
try { try {
map.removeControl(lidarControl); map.removeControl(lidarControl);
} catch(e) {} } catch (e) {}
lidarControl = null; lidarControl = null;
lidarControl = new LidarControl({ lidarControl = new LidarControl({
title: 'Mein LiDAR-Viewer', title: "LiDAR-Viewer",
collapsed: false, collapsed: false,
pointSize: currentPointSize > 0 ? currentPointSize : 2, pointSize: currentPointSize > 0 ? currentPointSize : 2,
colorScheme: currentColorMode === 'rgb' ? 'rgb' : 'elevation', colorScheme: currentColorMode === "rgb" ? "rgb" : "elevation",
pointBudget: 3000000, pointBudget: 3000000,
maxRequests: 32, maxRequests: 32,
pickable: false pickable: false,
}); });
map.addControl(lidarControl, 'top-right'); map.addControl(lidarControl, "top-right");
} }
//-------------- PUNKTWOLKEN-DATENQUELLEN (Pfade je Renderer-Format) - aktuell auf Server der Jade-HS --------------------
// Punktwolkendaten im Potree-Format // Punktwolkendaten im Potree-Format
function getPointCloudFilesPOTREE() { function getPointCloudFilesPOTREE() {
return { return {
@@ -112,114 +118,142 @@ function getPointCloudFilesCOPC() {
} }
function loadCurrentPointCloud() { //---------------------------- PUNKTWOLKEN LADEN (je nach aktivem Renderer) -------------------------------------
if (!currentPointcloudKey) return;
if (currentRenderer === 'deckgl') { // wählt automatisch Potree- oder Deck.gl-Renderer und lädt die Punktwolke
function loadPointCloud() {
if (!currentPointcloudKey)
return;
if (currentRenderer === "deckgl") {
loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]); loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]);
} else { } else {
loadPointCloud(getPointCloudFilesPOTREE()[currentPointcloudKey]); loadPointCloudPotree(getPointCloudFilesPOTREE()[currentPointcloudKey]);
} }
} }
//---------------------- Punktwolken laden über den deck.gl-Renderer ----------------------
function loadDeckGL(path) { function loadDeckGL(path) {
if (!lidarControl || !path) return; if (!lidarControl || !path)
return;
resetLidarControl(); resetLidarControl();
lidarControl.loadPointCloudStreaming(path); lidarControl.loadPointCloudStreaming(path);
setTimeout(() => applyDeckGLSettings(), 500); setTimeout(() => applyDeckGLSettings(), 500);
} }
// Punktgröße, Farbschema und Colormap auf LidarControl anwenden
function applyDeckGLSettings() { function applyDeckGLSettings() {
if (!lidarControl) return; if (!lidarControl)
return;
try { try {
lidarControl.setPointSize(currentPointSize > 0 ? currentPointSize : 2); lidarControl.setPointSize(currentPointSize > 0 ? currentPointSize : 2);
lidarControl.setColorScheme(currentColorMode === 'rgb' ? 'rgb' : 'elevation'); lidarControl.setColorScheme(currentColorMode === "rgb" ? "rgb" : "elevation",);
lidarControl.setColormap('jet'); lidarControl.setColormap("jet");
//lidarControl.setPointBudget(3000000); } catch (e) {}
} catch(e) { console.warn("LidarControl API:", e); }
} }
//------------------------- Renderer zwischen deck.gl und potree umstellen -----------------------------------
function switchRenderer(renderer) { function switchRenderer(renderer) {
currentRenderer = renderer; currentRenderer = renderer;
const slider = document.getElementById("pointSizeSlider"); const slider = document.getElementById("pointSizeSlider");
if (renderer === 'deckgl') { if (renderer === "deckgl") {
elRenderArea.style.display = 'none'; elRenderArea.style.display = "none";
if (currentPointCloud) currentPointCloud.visible = false; if (currentPointCloud){
currentPointCloud.visible = false;
}
currentPointSize = 2; currentPointSize = 2;
slider.min = 1; slider.min = 1;
slider.max = 10; slider.max = 10;
slider.value = 2; slider.value = 2;
if (lidarControl && currentPointcloudKey) loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]); if (lidarControl && currentPointcloudKey){
document.getElementById('rendererToggle').dataset.active = 'deckgl'; loadDeckGL(getPointCloudFilesCOPC()[currentPointcloudKey]);
document.getElementById('rendererLabel').textContent = 'Renderer: Deck.gl'; }
document.getElementById("rendererToggle").dataset.active = "deckgl";
document.getElementById("rendererLabel").textContent = "Renderer: Deck.gl";
} else { } else {
resetLidarControl(); resetLidarControl();
elRenderArea.style.display = 'block'; elRenderArea.style.display = "block";
currentPointSize = 0; currentPointSize = 0;
slider.min = 0; slider.min = 0;
slider.max = 1000; slider.max = 1000;
slider.value = 0; slider.value = 0;
if (currentPointcloudKey) loadPointCloud(getPointCloudFilesPOTREE()[currentPointcloudKey]); if (currentPointcloudKey){
document.getElementById('rendererToggle').dataset.active = 'potree'; loadPointCloudPotree(getPointCloudFilesPOTREE()[currentPointcloudKey]);
document.getElementById('rendererLabel').textContent = 'Renderer: Potree'; }
document.getElementById("rendererToggle").dataset.active = "potree";
document.getElementById("rendererLabel").textContent = "Renderer: Potree";
} }
applyVisibility(); applyVisibility();
} }
function applyVisibility() {
if (currentRenderer === 'potree' && currentPointCloud) { // Sichtbarkeit der Punktwolke je nach aktivem Renderer setzen
function applyVisibility() {
if (currentRenderer === "potree" && currentPointCloud) {
currentPointCloud.visible = isVisible; currentPointCloud.visible = isVisible;
} }
if (currentRenderer === 'deckgl') { if (currentRenderer === "deckgl") {
// Trick: Punktgröße auf 0 setzen
lidarControl.setPointSize(isVisible ? 2 : 0); lidarControl.setPointSize(isVisible ? 2 : 0);
} }
} }
// Farbmodus (rgb/elevation) auf die Potree-Punktwolke anwenden
function applyColorModePotree() { function applyColorModePotree() {
if (!currentPointCloud) return; if (!currentPointCloud)
return;
let mat = currentPointCloud.material; let mat = currentPointCloud.material;
mat.activeAttributeName = currentColorMode === 'rgb' ? "rgba" : "elevation"; mat.activeAttributeName = currentColorMode === "rgb" ? "rgba" : "elevation";
viewer.renderer.resetState(); viewer.renderer.resetState();
viewer.render(); viewer.render();
map.triggerRepaint(); map.triggerRepaint();
} }
// Farbmodus je nach aktivem Renderer anwenden
function applyColorMode() { function applyColorMode() {
if (currentRenderer === 'potree') { applyColorModePotree(); } if (currentRenderer === "potree") {
else { applyDeckGLSettings(); } applyColorModePotree();
} else {
applyDeckGLSettings();
}
} }
//-------------------- INFO-JSON laden (Texte/Bilder zu den Punktwolken) -----------------
function loadInfoJSON() { function loadInfoJSON() {
fetch("info.json") fetch("info.json").then((r) => r.json()).then((data) => {
.then(r => r.json()) pointCloudInfo = data;
.then(data => { pointCloudInfo = data; }) })
.catch(e => console.error("Fehler beim Laden der JSON:", e)); .catch((e) => console.error("Fehler beim Laden der JSON:", e));
} }
//------------------------ Punktwolke im Potree Format laden -----------------------------
//------------------------lädt Punktwolke im Potree Format----------------------------- function loadPointCloudPotree(path) {
function loadPointCloud(path) {
viewer.scene.view.yaw = 0; viewer.scene.view.yaw = 0;
viewer.scene.view.pitch = 0; viewer.scene.view.pitch = 0;
// vorhandene Punktwolke aus der Szene entfernen
if (currentPointCloud) { if (currentPointCloud) {
const index = viewer.scene.pointclouds.indexOf(currentPointCloud); const index = viewer.scene.pointclouds.indexOf(currentPointCloud);
if (index !== -1) viewer.scene.pointclouds.splice(index, 1); if (index !== -1)
if (currentPointCloud.parent) currentPointCloud.parent.remove(currentPointCloud); viewer.scene.pointclouds.splice(index, 1);
if (currentPointCloud.parent){
currentPointCloud.parent.remove(currentPointCloud);
}
currentPointCloud = null; currentPointCloud = null;
viewer.render(); viewer.render();
} }
if (!path || !isVisible) return; if (!path || !isVisible)
return;
Potree.loadPointCloud(path, "punktwolke", function(e) { Potree.loadPointCloud(path, "punktwolke", function (e) {
currentPointCloud = e.pointcloud; currentPointCloud = e.pointcloud;
viewer.scene.addPointCloud(currentPointCloud); viewer.scene.addPointCloud(currentPointCloud);
@@ -252,45 +286,34 @@ function loadPointCloud(path) {
// Variable, um Endlosschleifen zu verhindern // Variable, um Endlosschleifen zu verhindern
let isSyncing = false; let isSyncing = false;
/* //------------------ Kamera von Potree und MapLibre syncronisieren ------------------------------------
Kamera von Potree und MapLibre syncronisieren
*/
function syncCamera() { function syncCamera() {
if (!currentPointCloud || isSyncing || currentRenderer !== "potree")
if (!currentPointCloud || isSyncing || currentRenderer!== 'potree') return; return;
isSyncing = true; isSyncing = true;
const transform = map.transform; const transform = map.transform;
// 1. Dimensionen auf den Pixel genau angleichen // 1. Dimensionen auf den Pixel genau angleichen
if (viewer.renderer) { if (viewer.renderer) {
viewer.renderer.setSize(map.getCanvas().clientWidth, map.getCanvas().clientHeight); viewer.renderer.setSize(map.getCanvas().clientWidth, map.getCanvas().clientHeight,);
} }
// 2. Mathematische Parameter aus dem MapLibre-Kern extrahieren // 2. Mathematische Parameter aus dem MapLibre-Kern extrahieren
const pitch = transform.pitch * Math.PI / 180; const pitch = (transform.pitch * Math.PI) / 180;
const bearing = transform.bearing * Math.PI / 180; const bearing = (transform.bearing * Math.PI) / 180;
const mapCenter = map.getCenter(); const mapCenter = map.getCenter();
const [utmX, utmY] = proj4("EPSG:4326", "EPSG:25832", [mapCenter.lng, mapCenter.lat]); const [utmX, utmY] = proj4("EPSG:4326", "EPSG:25832", [mapCenter.lng, mapCenter.lat,]);
// WICHTIG: Nutze MapLibres exakte mathematische Kameradistanz // MapLibres exakte mathematische Kameradistanz nutzen
const distanceInMeters = transform.cameraToCenterDistance / transform.pixelsPerMeter; const distanceInMeters = transform.cameraToCenterDistance / transform.pixelsPerMeter;
const target = new THREE.Vector3(utmX, utmY, 0); const target = new THREE.Vector3(utmX, utmY, 0);
const offset = new THREE.Vector3( const offset = new THREE.Vector3(0, -distanceInMeters * Math.sin(pitch), distanceInMeters * Math.cos(pitch),);
0,
-distanceInMeters * Math.sin(pitch),
distanceInMeters * Math.cos(pitch)
);
offset.applyAxisAngle(new THREE.Vector3(0, 0, 1), -bearing); offset.applyAxisAngle(new THREE.Vector3(0, 0, 1), -bearing);
const cameraPosition = target.clone().add(offset); const cameraPosition = target.clone().add(offset);
// 3. Potree-Kamera absolut starr setzen // 3. Potree-Kamera absolut starr setzen
viewer.scene.view.position.copy(cameraPosition); viewer.scene.view.position.copy(cameraPosition);
viewer.scene.view.lookAt(target); viewer.scene.view.lookAt(target);
viewer.setFOV(transform.fov); viewer.setFOV(transform.fov);
// 4. Potree rendern // 4. Potree rendern
@@ -300,156 +323,173 @@ function syncCamera() {
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 // Kartenbewegungs-Events: Kamera-Synchronisation
map.on('movestart', () => { map.on("movestart", () => {
map.getCanvasContainer().style.cursor = 'grabbing'; map.getCanvasContainer().style.cursor = "grabbing";
}); });
// bei jeder Kartenbwegeung die Kameras synchronisieren // bei jeder Kartenbewegung die Kameras synchronisieren
map.on('zoom', syncCamera); map.on("zoom", syncCamera);
map.on('move', syncCamera); map.on("move", syncCamera);
map.on('rotate', syncCamera); map.on("rotate", syncCamera);
map.on('pitch', syncCamera); map.on("pitch", syncCamera);
map.on('draw', syncCamera); map.on("draw", syncCamera);
// Potree-Canvas soll keine Mausereignisse abfangen, nur die MapLibre-Canvas
elRenderArea.style.pointerEvents = "none"; elRenderArea.style.pointerEvents = "none";
map.getCanvas().style.pointerEvents = "auto"; map.getCanvas().style.pointerEvents = "auto";
/* //---------------------- Hintergrundkarte ändern ---------------------------------------------------
Hintergrundkarte ändern, Optionen: hell, dunkel, 3D-Gebäudemodelle, Satellit, Terrain, Satellit+Terrain
*/
function changeBaseMap(newMap) { function changeBaseMap(newMap) {
var basemapStyle; var basemapStyle;
switch(newMap) { switch (newMap) {
// dunkle Karte
case "openfree_dark": case "openfree_dark":
basemapStyle = "https://tiles.openfreemap.org/styles/dark"; basemapStyle = "https://tiles.openfreemap.org/styles/dark";
break; break;
// helle Karte
case "openfree_bright": case "openfree_bright":
basemapStyle = "https://tiles.openfreemap.org/styles/bright"; basemapStyle = "https://tiles.openfreemap.org/styles/bright";
break; break;
// mit LOD1-GEbäudemodellen
case "openfree_liberty": case "openfree_liberty":
basemapStyle = "https://tiles.openfreemap.org/styles/liberty"; basemapStyle = "https://tiles.openfreemap.org/styles/liberty";
break; break;
// Satellitenbild
case "satellite": case "satellite":
basemapStyle = { basemapStyle = {
version: 8, version: 8,
sources: { sources: {
"raster-tiles": { "raster-tiles": {
type: "raster", type: "raster",
tiles: ["https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH"], tiles: [
"https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH",
],
tileSize: 256, tileSize: 256,
attribution: "&copy; MapTiler" attribution: "&copy; MapTiler",
}
}, },
layers: [{ },
layers: [
{
id: "satellite-layer", id: "satellite-layer",
type: "raster", type: "raster",
source: "raster-tiles" source: "raster-tiles",
}] },
],
}; };
break; break;
// Gelände
case "terrain": case "terrain":
basemapStyle = { basemapStyle = {
version: 8, version: 8,
sources: { sources: {
topo: { topo: {
type: 'raster', type: "raster",
url: 'https://api.maptiler.com/maps/topo-v4/tiles.json?key=6mG881AthmTTWyLvFyjH', url: "https://api.maptiler.com/maps/topo-v4/tiles.json?key=6mG881AthmTTWyLvFyjH",
tileSize: 256 tileSize: 256,
}, },
terrainSource: { terrainSource: {
type: 'raster-dem', type: "raster-dem",
url: 'https://tiles.mapterhorn.com/tilejson.json' url: "https://tiles.mapterhorn.com/tilejson.json",
}, },
hillshadeSource: { hillshadeSource: {
type: 'raster-dem', type: "raster-dem",
url: 'https://tiles.mapterhorn.com/tilejson.json' url: "https://tiles.mapterhorn.com/tilejson.json",
}
}, },
layers: [{ },
id: 'topo', layers: [
type: 'raster', {
source: 'topo' id: "topo",
}, { type: "raster",
id: 'hills', source: "topo",
type: 'hillshade', },
source: 'hillshadeSource', {
id: "hills",
type: "hillshade",
source: "hillshadeSource",
layout: { layout: {
visibility: 'visible' visibility: "visible",
}, },
paint: { paint: {
'hillshade-shadow-color': '#473B24' "hillshade-shadow-color": "#473B24",
}
}],
terrain: {
source: 'terrainSource',
exaggeration: 1
}, },
sky: {} },
],
terrain: {
source: "terrainSource",
exaggeration: 1,
},
sky: {},
}; };
break; break;
// Gelände mit Satellitenbild
case "satellite_terrain": case "satellite_terrain":
basemapStyle = { basemapStyle = {
version: 8, version: 8,
sources: { sources: {
"raster-tiles": { "raster-tiles": {
type: "raster", type: "raster",
tiles: ["https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH"], tiles: [
"https://api.maptiler.com/tiles/satellite-v2/{z}/{x}/{y}.jpg?key=6mG881AthmTTWyLvFyjH",
],
tileSize: 256, tileSize: 256,
attribution: "&copy; MapTiler" attribution: "&copy; MapTiler",
}, },
terrainSource: { terrainSource: {
type: 'raster-dem', type: "raster-dem",
url: 'https://tiles.mapterhorn.com/tilejson.json' url: "https://tiles.mapterhorn.com/tilejson.json",
}, },
hillshadeSource: { hillshadeSource: {
type: 'raster-dem', type: "raster-dem",
url: 'https://tiles.mapterhorn.com/tilejson.json' url: "https://tiles.mapterhorn.com/tilejson.json",
}
}, },
layers: [{ },
id: 'raster-tiles', layers: [
type: 'raster', {
source: 'raster-tiles' id: "raster-tiles",
}, { type: "raster",
id: 'hills', source: "raster-tiles",
type: 'hillshade', },
source: 'hillshadeSource', {
id: "hills",
type: "hillshade",
source: "hillshadeSource",
layout: { layout: {
visibility: 'visible' visibility: "visible",
}, },
paint: { paint: {
'hillshade-shadow-color': '#473B24' "hillshade-shadow-color": "#473B24",
}
}],
terrain: {
source: 'terrainSource',
exaggeration: 1
}, },
sky: {} },
],
terrain: {
source: "terrainSource",
exaggeration: 1,
},
sky: {},
}; };
break; break;
} }
map.setStyle(basemapStyle); map.setStyle(basemapStyle);
} }
//------------------------------------------------------Action-Listener und Event-Handler---------------------------------------------------------------------------------
// Punktwolke aus/einblenden
document.querySelector('#disable').addEventListener('click', function() { //----------------------------------Action-Listener und Event-Handler--------------------------------------
// Punktwolke über Button aus/einblenden
document.querySelector("#disable").addEventListener("click", function () {
isVisible = !isVisible; isVisible = !isVisible;
//if (currentPointCloud)
// currentPointCloud.visible = isVisible;
applyVisibility(); applyVisibility();
if(isVisible) { if (isVisible) {
this.textContent = "Punktwolke ausblenden"; this.textContent = "Punktwolke ausblenden";
this.classList.add("active-state"); this.classList.add("active-state");
this.classList.remove("inactive-state"); this.classList.remove("inactive-state");
@@ -462,39 +502,43 @@ document.querySelector('#disable').addEventListener('click', function() {
// Koordinaten an der Mausposition darstellen (auf 5 Nachkommastellen begrenzt) // Koordinaten an der Mausposition darstellen (auf 5 Nachkommastellen begrenzt)
const coordinatesDiv = document.getElementById('coordinates'); const coordinatesDiv = document.getElementById("coordinates");
map.on('mousemove', (e) => { map.on("mousemove", (e) => {
coordinatesDiv.innerHTML = `Lon: ${e.lngLat.lng.toFixed(5)}° | Lat: ${e.lngLat.lat.toFixed(5)}°`; coordinatesDiv.innerHTML = `Lon: ${e.lngLat.lng.toFixed(5)}° | Lat: ${e.lngLat.lat.toFixed(5)}°`;
}); });
// Hintergrundkarte ändern, wenn anderes Element im DropDown gewählt wird // 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 // Punktwolke austauschen, wenn anderes Element im DropDown gewählt wird
document.querySelector('select[name="pointcloud"]').addEventListener('change', (e) => { document.querySelector('select[name="pointcloud"]').addEventListener("change", (e) => {
//loadPointCloud(getPointCloudFilesPOTREE()[e.target.value]);
currentPointcloudKey = e.target.value; currentPointcloudKey = e.target.value;
loadCurrentPointCloud(); loadPointCloud();
}); });
// Kartenausschnitt auf Ursprung zurücksetzen // 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() { // Punktgröße mit Slider anpassen
//viewer.setMinNodeSize(this.value); document.getElementById("pointSizeSlider").oninput = function () {
currentPointSize = Number(this.value); currentPointSize = Number(this.value);
if (currentRenderer === 'potree') { if (currentRenderer === "potree") {
viewer.setMinNodeSize(currentPointSize); viewer.setMinNodeSize(currentPointSize);
} else if (lidarControl) { } else if (lidarControl) {
try { lidarControl.setPointSize(currentPointSize); } catch(e) {} try {
} lidarControl.setPointSize(currentPointSize);
} catch (e) {}
}
}; };
//--------------------------- Sidepannel ein/ausklappen ----------------------------------------------------
const closeButton = document.getElementById("closeSideBarButton"); const closeButton = document.getElementById("closeSideBarButton");
const sidebar = document.getElementById("sidebar"); const sidebar = document.getElementById("sidebar");
const openOuter = document.getElementById("openButtonOuter"); const openOuter = document.getElementById("openButtonOuter");
@@ -513,23 +557,24 @@ if (closeButton) {
} }
document.querySelectorAll('.qualityButtons').forEach(btn => { //------------------------- QUALITÄTS- UND FARB-BUTTONS ----------------------------------------------------
btn.addEventListener('click', () => { // Qualitätsstufe wählen
document.querySelectorAll(".qualityButtons").forEach((btn) => {
btn.addEventListener("click", () => {
quality = btn.id; quality = btn.id;
//if (currentPointCloud) loadPointCloud(getPointCloudFiles()[document.getElementById("pointcloud").value]); if (currentPointcloudKey)
if (currentPointcloudKey) loadCurrentPointCloud(); loadPointCloud();
document.querySelectorAll('.qualityButtons').forEach(b => b.classList.remove('active')); document.querySelectorAll(".qualityButtons").forEach((b) => b.classList.remove("active"));
btn.classList.add('active'); btn.classList.add("active");
}); });
}); });
document.querySelectorAll('.colorButtons').forEach(btn => { // Farbmodus (rgb/elevation) wählen
btn.addEventListener('click', () => { document.querySelectorAll(".colorButtons").forEach((btn) => {
//if (!currentPointCloud) return; // Abbrechen, falls noch keine Wolke geladen ist btn.addEventListener("click", () => {
document.querySelectorAll(".colorButtons").forEach((b) => b.classList.remove("active"));
document.querySelectorAll('.colorButtons').forEach(b => b.classList.remove('active')); btn.classList.add("active");
btn.classList.add('active');
currentColorMode = btn.id; currentColorMode = btn.id;
applyColorMode(); applyColorMode();
}); });
@@ -537,26 +582,25 @@ document.querySelectorAll('.colorButtons').forEach(btn => {
// Sobald die Karte das allererste Mal stabil steht, Kamera abgleichen // Sobald die Karte das allererste Mal stabil steht, Kamera abgleichen
map.once('idle', () => { map.once("idle", () => {
syncCamera(); syncCamera();
}); });
//--------------- INFO-PANEL (Texte & Bilder zu den Punktwolken) -------------------------------
let pointCloudInfo = {}; let pointCloudInfo = {};
const infoPanel = document.getElementById("info-panel"); const infoPanel = document.getElementById("info-panel");
const infoPanelTitle = document.getElementById("info-panel-title"); const infoPanelTitle = document.getElementById("info-panel-title");
const infoPanelText = document.getElementById("info-panel-text"); const infoPanelText = document.getElementById("info-panel-text");
const openInfoPanel = document.getElementById("openInfoPanel"); const openInfoPanel = document.getElementById("openInfoPanel");
const closeInfoPanel = document.getElementById("closeInfoPanel"); const closeInfoPanel = document.getElementById("closeInfoPanel");
let currentImageIndex = 0; let currentImageIndex = 0;
// Info-Panel mit Titel, Text und Bild/Bildern der gewählten Punktwolke befüllen
function updateInfoPanel() { function updateInfoPanel() {
const key = document.querySelector('select[name="pointcloud"]').value; const key = document.querySelector('select[name="pointcloud"]').value;
const info = pointCloudInfo[key]; const info = pointCloudInfo[key];
currentImageIndex = 0; currentImageIndex = 0;
if (info) { if (info) {
@@ -572,6 +616,7 @@ function updateInfoPanel() {
} }
// aktuelles Bild + Bildzähler im Info-Panel anzeigen
function updateImage(info) { function updateImage(info) {
const img = document.getElementById("info-panel-image"); const img = document.getElementById("info-panel-image");
const nav = document.getElementById("info-image-nav"); const nav = document.getElementById("info-image-nav");
@@ -589,44 +634,49 @@ function updateImage(info) {
} }
// vorheriges Bild im Info-Panel anzeigen
document.getElementById("info-img-prev").addEventListener("click", () => { document.getElementById("info-img-prev").addEventListener("click", () => {
const key = document.querySelector('select[name="pointcloud"]').value; const key = document.querySelector('select[name="pointcloud"]').value;
const info = pointCloudInfo[key]; const info = pointCloudInfo[key];
if (!info) return; if (!info)
return;
currentImageIndex = (currentImageIndex - 1 + info.images.length) % info.images.length; currentImageIndex = (currentImageIndex - 1 + info.images.length) % info.images.length;
updateImage(info); updateImage(info);
}); });
// nächstes Bild im Info-Panel anzeigen
document.getElementById("info-img-next").addEventListener("click", () => { document.getElementById("info-img-next").addEventListener("click", () => {
const key = document.querySelector('select[name="pointcloud"]').value; const key = document.querySelector('select[name="pointcloud"]').value;
const info = pointCloudInfo[key]; const info = pointCloudInfo[key];
if (!info) return; if (!info)
return;
currentImageIndex = (currentImageIndex + 1) % info.images.length; currentImageIndex = (currentImageIndex + 1) % info.images.length;
updateImage(info); updateImage(info);
}); });
// Info-Panel öffnen
openInfoPanel.addEventListener("click", () => { openInfoPanel.addEventListener("click", () => {
infoPanel.classList.add("open"); infoPanel.classList.add("open");
//openInfoPanel.style.display = "none";
}); });
// Info-Panel schließen
closeInfoPanel.addEventListener("click", () => { closeInfoPanel.addEventListener("click", () => {
infoPanel.classList.remove("open"); infoPanel.classList.remove("open");
openInfoPanel.style.display = "flex"; openInfoPanel.style.display = "flex";
}); });
// Panel aktualisieren wenn Punktwolke gewechselt wird // Info-Panel aktualisieren, wenn Punktwolke gewechselt wird
document.querySelector('select[name="pointcloud"]').addEventListener("change", () => { document.querySelector('select[name="pointcloud"]').addEventListener("change", () => {
updateInfoPanel(); updateInfoPanel();
}); });
document.getElementById('rendererToggle').addEventListener('click', function() { //Renderer Toggle Button
const next = currentRenderer === 'deckgl' ? 'potree' : 'deckgl'; document.getElementById("rendererToggle").addEventListener("click", function () {
const next = currentRenderer === "deckgl" ? "potree" : "deckgl";
switchRenderer(next); switchRenderer(next);
}); });
+3 -3
View File
@@ -2,16 +2,16 @@
"first": { "first": {
"title": "Fläche im unteren Bereich", "title": "Fläche im unteren Bereich",
"text": "Dieser Bereich des Steinbruchs zeigt eine natürliche Oberfläche aus Obernkirchener Sandstein. Zwischen einzelnen Sandsteinblöcken haben sich Sträucher und andere Pflanzen angesiedelt. Der Sandstein entstand vor etwa 140 Millionen Jahren in einer küstennahen Landschaft der Unterkreide.", "text": "Dieser Bereich des Steinbruchs zeigt eine natürliche Oberfläche aus Obernkirchener Sandstein. Zwischen einzelnen Sandsteinblöcken haben sich Sträucher und andere Pflanzen angesiedelt. Der Sandstein entstand vor etwa 140 Millionen Jahren in einer küstennahen Landschaft der Unterkreide.",
"images": ["stand1_1.jpeg", "stand1_2.jpeg"] "images": ["/stand1_1.jpeg", "/stand1_2.jpeg"]
}, },
"second": { "second": {
"title": "Abbauwand", "title": "Abbauwand",
"text": "An dieser Felswand wird der Obernkirchener Sandstein abgebaut. Das Gestein ist für seine hohe Festigkeit und Witterungsbeständigkeit bekannt und wird seit Jahrhunderten als Bau- und Werkstein genutzt. Es findet sich unter anderem an zahlreichen historischen Gebäuden in Deutschland.", "text": "An dieser Felswand wird der Obernkirchener Sandstein abgebaut. Das Gestein ist für seine hohe Festigkeit und Witterungsbeständigkeit bekannt und wird seit Jahrhunderten als Bau- und Werkstein genutzt. Es findet sich unter anderem an zahlreichen historischen Gebäuden in Deutschland.",
"images": ["stand2_1.jpeg"] "images": ["/stand2_1.jpeg"]
}, },
"third": { "third": {
"title": "Dinosaurier-Spuren", "title": "Dinosaurier-Spuren",
"text": "Auf dieser öffentlich zugänglichen Fläche sind fossile Fußspuren von Dinosauriern erhalten geblieben. Die Spuren entstanden vor rund 140 Millionen Jahren in feuchtem Sand und wurden später durch weitere Sedimentschichten konserviert. Sie gehören zu den bekanntesten Fossilienfunden im Obernkirchener Sandstein.", "text": "Auf dieser öffentlich zugänglichen Fläche sind fossile Fußspuren von Dinosauriern erhalten geblieben. Die Spuren entstanden vor rund 140 Millionen Jahren in feuchtem Sand und wurden später durch weitere Sedimentschichten konserviert. Sie gehören zu den bekanntesten Fossilienfunden im Obernkirchener Sandstein.",
"images": ["stand3_1.jpeg", "stand3_2.jpeg"] "images": ["/stand3_1.jpeg", "/stand3_2.jpeg"]
} }
} }
+182 -173
View File
@@ -1,29 +1,30 @@
html, body { html,
body {
height: 100%; height: 100%;
margin: 0; margin: 0;
} }
body{ body {
margin:0; margin: 0;
overflow:hidden; overflow: hidden;
font-family: 'Inter', sans-serif; font-family: "Inter", sans-serif;
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
} }
#main{ #main {
width:100vw; width: 100vw;
height:100vh; height: 100vh;
position:relative; position: relative;
} }
#header{ #header {
position:absolute; position: absolute;
top:20px; top: 20px;
left:50%; left: 50%;
transform:translateX(-50%); transform: translateX(-50%);
z-index:30; z-index: 30;
padding:14px 28px; padding: 14px 28px;
border-radius: 20px; border-radius: 20px;
background: background:
linear-gradient( linear-gradient(
@@ -32,28 +33,28 @@ body{
rgba(79, 79, 79, 0.756) 60%, rgba(79, 79, 79, 0.756) 60%,
rgba(47, 47, 47, 0.864) 100% rgba(47, 47, 47, 0.864) 100%
), ),
rgba(24,24,27,0.78); rgba(24, 24, 27, 0.78);
backdrop-filter: blur(16px); backdrop-filter: blur(16px);
color:white; color: white;
font-size:24px; font-size: 24px;
font-weight:600; font-weight: 600;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.536); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.536);
} }
.logos{ .logos {
margin-top:20px; margin-top: 20px;
margin-left: 12px; margin-left: 12px;
opacity:0.8; opacity: 0.8;
} }
.logos img{ .logos img {
width:80px; width: 80px;
margin-right: 10px; margin-right: 10px;
} }
.logos img:hover{ .logos img:hover {
transform:translateY(-1px); transform: translateY(-1px);
box-shadow: 0 6px 18px rgba(0,0,0,0.12); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
} }
#location-button { #location-button {
@@ -69,7 +70,9 @@ body{
background-color: #f8f9fa; background-color: #f8f9fa;
border: none !important; border: none !important;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05); box-shadow:
0 0 0 2px rgba(0, 0, 0, 0.1),
0 4px 6px rgba(0, 0, 0, 0.05);
cursor: pointer; cursor: pointer;
box-sizing: border-box; box-sizing: border-box;
} }
@@ -78,28 +81,28 @@ body{
background-color: #f4f4f4; background-color: #f4f4f4;
} }
#location{ #location {
width:20px; width: 20px;
height:20px; height: 20px;
object-fit:contain; object-fit: contain;
margin-left: 1px; margin-left: 1px;
margin-top: 1px; margin-top: 1px;
cursor: pointer; cursor: pointer;
} }
#location:hover{ #location:hover {
transform:scale(1.05); transform: scale(1.05);
background:rgb(233, 232, 232); background: rgb(233, 232, 232);
} }
#location-button:hover{ #location-button:hover {
background:rgb(233, 232, 232); background: rgb(233, 232, 232);
} }
#sidebar { #sidebar {
position: absolute; position: absolute;
top: 48%; top: 48%;
transform:translateY(-50%); transform: translateY(-50%);
left: 20px; left: 20px;
width: 210px; width: 210px;
padding: 22px; padding: 22px;
@@ -107,13 +110,13 @@ body{
padding-top: 10px; padding-top: 10px;
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(18px); 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; 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; z-index: 20;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap:8px; gap: 8px;
} }
#main { #main {
@@ -126,9 +129,9 @@ body{
height: 100%; height: 100%;
} }
#potree_render_area{ #potree_render_area {
position:absolute; position: absolute;
top:0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -140,49 +143,48 @@ body{
background: transparent !important; background: transparent !important;
} }
select{ select {
appearance:none; appearance: none;
-webkit-appearance:none; -webkit-appearance: none;
-moz-appearance:none; -moz-appearance: none;
width:100%; width: 100%;
padding:10px 16px; padding: 10px 16px;
border:none; border: none;
border-radius:14px; border-radius: 14px;
background:rgba(255,255,255,0.92); background: rgba(255, 255, 255, 0.92);
font-size:14px; font-size: 14px;
font-weight:500; font-weight: 500;
color:#0f172a; color: #0f172a;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278);
cursor:pointer; cursor: pointer;
transition:0.2s; transition: 0.2s;
} }
.selectWrapper{ .selectWrapper {
position:relative; position: relative;
width:100%; width: 100%;
} }
.selectArrow{ .selectArrow {
position:absolute; position: absolute;
right:14px; right: 14px;
top:48%; top: 48%;
transform:translateY(-50%); transform: translateY(-50%);
pointer-events:none; pointer-events: none;
font-size:14px; font-size: 14px;
color:#000000; color: #000000;
} }
#disable{ #disable {
appearance:none; appearance: none;
-webkit-appearance:none; -webkit-appearance: none;
-moz-appearance:none; -moz-appearance: none;
width:100%; width: 100%;
padding:10px 16px; padding: 10px 16px;
border:none; border: none;
border-radius:14px; border-radius: 14px;
font-size:14px; font-size: 14px;
font-weight:500; font-weight: 500;
background: background:
linear-gradient( linear-gradient(
135deg, 135deg,
@@ -193,8 +195,8 @@ select{
rgba(6, 45, 135, 0.92); rgba(6, 45, 135, 0.92);
color: white; color: white;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278);
cursor:pointer; cursor: pointer;
transition:0.2s; transition: 0.2s;
margin-top: 8px; margin-top: 8px;
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -212,33 +214,35 @@ select{
} }
#disable.inactive-state { #disable.inactive-state {
background: rgba(255,255,255,0.92); background: rgba(255, 255, 255, 0.92);
color: #0f172a; color: #0f172a;
} }
select:hover{ select:hover {
transform:translateY(-1px); transform: translateY(-1px);
box-shadow: 0 6px 18px rgba(0,0,0,0.12); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
} }
select:focus{ select:focus {
outline:none; outline: none;
box-shadow:0 0 0 4px rgba(37,99,235,0.15), 0 8px 24px rgba(0,0,0,0.12); box-shadow:
0 0 0 4px rgba(37, 99, 235, 0.15),
0 8px 24px rgba(0, 0, 0, 0.12);
} }
#disable:hover{ #disable:hover {
transform:translateY(-1px); transform: translateY(-1px);
box-shadow: 0 6px 18px rgba(0,0,0,0.12); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
} }
*{ * {
transition: transition:
background 0.2s, background 0.2s,
transform 0.01s, transform 0.01s,
opacity 0.2s; opacity 0.2s;
} }
footer{ footer {
z-index: 30; z-index: 30;
position: absolute; position: absolute;
bottom: 8px; bottom: 8px;
@@ -246,13 +250,13 @@ footer{
font-size: small; font-size: small;
} }
.sideBarText{ .sideBarText {
font-size:80%; font-size: 80%;
margin:0; margin: 0;
margin-bottom: -2px; margin-bottom: -2px;
} }
#pointSizeSlider{ #pointSizeSlider {
width: 100%; width: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -260,7 +264,7 @@ margin-bottom: -2px;
margin-bottom: 8px; margin-bottom: 8px;
} }
input[type="range"]{ input[type="range"] {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
width: 100%; width: 100%;
@@ -272,11 +276,11 @@ input[type="range"]{
outline: none; outline: none;
} }
input[type="range"]::-webkit-slider-thumb{ input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
width:16px; width: 16px;
height:16px; height: 16px;
border-radius:50%; border-radius: 50%;
background: background:
linear-gradient( linear-gradient(
135deg, 135deg,
@@ -285,50 +289,50 @@ input[type="range"]::-webkit-slider-thumb{
rgba(6, 45, 135, 0.92) 100% rgba(6, 45, 135, 0.92) 100%
), ),
rgba(6, 45, 135, 0.92); rgba(6, 45, 135, 0.92);
cursor:pointer; cursor: pointer;
} }
.slider-labels{ .slider-labels {
display:flex; display: flex;
justify-content:space-between; justify-content: space-between;
font-size:0.5rem; font-size: 0.5rem;
color:#868686; color: #868686;
margin-bottom:0px; margin-bottom: 0px;
margin-top: -5px; margin-top: -5px;
} }
#title{ #title {
z-index: 30; z-index: 30;
position: absolute; position: absolute;
top: 75px; top: 75px;
left: 50%; left: 50%;
transform:translateX(-50%); transform: translateX(-50%);
font-size: small; font-size: small;
color: #ffffff; color: #ffffff;
text-shadow: 1px 1.5px #00000059; text-shadow: 1px 1.5px #00000059;
} }
#coordinates{ #coordinates {
z-index: 30; z-index: 30;
position: absolute; position: absolute;
bottom: 20px; bottom: 20px;
left: 50%; left: 50%;
transform:translateX(-50%); transform: translateX(-50%);
font-size: smaller; font-size: smaller;
color: #000000cf; color: #000000cf;
border: none; border: none;
border-radius: 14px; border-radius: 14px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278);
padding:8px; padding: 8px;
border-radius: 15px; border-radius: 15px;
background-color: rgba(255, 255, 255, 0.751); background-color: rgba(255, 255, 255, 0.751);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
.sideBarButtons{ .sideBarButtons {
font-family:monospace; font-family: monospace;
border:none; border: none;
border-radius:10px; border-radius: 10px;
background: background:
linear-gradient( linear-gradient(
135deg, 135deg,
@@ -336,54 +340,56 @@ input[type="range"]::-webkit-slider-thumb{
rgba(79, 79, 79, 0.756) 60%, rgba(79, 79, 79, 0.756) 60%,
rgba(47, 47, 47, 0.864) 100% rgba(47, 47, 47, 0.864) 100%
), ),
rgba(24,24,27,0.78); rgba(24, 24, 27, 0.78);
font-size:15px; font-size: 15px;
color:#ffffff; color: #ffffff;
box-shadow: 0 2px 10px rgba(0,0,0,0.08); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
cursor:pointer; cursor: pointer;
transition:0.2s; transition: 0.2s;
z-index: 30; z-index: 30;
} }
#closeSideBarButton{ #closeSideBarButton {
align-self: flex-end; align-self: flex-end;
right: 0%; right: 0%;
padding:5px 15px; padding: 5px 15px;
} }
#openSideBarButton{ #openSideBarButton {
position: absolute; position: absolute;
left: 5px; left: 5px;
padding:5px 15px; padding: 5px 15px;
} }
.buttonRow{ .buttonRow {
display: flex; display: flex;
width: 100%; width: 100%;
border: none; border: none;
border-radius: 10px; border-radius: 10px;
overflow:hidden; overflow: hidden;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278);
margin-bottom: 10px; 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{ .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: background:
linear-gradient( linear-gradient(
135deg, 135deg,
@@ -397,9 +403,9 @@ position: absolute;
#potree_render_area { #potree_render_area {
pointer-events: none; pointer-events: none;
} }
#info-button-outer { #info-button-outer {
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 20px; right: 20px;
@@ -417,31 +423,33 @@ position: absolute;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
box-shadow: 0 5px 10px rgba(0,0,0,0.278); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278);
transition: 0.2s; transition: 0.2s;
} }
#rendererToggle[data-active="deckgl"] { #rendererToggle[data-active="deckgl"] {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
rgba(18,77,216,0.92) 10%, rgba(18, 77, 216, 0.92) 10%,
rgba(13,61,175,0.92) 60%, rgba(13, 61, 175, 0.92) 60%,
rgba(6,45,135,0.92) 100%); rgba(6, 45, 135, 0.92) 100%
);
color: #ffffff; color: #ffffff;
} }
#rendererToggle[data-active="potree"] { #rendererToggle[data-active="potree"] {
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
rgba(22,163,74,0.92) 10%, rgba(22, 163, 74, 0.92) 10%,
rgba(15,118,54,0.92) 60%, rgba(15, 118, 54, 0.92) 60%,
rgba(6,78,32,0.92) 100%); rgba(6, 78, 32, 0.92) 100%
);
color: #ffffff; color: #ffffff;
} }
#rendererToggle:hover { #rendererToggle:hover {
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 6px 18px rgba(0,0,0,0.12); box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
} }
#rendererIcon { #rendererIcon {
@@ -457,8 +465,8 @@ position: absolute;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
background: rgba(255,255,255,0.9); background: rgba(255, 255, 255, 0.9);
box-shadow: 0 5px 10px rgba(0,0,0,0.278); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.278);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -467,7 +475,7 @@ position: absolute;
} }
#openInfoPanel:hover { #openInfoPanel:hover {
background: rgb(233,232,232); background: rgb(233, 232, 232);
transform: translateY(-1px); transform: translateY(-1px);
} }
@@ -485,11 +493,11 @@ position: absolute;
transform: translateY(-50%); transform: translateY(-50%);
width: 340px; width: 340px;
padding: 18px 22px; padding: 18px 22px;
background: rgba(255,255,255,0.9); background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(18px); 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; 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; z-index: 20;
display: none; display: none;
flex-direction: column; flex-direction: column;
@@ -536,18 +544,20 @@ position: absolute;
color: #1e293b; color: #1e293b;
} }
#info-img-prev, #info-img-next { #info-img-prev,
#info-img-next {
border: none; border: none;
background: rgba(255,255,255,0.9); background: rgba(255, 255, 255, 0.9);
border-radius: 8px; border-radius: 8px;
padding: 4px 10px; padding: 4px 10px;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
box-shadow: 0 2px 6px rgba(0,0,0,0.15); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
} }
#info-img-prev:hover, #info-img-next:hover { #info-img-prev:hover,
background: rgb(233,232,232); #info-img-next:hover {
background: rgb(233, 232, 232);
} }
#info-image-counter { #info-image-counter {
@@ -570,4 +580,3 @@ button:has(.lidar-control-icon) {
padding: 0 !important; padding: 0 !important;
border: none !important; border: none !important;
} }