index.js

import {
  createLocalStorageCheckbox,
  createLocalStorageDetails,
  createLocalStorageSlider,
  focusC3DTilesLayer,
} from '@ud-viz/utils_browser';

import { PointsMaterial, EventDispatcher } from 'three';

import { PlanarView, ColorLayersOrdering, ImageryLayers } from 'itowns';

/**
 * @typedef {object} LayerParam
 * @property {import("itowns").Layer} layer - itowns.Layer
 * @property {number} defaultPointCloudSize - default size of a pointcloud layer
 * @property {boolean} [isPointCloud=false] - the layer is a point cloud allow to know if a C3DTilesLayer is a pointcloud or not without pulling tileset.json
 */

export class LayerChoice extends EventDispatcher {
  /**
   *
   * @param {PlanarView} view - itowns view
   * @param {Array<LayerParam>} layerParams - layer params to initialization
   */
  constructor(view, layerParams) {
    super();
    /** @type {HTMLElement} */
    this.domElement = document.createElement('div');

    /** @type {HTMLDetailsElement} */
    this.colorLayersDomElement = createLocalStorageDetails(
      'color layers details',
      'Color Layers',
      this.domElement
    );

    /** @type {Array<{container:HTMLElement,idLayer:string}>} */
    this.containerColorLayers = [];

    /** @type {PlanarView} */
    this.view = view;

    layerParams =
      layerParams == undefined
        ? this.view.getLayers().map((el) => {
            return { layer: el };
          })
        : layerParams;

    layerParams.forEach((param) => {
      this.createLayerDomEl(param);
    });
  }

  /**
   *
   * @param {LayerParam} param - param of the layer created
   */
  createLayerDomEl(param) {
    const layerContainerDomElement = param.layer.isColorLayer
      ? createLocalStorageDetails(
          'details' + param.layer.id,
          param.layer.name || param.layer.id,
          this.colorLayersDomElement
        )
      : createLocalStorageDetails(
          'details' + param.layer.id,
          param.layer.name || param.layer.id,
          this.domElement
        );

    if (param.layer.isElevationLayer) {
      // scale
      const scaleInput = createLocalStorageSlider(
        param.layer.id + '_layer_scale',
        'Scale',
        layerContainerDomElement,
        {
          defaultValue: param.layer.scale,
        }
      );

      // sync opcity input with param.layer
      param.layer.scale = scaleInput.valueAsNumber;
      scaleInput.onchange = () => {
        param.layer.scale = scaleInput.valueAsNumber;
        this.view.notifyChange(this.view.camera.camera3D);
      };
    } else {
      // visibility checkbox
      const visibleInput = createLocalStorageCheckbox(
        param.layer.id + '_layer_visibility',
        'Visible',
        layerContainerDomElement,
        param.layer.visible
      );

      // sync visible input with param.layer
      param.layer.visible = visibleInput.checked;
      visibleInput.onchange = () => {
        param.layer.visible = visibleInput.checked;
        this.view.notifyChange(this.view.camera.camera3D);
      };

      if (param.layer.isC3DTilesLayer) {
        const focusButton = document.createElement('button');
        focusButton.innerText = 'Focus';
        layerContainerDomElement.appendChild(focusButton);
        focusButton.onclick = () => {
          focusC3DTilesLayer(this.view, param.layer).then((targetPos) => {
            this.dispatchEvent({
              type: LayerChoice.EVENT.FOCUS_3D_TILES,
              message: { layerFocused: param.layer, targetPos: targetPos },
            });
          });
        };
      }
      if (param.isPointCloud) {
        const pointCloudSize = createLocalStorageSlider(
          param.layer.id + '_layer_pnts_size',
          'Point size',
          layerContainerDomElement,
          {
            step: 0.001,
            max: 5,
            min: 0.001,
            defaultValue: param.defaultPointCloudSize || 0.03,
          }
        );

        const updatePointsSize = () => {
          // replace in current pnts
          param.layer.object3d.traverse((child) => {
            if (!child.material) return;
            if (child.material instanceof PointsMaterial) {
              child.material.size = pointCloudSize.valueAsNumber;
            }
          });

          this.view.notifyChange(this.view.camera.camera3D);
        };

        updatePointsSize();

        // change point cloud size
        pointCloudSize.oninput = updatePointsSize;
      } else {
        // opacity checkbox
        const opacityInput = createLocalStorageSlider(
          param.layer.id + '_layer_opacity',
          'Opacité',
          layerContainerDomElement,
          param.layer.opacity
        );

        // sync opcity input with param.layer
        param.layer.opacity = opacityInput.valueAsNumber;
        opacityInput.onchange = () => {
          param.layer.opacity = opacityInput.valueAsNumber;
          this.view.notifyChange(this.view.camera.camera3D);
        };

        // order
        if (param.layer.isColorLayer) {
          const buttonDown = document.createElement('button');
          buttonDown.innerText = 'v';
          layerContainerDomElement.appendChild(buttonDown);

          const buttonUp = document.createElement('button');
          buttonUp.innerText = '^';
          layerContainerDomElement.appendChild(buttonUp);

          this.containerColorLayers.push({
            container: layerContainerDomElement,
            idLayer: param.layer.id,
          });

          const updateColorLayerContainers = () => {
            const sequence = ImageryLayers.getColorLayersIdOrderedBySequence(
              this.view.getLayers().filter((el) => el.isColorLayer)
            );

            this.containerColorLayers.sort((a, b) => {
              const aIndexSequence = sequence.indexOf(a.idLayer);
              const bIndexSequence = sequence.indexOf(b.idLayer);
              return bIndexSequence - aIndexSequence;
            });

            // update html
            this.containerColorLayers.forEach((el) => {
              el.container.remove();
            });

            this.containerColorLayers.forEach((el) => {
              this.colorLayersDomElement.appendChild(el.container);
            });
          };
          updateColorLayerContainers();

          buttonDown.onclick = () => {
            ColorLayersOrdering.moveLayerDown(this.view, param.layer.id);
            updateColorLayerContainers();
          };
          buttonUp.onclick = () => {
            ColorLayersOrdering.moveLayerUp(this.view, param.layer.id);
            updateColorLayerContainers();
          };
        }
      }
    }

    if (!param.layer.isTiledGeometryLayer) {
      // remove button
      const removeButton = document.createElement('button');
      removeButton.innerText = 'Remove';
      layerContainerDomElement.appendChild(removeButton);
      removeButton.onclick = () => {
        this.view.removeLayer(param.layer.id, true);
        layerContainerDomElement.remove();
      };
    }

    this.view.notifyChange(this.view.camera.camera3D);
  }
}

LayerChoice.EVENT = {
  FOCUS_3D_TILES: 'focus_3d_tiles',
};