From 415dd77ec710ca6ffc7ea64cd4b0fe22ce720636 Mon Sep 17 00:00:00 2001 From: Mathieu Livebardon Date: Thu, 15 Sep 2022 16:53:18 +0100 Subject: [PATCH 1/6] feat(widgets): Widget to display layers layerchoice widget + example checkbox check all LayerChoice focus button examples 2 tileset undefined config updateUI() light and change one comment revert gitignore change --- .gitignore | 2 +- examples/css/widgets.css | 11 ++ examples/widgets_layer_choice.html | 161 +++++++++++++++++++ src/Utils/gui/LayerChoice.js | 247 +++++++++++++++++++++++++++++ src/Utils/gui/Main.js | 1 + 5 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 examples/widgets_layer_choice.html create mode 100644 src/Utils/gui/LayerChoice.js diff --git a/.gitignore b/.gitignore index 5ebc710462..109a676ef7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ potree /docs/out/ coverage .nyc_output/ -/src/ThreeExtended/ +/src/ThreeExtended/ \ No newline at end of file diff --git a/examples/css/widgets.css b/examples/css/widgets.css index 324ebadd1a..5170643ab9 100644 --- a/examples/css/widgets.css +++ b/examples/css/widgets.css @@ -247,7 +247,18 @@ color: #222222; } +/* ---------- LAYER CHOICE WIDGET SETTINGS : ------------------------------------------------------------------------ */ +#widgets-layer-choice { + position: absolute; + z-index: 10; + padding: 0 10px; + box-sizing: border-box; + + border: 1px solid #222222; + border-radius: 7px; + +} /* ---------- SCALE WIDGET SETTINGS : ------------------------------------------------------------------------------- */ diff --git a/examples/widgets_layer_choice.html b/examples/widgets_layer_choice.html new file mode 100644 index 0000000000..9ab9c1e943 --- /dev/null +++ b/examples/widgets_layer_choice.html @@ -0,0 +1,161 @@ + + + Itowns - Layer Choice widget + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/src/Utils/gui/LayerChoice.js b/src/Utils/gui/LayerChoice.js new file mode 100644 index 0000000000..d0944dfafe --- /dev/null +++ b/src/Utils/gui/LayerChoice.js @@ -0,0 +1,247 @@ +import CameraUtils from 'Utils/CameraUtils'; +import Widget from './Widget'; + +import Coordinates from '../../Core/Geographic/Coordinates'; + +const DEFAULT_OPTIONS = { + width: 200, + height: 'fit-content', + position: 'top-left', +}; + +/** + * example path : "/examples/widgets_layer_choice.html" + * + * @extends Widget + * + * @property {HTMLElement} domElement An html div containing the minimap. + */ +class LayerChoice extends Widget { + /** + * It creates a new layer-choice widget, which is a div element that contains a list of color layers, + * elevation layers, and geometry layers + * @param {View} view - the view object of the scene + * @param {Object} [config] - an object containing the configuration of the widget. + * @param {Object} [options] - the options object passed in the constructor. + */ + constructor(view, config = {}, options = {}) { + // ---------- BUILD PROPERTIES ACCORDING TO DEFAULT OPTIONS AND OPTIONS PASSED IN PARAMETERS : ---------- + + super(view, options, DEFAULT_OPTIONS); + + // ---------- this.domElement SETTINGS SPECIFIC TO layer-choice : ---------- + + this.domElement.id = 'widgets-layer-choice'; + + this.view = view; + + // Initialize the text content of the layer-choice, which will later be updated by a numerical value. + this.domElement.innerHTML = 'Layer Choice'; + + this.rangeFocus = config.rangeFocus || null; + this.tiltFocus = config.tiltFocus || 60; + + this.width = options.width || DEFAULT_OPTIONS.width; + this.height = options.height || DEFAULT_OPTIONS.height; + this.updateUI(); + } + + /** + * This function creates the UI for the widget + */ + updateUI() { + this.domElement.appendChild(this.initContentColorLayers()); + this.domElement.appendChild(this.initContentElevationLayers()); + this.domElement.appendChild(this.initContentGeometryLayers()); + } + + // Create the description part of ColorLayers + initContentColorLayers() { + const html = document.createElement('div'); + const titleColorLayers = document.createElement('h3'); + titleColorLayers.innerHTML = 'Color Layers : '; + html.appendChild(titleColorLayers); + + const list = document.createElement('div'); + + const layers = this.view.getLayers(layer => layer.isColorLayer); + for (let i = 0; i < layers.length; i++) { + const layer = layers[i]; + const divLayer = document.createElement('div'); + divLayer.innerHTML = layer.id; + + const labelVisible = document.createElement('label'); + labelVisible.innerHTML = ' Visible : '; + divLayer.appendChild(labelVisible); + const inputVisibleCheckbox = document.createElement('input'); + inputVisibleCheckbox.type = 'checkbox'; + inputVisibleCheckbox.checked = layer.visible; + divLayer.appendChild(inputVisibleCheckbox); + + const labelInputOpactity = document.createElement('label'); + labelInputOpactity.innerHTML = ' Opacity : '; + divLayer.appendChild(labelInputOpactity); + const inputOpacity = document.createElement('input'); + inputOpacity.type = 'number'; + inputOpacity.id = `opacity_${i}`; + inputOpacity.min = 0; + inputOpacity.max = 1; + inputOpacity.step = 0.05; + inputOpacity.value = layer.opacity; + divLayer.appendChild(inputOpacity); + + inputVisibleCheckbox.onchange = (event) => { + layer.visible = event.target.checked; + this.view.notifyChange(); + }; + + inputOpacity.oninput = (event) => { + layer.opacity = event.target.valueAsNumber; + this.view.notifyChange(); + }; + + list.appendChild(divLayer); + } + html.appendChild(list); + + return html; + } + + // Create the description part of ElevationLayers + initContentElevationLayers() { + const html = document.createElement('div'); + const titleElevationLayers = document.createElement('h3'); + titleElevationLayers.innerHTML = 'Elevation Layers : '; + html.appendChild(titleElevationLayers); + + const list = document.createElement('div'); + const layers = this.view.getLayers(layer => layer.isElevationLayer); + for (let i = 0; i < layers.length; i++) { + const layer = layers[i]; + const divLayer = document.createElement('div'); + + divLayer.innerHTML = layer.id; + + const labelScale = document.createElement('label'); + labelScale.innerHTML = ' Scale : '; + divLayer.appendChild(labelScale); + const spanScale = document.createElement('span'); + spanScale.innerHTML = layer.scale; + labelScale.appendChild(spanScale); + + const inputScale = document.createElement('input'); + inputScale.type = 'number'; + inputScale.min = 0; + inputScale.max = 10; + inputScale.step = 0.1; + inputScale.value = layer.scale; + divLayer.appendChild(inputScale); + + inputScale.oninput = (event) => { + layer.scale = event.target.valueAsNumber; + this.view.notifyChange(); + spanScale.innerHTML = layer.scale; + }; + list.appendChild(divLayer); + } + html.appendChild(list); + + return html; + } + + // Create the description part of GeometryLayers + initContentGeometryLayers() { + const html = document.createElement('div'); + const titleGeometryLayers = document.createElement('h3'); + titleGeometryLayers.innerHTML = 'Geometry Layers : '; + html.appendChild(titleGeometryLayers); + + const divCheckAll = document.createElement('div'); + const labelCheckAll = document.createElement('label'); + labelCheckAll.innerHTML = 'Check All : '; + divCheckAll.appendChild(labelCheckAll); + + const inputCheckAllCheckbox = document.createElement('input'); + inputCheckAllCheckbox.type = 'checkbox'; + inputCheckAllCheckbox.checked = true; + divCheckAll.appendChild(inputCheckAllCheckbox); + html.appendChild(divCheckAll); + + const layers = this.view.getLayers(layer => layer.isGeometryLayer); + const list = document.createElement('div'); + for (let i = 0; i < layers.length; i++) { + const layer = layers[i]; + + const divLayer = document.createElement('div'); + divLayer.innerHTML = layer.id; + + const divLayerVisibility = document.createElement('span'); + const inputLayerVisibilityCheckbox = + document.createElement('input'); + inputLayerVisibilityCheckbox.type = 'checkbox'; + inputLayerVisibilityCheckbox.checked = layer.visible; + + inputLayerVisibilityCheckbox.onclick = (event) => { + layer.visible = event.target.checked; + this.view.notifyChange(); + }; + + const focusButton = this.createFocusButton(layer); + + divLayerVisibility.appendChild(inputLayerVisibilityCheckbox); + divLayerVisibility.appendChild(focusButton); + divLayer.appendChild(divLayerVisibility); + list.appendChild(divLayer); + } + + inputCheckAllCheckbox.onclick = (event) => { + for (const inputCheckbox of list.getElementsByTagName('input')) { + inputCheckbox.checked = event.target.checked; + inputCheckbox.dispatchEvent(new Event('click')); + } + }; + + html.appendChild(list); + + return html; + } + + /** + * It creates a button that, when clicked, will animate the camera to look at the center of the layer's + * extent + * @param {Layer} layer - the layer to focus on + * @returns {HTMLButtonElement} A button that will focus the camera on the layer. + */ + createFocusButton(layer) { + const focusButton = document.createElement('button'); + focusButton.innerHTML = 'Focus'; + const _this = this; + focusButton.addEventListener('click', () => { + const view = _this.view; + const camera = view.camera.camera3D; + + const coord = new Coordinates( + view.referenceCrs, + layer.extent.center(), + ); + + const range = + _this.rangeFocus || + Math.max( + Math.abs(layer.extent.west - layer.extent.east), + Math.abs(layer.extent.north - layer.extent.south), + ); + + const params = { + coord, + range, + tilt: _this.tiltFocus, + }; + + CameraUtils.animateCameraToLookAtTarget(view, camera, params); + }); + return focusButton; + } +} + +export default LayerChoice; diff --git a/src/Utils/gui/Main.js b/src/Utils/gui/Main.js index e05397704b..c777f1191c 100644 --- a/src/Utils/gui/Main.js +++ b/src/Utils/gui/Main.js @@ -3,3 +3,4 @@ export { default as Navigation } from './Navigation'; export { default as Minimap } from './Minimap'; export { default as Scale } from './Scale'; export { default as Searchbar } from './Searchbar'; +export { default as LayerChoice } from './LayerChoice'; From 120066f18f12430aafcbd40d20576a75074ca861 Mon Sep 17 00:00:00 2001 From: Mathieu Livebardon Date: Fri, 17 Mar 2023 09:57:49 +0100 Subject: [PATCH 2/6] revert(gitignore): useless changes --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 109a676ef7..5ebc710462 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ potree /docs/out/ coverage .nyc_output/ -/src/ThreeExtended/ \ No newline at end of file +/src/ThreeExtended/ From bf646a07c2118c52b35eaa84cecb0a26432f36c3 Mon Sep 17 00:00:00 2001 From: Mathieu Livebardon Date: Mon, 20 Mar 2023 15:32:58 +0100 Subject: [PATCH 3/6] fix(widgets): layer_choice extent, tileset --- examples/widgets_layer_choice.html | 110 ++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/examples/widgets_layer_choice.html b/examples/widgets_layer_choice.html index 9ab9c1e943..c25a4a76a8 100644 --- a/examples/widgets_layer_choice.html +++ b/examples/widgets_layer_choice.html @@ -41,10 +41,10 @@ const viewExtent = new itowns.Extent( 'EPSG:3946', - 1837816.94334, - 1847692.32501, - 5170036.4587, - 5178412.82698, + 1837817, + 1847693, + 5169000, + 5181000, ); // Define the camera initial placement @@ -110,13 +110,99 @@ debugMenu.addLayerGUI.bind(debugMenu), ); + + + // Create the 3D Tiles layer + var $3DTilesLayerLyon1 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-1', + { + name: 'BTHierarchy', + source: new itowns.C3DTilesSource({ + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-1_2015/tileset.json', + }) + }, + view, + ); + + // Create the 3D Tiles layer + var $3DTilesLayerLyon2 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-2', + { + name: 'BTHierarchy', + source: new itowns.C3DTilesSource({ + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-2_2015/tileset.json', + }) + }, + view, + ); + + // Create the 3D Tiles layer + var $3DTilesLayerLyon3 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-3', + { + name: 'BTHierarchy', + source: new itowns.C3DTilesSource({ + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-3_2015/tileset.json', + }) + }, + view, + ); + + // Create the 3D Tiles layer + var $3DTilesLayerLyon4 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-4', + { + name: 'BTHierarchy', + source: new itowns.C3DTilesSource({ + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-4_2015/tileset.json', + }) + }, + view, + ); + + // Create the 3D Tiles layer + var $3DTilesLayerLyon5 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-5', + { + name: 'BTHierarchy', + source: new itowns.C3DTilesSource({ + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-5_2015/tileset.json', + }) + }, + view, + ); + + // Create the 3D Tiles layer + var $3DTilesLayerLyon6 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-6', + { + name: 'BTHierarchy', + source: new itowns.C3DTilesSource({ + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-6_2015/tileset.json', + }) + }, + view, + ); + + // Create the 3D Tiles layer + var $3DTilesLayerLyon7 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-7', + { + name: 'BTHierarchy', + source: new itowns.C3DTilesSource({ + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-7_2015/tileset.json', + }) + }, + view, + ); + // Create the 3D Tiles layer - var $3DTilesLayerLyon = new itowns.C3DTilesLayer( - '3d-tiles-lyon', + var $3DTilesLayerLyon8 = new itowns.C3DTilesLayer( + '3d-tiles-lyon-8', { name: 'BTHierarchy', source: new itowns.C3DTilesSource({ - url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/Demo/UD-Demo-vcity-py3dtilers-lyon/Lyon-1_2018_LODS/tileset.json', + url: 'https://dataset-dl.liris.cnrs.fr/three-d-tiles-lyon-metropolis/2015/Lyon-8_2015/tileset.json', }) }, view, @@ -134,7 +220,15 @@ view, ); - itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon); + + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon1); + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon2); + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon3); + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon4); + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon5); + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon6); + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon7); + itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon8); itowns.View.prototype.addLayer.call(view, $3DTilesLayerLyon9); // Add lights in the scene From 19ca0462d6deb063b732bdd481134410fe693340 Mon Sep 17 00:00:00 2001 From: Mathieu Livebardon Date: Mon, 20 Mar 2023 15:55:59 +0100 Subject: [PATCH 4/6] style(layerchoice): chg background/text color --- examples/css/widgets.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/css/widgets.css b/examples/css/widgets.css index 5170643ab9..5f7dd4e823 100644 --- a/examples/css/widgets.css +++ b/examples/css/widgets.css @@ -255,9 +255,18 @@ padding: 0 10px; box-sizing: border-box; + align-items: center; + border: 1px solid #222222; border-radius: 7px; + background-color: #313336bb; + color: #ffffff; + +} + +#widgets-layer-choice h3 { + color: #ffffff; } /* ---------- SCALE WIDGET SETTINGS : ------------------------------------------------------------------------------- */ From 50553ddcc8d69220520de94ccb991325ee44c9f2 Mon Sep 17 00:00:00 2001 From: Mathieu Livebardon Date: Mon, 20 Mar 2023 17:05:57 +0100 Subject: [PATCH 5/6] style(layerchoice):css showhide htmllist of layers --- examples/css/widgets.css | 1 + src/Utils/gui/LayerChoice.js | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/examples/css/widgets.css b/examples/css/widgets.css index 5f7dd4e823..3ebebe02a2 100644 --- a/examples/css/widgets.css +++ b/examples/css/widgets.css @@ -267,6 +267,7 @@ #widgets-layer-choice h3 { color: #ffffff; + cursor: pointer; } /* ---------- SCALE WIDGET SETTINGS : ------------------------------------------------------------------------------- */ diff --git a/src/Utils/gui/LayerChoice.js b/src/Utils/gui/LayerChoice.js index d0944dfafe..0fb58216b7 100644 --- a/src/Utils/gui/LayerChoice.js +++ b/src/Utils/gui/LayerChoice.js @@ -59,8 +59,8 @@ class LayerChoice extends Widget { initContentColorLayers() { const html = document.createElement('div'); const titleColorLayers = document.createElement('h3'); - titleColorLayers.innerHTML = 'Color Layers : '; html.appendChild(titleColorLayers); + const titleString = 'Color Layers'; const list = document.createElement('div'); @@ -102,8 +102,10 @@ class LayerChoice extends Widget { list.appendChild(divLayer); } - html.appendChild(list); + this.showHideListLayers(titleColorLayers, titleString, [list]); // Default Hide + titleColorLayers.onclick = () => { this.showHideListLayers(titleColorLayers, titleString, [list]); }; + html.appendChild(list); return html; } @@ -111,7 +113,7 @@ class LayerChoice extends Widget { initContentElevationLayers() { const html = document.createElement('div'); const titleElevationLayers = document.createElement('h3'); - titleElevationLayers.innerHTML = 'Elevation Layers : '; + const titleString = 'Elevation Layers '; html.appendChild(titleElevationLayers); const list = document.createElement('div'); @@ -125,9 +127,6 @@ class LayerChoice extends Widget { const labelScale = document.createElement('label'); labelScale.innerHTML = ' Scale : '; divLayer.appendChild(labelScale); - const spanScale = document.createElement('span'); - spanScale.innerHTML = layer.scale; - labelScale.appendChild(spanScale); const inputScale = document.createElement('input'); inputScale.type = 'number'; @@ -140,10 +139,12 @@ class LayerChoice extends Widget { inputScale.oninput = (event) => { layer.scale = event.target.valueAsNumber; this.view.notifyChange(); - spanScale.innerHTML = layer.scale; }; list.appendChild(divLayer); } + this.showHideListLayers(titleElevationLayers, titleString, [list]); // Default Hide + titleElevationLayers.onclick = () => { this.showHideListLayers(titleElevationLayers, titleString, [list]); }; + html.appendChild(list); return html; @@ -153,7 +154,7 @@ class LayerChoice extends Widget { initContentGeometryLayers() { const html = document.createElement('div'); const titleGeometryLayers = document.createElement('h3'); - titleGeometryLayers.innerHTML = 'Geometry Layers : '; + const titleString = 'Geometry Layers'; html.appendChild(titleGeometryLayers); const divCheckAll = document.createElement('div'); @@ -200,12 +201,26 @@ class LayerChoice extends Widget { inputCheckbox.dispatchEvent(new Event('click')); } }; + this.showHideListLayers(titleGeometryLayers, titleString, [divCheckAll, list]); // Default Hide + titleGeometryLayers.onclick = () => { this.showHideListLayers(titleGeometryLayers, titleString, [divCheckAll, list]); }; html.appendChild(list); return html; } + showHideListLayers(tiltleHtmlElement, titleString, lists) { + const hideListCharacter = '►'; + const showListCharacter = '▼'; + const showBool = tiltleHtmlElement.innerHTML.includes('►'); + lists.forEach((list) => { + if (showBool) { list.style.display = ''; tiltleHtmlElement.innerHTML = `${titleString} ${showListCharacter}`; } else { + list.style.display = 'none'; + tiltleHtmlElement.innerHTML = `${titleString} ${hideListCharacter}`; + } + }); + } + /** * It creates a button that, when clicked, will animate the camera to look at the center of the layer's * extent From 6cf7d10fbf50f502878155ccb67f12b65475d1cc Mon Sep 17 00:00:00 2001 From: Mathieu Livebardon Date: Mon, 20 Mar 2023 17:10:07 +0100 Subject: [PATCH 6/6] documentation(layerchoice):showHideListLayers --- src/Utils/gui/LayerChoice.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Utils/gui/LayerChoice.js b/src/Utils/gui/LayerChoice.js index 0fb58216b7..05844ee321 100644 --- a/src/Utils/gui/LayerChoice.js +++ b/src/Utils/gui/LayerChoice.js @@ -209,6 +209,13 @@ class LayerChoice extends Widget { return html; } + /** + * It takes a title element, a title string, and a list of elements, and toggles the display of the + * list elements based on the title element's innerHTML + * @param {HTMLElement} tiltleHtmlElement - The HTML element that contains the title of the list. + * @param {string} titleString - The string that will be displayed in the title element. + * @param {Array}lists - an array of HTML elements that you want to show/hide + */ showHideListLayers(tiltleHtmlElement, titleString, lists) { const hideListCharacter = '►'; const showListCharacter = '▼';