43 | | L.Util = { |
44 | | |
45 | | // @function extend(dest: Object, src?: Object): Object |
46 | | // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. |
47 | | extend: function (dest) { |
48 | | var i, j, len, src; |
49 | | |
50 | | for (j = 1, len = arguments.length; j < len; j++) { |
51 | | src = arguments[j]; |
52 | | for (i in src) { |
53 | | dest[i] = src[i]; |
54 | | } |
55 | | } |
56 | | return dest; |
57 | | }, |
58 | | |
59 | | // @function create(proto: Object, properties?: Object): Object |
60 | | // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) |
61 | | create: Object.create || (function () { |
62 | | function F() {} |
63 | | return function (proto) { |
64 | | F.prototype = proto; |
65 | | return new F(); |
66 | | }; |
67 | | })(), |
68 | | |
69 | | // @function bind(fn: Function, …): Function |
70 | | // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). |
71 | | // Has a `L.bind()` shortcut. |
72 | | bind: function (fn, obj) { |
73 | | var slice = Array.prototype.slice; |
74 | | |
75 | | if (fn.bind) { |
76 | | return fn.bind.apply(fn, slice.call(arguments, 1)); |
77 | | } |
78 | | |
79 | | var args = slice.call(arguments, 2); |
80 | | |
81 | | return function () { |
82 | | return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); |
83 | | }; |
84 | | }, |
85 | | |
86 | | // @function stamp(obj: Object): Number |
87 | | // Returns the unique ID of an object, assiging it one if it doesn't have it. |
88 | | stamp: function (obj) { |
89 | | /*eslint-disable */ |
90 | | obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId; |
91 | | return obj._leaflet_id; |
92 | | /*eslint-enable */ |
93 | | }, |
94 | | |
95 | | // @property lastId: Number |
96 | | // Last unique ID used by [`stamp()`](#util-stamp) |
97 | | lastId: 0, |
98 | | |
99 | | // @function throttle(fn: Function, time: Number, context: Object): Function |
100 | | // Returns a function which executes function `fn` with the given scope `context` |
101 | | // (so that the `this` keyword refers to `context` inside `fn`'s code). The function |
102 | | // `fn` will be called no more than one time per given amount of `time`. The arguments |
103 | | // received by the bound function will be any arguments passed when binding the |
104 | | // function, followed by any arguments passed when invoking the bound function. |
105 | | // Has an `L.bind` shortcut. |
106 | | throttle: function (fn, time, context) { |
107 | | var lock, args, wrapperFn, later; |
108 | | |
109 | | later = function () { |
110 | | // reset lock and call if queued |
111 | | lock = false; |
112 | | if (args) { |
113 | | wrapperFn.apply(context, args); |
114 | | args = false; |
115 | | } |
116 | | }; |
117 | | |
118 | | wrapperFn = function () { |
119 | | if (lock) { |
120 | | // called too soon, queue to call later |
121 | | args = arguments; |
122 | | |
123 | | } else { |
124 | | // call and lock until later |
125 | | fn.apply(context, arguments); |
126 | | setTimeout(later, time); |
127 | | lock = true; |
128 | | } |
129 | | }; |
130 | | |
131 | | return wrapperFn; |
132 | | }, |
133 | | |
134 | | // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number |
135 | | // Returns the number `num` modulo `range` in such a way so it lies within |
136 | | // `range[0]` and `range[1]`. The returned value will be always smaller than |
137 | | // `range[1]` unless `includeMax` is set to `true`. |
138 | | wrapNum: function (x, range, includeMax) { |
139 | | var max = range[1], |
140 | | min = range[0], |
141 | | d = max - min; |
142 | | return x === max && includeMax ? x : ((x - min) % d + d) % d + min; |
143 | | }, |
144 | | |
145 | | // @function falseFn(): Function |
146 | | // Returns a function which always returns `false`. |
147 | | falseFn: function () { return false; }, |
148 | | |
149 | | // @function formatNum(num: Number, digits?: Number): Number |
150 | | // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default. |
151 | | formatNum: function (num, digits) { |
152 | | var pow = Math.pow(10, digits || 5); |
153 | | return Math.round(num * pow) / pow; |
154 | | }, |
155 | | |
156 | | // @function trim(str: String): String |
157 | | // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) |
158 | | trim: function (str) { |
159 | | return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); |
160 | | }, |
161 | | |
162 | | // @function splitWords(str: String): String[] |
163 | | // Trims and splits the string on whitespace and returns the array of parts. |
164 | | splitWords: function (str) { |
165 | | return L.Util.trim(str).split(/\s+/); |
166 | | }, |
167 | | |
168 | | // @function setOptions(obj: Object, options: Object): Object |
169 | | // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. |
170 | | setOptions: function (obj, options) { |
171 | | if (!obj.hasOwnProperty('options')) { |
172 | | obj.options = obj.options ? L.Util.create(obj.options) : {}; |
173 | | } |
174 | | for (var i in options) { |
175 | | obj.options[i] = options[i]; |
176 | | } |
177 | | return obj.options; |
178 | | }, |
179 | | |
180 | | // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String |
181 | | // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` |
182 | | // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will |
183 | | // be appended at the end. If `uppercase` is `true`, the parameter names will |
184 | | // be uppercased (e.g. `'?A=foo&B=bar'`) |
185 | | getParamString: function (obj, existingUrl, uppercase) { |
186 | | var params = []; |
187 | | for (var i in obj) { |
188 | | params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); |
189 | | } |
190 | | return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); |
191 | | }, |
192 | | |
193 | | // @function template(str: String, data: Object): String |
194 | | // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` |
195 | | // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string |
196 | | // `('Hello foo, bar')`. You can also specify functions instead of strings for |
197 | | // data values — they will be evaluated passing `data` as an argument. |
198 | | template: function (str, data) { |
199 | | return str.replace(L.Util.templateRe, function (str, key) { |
200 | | var value = data[key]; |
201 | | |
202 | | if (value === undefined) { |
203 | | throw new Error('No value provided for variable ' + str); |
204 | | |
205 | | } else if (typeof value === 'function') { |
206 | | value = value(data); |
207 | | } |
208 | | return value; |
209 | | }); |
210 | | }, |
211 | | |
212 | | templateRe: /\{ *([\w_\-]+) *\}/g, |
213 | | |
214 | | // @function isArray(obj): Boolean |
215 | | // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) |
216 | | isArray: Array.isArray || function (obj) { |
217 | | return (Object.prototype.toString.call(obj) === '[object Array]'); |
218 | | }, |
219 | | |
220 | | // @function indexOf(array: Array, el: Object): Number |
221 | | // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) |
222 | | indexOf: function (array, el) { |
223 | | for (var i = 0; i < array.length; i++) { |
224 | | if (array[i] === el) { return i; } |
225 | | } |
226 | | return -1; |
227 | | }, |
228 | | |
229 | | // @property emptyImageUrl: String |
230 | | // Data URI string containing a base64-encoded empty GIF image. |
231 | | // Used as a hack to free memory from unused images on WebKit-powered |
232 | | // mobile devices (by setting image `src` to this string). |
233 | | emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' |
234 | | }; |
235 | | |
236 | | (function () { |
237 | | // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ |
238 | | |
239 | | function getPrefixed(name) { |
240 | | return window['webkit' + name] || window['moz' + name] || window['ms' + name]; |
241 | | } |
242 | | |
243 | | var lastTime = 0; |
244 | | |
245 | | // fallback for IE 7-8 |
246 | | function timeoutDefer(fn) { |
247 | | var time = +new Date(), |
248 | | timeToCall = Math.max(0, 16 - (time - lastTime)); |
249 | | |
250 | | lastTime = time + timeToCall; |
251 | | return window.setTimeout(fn, timeToCall); |
252 | | } |
253 | | |
254 | | var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer, |
255 | | cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || |
256 | | getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; |
257 | | |
258 | | |
259 | | // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number |
260 | | // Schedules `fn` to be executed when the browser repaints. `fn` is bound to |
261 | | // `context` if given. When `immediate` is set, `fn` is called immediately if |
262 | | // the browser doesn't have native support for |
263 | | // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), |
264 | | // otherwise it's delayed. Returns a request ID that can be used to cancel the request. |
265 | | L.Util.requestAnimFrame = function (fn, context, immediate) { |
266 | | if (immediate && requestFn === timeoutDefer) { |
267 | | fn.call(context); |
268 | | } else { |
269 | | return requestFn.call(window, L.bind(fn, context)); |
270 | | } |
271 | | }; |
272 | | |
273 | | // @function cancelAnimFrame(id: Number): undefined |
274 | | // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). |
275 | | L.Util.cancelAnimFrame = function (id) { |
276 | | if (id) { |
277 | | cancelFn.call(window, id); |
278 | | } |
| 19 | var freeze = Object.freeze; |
| 20 | Object.freeze = function (obj) { return obj; }; |
| 21 | |
| 22 | // @function extend(dest: Object, src?: Object): Object |
| 23 | // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. |
| 24 | function extend(dest) { |
| 25 | var i, j, len, src; |
| 26 | |
| 27 | for (j = 1, len = arguments.length; j < len; j++) { |
| 28 | src = arguments[j]; |
| 29 | for (i in src) { |
| 30 | dest[i] = src[i]; |
| 31 | } |
| 32 | } |
| 33 | return dest; |
| 34 | } |
| 35 | |
| 36 | // @function create(proto: Object, properties?: Object): Object |
| 37 | // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) |
| 38 | var create = Object.create || (function () { |
| 39 | function F() {} |
| 40 | return function (proto) { |
| 41 | F.prototype = proto; |
| 42 | return new F(); |
282 | | // shortcuts for most used utility functions |
283 | | L.extend = L.Util.extend; |
284 | | L.bind = L.Util.bind; |
285 | | L.stamp = L.Util.stamp; |
286 | | L.setOptions = L.Util.setOptions; |
287 | | |
288 | | |
289 | | |
| 46 | // @function bind(fn: Function, …): Function |
| 47 | // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). |
| 48 | // Has a `L.bind()` shortcut. |
| 49 | function bind(fn, obj) { |
| 50 | var slice = Array.prototype.slice; |
| 51 | |
| 52 | if (fn.bind) { |
| 53 | return fn.bind.apply(fn, slice.call(arguments, 1)); |
| 54 | } |
| 55 | |
| 56 | var args = slice.call(arguments, 2); |
| 57 | |
| 58 | return function () { |
| 59 | return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); |
| 60 | }; |
| 61 | } |
| 62 | |
| 63 | // @property lastId: Number |
| 64 | // Last unique ID used by [`stamp()`](#util-stamp) |
| 65 | var lastId = 0; |
| 66 | |
| 67 | // @function stamp(obj: Object): Number |
| 68 | // Returns the unique ID of an object, assiging it one if it doesn't have it. |
| 69 | function stamp(obj) { |
| 70 | /*eslint-disable */ |
| 71 | obj._leaflet_id = obj._leaflet_id || ++lastId; |
| 72 | return obj._leaflet_id; |
| 73 | /*eslint-enable */ |
| 74 | } |
| 75 | |
| 76 | // @function throttle(fn: Function, time: Number, context: Object): Function |
| 77 | // Returns a function which executes function `fn` with the given scope `context` |
| 78 | // (so that the `this` keyword refers to `context` inside `fn`'s code). The function |
| 79 | // `fn` will be called no more than one time per given amount of `time`. The arguments |
| 80 | // received by the bound function will be any arguments passed when binding the |
| 81 | // function, followed by any arguments passed when invoking the bound function. |
| 82 | // Has an `L.throttle` shortcut. |
| 83 | function throttle(fn, time, context) { |
| 84 | var lock, args, wrapperFn, later; |
| 85 | |
| 86 | later = function () { |
| 87 | // reset lock and call if queued |
| 88 | lock = false; |
| 89 | if (args) { |
| 90 | wrapperFn.apply(context, args); |
| 91 | args = false; |
| 92 | } |
| 93 | }; |
| 94 | |
| 95 | wrapperFn = function () { |
| 96 | if (lock) { |
| 97 | // called too soon, queue to call later |
| 98 | args = arguments; |
| 99 | |
| 100 | } else { |
| 101 | // call and lock until later |
| 102 | fn.apply(context, arguments); |
| 103 | setTimeout(later, time); |
| 104 | lock = true; |
| 105 | } |
| 106 | }; |
| 107 | |
| 108 | return wrapperFn; |
| 109 | } |
| 110 | |
| 111 | // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number |
| 112 | // Returns the number `num` modulo `range` in such a way so it lies within |
| 113 | // `range[0]` and `range[1]`. The returned value will be always smaller than |
| 114 | // `range[1]` unless `includeMax` is set to `true`. |
| 115 | function wrapNum(x, range, includeMax) { |
| 116 | var max = range[1], |
| 117 | min = range[0], |
| 118 | d = max - min; |
| 119 | return x === max && includeMax ? x : ((x - min) % d + d) % d + min; |
| 120 | } |
| 121 | |
| 122 | // @function falseFn(): Function |
| 123 | // Returns a function which always returns `false`. |
| 124 | function falseFn() { return false; } |
| 125 | |
| 126 | // @function formatNum(num: Number, digits?: Number): Number |
| 127 | // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default. |
| 128 | function formatNum(num, digits) { |
| 129 | var pow = Math.pow(10, digits || 5); |
| 130 | return Math.round(num * pow) / pow; |
| 131 | } |
| 132 | |
| 133 | // @function trim(str: String): String |
| 134 | // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) |
| 135 | function trim(str) { |
| 136 | return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); |
| 137 | } |
| 138 | |
| 139 | // @function splitWords(str: String): String[] |
| 140 | // Trims and splits the string on whitespace and returns the array of parts. |
| 141 | function splitWords(str) { |
| 142 | return trim(str).split(/\s+/); |
| 143 | } |
| 144 | |
| 145 | // @function setOptions(obj: Object, options: Object): Object |
| 146 | // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. |
| 147 | function setOptions(obj, options) { |
| 148 | if (!obj.hasOwnProperty('options')) { |
| 149 | obj.options = obj.options ? create(obj.options) : {}; |
| 150 | } |
| 151 | for (var i in options) { |
| 152 | obj.options[i] = options[i]; |
| 153 | } |
| 154 | return obj.options; |
| 155 | } |
| 156 | |
| 157 | // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String |
| 158 | // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` |
| 159 | // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will |
| 160 | // be appended at the end. If `uppercase` is `true`, the parameter names will |
| 161 | // be uppercased (e.g. `'?A=foo&B=bar'`) |
| 162 | function getParamString(obj, existingUrl, uppercase) { |
| 163 | var params = []; |
| 164 | for (var i in obj) { |
| 165 | params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); |
| 166 | } |
| 167 | return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); |
| 168 | } |
| 169 | |
| 170 | var templateRe = /\{ *([\w_\-]+) *\}/g; |
| 171 | |
| 172 | // @function template(str: String, data: Object): String |
| 173 | // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` |
| 174 | // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string |
| 175 | // `('Hello foo, bar')`. You can also specify functions instead of strings for |
| 176 | // data values — they will be evaluated passing `data` as an argument. |
| 177 | function template(str, data) { |
| 178 | return str.replace(templateRe, function (str, key) { |
| 179 | var value = data[key]; |
| 180 | |
| 181 | if (value === undefined) { |
| 182 | throw new Error('No value provided for variable ' + str); |
| 183 | |
| 184 | } else if (typeof value === 'function') { |
| 185 | value = value(data); |
| 186 | } |
| 187 | return value; |
| 188 | }); |
| 189 | } |
| 190 | |
| 191 | // @function isArray(obj): Boolean |
| 192 | // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) |
| 193 | var isArray = Array.isArray || function (obj) { |
| 194 | return (Object.prototype.toString.call(obj) === '[object Array]'); |
| 195 | }; |
| 196 | |
| 197 | // @function indexOf(array: Array, el: Object): Number |
| 198 | // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) |
| 199 | function indexOf(array, el) { |
| 200 | for (var i = 0; i < array.length; i++) { |
| 201 | if (array[i] === el) { return i; } |
| 202 | } |
| 203 | return -1; |
| 204 | } |
| 205 | |
| 206 | // @property emptyImageUrl: String |
| 207 | // Data URI string containing a base64-encoded empty GIF image. |
| 208 | // Used as a hack to free memory from unused images on WebKit-powered |
| 209 | // mobile devices (by setting image `src` to this string). |
| 210 | var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; |
| 211 | |
| 212 | // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ |
| 213 | |
| 214 | function getPrefixed(name) { |
| 215 | return window['webkit' + name] || window['moz' + name] || window['ms' + name]; |
| 216 | } |
| 217 | |
| 218 | var lastTime = 0; |
| 219 | |
| 220 | // fallback for IE 7-8 |
| 221 | function timeoutDefer(fn) { |
| 222 | var time = +new Date(), |
| 223 | timeToCall = Math.max(0, 16 - (time - lastTime)); |
| 224 | |
| 225 | lastTime = time + timeToCall; |
| 226 | return window.setTimeout(fn, timeToCall); |
| 227 | } |
| 228 | |
| 229 | var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; |
| 230 | var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || |
| 231 | getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; |
| 232 | |
| 233 | // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number |
| 234 | // Schedules `fn` to be executed when the browser repaints. `fn` is bound to |
| 235 | // `context` if given. When `immediate` is set, `fn` is called immediately if |
| 236 | // the browser doesn't have native support for |
| 237 | // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), |
| 238 | // otherwise it's delayed. Returns a request ID that can be used to cancel the request. |
| 239 | function requestAnimFrame(fn, context, immediate) { |
| 240 | if (immediate && requestFn === timeoutDefer) { |
| 241 | fn.call(context); |
| 242 | } else { |
| 243 | return requestFn.call(window, bind(fn, context)); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | // @function cancelAnimFrame(id: Number): undefined |
| 248 | // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). |
| 249 | function cancelAnimFrame(id) { |
| 250 | if (id) { |
| 251 | cancelFn.call(window, id); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | |
| 256 | var Util = (Object.freeze || Object)({ |
| 257 | freeze: freeze, |
| 258 | extend: extend, |
| 259 | create: create, |
| 260 | bind: bind, |
| 261 | lastId: lastId, |
| 262 | stamp: stamp, |
| 263 | throttle: throttle, |
| 264 | wrapNum: wrapNum, |
| 265 | falseFn: falseFn, |
| 266 | formatNum: formatNum, |
| 267 | trim: trim, |
| 268 | splitWords: splitWords, |
| 269 | setOptions: setOptions, |
| 270 | getParamString: getParamString, |
| 271 | template: template, |
| 272 | isArray: isArray, |
| 273 | indexOf: indexOf, |
| 274 | emptyImageUrl: emptyImageUrl, |
| 275 | requestFn: requestFn, |
| 276 | cancelFn: cancelFn, |
| 277 | requestAnimFrame: requestAnimFrame, |
| 278 | cancelAnimFrame: cancelAnimFrame |
| 279 | }); |
1205 | | return new L.Bounds(a, b); |
| 1058 | return new Bounds(a, b); |
| 1059 | } |
| 1060 | |
| 1061 | /* |
| 1062 | * @class LatLngBounds |
| 1063 | * @aka L.LatLngBounds |
| 1064 | * |
| 1065 | * Represents a rectangular geographical area on a map. |
| 1066 | * |
| 1067 | * @example |
| 1068 | * |
| 1069 | * ```js |
| 1070 | * var corner1 = L.latLng(40.712, -74.227), |
| 1071 | * corner2 = L.latLng(40.774, -74.125), |
| 1072 | * bounds = L.latLngBounds(corner1, corner2); |
| 1073 | * ``` |
| 1074 | * |
| 1075 | * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: |
| 1076 | * |
| 1077 | * ```js |
| 1078 | * map.fitBounds([ |
| 1079 | * [40.712, -74.227], |
| 1080 | * [40.774, -74.125] |
| 1081 | * ]); |
| 1082 | * ``` |
| 1083 | * |
| 1084 | * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. |
| 1085 | */ |
| 1086 | |
| 1087 | function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) |
| 1088 | if (!corner1) { return; } |
| 1089 | |
| 1090 | var latlngs = corner2 ? [corner1, corner2] : corner1; |
| 1091 | |
| 1092 | for (var i = 0, len = latlngs.length; i < len; i++) { |
| 1093 | this.extend(latlngs[i]); |
| 1094 | } |
| 1095 | } |
| 1096 | |
| 1097 | LatLngBounds.prototype = { |
| 1098 | |
| 1099 | // @method extend(latlng: LatLng): this |
| 1100 | // Extend the bounds to contain the given point |
| 1101 | |
| 1102 | // @alternative |
| 1103 | // @method extend(otherBounds: LatLngBounds): this |
| 1104 | // Extend the bounds to contain the given bounds |
| 1105 | extend: function (obj) { |
| 1106 | var sw = this._southWest, |
| 1107 | ne = this._northEast, |
| 1108 | sw2, ne2; |
| 1109 | |
| 1110 | if (obj instanceof LatLng) { |
| 1111 | sw2 = obj; |
| 1112 | ne2 = obj; |
| 1113 | |
| 1114 | } else if (obj instanceof LatLngBounds) { |
| 1115 | sw2 = obj._southWest; |
| 1116 | ne2 = obj._northEast; |
| 1117 | |
| 1118 | if (!sw2 || !ne2) { return this; } |
| 1119 | |
| 1120 | } else { |
| 1121 | return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; |
| 1122 | } |
| 1123 | |
| 1124 | if (!sw && !ne) { |
| 1125 | this._southWest = new LatLng(sw2.lat, sw2.lng); |
| 1126 | this._northEast = new LatLng(ne2.lat, ne2.lng); |
| 1127 | } else { |
| 1128 | sw.lat = Math.min(sw2.lat, sw.lat); |
| 1129 | sw.lng = Math.min(sw2.lng, sw.lng); |
| 1130 | ne.lat = Math.max(ne2.lat, ne.lat); |
| 1131 | ne.lng = Math.max(ne2.lng, ne.lng); |
| 1132 | } |
| 1133 | |
| 1134 | return this; |
| 1135 | }, |
| 1136 | |
| 1137 | // @method pad(bufferRatio: Number): LatLngBounds |
| 1138 | // Returns bigger bounds created by extending the current bounds by a given percentage in each direction. |
| 1139 | pad: function (bufferRatio) { |
| 1140 | var sw = this._southWest, |
| 1141 | ne = this._northEast, |
| 1142 | heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, |
| 1143 | widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; |
| 1144 | |
| 1145 | return new LatLngBounds( |
| 1146 | new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), |
| 1147 | new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); |
| 1148 | }, |
| 1149 | |
| 1150 | // @method getCenter(): LatLng |
| 1151 | // Returns the center point of the bounds. |
| 1152 | getCenter: function () { |
| 1153 | return new LatLng( |
| 1154 | (this._southWest.lat + this._northEast.lat) / 2, |
| 1155 | (this._southWest.lng + this._northEast.lng) / 2); |
| 1156 | }, |
| 1157 | |
| 1158 | // @method getSouthWest(): LatLng |
| 1159 | // Returns the south-west point of the bounds. |
| 1160 | getSouthWest: function () { |
| 1161 | return this._southWest; |
| 1162 | }, |
| 1163 | |
| 1164 | // @method getNorthEast(): LatLng |
| 1165 | // Returns the north-east point of the bounds. |
| 1166 | getNorthEast: function () { |
| 1167 | return this._northEast; |
| 1168 | }, |
| 1169 | |
| 1170 | // @method getNorthWest(): LatLng |
| 1171 | // Returns the north-west point of the bounds. |
| 1172 | getNorthWest: function () { |
| 1173 | return new LatLng(this.getNorth(), this.getWest()); |
| 1174 | }, |
| 1175 | |
| 1176 | // @method getSouthEast(): LatLng |
| 1177 | // Returns the south-east point of the bounds. |
| 1178 | getSouthEast: function () { |
| 1179 | return new LatLng(this.getSouth(), this.getEast()); |
| 1180 | }, |
| 1181 | |
| 1182 | // @method getWest(): Number |
| 1183 | // Returns the west longitude of the bounds |
| 1184 | getWest: function () { |
| 1185 | return this._southWest.lng; |
| 1186 | }, |
| 1187 | |
| 1188 | // @method getSouth(): Number |
| 1189 | // Returns the south latitude of the bounds |
| 1190 | getSouth: function () { |
| 1191 | return this._southWest.lat; |
| 1192 | }, |
| 1193 | |
| 1194 | // @method getEast(): Number |
| 1195 | // Returns the east longitude of the bounds |
| 1196 | getEast: function () { |
| 1197 | return this._northEast.lng; |
| 1198 | }, |
| 1199 | |
| 1200 | // @method getNorth(): Number |
| 1201 | // Returns the north latitude of the bounds |
| 1202 | getNorth: function () { |
| 1203 | return this._northEast.lat; |
| 1204 | }, |
| 1205 | |
| 1206 | // @method contains(otherBounds: LatLngBounds): Boolean |
| 1207 | // Returns `true` if the rectangle contains the given one. |
| 1208 | |
| 1209 | // @alternative |
| 1210 | // @method contains (latlng: LatLng): Boolean |
| 1211 | // Returns `true` if the rectangle contains the given point. |
| 1212 | contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean |
| 1213 | if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { |
| 1214 | obj = toLatLng(obj); |
| 1215 | } else { |
| 1216 | obj = toLatLngBounds(obj); |
| 1217 | } |
| 1218 | |
| 1219 | var sw = this._southWest, |
| 1220 | ne = this._northEast, |
| 1221 | sw2, ne2; |
| 1222 | |
| 1223 | if (obj instanceof LatLngBounds) { |
| 1224 | sw2 = obj.getSouthWest(); |
| 1225 | ne2 = obj.getNorthEast(); |
| 1226 | } else { |
| 1227 | sw2 = ne2 = obj; |
| 1228 | } |
| 1229 | |
| 1230 | return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && |
| 1231 | (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); |
| 1232 | }, |
| 1233 | |
| 1234 | // @method intersects(otherBounds: LatLngBounds): Boolean |
| 1235 | // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. |
| 1236 | intersects: function (bounds) { |
| 1237 | bounds = toLatLngBounds(bounds); |
| 1238 | |
| 1239 | var sw = this._southWest, |
| 1240 | ne = this._northEast, |
| 1241 | sw2 = bounds.getSouthWest(), |
| 1242 | ne2 = bounds.getNorthEast(), |
| 1243 | |
| 1244 | latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), |
| 1245 | lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); |
| 1246 | |
| 1247 | return latIntersects && lngIntersects; |
| 1248 | }, |
| 1249 | |
| 1250 | // @method overlaps(otherBounds: Bounds): Boolean |
| 1251 | // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. |
| 1252 | overlaps: function (bounds) { |
| 1253 | bounds = toLatLngBounds(bounds); |
| 1254 | |
| 1255 | var sw = this._southWest, |
| 1256 | ne = this._northEast, |
| 1257 | sw2 = bounds.getSouthWest(), |
| 1258 | ne2 = bounds.getNorthEast(), |
| 1259 | |
| 1260 | latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), |
| 1261 | lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); |
| 1262 | |
| 1263 | return latOverlaps && lngOverlaps; |
| 1264 | }, |
| 1265 | |
| 1266 | // @method toBBoxString(): String |
| 1267 | // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. |
| 1268 | toBBoxString: function () { |
| 1269 | return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); |
| 1270 | }, |
| 1271 | |
| 1272 | // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean |
| 1273 | // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number. |
| 1274 | equals: function (bounds, maxMargin) { |
| 1275 | if (!bounds) { return false; } |
| 1276 | |
| 1277 | bounds = toLatLngBounds(bounds); |
| 1278 | |
| 1279 | return this._southWest.equals(bounds.getSouthWest(), maxMargin) && |
| 1280 | this._northEast.equals(bounds.getNorthEast(), maxMargin); |
| 1281 | }, |
| 1282 | |
| 1283 | // @method isValid(): Boolean |
| 1284 | // Returns `true` if the bounds are properly initialized. |
| 1285 | isValid: function () { |
| 1286 | return !!(this._southWest && this._northEast); |
| 1287 | } |
1208 | | |
1209 | | |
1210 | | /* |
1211 | | * @class Transformation |
1212 | | * @aka L.Transformation |
1213 | | * |
1214 | | * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` |
1215 | | * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing |
1216 | | * the reverse. Used by Leaflet in its projections code. |
1217 | | * |
1218 | | * @example |
1219 | | * |
1220 | | * ```js |
1221 | | * var transformation = new L.Transformation(2, 5, -1, 10), |
1222 | | * p = L.point(1, 2), |
1223 | | * p2 = transformation.transform(p), // L.point(7, 8) |
1224 | | * p3 = transformation.untransform(p2); // L.point(1, 2) |
1225 | | * ``` |
1226 | | */ |
1227 | | |
1228 | | |
1229 | | // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) |
1230 | | // Creates a `Transformation` object with the given coefficients. |
1231 | | L.Transformation = function (a, b, c, d) { |
1232 | | this._a = a; |
1233 | | this._b = b; |
1234 | | this._c = c; |
1235 | | this._d = d; |
1236 | | }; |
1237 | | |
1238 | | L.Transformation.prototype = { |
1239 | | // @method transform(point: Point, scale?: Number): Point |
1240 | | // Returns a transformed point, optionally multiplied by the given scale. |
1241 | | // Only accepts actual `L.Point` instances, not arrays. |
1242 | | transform: function (point, scale) { // (Point, Number) -> Point |
1243 | | return this._transform(point.clone(), scale); |
1244 | | }, |
1245 | | |
1246 | | // destructive transform (faster) |
1247 | | _transform: function (point, scale) { |
1248 | | scale = scale || 1; |
1249 | | point.x = scale * (this._a * point.x + this._b); |
1250 | | point.y = scale * (this._c * point.y + this._d); |
1251 | | return point; |
1252 | | }, |
1253 | | |
1254 | | // @method untransform(point: Point, scale?: Number): Point |
1255 | | // Returns the reverse transformation of the given point, optionally divided |
1256 | | // by the given scale. Only accepts actual `L.Point` instances, not arrays. |
1257 | | untransform: function (point, scale) { |
1258 | | scale = scale || 1; |
1259 | | return new L.Point( |
1260 | | (point.x / scale - this._b) / this._a, |
1261 | | (point.y / scale - this._d) / this._c); |
1262 | | } |
1263 | | }; |
1264 | | |
1265 | | |
1266 | | |
1267 | | /* |
1268 | | * @namespace DomUtil |
1269 | | * |
1270 | | * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) |
1271 | | * tree, used by Leaflet internally. |
1272 | | * |
1273 | | * Most functions expecting or returning a `HTMLElement` also work for |
1274 | | * SVG elements. The only difference is that classes refer to CSS classes |
1275 | | * in HTML and SVG classes in SVG. |
1276 | | */ |
1277 | | |
1278 | | L.DomUtil = { |
1279 | | |
1280 | | // @function get(id: String|HTMLElement): HTMLElement |
1281 | | // Returns an element given its DOM id, or returns the element itself |
1282 | | // if it was passed directly. |
1283 | | get: function (id) { |
1284 | | return typeof id === 'string' ? document.getElementById(id) : id; |
1285 | | }, |
1286 | | |
1287 | | // @function getStyle(el: HTMLElement, styleAttrib: String): String |
1288 | | // Returns the value for a certain style attribute on an element, |
1289 | | // including computed values or values set through CSS. |
1290 | | getStyle: function (el, style) { |
1291 | | |
1292 | | var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); |
1293 | | |
1294 | | if ((!value || value === 'auto') && document.defaultView) { |
1295 | | var css = document.defaultView.getComputedStyle(el, null); |
1296 | | value = css ? css[style] : null; |
1297 | | } |
1298 | | |
1299 | | return value === 'auto' ? null : value; |
1300 | | }, |
1301 | | |
1302 | | // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement |
1303 | | // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. |
1304 | | create: function (tagName, className, container) { |
1305 | | |
1306 | | var el = document.createElement(tagName); |
1307 | | el.className = className || ''; |
1308 | | |
1309 | | if (container) { |
1310 | | container.appendChild(el); |
1311 | | } |
1312 | | |
1313 | | return el; |
1314 | | }, |
1315 | | |
1316 | | // @function remove(el: HTMLElement) |
1317 | | // Removes `el` from its parent element |
1318 | | remove: function (el) { |
1319 | | var parent = el.parentNode; |
1320 | | if (parent) { |
1321 | | parent.removeChild(el); |
1322 | | } |
1323 | | }, |
1324 | | |
1325 | | // @function empty(el: HTMLElement) |
1326 | | // Removes all of `el`'s children elements from `el` |
1327 | | empty: function (el) { |
1328 | | while (el.firstChild) { |
1329 | | el.removeChild(el.firstChild); |
1330 | | } |
1331 | | }, |
1332 | | |
1333 | | // @function toFront(el: HTMLElement) |
1334 | | // Makes `el` the last children of its parent, so it renders in front of the other children. |
1335 | | toFront: function (el) { |
1336 | | el.parentNode.appendChild(el); |
1337 | | }, |
1338 | | |
1339 | | // @function toBack(el: HTMLElement) |
1340 | | // Makes `el` the first children of its parent, so it renders back from the other children. |
1341 | | toBack: function (el) { |
1342 | | var parent = el.parentNode; |
1343 | | parent.insertBefore(el, parent.firstChild); |
1344 | | }, |
1345 | | |
1346 | | // @function hasClass(el: HTMLElement, name: String): Boolean |
1347 | | // Returns `true` if the element's class attribute contains `name`. |
1348 | | hasClass: function (el, name) { |
1349 | | if (el.classList !== undefined) { |
1350 | | return el.classList.contains(name); |
1351 | | } |
1352 | | var className = L.DomUtil.getClass(el); |
1353 | | return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); |
1354 | | }, |
1355 | | |
1356 | | // @function addClass(el: HTMLElement, name: String) |
1357 | | // Adds `name` to the element's class attribute. |
1358 | | addClass: function (el, name) { |
1359 | | if (el.classList !== undefined) { |
1360 | | var classes = L.Util.splitWords(name); |
1361 | | for (var i = 0, len = classes.length; i < len; i++) { |
1362 | | el.classList.add(classes[i]); |
1363 | | } |
1364 | | } else if (!L.DomUtil.hasClass(el, name)) { |
1365 | | var className = L.DomUtil.getClass(el); |
1366 | | L.DomUtil.setClass(el, (className ? className + ' ' : '') + name); |
1367 | | } |
1368 | | }, |
1369 | | |
1370 | | // @function removeClass(el: HTMLElement, name: String) |
1371 | | // Removes `name` from the element's class attribute. |
1372 | | removeClass: function (el, name) { |
1373 | | if (el.classList !== undefined) { |
1374 | | el.classList.remove(name); |
1375 | | } else { |
1376 | | L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' '))); |
1377 | | } |
1378 | | }, |
1379 | | |
1380 | | // @function setClass(el: HTMLElement, name: String) |
1381 | | // Sets the element's class. |
1382 | | setClass: function (el, name) { |
1383 | | if (el.className.baseVal === undefined) { |
1384 | | el.className = name; |
1385 | | } else { |
1386 | | // in case of SVG element |
1387 | | el.className.baseVal = name; |
1388 | | } |
1389 | | }, |
1390 | | |
1391 | | // @function getClass(el: HTMLElement): String |
1392 | | // Returns the element's class. |
1393 | | getClass: function (el) { |
1394 | | return el.className.baseVal === undefined ? el.className : el.className.baseVal; |
1395 | | }, |
1396 | | |
1397 | | // @function setOpacity(el: HTMLElement, opacity: Number) |
1398 | | // Set the opacity of an element (including old IE support). |
1399 | | // `opacity` must be a number from `0` to `1`. |
1400 | | setOpacity: function (el, value) { |
1401 | | |
1402 | | if ('opacity' in el.style) { |
1403 | | el.style.opacity = value; |
1404 | | |
1405 | | } else if ('filter' in el.style) { |
1406 | | L.DomUtil._setOpacityIE(el, value); |
1407 | | } |
1408 | | }, |
1409 | | |
1410 | | _setOpacityIE: function (el, value) { |
1411 | | var filter = false, |
1412 | | filterName = 'DXImageTransform.Microsoft.Alpha'; |
1413 | | |
1414 | | // filters collection throws an error if we try to retrieve a filter that doesn't exist |
1415 | | try { |
1416 | | filter = el.filters.item(filterName); |
1417 | | } catch (e) { |
1418 | | // don't set opacity to 1 if we haven't already set an opacity, |
1419 | | // it isn't needed and breaks transparent pngs. |
1420 | | if (value === 1) { return; } |
1421 | | } |
1422 | | |
1423 | | value = Math.round(value * 100); |
1424 | | |
1425 | | if (filter) { |
1426 | | filter.Enabled = (value !== 100); |
1427 | | filter.Opacity = value; |
1428 | | } else { |
1429 | | el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; |
1430 | | } |
1431 | | }, |
1432 | | |
1433 | | // @function testProp(props: String[]): String|false |
1434 | | // Goes through the array of style names and returns the first name |
1435 | | // that is a valid style name for an element. If no such name is found, |
1436 | | // it returns false. Useful for vendor-prefixed styles like `transform`. |
1437 | | testProp: function (props) { |
1438 | | |
1439 | | var style = document.documentElement.style; |
1440 | | |
1441 | | for (var i = 0; i < props.length; i++) { |
1442 | | if (props[i] in style) { |
1443 | | return props[i]; |
1444 | | } |
1445 | | } |
1446 | | return false; |
1447 | | }, |
1448 | | |
1449 | | // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) |
1450 | | // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels |
1451 | | // and optionally scaled by `scale`. Does not have an effect if the |
1452 | | // browser doesn't support 3D CSS transforms. |
1453 | | setTransform: function (el, offset, scale) { |
1454 | | var pos = offset || new L.Point(0, 0); |
1455 | | |
1456 | | el.style[L.DomUtil.TRANSFORM] = |
1457 | | (L.Browser.ie3d ? |
1458 | | 'translate(' + pos.x + 'px,' + pos.y + 'px)' : |
1459 | | 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + |
1460 | | (scale ? ' scale(' + scale + ')' : ''); |
1461 | | }, |
1462 | | |
1463 | | // @function setPosition(el: HTMLElement, position: Point) |
1464 | | // Sets the position of `el` to coordinates specified by `position`, |
1465 | | // using CSS translate or top/left positioning depending on the browser |
1466 | | // (used by Leaflet internally to position its layers). |
1467 | | setPosition: function (el, point) { // (HTMLElement, Point[, Boolean]) |
1468 | | |
1469 | | /*eslint-disable */ |
1470 | | el._leaflet_pos = point; |
1471 | | /*eslint-enable */ |
1472 | | |
1473 | | if (L.Browser.any3d) { |
1474 | | L.DomUtil.setTransform(el, point); |
1475 | | } else { |
1476 | | el.style.left = point.x + 'px'; |
1477 | | el.style.top = point.y + 'px'; |
1478 | | } |
1479 | | }, |
1480 | | |
1481 | | // @function getPosition(el: HTMLElement): Point |
1482 | | // Returns the coordinates of an element previously positioned with setPosition. |
1483 | | getPosition: function (el) { |
1484 | | // this method is only used for elements previously positioned using setPosition, |
1485 | | // so it's safe to cache the position for performance |
1486 | | |
1487 | | return el._leaflet_pos || new L.Point(0, 0); |
1488 | | } |
1489 | | }; |
1490 | | |
1491 | | |
1492 | | (function () { |
1493 | | // prefix style property names |
1494 | | |
1495 | | // @property TRANSFORM: String |
1496 | | // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit). |
1497 | | L.DomUtil.TRANSFORM = L.DomUtil.testProp( |
1498 | | ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); |
1499 | | |
1500 | | |
1501 | | // webkitTransition comes first because some browser versions that drop vendor prefix don't do |
1502 | | // the same for the transitionend event, in particular the Android 4.1 stock browser |
1503 | | |
1504 | | // @property TRANSITION: String |
1505 | | // Vendor-prefixed transform style name. |
1506 | | var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp( |
1507 | | ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); |
1508 | | |
1509 | | L.DomUtil.TRANSITION_END = |
1510 | | transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend'; |
1511 | | |
1512 | | // @function disableTextSelection() |
1513 | | // Prevents the user from generating `selectstart` DOM events, usually generated |
1514 | | // when the user drags the mouse through a page with text. Used internally |
1515 | | // by Leaflet to override the behaviour of any click-and-drag interaction on |
1516 | | // the map. Affects drag interactions on the whole document. |
1517 | | |
1518 | | // @function enableTextSelection() |
1519 | | // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). |
1520 | | if ('onselectstart' in document) { |
1521 | | L.DomUtil.disableTextSelection = function () { |
1522 | | L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); |
1523 | | }; |
1524 | | L.DomUtil.enableTextSelection = function () { |
1525 | | L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); |
1526 | | }; |
1527 | | |
1528 | | } else { |
1529 | | var userSelectProperty = L.DomUtil.testProp( |
1530 | | ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); |
1531 | | |
1532 | | L.DomUtil.disableTextSelection = function () { |
1533 | | if (userSelectProperty) { |
1534 | | var style = document.documentElement.style; |
1535 | | this._userSelect = style[userSelectProperty]; |
1536 | | style[userSelectProperty] = 'none'; |
1537 | | } |
1538 | | }; |
1539 | | L.DomUtil.enableTextSelection = function () { |
1540 | | if (userSelectProperty) { |
1541 | | document.documentElement.style[userSelectProperty] = this._userSelect; |
1542 | | delete this._userSelect; |
1543 | | } |
1544 | | }; |
1545 | | } |
1546 | | |
1547 | | // @function disableImageDrag() |
1548 | | // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but |
1549 | | // for `dragstart` DOM events, usually generated when the user drags an image. |
1550 | | L.DomUtil.disableImageDrag = function () { |
1551 | | L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); |
1552 | | }; |
1553 | | |
1554 | | // @function enableImageDrag() |
1555 | | // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). |
1556 | | L.DomUtil.enableImageDrag = function () { |
1557 | | L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); |
1558 | | }; |
1559 | | |
1560 | | // @function preventOutline(el: HTMLElement) |
1561 | | // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) |
1562 | | // of the element `el` invisible. Used internally by Leaflet to prevent |
1563 | | // focusable elements from displaying an outline when the user performs a |
1564 | | // drag interaction on them. |
1565 | | L.DomUtil.preventOutline = function (element) { |
1566 | | while (element.tabIndex === -1) { |
1567 | | element = element.parentNode; |
1568 | | } |
1569 | | if (!element || !element.style) { return; } |
1570 | | L.DomUtil.restoreOutline(); |
1571 | | this._outlineElement = element; |
1572 | | this._outlineStyle = element.style.outline; |
1573 | | element.style.outline = 'none'; |
1574 | | L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this); |
1575 | | }; |
1576 | | |
1577 | | // @function restoreOutline() |
1578 | | // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). |
1579 | | L.DomUtil.restoreOutline = function () { |
1580 | | if (!this._outlineElement) { return; } |
1581 | | this._outlineElement.style.outline = this._outlineStyle; |
1582 | | delete this._outlineElement; |
1583 | | delete this._outlineStyle; |
1584 | | L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this); |
1585 | | }; |
1586 | | })(); |
1587 | | |
1588 | | |
| 1290 | // TODO International date line? |
| 1291 | |
| 1292 | // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) |
| 1293 | // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. |
| 1294 | |
| 1295 | // @alternative |
| 1296 | // @factory L.latLngBounds(latlngs: LatLng[]) |
| 1297 | // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). |
| 1298 | function toLatLngBounds(a, b) { |
| 1299 | if (a instanceof LatLngBounds) { |
| 1300 | return a; |
| 1301 | } |
| 1302 | return new LatLngBounds(a, b); |
| 1303 | } |
1723 | | * @class LatLngBounds |
1724 | | * @aka L.LatLngBounds |
1725 | | * |
1726 | | * Represents a rectangular geographical area on a map. |
1727 | | * |
1728 | | * @example |
1729 | | * |
1730 | | * ```js |
1731 | | * var corner1 = L.latLng(40.712, -74.227), |
1732 | | * corner2 = L.latLng(40.774, -74.125), |
1733 | | * bounds = L.latLngBounds(corner1, corner2); |
1734 | | * ``` |
1735 | | * |
1736 | | * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: |
1737 | | * |
1738 | | * ```js |
1739 | | * map.fitBounds([ |
1740 | | * [40.712, -74.227], |
1741 | | * [40.774, -74.125] |
1742 | | * ]); |
1743 | | * ``` |
1744 | | * |
1745 | | * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. |
1746 | | */ |
1747 | | |
1748 | | L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) |
1749 | | if (!corner1) { return; } |
1750 | | |
1751 | | var latlngs = corner2 ? [corner1, corner2] : corner1; |
1752 | | |
1753 | | for (var i = 0, len = latlngs.length; i < len; i++) { |
1754 | | this.extend(latlngs[i]); |
1755 | | } |
1756 | | }; |
1757 | | |
1758 | | L.LatLngBounds.prototype = { |
1759 | | |
1760 | | // @method extend(latlng: LatLng): this |
1761 | | // Extend the bounds to contain the given point |
1762 | | |
1763 | | // @alternative |
1764 | | // @method extend(otherBounds: LatLngBounds): this |
1765 | | // Extend the bounds to contain the given bounds |
1766 | | extend: function (obj) { |
1767 | | var sw = this._southWest, |
1768 | | ne = this._northEast, |
1769 | | sw2, ne2; |
1770 | | |
1771 | | if (obj instanceof L.LatLng) { |
1772 | | sw2 = obj; |
1773 | | ne2 = obj; |
1774 | | |
1775 | | } else if (obj instanceof L.LatLngBounds) { |
1776 | | sw2 = obj._southWest; |
1777 | | ne2 = obj._northEast; |
1778 | | |
1779 | | if (!sw2 || !ne2) { return this; } |
1780 | | |
1781 | | } else { |
1782 | | return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this; |
1783 | | } |
1784 | | |
1785 | | if (!sw && !ne) { |
1786 | | this._southWest = new L.LatLng(sw2.lat, sw2.lng); |
1787 | | this._northEast = new L.LatLng(ne2.lat, ne2.lng); |
1788 | | } else { |
1789 | | sw.lat = Math.min(sw2.lat, sw.lat); |
1790 | | sw.lng = Math.min(sw2.lng, sw.lng); |
1791 | | ne.lat = Math.max(ne2.lat, ne.lat); |
1792 | | ne.lng = Math.max(ne2.lng, ne.lng); |
1793 | | } |
1794 | | |
1795 | | return this; |
1796 | | }, |
1797 | | |
1798 | | // @method pad(bufferRatio: Number): LatLngBounds |
1799 | | // Returns bigger bounds created by extending the current bounds by a given percentage in each direction. |
1800 | | pad: function (bufferRatio) { |
1801 | | var sw = this._southWest, |
1802 | | ne = this._northEast, |
1803 | | heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, |
1804 | | widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; |
1805 | | |
1806 | | return new L.LatLngBounds( |
1807 | | new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), |
1808 | | new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); |
1809 | | }, |
1810 | | |
1811 | | // @method getCenter(): LatLng |
1812 | | // Returns the center point of the bounds. |
1813 | | getCenter: function () { |
1814 | | return new L.LatLng( |
1815 | | (this._southWest.lat + this._northEast.lat) / 2, |
1816 | | (this._southWest.lng + this._northEast.lng) / 2); |
1817 | | }, |
1818 | | |
1819 | | // @method getSouthWest(): LatLng |
1820 | | // Returns the south-west point of the bounds. |
1821 | | getSouthWest: function () { |
1822 | | return this._southWest; |
1823 | | }, |
1824 | | |
1825 | | // @method getNorthEast(): LatLng |
1826 | | // Returns the north-east point of the bounds. |
1827 | | getNorthEast: function () { |
1828 | | return this._northEast; |
1829 | | }, |
1830 | | |
1831 | | // @method getNorthWest(): LatLng |
1832 | | // Returns the north-west point of the bounds. |
1833 | | getNorthWest: function () { |
1834 | | return new L.LatLng(this.getNorth(), this.getWest()); |
1835 | | }, |
1836 | | |
1837 | | // @method getSouthEast(): LatLng |
1838 | | // Returns the south-east point of the bounds. |
1839 | | getSouthEast: function () { |
1840 | | return new L.LatLng(this.getSouth(), this.getEast()); |
1841 | | }, |
1842 | | |
1843 | | // @method getWest(): Number |
1844 | | // Returns the west longitude of the bounds |
1845 | | getWest: function () { |
1846 | | return this._southWest.lng; |
1847 | | }, |
1848 | | |
1849 | | // @method getSouth(): Number |
1850 | | // Returns the south latitude of the bounds |
1851 | | getSouth: function () { |
1852 | | return this._southWest.lat; |
1853 | | }, |
1854 | | |
1855 | | // @method getEast(): Number |
1856 | | // Returns the east longitude of the bounds |
1857 | | getEast: function () { |
1858 | | return this._northEast.lng; |
1859 | | }, |
1860 | | |
1861 | | // @method getNorth(): Number |
1862 | | // Returns the north latitude of the bounds |
1863 | | getNorth: function () { |
1864 | | return this._northEast.lat; |
1865 | | }, |
1866 | | |
1867 | | // @method contains(otherBounds: LatLngBounds): Boolean |
1868 | | // Returns `true` if the rectangle contains the given one. |
1869 | | |
1870 | | // @alternative |
1871 | | // @method contains (latlng: LatLng): Boolean |
1872 | | // Returns `true` if the rectangle contains the given point. |
1873 | | contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean |
1874 | | if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) { |
1875 | | obj = L.latLng(obj); |
1876 | | } else { |
1877 | | obj = L.latLngBounds(obj); |
1878 | | } |
1879 | | |
1880 | | var sw = this._southWest, |
1881 | | ne = this._northEast, |
1882 | | sw2, ne2; |
1883 | | |
1884 | | if (obj instanceof L.LatLngBounds) { |
1885 | | sw2 = obj.getSouthWest(); |
1886 | | ne2 = obj.getNorthEast(); |
1887 | | } else { |
1888 | | sw2 = ne2 = obj; |
1889 | | } |
1890 | | |
1891 | | return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && |
1892 | | (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); |
1893 | | }, |
1894 | | |
1895 | | // @method intersects(otherBounds: LatLngBounds): Boolean |
1896 | | // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. |
1897 | | intersects: function (bounds) { |
1898 | | bounds = L.latLngBounds(bounds); |
1899 | | |
1900 | | var sw = this._southWest, |
1901 | | ne = this._northEast, |
1902 | | sw2 = bounds.getSouthWest(), |
1903 | | ne2 = bounds.getNorthEast(), |
1904 | | |
1905 | | latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), |
1906 | | lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); |
1907 | | |
1908 | | return latIntersects && lngIntersects; |
1909 | | }, |
1910 | | |
1911 | | // @method overlaps(otherBounds: Bounds): Boolean |
1912 | | // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. |
1913 | | overlaps: function (bounds) { |
1914 | | bounds = L.latLngBounds(bounds); |
1915 | | |
1916 | | var sw = this._southWest, |
1917 | | ne = this._northEast, |
1918 | | sw2 = bounds.getSouthWest(), |
1919 | | ne2 = bounds.getNorthEast(), |
1920 | | |
1921 | | latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), |
1922 | | lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); |
1923 | | |
1924 | | return latOverlaps && lngOverlaps; |
1925 | | }, |
1926 | | |
1927 | | // @method toBBoxString(): String |
1928 | | // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. |
1929 | | toBBoxString: function () { |
1930 | | return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); |
1931 | | }, |
1932 | | |
1933 | | // @method equals(otherBounds: LatLngBounds): Boolean |
1934 | | // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. |
1935 | | equals: function (bounds) { |
1936 | | if (!bounds) { return false; } |
1937 | | |
1938 | | bounds = L.latLngBounds(bounds); |
1939 | | |
1940 | | return this._southWest.equals(bounds.getSouthWest()) && |
1941 | | this._northEast.equals(bounds.getNorthEast()); |
1942 | | }, |
1943 | | |
1944 | | // @method isValid(): Boolean |
1945 | | // Returns `true` if the bounds are properly initialized. |
1946 | | isValid: function () { |
1947 | | return !!(this._southWest && this._northEast); |
1948 | | } |
1949 | | }; |
1950 | | |
1951 | | // TODO International date line? |
1952 | | |
1953 | | // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) |
1954 | | // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. |
1955 | | |
1956 | | // @alternative |
1957 | | // @factory L.latLngBounds(latlngs: LatLng[]) |
1958 | | // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). |
1959 | | L.latLngBounds = function (a, b) { |
1960 | | if (a instanceof L.LatLngBounds) { |
1961 | | return a; |
1962 | | } |
1963 | | return new L.LatLngBounds(a, b); |
1964 | | }; |
1965 | | |
1966 | | |
1967 | | |
1968 | | /* |
1969 | | * @namespace Projection |
1970 | | * @section |
1971 | | * Leaflet comes with a set of already defined Projections out of the box: |
1972 | | * |
1973 | | * @projection L.Projection.LonLat |
1974 | | * |
1975 | | * Equirectangular, or Plate Carree projection — the most simple projection, |
1976 | | * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as |
1977 | | * latitude. Also suitable for flat worlds, e.g. game maps. Used by the |
1978 | | * `EPSG:3395` and `Simple` CRS. |
1979 | | */ |
1980 | | |
1981 | | L.Projection = {}; |
1982 | | |
1983 | | L.Projection.LonLat = { |
1984 | | project: function (latlng) { |
1985 | | return new L.Point(latlng.lng, latlng.lat); |
1986 | | }, |
1987 | | |
1988 | | unproject: function (point) { |
1989 | | return new L.LatLng(point.y, point.x); |
1990 | | }, |
1991 | | |
1992 | | bounds: L.bounds([-180, -90], [180, 90]) |
1993 | | }; |
1994 | | |
1995 | | |
1996 | | |
1997 | | /* |
1998 | | * @namespace Projection |
1999 | | * @projection L.Projection.SphericalMercator |
2000 | | * |
2001 | | * Spherical Mercator projection — the most common projection for online maps, |
2002 | | * used by almost all free and commercial tile providers. Assumes that Earth is |
2003 | | * a sphere. Used by the `EPSG:3857` CRS. |
2004 | | */ |
2005 | | |
2006 | | L.Projection.SphericalMercator = { |
2007 | | |
2008 | | R: 6378137, |
2009 | | MAX_LATITUDE: 85.0511287798, |
2010 | | |
2011 | | project: function (latlng) { |
2012 | | var d = Math.PI / 180, |
2013 | | max = this.MAX_LATITUDE, |
2014 | | lat = Math.max(Math.min(max, latlng.lat), -max), |
2015 | | sin = Math.sin(lat * d); |
2016 | | |
2017 | | return new L.Point( |
2018 | | this.R * latlng.lng * d, |
2019 | | this.R * Math.log((1 + sin) / (1 - sin)) / 2); |
2020 | | }, |
2021 | | |
2022 | | unproject: function (point) { |
2023 | | var d = 180 / Math.PI; |
2024 | | |
2025 | | return new L.LatLng( |
2026 | | (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, |
2027 | | point.x * d / this.R); |
2028 | | }, |
2029 | | |
2030 | | bounds: (function () { |
2031 | | var d = 6378137 * Math.PI; |
2032 | | return L.bounds([-d, -d], [d, d]); |
2033 | | })() |
2034 | | }; |
2035 | | |
2036 | | |
2037 | | |
2038 | | /* |
2039 | | * @class CRS |
2040 | | * @aka L.CRS |
2041 | | * Abstract class that defines coordinate reference systems for projecting |
| 1436 | * @namespace CRS |
| 1437 | * @crs L.CRS.Base |
| 1438 | * Object that defines coordinate reference systems for projecting |
2281 | | |
| 1942 | /* |
| 1943 | * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. |
| 1944 | */ |
| 1945 | |
| 1946 | |
| 1947 | var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown'; |
| 1948 | var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove'; |
| 1949 | var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup'; |
| 1950 | var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel'; |
| 1951 | var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION']; |
| 1952 | var _pointers = {}; |
| 1953 | var _pointerDocListener = false; |
| 1954 | |
| 1955 | // DomEvent.DoubleTap needs to know about this |
| 1956 | var _pointersCount = 0; |
| 1957 | |
| 1958 | // Provides a touch events wrapper for (ms)pointer events. |
| 1959 | // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 |
| 1960 | |
| 1961 | function addPointerListener(obj, type, handler, id) { |
| 1962 | if (type === 'touchstart') { |
| 1963 | _addPointerStart(obj, handler, id); |
| 1964 | |
| 1965 | } else if (type === 'touchmove') { |
| 1966 | _addPointerMove(obj, handler, id); |
| 1967 | |
| 1968 | } else if (type === 'touchend') { |
| 1969 | _addPointerEnd(obj, handler, id); |
| 1970 | } |
| 1971 | |
| 1972 | return this; |
| 1973 | } |
| 1974 | |
| 1975 | function removePointerListener(obj, type, id) { |
| 1976 | var handler = obj['_leaflet_' + type + id]; |
| 1977 | |
| 1978 | if (type === 'touchstart') { |
| 1979 | obj.removeEventListener(POINTER_DOWN, handler, false); |
| 1980 | |
| 1981 | } else if (type === 'touchmove') { |
| 1982 | obj.removeEventListener(POINTER_MOVE, handler, false); |
| 1983 | |
| 1984 | } else if (type === 'touchend') { |
| 1985 | obj.removeEventListener(POINTER_UP, handler, false); |
| 1986 | obj.removeEventListener(POINTER_CANCEL, handler, false); |
| 1987 | } |
| 1988 | |
| 1989 | return this; |
| 1990 | } |
| 1991 | |
| 1992 | function _addPointerStart(obj, handler, id) { |
| 1993 | var onDown = bind(function (e) { |
| 1994 | if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { |
| 1995 | // In IE11, some touch events needs to fire for form controls, or |
| 1996 | // the controls will stop working. We keep a whitelist of tag names that |
| 1997 | // need these events. For other target tags, we prevent default on the event. |
| 1998 | if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { |
| 1999 | preventDefault(e); |
| 2000 | } else { |
| 2001 | return; |
| 2002 | } |
| 2003 | } |
| 2004 | |
| 2005 | _handlePointer(e, handler); |
| 2006 | }); |
| 2007 | |
| 2008 | obj['_leaflet_touchstart' + id] = onDown; |
| 2009 | obj.addEventListener(POINTER_DOWN, onDown, false); |
| 2010 | |
| 2011 | // need to keep track of what pointers and how many are active to provide e.touches emulation |
| 2012 | if (!_pointerDocListener) { |
| 2013 | // we listen documentElement as any drags that end by moving the touch off the screen get fired there |
| 2014 | document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true); |
| 2015 | document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true); |
| 2016 | document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true); |
| 2017 | document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true); |
| 2018 | |
| 2019 | _pointerDocListener = true; |
| 2020 | } |
| 2021 | } |
| 2022 | |
| 2023 | function _globalPointerDown(e) { |
| 2024 | _pointers[e.pointerId] = e; |
| 2025 | _pointersCount++; |
| 2026 | } |
| 2027 | |
| 2028 | function _globalPointerMove(e) { |
| 2029 | if (_pointers[e.pointerId]) { |
| 2030 | _pointers[e.pointerId] = e; |
| 2031 | } |
| 2032 | } |
| 2033 | |
| 2034 | function _globalPointerUp(e) { |
| 2035 | delete _pointers[e.pointerId]; |
| 2036 | _pointersCount--; |
| 2037 | } |
| 2038 | |
| 2039 | function _handlePointer(e, handler) { |
| 2040 | e.touches = []; |
| 2041 | for (var i in _pointers) { |
| 2042 | e.touches.push(_pointers[i]); |
| 2043 | } |
| 2044 | e.changedTouches = [e]; |
| 2045 | |
| 2046 | handler(e); |
| 2047 | } |
| 2048 | |
| 2049 | function _addPointerMove(obj, handler, id) { |
| 2050 | var onMove = function (e) { |
| 2051 | // don't fire touch moves when mouse isn't down |
| 2052 | if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } |
| 2053 | |
| 2054 | _handlePointer(e, handler); |
| 2055 | }; |
| 2056 | |
| 2057 | obj['_leaflet_touchmove' + id] = onMove; |
| 2058 | obj.addEventListener(POINTER_MOVE, onMove, false); |
| 2059 | } |
| 2060 | |
| 2061 | function _addPointerEnd(obj, handler, id) { |
| 2062 | var onUp = function (e) { |
| 2063 | _handlePointer(e, handler); |
| 2064 | }; |
| 2065 | |
| 2066 | obj['_leaflet_touchend' + id] = onUp; |
| 2067 | obj.addEventListener(POINTER_UP, onUp, false); |
| 2068 | obj.addEventListener(POINTER_CANCEL, onUp, false); |
| 2069 | } |
| 2070 | |
| 2071 | /* |
| 2072 | * Extends the event handling code with double tap support for mobile browsers. |
| 2073 | */ |
| 2074 | |
| 2075 | var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart'; |
| 2076 | var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend'; |
| 2077 | var _pre = '_leaflet_'; |
| 2078 | |
| 2079 | // inspired by Zepto touch code by Thomas Fuchs |
| 2080 | function addDoubleTapListener(obj, handler, id) { |
| 2081 | var last, touch$$1, |
| 2082 | doubleTap = false, |
| 2083 | delay = 250; |
| 2084 | |
| 2085 | function onTouchStart(e) { |
| 2086 | var count; |
| 2087 | |
| 2088 | if (pointer) { |
| 2089 | if ((!edge) || e.pointerType === 'mouse') { return; } |
| 2090 | count = _pointersCount; |
| 2091 | } else { |
| 2092 | count = e.touches.length; |
| 2093 | } |
| 2094 | |
| 2095 | if (count > 1) { return; } |
| 2096 | |
| 2097 | var now = Date.now(), |
| 2098 | delta = now - (last || now); |
| 2099 | |
| 2100 | touch$$1 = e.touches ? e.touches[0] : e; |
| 2101 | doubleTap = (delta > 0 && delta <= delay); |
| 2102 | last = now; |
| 2103 | } |
| 2104 | |
| 2105 | function onTouchEnd(e) { |
| 2106 | if (doubleTap && !touch$$1.cancelBubble) { |
| 2107 | if (pointer) { |
| 2108 | if ((!edge) || e.pointerType === 'mouse') { return; } |
| 2109 | // work around .type being readonly with MSPointer* events |
| 2110 | var newTouch = {}, |
| 2111 | prop, i; |
| 2112 | |
| 2113 | for (i in touch$$1) { |
| 2114 | prop = touch$$1[i]; |
| 2115 | newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop; |
| 2116 | } |
| 2117 | touch$$1 = newTouch; |
| 2118 | } |
| 2119 | touch$$1.type = 'dblclick'; |
| 2120 | handler(touch$$1); |
| 2121 | last = null; |
| 2122 | } |
| 2123 | } |
| 2124 | |
| 2125 | obj[_pre + _touchstart + id] = onTouchStart; |
| 2126 | obj[_pre + _touchend + id] = onTouchEnd; |
| 2127 | obj[_pre + 'dblclick' + id] = handler; |
| 2128 | |
| 2129 | obj.addEventListener(_touchstart, onTouchStart, false); |
| 2130 | obj.addEventListener(_touchend, onTouchEnd, false); |
| 2131 | |
| 2132 | // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), |
| 2133 | // the browser doesn't fire touchend/pointerup events but does fire |
| 2134 | // native dblclicks. See #4127. |
| 2135 | // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. |
| 2136 | obj.addEventListener('dblclick', handler, false); |
| 2137 | |
| 2138 | return this; |
| 2139 | } |
| 2140 | |
| 2141 | function removeDoubleTapListener(obj, id) { |
| 2142 | var touchstart = obj[_pre + _touchstart + id], |
| 2143 | touchend = obj[_pre + _touchend + id], |
| 2144 | dblclick = obj[_pre + 'dblclick' + id]; |
| 2145 | |
| 2146 | obj.removeEventListener(_touchstart, touchstart, false); |
| 2147 | obj.removeEventListener(_touchend, touchend, false); |
| 2148 | if (!edge) { |
| 2149 | obj.removeEventListener('dblclick', dblclick, false); |
| 2150 | } |
| 2151 | |
| 2152 | return this; |
| 2153 | } |
| 2154 | |
| 2155 | /* |
| 2156 | * @namespace DomEvent |
| 2157 | * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. |
| 2158 | */ |
| 2159 | |
| 2160 | // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. |
| 2161 | |
| 2162 | // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this |
| 2163 | // Adds a listener function (`fn`) to a particular DOM event type of the |
| 2164 | // element `el`. You can optionally specify the context of the listener |
| 2165 | // (object the `this` keyword will point to). You can also pass several |
| 2166 | // space-separated types (e.g. `'click dblclick'`). |
| 2167 | |
| 2168 | // @alternative |
| 2169 | // @function on(el: HTMLElement, eventMap: Object, context?: Object): this |
| 2170 | // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` |
| 2171 | function on(obj, types, fn, context) { |
| 2172 | |
| 2173 | if (typeof types === 'object') { |
| 2174 | for (var type in types) { |
| 2175 | addOne(obj, type, types[type], fn); |
| 2176 | } |
| 2177 | } else { |
| 2178 | types = splitWords(types); |
| 2179 | |
| 2180 | for (var i = 0, len = types.length; i < len; i++) { |
| 2181 | addOne(obj, types[i], fn, context); |
| 2182 | } |
| 2183 | } |
| 2184 | |
| 2185 | return this; |
| 2186 | } |
| 2187 | |
| 2188 | var eventsKey = '_leaflet_events'; |
| 2189 | |
| 2190 | // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this |
| 2191 | // Removes a previously added listener function. If no function is specified, |
| 2192 | // it will remove all the listeners of that particular DOM event from the element. |
| 2193 | // Note that if you passed a custom context to on, you must pass the same |
| 2194 | // context to `off` in order to remove the listener. |
| 2195 | |
| 2196 | // @alternative |
| 2197 | // @function off(el: HTMLElement, eventMap: Object, context?: Object): this |
| 2198 | // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` |
| 2199 | |
| 2200 | // @alternative |
| 2201 | // @function off(el: HTMLElement): this |
| 2202 | // Removes all known event listeners |
| 2203 | function off(obj, types, fn, context) { |
| 2204 | |
| 2205 | if (typeof types === 'object') { |
| 2206 | for (var type in types) { |
| 2207 | removeOne(obj, type, types[type], fn); |
| 2208 | } |
| 2209 | } else if (types) { |
| 2210 | types = splitWords(types); |
| 2211 | |
| 2212 | for (var i = 0, len = types.length; i < len; i++) { |
| 2213 | removeOne(obj, types[i], fn, context); |
| 2214 | } |
| 2215 | } else { |
| 2216 | for (var j in obj[eventsKey]) { |
| 2217 | removeOne(obj, j, obj[eventsKey][j]); |
| 2218 | } |
| 2219 | delete obj[eventsKey]; |
| 2220 | } |
| 2221 | |
| 2222 | return this; |
| 2223 | } |
| 2224 | |
| 2225 | function addOne(obj, type, fn, context) { |
| 2226 | var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); |
| 2227 | |
| 2228 | if (obj[eventsKey] && obj[eventsKey][id]) { return this; } |
| 2229 | |
| 2230 | var handler = function (e) { |
| 2231 | return fn.call(context || obj, e || window.event); |
| 2232 | }; |
| 2233 | |
| 2234 | var originalHandler = handler; |
| 2235 | |
| 2236 | if (pointer && type.indexOf('touch') === 0) { |
| 2237 | // Needs DomEvent.Pointer.js |
| 2238 | addPointerListener(obj, type, handler, id); |
| 2239 | |
| 2240 | } else if (touch && (type === 'dblclick') && addDoubleTapListener && |
| 2241 | !(pointer && chrome)) { |
| 2242 | // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener |
| 2243 | // See #5180 |
| 2244 | addDoubleTapListener(obj, handler, id); |
| 2245 | |
| 2246 | } else if ('addEventListener' in obj) { |
| 2247 | |
| 2248 | if (type === 'mousewheel') { |
| 2249 | obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); |
| 2250 | |
| 2251 | } else if ((type === 'mouseenter') || (type === 'mouseleave')) { |
| 2252 | handler = function (e) { |
| 2253 | e = e || window.event; |
| 2254 | if (isExternalTarget(obj, e)) { |
| 2255 | originalHandler(e); |
| 2256 | } |
| 2257 | }; |
| 2258 | obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); |
| 2259 | |
| 2260 | } else { |
| 2261 | if (type === 'click' && android) { |
| 2262 | handler = function (e) { |
| 2263 | filterClick(e, originalHandler); |
| 2264 | }; |
| 2265 | } |
| 2266 | obj.addEventListener(type, handler, false); |
| 2267 | } |
| 2268 | |
| 2269 | } else if ('attachEvent' in obj) { |
| 2270 | obj.attachEvent('on' + type, handler); |
| 2271 | } |
| 2272 | |
| 2273 | obj[eventsKey] = obj[eventsKey] || {}; |
| 2274 | obj[eventsKey][id] = handler; |
| 2275 | } |
| 2276 | |
| 2277 | function removeOne(obj, type, fn, context) { |
| 2278 | |
| 2279 | var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''), |
| 2280 | handler = obj[eventsKey] && obj[eventsKey][id]; |
| 2281 | |
| 2282 | if (!handler) { return this; } |
| 2283 | |
| 2284 | if (pointer && type.indexOf('touch') === 0) { |
| 2285 | removePointerListener(obj, type, id); |
| 2286 | |
| 2287 | } else if (touch && (type === 'dblclick') && removeDoubleTapListener) { |
| 2288 | removeDoubleTapListener(obj, id); |
| 2289 | |
| 2290 | } else if ('removeEventListener' in obj) { |
| 2291 | |
| 2292 | if (type === 'mousewheel') { |
| 2293 | obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); |
| 2294 | |
| 2295 | } else { |
| 2296 | obj.removeEventListener( |
| 2297 | type === 'mouseenter' ? 'mouseover' : |
| 2298 | type === 'mouseleave' ? 'mouseout' : type, handler, false); |
| 2299 | } |
| 2300 | |
| 2301 | } else if ('detachEvent' in obj) { |
| 2302 | obj.detachEvent('on' + type, handler); |
| 2303 | } |
| 2304 | |
| 2305 | obj[eventsKey][id] = null; |
| 2306 | } |
| 2307 | |
| 2308 | // @function stopPropagation(ev: DOMEvent): this |
| 2309 | // Stop the given event from propagation to parent elements. Used inside the listener functions: |
| 2310 | // ```js |
| 2311 | // L.DomEvent.on(div, 'click', function (ev) { |
| 2312 | // L.DomEvent.stopPropagation(ev); |
| 2313 | // }); |
| 2314 | // ``` |
| 2315 | function stopPropagation(e) { |
| 2316 | |
| 2317 | if (e.stopPropagation) { |
| 2318 | e.stopPropagation(); |
| 2319 | } else if (e.originalEvent) { // In case of Leaflet event. |
| 2320 | e.originalEvent._stopped = true; |
| 2321 | } else { |
| 2322 | e.cancelBubble = true; |
| 2323 | } |
| 2324 | skipped(e); |
| 2325 | |
| 2326 | return this; |
| 2327 | } |
| 2328 | |
| 2329 | // @function disableScrollPropagation(el: HTMLElement): this |
| 2330 | // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). |
| 2331 | function disableScrollPropagation(el) { |
| 2332 | addOne(el, 'mousewheel', stopPropagation); |
| 2333 | return this; |
| 2334 | } |
| 2335 | |
| 2336 | // @function disableClickPropagation(el: HTMLElement): this |
| 2337 | // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, |
| 2338 | // `'mousedown'` and `'touchstart'` events (plus browser variants). |
| 2339 | function disableClickPropagation(el) { |
| 2340 | on(el, 'mousedown touchstart dblclick', stopPropagation); |
| 2341 | addOne(el, 'click', fakeStop); |
| 2342 | return this; |
| 2343 | } |
| 2344 | |
| 2345 | // @function preventDefault(ev: DOMEvent): this |
| 2346 | // Prevents the default action of the DOM Event `ev` from happening (such as |
| 2347 | // following a link in the href of the a element, or doing a POST request |
| 2348 | // with page reload when a `<form>` is submitted). |
| 2349 | // Use it inside listener functions. |
| 2350 | function preventDefault(e) { |
| 2351 | if (e.preventDefault) { |
| 2352 | e.preventDefault(); |
| 2353 | } else { |
| 2354 | e.returnValue = false; |
| 2355 | } |
| 2356 | return this; |
| 2357 | } |
| 2358 | |
| 2359 | // @function stop(ev): this |
| 2360 | // Does `stopPropagation` and `preventDefault` at the same time. |
| 2361 | function stop(e) { |
| 2362 | preventDefault(e); |
| 2363 | stopPropagation(e); |
| 2364 | return this; |
| 2365 | } |
| 2366 | |
| 2367 | // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point |
| 2368 | // Gets normalized mouse position from a DOM event relative to the |
| 2369 | // `container` or to the whole page if not specified. |
| 2370 | function getMousePosition(e, container) { |
| 2371 | if (!container) { |
| 2372 | return new Point(e.clientX, e.clientY); |
| 2373 | } |
| 2374 | |
| 2375 | var rect = container.getBoundingClientRect(); |
| 2376 | |
| 2377 | return new Point( |
| 2378 | e.clientX - rect.left - container.clientLeft, |
| 2379 | e.clientY - rect.top - container.clientTop); |
| 2380 | } |
| 2381 | |
| 2382 | // Chrome on Win scrolls double the pixels as in other platforms (see #4538), |
| 2383 | // and Firefox scrolls device pixels, not CSS pixels |
| 2384 | var wheelPxFactor = |
| 2385 | (win && chrome) ? 2 * window.devicePixelRatio : |
| 2386 | gecko ? window.devicePixelRatio : 1; |
| 2387 | |
| 2388 | // @function getWheelDelta(ev: DOMEvent): Number |
| 2389 | // Gets normalized wheel delta from a mousewheel DOM event, in vertical |
| 2390 | // pixels scrolled (negative if scrolling down). |
| 2391 | // Events from pointing devices without precise scrolling are mapped to |
| 2392 | // a best guess of 60 pixels. |
| 2393 | function getWheelDelta(e) { |
| 2394 | return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta |
| 2395 | (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels |
| 2396 | (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines |
| 2397 | (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages |
| 2398 | (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events |
| 2399 | e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels |
| 2400 | (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines |
| 2401 | e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages |
| 2402 | 0; |
| 2403 | } |
| 2404 | |
| 2405 | var skipEvents = {}; |
| 2406 | |
| 2407 | function fakeStop(e) { |
| 2408 | // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) |
| 2409 | skipEvents[e.type] = true; |
| 2410 | } |
| 2411 | |
| 2412 | function skipped(e) { |
| 2413 | var events = skipEvents[e.type]; |
| 2414 | // reset when checking, as it's only used in map container and propagates outside of the map |
| 2415 | skipEvents[e.type] = false; |
| 2416 | return events; |
| 2417 | } |
| 2418 | |
| 2419 | // check if element really left/entered the event target (for mouseenter/mouseleave) |
| 2420 | function isExternalTarget(el, e) { |
| 2421 | |
| 2422 | var related = e.relatedTarget; |
| 2423 | |
| 2424 | if (!related) { return true; } |
| 2425 | |
| 2426 | try { |
| 2427 | while (related && (related !== el)) { |
| 2428 | related = related.parentNode; |
| 2429 | } |
| 2430 | } catch (err) { |
| 2431 | return false; |
| 2432 | } |
| 2433 | return (related !== el); |
| 2434 | } |
| 2435 | |
| 2436 | var lastClick; |
| 2437 | |
| 2438 | // this is a horrible workaround for a bug in Android where a single touch triggers two click events |
| 2439 | function filterClick(e, handler) { |
| 2440 | var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), |
| 2441 | elapsed = lastClick && (timeStamp - lastClick); |
| 2442 | |
| 2443 | // are they closer together than 500ms yet more than 100ms? |
| 2444 | // Android typically triggers them ~300ms apart while multiple listeners |
| 2445 | // on the same event should be triggered far faster; |
| 2446 | // or check if click is simulated on the element, and if it is, reject any non-simulated events |
| 2447 | |
| 2448 | if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { |
| 2449 | stop(e); |
| 2450 | return; |
| 2451 | } |
| 2452 | lastClick = timeStamp; |
| 2453 | |
| 2454 | handler(e); |
| 2455 | } |
| 2456 | |
| 2457 | |
| 2458 | |
| 2459 | |
| 2460 | var DomEvent = (Object.freeze || Object)({ |
| 2461 | on: on, |
| 2462 | off: off, |
| 2463 | stopPropagation: stopPropagation, |
| 2464 | disableScrollPropagation: disableScrollPropagation, |
| 2465 | disableClickPropagation: disableClickPropagation, |
| 2466 | preventDefault: preventDefault, |
| 2467 | stop: stop, |
| 2468 | getMousePosition: getMousePosition, |
| 2469 | getWheelDelta: getWheelDelta, |
| 2470 | fakeStop: fakeStop, |
| 2471 | skipped: skipped, |
| 2472 | isExternalTarget: isExternalTarget, |
| 2473 | addListener: on, |
| 2474 | removeListener: off |
| 2475 | }); |
| 2476 | |
| 2477 | /* |
| 2478 | * @namespace DomUtil |
| 2479 | * |
| 2480 | * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) |
| 2481 | * tree, used by Leaflet internally. |
| 2482 | * |
| 2483 | * Most functions expecting or returning a `HTMLElement` also work for |
| 2484 | * SVG elements. The only difference is that classes refer to CSS classes |
| 2485 | * in HTML and SVG classes in SVG. |
| 2486 | */ |
| 2487 | |
| 2488 | |
| 2489 | // @property TRANSFORM: String |
| 2490 | // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). |
| 2491 | var TRANSFORM = testProp( |
| 2492 | ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); |
| 2493 | |
| 2494 | // webkitTransition comes first because some browser versions that drop vendor prefix don't do |
| 2495 | // the same for the transitionend event, in particular the Android 4.1 stock browser |
| 2496 | |
| 2497 | // @property TRANSITION: String |
| 2498 | // Vendor-prefixed transition style name. |
| 2499 | var TRANSITION = testProp( |
| 2500 | ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); |
| 2501 | |
| 2502 | // @property TRANSITION_END: String |
| 2503 | // Vendor-prefixed transitionend event name. |
| 2504 | var TRANSITION_END = |
| 2505 | TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; |
| 2506 | |
| 2507 | |
| 2508 | // @function get(id: String|HTMLElement): HTMLElement |
| 2509 | // Returns an element given its DOM id, or returns the element itself |
| 2510 | // if it was passed directly. |
| 2511 | function get(id) { |
| 2512 | return typeof id === 'string' ? document.getElementById(id) : id; |
| 2513 | } |
| 2514 | |
| 2515 | // @function getStyle(el: HTMLElement, styleAttrib: String): String |
| 2516 | // Returns the value for a certain style attribute on an element, |
| 2517 | // including computed values or values set through CSS. |
| 2518 | function getStyle(el, style) { |
| 2519 | var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); |
| 2520 | |
| 2521 | if ((!value || value === 'auto') && document.defaultView) { |
| 2522 | var css = document.defaultView.getComputedStyle(el, null); |
| 2523 | value = css ? css[style] : null; |
| 2524 | } |
| 2525 | return value === 'auto' ? null : value; |
| 2526 | } |
| 2527 | |
| 2528 | // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement |
| 2529 | // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. |
| 2530 | function create$1(tagName, className, container) { |
| 2531 | var el = document.createElement(tagName); |
| 2532 | el.className = className || ''; |
| 2533 | |
| 2534 | if (container) { |
| 2535 | container.appendChild(el); |
| 2536 | } |
| 2537 | return el; |
| 2538 | } |
| 2539 | |
| 2540 | // @function remove(el: HTMLElement) |
| 2541 | // Removes `el` from its parent element |
| 2542 | function remove(el) { |
| 2543 | var parent = el.parentNode; |
| 2544 | if (parent) { |
| 2545 | parent.removeChild(el); |
| 2546 | } |
| 2547 | } |
| 2548 | |
| 2549 | // @function empty(el: HTMLElement) |
| 2550 | // Removes all of `el`'s children elements from `el` |
| 2551 | function empty(el) { |
| 2552 | while (el.firstChild) { |
| 2553 | el.removeChild(el.firstChild); |
| 2554 | } |
| 2555 | } |
| 2556 | |
| 2557 | // @function toFront(el: HTMLElement) |
| 2558 | // Makes `el` the last child of its parent, so it renders in front of the other children. |
| 2559 | function toFront(el) { |
| 2560 | var parent = el.parentNode; |
| 2561 | if (parent.lastChild !== el) { |
| 2562 | parent.appendChild(el); |
| 2563 | } |
| 2564 | } |
| 2565 | |
| 2566 | // @function toBack(el: HTMLElement) |
| 2567 | // Makes `el` the first child of its parent, so it renders behind the other children. |
| 2568 | function toBack(el) { |
| 2569 | var parent = el.parentNode; |
| 2570 | if (parent.firstChild !== el) { |
| 2571 | parent.insertBefore(el, parent.firstChild); |
| 2572 | } |
| 2573 | } |
| 2574 | |
| 2575 | // @function hasClass(el: HTMLElement, name: String): Boolean |
| 2576 | // Returns `true` if the element's class attribute contains `name`. |
| 2577 | function hasClass(el, name) { |
| 2578 | if (el.classList !== undefined) { |
| 2579 | return el.classList.contains(name); |
| 2580 | } |
| 2581 | var className = getClass(el); |
| 2582 | return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); |
| 2583 | } |
| 2584 | |
| 2585 | // @function addClass(el: HTMLElement, name: String) |
| 2586 | // Adds `name` to the element's class attribute. |
| 2587 | function addClass(el, name) { |
| 2588 | if (el.classList !== undefined) { |
| 2589 | var classes = splitWords(name); |
| 2590 | for (var i = 0, len = classes.length; i < len; i++) { |
| 2591 | el.classList.add(classes[i]); |
| 2592 | } |
| 2593 | } else if (!hasClass(el, name)) { |
| 2594 | var className = getClass(el); |
| 2595 | setClass(el, (className ? className + ' ' : '') + name); |
| 2596 | } |
| 2597 | } |
| 2598 | |
| 2599 | // @function removeClass(el: HTMLElement, name: String) |
| 2600 | // Removes `name` from the element's class attribute. |
| 2601 | function removeClass(el, name) { |
| 2602 | if (el.classList !== undefined) { |
| 2603 | el.classList.remove(name); |
| 2604 | } else { |
| 2605 | setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); |
| 2606 | } |
| 2607 | } |
| 2608 | |
| 2609 | // @function setClass(el: HTMLElement, name: String) |
| 2610 | // Sets the element's class. |
| 2611 | function setClass(el, name) { |
| 2612 | if (el.className.baseVal === undefined) { |
| 2613 | el.className = name; |
| 2614 | } else { |
| 2615 | // in case of SVG element |
| 2616 | el.className.baseVal = name; |
| 2617 | } |
| 2618 | } |
| 2619 | |
| 2620 | // @function getClass(el: HTMLElement): String |
| 2621 | // Returns the element's class. |
| 2622 | function getClass(el) { |
| 2623 | return el.className.baseVal === undefined ? el.className : el.className.baseVal; |
| 2624 | } |
| 2625 | |
| 2626 | // @function setOpacity(el: HTMLElement, opacity: Number) |
| 2627 | // Set the opacity of an element (including old IE support). |
| 2628 | // `opacity` must be a number from `0` to `1`. |
| 2629 | function setOpacity(el, value) { |
| 2630 | if ('opacity' in el.style) { |
| 2631 | el.style.opacity = value; |
| 2632 | } else if ('filter' in el.style) { |
| 2633 | _setOpacityIE(el, value); |
| 2634 | } |
| 2635 | } |
| 2636 | |
| 2637 | function _setOpacityIE(el, value) { |
| 2638 | var filter = false, |
| 2639 | filterName = 'DXImageTransform.Microsoft.Alpha'; |
| 2640 | |
| 2641 | // filters collection throws an error if we try to retrieve a filter that doesn't exist |
| 2642 | try { |
| 2643 | filter = el.filters.item(filterName); |
| 2644 | } catch (e) { |
| 2645 | // don't set opacity to 1 if we haven't already set an opacity, |
| 2646 | // it isn't needed and breaks transparent pngs. |
| 2647 | if (value === 1) { return; } |
| 2648 | } |
| 2649 | |
| 2650 | value = Math.round(value * 100); |
| 2651 | |
| 2652 | if (filter) { |
| 2653 | filter.Enabled = (value !== 100); |
| 2654 | filter.Opacity = value; |
| 2655 | } else { |
| 2656 | el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; |
| 2657 | } |
| 2658 | } |
| 2659 | |
| 2660 | // @function testProp(props: String[]): String|false |
| 2661 | // Goes through the array of style names and returns the first name |
| 2662 | // that is a valid style name for an element. If no such name is found, |
| 2663 | // it returns false. Useful for vendor-prefixed styles like `transform`. |
| 2664 | function testProp(props) { |
| 2665 | var style = document.documentElement.style; |
| 2666 | |
| 2667 | for (var i = 0; i < props.length; i++) { |
| 2668 | if (props[i] in style) { |
| 2669 | return props[i]; |
| 2670 | } |
| 2671 | } |
| 2672 | return false; |
| 2673 | } |
| 2674 | |
| 2675 | // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) |
| 2676 | // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels |
| 2677 | // and optionally scaled by `scale`. Does not have an effect if the |
| 2678 | // browser doesn't support 3D CSS transforms. |
| 2679 | function setTransform(el, offset, scale) { |
| 2680 | var pos = offset || new Point(0, 0); |
| 2681 | |
| 2682 | el.style[TRANSFORM] = |
| 2683 | (ie3d ? |
| 2684 | 'translate(' + pos.x + 'px,' + pos.y + 'px)' : |
| 2685 | 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + |
| 2686 | (scale ? ' scale(' + scale + ')' : ''); |
| 2687 | } |
| 2688 | |
| 2689 | // @function setPosition(el: HTMLElement, position: Point) |
| 2690 | // Sets the position of `el` to coordinates specified by `position`, |
| 2691 | // using CSS translate or top/left positioning depending on the browser |
| 2692 | // (used by Leaflet internally to position its layers). |
| 2693 | function setPosition(el, point) { |
| 2694 | |
| 2695 | /*eslint-disable */ |
| 2696 | el._leaflet_pos = point; |
| 2697 | /*eslint-enable */ |
| 2698 | |
| 2699 | if (any3d) { |
| 2700 | setTransform(el, point); |
| 2701 | } else { |
| 2702 | el.style.left = point.x + 'px'; |
| 2703 | el.style.top = point.y + 'px'; |
| 2704 | } |
| 2705 | } |
| 2706 | |
| 2707 | // @function getPosition(el: HTMLElement): Point |
| 2708 | // Returns the coordinates of an element previously positioned with setPosition. |
| 2709 | function getPosition(el) { |
| 2710 | // this method is only used for elements previously positioned using setPosition, |
| 2711 | // so it's safe to cache the position for performance |
| 2712 | |
| 2713 | return el._leaflet_pos || new Point(0, 0); |
| 2714 | } |
| 2715 | |
| 2716 | // @function disableTextSelection() |
| 2717 | // Prevents the user from generating `selectstart` DOM events, usually generated |
| 2718 | // when the user drags the mouse through a page with text. Used internally |
| 2719 | // by Leaflet to override the behaviour of any click-and-drag interaction on |
| 2720 | // the map. Affects drag interactions on the whole document. |
| 2721 | |
| 2722 | // @function enableTextSelection() |
| 2723 | // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). |
| 2724 | var disableTextSelection; |
| 2725 | var enableTextSelection; |
| 2726 | var _userSelect; |
| 2727 | if ('onselectstart' in document) { |
| 2728 | disableTextSelection = function () { |
| 2729 | on(window, 'selectstart', preventDefault); |
| 2730 | }; |
| 2731 | enableTextSelection = function () { |
| 2732 | off(window, 'selectstart', preventDefault); |
| 2733 | }; |
| 2734 | } else { |
| 2735 | var userSelectProperty = testProp( |
| 2736 | ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); |
| 2737 | |
| 2738 | disableTextSelection = function () { |
| 2739 | if (userSelectProperty) { |
| 2740 | var style = document.documentElement.style; |
| 2741 | _userSelect = style[userSelectProperty]; |
| 2742 | style[userSelectProperty] = 'none'; |
| 2743 | } |
| 2744 | }; |
| 2745 | enableTextSelection = function () { |
| 2746 | if (userSelectProperty) { |
| 2747 | document.documentElement.style[userSelectProperty] = _userSelect; |
| 2748 | _userSelect = undefined; |
| 2749 | } |
| 2750 | }; |
| 2751 | } |
| 2752 | |
| 2753 | // @function disableImageDrag() |
| 2754 | // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but |
| 2755 | // for `dragstart` DOM events, usually generated when the user drags an image. |
| 2756 | function disableImageDrag() { |
| 2757 | on(window, 'dragstart', preventDefault); |
| 2758 | } |
| 2759 | |
| 2760 | // @function enableImageDrag() |
| 2761 | // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). |
| 2762 | function enableImageDrag() { |
| 2763 | off(window, 'dragstart', preventDefault); |
| 2764 | } |
| 2765 | |
| 2766 | var _outlineElement; |
| 2767 | var _outlineStyle; |
| 2768 | // @function preventOutline(el: HTMLElement) |
| 2769 | // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) |
| 2770 | // of the element `el` invisible. Used internally by Leaflet to prevent |
| 2771 | // focusable elements from displaying an outline when the user performs a |
| 2772 | // drag interaction on them. |
| 2773 | function preventOutline(element) { |
| 2774 | while (element.tabIndex === -1) { |
| 2775 | element = element.parentNode; |
| 2776 | } |
| 2777 | if (!element.style) { return; } |
| 2778 | restoreOutline(); |
| 2779 | _outlineElement = element; |
| 2780 | _outlineStyle = element.style.outline; |
| 2781 | element.style.outline = 'none'; |
| 2782 | on(window, 'keydown', restoreOutline); |
| 2783 | } |
| 2784 | |
| 2785 | // @function restoreOutline() |
| 2786 | // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). |
| 2787 | function restoreOutline() { |
| 2788 | if (!_outlineElement) { return; } |
| 2789 | _outlineElement.style.outline = _outlineStyle; |
| 2790 | _outlineElement = undefined; |
| 2791 | _outlineStyle = undefined; |
| 2792 | off(window, 'keydown', restoreOutline); |
| 2793 | } |
| 2794 | |
| 2795 | |
| 2796 | var DomUtil = (Object.freeze || Object)({ |
| 2797 | TRANSFORM: TRANSFORM, |
| 2798 | TRANSITION: TRANSITION, |
| 2799 | TRANSITION_END: TRANSITION_END, |
| 2800 | get: get, |
| 2801 | getStyle: getStyle, |
| 2802 | create: create$1, |
| 2803 | remove: remove, |
| 2804 | empty: empty, |
| 2805 | toFront: toFront, |
| 2806 | toBack: toBack, |
| 2807 | hasClass: hasClass, |
| 2808 | addClass: addClass, |
| 2809 | removeClass: removeClass, |
| 2810 | setClass: setClass, |
| 2811 | getClass: getClass, |
| 2812 | setOpacity: setOpacity, |
| 2813 | testProp: testProp, |
| 2814 | setTransform: setTransform, |
| 2815 | setPosition: setPosition, |
| 2816 | getPosition: getPosition, |
| 2817 | disableTextSelection: disableTextSelection, |
| 2818 | enableTextSelection: enableTextSelection, |
| 2819 | disableImageDrag: disableImageDrag, |
| 2820 | enableImageDrag: enableImageDrag, |
| 2821 | preventOutline: preventOutline, |
| 2822 | restoreOutline: restoreOutline |
| 2823 | }); |
| 2824 | |
| 2825 | /* |
| 2826 | * @class PosAnimation |
| 2827 | * @aka L.PosAnimation |
| 2828 | * @inherits Evented |
| 2829 | * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. |
| 2830 | * |
| 2831 | * @example |
| 2832 | * ```js |
| 2833 | * var fx = new L.PosAnimation(); |
| 2834 | * fx.run(el, [300, 500], 0.5); |
| 2835 | * ``` |
| 2836 | * |
| 2837 | * @constructor L.PosAnimation() |
| 2838 | * Creates a `PosAnimation` object. |
| 2839 | * |
| 2840 | */ |
| 2841 | |
| 2842 | var PosAnimation = Evented.extend({ |
| 2843 | |
| 2844 | // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) |
| 2845 | // Run an animation of a given element to a new position, optionally setting |
| 2846 | // duration in seconds (`0.25` by default) and easing linearity factor (3rd |
| 2847 | // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), |
| 2848 | // `0.5` by default). |
| 2849 | run: function (el, newPos, duration, easeLinearity) { |
| 2850 | this.stop(); |
| 2851 | |
| 2852 | this._el = el; |
| 2853 | this._inProgress = true; |
| 2854 | this._duration = duration || 0.25; |
| 2855 | this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); |
| 2856 | |
| 2857 | this._startPos = getPosition(el); |
| 2858 | this._offset = newPos.subtract(this._startPos); |
| 2859 | this._startTime = +new Date(); |
| 2860 | |
| 2861 | // @event start: Event |
| 2862 | // Fired when the animation starts |
| 2863 | this.fire('start'); |
| 2864 | |
| 2865 | this._animate(); |
| 2866 | }, |
| 2867 | |
| 2868 | // @method stop() |
| 2869 | // Stops the animation (if currently running). |
| 2870 | stop: function () { |
| 2871 | if (!this._inProgress) { return; } |
| 2872 | |
| 2873 | this._step(true); |
| 2874 | this._complete(); |
| 2875 | }, |
| 2876 | |
| 2877 | _animate: function () { |
| 2878 | // animation loop |
| 2879 | this._animId = requestAnimFrame(this._animate, this); |
| 2880 | this._step(); |
| 2881 | }, |
| 2882 | |
| 2883 | _step: function (round) { |
| 2884 | var elapsed = (+new Date()) - this._startTime, |
| 2885 | duration = this._duration * 1000; |
| 2886 | |
| 2887 | if (elapsed < duration) { |
| 2888 | this._runFrame(this._easeOut(elapsed / duration), round); |
| 2889 | } else { |
| 2890 | this._runFrame(1); |
| 2891 | this._complete(); |
| 2892 | } |
| 2893 | }, |
| 2894 | |
| 2895 | _runFrame: function (progress, round) { |
| 2896 | var pos = this._startPos.add(this._offset.multiplyBy(progress)); |
| 2897 | if (round) { |
| 2898 | pos._round(); |
| 2899 | } |
| 2900 | setPosition(this._el, pos); |
| 2901 | |
| 2902 | // @event step: Event |
| 2903 | // Fired continuously during the animation. |
| 2904 | this.fire('step'); |
| 2905 | }, |
| 2906 | |
| 2907 | _complete: function () { |
| 2908 | cancelAnimFrame(this._animId); |
| 2909 | |
| 2910 | this._inProgress = false; |
| 2911 | // @event end: Event |
| 2912 | // Fired when the animation ends. |
| 2913 | this.fire('end'); |
| 2914 | }, |
| 2915 | |
| 2916 | _easeOut: function (t) { |
| 2917 | return 1 - Math.pow(1 - t, this._easeOutPower); |
| 2918 | } |
| 2919 | }); |
3888 | | |
3889 | | |
| 4663 | /* @section Extension methods |
| 4664 | * @uninheritable |
| 4665 | * |
| 4666 | * Every control should extend from `L.Control` and (re-)implement the following methods. |
| 4667 | * |
| 4668 | * @method onAdd(map: Map): HTMLElement |
| 4669 | * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). |
| 4670 | * |
| 4671 | * @method onRemove(map: Map) |
| 4672 | * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). |
| 4673 | */ |
| 4674 | |
| 4675 | /* @namespace Map |
| 4676 | * @section Methods for Layers and Controls |
| 4677 | */ |
| 4678 | Map.include({ |
| 4679 | // @method addControl(control: Control): this |
| 4680 | // Adds the given control to the map |
| 4681 | addControl: function (control) { |
| 4682 | control.addTo(this); |
| 4683 | return this; |
| 4684 | }, |
| 4685 | |
| 4686 | // @method removeControl(control: Control): this |
| 4687 | // Removes the given control from the map |
| 4688 | removeControl: function (control) { |
| 4689 | control.remove(); |
| 4690 | return this; |
| 4691 | }, |
| 4692 | |
| 4693 | _initControlPos: function () { |
| 4694 | var corners = this._controlCorners = {}, |
| 4695 | l = 'leaflet-', |
| 4696 | container = this._controlContainer = |
| 4697 | create$1('div', l + 'control-container', this._container); |
| 4698 | |
| 4699 | function createCorner(vSide, hSide) { |
| 4700 | var className = l + vSide + ' ' + l + hSide; |
| 4701 | |
| 4702 | corners[vSide + hSide] = create$1('div', className, container); |
| 4703 | } |
| 4704 | |
| 4705 | createCorner('top', 'left'); |
| 4706 | createCorner('top', 'right'); |
| 4707 | createCorner('bottom', 'left'); |
| 4708 | createCorner('bottom', 'right'); |
| 4709 | }, |
| 4710 | |
| 4711 | _clearControlPos: function () { |
| 4712 | for (var i in this._controlCorners) { |
| 4713 | remove(this._controlCorners[i]); |
| 4714 | } |
| 4715 | remove(this._controlContainer); |
| 4716 | delete this._controlCorners; |
| 4717 | delete this._controlContainer; |
| 4718 | } |
| 4719 | }); |
| 4720 | |
| 4721 | /* |
| 4722 | * @class Control.Layers |
| 4723 | * @aka L.Control.Layers |
| 4724 | * @inherits Control |
| 4725 | * |
| 4726 | * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`. |
| 4727 | * |
| 4728 | * @example |
| 4729 | * |
| 4730 | * ```js |
| 4731 | * var baseLayers = { |
| 4732 | * "Mapbox": mapbox, |
| 4733 | * "OpenStreetMap": osm |
| 4734 | * }; |
| 4735 | * |
| 4736 | * var overlays = { |
| 4737 | * "Marker": marker, |
| 4738 | * "Roads": roadsLayer |
| 4739 | * }; |
| 4740 | * |
| 4741 | * L.control.layers(baseLayers, overlays).addTo(map); |
| 4742 | * ``` |
| 4743 | * |
| 4744 | * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: |
| 4745 | * |
| 4746 | * ```js |
| 4747 | * { |
| 4748 | * "<someName1>": layer1, |
| 4749 | * "<someName2>": layer2 |
| 4750 | * } |
| 4751 | * ``` |
| 4752 | * |
| 4753 | * The layer names can contain HTML, which allows you to add additional styling to the items: |
| 4754 | * |
| 4755 | * ```js |
| 4756 | * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} |
| 4757 | * ``` |
| 4758 | */ |
| 4759 | |
| 4760 | var Layers = Control.extend({ |
| 4761 | // @section |
| 4762 | // @aka Control.Layers options |
| 4763 | options: { |
| 4764 | // @option collapsed: Boolean = true |
| 4765 | // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. |
| 4766 | collapsed: true, |
| 4767 | position: 'topright', |
| 4768 | |
| 4769 | // @option autoZIndex: Boolean = true |
| 4770 | // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off. |
| 4771 | autoZIndex: true, |
| 4772 | |
| 4773 | // @option hideSingleBase: Boolean = false |
| 4774 | // If `true`, the base layers in the control will be hidden when there is only one. |
| 4775 | hideSingleBase: false, |
| 4776 | |
| 4777 | // @option sortLayers: Boolean = false |
| 4778 | // Whether to sort the layers. When `false`, layers will keep the order |
| 4779 | // in which they were added to the control. |
| 4780 | sortLayers: false, |
| 4781 | |
| 4782 | // @option sortFunction: Function = * |
| 4783 | // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) |
| 4784 | // that will be used for sorting the layers, when `sortLayers` is `true`. |
| 4785 | // The function receives both the `L.Layer` instances and their names, as in |
| 4786 | // `sortFunction(layerA, layerB, nameA, nameB)`. |
| 4787 | // By default, it sorts layers alphabetically by their name. |
| 4788 | sortFunction: function (layerA, layerB, nameA, nameB) { |
| 4789 | return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); |
| 4790 | } |
| 4791 | }, |
| 4792 | |
| 4793 | initialize: function (baseLayers, overlays, options) { |
| 4794 | setOptions(this, options); |
| 4795 | |
| 4796 | this._layerControlInputs = []; |
| 4797 | this._layers = []; |
| 4798 | this._lastZIndex = 0; |
| 4799 | this._handlingClick = false; |
| 4800 | |
| 4801 | for (var i in baseLayers) { |
| 4802 | this._addLayer(baseLayers[i], i); |
| 4803 | } |
| 4804 | |
| 4805 | for (i in overlays) { |
| 4806 | this._addLayer(overlays[i], i, true); |
| 4807 | } |
| 4808 | }, |
| 4809 | |
| 4810 | onAdd: function (map) { |
| 4811 | this._initLayout(); |
| 4812 | this._update(); |
| 4813 | |
| 4814 | this._map = map; |
| 4815 | map.on('zoomend', this._checkDisabledLayers, this); |
| 4816 | |
| 4817 | for (var i = 0; i < this._layers.length; i++) { |
| 4818 | this._layers[i].layer.on('add remove', this._onLayerChange, this); |
| 4819 | } |
| 4820 | |
| 4821 | return this._container; |
| 4822 | }, |
| 4823 | |
| 4824 | addTo: function (map) { |
| 4825 | Control.prototype.addTo.call(this, map); |
| 4826 | // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. |
| 4827 | return this._expandIfNotCollapsed(); |
| 4828 | }, |
| 4829 | |
| 4830 | onRemove: function () { |
| 4831 | this._map.off('zoomend', this._checkDisabledLayers, this); |
| 4832 | |
| 4833 | for (var i = 0; i < this._layers.length; i++) { |
| 4834 | this._layers[i].layer.off('add remove', this._onLayerChange, this); |
| 4835 | } |
| 4836 | }, |
| 4837 | |
| 4838 | // @method addBaseLayer(layer: Layer, name: String): this |
| 4839 | // Adds a base layer (radio button entry) with the given name to the control. |
| 4840 | addBaseLayer: function (layer, name) { |
| 4841 | this._addLayer(layer, name); |
| 4842 | return (this._map) ? this._update() : this; |
| 4843 | }, |
| 4844 | |
| 4845 | // @method addOverlay(layer: Layer, name: String): this |
| 4846 | // Adds an overlay (checkbox entry) with the given name to the control. |
| 4847 | addOverlay: function (layer, name) { |
| 4848 | this._addLayer(layer, name, true); |
| 4849 | return (this._map) ? this._update() : this; |
| 4850 | }, |
| 4851 | |
| 4852 | // @method removeLayer(layer: Layer): this |
| 4853 | // Remove the given layer from the control. |
| 4854 | removeLayer: function (layer) { |
| 4855 | layer.off('add remove', this._onLayerChange, this); |
| 4856 | |
| 4857 | var obj = this._getLayer(stamp(layer)); |
| 4858 | if (obj) { |
| 4859 | this._layers.splice(this._layers.indexOf(obj), 1); |
| 4860 | } |
| 4861 | return (this._map) ? this._update() : this; |
| 4862 | }, |
| 4863 | |
| 4864 | // @method expand(): this |
| 4865 | // Expand the control container if collapsed. |
| 4866 | expand: function () { |
| 4867 | addClass(this._container, 'leaflet-control-layers-expanded'); |
| 4868 | this._form.style.height = null; |
| 4869 | var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); |
| 4870 | if (acceptableHeight < this._form.clientHeight) { |
| 4871 | addClass(this._form, 'leaflet-control-layers-scrollbar'); |
| 4872 | this._form.style.height = acceptableHeight + 'px'; |
| 4873 | } else { |
| 4874 | removeClass(this._form, 'leaflet-control-layers-scrollbar'); |
| 4875 | } |
| 4876 | this._checkDisabledLayers(); |
| 4877 | return this; |
| 4878 | }, |
| 4879 | |
| 4880 | // @method collapse(): this |
| 4881 | // Collapse the control container if expanded. |
| 4882 | collapse: function () { |
| 4883 | removeClass(this._container, 'leaflet-control-layers-expanded'); |
| 4884 | return this; |
| 4885 | }, |
| 4886 | |
| 4887 | _initLayout: function () { |
| 4888 | var className = 'leaflet-control-layers', |
| 4889 | container = this._container = create$1('div', className), |
| 4890 | collapsed = this.options.collapsed; |
| 4891 | |
| 4892 | // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released |
| 4893 | container.setAttribute('aria-haspopup', true); |
| 4894 | |
| 4895 | disableClickPropagation(container); |
| 4896 | disableScrollPropagation(container); |
| 4897 | |
| 4898 | var form = this._form = create$1('form', className + '-list'); |
| 4899 | |
| 4900 | if (collapsed) { |
| 4901 | this._map.on('click', this.collapse, this); |
| 4902 | |
| 4903 | if (!android) { |
| 4904 | on(container, { |
| 4905 | mouseenter: this.expand, |
| 4906 | mouseleave: this.collapse |
| 4907 | }, this); |
| 4908 | } |
| 4909 | } |
| 4910 | |
| 4911 | var link = this._layersLink = create$1('a', className + '-toggle', container); |
| 4912 | link.href = '#'; |
| 4913 | link.title = 'Layers'; |
| 4914 | |
| 4915 | if (touch) { |
| 4916 | on(link, 'click', stop); |
| 4917 | on(link, 'click', this.expand, this); |
| 4918 | } else { |
| 4919 | on(link, 'focus', this.expand, this); |
| 4920 | } |
| 4921 | |
| 4922 | if (!collapsed) { |
| 4923 | this.expand(); |
| 4924 | } |
| 4925 | |
| 4926 | this._baseLayersList = create$1('div', className + '-base', form); |
| 4927 | this._separator = create$1('div', className + '-separator', form); |
| 4928 | this._overlaysList = create$1('div', className + '-overlays', form); |
| 4929 | |
| 4930 | container.appendChild(form); |
| 4931 | }, |
| 4932 | |
| 4933 | _getLayer: function (id) { |
| 4934 | for (var i = 0; i < this._layers.length; i++) { |
| 4935 | |
| 4936 | if (this._layers[i] && stamp(this._layers[i].layer) === id) { |
| 4937 | return this._layers[i]; |
| 4938 | } |
| 4939 | } |
| 4940 | }, |
| 4941 | |
| 4942 | _addLayer: function (layer, name, overlay) { |
| 4943 | if (this._map) { |
| 4944 | layer.on('add remove', this._onLayerChange, this); |
| 4945 | } |
| 4946 | |
| 4947 | this._layers.push({ |
| 4948 | layer: layer, |
| 4949 | name: name, |
| 4950 | overlay: overlay |
| 4951 | }); |
| 4952 | |
| 4953 | if (this.options.sortLayers) { |
| 4954 | this._layers.sort(bind(function (a, b) { |
| 4955 | return this.options.sortFunction(a.layer, b.layer, a.name, b.name); |
| 4956 | }, this)); |
| 4957 | } |
| 4958 | |
| 4959 | if (this.options.autoZIndex && layer.setZIndex) { |
| 4960 | this._lastZIndex++; |
| 4961 | layer.setZIndex(this._lastZIndex); |
| 4962 | } |
| 4963 | |
| 4964 | this._expandIfNotCollapsed(); |
| 4965 | }, |
| 4966 | |
| 4967 | _update: function () { |
| 4968 | if (!this._container) { return this; } |
| 4969 | |
| 4970 | empty(this._baseLayersList); |
| 4971 | empty(this._overlaysList); |
| 4972 | |
| 4973 | this._layerControlInputs = []; |
| 4974 | var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; |
| 4975 | |
| 4976 | for (i = 0; i < this._layers.length; i++) { |
| 4977 | obj = this._layers[i]; |
| 4978 | this._addItem(obj); |
| 4979 | overlaysPresent = overlaysPresent || obj.overlay; |
| 4980 | baseLayersPresent = baseLayersPresent || !obj.overlay; |
| 4981 | baseLayersCount += !obj.overlay ? 1 : 0; |
| 4982 | } |
| 4983 | |
| 4984 | // Hide base layers section if there's only one layer. |
| 4985 | if (this.options.hideSingleBase) { |
| 4986 | baseLayersPresent = baseLayersPresent && baseLayersCount > 1; |
| 4987 | this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; |
| 4988 | } |
| 4989 | |
| 4990 | this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; |
| 4991 | |
| 4992 | return this; |
| 4993 | }, |
| 4994 | |
| 4995 | _onLayerChange: function (e) { |
| 4996 | if (!this._handlingClick) { |
| 4997 | this._update(); |
| 4998 | } |
| 4999 | |
| 5000 | var obj = this._getLayer(stamp(e.target)); |
| 5001 | |
| 5002 | // @namespace Map |
| 5003 | // @section Layer events |
| 5004 | // @event baselayerchange: LayersControlEvent |
| 5005 | // Fired when the base layer is changed through the [layer control](#control-layers). |
| 5006 | // @event overlayadd: LayersControlEvent |
| 5007 | // Fired when an overlay is selected through the [layer control](#control-layers). |
| 5008 | // @event overlayremove: LayersControlEvent |
| 5009 | // Fired when an overlay is deselected through the [layer control](#control-layers). |
| 5010 | // @namespace Control.Layers |
| 5011 | var type = obj.overlay ? |
| 5012 | (e.type === 'add' ? 'overlayadd' : 'overlayremove') : |
| 5013 | (e.type === 'add' ? 'baselayerchange' : null); |
| 5014 | |
| 5015 | if (type) { |
| 5016 | this._map.fire(type, obj); |
| 5017 | } |
| 5018 | }, |
| 5019 | |
| 5020 | // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) |
| 5021 | _createRadioElement: function (name, checked) { |
| 5022 | |
| 5023 | var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + |
| 5024 | name + '"' + (checked ? ' checked="checked"' : '') + '/>'; |
| 5025 | |
| 5026 | var radioFragment = document.createElement('div'); |
| 5027 | radioFragment.innerHTML = radioHtml; |
| 5028 | |
| 5029 | return radioFragment.firstChild; |
| 5030 | }, |
| 5031 | |
| 5032 | _addItem: function (obj) { |
| 5033 | var label = document.createElement('label'), |
| 5034 | checked = this._map.hasLayer(obj.layer), |
| 5035 | input; |
| 5036 | |
| 5037 | if (obj.overlay) { |
| 5038 | input = document.createElement('input'); |
| 5039 | input.type = 'checkbox'; |
| 5040 | input.className = 'leaflet-control-layers-selector'; |
| 5041 | input.defaultChecked = checked; |
| 5042 | } else { |
| 5043 | input = this._createRadioElement('leaflet-base-layers', checked); |
| 5044 | } |
| 5045 | |
| 5046 | this._layerControlInputs.push(input); |
| 5047 | input.layerId = stamp(obj.layer); |
| 5048 | |
| 5049 | on(input, 'click', this._onInputClick, this); |
| 5050 | |
| 5051 | var name = document.createElement('span'); |
| 5052 | name.innerHTML = ' ' + obj.name; |
| 5053 | |
| 5054 | // Helps from preventing layer control flicker when checkboxes are disabled |
| 5055 | // https://github.com/Leaflet/Leaflet/issues/2771 |
| 5056 | var holder = document.createElement('div'); |
| 5057 | |
| 5058 | label.appendChild(holder); |
| 5059 | holder.appendChild(input); |
| 5060 | holder.appendChild(name); |
| 5061 | |
| 5062 | var container = obj.overlay ? this._overlaysList : this._baseLayersList; |
| 5063 | container.appendChild(label); |
| 5064 | |
| 5065 | this._checkDisabledLayers(); |
| 5066 | return label; |
| 5067 | }, |
| 5068 | |
| 5069 | _onInputClick: function () { |
| 5070 | var inputs = this._layerControlInputs, |
| 5071 | input, layer; |
| 5072 | var addedLayers = [], |
| 5073 | removedLayers = []; |
| 5074 | |
| 5075 | this._handlingClick = true; |
| 5076 | |
| 5077 | for (var i = inputs.length - 1; i >= 0; i--) { |
| 5078 | input = inputs[i]; |
| 5079 | layer = this._getLayer(input.layerId).layer; |
| 5080 | |
| 5081 | if (input.checked) { |
| 5082 | addedLayers.push(layer); |
| 5083 | } else if (!input.checked) { |
| 5084 | removedLayers.push(layer); |
| 5085 | } |
| 5086 | } |
| 5087 | |
| 5088 | // Bugfix issue 2318: Should remove all old layers before readding new ones |
| 5089 | for (i = 0; i < removedLayers.length; i++) { |
| 5090 | if (this._map.hasLayer(removedLayers[i])) { |
| 5091 | this._map.removeLayer(removedLayers[i]); |
| 5092 | } |
| 5093 | } |
| 5094 | for (i = 0; i < addedLayers.length; i++) { |
| 5095 | if (!this._map.hasLayer(addedLayers[i])) { |
| 5096 | this._map.addLayer(addedLayers[i]); |
| 5097 | } |
| 5098 | } |
| 5099 | |
| 5100 | this._handlingClick = false; |
| 5101 | |
| 5102 | this._refocusOnMap(); |
| 5103 | }, |
| 5104 | |
| 5105 | _checkDisabledLayers: function () { |
| 5106 | var inputs = this._layerControlInputs, |
| 5107 | input, |
| 5108 | layer, |
| 5109 | zoom = this._map.getZoom(); |
| 5110 | |
| 5111 | for (var i = inputs.length - 1; i >= 0; i--) { |
| 5112 | input = inputs[i]; |
| 5113 | layer = this._getLayer(input.layerId).layer; |
| 5114 | input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || |
| 5115 | (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); |
| 5116 | |
| 5117 | } |
| 5118 | }, |
| 5119 | |
| 5120 | _expandIfNotCollapsed: function () { |
| 5121 | if (this._map && !this.options.collapsed) { |
| 5122 | this.expand(); |
| 5123 | } |
| 5124 | return this; |
| 5125 | }, |
| 5126 | |
| 5127 | _expand: function () { |
| 5128 | // Backward compatibility, remove me in 1.1. |
| 5129 | return this.expand(); |
| 5130 | }, |
| 5131 | |
| 5132 | _collapse: function () { |
| 5133 | // Backward compatibility, remove me in 1.1. |
| 5134 | return this.collapse(); |
| 5135 | } |
| 5136 | |
| 5137 | }); |
| 5138 | |
| 5139 | |
| 5140 | // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) |
| 5141 | // Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. |
| 5142 | var layers = function (baseLayers, overlays, options) { |
| 5143 | return new Layers(baseLayers, overlays, options); |
| 5144 | }; |
| 5145 | |
| 5146 | /* |
| 5147 | * @class Control.Zoom |
| 5148 | * @aka L.Control.Zoom |
| 5149 | * @inherits Control |
| 5150 | * |
| 5151 | * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`. |
| 5152 | */ |
| 5153 | |
| 5154 | var Zoom = Control.extend({ |
| 5155 | // @section |
| 5156 | // @aka Control.Zoom options |
| 5157 | options: { |
| 5158 | position: 'topleft', |
| 5159 | |
| 5160 | // @option zoomInText: String = '+' |
| 5161 | // The text set on the 'zoom in' button. |
| 5162 | zoomInText: '+', |
| 5163 | |
| 5164 | // @option zoomInTitle: String = 'Zoom in' |
| 5165 | // The title set on the 'zoom in' button. |
| 5166 | zoomInTitle: 'Zoom in', |
| 5167 | |
| 5168 | // @option zoomOutText: String = '−' |
| 5169 | // The text set on the 'zoom out' button. |
| 5170 | zoomOutText: '−', |
| 5171 | |
| 5172 | // @option zoomOutTitle: String = 'Zoom out' |
| 5173 | // The title set on the 'zoom out' button. |
| 5174 | zoomOutTitle: 'Zoom out' |
| 5175 | }, |
| 5176 | |
| 5177 | onAdd: function (map) { |
| 5178 | var zoomName = 'leaflet-control-zoom', |
| 5179 | container = create$1('div', zoomName + ' leaflet-bar'), |
| 5180 | options = this.options; |
| 5181 | |
| 5182 | this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, |
| 5183 | zoomName + '-in', container, this._zoomIn); |
| 5184 | this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, |
| 5185 | zoomName + '-out', container, this._zoomOut); |
| 5186 | |
| 5187 | this._updateDisabled(); |
| 5188 | map.on('zoomend zoomlevelschange', this._updateDisabled, this); |
| 5189 | |
| 5190 | return container; |
| 5191 | }, |
| 5192 | |
| 5193 | onRemove: function (map) { |
| 5194 | map.off('zoomend zoomlevelschange', this._updateDisabled, this); |
| 5195 | }, |
| 5196 | |
| 5197 | disable: function () { |
| 5198 | this._disabled = true; |
| 5199 | this._updateDisabled(); |
| 5200 | return this; |
| 5201 | }, |
| 5202 | |
| 5203 | enable: function () { |
| 5204 | this._disabled = false; |
| 5205 | this._updateDisabled(); |
| 5206 | return this; |
| 5207 | }, |
| 5208 | |
| 5209 | _zoomIn: function (e) { |
| 5210 | if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { |
| 5211 | this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); |
| 5212 | } |
| 5213 | }, |
| 5214 | |
| 5215 | _zoomOut: function (e) { |
| 5216 | if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { |
| 5217 | this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); |
| 5218 | } |
| 5219 | }, |
| 5220 | |
| 5221 | _createButton: function (html, title, className, container, fn) { |
| 5222 | var link = create$1('a', className, container); |
| 5223 | link.innerHTML = html; |
| 5224 | link.href = '#'; |
| 5225 | link.title = title; |
| 5226 | |
| 5227 | /* |
| 5228 | * Will force screen readers like VoiceOver to read this as "Zoom in - button" |
| 5229 | */ |
| 5230 | link.setAttribute('role', 'button'); |
| 5231 | link.setAttribute('aria-label', title); |
| 5232 | |
| 5233 | disableClickPropagation(link); |
| 5234 | on(link, 'click', stop); |
| 5235 | on(link, 'click', fn, this); |
| 5236 | on(link, 'click', this._refocusOnMap, this); |
| 5237 | |
| 5238 | return link; |
| 5239 | }, |
| 5240 | |
| 5241 | _updateDisabled: function () { |
| 5242 | var map = this._map, |
| 5243 | className = 'leaflet-disabled'; |
| 5244 | |
| 5245 | removeClass(this._zoomInButton, className); |
| 5246 | removeClass(this._zoomOutButton, className); |
| 5247 | |
| 5248 | if (this._disabled || map._zoom === map.getMinZoom()) { |
| 5249 | addClass(this._zoomOutButton, className); |
| 5250 | } |
| 5251 | if (this._disabled || map._zoom === map.getMaxZoom()) { |
| 5252 | addClass(this._zoomInButton, className); |
| 5253 | } |
| 5254 | } |
| 5255 | }); |
| 5256 | |
| 5257 | // @namespace Map |
| 5258 | // @section Control options |
| 5259 | // @option zoomControl: Boolean = true |
| 5260 | // Whether a [zoom control](#control-zoom) is added to the map by default. |
| 5261 | Map.mergeOptions({ |
| 5262 | zoomControl: true |
| 5263 | }); |
| 5264 | |
| 5265 | Map.addInitHook(function () { |
| 5266 | if (this.options.zoomControl) { |
| 5267 | this.zoomControl = new Zoom(); |
| 5268 | this.addControl(this.zoomControl); |
| 5269 | } |
| 5270 | }); |
| 5271 | |
| 5272 | // @namespace Control.Zoom |
| 5273 | // @factory L.control.zoom(options: Control.Zoom options) |
| 5274 | // Creates a zoom control |
| 5275 | var zoom = function (options) { |
| 5276 | return new Zoom(options); |
| 5277 | }; |
| 5278 | |
| 5279 | /* |
| 5280 | * @class Control.Scale |
| 5281 | * @aka L.Control.Scale |
| 5282 | * @inherits Control |
| 5283 | * |
| 5284 | * 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`. |
| 5285 | * |
| 5286 | * @example |
| 5287 | * |
| 5288 | * ```js |
| 5289 | * L.control.scale().addTo(map); |
| 5290 | * ``` |
| 5291 | */ |
| 5292 | |
| 5293 | var Scale = Control.extend({ |
| 5294 | // @section |
| 5295 | // @aka Control.Scale options |
| 5296 | options: { |
| 5297 | position: 'bottomleft', |
| 5298 | |
| 5299 | // @option maxWidth: Number = 100 |
| 5300 | // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). |
| 5301 | maxWidth: 100, |
| 5302 | |
| 5303 | // @option metric: Boolean = True |
| 5304 | // Whether to show the metric scale line (m/km). |
| 5305 | metric: true, |
| 5306 | |
| 5307 | // @option imperial: Boolean = True |
| 5308 | // Whether to show the imperial scale line (mi/ft). |
| 5309 | imperial: true |
| 5310 | |
| 5311 | // @option updateWhenIdle: Boolean = false |
| 5312 | // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). |
| 5313 | }, |
| 5314 | |
| 5315 | onAdd: function (map) { |
| 5316 | var className = 'leaflet-control-scale', |
| 5317 | container = create$1('div', className), |
| 5318 | options = this.options; |
| 5319 | |
| 5320 | this._addScales(options, className + '-line', container); |
| 5321 | |
| 5322 | map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); |
| 5323 | map.whenReady(this._update, this); |
| 5324 | |
| 5325 | return container; |
| 5326 | }, |
| 5327 | |
| 5328 | onRemove: function (map) { |
| 5329 | map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); |
| 5330 | }, |
| 5331 | |
| 5332 | _addScales: function (options, className, container) { |
| 5333 | if (options.metric) { |
| 5334 | this._mScale = create$1('div', className, container); |
| 5335 | } |
| 5336 | if (options.imperial) { |
| 5337 | this._iScale = create$1('div', className, container); |
| 5338 | } |
| 5339 | }, |
| 5340 | |
| 5341 | _update: function () { |
| 5342 | var map = this._map, |
| 5343 | y = map.getSize().y / 2; |
| 5344 | |
| 5345 | var maxMeters = map.distance( |
| 5346 | map.containerPointToLatLng([0, y]), |
| 5347 | map.containerPointToLatLng([this.options.maxWidth, y])); |
| 5348 | |
| 5349 | this._updateScales(maxMeters); |
| 5350 | }, |
| 5351 | |
| 5352 | _updateScales: function (maxMeters) { |
| 5353 | if (this.options.metric && maxMeters) { |
| 5354 | this._updateMetric(maxMeters); |
| 5355 | } |
| 5356 | if (this.options.imperial && maxMeters) { |
| 5357 | this._updateImperial(maxMeters); |
| 5358 | } |
| 5359 | }, |
| 5360 | |
| 5361 | _updateMetric: function (maxMeters) { |
| 5362 | var meters = this._getRoundNum(maxMeters), |
| 5363 | label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; |
| 5364 | |
| 5365 | this._updateScale(this._mScale, label, meters / maxMeters); |
| 5366 | }, |
| 5367 | |
| 5368 | _updateImperial: function (maxMeters) { |
| 5369 | var maxFeet = maxMeters * 3.2808399, |
| 5370 | maxMiles, miles, feet; |
| 5371 | |
| 5372 | if (maxFeet > 5280) { |
| 5373 | maxMiles = maxFeet / 5280; |
| 5374 | miles = this._getRoundNum(maxMiles); |
| 5375 | this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); |
| 5376 | |
| 5377 | } else { |
| 5378 | feet = this._getRoundNum(maxFeet); |
| 5379 | this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); |
| 5380 | } |
| 5381 | }, |
| 5382 | |
| 5383 | _updateScale: function (scale, text, ratio) { |
| 5384 | scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; |
| 5385 | scale.innerHTML = text; |
| 5386 | }, |
| 5387 | |
| 5388 | _getRoundNum: function (num) { |
| 5389 | var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), |
| 5390 | d = num / pow10; |
| 5391 | |
| 5392 | d = d >= 10 ? 10 : |
| 5393 | d >= 5 ? 5 : |
| 5394 | d >= 3 ? 3 : |
| 5395 | d >= 2 ? 2 : 1; |
| 5396 | |
| 5397 | return pow10 * d; |
| 5398 | } |
| 5399 | }); |
| 5400 | |
| 5401 | |
| 5402 | // @factory L.control.scale(options?: Control.Scale options) |
| 5403 | // Creates an scale control with the given options. |
| 5404 | var scale = function (options) { |
| 5405 | return new Scale(options); |
| 5406 | }; |
| 5407 | |
| 5408 | /* |
| 5409 | * @class Control.Attribution |
| 5410 | * @aka L.Control.Attribution |
| 5411 | * @inherits Control |
| 5412 | * |
| 5413 | * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control. |
| 5414 | */ |
| 5415 | |
| 5416 | var Attribution = Control.extend({ |
| 5417 | // @section |
| 5418 | // @aka Control.Attribution options |
| 5419 | options: { |
| 5420 | position: 'bottomright', |
| 5421 | |
| 5422 | // @option prefix: String = 'Leaflet' |
| 5423 | // The HTML text shown before the attributions. Pass `false` to disable. |
| 5424 | prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' |
| 5425 | }, |
| 5426 | |
| 5427 | initialize: function (options) { |
| 5428 | setOptions(this, options); |
| 5429 | |
| 5430 | this._attributions = {}; |
| 5431 | }, |
| 5432 | |
| 5433 | onAdd: function (map) { |
| 5434 | map.attributionControl = this; |
| 5435 | this._container = create$1('div', 'leaflet-control-attribution'); |
| 5436 | disableClickPropagation(this._container); |
| 5437 | |
| 5438 | // TODO ugly, refactor |
| 5439 | for (var i in map._layers) { |
| 5440 | if (map._layers[i].getAttribution) { |
| 5441 | this.addAttribution(map._layers[i].getAttribution()); |
| 5442 | } |
| 5443 | } |
| 5444 | |
| 5445 | this._update(); |
| 5446 | |
| 5447 | return this._container; |
| 5448 | }, |
| 5449 | |
| 5450 | // @method setPrefix(prefix: String): this |
| 5451 | // Sets the text before the attributions. |
| 5452 | setPrefix: function (prefix) { |
| 5453 | this.options.prefix = prefix; |
| 5454 | this._update(); |
| 5455 | return this; |
| 5456 | }, |
| 5457 | |
| 5458 | // @method addAttribution(text: String): this |
| 5459 | // Adds an attribution text (e.g. `'Vector data © Mapbox'`). |
| 5460 | addAttribution: function (text) { |
| 5461 | if (!text) { return this; } |
| 5462 | |
| 5463 | if (!this._attributions[text]) { |
| 5464 | this._attributions[text] = 0; |
| 5465 | } |
| 5466 | this._attributions[text]++; |
| 5467 | |
| 5468 | this._update(); |
| 5469 | |
| 5470 | return this; |
| 5471 | }, |
| 5472 | |
| 5473 | // @method removeAttribution(text: String): this |
| 5474 | // Removes an attribution text. |
| 5475 | removeAttribution: function (text) { |
| 5476 | if (!text) { return this; } |
| 5477 | |
| 5478 | if (this._attributions[text]) { |
| 5479 | this._attributions[text]--; |
| 5480 | this._update(); |
| 5481 | } |
| 5482 | |
| 5483 | return this; |
| 5484 | }, |
| 5485 | |
| 5486 | _update: function () { |
| 5487 | if (!this._map) { return; } |
| 5488 | |
| 5489 | var attribs = []; |
| 5490 | |
| 5491 | for (var i in this._attributions) { |
| 5492 | if (this._attributions[i]) { |
| 5493 | attribs.push(i); |
| 5494 | } |
| 5495 | } |
| 5496 | |
| 5497 | var prefixAndAttribs = []; |
| 5498 | |
| 5499 | if (this.options.prefix) { |
| 5500 | prefixAndAttribs.push(this.options.prefix); |
| 5501 | } |
| 5502 | if (attribs.length) { |
| 5503 | prefixAndAttribs.push(attribs.join(', ')); |
| 5504 | } |
| 5505 | |
| 5506 | this._container.innerHTML = prefixAndAttribs.join(' | '); |
| 5507 | } |
| 5508 | }); |
| 5509 | |
| 5510 | // @namespace Map |
| 5511 | // @section Control options |
| 5512 | // @option attributionControl: Boolean = true |
| 5513 | // Whether a [attribution control](#control-attribution) is added to the map by default. |
| 5514 | Map.mergeOptions({ |
| 5515 | attributionControl: true |
| 5516 | }); |
| 5517 | |
| 5518 | Map.addInitHook(function () { |
| 5519 | if (this.options.attributionControl) { |
| 5520 | new Attribution().addTo(this); |
| 5521 | } |
| 5522 | }); |
| 5523 | |
| 5524 | // @namespace Control.Attribution |
| 5525 | // @factory L.control.attribution(options: Control.Attribution options) |
| 5526 | // Creates an attribution control. |
| 5527 | var attribution = function (options) { |
| 5528 | return new Attribution(options); |
| 5529 | }; |
| 5530 | |
| 5531 | Control.Layers = Layers; |
| 5532 | Control.Zoom = Zoom; |
| 5533 | Control.Scale = Scale; |
| 5534 | Control.Attribution = Attribution; |
| 5535 | |
| 5536 | control.layers = layers; |
| 5537 | control.zoom = zoom; |
| 5538 | control.scale = scale; |
| 5539 | control.attribution = attribution; |
| 5540 | |
| 5541 | /* |
| 5542 | L.Handler is a base class for handler classes that are used internally to inject |
| 5543 | interaction features like dragging to classes like Map and Marker. |
| 5544 | */ |
| 5545 | |
| 5546 | // @class Handler |
| 5547 | // @aka L.Handler |
| 5548 | // Abstract class for map interaction handlers |
| 5549 | |
| 5550 | var Handler = Class.extend({ |
| 5551 | initialize: function (map) { |
| 5552 | this._map = map; |
| 5553 | }, |
| 5554 | |
| 5555 | // @method enable(): this |
| 5556 | // Enables the handler |
| 5557 | enable: function () { |
| 5558 | if (this._enabled) { return this; } |
| 5559 | |
| 5560 | this._enabled = true; |
| 5561 | this.addHooks(); |
| 5562 | return this; |
| 5563 | }, |
| 5564 | |
| 5565 | // @method disable(): this |
| 5566 | // Disables the handler |
| 5567 | disable: function () { |
| 5568 | if (!this._enabled) { return this; } |
| 5569 | |
| 5570 | this._enabled = false; |
| 5571 | this.removeHooks(); |
| 5572 | return this; |
| 5573 | }, |
| 5574 | |
| 5575 | // @method enabled(): Boolean |
| 5576 | // Returns `true` if the handler is enabled |
| 5577 | enabled: function () { |
| 5578 | return !!this._enabled; |
| 5579 | } |
| 5580 | |
| 5581 | // @section Extension methods |
| 5582 | // Classes inheriting from `Handler` must implement the two following methods: |
| 5583 | // @method addHooks() |
| 5584 | // Called when the handler is enabled, should add event hooks. |
| 5585 | // @method removeHooks() |
| 5586 | // Called when the handler is disabled, should remove the event hooks added previously. |
| 5587 | }); |
| 5588 | |
| 5589 | var Mixin = {Events: Events}; |
| 5590 | |
| 5591 | /* |
| 5592 | * @class Draggable |
| 5593 | * @aka L.Draggable |
| 5594 | * @inherits Evented |
| 5595 | * |
| 5596 | * A class for making DOM elements draggable (including touch support). |
| 5597 | * Used internally for map and marker dragging. Only works for elements |
| 5598 | * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). |
| 5599 | * |
| 5600 | * @example |
| 5601 | * ```js |
| 5602 | * var draggable = new L.Draggable(elementToDrag); |
| 5603 | * draggable.enable(); |
| 5604 | * ``` |
| 5605 | */ |
| 5606 | |
| 5607 | var START = touch ? 'touchstart mousedown' : 'mousedown'; |
| 5608 | var END = { |
| 5609 | mousedown: 'mouseup', |
| 5610 | touchstart: 'touchend', |
| 5611 | pointerdown: 'touchend', |
| 5612 | MSPointerDown: 'touchend' |
| 5613 | }; |
| 5614 | var MOVE = { |
| 5615 | mousedown: 'mousemove', |
| 5616 | touchstart: 'touchmove', |
| 5617 | pointerdown: 'touchmove', |
| 5618 | MSPointerDown: 'touchmove' |
| 5619 | }; |
| 5620 | |
| 5621 | |
| 5622 | var Draggable = Evented.extend({ |
| 5623 | |
| 5624 | options: { |
| 5625 | // @section |
| 5626 | // @aka Draggable options |
| 5627 | // @option clickTolerance: Number = 3 |
| 5628 | // The max number of pixels a user can shift the mouse pointer during a click |
| 5629 | // for it to be considered a valid click (as opposed to a mouse drag). |
| 5630 | clickTolerance: 3 |
| 5631 | }, |
| 5632 | |
| 5633 | // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) |
| 5634 | // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). |
| 5635 | initialize: function (element, dragStartTarget, preventOutline$$1, options) { |
| 5636 | setOptions(this, options); |
| 5637 | |
| 5638 | this._element = element; |
| 5639 | this._dragStartTarget = dragStartTarget || element; |
| 5640 | this._preventOutline = preventOutline$$1; |
| 5641 | }, |
| 5642 | |
| 5643 | // @method enable() |
| 5644 | // Enables the dragging ability |
| 5645 | enable: function () { |
| 5646 | if (this._enabled) { return; } |
| 5647 | |
| 5648 | on(this._dragStartTarget, START, this._onDown, this); |
| 5649 | |
| 5650 | this._enabled = true; |
| 5651 | }, |
| 5652 | |
| 5653 | // @method disable() |
| 5654 | // Disables the dragging ability |
| 5655 | disable: function () { |
| 5656 | if (!this._enabled) { return; } |
| 5657 | |
| 5658 | // If we're currently dragging this draggable, |
| 5659 | // disabling it counts as first ending the drag. |
| 5660 | if (Draggable._dragging === this) { |
| 5661 | this.finishDrag(); |
| 5662 | } |
| 5663 | |
| 5664 | off(this._dragStartTarget, START, this._onDown, this); |
| 5665 | |
| 5666 | this._enabled = false; |
| 5667 | this._moved = false; |
| 5668 | }, |
| 5669 | |
| 5670 | _onDown: function (e) { |
| 5671 | // Ignore simulated events, since we handle both touch and |
| 5672 | // mouse explicitly; otherwise we risk getting duplicates of |
| 5673 | // touch events, see #4315. |
| 5674 | // Also ignore the event if disabled; this happens in IE11 |
| 5675 | // under some circumstances, see #3666. |
| 5676 | if (e._simulated || !this._enabled) { return; } |
| 5677 | |
| 5678 | this._moved = false; |
| 5679 | |
| 5680 | if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } |
| 5681 | |
| 5682 | if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } |
| 5683 | Draggable._dragging = this; // Prevent dragging multiple objects at once. |
| 5684 | |
| 5685 | if (this._preventOutline) { |
| 5686 | preventOutline(this._element); |
| 5687 | } |
| 5688 | |
| 5689 | disableImageDrag(); |
| 5690 | disableTextSelection(); |
| 5691 | |
| 5692 | if (this._moving) { return; } |
| 5693 | |
| 5694 | // @event down: Event |
| 5695 | // Fired when a drag is about to start. |
| 5696 | this.fire('down'); |
| 5697 | |
| 5698 | var first = e.touches ? e.touches[0] : e; |
| 5699 | |
| 5700 | this._startPoint = new Point(first.clientX, first.clientY); |
| 5701 | |
| 5702 | on(document, MOVE[e.type], this._onMove, this); |
| 5703 | on(document, END[e.type], this._onUp, this); |
| 5704 | }, |
| 5705 | |
| 5706 | _onMove: function (e) { |
| 5707 | // Ignore simulated events, since we handle both touch and |
| 5708 | // mouse explicitly; otherwise we risk getting duplicates of |
| 5709 | &nbs |