itownsUtil.js

// Everything there should be contributed at some point in itowns

const THREE = require('three');
const itowns = require('itowns');

/** CAMERA */

/**
 * Compute a distance automatically for travel features of itowns Planar View
 *
 * Note: Code retrieve itowns - {@link https://github.com/iTowns/itowns/blob/1bad0d627764c73b606179c636ad7b70105dd633/src/Controls/PlanarControls.js#L712}
 *
 * @param {itowns.PlanarControls} controls - planar controls of itownsView
 * @param {THREE.Vector3} targetPos - position of our target
 * @returns {number} the duration of travel to focus a targetPosition
 */
function autoDurationTravel(controls, targetPos) {
  const normalizedDistance = Math.min(
    1,
    targetPos.distanceTo(controls.camera.position) / controls.autoTravelTimeDist
  );

  return THREE.MathUtils.lerp(
    controls.autoTravelTimeMin,
    controls.autoTravelTimeMax,
    normalizedDistance
  );
}

/**
 * Makes the camera move to focus on the target position.
 *
 * @param {itowns.PlanarView} view The iTowns view.
 * @param {itowns.PlanarControls} controls The camera controls.
 * @param {THREE.Vector3} targetPos The target position.
 * @param {*} [options] Optional parameters for the travel. Accepted entries
 * are :
 * - `duration` : the duration of the movement, in seconds. The promise will
 * resolve after this value. If not specified, the value `auto` is used for
 * the movement (see the `PlanarControls.initateTravel` method), and the promise
 * resolves imediatly.
 * - `verticalDistance` : Desired height of the camera relative to the target
 * position.
 * - `horizontalDistance` : Desired distance of the camera from the target
 * position.
 * @returns {Promise} Promise of the camera focusing on target
 * @todo this function is used by widget should be contributed or removed
 */
export function focusCameraOn(view, controls, targetPos, options = {}) {
  return new Promise((resolve, reject) => {
    try {
      const duration = options.duration || null;
      const verticalDist = options.verticalDistance || 800;
      const horizontalDist = options.horizontalDistance || 1000;

      const cameraPos = view.camera.camera3D.position.clone();
      const direction = new THREE.Vector3().subVectors(targetPos, cameraPos);
      const currentDist = Math.sqrt(
        direction.x * direction.x + direction.y * direction.y
      );
      cameraPos.addScaledVector(direction, 1 - horizontalDist / currentDist);
      cameraPos.z = targetPos.z + verticalDist;

      const noControls = !view.controls;

      if (noControls) controls = new itowns.PlanarControls(view);

      const travelDuration =
        duration || duration == 0
          ? duration
          : autoDurationTravel(controls, targetPos);

      const timeoutDuration = travelDuration * 1000;
      controls.initiateTravel(cameraPos, travelDuration, targetPos, true);

      setTimeout(() => {
        if (noControls) {
          view.controls.dispose();
          view.controls = undefined;
        }
        resolve(targetPos);
      }, timeoutDuration);
    } catch (e) {
      reject(e);
    }
  });
}

/**
 * Focus a C3DTiles Layer
 *
 * @param {itowns.PlanarView} itownsView - view
 * @param {itowns.C3DTilesLayer} layer - layer to focus
 * @returns {Promise | null} Promise of the camera focusing on C3DTiles
 * @todo this function is used by widget should be contributed or removed
 */
export function focusC3DTilesLayer(itownsView, layer) {
  if (!layer.isC3DTilesLayer) return null;

  const coordinates = itownsView.camera.position();
  const extent = layer.extent;
  coordinates.x = (extent.east + extent.west) / 2;
  coordinates.y = (extent.north + extent.south) / 2;
  coordinates.z = 200;
  if (layer.tileset.tiles[0])
    coordinates.z = layer.tileset.tiles[0].boundingVolume.box.max.z;

  return focusCameraOn(
    itownsView,
    itownsView.controls,
    new THREE.Vector3().copy(coordinates),
    {
      verticalDistance: 200,
      horizontalDistance: 200,
    }
  );
}

/**
 * fetchC3DTileFeatureWithNodeText takes a parameter `batchTableKey` and returns a feature from a `3DTileslayer` if
 * the batch table content of the feature contains a given `batchTableValue` string in the given key. Returns an object
 * containing the first matching feature and its layer.
 *
 * @param {itowns.PlanarView} itownsView - view
 * @param {string} batchTableKey a given batch table key
 * @param {string} batchTableValue a given batch table value
 * @returns {object} containting the feature and the layer containing the feature
 */
export function fetchC3DTileFeatureWithNodeText(
  itownsView,
  batchTableKey,
  batchTableValue
) {
  let result = null;
  itownsView
    .getLayers()
    .filter((el) => el.isC3DTilesLayer)
    .forEach((c3DTilesLayer) => {
      for (const [
        // eslint-disable-next-line no-unused-vars
        tileId,
        tileC3DTileFeatures,
      ] of c3DTilesLayer.tilesC3DTileFeatures) {
        // eslint-disable-next-line no-unused-vars
        for (const [batchId, c3DTileFeature] of tileC3DTileFeatures) {
          if (
            c3DTileFeature.getInfo().batchTable[batchTableKey] ==
            batchTableValue
          ) {
            result = {
              feature: c3DTileFeature,
              layer: c3DTilesLayer,
            };
            break;
          }
        }
      }
    });

  return result;
}