-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
scale_control.js
155 lines (128 loc) · 4.54 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
146
147
148
149
150
151
152
153
154
155
// @flow
import DOM from '../../util/dom';
import {extend, bindAll} from '../../util/util';
import type Map from '../map';
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.
*
* @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
* var 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 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 maxMeters = getDistance(map.unproject([0, y]), map.unproject([maxWidth, y]));
// 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 getDistance(latlng1, latlng2) {
// Uses spherical law of cosines approximation.
const R = 6371000;
const rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad,
a = Math.sin(lat1) * Math.sin(lat2) +
Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
const maxMeters = R * Math.acos(Math.min(a, 1));
return maxMeters;
}
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;
}