-
-
Notifications
You must be signed in to change notification settings - Fork 643
/
scale_control.ts
149 lines (129 loc) · 4.4 KB
/
scale_control.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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;
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;
}