Source: custom-plugins/plugin-footprint.js

/** @module */

import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import Feature from 'ol/Feature'
import Polygon from 'ol/geom/Polygon'
import Point from 'ol/geom/Point'
import Style from 'ol/style/Style'
import Icon from 'ol/style/Icon'
import Stroke from 'ol/style/Stroke'
import { image2world } from '@dataforsyningen/saul'
import { configuration } from "../modules/configuration"
import { state } from '../state/index.js'

const camera_styles = {
  'nadir': new Style(),
  'north': createCameraStyle('north'),
  'south': createCameraStyle('south'),
  'east': createCameraStyle('east'),
  'west': createCameraStyle('west')
}

/**
 * Creates an Openlayers layer with a footprint bounding box and camera illustrating the direction.
 * @returns An Openlayers layer with a footprint bounding box and camera illustrating the direction.
 */
function generateFootprintLayer() {
  const source = new VectorSource({
    features: [
      new Feature({ name: 'point', geometry: new Point([-9999, -9999]) }),
      new Feature({ name: 'polygon' })
    ]
  })
  const colorSetting = configuration.COLOR_SETTINGS.footprintColor
  const style = new Style({
    stroke: new Stroke({
      color: colorSetting, // --mork-tyrkis
      width: 2
    }),
    zIndex: Infinity
  })
  const layer = new VectorLayer({
    source: source,
    style: style,
    title: 'Footprint'
  })
  layer.setZIndex(9)
  return layer
}

/**
 * Creates a camera style for the given orientation.
 * @param {String} orientation The orientation.
 * @returns {ol.Style} The resulting style.
 */
function createCameraStyle(orientation) {
  return new Style({
    image: new Icon({
      opacity: 1,
      src: `img/icons/icon_${ orientation }_camera.svg`,
      scale: 1
    })
  })
}

/**
 * Calculate the position of the camera object belonging to the bbox based on the orientation.
 * The camera is placed between the two points nearest the camera at a distance equal to the
 * distance between the same two points.
 * @param {Number[][]} bbox The bounding box.
 * @returns {Number[]} the position of the camera object.
 */
function calculateCameraPosition(bbox) {
  const l = bbox[0][0]
  const r = bbox[0][1]
  const lr = [r[0] - l[0], r[1] - l[1]] // lr vector
  const rlr = [lr[1], 0 - lr[0]] // lr vector rotated 90 degrees clockwise
  const hlr = [lr[0] / 2, lr[1] / 2] // half length lr vector
  const camera_position = [l[0] + (rlr[0] / 2) + hlr[0], l[1] + (rlr[1] / 2) + hlr[1]]
  return camera_position
}

/**
 * Updates the footprint for the map.
 * @param {ol.Map} map The map to update. The map needs to previously have added a footprint layer generated using
 * the generateFootprintLayer function.
 * @param {ol.Extent} bounding_box The bounding box of the footprint in map coordinates.
 * @param {String} orientation The orientation of the footprint.
 */
function updateFootprint(map, bounding_box, orientation) {
  const layer = map.getLayers().getArray().find((pLayer) => {
    return pLayer.get('title') === 'Footprint'
  })
  const source = layer.getSource()
  // TODO: avoid rerendering if nothing changed
  const features = source.getFeatures()
  const polygon = features.find(feature => {
    return feature.get('name') === 'polygon'
  })
  const point = features.find(feature => {
    return feature.get('name') === 'point'
  })
  polygon.setGeometry(new Polygon(bounding_box))
  point.getGeometry().setCoordinates(calculateCameraPosition(bounding_box, orientation))
  point.setStyle(camera_styles[orientation])
}

/**
 * Adds an eventlistener to the viewport for updating the footprint.
 * @param {Object} viewport The viewport.
 * @param {Object} item Image item displayed by the viewport
 */
function footprintHandler(event, item) {
  const bbox_image = event.map.getView().calculateExtent(event.map.getSize())
  const bl = image2world(item, bbox_image[0], bbox_image[1], state.view.kote).slice(0, -1)
  const br = image2world(item, bbox_image[2], bbox_image[1], state.view.kote).slice(0, -1)
  const tl = image2world(item, bbox_image[0], bbox_image[3], state.view.kote).slice(0, -1)
  const tr = image2world(item, bbox_image[2], bbox_image[3], state.view.kote).slice(0, -1)
  const bbox = [[ bl, br, tr, tl, bl]]
  window.dispatchEvent(new CustomEvent("updateFootprint", {
    detail: { bounding_box: bbox, orientation: item.properties.direction }
  }))
}

/**
 * Adds a footprintLayer to a map.
 * @param {ol.Map} map The map.
 */
function addFootprintLayerToMap(map) {
  map.addLayer(generateFootprintLayer())
}

/**
 * Gets a function for updating the map footprint.
 * @param {ol.Map} map The map.
 * @returns {function} The map footprint update function.
 */
function getUpdateMapFootprintFunction(map) {
  return event => {
    updateFootprint(map, event.detail.bounding_box, event.detail.orientation)
  }
}

export {
  footprintHandler,
  addFootprintLayerToMap,
  getUpdateMapFootprintFunction
}