pacificclimate/climate-explorer-frontend

View on GitHub
src/core/leaflet-ncwms-colorbar.js

Summary

Maintainability
F
4 days
Test Coverage
/*
 * Requires global Leaflet variable `L`
 * Compatible with any ncWMS server which fulfills `minmax` and
 * `layerDetails` `GetMetadata` requests.
*/
import axios from 'axios';
import {getVariableOptions, PRECISION} from './util';
import L from 'leaflet';

var round = function (number, places) {
  return Math.round(number * Math.pow(10, places)) / Math.pow(10, places);
};

var ncWMSColorbarControl = L.Control.extend({
  options: {
    position: 'bottomright',
    decimalPlaces: 2,
    width: 20,
    height: 300,
    borderWidth: 2,
    borderStyle: 'solid',
    borderRadius: 10,
    opacity: 0.75,
    color: '#424242',
  },

  initialize: function (layer, options) {
    this.layer = layer;
    options.decimalPlaces = this.getDecimalPrecision(layer);
    L.Util.setOptions(this, options);
  },

  onAdd: function () {
    // Container element
    this.container = L.DomUtil.create('div', 'leaflet-control');

    Object.assign(this.container.style, {
      position: 'relative',
      width: this.options.width + 'px',
      height: this.options.height + 'px',
      borderWidth: this.options.borderWidth + 'px',
      borderStyle: this.options.borderStyle,
      borderRadius: this.options.borderRadius + 'px',
      opacity: this.options.opacity,
      color: this.options.color,
      fontWeight: 'bold',
      textShadow: '0 0 0.2em white, 0 0 0.2em white, 0 0 0.2em white',
      whiteSpace: 'nowrap',
    });

    // Set up event handling
    L.DomEvent
      .addListener(this.container, 'click', L.DomEvent.stopPropagation)
      .addListener(this.container, 'click', L.DomEvent.preventDefault);
    this.layer.on('loading', function () {
      this.refreshValues();
    }.bind(this));

    // Create and style labels
    var applyLabelStyle = function (el) {
      el.style.position = 'absolute';
      el.style.right = this.options.width + 'px';
    }.bind(this);

    this.maxContainer = L.DomUtil.create('div', '', this.container);
    applyLabelStyle(this.maxContainer);
    this.maxContainer.style.top = '-0.5em';
    this.maxContainer.innerHTML = 'max';

    this.midContainer = L.DomUtil.create('div', '', this.container);
    applyLabelStyle(this.midContainer);
    this.midContainer.style.top = '50%';
    this.midContainer.innerHTML = 'mid';

    this.minContainer = L.DomUtil.create('div', '', this.container);
    applyLabelStyle(this.minContainer);
    this.minContainer.style.bottom = '-0.5em';
    this.minContainer.innerHTML = 'min';

    this.refreshValues();
    return this.container;
  },

  refreshValues: function () {
    /*
     * Source new values from the ncWMS server. Possible future breakage due to
     * using layer._url and layer._map.
     */

    if (this.layer.wmsParams.colorscalerange) {
      // Use colorscalerange if defined on the layer
      this.min = +this.layer.wmsParams.colorscalerange.split(',')[0];
      this.max = +this.layer.wmsParams.colorscalerange.split(',')[1];
      this.redraw();
    } else {
      // Get layer bounds from `layerDetails`
      var getLayerInfo = axios(this.layer._url, {
        dataType: 'json',
        params: {
          request: 'GetMetadata',
          item: 'layerDetails',
          layerName: this.layer.wmsParams.layers,
          time: this.layer.wmsParams.time,
        },
      });

      var getMinMax = layerInfo => {
        var bbox = layerInfo.data.bbox;
        if(bbox[0] == bbox[2] || bbox[1] == bbox[3]) {
          //This netcdf file does not have a valid bounding box, or ncWMS
          //cannot generate a valid bounding box for it. This is likely due to
          //processing by a latitude normalization script.
          //See https://github.com/pacificclimate/climate-explorer-data-prep/issues/11
          //In this case, longitudes in the file run from 0 to 180, then from -180
          //to zero, which results in the eastmost and westmost points of the file
          //both being 0, giving the entire file a null bounding box.
          //Supply a Canada-centered bounding box, ignoring the worldwide extent
          //of this file.
          bbox = [-150, 40, -50, 90];
        }
        return axios(this.layer._url, {
          params: {
            request: 'GetMetadata',
            item: 'minmax',
            layers: escape(this.layer.wmsParams.layers),
            styles: 'default-scalar',
            version: '1.1.1',
            bbox: bbox.join(),
            srs: this.layer.wmsParams.srs,
            crs: this.layer.wmsParams.srs,
            time: this.layer.wmsParams.time,
            elevation: 0,
            width: 100,
            height: 100,
          },
        });
      };

      getLayerInfo.then(getMinMax).then(response => {
        this.min = response.data.min;
        this.max = response.data.max;
        this.redraw();
      });
    }
  },

  getMidpoint: function (mn, mx, logscale) {
    var mid;

    if (logscale === true || logscale === 'true') {
      var logMin = mn <= 0 ? 1 : mn;
      mid = Math.exp(((Math.log(mx) - Math.log(logMin)) / 2) + Math.log(logMin));
    } else {
      mid = (mn + mx) / 2;
    }
    return mid;
  },

  graphicUrl: function () {
    var palette = this.layer.wmsParams.styles.split('/')[1];
    return this.layer._url + '?REQUEST=GetLegendGraphic' +
      '&COLORBARONLY=true' +
      '&WIDTH=1' +
      '&HEIGHT=' + this.options.height +
      '&PALETTE=' + palette +
      '&NUMCOLORBANDS=' + this.layer.wmsParams.numcolorbands;
  },

  //uses the variable defined in the layer and the variable-options.yaml
  //config file to determine decimal precision. Defaults to util.PRECISION
  getDecimalPrecision: function (layer = this.layer) {
    var places = PRECISION;
    var variableName = layer.wmsParams.layers.split("/")[1];

    if (getVariableOptions(variableName, "decimalPrecision") !== undefined) {
      places = getVariableOptions(variableName, "decimalPrecision");
    }
    return places;
  },

  redraw: function () {
    this.container.style.backgroundImage = 'url("' + this.graphicUrl() + '")';
    this.maxContainer.innerHTML = round(this.max, this.getDecimalPrecision());
    this.midContainer.innerHTML = round(this.getMidpoint(this.min, this.max, this.layer.wmsParams.logscale), this.getDecimalPrecision());
    this.minContainer.innerHTML = round(this.min, this.getDecimalPrecision());
  },
});

export default ncWMSColorbarControl;