leaflet
Version:
JavaScript library for mobile-friendly interactive maps
157 lines (128 loc) • 4.01 kB
JavaScript
import {Control} from './Control.js';
import * as DomUtil from '../dom/DomUtil.js';
/** @import {ControlOptions, Defaults, ScaleControlDefaults, ScaleControlOptions} from '../types.js' */
/** @import {LeafletMap} from '../map/Map.js' */
/** @satisfies {Defaults<ControlOptions>} */
const defaultOptions = {
/**
* The position of the control (one of the map corners). Possible values
* are `'topleft'`, `'topright'`, `'bottomleft'` or `'bottomright'`
*/
position: 'bottomleft',
/**
* Maximum width of the control in pixels. The width is set
* dynamically to show round values (e.g. 100, 200, 500).
*/
maxWidth: 100,
/** Whether to show the metric scale line (m/km). */
metric: true,
/** Whether to show the imperial scale line (mi/ft). */
imperial: true,
/**
* If `true`, the control is updated on [`moveend`](#map-moveend), otherwise
* it's always up-to-date (updated on [`move`](#map-move)).
*/
updateWhenIdle: false
};
/** @typedef {typeof defaultOptions} ScaleControlDefaultsValues */
/**
* A simple scale control that shows the scale of the current center of screen
* in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
* @example
* ```js
* new ScaleControl().addTo(map);
* ```
* @template {ScaleControlOptions} [O=ScaleControlOptions]
* @extends {Control<O & ScaleControlDefaults>}
*/
export class ScaleControl extends Control {
/** @type {Defaults<ScaleControlOptions>} */
static defaultOptions = defaultOptions;
/**
* @param {LeafletMap} map
* @returns {HTMLElement}
*/
onAdd(map) {
const className = 'leaflet-control-scale',
container = DomUtil.create('div', className),
options = this.options;
this._addScales(options, `${className}-line`, container);
map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
map.whenReady(this._update, this);
return container;
}
/** @param {LeafletMap} map */
onRemove(map) {
map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
}
/**
* @param {ScaleControlOptions} options
* @param {string} className
* @param {HTMLElement} container
*/
_addScales(options, className, container) {
if (options.metric) {
/** @type {HTMLElement} */
this._mScale = DomUtil.create('div', className, container);
}
if (options.imperial) {
/** @type {HTMLElement} */
this._iScale = DomUtil.create('div', className, container);
}
}
_update() {
const map = this._map,
y = map.getSize().y / 2;
const maxMeters = map.distance(
map.containerPointToLatLng([0, y]),
map.containerPointToLatLng([this.options.maxWidth, y]));
this._updateScales(maxMeters);
}
/** @param {number} maxMeters */
_updateScales(maxMeters) {
if (this.options.metric && maxMeters) {
this._updateMetric(maxMeters);
}
if (this.options.imperial && maxMeters) {
this._updateImperial(maxMeters);
}
}
/** @param {number} maxMeters */
_updateMetric(maxMeters) {
const meters = this._getRoundNum(maxMeters),
label = meters < 1000 ? `${meters} m` : `${meters / 1000} km`;
this._updateScale(this._mScale, label, meters / maxMeters);
}
/** @param {number} maxMeters */
_updateImperial(maxMeters) {
const maxFeet = maxMeters * 3.2808399;
let maxMiles, miles, feet;
if (maxFeet > 5280) {
maxMiles = maxFeet / 5280;
miles = this._getRoundNum(maxMiles);
this._updateScale(this._iScale, `${miles} mi`, miles / maxMiles);
} else {
feet = this._getRoundNum(maxFeet);
this._updateScale(this._iScale, `${feet} ft`, feet / maxFeet);
}
}
/**
* @param {HTMLElement} scale
* @param {string} text
* @param {number} ratio
*/
_updateScale(scale, text, ratio) {
scale.style.width = `${Math.round(this.options.maxWidth * ratio)}px`;
scale.innerHTML = text;
}
/** @param {number} num */
_getRoundNum(num) {
const pow10 = 10 ** ((`${Math.floor(num)}`).length - 1);
let d = num / pow10;
d = d >= 10 ? 10 :
d >= 5 ? 5 :
d >= 3 ? 3 :
d >= 2 ? 2 : 1;
return pow10 * d;
}
}