Home Reference Source Repository

src/coverage/create.js

import { COVERAGE, DOMAIN } from '../constants.js'
import { checkDomain, checkCoverage } from '../validate.js'
import { subsetByIndex, subsetByValue } from './subset.js'

/**
 * Wraps a Domain into a Coverage object by adding dummy parameter and range data.
 *
 * @param {Domain} domain the Domain object
 * @param {array} [options.gridAxes] The horizontal grid axis names, used for checkerboard pattern.
 * @return {Coverage}
 */
export function fromDomain (domain, options = {}) {
  checkDomain(domain)

  let {gridAxes: [x, y] = ['x', 'y']} = options

  let dummyKey = 'domain'
  let dummyLabel = 'Domain'

  let assumeGrid = domain.axes.has(x) && domain.axes.has(y) &&
    (domain.axes.get(x).values.length > 1 || domain.axes.get(y).values.length > 1)
  let categories
  let categoryEncoding
  const a = 'a'
  const av = 0
  const b = 'b'
  const bv = 1
  if (assumeGrid) {
    categories = [{
      id: a,
      label: {en: 'A'}
    }, {
      id: b,
      label: {en: 'B'}
    }]
    categoryEncoding = new Map([[a, [av]], [b, [bv]]])
  } else {
    categories = [{
      id: a,
      label: {en: 'X'}
    }]
    categoryEncoding = new Map([[a, [av]]])
  }

  let parameters = new Map()
  parameters.set(dummyKey, {
    key: dummyKey,
    observedProperty: {
      label: {en: dummyLabel},
      categories
    },
    categoryEncoding
  })

  let shape = new Map([...domain.axes].map(([name, axis]) => [name, axis.values.length]))

  let get
  if (assumeGrid) {
    // checkerboard pattern to see grid cells
    let isOdd = n => n % 2
    get = ({ x = 0, y = 0 }) => isOdd(x + y) ? av : bv
  } else {
    get = () => av
  }

  let loadRange = () => Promise.resolve({
    shape,
    dataType: 'integer',
    get
  })

  let cov = {
    type: COVERAGE,
    domainType: domain.domainType,
    parameters,
    loadDomain: () => Promise.resolve(domain),
    loadRange
  }
  addLoadRangesFunction(cov)
  addSubsetFunctions(cov)
  return cov
}

/**
 * Creates a Coverage with a single parameter from an xndarray object.
 *
 * @example
 * var arr = xndarray(new Float64Array(
 *   [ 1,2,3,
 *     4,5,6 ]), {
 *   shape: [2,3],
 *   names: ['y','x'],
 *   coords: {
 *     y: [10,12,14],
 *     x: [100,101,102],
 *     t: [new Date('2001-01-01')]
 *   }
 * })
 * var cov = CovUtils.fromXndarray(arr, {
 *   parameter: {
 *     key: 'temperature',
 *     observedProperty: {
 *       label: {en: 'Air temperature'}
 *     },
 *     unit: { symbol: '°C' }
 *   }
 * })
 * let param = cov.parameters.get('temperature')
 * let unit = param.unit.symbol // °C
 * cov.loadRange('temperature').then(temps => {
 *   let val = temps.get({x:0, y:1}) // val == 4
 * })
 *
 * @param {xndarray} xndarr - Coordinates must be primitive, not tuples etc.
 * @param {object} [options] Options object.
 * @param {Parameter} [options.parameter] Specifies the parameter, default parameter has a key of 'p1'.
 * @param {string} [options.domainType] A domain type URI.
 * @param {Array<object>} [options.referencing] Optional referencing system info,
 *   defaults to longitude/latitude in WGS84 for x/y axes and ISO8601 time strings for t axis.
 * @return {Coverage}
 */
export function fromXndarray (xndarr, options = {}) {
  let { parameter = {
    key: 'p1',
    observedProperty: {
      label: {en: 'Parameter 1'}
    }
  }, referencing, domainType} = options

  let parameters = new Map()
  parameters.set(parameter.key, parameter)

  // assume lon/lat/ISO time for x/y/t by default, for convenience
  if (!referencing) {
    referencing = []
    if (xndarr.coords.has('x') && xndarr.coords.has('y')) {
      referencing.push({
        components: ['x', 'y'],
        system: {
          type: 'GeodeticCRS',
          id: 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'
        }
      })
    }
    if (xndarr.coords.has('t')) {
      referencing.push({
        components: ['t'],
        system: {
          type: 'TemporalRS',
          calendar: 'Gregorian'
        }
      })
    }
  }

  let axes = new Map()
  for (let [axisName, vals1Dnd] of xndarr.coords) {
    let values = new Array(vals1Dnd.size)
    for (let i = 0; i < vals1Dnd.size; i++) {
      values[i] = vals1Dnd.get(i)
    }
    axes.set(axisName, {
      key: axisName,
      components: [axisName],
      values
    })
  }

  let domain = {
    type: DOMAIN,
    domainType,
    referencing,
    axes
  }

  let shape = new Map([...domain.axes].map(([name, axis]) => [name, axis.values.length]))
  let dataType = xndarr.dtype.indexOf('int') !== -1 ? 'integer' : 'float'

  let loadRange = () => Promise.resolve({
    shape,
    dataType,
    get: xndarr.xget.bind(xndarr)
  })

  let cov = {
    type: COVERAGE,
    domainType,
    parameters,
    loadDomain: () => Promise.resolve(domain),
    loadRange
  }
  addLoadRangesFunction(cov)
  addSubsetFunctions(cov)
  return cov
}

export function addSubsetFunctions (cov) {
  checkCoverage(cov)
  cov.subsetByIndex = subsetByIndex.bind(null, cov)
  cov.subsetByValue = subsetByValue.bind(null, cov)
}

export function addLoadRangesFunction (cov) {
  checkCoverage(cov)
  function loadRanges (keys) {
    if (!keys) {
      keys = cov.parameters.keys()
    }
    return Promise.all([...keys].map(cov.loadRange)).then(ranges => new Map(keys.map((key, i) => [key, ranges[i]]))
    )
  }
  cov.loadRanges = loadRanges
}