UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

188 lines (155 loc) 6.14 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import type GUI from 'lil-gui'; import { Color, Group, Vector2, Vector3 } from 'three'; import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'; import type Instance from '../core/Instance'; import type Map from '../entities/Map'; import type TileMesh from '../entities/tiles/TileMesh'; import Ellipsoid from '../core/geographic/Ellipsoid'; import Panel from './Panel'; const tmpVec2 = new Vector2(); function createTileLabel(): HTMLDivElement { const text = document.createElement('div'); text.style.color = '#FFFFFF'; text.style.padding = '0.2em 1em'; text.style.textShadow = '2px 2px 2px black'; text.style.textAlign = 'center'; text.style.fontSize = '12px'; text.style.backgroundColor = 'rgba(0,0,0,0.5)'; return text; } const tmpLines: string[] = []; class TileInfoPanel extends Panel { private readonly _labels: globalThis.Map<number, CSS2DObject> = new globalThis.Map(); private readonly _map: Map; private readonly _root = new Group(); public readonly params: { enabled: boolean; nodeInfo: boolean; imageSize: boolean; minMax: boolean; layerInfo: boolean; color: Color; } = { enabled: false, nodeInfo: true, imageSize: true, layerInfo: false, minMax: false, color: new Color('yellow'), }; /** * @param map - The map. * @param parentGui - Parent GUI * @param instance - The instance */ public constructor(map: Map, parentGui: GUI, instance: Instance) { super(parentGui, instance, 'Tile info'); this._map = map; this.addController(this.params, 'enabled') .name('Show tile info') .onChange(() => this.updateValues()); this.addController(this.params, 'imageSize').onChange(() => this.updateValues()); this.addController(this.params, 'minMax') .name('Elevation range') .onChange(() => this.updateValues()); this.addController(this.params, 'layerInfo').onChange(() => this.updateValues()); } public getOrCreateLabel(obj: TileMesh): CSS2DObject { if (!this._labels.has(obj.id)) { const label = new CSS2DObject(createTileLabel()); label.name = 'MapInspector label'; obj.addEventListener('dispose', () => { this.removeLabel(obj.id, label); }); this._root.add(label); this._root.updateMatrixWorld(true); if (this._root.parent == null) { this._root.name = 'TileInfoPanel'; this.instance.threeObjects.add(this._root); } this._labels.set(obj.id, label); } return this._labels.get(obj.id) as CSS2DObject; } public getInfo(tile: TileMesh): string { tmpLines.length = 0; if (this.params.nodeInfo) { tmpLines.push( `Node #${tile.id} LOD=${tile.lod} (${tile.coordinate.x}, ${tile.coordinate.y})`, ); } if (this.params.minMax) { tmpLines.push(`min: ${tile.minmax.min.toFixed(1)}, max: ${tile.minmax.max.toFixed(1)}`); } if (this.params.imageSize) { const size = tile.getScreenPixelSize(this.instance.view, tmpVec2); tmpLines.push( `${tile.textureSize.width} * ${tile.textureSize.height} / ${size != null ? Math.max(size.width, size.height) : 'NULL'}`, ); } if (this.params.layerInfo) { this._map.forEachLayer(layer => { const info = layer.getInfo(tile); tmpLines.push( `${layer.name ?? layer.id}: ${info.imageCount} img, ${info.state}, ${info.paintCount} paints)`, ); }); } return tmpLines.join('\n'); } private getLabelVisibility(tile: TileMesh, position: Vector3): boolean { let result = tile.visible && tile.material.visible; if (this.instance.coordinateSystem.isEpsg(4978)) { const camPos = this.instance.view.camera.position; result = result && Ellipsoid.WGS84.isHorizonVisible(camPos, position); } return result; } private getLabelPosition(tile: TileMesh): Vector3 { const center = tile.extent.center(); const elev = this._map.getElevation({ coordinates: center }); const sample = elev.samples?.sort((a, b) => a.resolution - b.resolution)[0]; const z = sample?.elevation ?? 0; if (this.instance.coordinateSystem.isEpsg(4978)) { return Ellipsoid.WGS84.toCartesian(center.latitude, center.longitude, z); } else { return new Vector3(center.x, center.y, z); } } private removeLabel(id: number, label: CSS2DObject): void { label.element.remove(); label.removeFromParent(); this._labels.delete(id); } public updateLabel(tile: TileMesh): void { const color = this.params.color; const visible = tile.visible && tile.material.visible && this.params.enabled; if (!visible) { const label = this._labels.get(tile.id); if (label) { this.removeLabel(tile.id, label); } } else { const label = this.getOrCreateLabel(tile); const element = label.element; element.innerText = this.getInfo(tile); element.style.color = `#${color.getHexString()}`; label.position.copy(this.getLabelPosition(tile)); label.updateMatrixWorld(true); const isVisible = this.getLabelVisibility(tile, label.position); element.style.opacity = isVisible ? '100%' : '0%'; label.visible = isVisible; } } public override updateValues(): void { this._map.traverseTiles(tile => { this.updateLabel(tile); }); } } export default TileInfoPanel;