/**
* @module webgis4u/ol/control/ScaleLine
*/
import Control from 'ol/control/Control';
import { createElement } from '../../util/dom/createElement';
import { convertUnitValue } from '../../util/number/convertUnitValue';
import { getScale } from '../util/getScale';
import { CSS_CONTROL_PREFIX } from './common';
import './ScaleLine/ScaleLine.scss';
/**
* Units for the scale line (at this time only internal ).
* @enum {string}
*/
export const ScaleLineUnitEnum = {
METRIC: 'metric',
};
/**
* The root CSS class
* @type {string}
*/
export const CSS_CLASS = `${CSS_CONTROL_PREFIX}-scaleline`;
export const CSS_CLASS_SCALE = `${CSS_CONTROL_PREFIX}-scale`;
export const CSS_CLASS_SCALEBAR = `${CSS_CONTROL_PREFIX}-scalebar`;
/**
* @const
* @type {Array.<number>}
*/
const LEADING_DIGITS = [1, 2, 5];
/**
* Get the units for function `convertUnitValue`
*
* @param {ScaleLineUnitEnum} scaleLineUnit The scale line unit
*
* @returns {undefined|Array<module:webgis4u/util/number/convertUnitValue~Unit>}
*/
function getConvertUnitsForScaleLineUnit(scaleLineUnit) {
switch (scaleLineUnit) {
case ScaleLineUnitEnum.METRIC:
return [
{ abbreviation: 'mm', factor: 0.001 },
{ abbreviation: 'm', fallback: true },
{ abbreviation: 'km', factor: 1000 },
];
default: return undefined;
}
}
/**
*
*/
class ScaleLine extends Control {
/**
* If `true`, the scale is shown
* @type {boolean}
*/
static SHOW_SCALE = true;
/**
* @type {ScaleLineUnitEnum|undefined}
* @private
*/
units_ = ScaleLineUnitEnum.METRIC;
/**
* Stores the canvas
* @type {HTMLCanvasElement|null}
*/
canvas_ = null;
/**
* @type {number}
*/
minWidth_ = 64;
constructor(options) {
const element = createElement({
tag: 'div',
cssClass: `${CSS_CLASS} noUbaTooltip`,
});
const canvas = createElement({
tag: 'canvas',
cssClass: CSS_CLASS_SCALEBAR,
});
element.appendChild(canvas);
super({
element,
...options,
});
this.canvas_ = canvas;
}
/**
* @return {ScaleLineUnitEnum|undefined} The units to use in the scale line.
*/
getUnits() { return this.units_; }
/**
* @param {ScaleLineUnitEnum} value The units to use in the scale line.
*/
setUnits(value) { this.units_ = value; }
/**
* @return {HTMLCanvasElement|null} The canvas element
*/
getCanvas() { return this.canvas_; }
/**
* @inheritdoc
*/
setMap(map) {
super.setMap(map);
// Do nothing else if map is not set
if (!map) { return; }
map.getView().on('propertychange', this.updateElement);
this.updateElement();
}
/**
* Draws the scalbar in the canvas
*
* @param {object} options The draw options
* @param {string} options.unit The unit.
* @param {number} options.length The length.
* @param {number} options.size The size in pixel.
* @param {HTMLCanvasElement} options.canvas The canvas to draw on.
*
* @private
*/
draw(options) {
const { unit, length, size, canvas } = options;
const height = 30;
const fontSize = height / 3;
const sp = 20;
const st = 10;
const sm = 14;
const sb = 18;
const bh = sb - sm;
const tb = height;
const ctx = canvas.getContext('2d');
// first clear for redrawing
ctx.beginPath();
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = size + 2 * sp;
canvas.height = height;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < 2; i++) {
ctx.moveTo(i * size + sp, st);
ctx.lineTo(i * size + sp, sb);
}
ctx.strokeStyle = 'black';
ctx.fillRect(sp, sm, size, bh);
ctx.fillStyle = 'white';
ctx.fillRect(sp, sm, size / 4, bh);
ctx.fillRect(sp + size / 2, sm, size / 4, bh);
ctx.rect(sp, sm, size, bh);
ctx.fillStyle = 'fff';
ctx.stroke();
ctx.textAlign = 'center';
ctx.font = '10px Arial';
ctx.fillText(unit, sp + size / 2, fontSize);
ctx.fillText('0', sp, tb);
ctx.fillText(length / 2, sp + size / 2, tb);
ctx.fillText(length, sp + size, tb);
}
/**
* Toggles the elements visibility
* @param {boolean} isVisible If `true`, the element will be displayed
*
* @private
*/
toggleElement(isVisible) {
const { element } = this;
if (!element) { return; }
element.setAttribute('data-visible', isVisible);
this.renderedVisible_ = isVisible;
}
/**
* Updates the element
*
* @private
*/
updateElement = () => {
const viewState = this.getMap().getView();
if (viewState === null) {
if (this.renderedVisible_) {
this.toggleElement(false);
}
return;
}
const units = this.units_;
const scale = getScale(this.getMap());
const { formated } = scale;
let { resolution } = scale;
const nominalCount = this.minWidth_ * resolution;// pointResolution;
let unit = '';
// Get the converted value
const convertUnits = getConvertUnitsForScaleLineUnit(units);
if (convertUnits) {
const { unit: targetUnit } = convertUnitValue({
value: nominalCount,
units: convertUnits,
});
unit = targetUnit.abbreviation;
resolution /= targetUnit.factor;
}
let i = 3 * Math.floor(
Math.log(this.minWidth_ * resolution) / Math.log(10),
);
let length;
let size;
// Calculate the scale to use
do {
length = LEADING_DIGITS[i % 3]
* (10 ** Math.floor(i / 3));
size = Math.round(length / resolution);
if (Number.isNaN(size)) {
this.toggleElement(false);
return;
}
i += 1;
} while (size < this.minWidth_);
const html = length + unit;
// draw the scalebar
if (this.renderedHTML_ !== html) {
this.element.setAttribute('title', `Ma${String.fromCharCode(223)}stab ${formated}`);
this.updateScale({ size, resolution: formated, showScale: ScaleLine.SHOW_SCALE });
this.renderedHTML_ = html;
}
this.draw({ unit, length, size, canvas: this.canvas_ });
if (!this.renderedVisible_) {
this.toggleElement(true);
}
}
/**
* Gets the scale line as base 64 encoded image.
* @returns The scale line as base 64 encoded image.
*/
export() {
return this.getCanvas().toDataURL('image/png');
}
/**
* Update the scale element
*
* @param {object} options The options
* @param {number} options.size The width.
* @param {string} options.resolution The resolution
* @param {boolean} options.showScale If `true` the resolution will be shown
*/
updateScale(options) {
const scaleElements = document.getElementsByClassName(CSS_CLASS_SCALE);
if (scaleElements.length === 0) { return; }
const { size, resolution, showScale } = options;
const scale = scaleElements[0];
scale.style.left = size + 50;
scale.innerHTML = (!showScale)
? ''
: `M ${resolution}`;
}
}
export default ScaleLine;