Home Reference Source Repository

src/controls/VerticalAxis.js

import L from 'leaflet'

import {Dropdown} from './Dropdown.js'
import {EventMixin} from '../util/EventMixin.js'
import {getLanguageString, stringifyUnit} from 'covutils'

/**
 * The `change` event, signalling that a different vertical coordinate value has been selected.
 * 
 * @typedef {L.Event} VerticalAxis#change
 * @property {number} vertical The vertical coordinate value that has been selected.
 */

/**
 * Displays a simple vertical coordinate dropdown selector for a coverage data layer.
 * 
 * @example <caption>Coverage data layer</caption>
 * new C.VerticalAxis(covLayer).addTo(map)
 * // Selecting a vertical coordinate automatically sets the 'vertical' property in the layer.
 * // Similarly, when the layer fires an 'axisChange' event with {axis: 'vertical'}
 * // the control reflects that change.
 * 
 * @example <caption>Fake layer</caption>
 * var heights = [0,10,20,50,100,500,1000]
 * var fakeLayer = {
 *   verticalSlices: heights,
 *   vertical: heights[1], // select the second height step initially
 *   crsVerticalAxis: {
 *     name: { 
 *       en: 'Gravity-related height'
 *     },
 *     unit: {
 *       symbol: 'm'
 *     }
 *   }
 * }
 * var verticalAxis = new C.VerticalAxis(fakeLayer).addTo(map)
 * 
 * // change the height and trigger a manual update
 * fakeLayer.vertical = heights[0]
 * verticalAxis.update()
 * 
 * @extends {L.Layer}
 * 
 * @emits {VerticalAxis#change} when a different entry has been selected
 */
export class VerticalAxis extends L.Layer {
  
  /**
   * Creates a vertical axis control.
   * 
   * @param {object} covLayer 
   *   The coverage data layer, or any object with `verticalSlices`
   *   and `vertical` properties, optionally `crsVerticalAxis` property.
   *   If the object has `on`/`off` methods, then the control will
   *   listen for `axisChange` events with `{axis: 'vertical'}`
   *   and update itself automatically.
   *   If the layer fires a `remove` event, then the control will remove itself
   *   from the map.
   * @param {object} [options] Control options.
   * @param {string} [options.position='topleft'] The initial position of the control (see Leaflet docs).
   * @param {string} [options.title='Vertical'] 
   *   The label to show above the control if `covLayer.crsVerticalAxis.name` is missing.
   * @param {string} [options.templateId] Element ID of an alternative HTML `<template>` element to use. 
   */
  constructor (covLayer, options = {}) {
    super()
    this._templateId = options.templateId
    this._covLayer = covLayer
    this._title = options.title || 'Vertical'
    this._position = options.position || 'topleft'

    if (covLayer.on) {
      this._remove = () => this.remove()
      covLayer.on('remove', this._remove)
      
      this._axisListener = e => {
        if (e.axis === 'vertical') this.update()
      }
    }
  }
  
  /**
   * @ignore
   * @override
   */
  onAdd (map) {
    this._map = map
    
    if (this._covLayer.on) {
      this._covLayer.on('axisChange', this._axisListener)
    }
    
    let crsVertAxis = this._covLayer.crsVerticalAxis || {}
    let title = crsVertAxis.name ? getLanguageString(crsVertAxis.name) : this._title
    let unit = stringifyUnit(crsVertAxis.unit)
    if (unit) {
      unit = ' ' + unit
    }
    
    let choices = []
    let vals = this._covLayer.verticalSlices
    for (let i=0; i < vals.length; i++) {
      choices.push({
        value: i.toString(),
        label: vals[i] + unit
      })
    }
      
    this._dropdown = new Dropdown(choices, {
      templateId: this._templateId,
      position: this._position,
      title,
      value: this._getVerticalIndex().toString()
    }).on('change', event => {
      let i = parseInt(event.value)
      let val = this._covLayer.verticalSlices[i]
      this._covLayer.vertical = val
      this.fire('change', {vertical: val})
    }).addTo(map)
  }
  
  /**
   * @ignore
   * @override
   */
  onRemove (map) {
    this._dropdown.remove()
    if (this._covLayer.off) {
      this._covLayer.off('remove', this._remove)
      this._covLayer.off('axisChange', this._axisListener)
    }
  }
    
  _getVerticalIndex () {
    let vals = this._covLayer.verticalSlices
    let i = vals.indexOf(this._covLayer.vertical)
    return i
  }
  
  /**
   * Triggers a manual update of the vertical axis control based on the
   * `vertical` property of the layer.
   * 
   * Useful if the supplied coverage data layer is not a real layer
   * and won't fire the necessary events for automatic updates.
   */
  update () {
    let i = this._getVerticalIndex()
    this._dropdown.value = i.toString()
  }
    
}