maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
153 lines (132 loc) • 4.57 kB
text/typescript
import {DOM} from '../../util/dom';
import type {Map} from '../map';
import type {ControlPosition, IControl} from './control';
/**
* The unit type for length to use for the {@link ScaleControl}
*/
export type Unit = 'imperial' | 'metric' | 'nautical';
/**
* The {@link ScaleControl} options object
*/
type ScaleControlOptions = {
/**
* The maximum length of the scale control in pixels.
* @defaultValue 100
*/
maxWidth?: number;
/**
* Unit of the distance (`'imperial'`, `'metric'` or `'nautical'`).
* @defaultValue 'metric'
*/
unit?: Unit;
};
const defaultOptions: ScaleControlOptions = {
maxWidth: 100,
unit: 'metric'
};
/**
* A `ScaleControl` control displays the ratio of a distance on the map to the corresponding distance on the ground.
*
* @group Markers and Controls
*
* @example
* ```ts
* let scale = new ScaleControl({
* maxWidth: 80,
* unit: 'imperial'
* });
* map.addControl(scale);
*
* scale.setUnit('metric');
* ```
*/
export class ScaleControl implements IControl {
_map: Map;
_container: HTMLElement;
options: ScaleControlOptions;
/**
* @param options - the control's options
*/
constructor(options?: ScaleControlOptions) {
this.options = {...defaultOptions, ...options};
}
getDefaultPosition(): ControlPosition {
return 'bottom-left';
}
_onMove = () => {
updateScale(this._map, this._container, this.options);
};
/** {@inheritDoc IControl.onAdd} */
onAdd(map: Map) {
this._map = map;
this._container = DOM.create('div', 'maplibregl-ctrl maplibregl-ctrl-scale', map.getContainer());
this._map.on('move', this._onMove);
this._onMove();
return this._container;
}
/** {@inheritDoc IControl.onRemove} */
onRemove() {
DOM.remove(this._container);
this._map.off('move', this._onMove);
this._map = undefined;
}
/**
* Set the scale's unit of the distance
*
* @param unit - Unit of the distance (`'imperial'`, `'metric'` or `'nautical'`).
*/
setUnit = (unit: Unit) => {
this.options.unit = unit;
updateScale(this._map, this._container, this.options);
};
}
function updateScale(map, container, options) {
// A horizontal scale is imagined to be present at center of the map
// container with maximum length (Default) as 100px.
// Using spherical law of cosines approximation, the real distance is
// found between the two coordinates.
const maxWidth = options && options.maxWidth || 100;
const y = map._container.clientHeight / 2;
const left = map.unproject([0, y]);
const right = map.unproject([maxWidth, y]);
const maxMeters = left.distanceTo(right);
// The real distance corresponding to 100px scale length is rounded off to
// near pretty number and the scale length for the same is found out.
// Default unit of the scale is based on User's locale.
if (options && options.unit === 'imperial') {
const maxFeet = 3.2808 * maxMeters;
if (maxFeet > 5280) {
const maxMiles = maxFeet / 5280;
setScale(container, maxWidth, maxMiles, map._getUIString('ScaleControl.Miles'));
} else {
setScale(container, maxWidth, maxFeet, map._getUIString('ScaleControl.Feet'));
}
} else if (options && options.unit === 'nautical') {
const maxNauticals = maxMeters / 1852;
setScale(container, maxWidth, maxNauticals, map._getUIString('ScaleControl.NauticalMiles'));
} else if (maxMeters >= 1000) {
setScale(container, maxWidth, maxMeters / 1000, map._getUIString('ScaleControl.Kilometers'));
} else {
setScale(container, maxWidth, maxMeters, map._getUIString('ScaleControl.Meters'));
}
}
function setScale(container, maxWidth, maxDistance, unit) {
const distance = getRoundNum(maxDistance);
const ratio = distance / maxDistance;
container.style.width = `${maxWidth * ratio}px`;
container.innerHTML = `${distance} ${unit}`;
}
function getDecimalRoundNum(d) {
const multiplier = Math.pow(10, Math.ceil(-Math.log(d) / Math.LN10));
return Math.round(d * multiplier) / multiplier;
}
function getRoundNum(num) {
const pow10 = Math.pow(10, (`${Math.floor(num)}`).length - 1);
let d = num / pow10;
d = d >= 10 ? 10 :
d >= 5 ? 5 :
d >= 3 ? 3 :
d >= 2 ? 2 :
d >= 1 ? 1 : getDecimalRoundNum(d);
return pow10 * d;
}