-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
scale_control.js
145 lines (122 loc) · 4.36 KB
/
scale_control.js
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
// @flow
import DOM from '../../util/dom.js';
import {extend, bindAll} from '../../util/util.js';
import type Map from '../map.js';
type Unit = 'imperial' | 'metric' | 'nautical';
type Options = {
maxWidth?: number,
unit?: Unit;
};
const defaultOptions: Options = {
maxWidth: 100,
unit: 'metric'
};
/**
* A `ScaleControl` control displays the ratio of a distance on the map to the corresponding distance on the ground.
* Add this control to a map using {@link Map#addControl}.
*
* @implements {IControl}
* @param {Object} [options]
* @param {number} [options.maxWidth='100'] The maximum length of the scale control in pixels.
* @param {string} [options.unit='metric'] Unit of the distance (`'imperial'`, `'metric'` or `'nautical'`).
* @example
* const scale = new mapboxgl.ScaleControl({
* maxWidth: 80,
* unit: 'imperial'
* });
* map.addControl(scale);
*
* scale.setUnit('metric');
*/
class ScaleControl {
_map: Map;
_container: HTMLElement;
options: Options;
constructor(options: Options) {
this.options = extend({}, defaultOptions, options);
bindAll([
'_onMove',
'setUnit'
], this);
}
getDefaultPosition() {
return 'bottom-left';
}
_onMove() {
updateScale(this._map, this._container, this.options);
}
onAdd(map: Map) {
this._map = map;
this._container = DOM.create('div', 'mapboxgl-ctrl mapboxgl-ctrl-scale', map.getContainer());
this._map.on('move', this._onMove);
this._onMove();
return this._container;
}
onRemove() {
DOM.remove(this._container);
this._map.off('move', this._onMove);
this._map = (undefined: any);
}
/**
* Set the scale's unit of the distance.
*
* @param {'imperial' | 'metric' | 'nautical'} unit Unit of the distance (`'imperial'`, `'metric'` or `'nautical'`).
*/
setUnit(unit: Unit) {
this.options.unit = unit;
updateScale(this._map, this._container, this.options);
}
}
export default ScaleControl;
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'), map);
} else {
setScale(container, maxWidth, maxFeet, map._getUIString('ScaleControl.Feet'), map);
}
} else if (options && options.unit === 'nautical') {
const maxNauticals = maxMeters / 1852;
setScale(container, maxWidth, maxNauticals, map._getUIString('ScaleControl.NauticalMiles'), map);
} else if (maxMeters >= 1000) {
setScale(container, maxWidth, maxMeters / 1000, map._getUIString('ScaleControl.Kilometers'), map);
} else {
setScale(container, maxWidth, maxMeters, map._getUIString('ScaleControl.Meters'), map);
}
}
function setScale(container, maxWidth, maxDistance, unit, map) {
const distance = getRoundNum(maxDistance);
const ratio = distance / maxDistance;
map._requestDomTask(() => {
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;
}