UNPKG

431 kBJavaScriptView Raw
1/* @preserve
2 * Leaflet 1.7.1, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6(function (global, factory) {
7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8 typeof define === 'function' && define.amd ? define(['exports'], factory) :
9 (factory((global.L = {})));
10}(this, (function (exports) { 'use strict';
11
12 var version = "1.7.1";
13
14 /*
15 * @namespace Util
16 *
17 * Various utility functions, used by Leaflet internally.
18 */
19
20 // @function extend(dest: Object, src?: Object): Object
21 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
22 function extend(dest) {
23 var i, j, len, src;
24
25 for (j = 1, len = arguments.length; j < len; j++) {
26 src = arguments[j];
27 for (i in src) {
28 dest[i] = src[i];
29 }
30 }
31 return dest;
32 }
33
34 // @function create(proto: Object, properties?: Object): Object
35 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
36 var create = Object.create || (function () {
37 function F() {}
38 return function (proto) {
39 F.prototype = proto;
40 return new F();
41 };
42 })();
43
44 // @function bind(fn: Function, …): Function
45 // 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).
46 // Has a `L.bind()` shortcut.
47 function bind(fn, obj) {
48 var slice = Array.prototype.slice;
49
50 if (fn.bind) {
51 return fn.bind.apply(fn, slice.call(arguments, 1));
52 }
53
54 var args = slice.call(arguments, 2);
55
56 return function () {
57 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
58 };
59 }
60
61 // @property lastId: Number
62 // Last unique ID used by [`stamp()`](#util-stamp)
63 var lastId = 0;
64
65 // @function stamp(obj: Object): Number
66 // Returns the unique ID of an object, assigning it one if it doesn't have it.
67 function stamp(obj) {
68 /*eslint-disable */
69 obj._leaflet_id = obj._leaflet_id || ++lastId;
70 return obj._leaflet_id;
71 /* eslint-enable */
72 }
73
74 // @function throttle(fn: Function, time: Number, context: Object): Function
75 // Returns a function which executes function `fn` with the given scope `context`
76 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
77 // `fn` will be called no more than one time per given amount of `time`. The arguments
78 // received by the bound function will be any arguments passed when binding the
79 // function, followed by any arguments passed when invoking the bound function.
80 // Has an `L.throttle` shortcut.
81 function throttle(fn, time, context) {
82 var lock, args, wrapperFn, later;
83
84 later = function () {
85 // reset lock and call if queued
86 lock = false;
87 if (args) {
88 wrapperFn.apply(context, args);
89 args = false;
90 }
91 };
92
93 wrapperFn = function () {
94 if (lock) {
95 // called too soon, queue to call later
96 args = arguments;
97
98 } else {
99 // call and lock until later
100 fn.apply(context, arguments);
101 setTimeout(later, time);
102 lock = true;
103 }
104 };
105
106 return wrapperFn;
107 }
108
109 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
110 // Returns the number `num` modulo `range` in such a way so it lies within
111 // `range[0]` and `range[1]`. The returned value will be always smaller than
112 // `range[1]` unless `includeMax` is set to `true`.
113 function wrapNum(x, range, includeMax) {
114 var max = range[1],
115 min = range[0],
116 d = max - min;
117 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
118 }
119
120 // @function falseFn(): Function
121 // Returns a function which always returns `false`.
122 function falseFn() { return false; }
123
124 // @function formatNum(num: Number, digits?: Number): Number
125 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
126 function formatNum(num, digits) {
127 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
128 return Math.round(num * pow) / pow;
129 }
130
131 // @function trim(str: String): String
132 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
133 function trim(str) {
134 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
135 }
136
137 // @function splitWords(str: String): String[]
138 // Trims and splits the string on whitespace and returns the array of parts.
139 function splitWords(str) {
140 return trim(str).split(/\s+/);
141 }
142
143 // @function setOptions(obj: Object, options: Object): Object
144 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
145 function setOptions(obj, options) {
146 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
147 obj.options = obj.options ? create(obj.options) : {};
148 }
149 for (var i in options) {
150 obj.options[i] = options[i];
151 }
152 return obj.options;
153 }
154
155 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
156 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
157 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
158 // be appended at the end. If `uppercase` is `true`, the parameter names will
159 // be uppercased (e.g. `'?A=foo&B=bar'`)
160 function getParamString(obj, existingUrl, uppercase) {
161 var params = [];
162 for (var i in obj) {
163 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
164 }
165 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
166 }
167
168 var templateRe = /\{ *([\w_-]+) *\}/g;
169
170 // @function template(str: String, data: Object): String
171 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
172 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
173 // `('Hello foo, bar')`. You can also specify functions instead of strings for
174 // data values — they will be evaluated passing `data` as an argument.
175 function template(str, data) {
176 return str.replace(templateRe, function (str, key) {
177 var value = data[key];
178
179 if (value === undefined) {
180 throw new Error('No value provided for variable ' + str);
181
182 } else if (typeof value === 'function') {
183 value = value(data);
184 }
185 return value;
186 });
187 }
188
189 // @function isArray(obj): Boolean
190 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
191 var isArray = Array.isArray || function (obj) {
192 return (Object.prototype.toString.call(obj) === '[object Array]');
193 };
194
195 // @function indexOf(array: Array, el: Object): Number
196 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
197 function indexOf(array, el) {
198 for (var i = 0; i < array.length; i++) {
199 if (array[i] === el) { return i; }
200 }
201 return -1;
202 }
203
204 // @property emptyImageUrl: String
205 // Data URI string containing a base64-encoded empty GIF image.
206 // Used as a hack to free memory from unused images on WebKit-powered
207 // mobile devices (by setting image `src` to this string).
208 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
209
210 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
211
212 function getPrefixed(name) {
213 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
214 }
215
216 var lastTime = 0;
217
218 // fallback for IE 7-8
219 function timeoutDefer(fn) {
220 var time = +new Date(),
221 timeToCall = Math.max(0, 16 - (time - lastTime));
222
223 lastTime = time + timeToCall;
224 return window.setTimeout(fn, timeToCall);
225 }
226
227 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
228 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
229 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
230
231 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
232 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
233 // `context` if given. When `immediate` is set, `fn` is called immediately if
234 // the browser doesn't have native support for
235 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
236 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
237 function requestAnimFrame(fn, context, immediate) {
238 if (immediate && requestFn === timeoutDefer) {
239 fn.call(context);
240 } else {
241 return requestFn.call(window, bind(fn, context));
242 }
243 }
244
245 // @function cancelAnimFrame(id: Number): undefined
246 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
247 function cancelAnimFrame(id) {
248 if (id) {
249 cancelFn.call(window, id);
250 }
251 }
252
253 var Util = ({
254 extend: extend,
255 create: create,
256 bind: bind,
257 lastId: lastId,
258 stamp: stamp,
259 throttle: throttle,
260 wrapNum: wrapNum,
261 falseFn: falseFn,
262 formatNum: formatNum,
263 trim: trim,
264 splitWords: splitWords,
265 setOptions: setOptions,
266 getParamString: getParamString,
267 template: template,
268 isArray: isArray,
269 indexOf: indexOf,
270 emptyImageUrl: emptyImageUrl,
271 requestFn: requestFn,
272 cancelFn: cancelFn,
273 requestAnimFrame: requestAnimFrame,
274 cancelAnimFrame: cancelAnimFrame
275 });
276
277 // @class Class
278 // @aka L.Class
279
280 // @section
281 // @uninheritable
282
283 // Thanks to John Resig and Dean Edwards for inspiration!
284
285 function Class() {}
286
287 Class.extend = function (props) {
288
289 // @function extend(props: Object): Function
290 // [Extends the current class](#class-inheritance) given the properties to be included.
291 // Returns a Javascript function that is a class constructor (to be called with `new`).
292 var NewClass = function () {
293
294 // call the constructor
295 if (this.initialize) {
296 this.initialize.apply(this, arguments);
297 }
298
299 // call all constructor hooks
300 this.callInitHooks();
301 };
302
303 var parentProto = NewClass.__super__ = this.prototype;
304
305 var proto = create(parentProto);
306 proto.constructor = NewClass;
307
308 NewClass.prototype = proto;
309
310 // inherit parent's statics
311 for (var i in this) {
312 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
313 NewClass[i] = this[i];
314 }
315 }
316
317 // mix static properties into the class
318 if (props.statics) {
319 extend(NewClass, props.statics);
320 delete props.statics;
321 }
322
323 // mix includes into the prototype
324 if (props.includes) {
325 checkDeprecatedMixinEvents(props.includes);
326 extend.apply(null, [proto].concat(props.includes));
327 delete props.includes;
328 }
329
330 // merge options
331 if (proto.options) {
332 props.options = extend(create(proto.options), props.options);
333 }
334
335 // mix given properties into the prototype
336 extend(proto, props);
337
338 proto._initHooks = [];
339
340 // add method for calling all hooks
341 proto.callInitHooks = function () {
342
343 if (this._initHooksCalled) { return; }
344
345 if (parentProto.callInitHooks) {
346 parentProto.callInitHooks.call(this);
347 }
348
349 this._initHooksCalled = true;
350
351 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
352 proto._initHooks[i].call(this);
353 }
354 };
355
356 return NewClass;
357 };
358
359
360 // @function include(properties: Object): this
361 // [Includes a mixin](#class-includes) into the current class.
362 Class.include = function (props) {
363 extend(this.prototype, props);
364 return this;
365 };
366
367 // @function mergeOptions(options: Object): this
368 // [Merges `options`](#class-options) into the defaults of the class.
369 Class.mergeOptions = function (options) {
370 extend(this.prototype.options, options);
371 return this;
372 };
373
374 // @function addInitHook(fn: Function): this
375 // Adds a [constructor hook](#class-constructor-hooks) to the class.
376 Class.addInitHook = function (fn) { // (Function) || (String, args...)
377 var args = Array.prototype.slice.call(arguments, 1);
378
379 var init = typeof fn === 'function' ? fn : function () {
380 this[fn].apply(this, args);
381 };
382
383 this.prototype._initHooks = this.prototype._initHooks || [];
384 this.prototype._initHooks.push(init);
385 return this;
386 };
387
388 function checkDeprecatedMixinEvents(includes) {
389 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
390
391 includes = isArray(includes) ? includes : [includes];
392
393 for (var i = 0; i < includes.length; i++) {
394 if (includes[i] === L.Mixin.Events) {
395 console.warn('Deprecated include of L.Mixin.Events: ' +
396 'this property will be removed in future releases, ' +
397 'please inherit from L.Evented instead.', new Error().stack);
398 }
399 }
400 }
401
402 /*
403 * @class Evented
404 * @aka L.Evented
405 * @inherits Class
406 *
407 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
408 *
409 * @example
410 *
411 * ```js
412 * map.on('click', function(e) {
413 * alert(e.latlng);
414 * } );
415 * ```
416 *
417 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
418 *
419 * ```js
420 * function onClick(e) { ... }
421 *
422 * map.on('click', onClick);
423 * map.off('click', onClick);
424 * ```
425 */
426
427 var Events = {
428 /* @method on(type: String, fn: Function, context?: Object): this
429 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
430 *
431 * @alternative
432 * @method on(eventMap: Object): this
433 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
434 */
435 on: function (types, fn, context) {
436
437 // types can be a map of types/handlers
438 if (typeof types === 'object') {
439 for (var type in types) {
440 // we don't process space-separated events here for performance;
441 // it's a hot path since Layer uses the on(obj) syntax
442 this._on(type, types[type], fn);
443 }
444
445 } else {
446 // types can be a string of space-separated words
447 types = splitWords(types);
448
449 for (var i = 0, len = types.length; i < len; i++) {
450 this._on(types[i], fn, context);
451 }
452 }
453
454 return this;
455 },
456
457 /* @method off(type: String, fn?: Function, context?: Object): this
458 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
459 *
460 * @alternative
461 * @method off(eventMap: Object): this
462 * Removes a set of type/listener pairs.
463 *
464 * @alternative
465 * @method off: this
466 * Removes all listeners to all events on the object. This includes implicitly attached events.
467 */
468 off: function (types, fn, context) {
469
470 if (!types) {
471 // clear all listeners if called without arguments
472 delete this._events;
473
474 } else if (typeof types === 'object') {
475 for (var type in types) {
476 this._off(type, types[type], fn);
477 }
478
479 } else {
480 types = splitWords(types);
481
482 for (var i = 0, len = types.length; i < len; i++) {
483 this._off(types[i], fn, context);
484 }
485 }
486
487 return this;
488 },
489
490 // attach listener (without syntactic sugar now)
491 _on: function (type, fn, context) {
492 this._events = this._events || {};
493
494 /* get/init listeners for type */
495 var typeListeners = this._events[type];
496 if (!typeListeners) {
497 typeListeners = [];
498 this._events[type] = typeListeners;
499 }
500
501 if (context === this) {
502 // Less memory footprint.
503 context = undefined;
504 }
505 var newListener = {fn: fn, ctx: context},
506 listeners = typeListeners;
507
508 // check if fn already there
509 for (var i = 0, len = listeners.length; i < len; i++) {
510 if (listeners[i].fn === fn && listeners[i].ctx === context) {
511 return;
512 }
513 }
514
515 listeners.push(newListener);
516 },
517
518 _off: function (type, fn, context) {
519 var listeners,
520 i,
521 len;
522
523 if (!this._events) { return; }
524
525 listeners = this._events[type];
526
527 if (!listeners) {
528 return;
529 }
530
531 if (!fn) {
532 // Set all removed listeners to noop so they are not called if remove happens in fire
533 for (i = 0, len = listeners.length; i < len; i++) {
534 listeners[i].fn = falseFn;
535 }
536 // clear all listeners for a type if function isn't specified
537 delete this._events[type];
538 return;
539 }
540
541 if (context === this) {
542 context = undefined;
543 }
544
545 if (listeners) {
546
547 // find fn and remove it
548 for (i = 0, len = listeners.length; i < len; i++) {
549 var l = listeners[i];
550 if (l.ctx !== context) { continue; }
551 if (l.fn === fn) {
552
553 // set the removed listener to noop so that's not called if remove happens in fire
554 l.fn = falseFn;
555
556 if (this._firingCount) {
557 /* copy array in case events are being fired */
558 this._events[type] = listeners = listeners.slice();
559 }
560 listeners.splice(i, 1);
561
562 return;
563 }
564 }
565 }
566 },
567
568 // @method fire(type: String, data?: Object, propagate?: Boolean): this
569 // Fires an event of the specified type. You can optionally provide an data
570 // object — the first argument of the listener function will contain its
571 // properties. The event can optionally be propagated to event parents.
572 fire: function (type, data, propagate) {
573 if (!this.listens(type, propagate)) { return this; }
574
575 var event = extend({}, data, {
576 type: type,
577 target: this,
578 sourceTarget: data && data.sourceTarget || this
579 });
580
581 if (this._events) {
582 var listeners = this._events[type];
583
584 if (listeners) {
585 this._firingCount = (this._firingCount + 1) || 1;
586 for (var i = 0, len = listeners.length; i < len; i++) {
587 var l = listeners[i];
588 l.fn.call(l.ctx || this, event);
589 }
590
591 this._firingCount--;
592 }
593 }
594
595 if (propagate) {
596 // propagate the event to parents (set with addEventParent)
597 this._propagateEvent(event);
598 }
599
600 return this;
601 },
602
603 // @method listens(type: String): Boolean
604 // Returns `true` if a particular event type has any listeners attached to it.
605 listens: function (type, propagate) {
606 var listeners = this._events && this._events[type];
607 if (listeners && listeners.length) { return true; }
608
609 if (propagate) {
610 // also check parents for listeners if event propagates
611 for (var id in this._eventParents) {
612 if (this._eventParents[id].listens(type, propagate)) { return true; }
613 }
614 }
615 return false;
616 },
617
618 // @method once(…): this
619 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620 once: function (types, fn, context) {
621
622 if (typeof types === 'object') {
623 for (var type in types) {
624 this.once(type, types[type], fn);
625 }
626 return this;
627 }
628
629 var handler = bind(function () {
630 this
631 .off(types, fn, context)
632 .off(types, handler, context);
633 }, this);
634
635 // add a listener that's executed once and removed after that
636 return this
637 .on(types, fn, context)
638 .on(types, handler, context);
639 },
640
641 // @method addEventParent(obj: Evented): this
642 // Adds an event parent - an `Evented` that will receive propagated events
643 addEventParent: function (obj) {
644 this._eventParents = this._eventParents || {};
645 this._eventParents[stamp(obj)] = obj;
646 return this;
647 },
648
649 // @method removeEventParent(obj: Evented): this
650 // Removes an event parent, so it will stop receiving propagated events
651 removeEventParent: function (obj) {
652 if (this._eventParents) {
653 delete this._eventParents[stamp(obj)];
654 }
655 return this;
656 },
657
658 _propagateEvent: function (e) {
659 for (var id in this._eventParents) {
660 this._eventParents[id].fire(e.type, extend({
661 layer: e.target,
662 propagatedFrom: e.target
663 }, e), true);
664 }
665 }
666 };
667
668 // aliases; we should ditch those eventually
669
670 // @method addEventListener(…): this
671 // Alias to [`on(…)`](#evented-on)
672 Events.addEventListener = Events.on;
673
674 // @method removeEventListener(…): this
675 // Alias to [`off(…)`](#evented-off)
676
677 // @method clearAllEventListeners(…): this
678 // Alias to [`off()`](#evented-off)
679 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
680
681 // @method addOneTimeEventListener(…): this
682 // Alias to [`once(…)`](#evented-once)
683 Events.addOneTimeEventListener = Events.once;
684
685 // @method fireEvent(…): this
686 // Alias to [`fire(…)`](#evented-fire)
687 Events.fireEvent = Events.fire;
688
689 // @method hasEventListeners(…): Boolean
690 // Alias to [`listens(…)`](#evented-listens)
691 Events.hasEventListeners = Events.listens;
692
693 var Evented = Class.extend(Events);
694
695 /*
696 * @class Point
697 * @aka L.Point
698 *
699 * Represents a point with `x` and `y` coordinates in pixels.
700 *
701 * @example
702 *
703 * ```js
704 * var point = L.point(200, 300);
705 * ```
706 *
707 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
708 *
709 * ```js
710 * map.panBy([200, 300]);
711 * map.panBy(L.point(200, 300));
712 * ```
713 *
714 * Note that `Point` does not inherit from Leaflet's `Class` object,
715 * which means new classes can't inherit from it, and new methods
716 * can't be added to it with the `include` function.
717 */
718
719 function Point(x, y, round) {
720 // @property x: Number; The `x` coordinate of the point
721 this.x = (round ? Math.round(x) : x);
722 // @property y: Number; The `y` coordinate of the point
723 this.y = (round ? Math.round(y) : y);
724 }
725
726 var trunc = Math.trunc || function (v) {
727 return v > 0 ? Math.floor(v) : Math.ceil(v);
728 };
729
730 Point.prototype = {
731
732 // @method clone(): Point
733 // Returns a copy of the current point.
734 clone: function () {
735 return new Point(this.x, this.y);
736 },
737
738 // @method add(otherPoint: Point): Point
739 // Returns the result of addition of the current and the given points.
740 add: function (point) {
741 // non-destructive, returns a new point
742 return this.clone()._add(toPoint(point));
743 },
744
745 _add: function (point) {
746 // destructive, used directly for performance in situations where it's safe to modify existing point
747 this.x += point.x;
748 this.y += point.y;
749 return this;
750 },
751
752 // @method subtract(otherPoint: Point): Point
753 // Returns the result of subtraction of the given point from the current.
754 subtract: function (point) {
755 return this.clone()._subtract(toPoint(point));
756 },
757
758 _subtract: function (point) {
759 this.x -= point.x;
760 this.y -= point.y;
761 return this;
762 },
763
764 // @method divideBy(num: Number): Point
765 // Returns the result of division of the current point by the given number.
766 divideBy: function (num) {
767 return this.clone()._divideBy(num);
768 },
769
770 _divideBy: function (num) {
771 this.x /= num;
772 this.y /= num;
773 return this;
774 },
775
776 // @method multiplyBy(num: Number): Point
777 // Returns the result of multiplication of the current point by the given number.
778 multiplyBy: function (num) {
779 return this.clone()._multiplyBy(num);
780 },
781
782 _multiplyBy: function (num) {
783 this.x *= num;
784 this.y *= num;
785 return this;
786 },
787
788 // @method scaleBy(scale: Point): Point
789 // Multiply each coordinate of the current point by each coordinate of
790 // `scale`. In linear algebra terms, multiply the point by the
791 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
792 // defined by `scale`.
793 scaleBy: function (point) {
794 return new Point(this.x * point.x, this.y * point.y);
795 },
796
797 // @method unscaleBy(scale: Point): Point
798 // Inverse of `scaleBy`. Divide each coordinate of the current point by
799 // each coordinate of `scale`.
800 unscaleBy: function (point) {
801 return new Point(this.x / point.x, this.y / point.y);
802 },
803
804 // @method round(): Point
805 // Returns a copy of the current point with rounded coordinates.
806 round: function () {
807 return this.clone()._round();
808 },
809
810 _round: function () {
811 this.x = Math.round(this.x);
812 this.y = Math.round(this.y);
813 return this;
814 },
815
816 // @method floor(): Point
817 // Returns a copy of the current point with floored coordinates (rounded down).
818 floor: function () {
819 return this.clone()._floor();
820 },
821
822 _floor: function () {
823 this.x = Math.floor(this.x);
824 this.y = Math.floor(this.y);
825 return this;
826 },
827
828 // @method ceil(): Point
829 // Returns a copy of the current point with ceiled coordinates (rounded up).
830 ceil: function () {
831 return this.clone()._ceil();
832 },
833
834 _ceil: function () {
835 this.x = Math.ceil(this.x);
836 this.y = Math.ceil(this.y);
837 return this;
838 },
839
840 // @method trunc(): Point
841 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
842 trunc: function () {
843 return this.clone()._trunc();
844 },
845
846 _trunc: function () {
847 this.x = trunc(this.x);
848 this.y = trunc(this.y);
849 return this;
850 },
851
852 // @method distanceTo(otherPoint: Point): Number
853 // Returns the cartesian distance between the current and the given points.
854 distanceTo: function (point) {
855 point = toPoint(point);
856
857 var x = point.x - this.x,
858 y = point.y - this.y;
859
860 return Math.sqrt(x * x + y * y);
861 },
862
863 // @method equals(otherPoint: Point): Boolean
864 // Returns `true` if the given point has the same coordinates.
865 equals: function (point) {
866 point = toPoint(point);
867
868 return point.x === this.x &&
869 point.y === this.y;
870 },
871
872 // @method contains(otherPoint: Point): Boolean
873 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
874 contains: function (point) {
875 point = toPoint(point);
876
877 return Math.abs(point.x) <= Math.abs(this.x) &&
878 Math.abs(point.y) <= Math.abs(this.y);
879 },
880
881 // @method toString(): String
882 // Returns a string representation of the point for debugging purposes.
883 toString: function () {
884 return 'Point(' +
885 formatNum(this.x) + ', ' +
886 formatNum(this.y) + ')';
887 }
888 };
889
890 // @factory L.point(x: Number, y: Number, round?: Boolean)
891 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
892
893 // @alternative
894 // @factory L.point(coords: Number[])
895 // Expects an array of the form `[x, y]` instead.
896
897 // @alternative
898 // @factory L.point(coords: Object)
899 // Expects a plain object of the form `{x: Number, y: Number}` instead.
900 function toPoint(x, y, round) {
901 if (x instanceof Point) {
902 return x;
903 }
904 if (isArray(x)) {
905 return new Point(x[0], x[1]);
906 }
907 if (x === undefined || x === null) {
908 return x;
909 }
910 if (typeof x === 'object' && 'x' in x && 'y' in x) {
911 return new Point(x.x, x.y);
912 }
913 return new Point(x, y, round);
914 }
915
916 /*
917 * @class Bounds
918 * @aka L.Bounds
919 *
920 * Represents a rectangular area in pixel coordinates.
921 *
922 * @example
923 *
924 * ```js
925 * var p1 = L.point(10, 10),
926 * p2 = L.point(40, 60),
927 * bounds = L.bounds(p1, p2);
928 * ```
929 *
930 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
931 *
932 * ```js
933 * otherBounds.intersects([[10, 10], [40, 60]]);
934 * ```
935 *
936 * Note that `Bounds` does not inherit from Leaflet's `Class` object,
937 * which means new classes can't inherit from it, and new methods
938 * can't be added to it with the `include` function.
939 */
940
941 function Bounds(a, b) {
942 if (!a) { return; }
943
944 var points = b ? [a, b] : a;
945
946 for (var i = 0, len = points.length; i < len; i++) {
947 this.extend(points[i]);
948 }
949 }
950
951 Bounds.prototype = {
952 // @method extend(point: Point): this
953 // Extends the bounds to contain the given point.
954 extend: function (point) { // (Point)
955 point = toPoint(point);
956
957 // @property min: Point
958 // The top left corner of the rectangle.
959 // @property max: Point
960 // The bottom right corner of the rectangle.
961 if (!this.min && !this.max) {
962 this.min = point.clone();
963 this.max = point.clone();
964 } else {
965 this.min.x = Math.min(point.x, this.min.x);
966 this.max.x = Math.max(point.x, this.max.x);
967 this.min.y = Math.min(point.y, this.min.y);
968 this.max.y = Math.max(point.y, this.max.y);
969 }
970 return this;
971 },
972
973 // @method getCenter(round?: Boolean): Point
974 // Returns the center point of the bounds.
975 getCenter: function (round) {
976 return new Point(
977 (this.min.x + this.max.x) / 2,
978 (this.min.y + this.max.y) / 2, round);
979 },
980
981 // @method getBottomLeft(): Point
982 // Returns the bottom-left point of the bounds.
983 getBottomLeft: function () {
984 return new Point(this.min.x, this.max.y);
985 },
986
987 // @method getTopRight(): Point
988 // Returns the top-right point of the bounds.
989 getTopRight: function () { // -> Point
990 return new Point(this.max.x, this.min.y);
991 },
992
993 // @method getTopLeft(): Point
994 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
995 getTopLeft: function () {
996 return this.min; // left, top
997 },
998
999 // @method getBottomRight(): Point
1000 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1001 getBottomRight: function () {
1002 return this.max; // right, bottom
1003 },
1004
1005 // @method getSize(): Point
1006 // Returns the size of the given bounds
1007 getSize: function () {
1008 return this.max.subtract(this.min);
1009 },
1010
1011 // @method contains(otherBounds: Bounds): Boolean
1012 // Returns `true` if the rectangle contains the given one.
1013 // @alternative
1014 // @method contains(point: Point): Boolean
1015 // Returns `true` if the rectangle contains the given point.
1016 contains: function (obj) {
1017 var min, max;
1018
1019 if (typeof obj[0] === 'number' || obj instanceof Point) {
1020 obj = toPoint(obj);
1021 } else {
1022 obj = toBounds(obj);
1023 }
1024
1025 if (obj instanceof Bounds) {
1026 min = obj.min;
1027 max = obj.max;
1028 } else {
1029 min = max = obj;
1030 }
1031
1032 return (min.x >= this.min.x) &&
1033 (max.x <= this.max.x) &&
1034 (min.y >= this.min.y) &&
1035 (max.y <= this.max.y);
1036 },
1037
1038 // @method intersects(otherBounds: Bounds): Boolean
1039 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1040 // intersect if they have at least one point in common.
1041 intersects: function (bounds) { // (Bounds) -> Boolean
1042 bounds = toBounds(bounds);
1043
1044 var min = this.min,
1045 max = this.max,
1046 min2 = bounds.min,
1047 max2 = bounds.max,
1048 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1049 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1050
1051 return xIntersects && yIntersects;
1052 },
1053
1054 // @method overlaps(otherBounds: Bounds): Boolean
1055 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1056 // overlap if their intersection is an area.
1057 overlaps: function (bounds) { // (Bounds) -> Boolean
1058 bounds = toBounds(bounds);
1059
1060 var min = this.min,
1061 max = this.max,
1062 min2 = bounds.min,
1063 max2 = bounds.max,
1064 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1065 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1066
1067 return xOverlaps && yOverlaps;
1068 },
1069
1070 isValid: function () {
1071 return !!(this.min && this.max);
1072 }
1073 };
1074
1075
1076 // @factory L.bounds(corner1: Point, corner2: Point)
1077 // Creates a Bounds object from two corners coordinate pairs.
1078 // @alternative
1079 // @factory L.bounds(points: Point[])
1080 // Creates a Bounds object from the given array of points.
1081 function toBounds(a, b) {
1082 if (!a || a instanceof Bounds) {
1083 return a;
1084 }
1085 return new Bounds(a, b);
1086 }
1087
1088 /*
1089 * @class LatLngBounds
1090 * @aka L.LatLngBounds
1091 *
1092 * Represents a rectangular geographical area on a map.
1093 *
1094 * @example
1095 *
1096 * ```js
1097 * var corner1 = L.latLng(40.712, -74.227),
1098 * corner2 = L.latLng(40.774, -74.125),
1099 * bounds = L.latLngBounds(corner1, corner2);
1100 * ```
1101 *
1102 * 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:
1103 *
1104 * ```js
1105 * map.fitBounds([
1106 * [40.712, -74.227],
1107 * [40.774, -74.125]
1108 * ]);
1109 * ```
1110 *
1111 * 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.
1112 *
1113 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
1114 * which means new classes can't inherit from it, and new methods
1115 * can't be added to it with the `include` function.
1116 */
1117
1118 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1119 if (!corner1) { return; }
1120
1121 var latlngs = corner2 ? [corner1, corner2] : corner1;
1122
1123 for (var i = 0, len = latlngs.length; i < len; i++) {
1124 this.extend(latlngs[i]);
1125 }
1126 }
1127
1128 LatLngBounds.prototype = {
1129
1130 // @method extend(latlng: LatLng): this
1131 // Extend the bounds to contain the given point
1132
1133 // @alternative
1134 // @method extend(otherBounds: LatLngBounds): this
1135 // Extend the bounds to contain the given bounds
1136 extend: function (obj) {
1137 var sw = this._southWest,
1138 ne = this._northEast,
1139 sw2, ne2;
1140
1141 if (obj instanceof LatLng) {
1142 sw2 = obj;
1143 ne2 = obj;
1144
1145 } else if (obj instanceof LatLngBounds) {
1146 sw2 = obj._southWest;
1147 ne2 = obj._northEast;
1148
1149 if (!sw2 || !ne2) { return this; }
1150
1151 } else {
1152 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1153 }
1154
1155 if (!sw && !ne) {
1156 this._southWest = new LatLng(sw2.lat, sw2.lng);
1157 this._northEast = new LatLng(ne2.lat, ne2.lng);
1158 } else {
1159 sw.lat = Math.min(sw2.lat, sw.lat);
1160 sw.lng = Math.min(sw2.lng, sw.lng);
1161 ne.lat = Math.max(ne2.lat, ne.lat);
1162 ne.lng = Math.max(ne2.lng, ne.lng);
1163 }
1164
1165 return this;
1166 },
1167
1168 // @method pad(bufferRatio: Number): LatLngBounds
1169 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1170 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1171 // Negative values will retract the bounds.
1172 pad: function (bufferRatio) {
1173 var sw = this._southWest,
1174 ne = this._northEast,
1175 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1176 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1177
1178 return new LatLngBounds(
1179 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1180 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1181 },
1182
1183 // @method getCenter(): LatLng
1184 // Returns the center point of the bounds.
1185 getCenter: function () {
1186 return new LatLng(
1187 (this._southWest.lat + this._northEast.lat) / 2,
1188 (this._southWest.lng + this._northEast.lng) / 2);
1189 },
1190
1191 // @method getSouthWest(): LatLng
1192 // Returns the south-west point of the bounds.
1193 getSouthWest: function () {
1194 return this._southWest;
1195 },
1196
1197 // @method getNorthEast(): LatLng
1198 // Returns the north-east point of the bounds.
1199 getNorthEast: function () {
1200 return this._northEast;
1201 },
1202
1203 // @method getNorthWest(): LatLng
1204 // Returns the north-west point of the bounds.
1205 getNorthWest: function () {
1206 return new LatLng(this.getNorth(), this.getWest());
1207 },
1208
1209 // @method getSouthEast(): LatLng
1210 // Returns the south-east point of the bounds.
1211 getSouthEast: function () {
1212 return new LatLng(this.getSouth(), this.getEast());
1213 },
1214
1215 // @method getWest(): Number
1216 // Returns the west longitude of the bounds
1217 getWest: function () {
1218 return this._southWest.lng;
1219 },
1220
1221 // @method getSouth(): Number
1222 // Returns the south latitude of the bounds
1223 getSouth: function () {
1224 return this._southWest.lat;
1225 },
1226
1227 // @method getEast(): Number
1228 // Returns the east longitude of the bounds
1229 getEast: function () {
1230 return this._northEast.lng;
1231 },
1232
1233 // @method getNorth(): Number
1234 // Returns the north latitude of the bounds
1235 getNorth: function () {
1236 return this._northEast.lat;
1237 },
1238
1239 // @method contains(otherBounds: LatLngBounds): Boolean
1240 // Returns `true` if the rectangle contains the given one.
1241
1242 // @alternative
1243 // @method contains (latlng: LatLng): Boolean
1244 // Returns `true` if the rectangle contains the given point.
1245 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1246 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1247 obj = toLatLng(obj);
1248 } else {
1249 obj = toLatLngBounds(obj);
1250 }
1251
1252 var sw = this._southWest,
1253 ne = this._northEast,
1254 sw2, ne2;
1255
1256 if (obj instanceof LatLngBounds) {
1257 sw2 = obj.getSouthWest();
1258 ne2 = obj.getNorthEast();
1259 } else {
1260 sw2 = ne2 = obj;
1261 }
1262
1263 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1264 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1265 },
1266
1267 // @method intersects(otherBounds: LatLngBounds): Boolean
1268 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1269 intersects: function (bounds) {
1270 bounds = toLatLngBounds(bounds);
1271
1272 var sw = this._southWest,
1273 ne = this._northEast,
1274 sw2 = bounds.getSouthWest(),
1275 ne2 = bounds.getNorthEast(),
1276
1277 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1278 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1279
1280 return latIntersects && lngIntersects;
1281 },
1282
1283 // @method overlaps(otherBounds: LatLngBounds): Boolean
1284 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1285 overlaps: function (bounds) {
1286 bounds = toLatLngBounds(bounds);
1287
1288 var sw = this._southWest,
1289 ne = this._northEast,
1290 sw2 = bounds.getSouthWest(),
1291 ne2 = bounds.getNorthEast(),
1292
1293 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1294 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1295
1296 return latOverlaps && lngOverlaps;
1297 },
1298
1299 // @method toBBoxString(): String
1300 // 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.
1301 toBBoxString: function () {
1302 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1303 },
1304
1305 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1306 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
1307 equals: function (bounds, maxMargin) {
1308 if (!bounds) { return false; }
1309
1310 bounds = toLatLngBounds(bounds);
1311
1312 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1313 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1314 },
1315
1316 // @method isValid(): Boolean
1317 // Returns `true` if the bounds are properly initialized.
1318 isValid: function () {
1319 return !!(this._southWest && this._northEast);
1320 }
1321 };
1322
1323 // TODO International date line?
1324
1325 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1326 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1327
1328 // @alternative
1329 // @factory L.latLngBounds(latlngs: LatLng[])
1330 // 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).
1331 function toLatLngBounds(a, b) {
1332 if (a instanceof LatLngBounds) {
1333 return a;
1334 }
1335 return new LatLngBounds(a, b);
1336 }
1337
1338 /* @class LatLng
1339 * @aka L.LatLng
1340 *
1341 * Represents a geographical point with a certain latitude and longitude.
1342 *
1343 * @example
1344 *
1345 * ```
1346 * var latlng = L.latLng(50.5, 30.5);
1347 * ```
1348 *
1349 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1350 *
1351 * ```
1352 * map.panTo([50, 30]);
1353 * map.panTo({lon: 30, lat: 50});
1354 * map.panTo({lat: 50, lng: 30});
1355 * map.panTo(L.latLng(50, 30));
1356 * ```
1357 *
1358 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1359 * which means new classes can't inherit from it, and new methods
1360 * can't be added to it with the `include` function.
1361 */
1362
1363 function LatLng(lat, lng, alt) {
1364 if (isNaN(lat) || isNaN(lng)) {
1365 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1366 }
1367
1368 // @property lat: Number
1369 // Latitude in degrees
1370 this.lat = +lat;
1371
1372 // @property lng: Number
1373 // Longitude in degrees
1374 this.lng = +lng;
1375
1376 // @property alt: Number
1377 // Altitude in meters (optional)
1378 if (alt !== undefined) {
1379 this.alt = +alt;
1380 }
1381 }
1382
1383 LatLng.prototype = {
1384 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1385 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
1386 equals: function (obj, maxMargin) {
1387 if (!obj) { return false; }
1388
1389 obj = toLatLng(obj);
1390
1391 var margin = Math.max(
1392 Math.abs(this.lat - obj.lat),
1393 Math.abs(this.lng - obj.lng));
1394
1395 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1396 },
1397
1398 // @method toString(): String
1399 // Returns a string representation of the point (for debugging purposes).
1400 toString: function (precision) {
1401 return 'LatLng(' +
1402 formatNum(this.lat, precision) + ', ' +
1403 formatNum(this.lng, precision) + ')';
1404 },
1405
1406 // @method distanceTo(otherLatLng: LatLng): Number
1407 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
1408 distanceTo: function (other) {
1409 return Earth.distance(this, toLatLng(other));
1410 },
1411
1412 // @method wrap(): LatLng
1413 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1414 wrap: function () {
1415 return Earth.wrapLatLng(this);
1416 },
1417
1418 // @method toBounds(sizeInMeters: Number): LatLngBounds
1419 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1420 toBounds: function (sizeInMeters) {
1421 var latAccuracy = 180 * sizeInMeters / 40075017,
1422 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1423
1424 return toLatLngBounds(
1425 [this.lat - latAccuracy, this.lng - lngAccuracy],
1426 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1427 },
1428
1429 clone: function () {
1430 return new LatLng(this.lat, this.lng, this.alt);
1431 }
1432 };
1433
1434
1435
1436 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1437 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1438
1439 // @alternative
1440 // @factory L.latLng(coords: Array): LatLng
1441 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1442
1443 // @alternative
1444 // @factory L.latLng(coords: Object): LatLng
1445 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1446
1447 function toLatLng(a, b, c) {
1448 if (a instanceof LatLng) {
1449 return a;
1450 }
1451 if (isArray(a) && typeof a[0] !== 'object') {
1452 if (a.length === 3) {
1453 return new LatLng(a[0], a[1], a[2]);
1454 }
1455 if (a.length === 2) {
1456 return new LatLng(a[0], a[1]);
1457 }
1458 return null;
1459 }
1460 if (a === undefined || a === null) {
1461 return a;
1462 }
1463 if (typeof a === 'object' && 'lat' in a) {
1464 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1465 }
1466 if (b === undefined) {
1467 return null;
1468 }
1469 return new LatLng(a, b, c);
1470 }
1471
1472 /*
1473 * @namespace CRS
1474 * @crs L.CRS.Base
1475 * Object that defines coordinate reference systems for projecting
1476 * geographical points into pixel (screen) coordinates and back (and to
1477 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1478 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1479 *
1480 * Leaflet defines the most usual CRSs by default. If you want to use a
1481 * CRS not defined by default, take a look at the
1482 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1483 *
1484 * Note that the CRS instances do not inherit from Leaflet's `Class` object,
1485 * and can't be instantiated. Also, new classes can't inherit from them,
1486 * and methods can't be added to them with the `include` function.
1487 */
1488
1489 var CRS = {
1490 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1491 // Projects geographical coordinates into pixel coordinates for a given zoom.
1492 latLngToPoint: function (latlng, zoom) {
1493 var projectedPoint = this.projection.project(latlng),
1494 scale = this.scale(zoom);
1495
1496 return this.transformation._transform(projectedPoint, scale);
1497 },
1498
1499 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1500 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1501 // zoom into geographical coordinates.
1502 pointToLatLng: function (point, zoom) {
1503 var scale = this.scale(zoom),
1504 untransformedPoint = this.transformation.untransform(point, scale);
1505
1506 return this.projection.unproject(untransformedPoint);
1507 },
1508
1509 // @method project(latlng: LatLng): Point
1510 // Projects geographical coordinates into coordinates in units accepted for
1511 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1512 project: function (latlng) {
1513 return this.projection.project(latlng);
1514 },
1515
1516 // @method unproject(point: Point): LatLng
1517 // Given a projected coordinate returns the corresponding LatLng.
1518 // The inverse of `project`.
1519 unproject: function (point) {
1520 return this.projection.unproject(point);
1521 },
1522
1523 // @method scale(zoom: Number): Number
1524 // Returns the scale used when transforming projected coordinates into
1525 // pixel coordinates for a particular zoom. For example, it returns
1526 // `256 * 2^zoom` for Mercator-based CRS.
1527 scale: function (zoom) {
1528 return 256 * Math.pow(2, zoom);
1529 },
1530
1531 // @method zoom(scale: Number): Number
1532 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1533 // factor of `scale`.
1534 zoom: function (scale) {
1535 return Math.log(scale / 256) / Math.LN2;
1536 },
1537
1538 // @method getProjectedBounds(zoom: Number): Bounds
1539 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1540 getProjectedBounds: function (zoom) {
1541 if (this.infinite) { return null; }
1542
1543 var b = this.projection.bounds,
1544 s = this.scale(zoom),
1545 min = this.transformation.transform(b.min, s),
1546 max = this.transformation.transform(b.max, s);
1547
1548 return new Bounds(min, max);
1549 },
1550
1551 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1552 // Returns the distance between two geographical coordinates.
1553
1554 // @property code: String
1555 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1556 //
1557 // @property wrapLng: Number[]
1558 // An array of two numbers defining whether the longitude (horizontal) coordinate
1559 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1560 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1561 //
1562 // @property wrapLat: Number[]
1563 // Like `wrapLng`, but for the latitude (vertical) axis.
1564
1565 // wrapLng: [min, max],
1566 // wrapLat: [min, max],
1567
1568 // @property infinite: Boolean
1569 // If true, the coordinate space will be unbounded (infinite in both axes)
1570 infinite: false,
1571
1572 // @method wrapLatLng(latlng: LatLng): LatLng
1573 // Returns a `LatLng` where lat and lng has been wrapped according to the
1574 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1575 wrapLatLng: function (latlng) {
1576 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1577 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1578 alt = latlng.alt;
1579
1580 return new LatLng(lat, lng, alt);
1581 },
1582
1583 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1584 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1585 // that its center is within the CRS's bounds.
1586 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1587 wrapLatLngBounds: function (bounds) {
1588 var center = bounds.getCenter(),
1589 newCenter = this.wrapLatLng(center),
1590 latShift = center.lat - newCenter.lat,
1591 lngShift = center.lng - newCenter.lng;
1592
1593 if (latShift === 0 && lngShift === 0) {
1594 return bounds;
1595 }
1596
1597 var sw = bounds.getSouthWest(),
1598 ne = bounds.getNorthEast(),
1599 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1600 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1601
1602 return new LatLngBounds(newSw, newNe);
1603 }
1604 };
1605
1606 /*
1607 * @namespace CRS
1608 * @crs L.CRS.Earth
1609 *
1610 * Serves as the base for CRS that are global such that they cover the earth.
1611 * Can only be used as the base for other CRS and cannot be used directly,
1612 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1613 * meters.
1614 */
1615
1616 var Earth = extend({}, CRS, {
1617 wrapLng: [-180, 180],
1618
1619 // Mean Earth Radius, as recommended for use by
1620 // the International Union of Geodesy and Geophysics,
1621 // see http://rosettacode.org/wiki/Haversine_formula
1622 R: 6371000,
1623
1624 // distance between two geographical points using spherical law of cosines approximation
1625 distance: function (latlng1, latlng2) {
1626 var rad = Math.PI / 180,
1627 lat1 = latlng1.lat * rad,
1628 lat2 = latlng2.lat * rad,
1629 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1630 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1631 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1632 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1633 return this.R * c;
1634 }
1635 });
1636
1637 /*
1638 * @namespace Projection
1639 * @projection L.Projection.SphericalMercator
1640 *
1641 * Spherical Mercator projection — the most common projection for online maps,
1642 * used by almost all free and commercial tile providers. Assumes that Earth is
1643 * a sphere. Used by the `EPSG:3857` CRS.
1644 */
1645
1646 var earthRadius = 6378137;
1647
1648 var SphericalMercator = {
1649
1650 R: earthRadius,
1651 MAX_LATITUDE: 85.0511287798,
1652
1653 project: function (latlng) {
1654 var d = Math.PI / 180,
1655 max = this.MAX_LATITUDE,
1656 lat = Math.max(Math.min(max, latlng.lat), -max),
1657 sin = Math.sin(lat * d);
1658
1659 return new Point(
1660 this.R * latlng.lng * d,
1661 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1662 },
1663
1664 unproject: function (point) {
1665 var d = 180 / Math.PI;
1666
1667 return new LatLng(
1668 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1669 point.x * d / this.R);
1670 },
1671
1672 bounds: (function () {
1673 var d = earthRadius * Math.PI;
1674 return new Bounds([-d, -d], [d, d]);
1675 })()
1676 };
1677
1678 /*
1679 * @class Transformation
1680 * @aka L.Transformation
1681 *
1682 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1683 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1684 * the reverse. Used by Leaflet in its projections code.
1685 *
1686 * @example
1687 *
1688 * ```js
1689 * var transformation = L.transformation(2, 5, -1, 10),
1690 * p = L.point(1, 2),
1691 * p2 = transformation.transform(p), // L.point(7, 8)
1692 * p3 = transformation.untransform(p2); // L.point(1, 2)
1693 * ```
1694 */
1695
1696
1697 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1698 // Creates a `Transformation` object with the given coefficients.
1699 function Transformation(a, b, c, d) {
1700 if (isArray(a)) {
1701 // use array properties
1702 this._a = a[0];
1703 this._b = a[1];
1704 this._c = a[2];
1705 this._d = a[3];
1706 return;
1707 }
1708 this._a = a;
1709 this._b = b;
1710 this._c = c;
1711 this._d = d;
1712 }
1713
1714 Transformation.prototype = {
1715 // @method transform(point: Point, scale?: Number): Point
1716 // Returns a transformed point, optionally multiplied by the given scale.
1717 // Only accepts actual `L.Point` instances, not arrays.
1718 transform: function (point, scale) { // (Point, Number) -> Point
1719 return this._transform(point.clone(), scale);
1720 },
1721
1722 // destructive transform (faster)
1723 _transform: function (point, scale) {
1724 scale = scale || 1;
1725 point.x = scale * (this._a * point.x + this._b);
1726 point.y = scale * (this._c * point.y + this._d);
1727 return point;
1728 },
1729
1730 // @method untransform(point: Point, scale?: Number): Point
1731 // Returns the reverse transformation of the given point, optionally divided
1732 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1733 untransform: function (point, scale) {
1734 scale = scale || 1;
1735 return new Point(
1736 (point.x / scale - this._b) / this._a,
1737 (point.y / scale - this._d) / this._c);
1738 }
1739 };
1740
1741 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1742
1743 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1744 // Instantiates a Transformation object with the given coefficients.
1745
1746 // @alternative
1747 // @factory L.transformation(coefficients: Array): Transformation
1748 // Expects an coefficients array of the form
1749 // `[a: Number, b: Number, c: Number, d: Number]`.
1750
1751 function toTransformation(a, b, c, d) {
1752 return new Transformation(a, b, c, d);
1753 }
1754
1755 /*
1756 * @namespace CRS
1757 * @crs L.CRS.EPSG3857
1758 *
1759 * The most common CRS for online maps, used by almost all free and commercial
1760 * tile providers. Uses Spherical Mercator projection. Set in by default in
1761 * Map's `crs` option.
1762 */
1763
1764 var EPSG3857 = extend({}, Earth, {
1765 code: 'EPSG:3857',
1766 projection: SphericalMercator,
1767
1768 transformation: (function () {
1769 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1770 return toTransformation(scale, 0.5, -scale, 0.5);
1771 }())
1772 });
1773
1774 var EPSG900913 = extend({}, EPSG3857, {
1775 code: 'EPSG:900913'
1776 });
1777
1778 // @namespace SVG; @section
1779 // There are several static functions which can be called without instantiating L.SVG:
1780
1781 // @function create(name: String): SVGElement
1782 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1783 // corresponding to the class name passed. For example, using 'line' will return
1784 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1785 function svgCreate(name) {
1786 return document.createElementNS('http://www.w3.org/2000/svg', name);
1787 }
1788
1789 // @function pointsToPath(rings: Point[], closed: Boolean): String
1790 // Generates a SVG path string for multiple rings, with each ring turning
1791 // into "M..L..L.." instructions
1792 function pointsToPath(rings, closed) {
1793 var str = '',
1794 i, j, len, len2, points, p;
1795
1796 for (i = 0, len = rings.length; i < len; i++) {
1797 points = rings[i];
1798
1799 for (j = 0, len2 = points.length; j < len2; j++) {
1800 p = points[j];
1801 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1802 }
1803
1804 // closes the ring for polygons; "x" is VML syntax
1805 str += closed ? (svg ? 'z' : 'x') : '';
1806 }
1807
1808 // SVG complains about empty path strings
1809 return str || 'M0 0';
1810 }
1811
1812 /*
1813 * @namespace Browser
1814 * @aka L.Browser
1815 *
1816 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1817 *
1818 * @example
1819 *
1820 * ```js
1821 * if (L.Browser.ielt9) {
1822 * alert('Upgrade your browser, dude!');
1823 * }
1824 * ```
1825 */
1826
1827 var style$1 = document.documentElement.style;
1828
1829 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1830 var ie = 'ActiveXObject' in window;
1831
1832 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1833 var ielt9 = ie && !document.addEventListener;
1834
1835 // @property edge: Boolean; `true` for the Edge web browser.
1836 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1837
1838 // @property webkit: Boolean;
1839 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1840 var webkit = userAgentContains('webkit');
1841
1842 // @property android: Boolean
1843 // `true` for any browser running on an Android platform.
1844 var android = userAgentContains('android');
1845
1846 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1847 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1848
1849 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1850 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1851 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1852 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1853
1854 // @property opera: Boolean; `true` for the Opera browser
1855 var opera = !!window.opera;
1856
1857 // @property chrome: Boolean; `true` for the Chrome browser.
1858 var chrome = !edge && userAgentContains('chrome');
1859
1860 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1861 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1862
1863 // @property safari: Boolean; `true` for the Safari browser.
1864 var safari = !chrome && userAgentContains('safari');
1865
1866 var phantom = userAgentContains('phantom');
1867
1868 // @property opera12: Boolean
1869 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1870 var opera12 = 'OTransition' in style$1;
1871
1872 // @property win: Boolean; `true` when the browser is running in a Windows platform
1873 var win = navigator.platform.indexOf('Win') === 0;
1874
1875 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1876 var ie3d = ie && ('transition' in style$1);
1877
1878 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1879 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1880
1881 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1882 var gecko3d = 'MozPerspective' in style$1;
1883
1884 // @property any3d: Boolean
1885 // `true` for all browsers supporting CSS transforms.
1886 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1887
1888 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1889 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1890
1891 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1892 var mobileWebkit = mobile && webkit;
1893
1894 // @property mobileWebkit3d: Boolean
1895 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1896 var mobileWebkit3d = mobile && webkit3d;
1897
1898 // @property msPointer: Boolean
1899 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1900 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1901
1902 // @property pointer: Boolean
1903 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1904 var pointer = !!(window.PointerEvent || msPointer);
1905
1906 // @property touch: Boolean
1907 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1908 // This does not necessarily mean that the browser is running in a computer with
1909 // a touchscreen, it only means that the browser is capable of understanding
1910 // touch events.
1911 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1912 (window.DocumentTouch && document instanceof window.DocumentTouch));
1913
1914 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1915 var mobileOpera = mobile && opera;
1916
1917 // @property mobileGecko: Boolean
1918 // `true` for gecko-based browsers running in a mobile device.
1919 var mobileGecko = mobile && gecko;
1920
1921 // @property retina: Boolean
1922 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1923 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1924
1925 // @property passiveEvents: Boolean
1926 // `true` for browsers that support passive events.
1927 var passiveEvents = (function () {
1928 var supportsPassiveOption = false;
1929 try {
1930 var opts = Object.defineProperty({}, 'passive', {
1931 get: function () { // eslint-disable-line getter-return
1932 supportsPassiveOption = true;
1933 }
1934 });
1935 window.addEventListener('testPassiveEventSupport', falseFn, opts);
1936 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
1937 } catch (e) {
1938 // Errors can safely be ignored since this is only a browser support test.
1939 }
1940 return supportsPassiveOption;
1941 }());
1942
1943 // @property canvas: Boolean
1944 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1945 var canvas = (function () {
1946 return !!document.createElement('canvas').getContext;
1947 }());
1948
1949 // @property svg: Boolean
1950 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1951 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1952
1953 // @property vml: Boolean
1954 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1955 var vml = !svg && (function () {
1956 try {
1957 var div = document.createElement('div');
1958 div.innerHTML = '<v:shape adj="1"/>';
1959
1960 var shape = div.firstChild;
1961 shape.style.behavior = 'url(#default#VML)';
1962
1963 return shape && (typeof shape.adj === 'object');
1964
1965 } catch (e) {
1966 return false;
1967 }
1968 }());
1969
1970
1971 function userAgentContains(str) {
1972 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1973 }
1974
1975 var Browser = ({
1976 ie: ie,
1977 ielt9: ielt9,
1978 edge: edge,
1979 webkit: webkit,
1980 android: android,
1981 android23: android23,
1982 androidStock: androidStock,
1983 opera: opera,
1984 chrome: chrome,
1985 gecko: gecko,
1986 safari: safari,
1987 phantom: phantom,
1988 opera12: opera12,
1989 win: win,
1990 ie3d: ie3d,
1991 webkit3d: webkit3d,
1992 gecko3d: gecko3d,
1993 any3d: any3d,
1994 mobile: mobile,
1995 mobileWebkit: mobileWebkit,
1996 mobileWebkit3d: mobileWebkit3d,
1997 msPointer: msPointer,
1998 pointer: pointer,
1999 touch: touch,
2000 mobileOpera: mobileOpera,
2001 mobileGecko: mobileGecko,
2002 retina: retina,
2003 passiveEvents: passiveEvents,
2004 canvas: canvas,
2005 svg: svg,
2006 vml: vml
2007 });
2008
2009 /*
2010 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2011 */
2012
2013
2014 var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2015 var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2016 var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2017 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2018
2019 var _pointers = {};
2020 var _pointerDocListener = false;
2021
2022 // Provides a touch events wrapper for (ms)pointer events.
2023 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2024
2025 function addPointerListener(obj, type, handler, id) {
2026 if (type === 'touchstart') {
2027 _addPointerStart(obj, handler, id);
2028
2029 } else if (type === 'touchmove') {
2030 _addPointerMove(obj, handler, id);
2031
2032 } else if (type === 'touchend') {
2033 _addPointerEnd(obj, handler, id);
2034 }
2035
2036 return this;
2037 }
2038
2039 function removePointerListener(obj, type, id) {
2040 var handler = obj['_leaflet_' + type + id];
2041
2042 if (type === 'touchstart') {
2043 obj.removeEventListener(POINTER_DOWN, handler, false);
2044
2045 } else if (type === 'touchmove') {
2046 obj.removeEventListener(POINTER_MOVE, handler, false);
2047
2048 } else if (type === 'touchend') {
2049 obj.removeEventListener(POINTER_UP, handler, false);
2050 obj.removeEventListener(POINTER_CANCEL, handler, false);
2051 }
2052
2053 return this;
2054 }
2055
2056 function _addPointerStart(obj, handler, id) {
2057 var onDown = bind(function (e) {
2058 // IE10 specific: MsTouch needs preventDefault. See #2000
2059 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2060 preventDefault(e);
2061 }
2062
2063 _handlePointer(e, handler);
2064 });
2065
2066 obj['_leaflet_touchstart' + id] = onDown;
2067 obj.addEventListener(POINTER_DOWN, onDown, false);
2068
2069 // need to keep track of what pointers and how many are active to provide e.touches emulation
2070 if (!_pointerDocListener) {
2071 // we listen document as any drags that end by moving the touch off the screen get fired there
2072 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2073 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2074 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2075 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2076
2077 _pointerDocListener = true;
2078 }
2079 }
2080
2081 function _globalPointerDown(e) {
2082 _pointers[e.pointerId] = e;
2083 }
2084
2085 function _globalPointerMove(e) {
2086 if (_pointers[e.pointerId]) {
2087 _pointers[e.pointerId] = e;
2088 }
2089 }
2090
2091 function _globalPointerUp(e) {
2092 delete _pointers[e.pointerId];
2093 }
2094
2095 function _handlePointer(e, handler) {
2096 e.touches = [];
2097 for (var i in _pointers) {
2098 e.touches.push(_pointers[i]);
2099 }
2100 e.changedTouches = [e];
2101
2102 handler(e);
2103 }
2104
2105 function _addPointerMove(obj, handler, id) {
2106 var onMove = function (e) {
2107 // don't fire touch moves when mouse isn't down
2108 if ((e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) && e.buttons === 0) {
2109 return;
2110 }
2111
2112 _handlePointer(e, handler);
2113 };
2114
2115 obj['_leaflet_touchmove' + id] = onMove;
2116 obj.addEventListener(POINTER_MOVE, onMove, false);
2117 }
2118
2119 function _addPointerEnd(obj, handler, id) {
2120 var onUp = function (e) {
2121 _handlePointer(e, handler);
2122 };
2123
2124 obj['_leaflet_touchend' + id] = onUp;
2125 obj.addEventListener(POINTER_UP, onUp, false);
2126 obj.addEventListener(POINTER_CANCEL, onUp, false);
2127 }
2128
2129 /*
2130 * Extends the event handling code with double tap support for mobile browsers.
2131 */
2132
2133 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2134 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2135 var _pre = '_leaflet_';
2136
2137 // inspired by Zepto touch code by Thomas Fuchs
2138 function addDoubleTapListener(obj, handler, id) {
2139 var last, touch$$1,
2140 doubleTap = false,
2141 delay = 250;
2142
2143 function onTouchStart(e) {
2144
2145 if (pointer) {
2146 if (!e.isPrimary) { return; }
2147 if (e.pointerType === 'mouse') { return; } // mouse fires native dblclick
2148 } else if (e.touches.length > 1) {
2149 return;
2150 }
2151
2152 var now = Date.now(),
2153 delta = now - (last || now);
2154
2155 touch$$1 = e.touches ? e.touches[0] : e;
2156 doubleTap = (delta > 0 && delta <= delay);
2157 last = now;
2158 }
2159
2160 function onTouchEnd(e) {
2161 if (doubleTap && !touch$$1.cancelBubble) {
2162 if (pointer) {
2163 if (e.pointerType === 'mouse') { return; }
2164 // work around .type being readonly with MSPointer* events
2165 var newTouch = {},
2166 prop, i;
2167
2168 for (i in touch$$1) {
2169 prop = touch$$1[i];
2170 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2171 }
2172 touch$$1 = newTouch;
2173 }
2174 touch$$1.type = 'dblclick';
2175 touch$$1.button = 0;
2176 handler(touch$$1);
2177 last = null;
2178 }
2179 }
2180
2181 obj[_pre + _touchstart + id] = onTouchStart;
2182 obj[_pre + _touchend + id] = onTouchEnd;
2183 obj[_pre + 'dblclick' + id] = handler;
2184
2185 obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
2186 obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
2187
2188 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2189 // the browser doesn't fire touchend/pointerup events but does fire
2190 // native dblclicks. See #4127.
2191 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2192 obj.addEventListener('dblclick', handler, false);
2193
2194 return this;
2195 }
2196
2197 function removeDoubleTapListener(obj, id) {
2198 var touchstart = obj[_pre + _touchstart + id],
2199 touchend = obj[_pre + _touchend + id],
2200 dblclick = obj[_pre + 'dblclick' + id];
2201
2202 obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
2203 obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
2204 obj.removeEventListener('dblclick', dblclick, false);
2205
2206 return this;
2207 }
2208
2209 /*
2210 * @namespace DomUtil
2211 *
2212 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2213 * tree, used by Leaflet internally.
2214 *
2215 * Most functions expecting or returning a `HTMLElement` also work for
2216 * SVG elements. The only difference is that classes refer to CSS classes
2217 * in HTML and SVG classes in SVG.
2218 */
2219
2220
2221 // @property TRANSFORM: String
2222 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2223 var TRANSFORM = testProp(
2224 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2225
2226 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2227 // the same for the transitionend event, in particular the Android 4.1 stock browser
2228
2229 // @property TRANSITION: String
2230 // Vendor-prefixed transition style name.
2231 var TRANSITION = testProp(
2232 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2233
2234 // @property TRANSITION_END: String
2235 // Vendor-prefixed transitionend event name.
2236 var TRANSITION_END =
2237 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2238
2239
2240 // @function get(id: String|HTMLElement): HTMLElement
2241 // Returns an element given its DOM id, or returns the element itself
2242 // if it was passed directly.
2243 function get(id) {
2244 return typeof id === 'string' ? document.getElementById(id) : id;
2245 }
2246
2247 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2248 // Returns the value for a certain style attribute on an element,
2249 // including computed values or values set through CSS.
2250 function getStyle(el, style) {
2251 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2252
2253 if ((!value || value === 'auto') && document.defaultView) {
2254 var css = document.defaultView.getComputedStyle(el, null);
2255 value = css ? css[style] : null;
2256 }
2257 return value === 'auto' ? null : value;
2258 }
2259
2260 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2261 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2262 function create$1(tagName, className, container) {
2263 var el = document.createElement(tagName);
2264 el.className = className || '';
2265
2266 if (container) {
2267 container.appendChild(el);
2268 }
2269 return el;
2270 }
2271
2272 // @function remove(el: HTMLElement)
2273 // Removes `el` from its parent element
2274 function remove(el) {
2275 var parent = el.parentNode;
2276 if (parent) {
2277 parent.removeChild(el);
2278 }
2279 }
2280
2281 // @function empty(el: HTMLElement)
2282 // Removes all of `el`'s children elements from `el`
2283 function empty(el) {
2284 while (el.firstChild) {
2285 el.removeChild(el.firstChild);
2286 }
2287 }
2288
2289 // @function toFront(el: HTMLElement)
2290 // Makes `el` the last child of its parent, so it renders in front of the other children.
2291 function toFront(el) {
2292 var parent = el.parentNode;
2293 if (parent && parent.lastChild !== el) {
2294 parent.appendChild(el);
2295 }
2296 }
2297
2298 // @function toBack(el: HTMLElement)
2299 // Makes `el` the first child of its parent, so it renders behind the other children.
2300 function toBack(el) {
2301 var parent = el.parentNode;
2302 if (parent && parent.firstChild !== el) {
2303 parent.insertBefore(el, parent.firstChild);
2304 }
2305 }
2306
2307 // @function hasClass(el: HTMLElement, name: String): Boolean
2308 // Returns `true` if the element's class attribute contains `name`.
2309 function hasClass(el, name) {
2310 if (el.classList !== undefined) {
2311 return el.classList.contains(name);
2312 }
2313 var className = getClass(el);
2314 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2315 }
2316
2317 // @function addClass(el: HTMLElement, name: String)
2318 // Adds `name` to the element's class attribute.
2319 function addClass(el, name) {
2320 if (el.classList !== undefined) {
2321 var classes = splitWords(name);
2322 for (var i = 0, len = classes.length; i < len; i++) {
2323 el.classList.add(classes[i]);
2324 }
2325 } else if (!hasClass(el, name)) {
2326 var className = getClass(el);
2327 setClass(el, (className ? className + ' ' : '') + name);
2328 }
2329 }
2330
2331 // @function removeClass(el: HTMLElement, name: String)
2332 // Removes `name` from the element's class attribute.
2333 function removeClass(el, name) {
2334 if (el.classList !== undefined) {
2335 el.classList.remove(name);
2336 } else {
2337 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2338 }
2339 }
2340
2341 // @function setClass(el: HTMLElement, name: String)
2342 // Sets the element's class.
2343 function setClass(el, name) {
2344 if (el.className.baseVal === undefined) {
2345 el.className = name;
2346 } else {
2347 // in case of SVG element
2348 el.className.baseVal = name;
2349 }
2350 }
2351
2352 // @function getClass(el: HTMLElement): String
2353 // Returns the element's class.
2354 function getClass(el) {
2355 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2356 // (Required for linked SVG elements in IE11.)
2357 if (el.correspondingElement) {
2358 el = el.correspondingElement;
2359 }
2360 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2361 }
2362
2363 // @function setOpacity(el: HTMLElement, opacity: Number)
2364 // Set the opacity of an element (including old IE support).
2365 // `opacity` must be a number from `0` to `1`.
2366 function setOpacity(el, value) {
2367 if ('opacity' in el.style) {
2368 el.style.opacity = value;
2369 } else if ('filter' in el.style) {
2370 _setOpacityIE(el, value);
2371 }
2372 }
2373
2374 function _setOpacityIE(el, value) {
2375 var filter = false,
2376 filterName = 'DXImageTransform.Microsoft.Alpha';
2377
2378 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2379 try {
2380 filter = el.filters.item(filterName);
2381 } catch (e) {
2382 // don't set opacity to 1 if we haven't already set an opacity,
2383 // it isn't needed and breaks transparent pngs.
2384 if (value === 1) { return; }
2385 }
2386
2387 value = Math.round(value * 100);
2388
2389 if (filter) {
2390 filter.Enabled = (value !== 100);
2391 filter.Opacity = value;
2392 } else {
2393 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2394 }
2395 }
2396
2397 // @function testProp(props: String[]): String|false
2398 // Goes through the array of style names and returns the first name
2399 // that is a valid style name for an element. If no such name is found,
2400 // it returns false. Useful for vendor-prefixed styles like `transform`.
2401 function testProp(props) {
2402 var style = document.documentElement.style;
2403
2404 for (var i = 0; i < props.length; i++) {
2405 if (props[i] in style) {
2406 return props[i];
2407 }
2408 }
2409 return false;
2410 }
2411
2412 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2413 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2414 // and optionally scaled by `scale`. Does not have an effect if the
2415 // browser doesn't support 3D CSS transforms.
2416 function setTransform(el, offset, scale) {
2417 var pos = offset || new Point(0, 0);
2418
2419 el.style[TRANSFORM] =
2420 (ie3d ?
2421 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2422 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2423 (scale ? ' scale(' + scale + ')' : '');
2424 }
2425
2426 // @function setPosition(el: HTMLElement, position: Point)
2427 // Sets the position of `el` to coordinates specified by `position`,
2428 // using CSS translate or top/left positioning depending on the browser
2429 // (used by Leaflet internally to position its layers).
2430 function setPosition(el, point) {
2431
2432 /*eslint-disable */
2433 el._leaflet_pos = point;
2434 /* eslint-enable */
2435
2436 if (any3d) {
2437 setTransform(el, point);
2438 } else {
2439 el.style.left = point.x + 'px';
2440 el.style.top = point.y + 'px';
2441 }
2442 }
2443
2444 // @function getPosition(el: HTMLElement): Point
2445 // Returns the coordinates of an element previously positioned with setPosition.
2446 function getPosition(el) {
2447 // this method is only used for elements previously positioned using setPosition,
2448 // so it's safe to cache the position for performance
2449
2450 return el._leaflet_pos || new Point(0, 0);
2451 }
2452
2453 // @function disableTextSelection()
2454 // Prevents the user from generating `selectstart` DOM events, usually generated
2455 // when the user drags the mouse through a page with text. Used internally
2456 // by Leaflet to override the behaviour of any click-and-drag interaction on
2457 // the map. Affects drag interactions on the whole document.
2458
2459 // @function enableTextSelection()
2460 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2461 var disableTextSelection;
2462 var enableTextSelection;
2463 var _userSelect;
2464 if ('onselectstart' in document) {
2465 disableTextSelection = function () {
2466 on(window, 'selectstart', preventDefault);
2467 };
2468 enableTextSelection = function () {
2469 off(window, 'selectstart', preventDefault);
2470 };
2471 } else {
2472 var userSelectProperty = testProp(
2473 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2474
2475 disableTextSelection = function () {
2476 if (userSelectProperty) {
2477 var style = document.documentElement.style;
2478 _userSelect = style[userSelectProperty];
2479 style[userSelectProperty] = 'none';
2480 }
2481 };
2482 enableTextSelection = function () {
2483 if (userSelectProperty) {
2484 document.documentElement.style[userSelectProperty] = _userSelect;
2485 _userSelect = undefined;
2486 }
2487 };
2488 }
2489
2490 // @function disableImageDrag()
2491 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2492 // for `dragstart` DOM events, usually generated when the user drags an image.
2493 function disableImageDrag() {
2494 on(window, 'dragstart', preventDefault);
2495 }
2496
2497 // @function enableImageDrag()
2498 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2499 function enableImageDrag() {
2500 off(window, 'dragstart', preventDefault);
2501 }
2502
2503 var _outlineElement, _outlineStyle;
2504 // @function preventOutline(el: HTMLElement)
2505 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2506 // of the element `el` invisible. Used internally by Leaflet to prevent
2507 // focusable elements from displaying an outline when the user performs a
2508 // drag interaction on them.
2509 function preventOutline(element) {
2510 while (element.tabIndex === -1) {
2511 element = element.parentNode;
2512 }
2513 if (!element.style) { return; }
2514 restoreOutline();
2515 _outlineElement = element;
2516 _outlineStyle = element.style.outline;
2517 element.style.outline = 'none';
2518 on(window, 'keydown', restoreOutline);
2519 }
2520
2521 // @function restoreOutline()
2522 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2523 function restoreOutline() {
2524 if (!_outlineElement) { return; }
2525 _outlineElement.style.outline = _outlineStyle;
2526 _outlineElement = undefined;
2527 _outlineStyle = undefined;
2528 off(window, 'keydown', restoreOutline);
2529 }
2530
2531 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2532 // Finds the closest parent node which size (width and height) is not null.
2533 function getSizedParentNode(element) {
2534 do {
2535 element = element.parentNode;
2536 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2537 return element;
2538 }
2539
2540 // @function getScale(el: HTMLElement): Object
2541 // Computes the CSS scale currently applied on the element.
2542 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2543 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2544 function getScale(element) {
2545 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2546
2547 return {
2548 x: rect.width / element.offsetWidth || 1,
2549 y: rect.height / element.offsetHeight || 1,
2550 boundingClientRect: rect
2551 };
2552 }
2553
2554 var DomUtil = ({
2555 TRANSFORM: TRANSFORM,
2556 TRANSITION: TRANSITION,
2557 TRANSITION_END: TRANSITION_END,
2558 get: get,
2559 getStyle: getStyle,
2560 create: create$1,
2561 remove: remove,
2562 empty: empty,
2563 toFront: toFront,
2564 toBack: toBack,
2565 hasClass: hasClass,
2566 addClass: addClass,
2567 removeClass: removeClass,
2568 setClass: setClass,
2569 getClass: getClass,
2570 setOpacity: setOpacity,
2571 testProp: testProp,
2572 setTransform: setTransform,
2573 setPosition: setPosition,
2574 getPosition: getPosition,
2575 disableTextSelection: disableTextSelection,
2576 enableTextSelection: enableTextSelection,
2577 disableImageDrag: disableImageDrag,
2578 enableImageDrag: enableImageDrag,
2579 preventOutline: preventOutline,
2580 restoreOutline: restoreOutline,
2581 getSizedParentNode: getSizedParentNode,
2582 getScale: getScale
2583 });
2584
2585 /*
2586 * @namespace DomEvent
2587 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2588 */
2589
2590 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2591
2592 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2593 // Adds a listener function (`fn`) to a particular DOM event type of the
2594 // element `el`. You can optionally specify the context of the listener
2595 // (object the `this` keyword will point to). You can also pass several
2596 // space-separated types (e.g. `'click dblclick'`).
2597
2598 // @alternative
2599 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2600 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2601 function on(obj, types, fn, context) {
2602
2603 if (typeof types === 'object') {
2604 for (var type in types) {
2605 addOne(obj, type, types[type], fn);
2606 }
2607 } else {
2608 types = splitWords(types);
2609
2610 for (var i = 0, len = types.length; i < len; i++) {
2611 addOne(obj, types[i], fn, context);
2612 }
2613 }
2614
2615 return this;
2616 }
2617
2618 var eventsKey = '_leaflet_events';
2619
2620 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2621 // Removes a previously added listener function.
2622 // Note that if you passed a custom context to on, you must pass the same
2623 // context to `off` in order to remove the listener.
2624
2625 // @alternative
2626 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2627 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2628 function off(obj, types, fn, context) {
2629
2630 if (typeof types === 'object') {
2631 for (var type in types) {
2632 removeOne(obj, type, types[type], fn);
2633 }
2634 } else if (types) {
2635 types = splitWords(types);
2636
2637 for (var i = 0, len = types.length; i < len; i++) {
2638 removeOne(obj, types[i], fn, context);
2639 }
2640 } else {
2641 for (var j in obj[eventsKey]) {
2642 removeOne(obj, j, obj[eventsKey][j]);
2643 }
2644 delete obj[eventsKey];
2645 }
2646
2647 return this;
2648 }
2649
2650 function browserFiresNativeDblClick() {
2651 // See https://github.com/w3c/pointerevents/issues/171
2652 if (pointer) {
2653 return !(edge || safari);
2654 }
2655 }
2656
2657 var mouseSubst = {
2658 mouseenter: 'mouseover',
2659 mouseleave: 'mouseout',
2660 wheel: !('onwheel' in window) && 'mousewheel'
2661 };
2662
2663 function addOne(obj, type, fn, context) {
2664 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2665
2666 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2667
2668 var handler = function (e) {
2669 return fn.call(context || obj, e || window.event);
2670 };
2671
2672 var originalHandler = handler;
2673
2674 if (pointer && type.indexOf('touch') === 0) {
2675 // Needs DomEvent.Pointer.js
2676 addPointerListener(obj, type, handler, id);
2677
2678 } else if (touch && (type === 'dblclick') && !browserFiresNativeDblClick()) {
2679 addDoubleTapListener(obj, handler, id);
2680
2681 } else if ('addEventListener' in obj) {
2682
2683 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
2684 obj.addEventListener(mouseSubst[type] || type, handler, passiveEvents ? {passive: false} : false);
2685
2686 } else if (type === 'mouseenter' || type === 'mouseleave') {
2687 handler = function (e) {
2688 e = e || window.event;
2689 if (isExternalTarget(obj, e)) {
2690 originalHandler(e);
2691 }
2692 };
2693 obj.addEventListener(mouseSubst[type], handler, false);
2694
2695 } else {
2696 obj.addEventListener(type, originalHandler, false);
2697 }
2698
2699 } else if ('attachEvent' in obj) {
2700 obj.attachEvent('on' + type, handler);
2701 }
2702
2703 obj[eventsKey] = obj[eventsKey] || {};
2704 obj[eventsKey][id] = handler;
2705 }
2706
2707 function removeOne(obj, type, fn, context) {
2708
2709 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2710 handler = obj[eventsKey] && obj[eventsKey][id];
2711
2712 if (!handler) { return this; }
2713
2714 if (pointer && type.indexOf('touch') === 0) {
2715 removePointerListener(obj, type, id);
2716
2717 } else if (touch && (type === 'dblclick') && !browserFiresNativeDblClick()) {
2718 removeDoubleTapListener(obj, id);
2719
2720 } else if ('removeEventListener' in obj) {
2721
2722 obj.removeEventListener(mouseSubst[type] || type, handler, false);
2723
2724 } else if ('detachEvent' in obj) {
2725 obj.detachEvent('on' + type, handler);
2726 }
2727
2728 obj[eventsKey][id] = null;
2729 }
2730
2731 // @function stopPropagation(ev: DOMEvent): this
2732 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2733 // ```js
2734 // L.DomEvent.on(div, 'click', function (ev) {
2735 // L.DomEvent.stopPropagation(ev);
2736 // });
2737 // ```
2738 function stopPropagation(e) {
2739
2740 if (e.stopPropagation) {
2741 e.stopPropagation();
2742 } else if (e.originalEvent) { // In case of Leaflet event.
2743 e.originalEvent._stopped = true;
2744 } else {
2745 e.cancelBubble = true;
2746 }
2747 skipped(e);
2748
2749 return this;
2750 }
2751
2752 // @function disableScrollPropagation(el: HTMLElement): this
2753 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
2754 function disableScrollPropagation(el) {
2755 addOne(el, 'wheel', stopPropagation);
2756 return this;
2757 }
2758
2759 // @function disableClickPropagation(el: HTMLElement): this
2760 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2761 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2762 function disableClickPropagation(el) {
2763 on(el, 'mousedown touchstart dblclick', stopPropagation);
2764 addOne(el, 'click', fakeStop);
2765 return this;
2766 }
2767
2768 // @function preventDefault(ev: DOMEvent): this
2769 // Prevents the default action of the DOM Event `ev` from happening (such as
2770 // following a link in the href of the a element, or doing a POST request
2771 // with page reload when a `<form>` is submitted).
2772 // Use it inside listener functions.
2773 function preventDefault(e) {
2774 if (e.preventDefault) {
2775 e.preventDefault();
2776 } else {
2777 e.returnValue = false;
2778 }
2779 return this;
2780 }
2781
2782 // @function stop(ev: DOMEvent): this
2783 // Does `stopPropagation` and `preventDefault` at the same time.
2784 function stop(e) {
2785 preventDefault(e);
2786 stopPropagation(e);
2787 return this;
2788 }
2789
2790 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2791 // Gets normalized mouse position from a DOM event relative to the
2792 // `container` (border excluded) or to the whole page if not specified.
2793 function getMousePosition(e, container) {
2794 if (!container) {
2795 return new Point(e.clientX, e.clientY);
2796 }
2797
2798 var scale = getScale(container),
2799 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2800
2801 return new Point(
2802 // offset.left/top values are in page scale (like clientX/Y),
2803 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2804 (e.clientX - offset.left) / scale.x - container.clientLeft,
2805 (e.clientY - offset.top) / scale.y - container.clientTop
2806 );
2807 }
2808
2809 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2810 // and Firefox scrolls device pixels, not CSS pixels
2811 var wheelPxFactor =
2812 (win && chrome) ? 2 * window.devicePixelRatio :
2813 gecko ? window.devicePixelRatio : 1;
2814
2815 // @function getWheelDelta(ev: DOMEvent): Number
2816 // Gets normalized wheel delta from a wheel DOM event, in vertical
2817 // pixels scrolled (negative if scrolling down).
2818 // Events from pointing devices without precise scrolling are mapped to
2819 // a best guess of 60 pixels.
2820 function getWheelDelta(e) {
2821 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2822 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2823 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2824 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2825 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2826 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2827 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2828 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2829 0;
2830 }
2831
2832 var skipEvents = {};
2833
2834 function fakeStop(e) {
2835 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2836 skipEvents[e.type] = true;
2837 }
2838
2839 function skipped(e) {
2840 var events = skipEvents[e.type];
2841 // reset when checking, as it's only used in map container and propagates outside of the map
2842 skipEvents[e.type] = false;
2843 return events;
2844 }
2845
2846 // check if element really left/entered the event target (for mouseenter/mouseleave)
2847 function isExternalTarget(el, e) {
2848
2849 var related = e.relatedTarget;
2850
2851 if (!related) { return true; }
2852
2853 try {
2854 while (related && (related !== el)) {
2855 related = related.parentNode;
2856 }
2857 } catch (err) {
2858 return false;
2859 }
2860 return (related !== el);
2861 }
2862
2863 var DomEvent = ({
2864 on: on,
2865 off: off,
2866 stopPropagation: stopPropagation,
2867 disableScrollPropagation: disableScrollPropagation,
2868 disableClickPropagation: disableClickPropagation,
2869 preventDefault: preventDefault,
2870 stop: stop,
2871 getMousePosition: getMousePosition,
2872 getWheelDelta: getWheelDelta,
2873 fakeStop: fakeStop,
2874 skipped: skipped,
2875 isExternalTarget: isExternalTarget,
2876 addListener: on,
2877 removeListener: off
2878 });
2879
2880 /*
2881 * @class PosAnimation
2882 * @aka L.PosAnimation
2883 * @inherits Evented
2884 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2885 *
2886 * @example
2887 * ```js
2888 * var fx = new L.PosAnimation();
2889 * fx.run(el, [300, 500], 0.5);
2890 * ```
2891 *
2892 * @constructor L.PosAnimation()
2893 * Creates a `PosAnimation` object.
2894 *
2895 */
2896
2897 var PosAnimation = Evented.extend({
2898
2899 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2900 // Run an animation of a given element to a new position, optionally setting
2901 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2902 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2903 // `0.5` by default).
2904 run: function (el, newPos, duration, easeLinearity) {
2905 this.stop();
2906
2907 this._el = el;
2908 this._inProgress = true;
2909 this._duration = duration || 0.25;
2910 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2911
2912 this._startPos = getPosition(el);
2913 this._offset = newPos.subtract(this._startPos);
2914 this._startTime = +new Date();
2915
2916 // @event start: Event
2917 // Fired when the animation starts
2918 this.fire('start');
2919
2920 this._animate();
2921 },
2922
2923 // @method stop()
2924 // Stops the animation (if currently running).
2925 stop: function () {
2926 if (!this._inProgress) { return; }
2927
2928 this._step(true);
2929 this._complete();
2930 },
2931
2932 _animate: function () {
2933 // animation loop
2934 this._animId = requestAnimFrame(this._animate, this);
2935 this._step();
2936 },
2937
2938 _step: function (round) {
2939 var elapsed = (+new Date()) - this._startTime,
2940 duration = this._duration * 1000;
2941
2942 if (elapsed < duration) {
2943 this._runFrame(this._easeOut(elapsed / duration), round);
2944 } else {
2945 this._runFrame(1);
2946 this._complete();
2947 }
2948 },
2949
2950 _runFrame: function (progress, round) {
2951 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2952 if (round) {
2953 pos._round();
2954 }
2955 setPosition(this._el, pos);
2956
2957 // @event step: Event
2958 // Fired continuously during the animation.
2959 this.fire('step');
2960 },
2961
2962 _complete: function () {
2963 cancelAnimFrame(this._animId);
2964
2965 this._inProgress = false;
2966 // @event end: Event
2967 // Fired when the animation ends.
2968 this.fire('end');
2969 },
2970
2971 _easeOut: function (t) {
2972 return 1 - Math.pow(1 - t, this._easeOutPower);
2973 }
2974 });
2975
2976 /*
2977 * @class Map
2978 * @aka L.Map
2979 * @inherits Evented
2980 *
2981 * The central class of the API — it is used to create a map on a page and manipulate it.
2982 *
2983 * @example
2984 *
2985 * ```js
2986 * // initialize the map on the "map" div with a given center and zoom
2987 * var map = L.map('map', {
2988 * center: [51.505, -0.09],
2989 * zoom: 13
2990 * });
2991 * ```
2992 *
2993 */
2994
2995 var Map = Evented.extend({
2996
2997 options: {
2998 // @section Map State Options
2999 // @option crs: CRS = L.CRS.EPSG3857
3000 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3001 // sure what it means.
3002 crs: EPSG3857,
3003
3004 // @option center: LatLng = undefined
3005 // Initial geographic center of the map
3006 center: undefined,
3007
3008 // @option zoom: Number = undefined
3009 // Initial map zoom level
3010 zoom: undefined,
3011
3012 // @option minZoom: Number = *
3013 // Minimum zoom level of the map.
3014 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3015 // the lowest of their `minZoom` options will be used instead.
3016 minZoom: undefined,
3017
3018 // @option maxZoom: Number = *
3019 // Maximum zoom level of the map.
3020 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3021 // the highest of their `maxZoom` options will be used instead.
3022 maxZoom: undefined,
3023
3024 // @option layers: Layer[] = []
3025 // Array of layers that will be added to the map initially
3026 layers: [],
3027
3028 // @option maxBounds: LatLngBounds = null
3029 // When this option is set, the map restricts the view to the given
3030 // geographical bounds, bouncing the user back if the user tries to pan
3031 // outside the view. To set the restriction dynamically, use
3032 // [`setMaxBounds`](#map-setmaxbounds) method.
3033 maxBounds: undefined,
3034
3035 // @option renderer: Renderer = *
3036 // The default method for drawing vector layers on the map. `L.SVG`
3037 // or `L.Canvas` by default depending on browser support.
3038 renderer: undefined,
3039
3040
3041 // @section Animation Options
3042 // @option zoomAnimation: Boolean = true
3043 // Whether the map zoom animation is enabled. By default it's enabled
3044 // in all browsers that support CSS3 Transitions except Android.
3045 zoomAnimation: true,
3046
3047 // @option zoomAnimationThreshold: Number = 4
3048 // Won't animate zoom if the zoom difference exceeds this value.
3049 zoomAnimationThreshold: 4,
3050
3051 // @option fadeAnimation: Boolean = true
3052 // Whether the tile fade animation is enabled. By default it's enabled
3053 // in all browsers that support CSS3 Transitions except Android.
3054 fadeAnimation: true,
3055
3056 // @option markerZoomAnimation: Boolean = true
3057 // Whether markers animate their zoom with the zoom animation, if disabled
3058 // they will disappear for the length of the animation. By default it's
3059 // enabled in all browsers that support CSS3 Transitions except Android.
3060 markerZoomAnimation: true,
3061
3062 // @option transform3DLimit: Number = 2^23
3063 // Defines the maximum size of a CSS translation transform. The default
3064 // value should not be changed unless a web browser positions layers in
3065 // the wrong place after doing a large `panBy`.
3066 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3067
3068 // @section Interaction Options
3069 // @option zoomSnap: Number = 1
3070 // Forces the map's zoom level to always be a multiple of this, particularly
3071 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3072 // By default, the zoom level snaps to the nearest integer; lower values
3073 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3074 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3075 zoomSnap: 1,
3076
3077 // @option zoomDelta: Number = 1
3078 // Controls how much the map's zoom level will change after a
3079 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3080 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3081 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3082 zoomDelta: 1,
3083
3084 // @option trackResize: Boolean = true
3085 // Whether the map automatically handles browser window resize to update itself.
3086 trackResize: true
3087 },
3088
3089 initialize: function (id, options) { // (HTMLElement or String, Object)
3090 options = setOptions(this, options);
3091
3092 // Make sure to assign internal flags at the beginning,
3093 // to avoid inconsistent state in some edge cases.
3094 this._handlers = [];
3095 this._layers = {};
3096 this._zoomBoundLayers = {};
3097 this._sizeChanged = true;
3098
3099 this._initContainer(id);
3100 this._initLayout();
3101
3102 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3103 this._onResize = bind(this._onResize, this);
3104
3105 this._initEvents();
3106
3107 if (options.maxBounds) {
3108 this.setMaxBounds(options.maxBounds);
3109 }
3110
3111 if (options.zoom !== undefined) {
3112 this._zoom = this._limitZoom(options.zoom);
3113 }
3114
3115 if (options.center && options.zoom !== undefined) {
3116 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3117 }
3118
3119 this.callInitHooks();
3120
3121 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3122 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3123 this.options.zoomAnimation;
3124
3125 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3126 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3127 if (this._zoomAnimated) {
3128 this._createAnimProxy();
3129 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3130 }
3131
3132 this._addLayers(this.options.layers);
3133 },
3134
3135
3136 // @section Methods for modifying map state
3137
3138 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3139 // Sets the view of the map (geographical center and zoom) with the given
3140 // animation options.
3141 setView: function (center, zoom, options) {
3142
3143 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3144 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3145 options = options || {};
3146
3147 this._stop();
3148
3149 if (this._loaded && !options.reset && options !== true) {
3150
3151 if (options.animate !== undefined) {
3152 options.zoom = extend({animate: options.animate}, options.zoom);
3153 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3154 }
3155
3156 // try animating pan or zoom
3157 var moved = (this._zoom !== zoom) ?
3158 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3159 this._tryAnimatedPan(center, options.pan);
3160
3161 if (moved) {
3162 // prevent resize handler call, the view will refresh after animation anyway
3163 clearTimeout(this._sizeTimer);
3164 return this;
3165 }
3166 }
3167
3168 // animation didn't start, just reset the map view
3169 this._resetView(center, zoom);
3170
3171 return this;
3172 },
3173
3174 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3175 // Sets the zoom of the map.
3176 setZoom: function (zoom, options) {
3177 if (!this._loaded) {
3178 this._zoom = zoom;
3179 return this;
3180 }
3181 return this.setView(this.getCenter(), zoom, {zoom: options});
3182 },
3183
3184 // @method zoomIn(delta?: Number, options?: Zoom options): this
3185 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3186 zoomIn: function (delta, options) {
3187 delta = delta || (any3d ? this.options.zoomDelta : 1);
3188 return this.setZoom(this._zoom + delta, options);
3189 },
3190
3191 // @method zoomOut(delta?: Number, options?: Zoom options): this
3192 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3193 zoomOut: function (delta, options) {
3194 delta = delta || (any3d ? this.options.zoomDelta : 1);
3195 return this.setZoom(this._zoom - delta, options);
3196 },
3197
3198 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3199 // Zooms the map while keeping a specified geographical point on the map
3200 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3201 // @alternative
3202 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3203 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3204 setZoomAround: function (latlng, zoom, options) {
3205 var scale = this.getZoomScale(zoom),
3206 viewHalf = this.getSize().divideBy(2),
3207 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3208
3209 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3210 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3211
3212 return this.setView(newCenter, zoom, {zoom: options});
3213 },
3214
3215 _getBoundsCenterZoom: function (bounds, options) {
3216
3217 options = options || {};
3218 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3219
3220 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3221 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3222
3223 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3224
3225 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3226
3227 if (zoom === Infinity) {
3228 return {
3229 center: bounds.getCenter(),
3230 zoom: zoom
3231 };
3232 }
3233
3234 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3235
3236 swPoint = this.project(bounds.getSouthWest(), zoom),
3237 nePoint = this.project(bounds.getNorthEast(), zoom),
3238 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3239
3240 return {
3241 center: center,
3242 zoom: zoom
3243 };
3244 },
3245
3246 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3247 // Sets a map view that contains the given geographical bounds with the
3248 // maximum zoom level possible.
3249 fitBounds: function (bounds, options) {
3250
3251 bounds = toLatLngBounds(bounds);
3252
3253 if (!bounds.isValid()) {
3254 throw new Error('Bounds are not valid.');
3255 }
3256
3257 var target = this._getBoundsCenterZoom(bounds, options);
3258 return this.setView(target.center, target.zoom, options);
3259 },
3260
3261 // @method fitWorld(options?: fitBounds options): this
3262 // Sets a map view that mostly contains the whole world with the maximum
3263 // zoom level possible.
3264 fitWorld: function (options) {
3265 return this.fitBounds([[-90, -180], [90, 180]], options);
3266 },
3267
3268 // @method panTo(latlng: LatLng, options?: Pan options): this
3269 // Pans the map to a given center.
3270 panTo: function (center, options) { // (LatLng)
3271 return this.setView(center, this._zoom, {pan: options});
3272 },
3273
3274 // @method panBy(offset: Point, options?: Pan options): this
3275 // Pans the map by a given number of pixels (animated).
3276 panBy: function (offset, options) {
3277 offset = toPoint(offset).round();
3278 options = options || {};
3279
3280 if (!offset.x && !offset.y) {
3281 return this.fire('moveend');
3282 }
3283 // If we pan too far, Chrome gets issues with tiles
3284 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3285 if (options.animate !== true && !this.getSize().contains(offset)) {
3286 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3287 return this;
3288 }
3289
3290 if (!this._panAnim) {
3291 this._panAnim = new PosAnimation();
3292
3293 this._panAnim.on({
3294 'step': this._onPanTransitionStep,
3295 'end': this._onPanTransitionEnd
3296 }, this);
3297 }
3298
3299 // don't fire movestart if animating inertia
3300 if (!options.noMoveStart) {
3301 this.fire('movestart');
3302 }
3303
3304 // animate pan unless animate: false specified
3305 if (options.animate !== false) {
3306 addClass(this._mapPane, 'leaflet-pan-anim');
3307
3308 var newPos = this._getMapPanePos().subtract(offset).round();
3309 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3310 } else {
3311 this._rawPanBy(offset);
3312 this.fire('move').fire('moveend');
3313 }
3314
3315 return this;
3316 },
3317
3318 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3319 // Sets the view of the map (geographical center and zoom) performing a smooth
3320 // pan-zoom animation.
3321 flyTo: function (targetCenter, targetZoom, options) {
3322
3323 options = options || {};
3324 if (options.animate === false || !any3d) {
3325 return this.setView(targetCenter, targetZoom, options);
3326 }
3327
3328 this._stop();
3329
3330 var from = this.project(this.getCenter()),
3331 to = this.project(targetCenter),
3332 size = this.getSize(),
3333 startZoom = this._zoom;
3334
3335 targetCenter = toLatLng(targetCenter);
3336 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3337
3338 var w0 = Math.max(size.x, size.y),
3339 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3340 u1 = (to.distanceTo(from)) || 1,
3341 rho = 1.42,
3342 rho2 = rho * rho;
3343
3344 function r(i) {
3345 var s1 = i ? -1 : 1,
3346 s2 = i ? w1 : w0,
3347 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3348 b1 = 2 * s2 * rho2 * u1,
3349 b = t1 / b1,
3350 sq = Math.sqrt(b * b + 1) - b;
3351
3352 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3353 // thus triggering an infinite loop in flyTo
3354 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3355
3356 return log;
3357 }
3358
3359 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3360 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3361 function tanh(n) { return sinh(n) / cosh(n); }
3362
3363 var r0 = r(0);
3364
3365 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3366 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3367
3368 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3369
3370 var start = Date.now(),
3371 S = (r(1) - r0) / rho,
3372 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3373
3374 function frame() {
3375 var t = (Date.now() - start) / duration,
3376 s = easeOut(t) * S;
3377
3378 if (t <= 1) {
3379 this._flyToFrame = requestAnimFrame(frame, this);
3380
3381 this._move(
3382 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3383 this.getScaleZoom(w0 / w(s), startZoom),
3384 {flyTo: true});
3385
3386 } else {
3387 this
3388 ._move(targetCenter, targetZoom)
3389 ._moveEnd(true);
3390 }
3391 }
3392
3393 this._moveStart(true, options.noMoveStart);
3394
3395 frame.call(this);
3396 return this;
3397 },
3398
3399 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3400 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3401 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3402 flyToBounds: function (bounds, options) {
3403 var target = this._getBoundsCenterZoom(bounds, options);
3404 return this.flyTo(target.center, target.zoom, options);
3405 },
3406
3407 // @method setMaxBounds(bounds: LatLngBounds): this
3408 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3409 setMaxBounds: function (bounds) {
3410 bounds = toLatLngBounds(bounds);
3411
3412 if (!bounds.isValid()) {
3413 this.options.maxBounds = null;
3414 return this.off('moveend', this._panInsideMaxBounds);
3415 } else if (this.options.maxBounds) {
3416 this.off('moveend', this._panInsideMaxBounds);
3417 }
3418
3419 this.options.maxBounds = bounds;
3420
3421 if (this._loaded) {
3422 this._panInsideMaxBounds();
3423 }
3424
3425 return this.on('moveend', this._panInsideMaxBounds);
3426 },
3427
3428 // @method setMinZoom(zoom: Number): this
3429 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3430 setMinZoom: function (zoom) {
3431 var oldZoom = this.options.minZoom;
3432 this.options.minZoom = zoom;
3433
3434 if (this._loaded && oldZoom !== zoom) {
3435 this.fire('zoomlevelschange');
3436
3437 if (this.getZoom() < this.options.minZoom) {
3438 return this.setZoom(zoom);
3439 }
3440 }
3441
3442 return this;
3443 },
3444
3445 // @method setMaxZoom(zoom: Number): this
3446 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3447 setMaxZoom: function (zoom) {
3448 var oldZoom = this.options.maxZoom;
3449 this.options.maxZoom = zoom;
3450
3451 if (this._loaded && oldZoom !== zoom) {
3452 this.fire('zoomlevelschange');
3453
3454 if (this.getZoom() > this.options.maxZoom) {
3455 return this.setZoom(zoom);
3456 }
3457 }
3458
3459 return this;
3460 },
3461
3462 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3463 // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
3464 panInsideBounds: function (bounds, options) {
3465 this._enforcingBounds = true;
3466 var center = this.getCenter(),
3467 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3468
3469 if (!center.equals(newCenter)) {
3470 this.panTo(newCenter, options);
3471 }
3472
3473 this._enforcingBounds = false;
3474 return this;
3475 },
3476
3477 // @method panInside(latlng: LatLng, options?: options): this
3478 // Pans the map the minimum amount to make the `latlng` visible. Use
3479 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3480 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3481 // If `latlng` is already within the (optionally padded) display bounds,
3482 // the map will not be panned.
3483 panInside: function (latlng, options) {
3484 options = options || {};
3485
3486 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3487 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3488 center = this.getCenter(),
3489 pixelCenter = this.project(center),
3490 pixelPoint = this.project(latlng),
3491 pixelBounds = this.getPixelBounds(),
3492 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3493 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3494
3495 if (!paddedBounds.contains(pixelPoint)) {
3496 this._enforcingBounds = true;
3497 var diff = pixelCenter.subtract(pixelPoint),
3498 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3499
3500 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3501 newCenter.x = pixelCenter.x - diff.x;
3502 if (diff.x > 0) {
3503 newCenter.x += halfPixelBounds.x - paddingTL.x;
3504 } else {
3505 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3506 }
3507 }
3508 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3509 newCenter.y = pixelCenter.y - diff.y;
3510 if (diff.y > 0) {
3511 newCenter.y += halfPixelBounds.y - paddingTL.y;
3512 } else {
3513 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3514 }
3515 }
3516 this.panTo(this.unproject(newCenter), options);
3517 this._enforcingBounds = false;
3518 }
3519 return this;
3520 },
3521
3522 // @method invalidateSize(options: Zoom/pan options): this
3523 // Checks if the map container size changed and updates the map if so —
3524 // call it after you've changed the map size dynamically, also animating
3525 // pan by default. If `options.pan` is `false`, panning will not occur.
3526 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3527 // that it doesn't happen often even if the method is called many
3528 // times in a row.
3529
3530 // @alternative
3531 // @method invalidateSize(animate: Boolean): this
3532 // Checks if the map container size changed and updates the map if so —
3533 // call it after you've changed the map size dynamically, also animating
3534 // pan by default.
3535 invalidateSize: function (options) {
3536 if (!this._loaded) { return this; }
3537
3538 options = extend({
3539 animate: false,
3540 pan: true
3541 }, options === true ? {animate: true} : options);
3542
3543 var oldSize = this.getSize();
3544 this._sizeChanged = true;
3545 this._lastCenter = null;
3546
3547 var newSize = this.getSize(),
3548 oldCenter = oldSize.divideBy(2).round(),
3549 newCenter = newSize.divideBy(2).round(),
3550 offset = oldCenter.subtract(newCenter);
3551
3552 if (!offset.x && !offset.y) { return this; }
3553
3554 if (options.animate && options.pan) {
3555 this.panBy(offset);
3556
3557 } else {
3558 if (options.pan) {
3559 this._rawPanBy(offset);
3560 }
3561
3562 this.fire('move');
3563
3564 if (options.debounceMoveend) {
3565 clearTimeout(this._sizeTimer);
3566 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3567 } else {
3568 this.fire('moveend');
3569 }
3570 }
3571
3572 // @section Map state change events
3573 // @event resize: ResizeEvent
3574 // Fired when the map is resized.
3575 return this.fire('resize', {
3576 oldSize: oldSize,
3577 newSize: newSize
3578 });
3579 },
3580
3581 // @section Methods for modifying map state
3582 // @method stop(): this
3583 // Stops the currently running `panTo` or `flyTo` animation, if any.
3584 stop: function () {
3585 this.setZoom(this._limitZoom(this._zoom));
3586 if (!this.options.zoomSnap) {
3587 this.fire('viewreset');
3588 }
3589 return this._stop();
3590 },
3591
3592 // @section Geolocation methods
3593 // @method locate(options?: Locate options): this
3594 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3595 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3596 // and optionally sets the map view to the user's location with respect to
3597 // detection accuracy (or to the world view if geolocation failed).
3598 // Note that, if your page doesn't use HTTPS, this method will fail in
3599 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3600 // See `Locate options` for more details.
3601 locate: function (options) {
3602
3603 options = this._locateOptions = extend({
3604 timeout: 10000,
3605 watch: false
3606 // setView: false
3607 // maxZoom: <Number>
3608 // maximumAge: 0
3609 // enableHighAccuracy: false
3610 }, options);
3611
3612 if (!('geolocation' in navigator)) {
3613 this._handleGeolocationError({
3614 code: 0,
3615 message: 'Geolocation not supported.'
3616 });
3617 return this;
3618 }
3619
3620 var onResponse = bind(this._handleGeolocationResponse, this),
3621 onError = bind(this._handleGeolocationError, this);
3622
3623 if (options.watch) {
3624 this._locationWatchId =
3625 navigator.geolocation.watchPosition(onResponse, onError, options);
3626 } else {
3627 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3628 }
3629 return this;
3630 },
3631
3632 // @method stopLocate(): this
3633 // Stops watching location previously initiated by `map.locate({watch: true})`
3634 // and aborts resetting the map view if map.locate was called with
3635 // `{setView: true}`.
3636 stopLocate: function () {
3637 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3638 navigator.geolocation.clearWatch(this._locationWatchId);
3639 }
3640 if (this._locateOptions) {
3641 this._locateOptions.setView = false;
3642 }
3643 return this;
3644 },
3645
3646 _handleGeolocationError: function (error) {
3647 var c = error.code,
3648 message = error.message ||
3649 (c === 1 ? 'permission denied' :
3650 (c === 2 ? 'position unavailable' : 'timeout'));
3651
3652 if (this._locateOptions.setView && !this._loaded) {
3653 this.fitWorld();
3654 }
3655
3656 // @section Location events
3657 // @event locationerror: ErrorEvent
3658 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3659 this.fire('locationerror', {
3660 code: c,
3661 message: 'Geolocation error: ' + message + '.'
3662 });
3663 },
3664
3665 _handleGeolocationResponse: function (pos) {
3666 var lat = pos.coords.latitude,
3667 lng = pos.coords.longitude,
3668 latlng = new LatLng(lat, lng),
3669 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3670 options = this._locateOptions;
3671
3672 if (options.setView) {
3673 var zoom = this.getBoundsZoom(bounds);
3674 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3675 }
3676
3677 var data = {
3678 latlng: latlng,
3679 bounds: bounds,
3680 timestamp: pos.timestamp
3681 };
3682
3683 for (var i in pos.coords) {
3684 if (typeof pos.coords[i] === 'number') {
3685 data[i] = pos.coords[i];
3686 }
3687 }
3688
3689 // @event locationfound: LocationEvent
3690 // Fired when geolocation (using the [`locate`](#map-locate) method)
3691 // went successfully.
3692 this.fire('locationfound', data);
3693 },
3694
3695 // TODO Appropriate docs section?
3696 // @section Other Methods
3697 // @method addHandler(name: String, HandlerClass: Function): this
3698 // Adds a new `Handler` to the map, given its name and constructor function.
3699 addHandler: function (name, HandlerClass) {
3700 if (!HandlerClass) { return this; }
3701
3702 var handler = this[name] = new HandlerClass(this);
3703
3704 this._handlers.push(handler);
3705
3706 if (this.options[name]) {
3707 handler.enable();
3708 }
3709
3710 return this;
3711 },
3712
3713 // @method remove(): this
3714 // Destroys the map and clears all related event listeners.
3715 remove: function () {
3716
3717 this._initEvents(true);
3718 this.off('moveend', this._panInsideMaxBounds);
3719
3720 if (this._containerId !== this._container._leaflet_id) {
3721 throw new Error('Map container is being reused by another instance');
3722 }
3723
3724 try {
3725 // throws error in IE6-8
3726 delete this._container._leaflet_id;
3727 delete this._containerId;
3728 } catch (e) {
3729 /*eslint-disable */
3730 this._container._leaflet_id = undefined;
3731 /* eslint-enable */
3732 this._containerId = undefined;
3733 }
3734
3735 if (this._locationWatchId !== undefined) {
3736 this.stopLocate();
3737 }
3738
3739 this._stop();
3740
3741 remove(this._mapPane);
3742
3743 if (this._clearControlPos) {
3744 this._clearControlPos();
3745 }
3746 if (this._resizeRequest) {
3747 cancelAnimFrame(this._resizeRequest);
3748 this._resizeRequest = null;
3749 }
3750
3751 this._clearHandlers();
3752
3753 if (this._loaded) {
3754 // @section Map state change events
3755 // @event unload: Event
3756 // Fired when the map is destroyed with [remove](#map-remove) method.
3757 this.fire('unload');
3758 }
3759
3760 var i;
3761 for (i in this._layers) {
3762 this._layers[i].remove();
3763 }
3764 for (i in this._panes) {
3765 remove(this._panes[i]);
3766 }
3767
3768 this._layers = [];
3769 this._panes = [];
3770 delete this._mapPane;
3771 delete this._renderer;
3772
3773 return this;
3774 },
3775
3776 // @section Other Methods
3777 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3778 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3779 // then returns it. The pane is created as a child of `container`, or
3780 // as a child of the main map pane if not set.
3781 createPane: function (name, container) {
3782 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3783 pane = create$1('div', className, container || this._mapPane);
3784
3785 if (name) {
3786 this._panes[name] = pane;
3787 }
3788 return pane;
3789 },
3790
3791 // @section Methods for Getting Map State
3792
3793 // @method getCenter(): LatLng
3794 // Returns the geographical center of the map view
3795 getCenter: function () {
3796 this._checkIfLoaded();
3797
3798 if (this._lastCenter && !this._moved()) {
3799 return this._lastCenter;
3800 }
3801 return this.layerPointToLatLng(this._getCenterLayerPoint());
3802 },
3803
3804 // @method getZoom(): Number
3805 // Returns the current zoom level of the map view
3806 getZoom: function () {
3807 return this._zoom;
3808 },
3809
3810 // @method getBounds(): LatLngBounds
3811 // Returns the geographical bounds visible in the current map view
3812 getBounds: function () {
3813 var bounds = this.getPixelBounds(),
3814 sw = this.unproject(bounds.getBottomLeft()),
3815 ne = this.unproject(bounds.getTopRight());
3816
3817 return new LatLngBounds(sw, ne);
3818 },
3819
3820 // @method getMinZoom(): Number
3821 // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
3822 getMinZoom: function () {
3823 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3824 },
3825
3826 // @method getMaxZoom(): Number
3827 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3828 getMaxZoom: function () {
3829 return this.options.maxZoom === undefined ?
3830 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3831 this.options.maxZoom;
3832 },
3833
3834 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3835 // Returns the maximum zoom level on which the given bounds fit to the map
3836 // view in its entirety. If `inside` (optional) is set to `true`, the method
3837 // instead returns the minimum zoom level on which the map view fits into
3838 // the given bounds in its entirety.
3839 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3840 bounds = toLatLngBounds(bounds);
3841 padding = toPoint(padding || [0, 0]);
3842
3843 var zoom = this.getZoom() || 0,
3844 min = this.getMinZoom(),
3845 max = this.getMaxZoom(),
3846 nw = bounds.getNorthWest(),
3847 se = bounds.getSouthEast(),
3848 size = this.getSize().subtract(padding),
3849 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3850 snap = any3d ? this.options.zoomSnap : 1,
3851 scalex = size.x / boundsSize.x,
3852 scaley = size.y / boundsSize.y,
3853 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3854
3855 zoom = this.getScaleZoom(scale, zoom);
3856
3857 if (snap) {
3858 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3859 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3860 }
3861
3862 return Math.max(min, Math.min(max, zoom));
3863 },
3864
3865 // @method getSize(): Point
3866 // Returns the current size of the map container (in pixels).
3867 getSize: function () {
3868 if (!this._size || this._sizeChanged) {
3869 this._size = new Point(
3870 this._container.clientWidth || 0,
3871 this._container.clientHeight || 0);
3872
3873 this._sizeChanged = false;
3874 }
3875 return this._size.clone();
3876 },
3877
3878 // @method getPixelBounds(): Bounds
3879 // Returns the bounds of the current map view in projected pixel
3880 // coordinates (sometimes useful in layer and overlay implementations).
3881 getPixelBounds: function (center, zoom) {
3882 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3883 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3884 },
3885
3886 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3887 // the map pane? "left point of the map layer" can be confusing, specially
3888 // since there can be negative offsets.
3889 // @method getPixelOrigin(): Point
3890 // Returns the projected pixel coordinates of the top left point of
3891 // the map layer (useful in custom layer and overlay implementations).
3892 getPixelOrigin: function () {
3893 this._checkIfLoaded();
3894 return this._pixelOrigin;
3895 },
3896
3897 // @method getPixelWorldBounds(zoom?: Number): Bounds
3898 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3899 // If `zoom` is omitted, the map's current zoom level is used.
3900 getPixelWorldBounds: function (zoom) {
3901 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3902 },
3903
3904 // @section Other Methods
3905
3906 // @method getPane(pane: String|HTMLElement): HTMLElement
3907 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3908 getPane: function (pane) {
3909 return typeof pane === 'string' ? this._panes[pane] : pane;
3910 },
3911
3912 // @method getPanes(): Object
3913 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3914 // the panes as values.
3915 getPanes: function () {
3916 return this._panes;
3917 },
3918
3919 // @method getContainer: HTMLElement
3920 // Returns the HTML element that contains the map.
3921 getContainer: function () {
3922 return this._container;
3923 },
3924
3925
3926 // @section Conversion Methods
3927
3928 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3929 // Returns the scale factor to be applied to a map transition from zoom level
3930 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3931 getZoomScale: function (toZoom, fromZoom) {
3932 // TODO replace with universal implementation after refactoring projections
3933 var crs = this.options.crs;
3934 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3935 return crs.scale(toZoom) / crs.scale(fromZoom);
3936 },
3937
3938 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3939 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3940 // level and everything is scaled by a factor of `scale`. Inverse of
3941 // [`getZoomScale`](#map-getZoomScale).
3942 getScaleZoom: function (scale, fromZoom) {
3943 var crs = this.options.crs;
3944 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3945 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3946 return isNaN(zoom) ? Infinity : zoom;
3947 },
3948
3949 // @method project(latlng: LatLng, zoom: Number): Point
3950 // Projects a geographical coordinate `LatLng` according to the projection
3951 // of the map's CRS, then scales it according to `zoom` and the CRS's
3952 // `Transformation`. The result is pixel coordinate relative to
3953 // the CRS origin.
3954 project: function (latlng, zoom) {
3955 zoom = zoom === undefined ? this._zoom : zoom;
3956 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3957 },
3958
3959 // @method unproject(point: Point, zoom: Number): LatLng
3960 // Inverse of [`project`](#map-project).
3961 unproject: function (point, zoom) {
3962 zoom = zoom === undefined ? this._zoom : zoom;
3963 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3964 },
3965
3966 // @method layerPointToLatLng(point: Point): LatLng
3967 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3968 // returns the corresponding geographical coordinate (for the current zoom level).
3969 layerPointToLatLng: function (point) {
3970 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3971 return this.unproject(projectedPoint);
3972 },
3973
3974 // @method latLngToLayerPoint(latlng: LatLng): Point
3975 // Given a geographical coordinate, returns the corresponding pixel coordinate
3976 // relative to the [origin pixel](#map-getpixelorigin).
3977 latLngToLayerPoint: function (latlng) {
3978 var projectedPoint = this.project(toLatLng(latlng))._round();
3979 return projectedPoint._subtract(this.getPixelOrigin());
3980 },
3981
3982 // @method wrapLatLng(latlng: LatLng): LatLng
3983 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3984 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3985 // CRS's bounds.
3986 // By default this means longitude is wrapped around the dateline so its
3987 // value is between -180 and +180 degrees.
3988 wrapLatLng: function (latlng) {
3989 return this.options.crs.wrapLatLng(toLatLng(latlng));
3990 },
3991
3992 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3993 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3994 // its center is within the CRS's bounds.
3995 // By default this means the center longitude is wrapped around the dateline so its
3996 // value is between -180 and +180 degrees, and the majority of the bounds
3997 // overlaps the CRS's bounds.
3998 wrapLatLngBounds: function (latlng) {
3999 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4000 },
4001
4002 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4003 // Returns the distance between two geographical coordinates according to
4004 // the map's CRS. By default this measures distance in meters.
4005 distance: function (latlng1, latlng2) {
4006 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4007 },
4008
4009 // @method containerPointToLayerPoint(point: Point): Point
4010 // Given a pixel coordinate relative to the map container, returns the corresponding
4011 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4012 containerPointToLayerPoint: function (point) { // (Point)
4013 return toPoint(point).subtract(this._getMapPanePos());
4014 },
4015
4016 // @method layerPointToContainerPoint(point: Point): Point
4017 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4018 // returns the corresponding pixel coordinate relative to the map container.
4019 layerPointToContainerPoint: function (point) { // (Point)
4020 return toPoint(point).add(this._getMapPanePos());
4021 },
4022
4023 // @method containerPointToLatLng(point: Point): LatLng
4024 // Given a pixel coordinate relative to the map container, returns
4025 // the corresponding geographical coordinate (for the current zoom level).
4026 containerPointToLatLng: function (point) {
4027 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4028 return this.layerPointToLatLng(layerPoint);
4029 },
4030
4031 // @method latLngToContainerPoint(latlng: LatLng): Point
4032 // Given a geographical coordinate, returns the corresponding pixel coordinate
4033 // relative to the map container.
4034 latLngToContainerPoint: function (latlng) {
4035 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4036 },
4037
4038 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4039 // Given a MouseEvent object, returns the pixel coordinate relative to the
4040 // map container where the event took place.
4041 mouseEventToContainerPoint: function (e) {
4042 return getMousePosition(e, this._container);
4043 },
4044
4045 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4046 // Given a MouseEvent object, returns the pixel coordinate relative to
4047 // the [origin pixel](#map-getpixelorigin) where the event took place.
4048 mouseEventToLayerPoint: function (e) {
4049 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4050 },
4051
4052 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4053 // Given a MouseEvent object, returns geographical coordinate where the
4054 // event took place.
4055 mouseEventToLatLng: function (e) { // (MouseEvent)
4056 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4057 },
4058
4059
4060 // map initialization methods
4061
4062 _initContainer: function (id) {
4063 var container = this._container = get(id);
4064
4065 if (!container) {
4066 throw new Error('Map container not found.');
4067 } else if (container._leaflet_id) {
4068 throw new Error('Map container is already initialized.');
4069 }
4070
4071 on(container, 'scroll', this._onScroll, this);
4072 this._containerId = stamp(container);
4073 },
4074
4075 _initLayout: function () {
4076 var container = this._container;
4077
4078 this._fadeAnimated = this.options.fadeAnimation && any3d;
4079
4080 addClass(container, 'leaflet-container' +
4081 (touch ? ' leaflet-touch' : '') +
4082 (retina ? ' leaflet-retina' : '') +
4083 (ielt9 ? ' leaflet-oldie' : '') +
4084 (safari ? ' leaflet-safari' : '') +
4085 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4086
4087 var position = getStyle(container, 'position');
4088
4089 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4090 container.style.position = 'relative';
4091 }
4092
4093 this._initPanes();
4094
4095 if (this._initControlPos) {
4096 this._initControlPos();
4097 }
4098 },
4099
4100 _initPanes: function () {
4101 var panes = this._panes = {};
4102 this._paneRenderers = {};
4103
4104 // @section
4105 //
4106 // Panes are DOM elements used to control the ordering of layers on the map. You
4107 // can access panes with [`map.getPane`](#map-getpane) or
4108 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4109 // [`map.createPane`](#map-createpane) method.
4110 //
4111 // Every map has the following default panes that differ only in zIndex.
4112 //
4113 // @pane mapPane: HTMLElement = 'auto'
4114 // Pane that contains all other map panes
4115
4116 this._mapPane = this.createPane('mapPane', this._container);
4117 setPosition(this._mapPane, new Point(0, 0));
4118
4119 // @pane tilePane: HTMLElement = 200
4120 // Pane for `GridLayer`s and `TileLayer`s
4121 this.createPane('tilePane');
4122 // @pane overlayPane: HTMLElement = 400
4123 // Pane for overlay shadows (e.g. `Marker` shadows)
4124 this.createPane('shadowPane');
4125 // @pane shadowPane: HTMLElement = 500
4126 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4127 this.createPane('overlayPane');
4128 // @pane markerPane: HTMLElement = 600
4129 // Pane for `Icon`s of `Marker`s
4130 this.createPane('markerPane');
4131 // @pane tooltipPane: HTMLElement = 650
4132 // Pane for `Tooltip`s.
4133 this.createPane('tooltipPane');
4134 // @pane popupPane: HTMLElement = 700
4135 // Pane for `Popup`s.
4136 this.createPane('popupPane');
4137
4138 if (!this.options.markerZoomAnimation) {
4139 addClass(panes.markerPane, 'leaflet-zoom-hide');
4140 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4141 }
4142 },
4143
4144
4145 // private methods that modify map state
4146
4147 // @section Map state change events
4148 _resetView: function (center, zoom) {
4149 setPosition(this._mapPane, new Point(0, 0));
4150
4151 var loading = !this._loaded;
4152 this._loaded = true;
4153 zoom = this._limitZoom(zoom);
4154
4155 this.fire('viewprereset');
4156
4157 var zoomChanged = this._zoom !== zoom;
4158 this
4159 ._moveStart(zoomChanged, false)
4160 ._move(center, zoom)
4161 ._moveEnd(zoomChanged);
4162
4163 // @event viewreset: Event
4164 // Fired when the map needs to redraw its content (this usually happens
4165 // on map zoom or load). Very useful for creating custom overlays.
4166 this.fire('viewreset');
4167
4168 // @event load: Event
4169 // Fired when the map is initialized (when its center and zoom are set
4170 // for the first time).
4171 if (loading) {
4172 this.fire('load');
4173 }
4174 },
4175
4176 _moveStart: function (zoomChanged, noMoveStart) {
4177 // @event zoomstart: Event
4178 // Fired when the map zoom is about to change (e.g. before zoom animation).
4179 // @event movestart: Event
4180 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4181 if (zoomChanged) {
4182 this.fire('zoomstart');
4183 }
4184 if (!noMoveStart) {
4185 this.fire('movestart');
4186 }
4187 return this;
4188 },
4189
4190 _move: function (center, zoom, data) {
4191 if (zoom === undefined) {
4192 zoom = this._zoom;
4193 }
4194 var zoomChanged = this._zoom !== zoom;
4195
4196 this._zoom = zoom;
4197 this._lastCenter = center;
4198 this._pixelOrigin = this._getNewPixelOrigin(center);
4199
4200 // @event zoom: Event
4201 // Fired repeatedly during any change in zoom level, including zoom
4202 // and fly animations.
4203 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4204 this.fire('zoom', data);
4205 }
4206
4207 // @event move: Event
4208 // Fired repeatedly during any movement of the map, including pan and
4209 // fly animations.
4210 return this.fire('move', data);
4211 },
4212
4213 _moveEnd: function (zoomChanged) {
4214 // @event zoomend: Event
4215 // Fired when the map has changed, after any animations.
4216 if (zoomChanged) {
4217 this.fire('zoomend');
4218 }
4219
4220 // @event moveend: Event
4221 // Fired when the center of the map stops changing (e.g. user stopped
4222 // dragging the map).
4223 return this.fire('moveend');
4224 },
4225
4226 _stop: function () {
4227 cancelAnimFrame(this._flyToFrame);
4228 if (this._panAnim) {
4229 this._panAnim.stop();
4230 }
4231 return this;
4232 },
4233
4234 _rawPanBy: function (offset) {
4235 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4236 },
4237
4238 _getZoomSpan: function () {
4239 return this.getMaxZoom() - this.getMinZoom();
4240 },
4241
4242 _panInsideMaxBounds: function () {
4243 if (!this._enforcingBounds) {
4244 this.panInsideBounds(this.options.maxBounds);
4245 }
4246 },
4247
4248 _checkIfLoaded: function () {
4249 if (!this._loaded) {
4250 throw new Error('Set map center and zoom first.');
4251 }
4252 },
4253
4254 // DOM event handling
4255
4256 // @section Interaction events
4257 _initEvents: function (remove$$1) {
4258 this._targets = {};
4259 this._targets[stamp(this._container)] = this;
4260
4261 var onOff = remove$$1 ? off : on;
4262
4263 // @event click: MouseEvent
4264 // Fired when the user clicks (or taps) the map.
4265 // @event dblclick: MouseEvent
4266 // Fired when the user double-clicks (or double-taps) the map.
4267 // @event mousedown: MouseEvent
4268 // Fired when the user pushes the mouse button on the map.
4269 // @event mouseup: MouseEvent
4270 // Fired when the user releases the mouse button on the map.
4271 // @event mouseover: MouseEvent
4272 // Fired when the mouse enters the map.
4273 // @event mouseout: MouseEvent
4274 // Fired when the mouse leaves the map.
4275 // @event mousemove: MouseEvent
4276 // Fired while the mouse moves over the map.
4277 // @event contextmenu: MouseEvent
4278 // Fired when the user pushes the right mouse button on the map, prevents
4279 // default browser context menu from showing if there are listeners on
4280 // this event. Also fired on mobile when the user holds a single touch
4281 // for a second (also called long press).
4282 // @event keypress: KeyboardEvent
4283 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4284 // @event keydown: KeyboardEvent
4285 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4286 // the `keydown` event is fired for keys that produce a character value and for keys
4287 // that do not produce a character value.
4288 // @event keyup: KeyboardEvent
4289 // Fired when the user releases a key from the keyboard while the map is focused.
4290 onOff(this._container, 'click dblclick mousedown mouseup ' +
4291 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4292
4293 if (this.options.trackResize) {
4294 onOff(window, 'resize', this._onResize, this);
4295 }
4296
4297 if (any3d && this.options.transform3DLimit) {
4298 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4299 }
4300 },
4301
4302 _onResize: function () {
4303 cancelAnimFrame(this._resizeRequest);
4304 this._resizeRequest = requestAnimFrame(
4305 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4306 },
4307
4308 _onScroll: function () {
4309 this._container.scrollTop = 0;
4310 this._container.scrollLeft = 0;
4311 },
4312
4313 _onMoveEnd: function () {
4314 var pos = this._getMapPanePos();
4315 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4316 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4317 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4318 this._resetView(this.getCenter(), this.getZoom());
4319 }
4320 },
4321
4322 _findEventTargets: function (e, type) {
4323 var targets = [],
4324 target,
4325 isHover = type === 'mouseout' || type === 'mouseover',
4326 src = e.target || e.srcElement,
4327 dragging = false;
4328
4329 while (src) {
4330 target = this._targets[stamp(src)];
4331 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4332 // Prevent firing click after you just dragged an object.
4333 dragging = true;
4334 break;
4335 }
4336 if (target && target.listens(type, true)) {
4337 if (isHover && !isExternalTarget(src, e)) { break; }
4338 targets.push(target);
4339 if (isHover) { break; }
4340 }
4341 if (src === this._container) { break; }
4342 src = src.parentNode;
4343 }
4344 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4345 targets = [this];
4346 }
4347 return targets;
4348 },
4349
4350 _handleDOMEvent: function (e) {
4351 if (!this._loaded || skipped(e)) { return; }
4352
4353 var type = e.type;
4354
4355 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4356 // prevents outline when clicking on keyboard-focusable element
4357 preventOutline(e.target || e.srcElement);
4358 }
4359
4360 this._fireDOMEvent(e, type);
4361 },
4362
4363 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4364
4365 _fireDOMEvent: function (e, type, targets) {
4366
4367 if (e.type === 'click') {
4368 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4369 // @event preclick: MouseEvent
4370 // Fired before mouse click on the map (sometimes useful when you
4371 // want something to happen on click before any existing click
4372 // handlers start running).
4373 var synth = extend({}, e);
4374 synth.type = 'preclick';
4375 this._fireDOMEvent(synth, synth.type, targets);
4376 }
4377
4378 if (e._stopped) { return; }
4379
4380 // Find the layer the event is propagating from and its parents.
4381 targets = (targets || []).concat(this._findEventTargets(e, type));
4382
4383 if (!targets.length) { return; }
4384
4385 var target = targets[0];
4386 if (type === 'contextmenu' && target.listens(type, true)) {
4387 preventDefault(e);
4388 }
4389
4390 var data = {
4391 originalEvent: e
4392 };
4393
4394 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4395 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4396 data.containerPoint = isMarker ?
4397 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4398 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4399 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4400 }
4401
4402 for (var i = 0; i < targets.length; i++) {
4403 targets[i].fire(type, data, true);
4404 if (data.originalEvent._stopped ||
4405 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4406 }
4407 },
4408
4409 _draggableMoved: function (obj) {
4410 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4411 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4412 },
4413
4414 _clearHandlers: function () {
4415 for (var i = 0, len = this._handlers.length; i < len; i++) {
4416 this._handlers[i].disable();
4417 }
4418 },
4419
4420 // @section Other Methods
4421
4422 // @method whenReady(fn: Function, context?: Object): this
4423 // Runs the given function `fn` when the map gets initialized with
4424 // a view (center and zoom) and at least one layer, or immediately
4425 // if it's already initialized, optionally passing a function context.
4426 whenReady: function (callback, context) {
4427 if (this._loaded) {
4428 callback.call(context || this, {target: this});
4429 } else {
4430 this.on('load', callback, context);
4431 }
4432 return this;
4433 },
4434
4435
4436 // private methods for getting map state
4437
4438 _getMapPanePos: function () {
4439 return getPosition(this._mapPane) || new Point(0, 0);
4440 },
4441
4442 _moved: function () {
4443 var pos = this._getMapPanePos();
4444 return pos && !pos.equals([0, 0]);
4445 },
4446
4447 _getTopLeftPoint: function (center, zoom) {
4448 var pixelOrigin = center && zoom !== undefined ?
4449 this._getNewPixelOrigin(center, zoom) :
4450 this.getPixelOrigin();
4451 return pixelOrigin.subtract(this._getMapPanePos());
4452 },
4453
4454 _getNewPixelOrigin: function (center, zoom) {
4455 var viewHalf = this.getSize()._divideBy(2);
4456 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4457 },
4458
4459 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4460 var topLeft = this._getNewPixelOrigin(center, zoom);
4461 return this.project(latlng, zoom)._subtract(topLeft);
4462 },
4463
4464 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4465 var topLeft = this._getNewPixelOrigin(center, zoom);
4466 return toBounds([
4467 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4468 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4469 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4470 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4471 ]);
4472 },
4473
4474 // layer point of the current center
4475 _getCenterLayerPoint: function () {
4476 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4477 },
4478
4479 // offset of the specified place to the current center in pixels
4480 _getCenterOffset: function (latlng) {
4481 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4482 },
4483
4484 // adjust center for view to get inside bounds
4485 _limitCenter: function (center, zoom, bounds) {
4486
4487 if (!bounds) { return center; }
4488
4489 var centerPoint = this.project(center, zoom),
4490 viewHalf = this.getSize().divideBy(2),
4491 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4492 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4493
4494 // If offset is less than a pixel, ignore.
4495 // This prevents unstable projections from getting into
4496 // an infinite loop of tiny offsets.
4497 if (offset.round().equals([0, 0])) {
4498 return center;
4499 }
4500
4501 return this.unproject(centerPoint.add(offset), zoom);
4502 },
4503
4504 // adjust offset for view to get inside bounds
4505 _limitOffset: function (offset, bounds) {
4506 if (!bounds) { return offset; }
4507
4508 var viewBounds = this.getPixelBounds(),
4509 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4510
4511 return offset.add(this._getBoundsOffset(newBounds, bounds));
4512 },
4513
4514 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4515 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4516 var projectedMaxBounds = toBounds(
4517 this.project(maxBounds.getNorthEast(), zoom),
4518 this.project(maxBounds.getSouthWest(), zoom)
4519 ),
4520 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4521 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4522
4523 dx = this._rebound(minOffset.x, -maxOffset.x),
4524 dy = this._rebound(minOffset.y, -maxOffset.y);
4525
4526 return new Point(dx, dy);
4527 },
4528
4529 _rebound: function (left, right) {
4530 return left + right > 0 ?
4531 Math.round(left - right) / 2 :
4532 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4533 },
4534
4535 _limitZoom: function (zoom) {
4536 var min = this.getMinZoom(),
4537 max = this.getMaxZoom(),
4538 snap = any3d ? this.options.zoomSnap : 1;
4539 if (snap) {
4540 zoom = Math.round(zoom / snap) * snap;
4541 }
4542 return Math.max(min, Math.min(max, zoom));
4543 },
4544
4545 _onPanTransitionStep: function () {
4546 this.fire('move');
4547 },
4548
4549 _onPanTransitionEnd: function () {
4550 removeClass(this._mapPane, 'leaflet-pan-anim');
4551 this.fire('moveend');
4552 },
4553
4554 _tryAnimatedPan: function (center, options) {
4555 // difference between the new and current centers in pixels
4556 var offset = this._getCenterOffset(center)._trunc();
4557
4558 // don't animate too far unless animate: true specified in options
4559 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4560
4561 this.panBy(offset, options);
4562
4563 return true;
4564 },
4565
4566 _createAnimProxy: function () {
4567
4568 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4569 this._panes.mapPane.appendChild(proxy);
4570
4571 this.on('zoomanim', function (e) {
4572 var prop = TRANSFORM,
4573 transform = this._proxy.style[prop];
4574
4575 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4576
4577 // workaround for case when transform is the same and so transitionend event is not fired
4578 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4579 this._onZoomTransitionEnd();
4580 }
4581 }, this);
4582
4583 this.on('load moveend', this._animMoveEnd, this);
4584
4585 this._on('unload', this._destroyAnimProxy, this);
4586 },
4587
4588 _destroyAnimProxy: function () {
4589 remove(this._proxy);
4590 this.off('load moveend', this._animMoveEnd, this);
4591 delete this._proxy;
4592 },
4593
4594 _animMoveEnd: function () {
4595 var c = this.getCenter(),
4596 z = this.getZoom();
4597 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4598 },
4599
4600 _catchTransitionEnd: function (e) {
4601 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4602 this._onZoomTransitionEnd();
4603 }
4604 },
4605
4606 _nothingToAnimate: function () {
4607 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4608 },
4609
4610 _tryAnimatedZoom: function (center, zoom, options) {
4611
4612 if (this._animatingZoom) { return true; }
4613
4614 options = options || {};
4615
4616 // don't animate if disabled, not supported or zoom difference is too large
4617 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4618 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4619
4620 // offset is the pixel coords of the zoom origin relative to the current center
4621 var scale = this.getZoomScale(zoom),
4622 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4623
4624 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4625 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4626
4627 requestAnimFrame(function () {
4628 this
4629 ._moveStart(true, false)
4630 ._animateZoom(center, zoom, true);
4631 }, this);
4632
4633 return true;
4634 },
4635
4636 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4637 if (!this._mapPane) { return; }
4638
4639 if (startAnim) {
4640 this._animatingZoom = true;
4641
4642 // remember what center/zoom to set after animation
4643 this._animateToCenter = center;
4644 this._animateToZoom = zoom;
4645
4646 addClass(this._mapPane, 'leaflet-zoom-anim');
4647 }
4648
4649 // @section Other Events
4650 // @event zoomanim: ZoomAnimEvent
4651 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4652 this.fire('zoomanim', {
4653 center: center,
4654 zoom: zoom,
4655 noUpdate: noUpdate
4656 });
4657
4658 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4659 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4660 },
4661
4662 _onZoomTransitionEnd: function () {
4663 if (!this._animatingZoom) { return; }
4664
4665 if (this._mapPane) {
4666 removeClass(this._mapPane, 'leaflet-zoom-anim');
4667 }
4668
4669 this._animatingZoom = false;
4670
4671 this._move(this._animateToCenter, this._animateToZoom);
4672
4673 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4674 requestAnimFrame(function () {
4675 this._moveEnd(true);
4676 }, this);
4677 }
4678 });
4679
4680 // @section
4681
4682 // @factory L.map(id: String, options?: Map options)
4683 // Instantiates a map object given the DOM ID of a `<div>` element
4684 // and optionally an object literal with `Map options`.
4685 //
4686 // @alternative
4687 // @factory L.map(el: HTMLElement, options?: Map options)
4688 // Instantiates a map object given an instance of a `<div>` HTML element
4689 // and optionally an object literal with `Map options`.
4690 function createMap(id, options) {
4691 return new Map(id, options);
4692 }
4693
4694 /*
4695 * @class Control
4696 * @aka L.Control
4697 * @inherits Class
4698 *
4699 * L.Control is a base class for implementing map controls. Handles positioning.
4700 * All other controls extend from this class.
4701 */
4702
4703 var Control = Class.extend({
4704 // @section
4705 // @aka Control options
4706 options: {
4707 // @option position: String = 'topright'
4708 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4709 // `'topright'`, `'bottomleft'` or `'bottomright'`
4710 position: 'topright'
4711 },
4712
4713 initialize: function (options) {
4714 setOptions(this, options);
4715 },
4716
4717 /* @section
4718 * Classes extending L.Control will inherit the following methods:
4719 *
4720 * @method getPosition: string
4721 * Returns the position of the control.
4722 */
4723 getPosition: function () {
4724 return this.options.position;
4725 },
4726
4727 // @method setPosition(position: string): this
4728 // Sets the position of the control.
4729 setPosition: function (position) {
4730 var map = this._map;
4731
4732 if (map) {
4733 map.removeControl(this);
4734 }
4735
4736 this.options.position = position;
4737
4738 if (map) {
4739 map.addControl(this);
4740 }
4741
4742 return this;
4743 },
4744
4745 // @method getContainer: HTMLElement
4746 // Returns the HTMLElement that contains the control.
4747 getContainer: function () {
4748 return this._container;
4749 },
4750
4751 // @method addTo(map: Map): this
4752 // Adds the control to the given map.
4753 addTo: function (map) {
4754 this.remove();
4755 this._map = map;
4756
4757 var container = this._container = this.onAdd(map),
4758 pos = this.getPosition(),
4759 corner = map._controlCorners[pos];
4760
4761 addClass(container, 'leaflet-control');
4762
4763 if (pos.indexOf('bottom') !== -1) {
4764 corner.insertBefore(container, corner.firstChild);
4765 } else {
4766 corner.appendChild(container);
4767 }
4768
4769 this._map.on('unload', this.remove, this);
4770
4771 return this;
4772 },
4773
4774 // @method remove: this
4775 // Removes the control from the map it is currently active on.
4776 remove: function () {
4777 if (!this._map) {
4778 return this;
4779 }
4780
4781 remove(this._container);
4782
4783 if (this.onRemove) {
4784 this.onRemove(this._map);
4785 }
4786
4787 this._map.off('unload', this.remove, this);
4788 this._map = null;
4789
4790 return this;
4791 },
4792
4793 _refocusOnMap: function (e) {
4794 // if map exists and event is not a keyboard event
4795 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4796 this._map.getContainer().focus();
4797 }
4798 }
4799 });
4800
4801 var control = function (options) {
4802 return new Control(options);
4803 };
4804
4805 /* @section Extension methods
4806 * @uninheritable
4807 *
4808 * Every control should extend from `L.Control` and (re-)implement the following methods.
4809 *
4810 * @method onAdd(map: Map): HTMLElement
4811 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4812 *
4813 * @method onRemove(map: Map)
4814 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4815 */
4816
4817 /* @namespace Map
4818 * @section Methods for Layers and Controls
4819 */
4820 Map.include({
4821 // @method addControl(control: Control): this
4822 // Adds the given control to the map
4823 addControl: function (control) {
4824 control.addTo(this);
4825 return this;
4826 },
4827
4828 // @method removeControl(control: Control): this
4829 // Removes the given control from the map
4830 removeControl: function (control) {
4831 control.remove();
4832 return this;
4833 },
4834
4835 _initControlPos: function () {
4836 var corners = this._controlCorners = {},
4837 l = 'leaflet-',
4838 container = this._controlContainer =
4839 create$1('div', l + 'control-container', this._container);
4840
4841 function createCorner(vSide, hSide) {
4842 var className = l + vSide + ' ' + l + hSide;
4843
4844 corners[vSide + hSide] = create$1('div', className, container);
4845 }
4846
4847 createCorner('top', 'left');
4848 createCorner('top', 'right');
4849 createCorner('bottom', 'left');
4850 createCorner('bottom', 'right');
4851 },
4852
4853 _clearControlPos: function () {
4854 for (var i in this._controlCorners) {
4855 remove(this._controlCorners[i]);
4856 }
4857 remove(this._controlContainer);
4858 delete this._controlCorners;
4859 delete this._controlContainer;
4860 }
4861 });
4862
4863 /*
4864 * @class Control.Layers
4865 * @aka L.Control.Layers
4866 * @inherits Control
4867 *
4868 * 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`.
4869 *
4870 * @example
4871 *
4872 * ```js
4873 * var baseLayers = {
4874 * "Mapbox": mapbox,
4875 * "OpenStreetMap": osm
4876 * };
4877 *
4878 * var overlays = {
4879 * "Marker": marker,
4880 * "Roads": roadsLayer
4881 * };
4882 *
4883 * L.control.layers(baseLayers, overlays).addTo(map);
4884 * ```
4885 *
4886 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4887 *
4888 * ```js
4889 * {
4890 * "<someName1>": layer1,
4891 * "<someName2>": layer2
4892 * }
4893 * ```
4894 *
4895 * The layer names can contain HTML, which allows you to add additional styling to the items:
4896 *
4897 * ```js
4898 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4899 * ```
4900 */
4901
4902 var Layers = Control.extend({
4903 // @section
4904 // @aka Control.Layers options
4905 options: {
4906 // @option collapsed: Boolean = true
4907 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4908 collapsed: true,
4909 position: 'topright',
4910
4911 // @option autoZIndex: Boolean = true
4912 // 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.
4913 autoZIndex: true,
4914
4915 // @option hideSingleBase: Boolean = false
4916 // If `true`, the base layers in the control will be hidden when there is only one.
4917 hideSingleBase: false,
4918
4919 // @option sortLayers: Boolean = false
4920 // Whether to sort the layers. When `false`, layers will keep the order
4921 // in which they were added to the control.
4922 sortLayers: false,
4923
4924 // @option sortFunction: Function = *
4925 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4926 // that will be used for sorting the layers, when `sortLayers` is `true`.
4927 // The function receives both the `L.Layer` instances and their names, as in
4928 // `sortFunction(layerA, layerB, nameA, nameB)`.
4929 // By default, it sorts layers alphabetically by their name.
4930 sortFunction: function (layerA, layerB, nameA, nameB) {
4931 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4932 }
4933 },
4934
4935 initialize: function (baseLayers, overlays, options) {
4936 setOptions(this, options);
4937
4938 this._layerControlInputs = [];
4939 this._layers = [];
4940 this._lastZIndex = 0;
4941 this._handlingClick = false;
4942
4943 for (var i in baseLayers) {
4944 this._addLayer(baseLayers[i], i);
4945 }
4946
4947 for (i in overlays) {
4948 this._addLayer(overlays[i], i, true);
4949 }
4950 },
4951
4952 onAdd: function (map) {
4953 this._initLayout();
4954 this._update();
4955
4956 this._map = map;
4957 map.on('zoomend', this._checkDisabledLayers, this);
4958
4959 for (var i = 0; i < this._layers.length; i++) {
4960 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4961 }
4962
4963 return this._container;
4964 },
4965
4966 addTo: function (map) {
4967 Control.prototype.addTo.call(this, map);
4968 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4969 return this._expandIfNotCollapsed();
4970 },
4971
4972 onRemove: function () {
4973 this._map.off('zoomend', this._checkDisabledLayers, this);
4974
4975 for (var i = 0; i < this._layers.length; i++) {
4976 this._layers[i].layer.off('add remove', this._onLayerChange, this);
4977 }
4978 },
4979
4980 // @method addBaseLayer(layer: Layer, name: String): this
4981 // Adds a base layer (radio button entry) with the given name to the control.
4982 addBaseLayer: function (layer, name) {
4983 this._addLayer(layer, name);
4984 return (this._map) ? this._update() : this;
4985 },
4986
4987 // @method addOverlay(layer: Layer, name: String): this
4988 // Adds an overlay (checkbox entry) with the given name to the control.
4989 addOverlay: function (layer, name) {
4990 this._addLayer(layer, name, true);
4991 return (this._map) ? this._update() : this;
4992 },
4993
4994 // @method removeLayer(layer: Layer): this
4995 // Remove the given layer from the control.
4996 removeLayer: function (layer) {
4997 layer.off('add remove', this._onLayerChange, this);
4998
4999 var obj = this._getLayer(stamp(layer));
5000 if (obj) {
5001 this._layers.splice(this._layers.indexOf(obj), 1);
5002 }
5003 return (this._map) ? this._update() : this;
5004 },
5005
5006 // @method expand(): this
5007 // Expand the control container if collapsed.
5008 expand: function () {
5009 addClass(this._container, 'leaflet-control-layers-expanded');
5010 this._section.style.height = null;
5011 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5012 if (acceptableHeight < this._section.clientHeight) {
5013 addClass(this._section, 'leaflet-control-layers-scrollbar');
5014 this._section.style.height = acceptableHeight + 'px';
5015 } else {
5016 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5017 }
5018 this._checkDisabledLayers();
5019 return this;
5020 },
5021
5022 // @method collapse(): this
5023 // Collapse the control container if expanded.
5024 collapse: function () {
5025 removeClass(this._container, 'leaflet-control-layers-expanded');
5026 return this;
5027 },
5028
5029 _initLayout: function () {
5030 var className = 'leaflet-control-layers',
5031 container = this._container = create$1('div', className),
5032 collapsed = this.options.collapsed;
5033
5034 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5035 container.setAttribute('aria-haspopup', true);
5036
5037 disableClickPropagation(container);
5038 disableScrollPropagation(container);
5039
5040 var section = this._section = create$1('section', className + '-list');
5041
5042 if (collapsed) {
5043 this._map.on('click', this.collapse, this);
5044
5045 if (!android) {
5046 on(container, {
5047 mouseenter: this.expand,
5048 mouseleave: this.collapse
5049 }, this);
5050 }
5051 }
5052
5053 var link = this._layersLink = create$1('a', className + '-toggle', container);
5054 link.href = '#';
5055 link.title = 'Layers';
5056
5057 if (touch) {
5058 on(link, 'click', stop);
5059 on(link, 'click', this.expand, this);
5060 } else {
5061 on(link, 'focus', this.expand, this);
5062 }
5063
5064 if (!collapsed) {
5065 this.expand();
5066 }
5067
5068 this._baseLayersList = create$1('div', className + '-base', section);
5069 this._separator = create$1('div', className + '-separator', section);
5070 this._overlaysList = create$1('div', className + '-overlays', section);
5071
5072 container.appendChild(section);
5073 },
5074
5075 _getLayer: function (id) {
5076 for (var i = 0; i < this._layers.length; i++) {
5077
5078 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5079 return this._layers[i];
5080 }
5081 }
5082 },
5083
5084 _addLayer: function (layer, name, overlay) {
5085 if (this._map) {
5086 layer.on('add remove', this._onLayerChange, this);
5087 }
5088
5089 this._layers.push({
5090 layer: layer,
5091 name: name,
5092 overlay: overlay
5093 });
5094
5095 if (this.options.sortLayers) {
5096 this._layers.sort(bind(function (a, b) {
5097 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5098 }, this));
5099 }
5100
5101 if (this.options.autoZIndex && layer.setZIndex) {
5102 this._lastZIndex++;
5103 layer.setZIndex(this._lastZIndex);
5104 }
5105
5106 this._expandIfNotCollapsed();
5107 },
5108
5109 _update: function () {
5110 if (!this._container) { return this; }
5111
5112 empty(this._baseLayersList);
5113 empty(this._overlaysList);
5114
5115 this._layerControlInputs = [];
5116 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5117
5118 for (i = 0; i < this._layers.length; i++) {
5119 obj = this._layers[i];
5120 this._addItem(obj);
5121 overlaysPresent = overlaysPresent || obj.overlay;
5122 baseLayersPresent = baseLayersPresent || !obj.overlay;
5123 baseLayersCount += !obj.overlay ? 1 : 0;
5124 }
5125
5126 // Hide base layers section if there's only one layer.
5127 if (this.options.hideSingleBase) {
5128 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5129 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5130 }
5131
5132 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5133
5134 return this;
5135 },
5136
5137 _onLayerChange: function (e) {
5138 if (!this._handlingClick) {
5139 this._update();
5140 }
5141
5142 var obj = this._getLayer(stamp(e.target));
5143
5144 // @namespace Map
5145 // @section Layer events
5146 // @event baselayerchange: LayersControlEvent
5147 // Fired when the base layer is changed through the [layers control](#control-layers).
5148 // @event overlayadd: LayersControlEvent
5149 // Fired when an overlay is selected through the [layers control](#control-layers).
5150 // @event overlayremove: LayersControlEvent
5151 // Fired when an overlay is deselected through the [layers control](#control-layers).
5152 // @namespace Control.Layers
5153 var type = obj.overlay ?
5154 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5155 (e.type === 'add' ? 'baselayerchange' : null);
5156
5157 if (type) {
5158 this._map.fire(type, obj);
5159 }
5160 },
5161
5162 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5163 _createRadioElement: function (name, checked) {
5164
5165 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5166 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5167
5168 var radioFragment = document.createElement('div');
5169 radioFragment.innerHTML = radioHtml;
5170
5171 return radioFragment.firstChild;
5172 },
5173
5174 _addItem: function (obj) {
5175 var label = document.createElement('label'),
5176 checked = this._map.hasLayer(obj.layer),
5177 input;
5178
5179 if (obj.overlay) {
5180 input = document.createElement('input');
5181 input.type = 'checkbox';
5182 input.className = 'leaflet-control-layers-selector';
5183 input.defaultChecked = checked;
5184 } else {
5185 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5186 }
5187
5188 this._layerControlInputs.push(input);
5189 input.layerId = stamp(obj.layer);
5190
5191 on(input, 'click', this._onInputClick, this);
5192
5193 var name = document.createElement('span');
5194 name.innerHTML = ' ' + obj.name;
5195
5196 // Helps from preventing layer control flicker when checkboxes are disabled
5197 // https://github.com/Leaflet/Leaflet/issues/2771
5198 var holder = document.createElement('div');
5199
5200 label.appendChild(holder);
5201 holder.appendChild(input);
5202 holder.appendChild(name);
5203
5204 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5205 container.appendChild(label);
5206
5207 this._checkDisabledLayers();
5208 return label;
5209 },
5210
5211 _onInputClick: function () {
5212 var inputs = this._layerControlInputs,
5213 input, layer;
5214 var addedLayers = [],
5215 removedLayers = [];
5216
5217 this._handlingClick = true;
5218
5219 for (var i = inputs.length - 1; i >= 0; i--) {
5220 input = inputs[i];
5221 layer = this._getLayer(input.layerId).layer;
5222
5223 if (input.checked) {
5224 addedLayers.push(layer);
5225 } else if (!input.checked) {
5226 removedLayers.push(layer);
5227 }
5228 }
5229
5230 // Bugfix issue 2318: Should remove all old layers before readding new ones
5231 for (i = 0; i < removedLayers.length; i++) {
5232 if (this._map.hasLayer(removedLayers[i])) {
5233 this._map.removeLayer(removedLayers[i]);
5234 }
5235 }
5236 for (i = 0; i < addedLayers.length; i++) {
5237 if (!this._map.hasLayer(addedLayers[i])) {
5238 this._map.addLayer(addedLayers[i]);
5239 }
5240 }
5241
5242 this._handlingClick = false;
5243
5244 this._refocusOnMap();
5245 },
5246
5247 _checkDisabledLayers: function () {
5248 var inputs = this._layerControlInputs,
5249 input,
5250 layer,
5251 zoom = this._map.getZoom();
5252
5253 for (var i = inputs.length - 1; i >= 0; i--) {
5254 input = inputs[i];
5255 layer = this._getLayer(input.layerId).layer;
5256 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5257 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5258
5259 }
5260 },
5261
5262 _expandIfNotCollapsed: function () {
5263 if (this._map && !this.options.collapsed) {
5264 this.expand();
5265 }
5266 return this;
5267 },
5268
5269 _expand: function () {
5270 // Backward compatibility, remove me in 1.1.
5271 return this.expand();
5272 },
5273
5274 _collapse: function () {
5275 // Backward compatibility, remove me in 1.1.
5276 return this.collapse();
5277 }
5278
5279 });
5280
5281
5282 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5283 // Creates a layers 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.
5284 var layers = function (baseLayers, overlays, options) {
5285 return new Layers(baseLayers, overlays, options);
5286 };
5287
5288 /*
5289 * @class Control.Zoom
5290 * @aka L.Control.Zoom
5291 * @inherits Control
5292 *
5293 * 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`.
5294 */
5295
5296 var Zoom = Control.extend({
5297 // @section
5298 // @aka Control.Zoom options
5299 options: {
5300 position: 'topleft',
5301
5302 // @option zoomInText: String = '+'
5303 // The text set on the 'zoom in' button.
5304 zoomInText: '+',
5305
5306 // @option zoomInTitle: String = 'Zoom in'
5307 // The title set on the 'zoom in' button.
5308 zoomInTitle: 'Zoom in',
5309
5310 // @option zoomOutText: String = '&#x2212;'
5311 // The text set on the 'zoom out' button.
5312 zoomOutText: '&#x2212;',
5313
5314 // @option zoomOutTitle: String = 'Zoom out'
5315 // The title set on the 'zoom out' button.
5316 zoomOutTitle: 'Zoom out'
5317 },
5318
5319 onAdd: function (map) {
5320 var zoomName = 'leaflet-control-zoom',
5321 container = create$1('div', zoomName + ' leaflet-bar'),
5322 options = this.options;
5323
5324 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5325 zoomName + '-in', container, this._zoomIn);
5326 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5327 zoomName + '-out', container, this._zoomOut);
5328
5329 this._updateDisabled();
5330 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5331
5332 return container;
5333 },
5334
5335 onRemove: function (map) {
5336 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5337 },
5338
5339 disable: function () {
5340 this._disabled = true;
5341 this._updateDisabled();
5342 return this;
5343 },
5344
5345 enable: function () {
5346 this._disabled = false;
5347 this._updateDisabled();
5348 return this;
5349 },
5350
5351 _zoomIn: function (e) {
5352 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5353 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5354 }
5355 },
5356
5357 _zoomOut: function (e) {
5358 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5359 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5360 }
5361 },
5362
5363 _createButton: function (html, title, className, container, fn) {
5364 var link = create$1('a', className, container);
5365 link.innerHTML = html;
5366 link.href = '#';
5367 link.title = title;
5368
5369 /*
5370 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5371 */
5372 link.setAttribute('role', 'button');
5373 link.setAttribute('aria-label', title);
5374
5375 disableClickPropagation(link);
5376 on(link, 'click', stop);
5377 on(link, 'click', fn, this);
5378 on(link, 'click', this._refocusOnMap, this);
5379
5380 return link;
5381 },
5382
5383 _updateDisabled: function () {
5384 var map = this._map,
5385 className = 'leaflet-disabled';
5386
5387 removeClass(this._zoomInButton, className);
5388 removeClass(this._zoomOutButton, className);
5389
5390 if (this._disabled || map._zoom === map.getMinZoom()) {
5391 addClass(this._zoomOutButton, className);
5392 }
5393 if (this._disabled || map._zoom === map.getMaxZoom()) {
5394 addClass(this._zoomInButton, className);
5395 }
5396 }
5397 });
5398
5399 // @namespace Map
5400 // @section Control options
5401 // @option zoomControl: Boolean = true
5402 // Whether a [zoom control](#control-zoom) is added to the map by default.
5403 Map.mergeOptions({
5404 zoomControl: true
5405 });
5406
5407 Map.addInitHook(function () {
5408 if (this.options.zoomControl) {
5409 // @section Controls
5410 // @property zoomControl: Control.Zoom
5411 // The default zoom control (only available if the
5412 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5413 this.zoomControl = new Zoom();
5414 this.addControl(this.zoomControl);
5415 }
5416 });
5417
5418 // @namespace Control.Zoom
5419 // @factory L.control.zoom(options: Control.Zoom options)
5420 // Creates a zoom control
5421 var zoom = function (options) {
5422 return new Zoom(options);
5423 };
5424
5425 /*
5426 * @class Control.Scale
5427 * @aka L.Control.Scale
5428 * @inherits Control
5429 *
5430 * 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`.
5431 *
5432 * @example
5433 *
5434 * ```js
5435 * L.control.scale().addTo(map);
5436 * ```
5437 */
5438
5439 var Scale = Control.extend({
5440 // @section
5441 // @aka Control.Scale options
5442 options: {
5443 position: 'bottomleft',
5444
5445 // @option maxWidth: Number = 100
5446 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5447 maxWidth: 100,
5448
5449 // @option metric: Boolean = True
5450 // Whether to show the metric scale line (m/km).
5451 metric: true,
5452
5453 // @option imperial: Boolean = True
5454 // Whether to show the imperial scale line (mi/ft).
5455 imperial: true
5456
5457 // @option updateWhenIdle: Boolean = false
5458 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5459 },
5460
5461 onAdd: function (map) {
5462 var className = 'leaflet-control-scale',
5463 container = create$1('div', className),
5464 options = this.options;
5465
5466 this._addScales(options, className + '-line', container);
5467
5468 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5469 map.whenReady(this._update, this);
5470
5471 return container;
5472 },
5473
5474 onRemove: function (map) {
5475 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5476 },
5477
5478 _addScales: function (options, className, container) {
5479 if (options.metric) {
5480 this._mScale = create$1('div', className, container);
5481 }
5482 if (options.imperial) {
5483 this._iScale = create$1('div', className, container);
5484 }
5485 },
5486
5487 _update: function () {
5488 var map = this._map,
5489 y = map.getSize().y / 2;
5490
5491 var maxMeters = map.distance(
5492 map.containerPointToLatLng([0, y]),
5493 map.containerPointToLatLng([this.options.maxWidth, y]));
5494
5495 this._updateScales(maxMeters);
5496 },
5497
5498 _updateScales: function (maxMeters) {
5499 if (this.options.metric && maxMeters) {
5500 this._updateMetric(maxMeters);
5501 }
5502 if (this.options.imperial && maxMeters) {
5503 this._updateImperial(maxMeters);
5504 }
5505 },
5506
5507 _updateMetric: function (maxMeters) {
5508 var meters = this._getRoundNum(maxMeters),
5509 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5510
5511 this._updateScale(this._mScale, label, meters / maxMeters);
5512 },
5513
5514 _updateImperial: function (maxMeters) {
5515 var maxFeet = maxMeters * 3.2808399,
5516 maxMiles, miles, feet;
5517
5518 if (maxFeet > 5280) {
5519 maxMiles = maxFeet / 5280;
5520 miles = this._getRoundNum(maxMiles);
5521 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5522
5523 } else {
5524 feet = this._getRoundNum(maxFeet);
5525 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5526 }
5527 },
5528
5529 _updateScale: function (scale, text, ratio) {
5530 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5531 scale.innerHTML = text;
5532 },
5533
5534 _getRoundNum: function (num) {
5535 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5536 d = num / pow10;
5537
5538 d = d >= 10 ? 10 :
5539 d >= 5 ? 5 :
5540 d >= 3 ? 3 :
5541 d >= 2 ? 2 : 1;
5542
5543 return pow10 * d;
5544 }
5545 });
5546
5547
5548 // @factory L.control.scale(options?: Control.Scale options)
5549 // Creates an scale control with the given options.
5550 var scale = function (options) {
5551 return new Scale(options);
5552 };
5553
5554 /*
5555 * @class Control.Attribution
5556 * @aka L.Control.Attribution
5557 * @inherits Control
5558 *
5559 * 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.
5560 */
5561
5562 var Attribution = Control.extend({
5563 // @section
5564 // @aka Control.Attribution options
5565 options: {
5566 position: 'bottomright',
5567
5568 // @option prefix: String = 'Leaflet'
5569 // The HTML text shown before the attributions. Pass `false` to disable.
5570 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5571 },
5572
5573 initialize: function (options) {
5574 setOptions(this, options);
5575
5576 this._attributions = {};
5577 },
5578
5579 onAdd: function (map) {
5580 map.attributionControl = this;
5581 this._container = create$1('div', 'leaflet-control-attribution');
5582 disableClickPropagation(this._container);
5583
5584 // TODO ugly, refactor
5585 for (var i in map._layers) {
5586 if (map._layers[i].getAttribution) {
5587 this.addAttribution(map._layers[i].getAttribution());
5588 }
5589 }
5590
5591 this._update();
5592
5593 return this._container;
5594 },
5595
5596 // @method setPrefix(prefix: String): this
5597 // Sets the text before the attributions.
5598 setPrefix: function (prefix) {
5599 this.options.prefix = prefix;
5600 this._update();
5601 return this;
5602 },
5603
5604 // @method addAttribution(text: String): this
5605 // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5606 addAttribution: function (text) {
5607 if (!text) { return this; }
5608
5609 if (!this._attributions[text]) {
5610 this._attributions[text] = 0;
5611 }
5612 this._attributions[text]++;
5613
5614 this._update();
5615
5616 return this;
5617 },
5618
5619 // @method removeAttribution(text: String): this
5620 // Removes an attribution text.
5621 removeAttribution: function (text) {
5622 if (!text) { return this; }
5623
5624 if (this._attributions[text]) {
5625 this._attributions[text]--;
5626 this._update();
5627 }
5628
5629 return this;
5630 },
5631
5632 _update: function () {
5633 if (!this._map) { return; }
5634
5635 var attribs = [];
5636
5637 for (var i in this._attributions) {
5638 if (this._attributions[i]) {
5639 attribs.push(i);
5640 }
5641 }
5642
5643 var prefixAndAttribs = [];
5644
5645 if (this.options.prefix) {
5646 prefixAndAttribs.push(this.options.prefix);
5647 }
5648 if (attribs.length) {
5649 prefixAndAttribs.push(attribs.join(', '));
5650 }
5651
5652 this._container.innerHTML = prefixAndAttribs.join(' | ');
5653 }
5654 });
5655
5656 // @namespace Map
5657 // @section Control options
5658 // @option attributionControl: Boolean = true
5659 // Whether a [attribution control](#control-attribution) is added to the map by default.
5660 Map.mergeOptions({
5661 attributionControl: true
5662 });
5663
5664 Map.addInitHook(function () {
5665 if (this.options.attributionControl) {
5666 new Attribution().addTo(this);
5667 }
5668 });
5669
5670 // @namespace Control.Attribution
5671 // @factory L.control.attribution(options: Control.Attribution options)
5672 // Creates an attribution control.
5673 var attribution = function (options) {
5674 return new Attribution(options);
5675 };
5676
5677 Control.Layers = Layers;
5678 Control.Zoom = Zoom;
5679 Control.Scale = Scale;
5680 Control.Attribution = Attribution;
5681
5682 control.layers = layers;
5683 control.zoom = zoom;
5684 control.scale = scale;
5685 control.attribution = attribution;
5686
5687 /*
5688 L.Handler is a base class for handler classes that are used internally to inject
5689 interaction features like dragging to classes like Map and Marker.
5690 */
5691
5692 // @class Handler
5693 // @aka L.Handler
5694 // Abstract class for map interaction handlers
5695
5696 var Handler = Class.extend({
5697 initialize: function (map) {
5698 this._map = map;
5699 },
5700
5701 // @method enable(): this
5702 // Enables the handler
5703 enable: function () {
5704 if (this._enabled) { return this; }
5705
5706 this._enabled = true;
5707 this.addHooks();
5708 return this;
5709 },
5710
5711 // @method disable(): this
5712 // Disables the handler
5713 disable: function () {
5714 if (!this._enabled) { return this; }
5715
5716 this._enabled = false;
5717 this.removeHooks();
5718 return this;
5719 },
5720
5721 // @method enabled(): Boolean
5722 // Returns `true` if the handler is enabled
5723 enabled: function () {
5724 return !!this._enabled;
5725 }
5726
5727 // @section Extension methods
5728 // Classes inheriting from `Handler` must implement the two following methods:
5729 // @method addHooks()
5730 // Called when the handler is enabled, should add event hooks.
5731 // @method removeHooks()
5732 // Called when the handler is disabled, should remove the event hooks added previously.
5733 });
5734
5735 // @section There is static function which can be called without instantiating L.Handler:
5736 // @function addTo(map: Map, name: String): this
5737 // Adds a new Handler to the given map with the given name.
5738 Handler.addTo = function (map, name) {
5739 map.addHandler(name, this);
5740 return this;
5741 };
5742
5743 var Mixin = {Events: Events};
5744
5745 /*
5746 * @class Draggable
5747 * @aka L.Draggable
5748 * @inherits Evented
5749 *
5750 * A class for making DOM elements draggable (including touch support).
5751 * Used internally for map and marker dragging. Only works for elements
5752 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5753 *
5754 * @example
5755 * ```js
5756 * var draggable = new L.Draggable(elementToDrag);
5757 * draggable.enable();
5758 * ```
5759 */
5760
5761 var START = touch ? 'touchstart mousedown' : 'mousedown';
5762 var END = {
5763 mousedown: 'mouseup',
5764 touchstart: 'touchend',
5765 pointerdown: 'touchend',
5766 MSPointerDown: 'touchend'
5767 };
5768 var MOVE = {
5769 mousedown: 'mousemove',
5770 touchstart: 'touchmove',
5771 pointerdown: 'touchmove',
5772 MSPointerDown: 'touchmove'
5773 };
5774
5775
5776 var Draggable = Evented.extend({
5777
5778 options: {
5779 // @section
5780 // @aka Draggable options
5781 // @option clickTolerance: Number = 3
5782 // The max number of pixels a user can shift the mouse pointer during a click
5783 // for it to be considered a valid click (as opposed to a mouse drag).
5784 clickTolerance: 3
5785 },
5786
5787 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5788 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5789 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5790 setOptions(this, options);
5791
5792 this._element = element;
5793 this._dragStartTarget = dragStartTarget || element;
5794 this._preventOutline = preventOutline$$1;
5795 },
5796
5797 // @method enable()
5798 // Enables the dragging ability
5799 enable: function () {
5800 if (this._enabled) { return; }
5801
5802 on(this._dragStartTarget, START, this._onDown, this);
5803
5804 this._enabled = true;
5805 },
5806
5807 // @method disable()
5808 // Disables the dragging ability
5809 disable: function () {
5810 if (!this._enabled) { return; }
5811
5812 // If we're currently dragging this draggable,
5813 // disabling it counts as first ending the drag.
5814 if (Draggable._dragging === this) {
5815 this.finishDrag();
5816 }
5817
5818 off(this._dragStartTarget, START, this._onDown, this);
5819
5820 this._enabled = false;
5821 this._moved = false;
5822 },
5823
5824 _onDown: function (e) {
5825 // Ignore simulated events, since we handle both touch and
5826 // mouse explicitly; otherwise we risk getting duplicates of
5827 // touch events, see #4315.
5828 // Also ignore the event if disabled; this happens in IE11
5829 // under some circumstances, see #3666.
5830 if (e._simulated || !this._enabled) { return; }
5831
5832 this._moved = false;
5833
5834 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5835
5836 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5837 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5838
5839 if (this._preventOutline) {
5840 preventOutline(this._element);
5841 }
5842
5843 disableImageDrag();
5844 disableTextSelection();
5845
5846 if (this._moving) { return; }
5847
5848 // @event down: Event
5849 // Fired when a drag is about to start.
5850 this.fire('down');
5851
5852 var first = e.touches ? e.touches[0] : e,
5853 sizedParent = getSizedParentNode(this._element);
5854
5855 this._startPoint = new Point(first.clientX, first.clientY);
5856
5857 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5858 this._parentScale = getScale(sizedParent);
5859
5860 on(document, MOVE[e.type], this._onMove, this);
5861 on(document, END[e.type], this._onUp, this);
5862 },
5863
5864 _onMove: function (e) {
5865 // Ignore simulated events, since we handle both touch and
5866 // mouse explicitly; otherwise we risk getting duplicates of
5867 // touch events, see #4315.
5868 // Also ignore the event if disabled; this happens in IE11
5869 // under some circumstances, see #3666.
5870 if (e._simulated || !this._enabled) { return; }
5871
5872 if (e.touches && e.touches.length > 1) {
5873 this._moved = true;
5874 return;
5875 }
5876
5877 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5878 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5879
5880 if (!offset.x && !offset.y) { return; }
5881 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5882
5883 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5884 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5885 // and we can use the cached value for the scale.
5886 offset.x /= this._parentScale.x;
5887 offset.y /= this._parentScale.y;
5888
5889 preventDefault(e);
5890
5891 if (!this._moved) {
5892 // @event dragstart: Event
5893 // Fired when a drag starts
5894 this.fire('dragstart');
5895
5896 this._moved = true;
5897 this._startPos = getPosition(this._element).subtract(offset);
5898
5899 addClass(document.body, 'leaflet-dragging');
5900
5901 this._lastTarget = e.target || e.srcElement;
5902 // IE and Edge do not give the <use> element, so fetch it
5903 // if necessary
5904 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
5905 this._lastTarget = this._lastTarget.correspondingUseElement;
5906 }
5907 addClass(this._lastTarget, 'leaflet-drag-target');
5908 }
5909
5910 this._newPos = this._startPos.add(offset);
5911 this._moving = true;
5912
5913 cancelAnimFrame(this._animRequest);
5914 this._lastEvent = e;
5915 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5916 },
5917
5918 _updatePosition: function () {
5919 var e = {originalEvent: this._lastEvent};
5920
5921 // @event predrag: Event
5922 // Fired continuously during dragging *before* each corresponding
5923 // update of the element's position.
5924 this.fire('predrag', e);
5925 setPosition(this._element, this._newPos);
5926
5927 // @event drag: Event
5928 // Fired continuously during dragging.
5929 this.fire('drag', e);
5930 },
5931
5932 _onUp: function (e) {
5933 // Ignore simulated events, since we handle both touch and
5934 // mouse explicitly; otherwise we risk getting duplicates of
5935 // touch events, see #4315.
5936 // Also ignore the event if disabled; this happens in IE11
5937 // under some circumstances, see #3666.
5938 if (e._simulated || !this._enabled) { return; }
5939 this.finishDrag();
5940 },
5941
5942 finishDrag: function () {
5943 removeClass(document.body, 'leaflet-dragging');
5944
5945 if (this._lastTarget) {
5946 removeClass(this._lastTarget, 'leaflet-drag-target');
5947 this._lastTarget = null;
5948 }
5949
5950 for (var i in MOVE) {
5951 off(document, MOVE[i], this._onMove, this);
5952 off(document, END[i], this._onUp, this);
5953 }
5954
5955 enableImageDrag();
5956 enableTextSelection();
5957
5958 if (this._moved && this._moving) {
5959 // ensure drag is not fired after dragend
5960 cancelAnimFrame(this._animRequest);
5961
5962 // @event dragend: DragEndEvent
5963 // Fired when the drag ends.
5964 this.fire('dragend', {
5965 distance: this._newPos.distanceTo(this._startPos)
5966 });
5967 }
5968
5969 this._moving = false;
5970 Draggable._dragging = false;
5971 }
5972
5973 });
5974
5975 /*
5976 * @namespace LineUtil
5977 *
5978 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5979 */
5980
5981 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5982 // Improves rendering performance dramatically by lessening the number of points to draw.
5983
5984 // @function simplify(points: Point[], tolerance: Number): Point[]
5985 // Dramatically reduces the number of points in a polyline while retaining
5986 // its shape and returns a new array of simplified points, using the
5987 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5988 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5989 // each zoom level and also reducing visual noise. tolerance affects the amount of
5990 // simplification (lesser value means higher quality but slower and with more points).
5991 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5992 function simplify(points, tolerance) {
5993 if (!tolerance || !points.length) {
5994 return points.slice();
5995 }
5996
5997 var sqTolerance = tolerance * tolerance;
5998
5999 // stage 1: vertex reduction
6000 points = _reducePoints(points, sqTolerance);
6001
6002 // stage 2: Douglas-Peucker simplification
6003 points = _simplifyDP(points, sqTolerance);
6004
6005 return points;
6006 }
6007
6008 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6009 // Returns the distance between point `p` and segment `p1` to `p2`.
6010 function pointToSegmentDistance(p, p1, p2) {
6011 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6012 }
6013
6014 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6015 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6016 function closestPointOnSegment(p, p1, p2) {
6017 return _sqClosestPointOnSegment(p, p1, p2);
6018 }
6019
6020 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6021 function _simplifyDP(points, sqTolerance) {
6022
6023 var len = points.length,
6024 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6025 markers = new ArrayConstructor(len);
6026
6027 markers[0] = markers[len - 1] = 1;
6028
6029 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6030
6031 var i,
6032 newPoints = [];
6033
6034 for (i = 0; i < len; i++) {
6035 if (markers[i]) {
6036 newPoints.push(points[i]);
6037 }
6038 }
6039
6040 return newPoints;
6041 }
6042
6043 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6044
6045 var maxSqDist = 0,
6046 index, i, sqDist;
6047
6048 for (i = first + 1; i <= last - 1; i++) {
6049 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6050
6051 if (sqDist > maxSqDist) {
6052 index = i;
6053 maxSqDist = sqDist;
6054 }
6055 }
6056
6057 if (maxSqDist > sqTolerance) {
6058 markers[index] = 1;
6059
6060 _simplifyDPStep(points, markers, sqTolerance, first, index);
6061 _simplifyDPStep(points, markers, sqTolerance, index, last);
6062 }
6063 }
6064
6065 // reduce points that are too close to each other to a single point
6066 function _reducePoints(points, sqTolerance) {
6067 var reducedPoints = [points[0]];
6068
6069 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6070 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6071 reducedPoints.push(points[i]);
6072 prev = i;
6073 }
6074 }
6075 if (prev < len - 1) {
6076 reducedPoints.push(points[len - 1]);
6077 }
6078 return reducedPoints;
6079 }
6080
6081 var _lastCode;
6082
6083 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6084 // Clips the segment a to b by rectangular bounds with the
6085 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6086 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6087 // points that are on the screen or near, increasing performance.
6088 function clipSegment(a, b, bounds, useLastCode, round) {
6089 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6090 codeB = _getBitCode(b, bounds),
6091
6092 codeOut, p, newCode;
6093
6094 // save 2nd code to avoid calculating it on the next segment
6095 _lastCode = codeB;
6096
6097 while (true) {
6098 // if a,b is inside the clip window (trivial accept)
6099 if (!(codeA | codeB)) {
6100 return [a, b];
6101 }
6102
6103 // if a,b is outside the clip window (trivial reject)
6104 if (codeA & codeB) {
6105 return false;
6106 }
6107
6108 // other cases
6109 codeOut = codeA || codeB;
6110 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6111 newCode = _getBitCode(p, bounds);
6112
6113 if (codeOut === codeA) {
6114 a = p;
6115 codeA = newCode;
6116 } else {
6117 b = p;
6118 codeB = newCode;
6119 }
6120 }
6121 }
6122
6123 function _getEdgeIntersection(a, b, code, bounds, round) {
6124 var dx = b.x - a.x,
6125 dy = b.y - a.y,
6126 min = bounds.min,
6127 max = bounds.max,
6128 x, y;
6129
6130 if (code & 8) { // top
6131 x = a.x + dx * (max.y - a.y) / dy;
6132 y = max.y;
6133
6134 } else if (code & 4) { // bottom
6135 x = a.x + dx * (min.y - a.y) / dy;
6136 y = min.y;
6137
6138 } else if (code & 2) { // right
6139 x = max.x;
6140 y = a.y + dy * (max.x - a.x) / dx;
6141
6142 } else if (code & 1) { // left
6143 x = min.x;
6144 y = a.y + dy * (min.x - a.x) / dx;
6145 }
6146
6147 return new Point(x, y, round);
6148 }
6149
6150 function _getBitCode(p, bounds) {
6151 var code = 0;
6152
6153 if (p.x < bounds.min.x) { // left
6154 code |= 1;
6155 } else if (p.x > bounds.max.x) { // right
6156 code |= 2;
6157 }
6158
6159 if (p.y < bounds.min.y) { // bottom
6160 code |= 4;
6161 } else if (p.y > bounds.max.y) { // top
6162 code |= 8;
6163 }
6164
6165 return code;
6166 }
6167
6168 // square distance (to avoid unnecessary Math.sqrt calls)
6169 function _sqDist(p1, p2) {
6170 var dx = p2.x - p1.x,
6171 dy = p2.y - p1.y;
6172 return dx * dx + dy * dy;
6173 }
6174
6175 // return closest point on segment or distance to that point
6176 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6177 var x = p1.x,
6178 y = p1.y,
6179 dx = p2.x - x,
6180 dy = p2.y - y,
6181 dot = dx * dx + dy * dy,
6182 t;
6183
6184 if (dot > 0) {
6185 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6186
6187 if (t > 1) {
6188 x = p2.x;
6189 y = p2.y;
6190 } else if (t > 0) {
6191 x += dx * t;
6192 y += dy * t;
6193 }
6194 }
6195
6196 dx = p.x - x;
6197 dy = p.y - y;
6198
6199 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6200 }
6201
6202
6203 // @function isFlat(latlngs: LatLng[]): Boolean
6204 // Returns true if `latlngs` is a flat array, false is nested.
6205 function isFlat(latlngs) {
6206 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6207 }
6208
6209 function _flat(latlngs) {
6210 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6211 return isFlat(latlngs);
6212 }
6213
6214 var LineUtil = ({
6215 simplify: simplify,
6216 pointToSegmentDistance: pointToSegmentDistance,
6217 closestPointOnSegment: closestPointOnSegment,
6218 clipSegment: clipSegment,
6219 _getEdgeIntersection: _getEdgeIntersection,
6220 _getBitCode: _getBitCode,
6221 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6222 isFlat: isFlat,
6223 _flat: _flat
6224 });
6225
6226 /*
6227 * @namespace PolyUtil
6228 * Various utility functions for polygon geometries.
6229 */
6230
6231 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6232 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6233 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6234 * performance. Note that polygon points needs different algorithm for clipping
6235 * than polyline, so there's a separate method for it.
6236 */
6237 function clipPolygon(points, bounds, round) {
6238 var clippedPoints,
6239 edges = [1, 4, 2, 8],
6240 i, j, k,
6241 a, b,
6242 len, edge, p;
6243
6244 for (i = 0, len = points.length; i < len; i++) {
6245 points[i]._code = _getBitCode(points[i], bounds);
6246 }
6247
6248 // for each edge (left, bottom, right, top)
6249 for (k = 0; k < 4; k++) {
6250 edge = edges[k];
6251 clippedPoints = [];
6252
6253 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6254 a = points[i];
6255 b = points[j];
6256
6257 // if a is inside the clip window
6258 if (!(a._code & edge)) {
6259 // if b is outside the clip window (a->b goes out of screen)
6260 if (b._code & edge) {
6261 p = _getEdgeIntersection(b, a, edge, bounds, round);
6262 p._code = _getBitCode(p, bounds);
6263 clippedPoints.push(p);
6264 }
6265 clippedPoints.push(a);
6266
6267 // else if b is inside the clip window (a->b enters the screen)
6268 } else if (!(b._code & edge)) {
6269 p = _getEdgeIntersection(b, a, edge, bounds, round);
6270 p._code = _getBitCode(p, bounds);
6271 clippedPoints.push(p);
6272 }
6273 }
6274 points = clippedPoints;
6275 }
6276
6277 return points;
6278 }
6279
6280 var PolyUtil = ({
6281 clipPolygon: clipPolygon
6282 });
6283
6284 /*
6285 * @namespace Projection
6286 * @section
6287 * Leaflet comes with a set of already defined Projections out of the box:
6288 *
6289 * @projection L.Projection.LonLat
6290 *
6291 * Equirectangular, or Plate Carree projection — the most simple projection,
6292 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6293 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6294 * `EPSG:4326` and `Simple` CRS.
6295 */
6296
6297 var LonLat = {
6298 project: function (latlng) {
6299 return new Point(latlng.lng, latlng.lat);
6300 },
6301
6302 unproject: function (point) {
6303 return new LatLng(point.y, point.x);
6304 },
6305
6306 bounds: new Bounds([-180, -90], [180, 90])
6307 };
6308
6309 /*
6310 * @namespace Projection
6311 * @projection L.Projection.Mercator
6312 *
6313 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6314 */
6315
6316 var Mercator = {
6317 R: 6378137,
6318 R_MINOR: 6356752.314245179,
6319
6320 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6321
6322 project: function (latlng) {
6323 var d = Math.PI / 180,
6324 r = this.R,
6325 y = latlng.lat * d,
6326 tmp = this.R_MINOR / r,
6327 e = Math.sqrt(1 - tmp * tmp),
6328 con = e * Math.sin(y);
6329
6330 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6331 y = -r * Math.log(Math.max(ts, 1E-10));
6332
6333 return new Point(latlng.lng * d * r, y);
6334 },
6335
6336 unproject: function (point) {
6337 var d = 180 / Math.PI,
6338 r = this.R,
6339 tmp = this.R_MINOR / r,
6340 e = Math.sqrt(1 - tmp * tmp),
6341 ts = Math.exp(-point.y / r),
6342 phi = Math.PI / 2 - 2 * Math.atan(ts);
6343
6344 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6345 con = e * Math.sin(phi);
6346 con = Math.pow((1 - con) / (1 + con), e / 2);
6347 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6348 phi += dphi;
6349 }
6350
6351 return new LatLng(phi * d, point.x * d / r);
6352 }
6353 };
6354
6355 /*
6356 * @class Projection
6357
6358 * An object with methods for projecting geographical coordinates of the world onto
6359 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6360
6361 * @property bounds: Bounds
6362 * The bounds (specified in CRS units) where the projection is valid
6363
6364 * @method project(latlng: LatLng): Point
6365 * Projects geographical coordinates into a 2D point.
6366 * Only accepts actual `L.LatLng` instances, not arrays.
6367
6368 * @method unproject(point: Point): LatLng
6369 * The inverse of `project`. Projects a 2D point into a geographical location.
6370 * Only accepts actual `L.Point` instances, not arrays.
6371
6372 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6373 * and can't be instantiated. Also, new classes can't inherit from them,
6374 * and methods can't be added to them with the `include` function.
6375
6376 */
6377
6378 var index = ({
6379 LonLat: LonLat,
6380 Mercator: Mercator,
6381 SphericalMercator: SphericalMercator
6382 });
6383
6384 /*
6385 * @namespace CRS
6386 * @crs L.CRS.EPSG3395
6387 *
6388 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6389 */
6390 var EPSG3395 = extend({}, Earth, {
6391 code: 'EPSG:3395',
6392 projection: Mercator,
6393
6394 transformation: (function () {
6395 var scale = 0.5 / (Math.PI * Mercator.R);
6396 return toTransformation(scale, 0.5, -scale, 0.5);
6397 }())
6398 });
6399
6400 /*
6401 * @namespace CRS
6402 * @crs L.CRS.EPSG4326
6403 *
6404 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6405 *
6406 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6407 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6408 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6409 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6410 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6411 */
6412
6413 var EPSG4326 = extend({}, Earth, {
6414 code: 'EPSG:4326',
6415 projection: LonLat,
6416 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6417 });
6418
6419 /*
6420 * @namespace CRS
6421 * @crs L.CRS.Simple
6422 *
6423 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6424 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6425 * axis should still be inverted (going from bottom to top). `distance()` returns
6426 * simple euclidean distance.
6427 */
6428
6429 var Simple = extend({}, CRS, {
6430 projection: LonLat,
6431 transformation: toTransformation(1, 0, -1, 0),
6432
6433 scale: function (zoom) {
6434 return Math.pow(2, zoom);
6435 },
6436
6437 zoom: function (scale) {
6438 return Math.log(scale) / Math.LN2;
6439 },
6440
6441 distance: function (latlng1, latlng2) {
6442 var dx = latlng2.lng - latlng1.lng,
6443 dy = latlng2.lat - latlng1.lat;
6444
6445 return Math.sqrt(dx * dx + dy * dy);
6446 },
6447
6448 infinite: true
6449 });
6450
6451 CRS.Earth = Earth;
6452 CRS.EPSG3395 = EPSG3395;
6453 CRS.EPSG3857 = EPSG3857;
6454 CRS.EPSG900913 = EPSG900913;
6455 CRS.EPSG4326 = EPSG4326;
6456 CRS.Simple = Simple;
6457
6458 /*
6459 * @class Layer
6460 * @inherits Evented
6461 * @aka L.Layer
6462 * @aka ILayer
6463 *
6464 * A set of methods from the Layer base class that all Leaflet layers use.
6465 * Inherits all methods, options and events from `L.Evented`.
6466 *
6467 * @example
6468 *
6469 * ```js
6470 * var layer = L.marker(latlng).addTo(map);
6471 * layer.addTo(map);
6472 * layer.remove();
6473 * ```
6474 *
6475 * @event add: Event
6476 * Fired after the layer is added to a map
6477 *
6478 * @event remove: Event
6479 * Fired after the layer is removed from a map
6480 */
6481
6482
6483 var Layer = Evented.extend({
6484
6485 // Classes extending `L.Layer` will inherit the following options:
6486 options: {
6487 // @option pane: String = 'overlayPane'
6488 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
6489 pane: 'overlayPane',
6490
6491 // @option attribution: String = null
6492 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6493 attribution: null,
6494
6495 bubblingMouseEvents: true
6496 },
6497
6498 /* @section
6499 * Classes extending `L.Layer` will inherit the following methods:
6500 *
6501 * @method addTo(map: Map|LayerGroup): this
6502 * Adds the layer to the given map or layer group.
6503 */
6504 addTo: function (map) {
6505 map.addLayer(this);
6506 return this;
6507 },
6508
6509 // @method remove: this
6510 // Removes the layer from the map it is currently active on.
6511 remove: function () {
6512 return this.removeFrom(this._map || this._mapToAdd);
6513 },
6514
6515 // @method removeFrom(map: Map): this
6516 // Removes the layer from the given map
6517 //
6518 // @alternative
6519 // @method removeFrom(group: LayerGroup): this
6520 // Removes the layer from the given `LayerGroup`
6521 removeFrom: function (obj) {
6522 if (obj) {
6523 obj.removeLayer(this);
6524 }
6525 return this;
6526 },
6527
6528 // @method getPane(name? : String): HTMLElement
6529 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6530 getPane: function (name) {
6531 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6532 },
6533
6534 addInteractiveTarget: function (targetEl) {
6535 this._map._targets[stamp(targetEl)] = this;
6536 return this;
6537 },
6538
6539 removeInteractiveTarget: function (targetEl) {
6540 delete this._map._targets[stamp(targetEl)];
6541 return this;
6542 },
6543
6544 // @method getAttribution: String
6545 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6546 getAttribution: function () {
6547 return this.options.attribution;
6548 },
6549
6550 _layerAdd: function (e) {
6551 var map = e.target;
6552
6553 // check in case layer gets added and then removed before the map is ready
6554 if (!map.hasLayer(this)) { return; }
6555
6556 this._map = map;
6557 this._zoomAnimated = map._zoomAnimated;
6558
6559 if (this.getEvents) {
6560 var events = this.getEvents();
6561 map.on(events, this);
6562 this.once('remove', function () {
6563 map.off(events, this);
6564 }, this);
6565 }
6566
6567 this.onAdd(map);
6568
6569 if (this.getAttribution && map.attributionControl) {
6570 map.attributionControl.addAttribution(this.getAttribution());
6571 }
6572
6573 this.fire('add');
6574 map.fire('layeradd', {layer: this});
6575 }
6576 });
6577
6578 /* @section Extension methods
6579 * @uninheritable
6580 *
6581 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6582 *
6583 * @method onAdd(map: Map): this
6584 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
6585 *
6586 * @method onRemove(map: Map): this
6587 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
6588 *
6589 * @method getEvents(): Object
6590 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
6591 *
6592 * @method getAttribution(): String
6593 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6594 *
6595 * @method beforeAdd(map: Map): this
6596 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
6597 */
6598
6599
6600 /* @namespace Map
6601 * @section Layer events
6602 *
6603 * @event layeradd: LayerEvent
6604 * Fired when a new layer is added to the map.
6605 *
6606 * @event layerremove: LayerEvent
6607 * Fired when some layer is removed from the map
6608 *
6609 * @section Methods for Layers and Controls
6610 */
6611 Map.include({
6612 // @method addLayer(layer: Layer): this
6613 // Adds the given layer to the map
6614 addLayer: function (layer) {
6615 if (!layer._layerAdd) {
6616 throw new Error('The provided object is not a Layer.');
6617 }
6618
6619 var id = stamp(layer);
6620 if (this._layers[id]) { return this; }
6621 this._layers[id] = layer;
6622
6623 layer._mapToAdd = this;
6624
6625 if (layer.beforeAdd) {
6626 layer.beforeAdd(this);
6627 }
6628
6629 this.whenReady(layer._layerAdd, layer);
6630
6631 return this;
6632 },
6633
6634 // @method removeLayer(layer: Layer): this
6635 // Removes the given layer from the map.
6636 removeLayer: function (layer) {
6637 var id = stamp(layer);
6638
6639 if (!this._layers[id]) { return this; }
6640
6641 if (this._loaded) {
6642 layer.onRemove(this);
6643 }
6644
6645 if (layer.getAttribution && this.attributionControl) {
6646 this.attributionControl.removeAttribution(layer.getAttribution());
6647 }
6648
6649 delete this._layers[id];
6650
6651 if (this._loaded) {
6652 this.fire('layerremove', {layer: layer});
6653 layer.fire('remove');
6654 }
6655
6656 layer._map = layer._mapToAdd = null;
6657
6658 return this;
6659 },
6660
6661 // @method hasLayer(layer: Layer): Boolean
6662 // Returns `true` if the given layer is currently added to the map
6663 hasLayer: function (layer) {
6664 return !!layer && (stamp(layer) in this._layers);
6665 },
6666
6667 /* @method eachLayer(fn: Function, context?: Object): this
6668 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6669 * ```
6670 * map.eachLayer(function(layer){
6671 * layer.bindPopup('Hello');
6672 * });
6673 * ```
6674 */
6675 eachLayer: function (method, context) {
6676 for (var i in this._layers) {
6677 method.call(context, this._layers[i]);
6678 }
6679 return this;
6680 },
6681
6682 _addLayers: function (layers) {
6683 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6684
6685 for (var i = 0, len = layers.length; i < len; i++) {
6686 this.addLayer(layers[i]);
6687 }
6688 },
6689
6690 _addZoomLimit: function (layer) {
6691 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6692 this._zoomBoundLayers[stamp(layer)] = layer;
6693 this._updateZoomLevels();
6694 }
6695 },
6696
6697 _removeZoomLimit: function (layer) {
6698 var id = stamp(layer);
6699
6700 if (this._zoomBoundLayers[id]) {
6701 delete this._zoomBoundLayers[id];
6702 this._updateZoomLevels();
6703 }
6704 },
6705
6706 _updateZoomLevels: function () {
6707 var minZoom = Infinity,
6708 maxZoom = -Infinity,
6709 oldZoomSpan = this._getZoomSpan();
6710
6711 for (var i in this._zoomBoundLayers) {
6712 var options = this._zoomBoundLayers[i].options;
6713
6714 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6715 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6716 }
6717
6718 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6719 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6720
6721 // @section Map state change events
6722 // @event zoomlevelschange: Event
6723 // Fired when the number of zoomlevels on the map is changed due
6724 // to adding or removing a layer.
6725 if (oldZoomSpan !== this._getZoomSpan()) {
6726 this.fire('zoomlevelschange');
6727 }
6728
6729 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6730 this.setZoom(this._layersMaxZoom);
6731 }
6732 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6733 this.setZoom(this._layersMinZoom);
6734 }
6735 }
6736 });
6737
6738 /*
6739 * @class LayerGroup
6740 * @aka L.LayerGroup
6741 * @inherits Layer
6742 *
6743 * Used to group several layers and handle them as one. If you add it to the map,
6744 * any layers added or removed from the group will be added/removed on the map as
6745 * well. Extends `Layer`.
6746 *
6747 * @example
6748 *
6749 * ```js
6750 * L.layerGroup([marker1, marker2])
6751 * .addLayer(polyline)
6752 * .addTo(map);
6753 * ```
6754 */
6755
6756 var LayerGroup = Layer.extend({
6757
6758 initialize: function (layers, options) {
6759 setOptions(this, options);
6760
6761 this._layers = {};
6762
6763 var i, len;
6764
6765 if (layers) {
6766 for (i = 0, len = layers.length; i < len; i++) {
6767 this.addLayer(layers[i]);
6768 }
6769 }
6770 },
6771
6772 // @method addLayer(layer: Layer): this
6773 // Adds the given layer to the group.
6774 addLayer: function (layer) {
6775 var id = this.getLayerId(layer);
6776
6777 this._layers[id] = layer;
6778
6779 if (this._map) {
6780 this._map.addLayer(layer);
6781 }
6782
6783 return this;
6784 },
6785
6786 // @method removeLayer(layer: Layer): this
6787 // Removes the given layer from the group.
6788 // @alternative
6789 // @method removeLayer(id: Number): this
6790 // Removes the layer with the given internal ID from the group.
6791 removeLayer: function (layer) {
6792 var id = layer in this._layers ? layer : this.getLayerId(layer);
6793
6794 if (this._map && this._layers[id]) {
6795 this._map.removeLayer(this._layers[id]);
6796 }
6797
6798 delete this._layers[id];
6799
6800 return this;
6801 },
6802
6803 // @method hasLayer(layer: Layer): Boolean
6804 // Returns `true` if the given layer is currently added to the group.
6805 // @alternative
6806 // @method hasLayer(id: Number): Boolean
6807 // Returns `true` if the given internal ID is currently added to the group.
6808 hasLayer: function (layer) {
6809 if (!layer) { return false; }
6810 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
6811 return layerId in this._layers;
6812 },
6813
6814 // @method clearLayers(): this
6815 // Removes all the layers from the group.
6816 clearLayers: function () {
6817 return this.eachLayer(this.removeLayer, this);
6818 },
6819
6820 // @method invoke(methodName: String, …): this
6821 // Calls `methodName` on every layer contained in this group, passing any
6822 // additional parameters. Has no effect if the layers contained do not
6823 // implement `methodName`.
6824 invoke: function (methodName) {
6825 var args = Array.prototype.slice.call(arguments, 1),
6826 i, layer;
6827
6828 for (i in this._layers) {
6829 layer = this._layers[i];
6830
6831 if (layer[methodName]) {
6832 layer[methodName].apply(layer, args);
6833 }
6834 }
6835
6836 return this;
6837 },
6838
6839 onAdd: function (map) {
6840 this.eachLayer(map.addLayer, map);
6841 },
6842
6843 onRemove: function (map) {
6844 this.eachLayer(map.removeLayer, map);
6845 },
6846
6847 // @method eachLayer(fn: Function, context?: Object): this
6848 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6849 // ```js
6850 // group.eachLayer(function (layer) {
6851 // layer.bindPopup('Hello');
6852 // });
6853 // ```
6854 eachLayer: function (method, context) {
6855 for (var i in this._layers) {
6856 method.call(context, this._layers[i]);
6857 }
6858 return this;
6859 },
6860
6861 // @method getLayer(id: Number): Layer
6862 // Returns the layer with the given internal ID.
6863 getLayer: function (id) {
6864 return this._layers[id];
6865 },
6866
6867 // @method getLayers(): Layer[]
6868 // Returns an array of all the layers added to the group.
6869 getLayers: function () {
6870 var layers = [];
6871 this.eachLayer(layers.push, layers);
6872 return layers;
6873 },
6874
6875 // @method setZIndex(zIndex: Number): this
6876 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6877 setZIndex: function (zIndex) {
6878 return this.invoke('setZIndex', zIndex);
6879 },
6880
6881 // @method getLayerId(layer: Layer): Number
6882 // Returns the internal ID for a layer
6883 getLayerId: function (layer) {
6884 return stamp(layer);
6885 }
6886 });
6887
6888
6889 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6890 // Create a layer group, optionally given an initial set of layers and an `options` object.
6891 var layerGroup = function (layers, options) {
6892 return new LayerGroup(layers, options);
6893 };
6894
6895 /*
6896 * @class FeatureGroup
6897 * @aka L.FeatureGroup
6898 * @inherits LayerGroup
6899 *
6900 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6901 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6902 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6903 * handler, it will handle events from any of the layers. This includes mouse events
6904 * and custom events.
6905 * * Has `layeradd` and `layerremove` events
6906 *
6907 * @example
6908 *
6909 * ```js
6910 * L.featureGroup([marker1, marker2, polyline])
6911 * .bindPopup('Hello world!')
6912 * .on('click', function() { alert('Clicked on a member of the group!'); })
6913 * .addTo(map);
6914 * ```
6915 */
6916
6917 var FeatureGroup = LayerGroup.extend({
6918
6919 addLayer: function (layer) {
6920 if (this.hasLayer(layer)) {
6921 return this;
6922 }
6923
6924 layer.addEventParent(this);
6925
6926 LayerGroup.prototype.addLayer.call(this, layer);
6927
6928 // @event layeradd: LayerEvent
6929 // Fired when a layer is added to this `FeatureGroup`
6930 return this.fire('layeradd', {layer: layer});
6931 },
6932
6933 removeLayer: function (layer) {
6934 if (!this.hasLayer(layer)) {
6935 return this;
6936 }
6937 if (layer in this._layers) {
6938 layer = this._layers[layer];
6939 }
6940
6941 layer.removeEventParent(this);
6942
6943 LayerGroup.prototype.removeLayer.call(this, layer);
6944
6945 // @event layerremove: LayerEvent
6946 // Fired when a layer is removed from this `FeatureGroup`
6947 return this.fire('layerremove', {layer: layer});
6948 },
6949
6950 // @method setStyle(style: Path options): this
6951 // Sets the given path options to each layer of the group that has a `setStyle` method.
6952 setStyle: function (style) {
6953 return this.invoke('setStyle', style);
6954 },
6955
6956 // @method bringToFront(): this
6957 // Brings the layer group to the top of all other layers
6958 bringToFront: function () {
6959 return this.invoke('bringToFront');
6960 },
6961
6962 // @method bringToBack(): this
6963 // Brings the layer group to the back of all other layers
6964 bringToBack: function () {
6965 return this.invoke('bringToBack');
6966 },
6967
6968 // @method getBounds(): LatLngBounds
6969 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6970 getBounds: function () {
6971 var bounds = new LatLngBounds();
6972
6973 for (var id in this._layers) {
6974 var layer = this._layers[id];
6975 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6976 }
6977 return bounds;
6978 }
6979 });
6980
6981 // @factory L.featureGroup(layers?: Layer[], options?: Object)
6982 // Create a feature group, optionally given an initial set of layers and an `options` object.
6983 var featureGroup = function (layers, options) {
6984 return new FeatureGroup(layers, options);
6985 };
6986
6987 /*
6988 * @class Icon
6989 * @aka L.Icon
6990 *
6991 * Represents an icon to provide when creating a marker.
6992 *
6993 * @example
6994 *
6995 * ```js
6996 * var myIcon = L.icon({
6997 * iconUrl: 'my-icon.png',
6998 * iconRetinaUrl: 'my-icon@2x.png',
6999 * iconSize: [38, 95],
7000 * iconAnchor: [22, 94],
7001 * popupAnchor: [-3, -76],
7002 * shadowUrl: 'my-icon-shadow.png',
7003 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7004 * shadowSize: [68, 95],
7005 * shadowAnchor: [22, 94]
7006 * });
7007 *
7008 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7009 * ```
7010 *
7011 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7012 *
7013 */
7014
7015 var Icon = Class.extend({
7016
7017 /* @section
7018 * @aka Icon options
7019 *
7020 * @option iconUrl: String = null
7021 * **(required)** The URL to the icon image (absolute or relative to your script path).
7022 *
7023 * @option iconRetinaUrl: String = null
7024 * The URL to a retina sized version of the icon image (absolute or relative to your
7025 * script path). Used for Retina screen devices.
7026 *
7027 * @option iconSize: Point = null
7028 * Size of the icon image in pixels.
7029 *
7030 * @option iconAnchor: Point = null
7031 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7032 * will be aligned so that this point is at the marker's geographical location. Centered
7033 * by default if size is specified, also can be set in CSS with negative margins.
7034 *
7035 * @option popupAnchor: Point = [0, 0]
7036 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7037 *
7038 * @option tooltipAnchor: Point = [0, 0]
7039 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7040 *
7041 * @option shadowUrl: String = null
7042 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7043 *
7044 * @option shadowRetinaUrl: String = null
7045 *
7046 * @option shadowSize: Point = null
7047 * Size of the shadow image in pixels.
7048 *
7049 * @option shadowAnchor: Point = null
7050 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7051 * as iconAnchor if not specified).
7052 *
7053 * @option className: String = ''
7054 * A custom class name to assign to both icon and shadow images. Empty by default.
7055 */
7056
7057 options: {
7058 popupAnchor: [0, 0],
7059 tooltipAnchor: [0, 0]
7060 },
7061
7062 initialize: function (options) {
7063 setOptions(this, options);
7064 },
7065
7066 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7067 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7068 // styled according to the options.
7069 createIcon: function (oldIcon) {
7070 return this._createIcon('icon', oldIcon);
7071 },
7072
7073 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7074 // As `createIcon`, but for the shadow beneath it.
7075 createShadow: function (oldIcon) {
7076 return this._createIcon('shadow', oldIcon);
7077 },
7078
7079 _createIcon: function (name, oldIcon) {
7080 var src = this._getIconUrl(name);
7081
7082 if (!src) {
7083 if (name === 'icon') {
7084 throw new Error('iconUrl not set in Icon options (see the docs).');
7085 }
7086 return null;
7087 }
7088
7089 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7090 this._setIconStyles(img, name);
7091
7092 return img;
7093 },
7094
7095 _setIconStyles: function (img, name) {
7096 var options = this.options;
7097 var sizeOption = options[name + 'Size'];
7098
7099 if (typeof sizeOption === 'number') {
7100 sizeOption = [sizeOption, sizeOption];
7101 }
7102
7103 var size = toPoint(sizeOption),
7104 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7105 size && size.divideBy(2, true));
7106
7107 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7108
7109 if (anchor) {
7110 img.style.marginLeft = (-anchor.x) + 'px';
7111 img.style.marginTop = (-anchor.y) + 'px';
7112 }
7113
7114 if (size) {
7115 img.style.width = size.x + 'px';
7116 img.style.height = size.y + 'px';
7117 }
7118 },
7119
7120 _createImg: function (src, el) {
7121 el = el || document.createElement('img');
7122 el.src = src;
7123 return el;
7124 },
7125
7126 _getIconUrl: function (name) {
7127 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7128 }
7129 });
7130
7131
7132 // @factory L.icon(options: Icon options)
7133 // Creates an icon instance with the given options.
7134 function icon(options) {
7135 return new Icon(options);
7136 }
7137
7138 /*
7139 * @miniclass Icon.Default (Icon)
7140 * @aka L.Icon.Default
7141 * @section
7142 *
7143 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7144 * no icon is specified. Points to the blue marker image distributed with Leaflet
7145 * releases.
7146 *
7147 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7148 * (which is a set of `Icon options`).
7149 *
7150 * If you want to _completely_ replace the default icon, override the
7151 * `L.Marker.prototype.options.icon` with your own icon instead.
7152 */
7153
7154 var IconDefault = Icon.extend({
7155
7156 options: {
7157 iconUrl: 'marker-icon.png',
7158 iconRetinaUrl: 'marker-icon-2x.png',
7159 shadowUrl: 'marker-shadow.png',
7160 iconSize: [25, 41],
7161 iconAnchor: [12, 41],
7162 popupAnchor: [1, -34],
7163 tooltipAnchor: [16, -28],
7164 shadowSize: [41, 41]
7165 },
7166
7167 _getIconUrl: function (name) {
7168 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7169 IconDefault.imagePath = this._detectIconPath();
7170 }
7171
7172 // @option imagePath: String
7173 // `Icon.Default` will try to auto-detect the location of the
7174 // blue icon images. If you are placing these images in a non-standard
7175 // way, set this option to point to the right path.
7176 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7177 },
7178
7179 _detectIconPath: function () {
7180 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7181 var path = getStyle(el, 'background-image') ||
7182 getStyle(el, 'backgroundImage'); // IE8
7183
7184 document.body.removeChild(el);
7185
7186 if (path === null || path.indexOf('url') !== 0) {
7187 path = '';
7188 } else {
7189 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7190 }
7191
7192 return path;
7193 }
7194 });
7195
7196 /*
7197 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7198 */
7199
7200
7201 /* @namespace Marker
7202 * @section Interaction handlers
7203 *
7204 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
7205 *
7206 * ```js
7207 * marker.dragging.disable();
7208 * ```
7209 *
7210 * @property dragging: Handler
7211 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7212 */
7213
7214 var MarkerDrag = Handler.extend({
7215 initialize: function (marker) {
7216 this._marker = marker;
7217 },
7218
7219 addHooks: function () {
7220 var icon = this._marker._icon;
7221
7222 if (!this._draggable) {
7223 this._draggable = new Draggable(icon, icon, true);
7224 }
7225
7226 this._draggable.on({
7227 dragstart: this._onDragStart,
7228 predrag: this._onPreDrag,
7229 drag: this._onDrag,
7230 dragend: this._onDragEnd
7231 }, this).enable();
7232
7233 addClass(icon, 'leaflet-marker-draggable');
7234 },
7235
7236 removeHooks: function () {
7237 this._draggable.off({
7238 dragstart: this._onDragStart,
7239 predrag: this._onPreDrag,
7240 drag: this._onDrag,
7241 dragend: this._onDragEnd
7242 }, this).disable();
7243
7244 if (this._marker._icon) {
7245 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7246 }
7247 },
7248
7249 moved: function () {
7250 return this._draggable && this._draggable._moved;
7251 },
7252
7253 _adjustPan: function (e) {
7254 var marker = this._marker,
7255 map = marker._map,
7256 speed = this._marker.options.autoPanSpeed,
7257 padding = this._marker.options.autoPanPadding,
7258 iconPos = getPosition(marker._icon),
7259 bounds = map.getPixelBounds(),
7260 origin = map.getPixelOrigin();
7261
7262 var panBounds = toBounds(
7263 bounds.min._subtract(origin).add(padding),
7264 bounds.max._subtract(origin).subtract(padding)
7265 );
7266
7267 if (!panBounds.contains(iconPos)) {
7268 // Compute incremental movement
7269 var movement = toPoint(
7270 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7271 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7272
7273 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7274 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7275 ).multiplyBy(speed);
7276
7277 map.panBy(movement, {animate: false});
7278
7279 this._draggable._newPos._add(movement);
7280 this._draggable._startPos._add(movement);
7281
7282 setPosition(marker._icon, this._draggable._newPos);
7283 this._onDrag(e);
7284
7285 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7286 }
7287 },
7288
7289 _onDragStart: function () {
7290 // @section Dragging events
7291 // @event dragstart: Event
7292 // Fired when the user starts dragging the marker.
7293
7294 // @event movestart: Event
7295 // Fired when the marker starts moving (because of dragging).
7296
7297 this._oldLatLng = this._marker.getLatLng();
7298
7299 // When using ES6 imports it could not be set when `Popup` was not imported as well
7300 this._marker.closePopup && this._marker.closePopup();
7301
7302 this._marker
7303 .fire('movestart')
7304 .fire('dragstart');
7305 },
7306
7307 _onPreDrag: function (e) {
7308 if (this._marker.options.autoPan) {
7309 cancelAnimFrame(this._panRequest);
7310 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7311 }
7312 },
7313
7314 _onDrag: function (e) {
7315 var marker = this._marker,
7316 shadow = marker._shadow,
7317 iconPos = getPosition(marker._icon),
7318 latlng = marker._map.layerPointToLatLng(iconPos);
7319
7320 // update shadow position
7321 if (shadow) {
7322 setPosition(shadow, iconPos);
7323 }
7324
7325 marker._latlng = latlng;
7326 e.latlng = latlng;
7327 e.oldLatLng = this._oldLatLng;
7328
7329 // @event drag: Event
7330 // Fired repeatedly while the user drags the marker.
7331 marker
7332 .fire('move', e)
7333 .fire('drag', e);
7334 },
7335
7336 _onDragEnd: function (e) {
7337 // @event dragend: DragEndEvent
7338 // Fired when the user stops dragging the marker.
7339
7340 cancelAnimFrame(this._panRequest);
7341
7342 // @event moveend: Event
7343 // Fired when the marker stops moving (because of dragging).
7344 delete this._oldLatLng;
7345 this._marker
7346 .fire('moveend')
7347 .fire('dragend', e);
7348 }
7349 });
7350
7351 /*
7352 * @class Marker
7353 * @inherits Interactive layer
7354 * @aka L.Marker
7355 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7356 *
7357 * @example
7358 *
7359 * ```js
7360 * L.marker([50.5, 30.5]).addTo(map);
7361 * ```
7362 */
7363
7364 var Marker = Layer.extend({
7365
7366 // @section
7367 // @aka Marker options
7368 options: {
7369 // @option icon: Icon = *
7370 // Icon instance to use for rendering the marker.
7371 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7372 // If not specified, a common instance of `L.Icon.Default` is used.
7373 icon: new IconDefault(),
7374
7375 // Option inherited from "Interactive layer" abstract class
7376 interactive: true,
7377
7378 // @option keyboard: Boolean = true
7379 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7380 keyboard: true,
7381
7382 // @option title: String = ''
7383 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7384 title: '',
7385
7386 // @option alt: String = ''
7387 // Text for the `alt` attribute of the icon image (useful for accessibility).
7388 alt: '',
7389
7390 // @option zIndexOffset: Number = 0
7391 // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
7392 zIndexOffset: 0,
7393
7394 // @option opacity: Number = 1.0
7395 // The opacity of the marker.
7396 opacity: 1,
7397
7398 // @option riseOnHover: Boolean = false
7399 // If `true`, the marker will get on top of others when you hover the mouse over it.
7400 riseOnHover: false,
7401
7402 // @option riseOffset: Number = 250
7403 // The z-index offset used for the `riseOnHover` feature.
7404 riseOffset: 250,
7405
7406 // @option pane: String = 'markerPane'
7407 // `Map pane` where the markers icon will be added.
7408 pane: 'markerPane',
7409
7410 // @option shadowPane: String = 'shadowPane'
7411 // `Map pane` where the markers shadow will be added.
7412 shadowPane: 'shadowPane',
7413
7414 // @option bubblingMouseEvents: Boolean = false
7415 // When `true`, a mouse event on this marker will trigger the same event on the map
7416 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7417 bubblingMouseEvents: false,
7418
7419 // @section Draggable marker options
7420 // @option draggable: Boolean = false
7421 // Whether the marker is draggable with mouse/touch or not.
7422 draggable: false,
7423
7424 // @option autoPan: Boolean = false
7425 // Whether to pan the map when dragging this marker near its edge or not.
7426 autoPan: false,
7427
7428 // @option autoPanPadding: Point = Point(50, 50)
7429 // Distance (in pixels to the left/right and to the top/bottom) of the
7430 // map edge to start panning the map.
7431 autoPanPadding: [50, 50],
7432
7433 // @option autoPanSpeed: Number = 10
7434 // Number of pixels the map should pan by.
7435 autoPanSpeed: 10
7436 },
7437
7438 /* @section
7439 *
7440 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7441 */
7442
7443 initialize: function (latlng, options) {
7444 setOptions(this, options);
7445 this._latlng = toLatLng(latlng);
7446 },
7447
7448 onAdd: function (map) {
7449 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7450
7451 if (this._zoomAnimated) {
7452 map.on('zoomanim', this._animateZoom, this);
7453 }
7454
7455 this._initIcon();
7456 this.update();
7457 },
7458
7459 onRemove: function (map) {
7460 if (this.dragging && this.dragging.enabled()) {
7461 this.options.draggable = true;
7462 this.dragging.removeHooks();
7463 }
7464 delete this.dragging;
7465
7466 if (this._zoomAnimated) {
7467 map.off('zoomanim', this._animateZoom, this);
7468 }
7469
7470 this._removeIcon();
7471 this._removeShadow();
7472 },
7473
7474 getEvents: function () {
7475 return {
7476 zoom: this.update,
7477 viewreset: this.update
7478 };
7479 },
7480
7481 // @method getLatLng: LatLng
7482 // Returns the current geographical position of the marker.
7483 getLatLng: function () {
7484 return this._latlng;
7485 },
7486
7487 // @method setLatLng(latlng: LatLng): this
7488 // Changes the marker position to the given point.
7489 setLatLng: function (latlng) {
7490 var oldLatLng = this._latlng;
7491 this._latlng = toLatLng(latlng);
7492 this.update();
7493
7494 // @event move: Event
7495 // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7496 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7497 },
7498
7499 // @method setZIndexOffset(offset: Number): this
7500 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7501 setZIndexOffset: function (offset) {
7502 this.options.zIndexOffset = offset;
7503 return this.update();
7504 },
7505
7506 // @method getIcon: Icon
7507 // Returns the current icon used by the marker
7508 getIcon: function () {
7509 return this.options.icon;
7510 },
7511
7512 // @method setIcon(icon: Icon): this
7513 // Changes the marker icon.
7514 setIcon: function (icon) {
7515
7516 this.options.icon = icon;
7517
7518 if (this._map) {
7519 this._initIcon();
7520 this.update();
7521 }
7522
7523 if (this._popup) {
7524 this.bindPopup(this._popup, this._popup.options);
7525 }
7526
7527 return this;
7528 },
7529
7530 getElement: function () {
7531 return this._icon;
7532 },
7533
7534 update: function () {
7535
7536 if (this._icon && this._map) {
7537 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7538 this._setPos(pos);
7539 }
7540
7541 return this;
7542 },
7543
7544 _initIcon: function () {
7545 var options = this.options,
7546 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7547
7548 var icon = options.icon.createIcon(this._icon),
7549 addIcon = false;
7550
7551 // if we're not reusing the icon, remove the old one and init new one
7552 if (icon !== this._icon) {
7553 if (this._icon) {
7554 this._removeIcon();
7555 }
7556 addIcon = true;
7557
7558 if (options.title) {
7559 icon.title = options.title;
7560 }
7561
7562 if (icon.tagName === 'IMG') {
7563 icon.alt = options.alt || '';
7564 }
7565 }
7566
7567 addClass(icon, classToAdd);
7568
7569 if (options.keyboard) {
7570 icon.tabIndex = '0';
7571 }
7572
7573 this._icon = icon;
7574
7575 if (options.riseOnHover) {
7576 this.on({
7577 mouseover: this._bringToFront,
7578 mouseout: this._resetZIndex
7579 });
7580 }
7581
7582 var newShadow = options.icon.createShadow(this._shadow),
7583 addShadow = false;
7584
7585 if (newShadow !== this._shadow) {
7586 this._removeShadow();
7587 addShadow = true;
7588 }
7589
7590 if (newShadow) {
7591 addClass(newShadow, classToAdd);
7592 newShadow.alt = '';
7593 }
7594 this._shadow = newShadow;
7595
7596
7597 if (options.opacity < 1) {
7598 this._updateOpacity();
7599 }
7600
7601
7602 if (addIcon) {
7603 this.getPane().appendChild(this._icon);
7604 }
7605 this._initInteraction();
7606 if (newShadow && addShadow) {
7607 this.getPane(options.shadowPane).appendChild(this._shadow);
7608 }
7609 },
7610
7611 _removeIcon: function () {
7612 if (this.options.riseOnHover) {
7613 this.off({
7614 mouseover: this._bringToFront,
7615 mouseout: this._resetZIndex
7616 });
7617 }
7618
7619 remove(this._icon);
7620 this.removeInteractiveTarget(this._icon);
7621
7622 this._icon = null;
7623 },
7624
7625 _removeShadow: function () {
7626 if (this._shadow) {
7627 remove(this._shadow);
7628 }
7629 this._shadow = null;
7630 },
7631
7632 _setPos: function (pos) {
7633
7634 if (this._icon) {
7635 setPosition(this._icon, pos);
7636 }
7637
7638 if (this._shadow) {
7639 setPosition(this._shadow, pos);
7640 }
7641
7642 this._zIndex = pos.y + this.options.zIndexOffset;
7643
7644 this._resetZIndex();
7645 },
7646
7647 _updateZIndex: function (offset) {
7648 if (this._icon) {
7649 this._icon.style.zIndex = this._zIndex + offset;
7650 }
7651 },
7652
7653 _animateZoom: function (opt) {
7654 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7655
7656 this._setPos(pos);
7657 },
7658
7659 _initInteraction: function () {
7660
7661 if (!this.options.interactive) { return; }
7662
7663 addClass(this._icon, 'leaflet-interactive');
7664
7665 this.addInteractiveTarget(this._icon);
7666
7667 if (MarkerDrag) {
7668 var draggable = this.options.draggable;
7669 if (this.dragging) {
7670 draggable = this.dragging.enabled();
7671 this.dragging.disable();
7672 }
7673
7674 this.dragging = new MarkerDrag(this);
7675
7676 if (draggable) {
7677 this.dragging.enable();
7678 }
7679 }
7680 },
7681
7682 // @method setOpacity(opacity: Number): this
7683 // Changes the opacity of the marker.
7684 setOpacity: function (opacity) {
7685 this.options.opacity = opacity;
7686 if (this._map) {
7687 this._updateOpacity();
7688 }
7689
7690 return this;
7691 },
7692
7693 _updateOpacity: function () {
7694 var opacity = this.options.opacity;
7695
7696 if (this._icon) {
7697 setOpacity(this._icon, opacity);
7698 }
7699
7700 if (this._shadow) {
7701 setOpacity(this._shadow, opacity);
7702 }
7703 },
7704
7705 _bringToFront: function () {
7706 this._updateZIndex(this.options.riseOffset);
7707 },
7708
7709 _resetZIndex: function () {
7710 this._updateZIndex(0);
7711 },
7712
7713 _getPopupAnchor: function () {
7714 return this.options.icon.options.popupAnchor;
7715 },
7716
7717 _getTooltipAnchor: function () {
7718 return this.options.icon.options.tooltipAnchor;
7719 }
7720 });
7721
7722
7723 // factory L.marker(latlng: LatLng, options? : Marker options)
7724
7725 // @factory L.marker(latlng: LatLng, options? : Marker options)
7726 // Instantiates a Marker object given a geographical point and optionally an options object.
7727 function marker(latlng, options) {
7728 return new Marker(latlng, options);
7729 }
7730
7731 /*
7732 * @class Path
7733 * @aka L.Path
7734 * @inherits Interactive layer
7735 *
7736 * An abstract class that contains options and constants shared between vector
7737 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7738 */
7739
7740 var Path = Layer.extend({
7741
7742 // @section
7743 // @aka Path options
7744 options: {
7745 // @option stroke: Boolean = true
7746 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7747 stroke: true,
7748
7749 // @option color: String = '#3388ff'
7750 // Stroke color
7751 color: '#3388ff',
7752
7753 // @option weight: Number = 3
7754 // Stroke width in pixels
7755 weight: 3,
7756
7757 // @option opacity: Number = 1.0
7758 // Stroke opacity
7759 opacity: 1,
7760
7761 // @option lineCap: String= 'round'
7762 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7763 lineCap: 'round',
7764
7765 // @option lineJoin: String = 'round'
7766 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7767 lineJoin: 'round',
7768
7769 // @option dashArray: String = null
7770 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7771 dashArray: null,
7772
7773 // @option dashOffset: String = null
7774 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7775 dashOffset: null,
7776
7777 // @option fill: Boolean = depends
7778 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7779 fill: false,
7780
7781 // @option fillColor: String = *
7782 // Fill color. Defaults to the value of the [`color`](#path-color) option
7783 fillColor: null,
7784
7785 // @option fillOpacity: Number = 0.2
7786 // Fill opacity.
7787 fillOpacity: 0.2,
7788
7789 // @option fillRule: String = 'evenodd'
7790 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7791 fillRule: 'evenodd',
7792
7793 // className: '',
7794
7795 // Option inherited from "Interactive layer" abstract class
7796 interactive: true,
7797
7798 // @option bubblingMouseEvents: Boolean = true
7799 // When `true`, a mouse event on this path will trigger the same event on the map
7800 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7801 bubblingMouseEvents: true
7802 },
7803
7804 beforeAdd: function (map) {
7805 // Renderer is set here because we need to call renderer.getEvents
7806 // before this.getEvents.
7807 this._renderer = map.getRenderer(this);
7808 },
7809
7810 onAdd: function () {
7811 this._renderer._initPath(this);
7812 this._reset();
7813 this._renderer._addPath(this);
7814 },
7815
7816 onRemove: function () {
7817 this._renderer._removePath(this);
7818 },
7819
7820 // @method redraw(): this
7821 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7822 redraw: function () {
7823 if (this._map) {
7824 this._renderer._updatePath(this);
7825 }
7826 return this;
7827 },
7828
7829 // @method setStyle(style: Path options): this
7830 // Changes the appearance of a Path based on the options in the `Path options` object.
7831 setStyle: function (style) {
7832 setOptions(this, style);
7833 if (this._renderer) {
7834 this._renderer._updateStyle(this);
7835 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
7836 this._updateBounds();
7837 }
7838 }
7839 return this;
7840 },
7841
7842 // @method bringToFront(): this
7843 // Brings the layer to the top of all path layers.
7844 bringToFront: function () {
7845 if (this._renderer) {
7846 this._renderer._bringToFront(this);
7847 }
7848 return this;
7849 },
7850
7851 // @method bringToBack(): this
7852 // Brings the layer to the bottom of all path layers.
7853 bringToBack: function () {
7854 if (this._renderer) {
7855 this._renderer._bringToBack(this);
7856 }
7857 return this;
7858 },
7859
7860 getElement: function () {
7861 return this._path;
7862 },
7863
7864 _reset: function () {
7865 // defined in child classes
7866 this._project();
7867 this._update();
7868 },
7869
7870 _clickTolerance: function () {
7871 // used when doing hit detection for Canvas layers
7872 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7873 }
7874 });
7875
7876 /*
7877 * @class CircleMarker
7878 * @aka L.CircleMarker
7879 * @inherits Path
7880 *
7881 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7882 */
7883
7884 var CircleMarker = Path.extend({
7885
7886 // @section
7887 // @aka CircleMarker options
7888 options: {
7889 fill: true,
7890
7891 // @option radius: Number = 10
7892 // Radius of the circle marker, in pixels
7893 radius: 10
7894 },
7895
7896 initialize: function (latlng, options) {
7897 setOptions(this, options);
7898 this._latlng = toLatLng(latlng);
7899 this._radius = this.options.radius;
7900 },
7901
7902 // @method setLatLng(latLng: LatLng): this
7903 // Sets the position of a circle marker to a new location.
7904 setLatLng: function (latlng) {
7905 var oldLatLng = this._latlng;
7906 this._latlng = toLatLng(latlng);
7907 this.redraw();
7908
7909 // @event move: Event
7910 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7911 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7912 },
7913
7914 // @method getLatLng(): LatLng
7915 // Returns the current geographical position of the circle marker
7916 getLatLng: function () {
7917 return this._latlng;
7918 },
7919
7920 // @method setRadius(radius: Number): this
7921 // Sets the radius of a circle marker. Units are in pixels.
7922 setRadius: function (radius) {
7923 this.options.radius = this._radius = radius;
7924 return this.redraw();
7925 },
7926
7927 // @method getRadius(): Number
7928 // Returns the current radius of the circle
7929 getRadius: function () {
7930 return this._radius;
7931 },
7932
7933 setStyle : function (options) {
7934 var radius = options && options.radius || this._radius;
7935 Path.prototype.setStyle.call(this, options);
7936 this.setRadius(radius);
7937 return this;
7938 },
7939
7940 _project: function () {
7941 this._point = this._map.latLngToLayerPoint(this._latlng);
7942 this._updateBounds();
7943 },
7944
7945 _updateBounds: function () {
7946 var r = this._radius,
7947 r2 = this._radiusY || r,
7948 w = this._clickTolerance(),
7949 p = [r + w, r2 + w];
7950 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7951 },
7952
7953 _update: function () {
7954 if (this._map) {
7955 this._updatePath();
7956 }
7957 },
7958
7959 _updatePath: function () {
7960 this._renderer._updateCircle(this);
7961 },
7962
7963 _empty: function () {
7964 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7965 },
7966
7967 // Needed by the `Canvas` renderer for interactivity
7968 _containsPoint: function (p) {
7969 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7970 }
7971 });
7972
7973
7974 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7975 // Instantiates a circle marker object given a geographical point, and an optional options object.
7976 function circleMarker(latlng, options) {
7977 return new CircleMarker(latlng, options);
7978 }
7979
7980 /*
7981 * @class Circle
7982 * @aka L.Circle
7983 * @inherits CircleMarker
7984 *
7985 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7986 *
7987 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7988 *
7989 * @example
7990 *
7991 * ```js
7992 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7993 * ```
7994 */
7995
7996 var Circle = CircleMarker.extend({
7997
7998 initialize: function (latlng, options, legacyOptions) {
7999 if (typeof options === 'number') {
8000 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8001 options = extend({}, legacyOptions, {radius: options});
8002 }
8003 setOptions(this, options);
8004 this._latlng = toLatLng(latlng);
8005
8006 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8007
8008 // @section
8009 // @aka Circle options
8010 // @option radius: Number; Radius of the circle, in meters.
8011 this._mRadius = this.options.radius;
8012 },
8013
8014 // @method setRadius(radius: Number): this
8015 // Sets the radius of a circle. Units are in meters.
8016 setRadius: function (radius) {
8017 this._mRadius = radius;
8018 return this.redraw();
8019 },
8020
8021 // @method getRadius(): Number
8022 // Returns the current radius of a circle. Units are in meters.
8023 getRadius: function () {
8024 return this._mRadius;
8025 },
8026
8027 // @method getBounds(): LatLngBounds
8028 // Returns the `LatLngBounds` of the path.
8029 getBounds: function () {
8030 var half = [this._radius, this._radiusY || this._radius];
8031
8032 return new LatLngBounds(
8033 this._map.layerPointToLatLng(this._point.subtract(half)),
8034 this._map.layerPointToLatLng(this._point.add(half)));
8035 },
8036
8037 setStyle: Path.prototype.setStyle,
8038
8039 _project: function () {
8040
8041 var lng = this._latlng.lng,
8042 lat = this._latlng.lat,
8043 map = this._map,
8044 crs = map.options.crs;
8045
8046 if (crs.distance === Earth.distance) {
8047 var d = Math.PI / 180,
8048 latR = (this._mRadius / Earth.R) / d,
8049 top = map.project([lat + latR, lng]),
8050 bottom = map.project([lat - latR, lng]),
8051 p = top.add(bottom).divideBy(2),
8052 lat2 = map.unproject(p).lat,
8053 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8054 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8055
8056 if (isNaN(lngR) || lngR === 0) {
8057 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8058 }
8059
8060 this._point = p.subtract(map.getPixelOrigin());
8061 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8062 this._radiusY = p.y - top.y;
8063
8064 } else {
8065 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8066
8067 this._point = map.latLngToLayerPoint(this._latlng);
8068 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8069 }
8070
8071 this._updateBounds();
8072 }
8073 });
8074
8075 // @factory L.circle(latlng: LatLng, options?: Circle options)
8076 // Instantiates a circle object given a geographical point, and an options object
8077 // which contains the circle radius.
8078 // @alternative
8079 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8080 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8081 // Do not use in new applications or plugins.
8082 function circle(latlng, options, legacyOptions) {
8083 return new Circle(latlng, options, legacyOptions);
8084 }
8085
8086 /*
8087 * @class Polyline
8088 * @aka L.Polyline
8089 * @inherits Path
8090 *
8091 * A class for drawing polyline overlays on a map. Extends `Path`.
8092 *
8093 * @example
8094 *
8095 * ```js
8096 * // create a red polyline from an array of LatLng points
8097 * var latlngs = [
8098 * [45.51, -122.68],
8099 * [37.77, -122.43],
8100 * [34.04, -118.2]
8101 * ];
8102 *
8103 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8104 *
8105 * // zoom the map to the polyline
8106 * map.fitBounds(polyline.getBounds());
8107 * ```
8108 *
8109 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8110 *
8111 * ```js
8112 * // create a red polyline from an array of arrays of LatLng points
8113 * var latlngs = [
8114 * [[45.51, -122.68],
8115 * [37.77, -122.43],
8116 * [34.04, -118.2]],
8117 * [[40.78, -73.91],
8118 * [41.83, -87.62],
8119 * [32.76, -96.72]]
8120 * ];
8121 * ```
8122 */
8123
8124
8125 var Polyline = Path.extend({
8126
8127 // @section
8128 // @aka Polyline options
8129 options: {
8130 // @option smoothFactor: Number = 1.0
8131 // How much to simplify the polyline on each zoom level. More means
8132 // better performance and smoother look, and less means more accurate representation.
8133 smoothFactor: 1.0,
8134
8135 // @option noClip: Boolean = false
8136 // Disable polyline clipping.
8137 noClip: false
8138 },
8139
8140 initialize: function (latlngs, options) {
8141 setOptions(this, options);
8142 this._setLatLngs(latlngs);
8143 },
8144
8145 // @method getLatLngs(): LatLng[]
8146 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8147 getLatLngs: function () {
8148 return this._latlngs;
8149 },
8150
8151 // @method setLatLngs(latlngs: LatLng[]): this
8152 // Replaces all the points in the polyline with the given array of geographical points.
8153 setLatLngs: function (latlngs) {
8154 this._setLatLngs(latlngs);
8155 return this.redraw();
8156 },
8157
8158 // @method isEmpty(): Boolean
8159 // Returns `true` if the Polyline has no LatLngs.
8160 isEmpty: function () {
8161 return !this._latlngs.length;
8162 },
8163
8164 // @method closestLayerPoint(p: Point): Point
8165 // Returns the point closest to `p` on the Polyline.
8166 closestLayerPoint: function (p) {
8167 var minDistance = Infinity,
8168 minPoint = null,
8169 closest = _sqClosestPointOnSegment,
8170 p1, p2;
8171
8172 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8173 var points = this._parts[j];
8174
8175 for (var i = 1, len = points.length; i < len; i++) {
8176 p1 = points[i - 1];
8177 p2 = points[i];
8178
8179 var sqDist = closest(p, p1, p2, true);
8180
8181 if (sqDist < minDistance) {
8182 minDistance = sqDist;
8183 minPoint = closest(p, p1, p2);
8184 }
8185 }
8186 }
8187 if (minPoint) {
8188 minPoint.distance = Math.sqrt(minDistance);
8189 }
8190 return minPoint;
8191 },
8192
8193 // @method getCenter(): LatLng
8194 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8195 getCenter: function () {
8196 // throws error when not yet added to map as this center calculation requires projected coordinates
8197 if (!this._map) {
8198 throw new Error('Must add layer to map before using getCenter()');
8199 }
8200
8201 var i, halfDist, segDist, dist, p1, p2, ratio,
8202 points = this._rings[0],
8203 len = points.length;
8204
8205 if (!len) { return null; }
8206
8207 // polyline centroid algorithm; only uses the first ring if there are multiple
8208
8209 for (i = 0, halfDist = 0; i < len - 1; i++) {
8210 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8211 }
8212
8213 // The line is so small in the current view that all points are on the same pixel.
8214 if (halfDist === 0) {
8215 return this._map.layerPointToLatLng(points[0]);
8216 }
8217
8218 for (i = 0, dist = 0; i < len - 1; i++) {
8219 p1 = points[i];
8220 p2 = points[i + 1];
8221 segDist = p1.distanceTo(p2);
8222 dist += segDist;
8223
8224 if (dist > halfDist) {
8225 ratio = (dist - halfDist) / segDist;
8226 return this._map.layerPointToLatLng([
8227 p2.x - ratio * (p2.x - p1.x),
8228 p2.y - ratio * (p2.y - p1.y)
8229 ]);
8230 }
8231 }
8232 },
8233
8234 // @method getBounds(): LatLngBounds
8235 // Returns the `LatLngBounds` of the path.
8236 getBounds: function () {
8237 return this._bounds;
8238 },
8239
8240 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8241 // Adds a given point to the polyline. By default, adds to the first ring of
8242 // the polyline in case of a multi-polyline, but can be overridden by passing
8243 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8244 addLatLng: function (latlng, latlngs) {
8245 latlngs = latlngs || this._defaultShape();
8246 latlng = toLatLng(latlng);
8247 latlngs.push(latlng);
8248 this._bounds.extend(latlng);
8249 return this.redraw();
8250 },
8251
8252 _setLatLngs: function (latlngs) {
8253 this._bounds = new LatLngBounds();
8254 this._latlngs = this._convertLatLngs(latlngs);
8255 },
8256
8257 _defaultShape: function () {
8258 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8259 },
8260
8261 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8262 _convertLatLngs: function (latlngs) {
8263 var result = [],
8264 flat = isFlat(latlngs);
8265
8266 for (var i = 0, len = latlngs.length; i < len; i++) {
8267 if (flat) {
8268 result[i] = toLatLng(latlngs[i]);
8269 this._bounds.extend(result[i]);
8270 } else {
8271 result[i] = this._convertLatLngs(latlngs[i]);
8272 }
8273 }
8274
8275 return result;
8276 },
8277
8278 _project: function () {
8279 var pxBounds = new Bounds();
8280 this._rings = [];
8281 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8282
8283 if (this._bounds.isValid() && pxBounds.isValid()) {
8284 this._rawPxBounds = pxBounds;
8285 this._updateBounds();
8286 }
8287 },
8288
8289 _updateBounds: function () {
8290 var w = this._clickTolerance(),
8291 p = new Point(w, w);
8292 this._pxBounds = new Bounds([
8293 this._rawPxBounds.min.subtract(p),
8294 this._rawPxBounds.max.add(p)
8295 ]);
8296 },
8297
8298 // recursively turns latlngs into a set of rings with projected coordinates
8299 _projectLatlngs: function (latlngs, result, projectedBounds) {
8300 var flat = latlngs[0] instanceof LatLng,
8301 len = latlngs.length,
8302 i, ring;
8303
8304 if (flat) {
8305 ring = [];
8306 for (i = 0; i < len; i++) {
8307 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8308 projectedBounds.extend(ring[i]);
8309 }
8310 result.push(ring);
8311 } else {
8312 for (i = 0; i < len; i++) {
8313 this._projectLatlngs(latlngs[i], result, projectedBounds);
8314 }
8315 }
8316 },
8317
8318 // clip polyline by renderer bounds so that we have less to render for performance
8319 _clipPoints: function () {
8320 var bounds = this._renderer._bounds;
8321
8322 this._parts = [];
8323 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8324 return;
8325 }
8326
8327 if (this.options.noClip) {
8328 this._parts = this._rings;
8329 return;
8330 }
8331
8332 var parts = this._parts,
8333 i, j, k, len, len2, segment, points;
8334
8335 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8336 points = this._rings[i];
8337
8338 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8339 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8340
8341 if (!segment) { continue; }
8342
8343 parts[k] = parts[k] || [];
8344 parts[k].push(segment[0]);
8345
8346 // if segment goes out of screen, or it's the last one, it's the end of the line part
8347 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8348 parts[k].push(segment[1]);
8349 k++;
8350 }
8351 }
8352 }
8353 },
8354
8355 // simplify each clipped part of the polyline for performance
8356 _simplifyPoints: function () {
8357 var parts = this._parts,
8358 tolerance = this.options.smoothFactor;
8359
8360 for (var i = 0, len = parts.length; i < len; i++) {
8361 parts[i] = simplify(parts[i], tolerance);
8362 }
8363 },
8364
8365 _update: function () {
8366 if (!this._map) { return; }
8367
8368 this._clipPoints();
8369 this._simplifyPoints();
8370 this._updatePath();
8371 },
8372
8373 _updatePath: function () {
8374 this._renderer._updatePoly(this);
8375 },
8376
8377 // Needed by the `Canvas` renderer for interactivity
8378 _containsPoint: function (p, closed) {
8379 var i, j, k, len, len2, part,
8380 w = this._clickTolerance();
8381
8382 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8383
8384 // hit detection for polylines
8385 for (i = 0, len = this._parts.length; i < len; i++) {
8386 part = this._parts[i];
8387
8388 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8389 if (!closed && (j === 0)) { continue; }
8390
8391 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8392 return true;
8393 }
8394 }
8395 }
8396 return false;
8397 }
8398 });
8399
8400 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8401 // Instantiates a polyline object given an array of geographical points and
8402 // optionally an options object. You can create a `Polyline` object with
8403 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8404 // of geographic points.
8405 function polyline(latlngs, options) {
8406 return new Polyline(latlngs, options);
8407 }
8408
8409 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8410 Polyline._flat = _flat;
8411
8412 /*
8413 * @class Polygon
8414 * @aka L.Polygon
8415 * @inherits Polyline
8416 *
8417 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8418 *
8419 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
8420 *
8421 *
8422 * @example
8423 *
8424 * ```js
8425 * // create a red polygon from an array of LatLng points
8426 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8427 *
8428 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8429 *
8430 * // zoom the map to the polygon
8431 * map.fitBounds(polygon.getBounds());
8432 * ```
8433 *
8434 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
8435 *
8436 * ```js
8437 * var latlngs = [
8438 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8439 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8440 * ];
8441 * ```
8442 *
8443 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8444 *
8445 * ```js
8446 * var latlngs = [
8447 * [ // first polygon
8448 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8449 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8450 * ],
8451 * [ // second polygon
8452 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8453 * ]
8454 * ];
8455 * ```
8456 */
8457
8458 var Polygon = Polyline.extend({
8459
8460 options: {
8461 fill: true
8462 },
8463
8464 isEmpty: function () {
8465 return !this._latlngs.length || !this._latlngs[0].length;
8466 },
8467
8468 getCenter: function () {
8469 // throws error when not yet added to map as this center calculation requires projected coordinates
8470 if (!this._map) {
8471 throw new Error('Must add layer to map before using getCenter()');
8472 }
8473
8474 var i, j, p1, p2, f, area, x, y, center,
8475 points = this._rings[0],
8476 len = points.length;
8477
8478 if (!len) { return null; }
8479
8480 // polygon centroid algorithm; only uses the first ring if there are multiple
8481
8482 area = x = y = 0;
8483
8484 for (i = 0, j = len - 1; i < len; j = i++) {
8485 p1 = points[i];
8486 p2 = points[j];
8487
8488 f = p1.y * p2.x - p2.y * p1.x;
8489 x += (p1.x + p2.x) * f;
8490 y += (p1.y + p2.y) * f;
8491 area += f * 3;
8492 }
8493
8494 if (area === 0) {
8495 // Polygon is so small that all points are on same pixel.
8496 center = points[0];
8497 } else {
8498 center = [x / area, y / area];
8499 }
8500 return this._map.layerPointToLatLng(center);
8501 },
8502
8503 _convertLatLngs: function (latlngs) {
8504 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8505 len = result.length;
8506
8507 // remove last point if it equals first one
8508 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8509 result.pop();
8510 }
8511 return result;
8512 },
8513
8514 _setLatLngs: function (latlngs) {
8515 Polyline.prototype._setLatLngs.call(this, latlngs);
8516 if (isFlat(this._latlngs)) {
8517 this._latlngs = [this._latlngs];
8518 }
8519 },
8520
8521 _defaultShape: function () {
8522 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8523 },
8524
8525 _clipPoints: function () {
8526 // polygons need a different clipping algorithm so we redefine that
8527
8528 var bounds = this._renderer._bounds,
8529 w = this.options.weight,
8530 p = new Point(w, w);
8531
8532 // increase clip padding by stroke width to avoid stroke on clip edges
8533 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8534
8535 this._parts = [];
8536 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8537 return;
8538 }
8539
8540 if (this.options.noClip) {
8541 this._parts = this._rings;
8542 return;
8543 }
8544
8545 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8546 clipped = clipPolygon(this._rings[i], bounds, true);
8547 if (clipped.length) {
8548 this._parts.push(clipped);
8549 }
8550 }
8551 },
8552
8553 _updatePath: function () {
8554 this._renderer._updatePoly(this, true);
8555 },
8556
8557 // Needed by the `Canvas` renderer for interactivity
8558 _containsPoint: function (p) {
8559 var inside = false,
8560 part, p1, p2, i, j, k, len, len2;
8561
8562 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8563
8564 // ray casting algorithm for detecting if point is in polygon
8565 for (i = 0, len = this._parts.length; i < len; i++) {
8566 part = this._parts[i];
8567
8568 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8569 p1 = part[j];
8570 p2 = part[k];
8571
8572 if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
8573 inside = !inside;
8574 }
8575 }
8576 }
8577
8578 // also check if it's on polygon stroke
8579 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8580 }
8581
8582 });
8583
8584
8585 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8586 function polygon(latlngs, options) {
8587 return new Polygon(latlngs, options);
8588 }
8589
8590 /*
8591 * @class GeoJSON
8592 * @aka L.GeoJSON
8593 * @inherits FeatureGroup
8594 *
8595 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8596 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8597 *
8598 * @example
8599 *
8600 * ```js
8601 * L.geoJSON(data, {
8602 * style: function (feature) {
8603 * return {color: feature.properties.color};
8604 * }
8605 * }).bindPopup(function (layer) {
8606 * return layer.feature.properties.description;
8607 * }).addTo(map);
8608 * ```
8609 */
8610
8611 var GeoJSON = FeatureGroup.extend({
8612
8613 /* @section
8614 * @aka GeoJSON options
8615 *
8616 * @option pointToLayer: Function = *
8617 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8618 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8619 * The default is to spawn a default `Marker`:
8620 * ```js
8621 * function(geoJsonPoint, latlng) {
8622 * return L.marker(latlng);
8623 * }
8624 * ```
8625 *
8626 * @option style: Function = *
8627 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8628 * called internally when data is added.
8629 * The default value is to not override any defaults:
8630 * ```js
8631 * function (geoJsonFeature) {
8632 * return {}
8633 * }
8634 * ```
8635 *
8636 * @option onEachFeature: Function = *
8637 * A `Function` that will be called once for each created `Feature`, after it has
8638 * been created and styled. Useful for attaching events and popups to features.
8639 * The default is to do nothing with the newly created layers:
8640 * ```js
8641 * function (feature, layer) {}
8642 * ```
8643 *
8644 * @option filter: Function = *
8645 * A `Function` that will be used to decide whether to include a feature or not.
8646 * The default is to include all features:
8647 * ```js
8648 * function (geoJsonFeature) {
8649 * return true;
8650 * }
8651 * ```
8652 * Note: dynamically changing the `filter` option will have effect only on newly
8653 * added data. It will _not_ re-evaluate already included features.
8654 *
8655 * @option coordsToLatLng: Function = *
8656 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8657 * The default is the `coordsToLatLng` static method.
8658 *
8659 * @option markersInheritOptions: Boolean = false
8660 * Whether default Markers for "Point" type Features inherit from group options.
8661 */
8662
8663 initialize: function (geojson, options) {
8664 setOptions(this, options);
8665
8666 this._layers = {};
8667
8668 if (geojson) {
8669 this.addData(geojson);
8670 }
8671 },
8672
8673 // @method addData( <GeoJSON> data ): this
8674 // Adds a GeoJSON object to the layer.
8675 addData: function (geojson) {
8676 var features = isArray(geojson) ? geojson : geojson.features,
8677 i, len, feature;
8678
8679 if (features) {
8680 for (i = 0, len = features.length; i < len; i++) {
8681 // only add this if geometry or geometries are set and not null
8682 feature = features[i];
8683 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8684 this.addData(feature);
8685 }
8686 }
8687 return this;
8688 }
8689
8690 var options = this.options;
8691
8692 if (options.filter && !options.filter(geojson)) { return this; }
8693
8694 var layer = geometryToLayer(geojson, options);
8695 if (!layer) {
8696 return this;
8697 }
8698 layer.feature = asFeature(geojson);
8699
8700 layer.defaultOptions = layer.options;
8701 this.resetStyle(layer);
8702
8703 if (options.onEachFeature) {
8704 options.onEachFeature(geojson, layer);
8705 }
8706
8707 return this.addLayer(layer);
8708 },
8709
8710 // @method resetStyle( <Path> layer? ): this
8711 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8712 // If `layer` is omitted, the style of all features in the current layer is reset.
8713 resetStyle: function (layer) {
8714 if (layer === undefined) {
8715 return this.eachLayer(this.resetStyle, this);
8716 }
8717 // reset any custom styles
8718 layer.options = extend({}, layer.defaultOptions);
8719 this._setLayerStyle(layer, this.options.style);
8720 return this;
8721 },
8722
8723 // @method setStyle( <Function> style ): this
8724 // Changes styles of GeoJSON vector layers with the given style function.
8725 setStyle: function (style) {
8726 return this.eachLayer(function (layer) {
8727 this._setLayerStyle(layer, style);
8728 }, this);
8729 },
8730
8731 _setLayerStyle: function (layer, style) {
8732 if (layer.setStyle) {
8733 if (typeof style === 'function') {
8734 style = style(layer.feature);
8735 }
8736 layer.setStyle(style);
8737 }
8738 }
8739 });
8740
8741 // @section
8742 // There are several static functions which can be called without instantiating L.GeoJSON:
8743
8744 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8745 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8746 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8747 // functions if provided as options.
8748 function geometryToLayer(geojson, options) {
8749
8750 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8751 coords = geometry ? geometry.coordinates : null,
8752 layers = [],
8753 pointToLayer = options && options.pointToLayer,
8754 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8755 latlng, latlngs, i, len;
8756
8757 if (!coords && !geometry) {
8758 return null;
8759 }
8760
8761 switch (geometry.type) {
8762 case 'Point':
8763 latlng = _coordsToLatLng(coords);
8764 return _pointToLayer(pointToLayer, geojson, latlng, options);
8765
8766 case 'MultiPoint':
8767 for (i = 0, len = coords.length; i < len; i++) {
8768 latlng = _coordsToLatLng(coords[i]);
8769 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
8770 }
8771 return new FeatureGroup(layers);
8772
8773 case 'LineString':
8774 case 'MultiLineString':
8775 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8776 return new Polyline(latlngs, options);
8777
8778 case 'Polygon':
8779 case 'MultiPolygon':
8780 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8781 return new Polygon(latlngs, options);
8782
8783 case 'GeometryCollection':
8784 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8785 var layer = geometryToLayer({
8786 geometry: geometry.geometries[i],
8787 type: 'Feature',
8788 properties: geojson.properties
8789 }, options);
8790
8791 if (layer) {
8792 layers.push(layer);
8793 }
8794 }
8795 return new FeatureGroup(layers);
8796
8797 default:
8798 throw new Error('Invalid GeoJSON object.');
8799 }
8800 }
8801
8802 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
8803 return pointToLayerFn ?
8804 pointToLayerFn(geojson, latlng) :
8805 new Marker(latlng, options && options.markersInheritOptions && options);
8806 }
8807
8808 // @function coordsToLatLng(coords: Array): LatLng
8809 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8810 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8811 function coordsToLatLng(coords) {
8812 return new LatLng(coords[1], coords[0], coords[2]);
8813 }
8814
8815 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8816 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8817 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8818 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8819 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8820 var latlngs = [];
8821
8822 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8823 latlng = levelsDeep ?
8824 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8825 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8826
8827 latlngs.push(latlng);
8828 }
8829
8830 return latlngs;
8831 }
8832
8833 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8834 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8835 function latLngToCoords(latlng, precision) {
8836 precision = typeof precision === 'number' ? precision : 6;
8837 return latlng.alt !== undefined ?
8838 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8839 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8840 }
8841
8842 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8843 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8844 // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
8845 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8846 var coords = [];
8847
8848 for (var i = 0, len = latlngs.length; i < len; i++) {
8849 coords.push(levelsDeep ?
8850 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8851 latLngToCoords(latlngs[i], precision));
8852 }
8853
8854 if (!levelsDeep && closed) {
8855 coords.push(coords[0]);
8856 }
8857
8858 return coords;
8859 }
8860
8861 function getFeature(layer, newGeometry) {
8862 return layer.feature ?
8863 extend({}, layer.feature, {geometry: newGeometry}) :
8864 asFeature(newGeometry);
8865 }
8866
8867 // @function asFeature(geojson: Object): Object
8868 // Normalize GeoJSON geometries/features into GeoJSON features.
8869 function asFeature(geojson) {
8870 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8871 return geojson;
8872 }
8873
8874 return {
8875 type: 'Feature',
8876 properties: {},
8877 geometry: geojson
8878 };
8879 }
8880
8881 var PointToGeoJSON = {
8882 toGeoJSON: function (precision) {
8883 return getFeature(this, {
8884 type: 'Point',
8885 coordinates: latLngToCoords(this.getLatLng(), precision)
8886 });
8887 }
8888 };
8889
8890 // @namespace Marker
8891 // @section Other methods
8892 // @method toGeoJSON(precision?: Number): Object
8893 // `precision` is the number of decimal places for coordinates.
8894 // The default value is 6 places.
8895 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8896 Marker.include(PointToGeoJSON);
8897
8898 // @namespace CircleMarker
8899 // @method toGeoJSON(precision?: Number): Object
8900 // `precision` is the number of decimal places for coordinates.
8901 // The default value is 6 places.
8902 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8903 Circle.include(PointToGeoJSON);
8904 CircleMarker.include(PointToGeoJSON);
8905
8906
8907 // @namespace Polyline
8908 // @method toGeoJSON(precision?: Number): Object
8909 // `precision` is the number of decimal places for coordinates.
8910 // The default value is 6 places.
8911 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8912 Polyline.include({
8913 toGeoJSON: function (precision) {
8914 var multi = !isFlat(this._latlngs);
8915
8916 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8917
8918 return getFeature(this, {
8919 type: (multi ? 'Multi' : '') + 'LineString',
8920 coordinates: coords
8921 });
8922 }
8923 });
8924
8925 // @namespace Polygon
8926 // @method toGeoJSON(precision?: Number): Object
8927 // `precision` is the number of decimal places for coordinates.
8928 // The default value is 6 places.
8929 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8930 Polygon.include({
8931 toGeoJSON: function (precision) {
8932 var holes = !isFlat(this._latlngs),
8933 multi = holes && !isFlat(this._latlngs[0]);
8934
8935 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8936
8937 if (!holes) {
8938 coords = [coords];
8939 }
8940
8941 return getFeature(this, {
8942 type: (multi ? 'Multi' : '') + 'Polygon',
8943 coordinates: coords
8944 });
8945 }
8946 });
8947
8948
8949 // @namespace LayerGroup
8950 LayerGroup.include({
8951 toMultiPoint: function (precision) {
8952 var coords = [];
8953
8954 this.eachLayer(function (layer) {
8955 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8956 });
8957
8958 return getFeature(this, {
8959 type: 'MultiPoint',
8960 coordinates: coords
8961 });
8962 },
8963
8964 // @method toGeoJSON(precision?: Number): Object
8965 // `precision` is the number of decimal places for coordinates.
8966 // The default value is 6 places.
8967 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8968 toGeoJSON: function (precision) {
8969
8970 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8971
8972 if (type === 'MultiPoint') {
8973 return this.toMultiPoint(precision);
8974 }
8975
8976 var isGeometryCollection = type === 'GeometryCollection',
8977 jsons = [];
8978
8979 this.eachLayer(function (layer) {
8980 if (layer.toGeoJSON) {
8981 var json = layer.toGeoJSON(precision);
8982 if (isGeometryCollection) {
8983 jsons.push(json.geometry);
8984 } else {
8985 var feature = asFeature(json);
8986 // Squash nested feature collections
8987 if (feature.type === 'FeatureCollection') {
8988 jsons.push.apply(jsons, feature.features);
8989 } else {
8990 jsons.push(feature);
8991 }
8992 }
8993 }
8994 });
8995
8996 if (isGeometryCollection) {
8997 return getFeature(this, {
8998 geometries: jsons,
8999 type: 'GeometryCollection'
9000 });
9001 }
9002
9003 return {
9004 type: 'FeatureCollection',
9005 features: jsons
9006 };
9007 }
9008 });
9009
9010 // @namespace GeoJSON
9011 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9012 // Creates a GeoJSON layer. Optionally accepts an object in
9013 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9014 // (you can alternatively add it later with `addData` method) and an `options` object.
9015 function geoJSON(geojson, options) {
9016 return new GeoJSON(geojson, options);
9017 }
9018
9019 // Backward compatibility.
9020 var geoJson = geoJSON;
9021
9022 /*
9023 * @class ImageOverlay
9024 * @aka L.ImageOverlay
9025 * @inherits Interactive layer
9026 *
9027 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9028 *
9029 * @example
9030 *
9031 * ```js
9032 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9033 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9034 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9035 * ```
9036 */
9037
9038 var ImageOverlay = Layer.extend({
9039
9040 // @section
9041 // @aka ImageOverlay options
9042 options: {
9043 // @option opacity: Number = 1.0
9044 // The opacity of the image overlay.
9045 opacity: 1,
9046
9047 // @option alt: String = ''
9048 // Text for the `alt` attribute of the image (useful for accessibility).
9049 alt: '',
9050
9051 // @option interactive: Boolean = false
9052 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9053 interactive: false,
9054
9055 // @option crossOrigin: Boolean|String = false
9056 // Whether the crossOrigin attribute will be added to the image.
9057 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
9058 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9059 crossOrigin: false,
9060
9061 // @option errorOverlayUrl: String = ''
9062 // URL to the overlay image to show in place of the overlay that failed to load.
9063 errorOverlayUrl: '',
9064
9065 // @option zIndex: Number = 1
9066 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9067 zIndex: 1,
9068
9069 // @option className: String = ''
9070 // A custom class name to assign to the image. Empty by default.
9071 className: ''
9072 },
9073
9074 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9075 this._url = url;
9076 this._bounds = toLatLngBounds(bounds);
9077
9078 setOptions(this, options);
9079 },
9080
9081 onAdd: function () {
9082 if (!this._image) {
9083 this._initImage();
9084
9085 if (this.options.opacity < 1) {
9086 this._updateOpacity();
9087 }
9088 }
9089
9090 if (this.options.interactive) {
9091 addClass(this._image, 'leaflet-interactive');
9092 this.addInteractiveTarget(this._image);
9093 }
9094
9095 this.getPane().appendChild(this._image);
9096 this._reset();
9097 },
9098
9099 onRemove: function () {
9100 remove(this._image);
9101 if (this.options.interactive) {
9102 this.removeInteractiveTarget(this._image);
9103 }
9104 },
9105
9106 // @method setOpacity(opacity: Number): this
9107 // Sets the opacity of the overlay.
9108 setOpacity: function (opacity) {
9109 this.options.opacity = opacity;
9110
9111 if (this._image) {
9112 this._updateOpacity();
9113 }
9114 return this;
9115 },
9116
9117 setStyle: function (styleOpts) {
9118 if (styleOpts.opacity) {
9119 this.setOpacity(styleOpts.opacity);
9120 }
9121 return this;
9122 },
9123
9124 // @method bringToFront(): this
9125 // Brings the layer to the top of all overlays.
9126 bringToFront: function () {
9127 if (this._map) {
9128 toFront(this._image);
9129 }
9130 return this;
9131 },
9132
9133 // @method bringToBack(): this
9134 // Brings the layer to the bottom of all overlays.
9135 bringToBack: function () {
9136 if (this._map) {
9137 toBack(this._image);
9138 }
9139 return this;
9140 },
9141
9142 // @method setUrl(url: String): this
9143 // Changes the URL of the image.
9144 setUrl: function (url) {
9145 this._url = url;
9146
9147 if (this._image) {
9148 this._image.src = url;
9149 }
9150 return this;
9151 },
9152
9153 // @method setBounds(bounds: LatLngBounds): this
9154 // Update the bounds that this ImageOverlay covers
9155 setBounds: function (bounds) {
9156 this._bounds = toLatLngBounds(bounds);
9157
9158 if (this._map) {
9159 this._reset();
9160 }
9161 return this;
9162 },
9163
9164 getEvents: function () {
9165 var events = {
9166 zoom: this._reset,
9167 viewreset: this._reset
9168 };
9169
9170 if (this._zoomAnimated) {
9171 events.zoomanim = this._animateZoom;
9172 }
9173
9174 return events;
9175 },
9176
9177 // @method setZIndex(value: Number): this
9178 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9179 setZIndex: function (value) {
9180 this.options.zIndex = value;
9181 this._updateZIndex();
9182 return this;
9183 },
9184
9185 // @method getBounds(): LatLngBounds
9186 // Get the bounds that this ImageOverlay covers
9187 getBounds: function () {
9188 return this._bounds;
9189 },
9190
9191 // @method getElement(): HTMLElement
9192 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9193 // used by this overlay.
9194 getElement: function () {
9195 return this._image;
9196 },
9197
9198 _initImage: function () {
9199 var wasElementSupplied = this._url.tagName === 'IMG';
9200 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9201
9202 addClass(img, 'leaflet-image-layer');
9203 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9204 if (this.options.className) { addClass(img, this.options.className); }
9205
9206 img.onselectstart = falseFn;
9207 img.onmousemove = falseFn;
9208
9209 // @event load: Event
9210 // Fired when the ImageOverlay layer has loaded its image
9211 img.onload = bind(this.fire, this, 'load');
9212 img.onerror = bind(this._overlayOnError, this, 'error');
9213
9214 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9215 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9216 }
9217
9218 if (this.options.zIndex) {
9219 this._updateZIndex();
9220 }
9221
9222 if (wasElementSupplied) {
9223 this._url = img.src;
9224 return;
9225 }
9226
9227 img.src = this._url;
9228 img.alt = this.options.alt;
9229 },
9230
9231 _animateZoom: function (e) {
9232 var scale = this._map.getZoomScale(e.zoom),
9233 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9234
9235 setTransform(this._image, offset, scale);
9236 },
9237
9238 _reset: function () {
9239 var image = this._image,
9240 bounds = new Bounds(
9241 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9242 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9243 size = bounds.getSize();
9244
9245 setPosition(image, bounds.min);
9246
9247 image.style.width = size.x + 'px';
9248 image.style.height = size.y + 'px';
9249 },
9250
9251 _updateOpacity: function () {
9252 setOpacity(this._image, this.options.opacity);
9253 },
9254
9255 _updateZIndex: function () {
9256 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9257 this._image.style.zIndex = this.options.zIndex;
9258 }
9259 },
9260
9261 _overlayOnError: function () {
9262 // @event error: Event
9263 // Fired when the ImageOverlay layer fails to load its image
9264 this.fire('error');
9265
9266 var errorUrl = this.options.errorOverlayUrl;
9267 if (errorUrl && this._url !== errorUrl) {
9268 this._url = errorUrl;
9269 this._image.src = errorUrl;
9270 }
9271 }
9272 });
9273
9274 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9275 // Instantiates an image overlay object given the URL of the image and the
9276 // geographical bounds it is tied to.
9277 var imageOverlay = function (url, bounds, options) {
9278 return new ImageOverlay(url, bounds, options);
9279 };
9280
9281 /*
9282 * @class VideoOverlay
9283 * @aka L.VideoOverlay
9284 * @inherits ImageOverlay
9285 *
9286 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9287 *
9288 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9289 * HTML5 element.
9290 *
9291 * @example
9292 *
9293 * ```js
9294 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9295 * videoBounds = [[ 32, -130], [ 13, -100]];
9296 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9297 * ```
9298 */
9299
9300 var VideoOverlay = ImageOverlay.extend({
9301
9302 // @section
9303 // @aka VideoOverlay options
9304 options: {
9305 // @option autoplay: Boolean = true
9306 // Whether the video starts playing automatically when loaded.
9307 autoplay: true,
9308
9309 // @option loop: Boolean = true
9310 // Whether the video will loop back to the beginning when played.
9311 loop: true,
9312
9313 // @option keepAspectRatio: Boolean = true
9314 // Whether the video will save aspect ratio after the projection.
9315 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9316 keepAspectRatio: true,
9317
9318 // @option muted: Boolean = false
9319 // Whether the video starts on mute when loaded.
9320 muted: false
9321 },
9322
9323 _initImage: function () {
9324 var wasElementSupplied = this._url.tagName === 'VIDEO';
9325 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9326
9327 addClass(vid, 'leaflet-image-layer');
9328 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9329 if (this.options.className) { addClass(vid, this.options.className); }
9330
9331 vid.onselectstart = falseFn;
9332 vid.onmousemove = falseFn;
9333
9334 // @event load: Event
9335 // Fired when the video has finished loading the first frame
9336 vid.onloadeddata = bind(this.fire, this, 'load');
9337
9338 if (wasElementSupplied) {
9339 var sourceElements = vid.getElementsByTagName('source');
9340 var sources = [];
9341 for (var j = 0; j < sourceElements.length; j++) {
9342 sources.push(sourceElements[j].src);
9343 }
9344
9345 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9346 return;
9347 }
9348
9349 if (!isArray(this._url)) { this._url = [this._url]; }
9350
9351 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
9352 vid.style['objectFit'] = 'fill';
9353 }
9354 vid.autoplay = !!this.options.autoplay;
9355 vid.loop = !!this.options.loop;
9356 vid.muted = !!this.options.muted;
9357 for (var i = 0; i < this._url.length; i++) {
9358 var source = create$1('source');
9359 source.src = this._url[i];
9360 vid.appendChild(source);
9361 }
9362 }
9363
9364 // @method getElement(): HTMLVideoElement
9365 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9366 // used by this overlay.
9367 });
9368
9369
9370 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9371 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9372 // geographical bounds it is tied to.
9373
9374 function videoOverlay(video, bounds, options) {
9375 return new VideoOverlay(video, bounds, options);
9376 }
9377
9378 /*
9379 * @class SVGOverlay
9380 * @aka L.SVGOverlay
9381 * @inherits ImageOverlay
9382 *
9383 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9384 *
9385 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9386 *
9387 * @example
9388 *
9389 * ```js
9390 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9391 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9392 * svgElement.setAttribute('viewBox', "0 0 200 200");
9393 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9394 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9395 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9396 * ```
9397 */
9398
9399 var SVGOverlay = ImageOverlay.extend({
9400 _initImage: function () {
9401 var el = this._image = this._url;
9402
9403 addClass(el, 'leaflet-image-layer');
9404 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9405 if (this.options.className) { addClass(el, this.options.className); }
9406
9407 el.onselectstart = falseFn;
9408 el.onmousemove = falseFn;
9409 }
9410
9411 // @method getElement(): SVGElement
9412 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9413 // used by this overlay.
9414 });
9415
9416
9417 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9418 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9419 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9420
9421 function svgOverlay(el, bounds, options) {
9422 return new SVGOverlay(el, bounds, options);
9423 }
9424
9425 /*
9426 * @class DivOverlay
9427 * @inherits Layer
9428 * @aka L.DivOverlay
9429 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9430 */
9431
9432 // @namespace DivOverlay
9433 var DivOverlay = Layer.extend({
9434
9435 // @section
9436 // @aka DivOverlay options
9437 options: {
9438 // @option offset: Point = Point(0, 7)
9439 // The offset of the popup position. Useful to control the anchor
9440 // of the popup when opening it on some overlays.
9441 offset: [0, 7],
9442
9443 // @option className: String = ''
9444 // A custom CSS class name to assign to the popup.
9445 className: '',
9446
9447 // @option pane: String = 'popupPane'
9448 // `Map pane` where the popup will be added.
9449 pane: 'popupPane'
9450 },
9451
9452 initialize: function (options, source) {
9453 setOptions(this, options);
9454
9455 this._source = source;
9456 },
9457
9458 onAdd: function (map) {
9459 this._zoomAnimated = map._zoomAnimated;
9460
9461 if (!this._container) {
9462 this._initLayout();
9463 }
9464
9465 if (map._fadeAnimated) {
9466 setOpacity(this._container, 0);
9467 }
9468
9469 clearTimeout(this._removeTimeout);
9470 this.getPane().appendChild(this._container);
9471 this.update();
9472
9473 if (map._fadeAnimated) {
9474 setOpacity(this._container, 1);
9475 }
9476
9477 this.bringToFront();
9478 },
9479
9480 onRemove: function (map) {
9481 if (map._fadeAnimated) {
9482 setOpacity(this._container, 0);
9483 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9484 } else {
9485 remove(this._container);
9486 }
9487 },
9488
9489 // @namespace Popup
9490 // @method getLatLng: LatLng
9491 // Returns the geographical point of popup.
9492 getLatLng: function () {
9493 return this._latlng;
9494 },
9495
9496 // @method setLatLng(latlng: LatLng): this
9497 // Sets the geographical point where the popup will open.
9498 setLatLng: function (latlng) {
9499 this._latlng = toLatLng(latlng);
9500 if (this._map) {
9501 this._updatePosition();
9502 this._adjustPan();
9503 }
9504 return this;
9505 },
9506
9507 // @method getContent: String|HTMLElement
9508 // Returns the content of the popup.
9509 getContent: function () {
9510 return this._content;
9511 },
9512
9513 // @method setContent(htmlContent: String|HTMLElement|Function): this
9514 // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
9515 setContent: function (content) {
9516 this._content = content;
9517 this.update();
9518 return this;
9519 },
9520
9521 // @method getElement: String|HTMLElement
9522 // Returns the HTML container of the popup.
9523 getElement: function () {
9524 return this._container;
9525 },
9526
9527 // @method update: null
9528 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9529 update: function () {
9530 if (!this._map) { return; }
9531
9532 this._container.style.visibility = 'hidden';
9533
9534 this._updateContent();
9535 this._updateLayout();
9536 this._updatePosition();
9537
9538 this._container.style.visibility = '';
9539
9540 this._adjustPan();
9541 },
9542
9543 getEvents: function () {
9544 var events = {
9545 zoom: this._updatePosition,
9546 viewreset: this._updatePosition
9547 };
9548
9549 if (this._zoomAnimated) {
9550 events.zoomanim = this._animateZoom;
9551 }
9552 return events;
9553 },
9554
9555 // @method isOpen: Boolean
9556 // Returns `true` when the popup is visible on the map.
9557 isOpen: function () {
9558 return !!this._map && this._map.hasLayer(this);
9559 },
9560
9561 // @method bringToFront: this
9562 // Brings this popup in front of other popups (in the same map pane).
9563 bringToFront: function () {
9564 if (this._map) {
9565 toFront(this._container);
9566 }
9567 return this;
9568 },
9569
9570 // @method bringToBack: this
9571 // Brings this popup to the back of other popups (in the same map pane).
9572 bringToBack: function () {
9573 if (this._map) {
9574 toBack(this._container);
9575 }
9576 return this;
9577 },
9578
9579 _prepareOpen: function (parent, layer, latlng) {
9580 if (!(layer instanceof Layer)) {
9581 latlng = layer;
9582 layer = parent;
9583 }
9584
9585 if (layer instanceof FeatureGroup) {
9586 for (var id in parent._layers) {
9587 layer = parent._layers[id];
9588 break;
9589 }
9590 }
9591
9592 if (!latlng) {
9593 if (layer.getCenter) {
9594 latlng = layer.getCenter();
9595 } else if (layer.getLatLng) {
9596 latlng = layer.getLatLng();
9597 } else {
9598 throw new Error('Unable to get source layer LatLng.');
9599 }
9600 }
9601
9602 // set overlay source to this layer
9603 this._source = layer;
9604
9605 // update the overlay (content, layout, ect...)
9606 this.update();
9607
9608 return latlng;
9609 },
9610
9611 _updateContent: function () {
9612 if (!this._content) { return; }
9613
9614 var node = this._contentNode;
9615 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9616
9617 if (typeof content === 'string') {
9618 node.innerHTML = content;
9619 } else {
9620 while (node.hasChildNodes()) {
9621 node.removeChild(node.firstChild);
9622 }
9623 node.appendChild(content);
9624 }
9625 this.fire('contentupdate');
9626 },
9627
9628 _updatePosition: function () {
9629 if (!this._map) { return; }
9630
9631 var pos = this._map.latLngToLayerPoint(this._latlng),
9632 offset = toPoint(this.options.offset),
9633 anchor = this._getAnchor();
9634
9635 if (this._zoomAnimated) {
9636 setPosition(this._container, pos.add(anchor));
9637 } else {
9638 offset = offset.add(pos).add(anchor);
9639 }
9640
9641 var bottom = this._containerBottom = -offset.y,
9642 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9643
9644 // bottom position the popup in case the height of the popup changes (images loading etc)
9645 this._container.style.bottom = bottom + 'px';
9646 this._container.style.left = left + 'px';
9647 },
9648
9649 _getAnchor: function () {
9650 return [0, 0];
9651 }
9652
9653 });
9654
9655 /*
9656 * @class Popup
9657 * @inherits DivOverlay
9658 * @aka L.Popup
9659 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9660 * open popups while making sure that only one popup is open at one time
9661 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9662 *
9663 * @example
9664 *
9665 * If you want to just bind a popup to marker click and then open it, it's really easy:
9666 *
9667 * ```js
9668 * marker.bindPopup(popupContent).openPopup();
9669 * ```
9670 * Path overlays like polylines also have a `bindPopup` method.
9671 * Here's a more complicated way to open a popup on a map:
9672 *
9673 * ```js
9674 * var popup = L.popup()
9675 * .setLatLng(latlng)
9676 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9677 * .openOn(map);
9678 * ```
9679 */
9680
9681
9682 // @namespace Popup
9683 var Popup = DivOverlay.extend({
9684
9685 // @section
9686 // @aka Popup options
9687 options: {
9688 // @option maxWidth: Number = 300
9689 // Max width of the popup, in pixels.
9690 maxWidth: 300,
9691
9692 // @option minWidth: Number = 50
9693 // Min width of the popup, in pixels.
9694 minWidth: 50,
9695
9696 // @option maxHeight: Number = null
9697 // If set, creates a scrollable container of the given height
9698 // inside a popup if its content exceeds it.
9699 maxHeight: null,
9700
9701 // @option autoPan: Boolean = true
9702 // Set it to `false` if you don't want the map to do panning animation
9703 // to fit the opened popup.
9704 autoPan: true,
9705
9706 // @option autoPanPaddingTopLeft: Point = null
9707 // The margin between the popup and the top left corner of the map
9708 // view after autopanning was performed.
9709 autoPanPaddingTopLeft: null,
9710
9711 // @option autoPanPaddingBottomRight: Point = null
9712 // The margin between the popup and the bottom right corner of the map
9713 // view after autopanning was performed.
9714 autoPanPaddingBottomRight: null,
9715
9716 // @option autoPanPadding: Point = Point(5, 5)
9717 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9718 autoPanPadding: [5, 5],
9719
9720 // @option keepInView: Boolean = false
9721 // Set it to `true` if you want to prevent users from panning the popup
9722 // off of the screen while it is open.
9723 keepInView: false,
9724
9725 // @option closeButton: Boolean = true
9726 // Controls the presence of a close button in the popup.
9727 closeButton: true,
9728
9729 // @option autoClose: Boolean = true
9730 // Set it to `false` if you want to override the default behavior of
9731 // the popup closing when another popup is opened.
9732 autoClose: true,
9733
9734 // @option closeOnEscapeKey: Boolean = true
9735 // Set it to `false` if you want to override the default behavior of
9736 // the ESC key for closing of the popup.
9737 closeOnEscapeKey: true,
9738
9739 // @option closeOnClick: Boolean = *
9740 // Set it if you want to override the default behavior of the popup closing when user clicks
9741 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9742
9743 // @option className: String = ''
9744 // A custom CSS class name to assign to the popup.
9745 className: ''
9746 },
9747
9748 // @namespace Popup
9749 // @method openOn(map: Map): this
9750 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9751 openOn: function (map) {
9752 map.openPopup(this);
9753 return this;
9754 },
9755
9756 onAdd: function (map) {
9757 DivOverlay.prototype.onAdd.call(this, map);
9758
9759 // @namespace Map
9760 // @section Popup events
9761 // @event popupopen: PopupEvent
9762 // Fired when a popup is opened in the map
9763 map.fire('popupopen', {popup: this});
9764
9765 if (this._source) {
9766 // @namespace Layer
9767 // @section Popup events
9768 // @event popupopen: PopupEvent
9769 // Fired when a popup bound to this layer is opened
9770 this._source.fire('popupopen', {popup: this}, true);
9771 // For non-path layers, we toggle the popup when clicking
9772 // again the layer, so prevent the map to reopen it.
9773 if (!(this._source instanceof Path)) {
9774 this._source.on('preclick', stopPropagation);
9775 }
9776 }
9777 },
9778
9779 onRemove: function (map) {
9780 DivOverlay.prototype.onRemove.call(this, map);
9781
9782 // @namespace Map
9783 // @section Popup events
9784 // @event popupclose: PopupEvent
9785 // Fired when a popup in the map is closed
9786 map.fire('popupclose', {popup: this});
9787
9788 if (this._source) {
9789 // @namespace Layer
9790 // @section Popup events
9791 // @event popupclose: PopupEvent
9792 // Fired when a popup bound to this layer is closed
9793 this._source.fire('popupclose', {popup: this}, true);
9794 if (!(this._source instanceof Path)) {
9795 this._source.off('preclick', stopPropagation);
9796 }
9797 }
9798 },
9799
9800 getEvents: function () {
9801 var events = DivOverlay.prototype.getEvents.call(this);
9802
9803 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9804 events.preclick = this._close;
9805 }
9806
9807 if (this.options.keepInView) {
9808 events.moveend = this._adjustPan;
9809 }
9810
9811 return events;
9812 },
9813
9814 _close: function () {
9815 if (this._map) {
9816 this._map.closePopup(this);
9817 }
9818 },
9819
9820 _initLayout: function () {
9821 var prefix = 'leaflet-popup',
9822 container = this._container = create$1('div',
9823 prefix + ' ' + (this.options.className || '') +
9824 ' leaflet-zoom-animated');
9825
9826 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9827 this._contentNode = create$1('div', prefix + '-content', wrapper);
9828
9829 disableClickPropagation(container);
9830 disableScrollPropagation(this._contentNode);
9831 on(container, 'contextmenu', stopPropagation);
9832
9833 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9834 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9835
9836 if (this.options.closeButton) {
9837 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9838 closeButton.href = '#close';
9839 closeButton.innerHTML = '&#215;';
9840
9841 on(closeButton, 'click', this._onCloseButtonClick, this);
9842 }
9843 },
9844
9845 _updateLayout: function () {
9846 var container = this._contentNode,
9847 style = container.style;
9848
9849 style.width = '';
9850 style.whiteSpace = 'nowrap';
9851
9852 var width = container.offsetWidth;
9853 width = Math.min(width, this.options.maxWidth);
9854 width = Math.max(width, this.options.minWidth);
9855
9856 style.width = (width + 1) + 'px';
9857 style.whiteSpace = '';
9858
9859 style.height = '';
9860
9861 var height = container.offsetHeight,
9862 maxHeight = this.options.maxHeight,
9863 scrolledClass = 'leaflet-popup-scrolled';
9864
9865 if (maxHeight && height > maxHeight) {
9866 style.height = maxHeight + 'px';
9867 addClass(container, scrolledClass);
9868 } else {
9869 removeClass(container, scrolledClass);
9870 }
9871
9872 this._containerWidth = this._container.offsetWidth;
9873 },
9874
9875 _animateZoom: function (e) {
9876 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9877 anchor = this._getAnchor();
9878 setPosition(this._container, pos.add(anchor));
9879 },
9880
9881 _adjustPan: function () {
9882 if (!this.options.autoPan) { return; }
9883 if (this._map._panAnim) { this._map._panAnim.stop(); }
9884
9885 var map = this._map,
9886 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9887 containerHeight = this._container.offsetHeight + marginBottom,
9888 containerWidth = this._containerWidth,
9889 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9890
9891 layerPos._add(getPosition(this._container));
9892
9893 var containerPos = map.layerPointToContainerPoint(layerPos),
9894 padding = toPoint(this.options.autoPanPadding),
9895 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9896 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9897 size = map.getSize(),
9898 dx = 0,
9899 dy = 0;
9900
9901 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9902 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9903 }
9904 if (containerPos.x - dx - paddingTL.x < 0) { // left
9905 dx = containerPos.x - paddingTL.x;
9906 }
9907 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9908 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9909 }
9910 if (containerPos.y - dy - paddingTL.y < 0) { // top
9911 dy = containerPos.y - paddingTL.y;
9912 }
9913
9914 // @namespace Map
9915 // @section Popup events
9916 // @event autopanstart: Event
9917 // Fired when the map starts autopanning when opening a popup.
9918 if (dx || dy) {
9919 map
9920 .fire('autopanstart')
9921 .panBy([dx, dy]);
9922 }
9923 },
9924
9925 _onCloseButtonClick: function (e) {
9926 this._close();
9927 stop(e);
9928 },
9929
9930 _getAnchor: function () {
9931 // Where should we anchor the popup on the source layer?
9932 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9933 }
9934
9935 });
9936
9937 // @namespace Popup
9938 // @factory L.popup(options?: Popup options, source?: Layer)
9939 // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
9940 var popup = function (options, source) {
9941 return new Popup(options, source);
9942 };
9943
9944
9945 /* @namespace Map
9946 * @section Interaction Options
9947 * @option closePopupOnClick: Boolean = true
9948 * Set it to `false` if you don't want popups to close when user clicks the map.
9949 */
9950 Map.mergeOptions({
9951 closePopupOnClick: true
9952 });
9953
9954
9955 // @namespace Map
9956 // @section Methods for Layers and Controls
9957 Map.include({
9958 // @method openPopup(popup: Popup): this
9959 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9960 // @alternative
9961 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9962 // Creates a popup with the specified content and options and opens it in the given point on a map.
9963 openPopup: function (popup, latlng, options) {
9964 if (!(popup instanceof Popup)) {
9965 popup = new Popup(options).setContent(popup);
9966 }
9967
9968 if (latlng) {
9969 popup.setLatLng(latlng);
9970 }
9971
9972 if (this.hasLayer(popup)) {
9973 return this;
9974 }
9975
9976 if (this._popup && this._popup.options.autoClose) {
9977 this.closePopup();
9978 }
9979
9980 this._popup = popup;
9981 return this.addLayer(popup);
9982 },
9983
9984 // @method closePopup(popup?: Popup): this
9985 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9986 closePopup: function (popup) {
9987 if (!popup || popup === this._popup) {
9988 popup = this._popup;
9989 this._popup = null;
9990 }
9991 if (popup) {
9992 this.removeLayer(popup);
9993 }
9994 return this;
9995 }
9996 });
9997
9998 /*
9999 * @namespace Layer
10000 * @section Popup methods example
10001 *
10002 * All layers share a set of methods convenient for binding popups to it.
10003 *
10004 * ```js
10005 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10006 * layer.openPopup();
10007 * layer.closePopup();
10008 * ```
10009 *
10010 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
10011 */
10012
10013 // @section Popup methods
10014 Layer.include({
10015
10016 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10017 // Binds a popup to the layer with the passed `content` and sets up the
10018 // necessary event listeners. If a `Function` is passed it will receive
10019 // the layer as the first argument and should return a `String` or `HTMLElement`.
10020 bindPopup: function (content, options) {
10021
10022 if (content instanceof Popup) {
10023 setOptions(content, options);
10024 this._popup = content;
10025 content._source = this;
10026 } else {
10027 if (!this._popup || options) {
10028 this._popup = new Popup(options, this);
10029 }
10030 this._popup.setContent(content);
10031 }
10032
10033 if (!this._popupHandlersAdded) {
10034 this.on({
10035 click: this._openPopup,
10036 keypress: this._onKeyPress,
10037 remove: this.closePopup,
10038 move: this._movePopup
10039 });
10040 this._popupHandlersAdded = true;
10041 }
10042
10043 return this;
10044 },
10045
10046 // @method unbindPopup(): this
10047 // Removes the popup previously bound with `bindPopup`.
10048 unbindPopup: function () {
10049 if (this._popup) {
10050 this.off({
10051 click: this._openPopup,
10052 keypress: this._onKeyPress,
10053 remove: this.closePopup,
10054 move: this._movePopup
10055 });
10056 this._popupHandlersAdded = false;
10057 this._popup = null;
10058 }
10059 return this;
10060 },
10061
10062 // @method openPopup(latlng?: LatLng): this
10063 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10064 openPopup: function (layer, latlng) {
10065 if (this._popup && this._map) {
10066 latlng = this._popup._prepareOpen(this, layer, latlng);
10067
10068 // open the popup on the map
10069 this._map.openPopup(this._popup, latlng);
10070 }
10071
10072 return this;
10073 },
10074
10075 // @method closePopup(): this
10076 // Closes the popup bound to this layer if it is open.
10077 closePopup: function () {
10078 if (this._popup) {
10079 this._popup._close();
10080 }
10081 return this;
10082 },
10083
10084 // @method togglePopup(): this
10085 // Opens or closes the popup bound to this layer depending on its current state.
10086 togglePopup: function (target) {
10087 if (this._popup) {
10088 if (this._popup._map) {
10089 this.closePopup();
10090 } else {
10091 this.openPopup(target);
10092 }
10093 }
10094 return this;
10095 },
10096
10097 // @method isPopupOpen(): boolean
10098 // Returns `true` if the popup bound to this layer is currently open.
10099 isPopupOpen: function () {
10100 return (this._popup ? this._popup.isOpen() : false);
10101 },
10102
10103 // @method setPopupContent(content: String|HTMLElement|Popup): this
10104 // Sets the content of the popup bound to this layer.
10105 setPopupContent: function (content) {
10106 if (this._popup) {
10107 this._popup.setContent(content);
10108 }
10109 return this;
10110 },
10111
10112 // @method getPopup(): Popup
10113 // Returns the popup bound to this layer.
10114 getPopup: function () {
10115 return this._popup;
10116 },
10117
10118 _openPopup: function (e) {
10119 var layer = e.layer || e.target;
10120
10121 if (!this._popup) {
10122 return;
10123 }
10124
10125 if (!this._map) {
10126 return;
10127 }
10128
10129 // prevent map click
10130 stop(e);
10131
10132 // if this inherits from Path its a vector and we can just
10133 // open the popup at the new location
10134 if (layer instanceof Path) {
10135 this.openPopup(e.layer || e.target, e.latlng);
10136 return;
10137 }
10138
10139 // otherwise treat it like a marker and figure out
10140 // if we should toggle it open/closed
10141 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10142 this.closePopup();
10143 } else {
10144 this.openPopup(layer, e.latlng);
10145 }
10146 },
10147
10148 _movePopup: function (e) {
10149 this._popup.setLatLng(e.latlng);
10150 },
10151
10152 _onKeyPress: function (e) {
10153 if (e.originalEvent.keyCode === 13) {
10154 this._openPopup(e);
10155 }
10156 }
10157 });
10158
10159 /*
10160 * @class Tooltip
10161 * @inherits DivOverlay
10162 * @aka L.Tooltip
10163 * Used to display small texts on top of map layers.
10164 *
10165 * @example
10166 *
10167 * ```js
10168 * marker.bindTooltip("my tooltip text").openTooltip();
10169 * ```
10170 * Note about tooltip offset. Leaflet takes two options in consideration
10171 * for computing tooltip offsetting:
10172 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10173 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10174 * move it to the bottom. Negatives will move to the left and top.
10175 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10176 * should adapt this value if you use a custom icon.
10177 */
10178
10179
10180 // @namespace Tooltip
10181 var Tooltip = DivOverlay.extend({
10182
10183 // @section
10184 // @aka Tooltip options
10185 options: {
10186 // @option pane: String = 'tooltipPane'
10187 // `Map pane` where the tooltip will be added.
10188 pane: 'tooltipPane',
10189
10190 // @option offset: Point = Point(0, 0)
10191 // Optional offset of the tooltip position.
10192 offset: [0, 0],
10193
10194 // @option direction: String = 'auto'
10195 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10196 // `top`, `bottom`, `center`, `auto`.
10197 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10198 // position on the map.
10199 direction: 'auto',
10200
10201 // @option permanent: Boolean = false
10202 // Whether to open the tooltip permanently or only on mouseover.
10203 permanent: false,
10204
10205 // @option sticky: Boolean = false
10206 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10207 sticky: false,
10208
10209 // @option interactive: Boolean = false
10210 // If true, the tooltip will listen to the feature events.
10211 interactive: false,
10212
10213 // @option opacity: Number = 0.9
10214 // Tooltip container opacity.
10215 opacity: 0.9
10216 },
10217
10218 onAdd: function (map) {
10219 DivOverlay.prototype.onAdd.call(this, map);
10220 this.setOpacity(this.options.opacity);
10221
10222 // @namespace Map
10223 // @section Tooltip events
10224 // @event tooltipopen: TooltipEvent
10225 // Fired when a tooltip is opened in the map.
10226 map.fire('tooltipopen', {tooltip: this});
10227
10228 if (this._source) {
10229 // @namespace Layer
10230 // @section Tooltip events
10231 // @event tooltipopen: TooltipEvent
10232 // Fired when a tooltip bound to this layer is opened.
10233 this._source.fire('tooltipopen', {tooltip: this}, true);
10234 }
10235 },
10236
10237 onRemove: function (map) {
10238 DivOverlay.prototype.onRemove.call(this, map);
10239
10240 // @namespace Map
10241 // @section Tooltip events
10242 // @event tooltipclose: TooltipEvent
10243 // Fired when a tooltip in the map is closed.
10244 map.fire('tooltipclose', {tooltip: this});
10245
10246 if (this._source) {
10247 // @namespace Layer
10248 // @section Tooltip events
10249 // @event tooltipclose: TooltipEvent
10250 // Fired when a tooltip bound to this layer is closed.
10251 this._source.fire('tooltipclose', {tooltip: this}, true);
10252 }
10253 },
10254
10255 getEvents: function () {
10256 var events = DivOverlay.prototype.getEvents.call(this);
10257
10258 if (touch && !this.options.permanent) {
10259 events.preclick = this._close;
10260 }
10261
10262 return events;
10263 },
10264
10265 _close: function () {
10266 if (this._map) {
10267 this._map.closeTooltip(this);
10268 }
10269 },
10270
10271 _initLayout: function () {
10272 var prefix = 'leaflet-tooltip',
10273 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10274
10275 this._contentNode = this._container = create$1('div', className);
10276 },
10277
10278 _updateLayout: function () {},
10279
10280 _adjustPan: function () {},
10281
10282 _setPosition: function (pos) {
10283 var subX, subY,
10284 map = this._map,
10285 container = this._container,
10286 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10287 tooltipPoint = map.layerPointToContainerPoint(pos),
10288 direction = this.options.direction,
10289 tooltipWidth = container.offsetWidth,
10290 tooltipHeight = container.offsetHeight,
10291 offset = toPoint(this.options.offset),
10292 anchor = this._getAnchor();
10293
10294 if (direction === 'top') {
10295 subX = tooltipWidth / 2;
10296 subY = tooltipHeight;
10297 } else if (direction === 'bottom') {
10298 subX = tooltipWidth / 2;
10299 subY = 0;
10300 } else if (direction === 'center') {
10301 subX = tooltipWidth / 2;
10302 subY = tooltipHeight / 2;
10303 } else if (direction === 'right') {
10304 subX = 0;
10305 subY = tooltipHeight / 2;
10306 } else if (direction === 'left') {
10307 subX = tooltipWidth;
10308 subY = tooltipHeight / 2;
10309 } else if (tooltipPoint.x < centerPoint.x) {
10310 direction = 'right';
10311 subX = 0;
10312 subY = tooltipHeight / 2;
10313 } else {
10314 direction = 'left';
10315 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10316 subY = tooltipHeight / 2;
10317 }
10318
10319 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10320
10321 removeClass(container, 'leaflet-tooltip-right');
10322 removeClass(container, 'leaflet-tooltip-left');
10323 removeClass(container, 'leaflet-tooltip-top');
10324 removeClass(container, 'leaflet-tooltip-bottom');
10325 addClass(container, 'leaflet-tooltip-' + direction);
10326 setPosition(container, pos);
10327 },
10328
10329 _updatePosition: function () {
10330 var pos = this._map.latLngToLayerPoint(this._latlng);
10331 this._setPosition(pos);
10332 },
10333
10334 setOpacity: function (opacity) {
10335 this.options.opacity = opacity;
10336
10337 if (this._container) {
10338 setOpacity(this._container, opacity);
10339 }
10340 },
10341
10342 _animateZoom: function (e) {
10343 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10344 this._setPosition(pos);
10345 },
10346
10347 _getAnchor: function () {
10348 // Where should we anchor the tooltip on the source layer?
10349 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10350 }
10351
10352 });
10353
10354 // @namespace Tooltip
10355 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10356 // Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
10357 var tooltip = function (options, source) {
10358 return new Tooltip(options, source);
10359 };
10360
10361 // @namespace Map
10362 // @section Methods for Layers and Controls
10363 Map.include({
10364
10365 // @method openTooltip(tooltip: Tooltip): this
10366 // Opens the specified tooltip.
10367 // @alternative
10368 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10369 // Creates a tooltip with the specified content and options and open it.
10370 openTooltip: function (tooltip, latlng, options) {
10371 if (!(tooltip instanceof Tooltip)) {
10372 tooltip = new Tooltip(options).setContent(tooltip);
10373 }
10374
10375 if (latlng) {
10376 tooltip.setLatLng(latlng);
10377 }
10378
10379 if (this.hasLayer(tooltip)) {
10380 return this;
10381 }
10382
10383 return this.addLayer(tooltip);
10384 },
10385
10386 // @method closeTooltip(tooltip?: Tooltip): this
10387 // Closes the tooltip given as parameter.
10388 closeTooltip: function (tooltip) {
10389 if (tooltip) {
10390 this.removeLayer(tooltip);
10391 }
10392 return this;
10393 }
10394
10395 });
10396
10397 /*
10398 * @namespace Layer
10399 * @section Tooltip methods example
10400 *
10401 * All layers share a set of methods convenient for binding tooltips to it.
10402 *
10403 * ```js
10404 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10405 * layer.openTooltip();
10406 * layer.closeTooltip();
10407 * ```
10408 */
10409
10410 // @section Tooltip methods
10411 Layer.include({
10412
10413 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10414 // Binds a tooltip to the layer with the passed `content` and sets up the
10415 // necessary event listeners. If a `Function` is passed it will receive
10416 // the layer as the first argument and should return a `String` or `HTMLElement`.
10417 bindTooltip: function (content, options) {
10418
10419 if (content instanceof Tooltip) {
10420 setOptions(content, options);
10421 this._tooltip = content;
10422 content._source = this;
10423 } else {
10424 if (!this._tooltip || options) {
10425 this._tooltip = new Tooltip(options, this);
10426 }
10427 this._tooltip.setContent(content);
10428
10429 }
10430
10431 this._initTooltipInteractions();
10432
10433 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10434 this.openTooltip();
10435 }
10436
10437 return this;
10438 },
10439
10440 // @method unbindTooltip(): this
10441 // Removes the tooltip previously bound with `bindTooltip`.
10442 unbindTooltip: function () {
10443 if (this._tooltip) {
10444 this._initTooltipInteractions(true);
10445 this.closeTooltip();
10446 this._tooltip = null;
10447 }
10448 return this;
10449 },
10450
10451 _initTooltipInteractions: function (remove$$1) {
10452 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10453 var onOff = remove$$1 ? 'off' : 'on',
10454 events = {
10455 remove: this.closeTooltip,
10456 move: this._moveTooltip
10457 };
10458 if (!this._tooltip.options.permanent) {
10459 events.mouseover = this._openTooltip;
10460 events.mouseout = this.closeTooltip;
10461 if (this._tooltip.options.sticky) {
10462 events.mousemove = this._moveTooltip;
10463 }
10464 if (touch) {
10465 events.click = this._openTooltip;
10466 }
10467 } else {
10468 events.add = this._openTooltip;
10469 }
10470 this[onOff](events);
10471 this._tooltipHandlersAdded = !remove$$1;
10472 },
10473
10474 // @method openTooltip(latlng?: LatLng): this
10475 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10476 openTooltip: function (layer, latlng) {
10477 if (this._tooltip && this._map) {
10478 latlng = this._tooltip._prepareOpen(this, layer, latlng);
10479
10480 // open the tooltip on the map
10481 this._map.openTooltip(this._tooltip, latlng);
10482
10483 // Tooltip container may not be defined if not permanent and never
10484 // opened.
10485 if (this._tooltip.options.interactive && this._tooltip._container) {
10486 addClass(this._tooltip._container, 'leaflet-clickable');
10487 this.addInteractiveTarget(this._tooltip._container);
10488 }
10489 }
10490
10491 return this;
10492 },
10493
10494 // @method closeTooltip(): this
10495 // Closes the tooltip bound to this layer if it is open.
10496 closeTooltip: function () {
10497 if (this._tooltip) {
10498 this._tooltip._close();
10499 if (this._tooltip.options.interactive && this._tooltip._container) {
10500 removeClass(this._tooltip._container, 'leaflet-clickable');
10501 this.removeInteractiveTarget(this._tooltip._container);
10502 }
10503 }
10504 return this;
10505 },
10506
10507 // @method toggleTooltip(): this
10508 // Opens or closes the tooltip bound to this layer depending on its current state.
10509 toggleTooltip: function (target) {
10510 if (this._tooltip) {
10511 if (this._tooltip._map) {
10512 this.closeTooltip();
10513 } else {
10514 this.openTooltip(target);
10515 }
10516 }
10517 return this;
10518 },
10519
10520 // @method isTooltipOpen(): boolean
10521 // Returns `true` if the tooltip bound to this layer is currently open.
10522 isTooltipOpen: function () {
10523 return this._tooltip.isOpen();
10524 },
10525
10526 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10527 // Sets the content of the tooltip bound to this layer.
10528 setTooltipContent: function (content) {
10529 if (this._tooltip) {
10530 this._tooltip.setContent(content);
10531 }
10532 return this;
10533 },
10534
10535 // @method getTooltip(): Tooltip
10536 // Returns the tooltip bound to this layer.
10537 getTooltip: function () {
10538 return this._tooltip;
10539 },
10540
10541 _openTooltip: function (e) {
10542 var layer = e.layer || e.target;
10543
10544 if (!this._tooltip || !this._map) {
10545 return;
10546 }
10547 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10548 },
10549
10550 _moveTooltip: function (e) {
10551 var latlng = e.latlng, containerPoint, layerPoint;
10552 if (this._tooltip.options.sticky && e.originalEvent) {
10553 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10554 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10555 latlng = this._map.layerPointToLatLng(layerPoint);
10556 }
10557 this._tooltip.setLatLng(latlng);
10558 }
10559 });
10560
10561 /*
10562 * @class DivIcon
10563 * @aka L.DivIcon
10564 * @inherits Icon
10565 *
10566 * Represents a lightweight icon for markers that uses a simple `<div>`
10567 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10568 *
10569 * @example
10570 * ```js
10571 * var myIcon = L.divIcon({className: 'my-div-icon'});
10572 * // you can set .my-div-icon styles in CSS
10573 *
10574 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10575 * ```
10576 *
10577 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10578 */
10579
10580 var DivIcon = Icon.extend({
10581 options: {
10582 // @section
10583 // @aka DivIcon options
10584 iconSize: [12, 12], // also can be set through CSS
10585
10586 // iconAnchor: (Point),
10587 // popupAnchor: (Point),
10588
10589 // @option html: String|HTMLElement = ''
10590 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10591 // an instance of `HTMLElement`.
10592 html: false,
10593
10594 // @option bgPos: Point = [0, 0]
10595 // Optional relative position of the background, in pixels
10596 bgPos: null,
10597
10598 className: 'leaflet-div-icon'
10599 },
10600
10601 createIcon: function (oldIcon) {
10602 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10603 options = this.options;
10604
10605 if (options.html instanceof Element) {
10606 empty(div);
10607 div.appendChild(options.html);
10608 } else {
10609 div.innerHTML = options.html !== false ? options.html : '';
10610 }
10611
10612 if (options.bgPos) {
10613 var bgPos = toPoint(options.bgPos);
10614 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10615 }
10616 this._setIconStyles(div, 'icon');
10617
10618 return div;
10619 },
10620
10621 createShadow: function () {
10622 return null;
10623 }
10624 });
10625
10626 // @factory L.divIcon(options: DivIcon options)
10627 // Creates a `DivIcon` instance with the given options.
10628 function divIcon(options) {
10629 return new DivIcon(options);
10630 }
10631
10632 Icon.Default = IconDefault;
10633
10634 /*
10635 * @class GridLayer
10636 * @inherits Layer
10637 * @aka L.GridLayer
10638 *
10639 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10640 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
10641 *
10642 *
10643 * @section Synchronous usage
10644 * @example
10645 *
10646 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
10647 *
10648 * ```js
10649 * var CanvasLayer = L.GridLayer.extend({
10650 * createTile: function(coords){
10651 * // create a <canvas> element for drawing
10652 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10653 *
10654 * // setup tile width and height according to the options
10655 * var size = this.getTileSize();
10656 * tile.width = size.x;
10657 * tile.height = size.y;
10658 *
10659 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10660 * var ctx = tile.getContext('2d');
10661 *
10662 * // return the tile so it can be rendered on screen
10663 * return tile;
10664 * }
10665 * });
10666 * ```
10667 *
10668 * @section Asynchronous usage
10669 * @example
10670 *
10671 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
10672 *
10673 * ```js
10674 * var CanvasLayer = L.GridLayer.extend({
10675 * createTile: function(coords, done){
10676 * var error;
10677 *
10678 * // create a <canvas> element for drawing
10679 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10680 *
10681 * // setup tile width and height according to the options
10682 * var size = this.getTileSize();
10683 * tile.width = size.x;
10684 * tile.height = size.y;
10685 *
10686 * // draw something asynchronously and pass the tile to the done() callback
10687 * setTimeout(function() {
10688 * done(error, tile);
10689 * }, 1000);
10690 *
10691 * return tile;
10692 * }
10693 * });
10694 * ```
10695 *
10696 * @section
10697 */
10698
10699
10700 var GridLayer = Layer.extend({
10701
10702 // @section
10703 // @aka GridLayer options
10704 options: {
10705 // @option tileSize: Number|Point = 256
10706 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10707 tileSize: 256,
10708
10709 // @option opacity: Number = 1.0
10710 // Opacity of the tiles. Can be used in the `createTile()` function.
10711 opacity: 1,
10712
10713 // @option updateWhenIdle: Boolean = (depends)
10714 // Load new tiles only when panning ends.
10715 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10716 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10717 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10718 updateWhenIdle: mobile,
10719
10720 // @option updateWhenZooming: Boolean = true
10721 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
10722 updateWhenZooming: true,
10723
10724 // @option updateInterval: Number = 200
10725 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10726 updateInterval: 200,
10727
10728 // @option zIndex: Number = 1
10729 // The explicit zIndex of the tile layer.
10730 zIndex: 1,
10731
10732 // @option bounds: LatLngBounds = undefined
10733 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10734 bounds: null,
10735
10736 // @option minZoom: Number = 0
10737 // The minimum zoom level down to which this layer will be displayed (inclusive).
10738 minZoom: 0,
10739
10740 // @option maxZoom: Number = undefined
10741 // The maximum zoom level up to which this layer will be displayed (inclusive).
10742 maxZoom: undefined,
10743
10744 // @option maxNativeZoom: Number = undefined
10745 // Maximum zoom number the tile source has available. If it is specified,
10746 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10747 // from `maxNativeZoom` level and auto-scaled.
10748 maxNativeZoom: undefined,
10749
10750 // @option minNativeZoom: Number = undefined
10751 // Minimum zoom number the tile source has available. If it is specified,
10752 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10753 // from `minNativeZoom` level and auto-scaled.
10754 minNativeZoom: undefined,
10755
10756 // @option noWrap: Boolean = false
10757 // Whether the layer is wrapped around the antimeridian. If `true`, the
10758 // GridLayer will only be displayed once at low zoom levels. Has no
10759 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10760 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10761 // tiles outside the CRS limits.
10762 noWrap: false,
10763
10764 // @option pane: String = 'tilePane'
10765 // `Map pane` where the grid layer will be added.
10766 pane: 'tilePane',
10767
10768 // @option className: String = ''
10769 // A custom class name to assign to the tile layer. Empty by default.
10770 className: '',
10771
10772 // @option keepBuffer: Number = 2
10773 // When panning the map, keep this many rows and columns of tiles before unloading them.
10774 keepBuffer: 2
10775 },
10776
10777 initialize: function (options) {
10778 setOptions(this, options);
10779 },
10780
10781 onAdd: function () {
10782 this._initContainer();
10783
10784 this._levels = {};
10785 this._tiles = {};
10786
10787 this._resetView();
10788 this._update();
10789 },
10790
10791 beforeAdd: function (map) {
10792 map._addZoomLimit(this);
10793 },
10794
10795 onRemove: function (map) {
10796 this._removeAllTiles();
10797 remove(this._container);
10798 map._removeZoomLimit(this);
10799 this._container = null;
10800 this._tileZoom = undefined;
10801 },
10802
10803 // @method bringToFront: this
10804 // Brings the tile layer to the top of all tile layers.
10805 bringToFront: function () {
10806 if (this._map) {
10807 toFront(this._container);
10808 this._setAutoZIndex(Math.max);
10809 }
10810 return this;
10811 },
10812
10813 // @method bringToBack: this
10814 // Brings the tile layer to the bottom of all tile layers.
10815 bringToBack: function () {
10816 if (this._map) {
10817 toBack(this._container);
10818 this._setAutoZIndex(Math.min);
10819 }
10820 return this;
10821 },
10822
10823 // @method getContainer: HTMLElement
10824 // Returns the HTML element that contains the tiles for this layer.
10825 getContainer: function () {
10826 return this._container;
10827 },
10828
10829 // @method setOpacity(opacity: Number): this
10830 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10831 setOpacity: function (opacity) {
10832 this.options.opacity = opacity;
10833 this._updateOpacity();
10834 return this;
10835 },
10836
10837 // @method setZIndex(zIndex: Number): this
10838 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10839 setZIndex: function (zIndex) {
10840 this.options.zIndex = zIndex;
10841 this._updateZIndex();
10842
10843 return this;
10844 },
10845
10846 // @method isLoading: Boolean
10847 // Returns `true` if any tile in the grid layer has not finished loading.
10848 isLoading: function () {
10849 return this._loading;
10850 },
10851
10852 // @method redraw: this
10853 // Causes the layer to clear all the tiles and request them again.
10854 redraw: function () {
10855 if (this._map) {
10856 this._removeAllTiles();
10857 this._update();
10858 }
10859 return this;
10860 },
10861
10862 getEvents: function () {
10863 var events = {
10864 viewprereset: this._invalidateAll,
10865 viewreset: this._resetView,
10866 zoom: this._resetView,
10867 moveend: this._onMoveEnd
10868 };
10869
10870 if (!this.options.updateWhenIdle) {
10871 // update tiles on move, but not more often than once per given interval
10872 if (!this._onMove) {
10873 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10874 }
10875
10876 events.move = this._onMove;
10877 }
10878
10879 if (this._zoomAnimated) {
10880 events.zoomanim = this._animateZoom;
10881 }
10882
10883 return events;
10884 },
10885
10886 // @section Extension methods
10887 // Layers extending `GridLayer` shall reimplement the following method.
10888 // @method createTile(coords: Object, done?: Function): HTMLElement
10889 // Called only internally, must be overridden by classes extending `GridLayer`.
10890 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10891 // is specified, it must be called when the tile has finished loading and drawing.
10892 createTile: function () {
10893 return document.createElement('div');
10894 },
10895
10896 // @section
10897 // @method getTileSize: Point
10898 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10899 getTileSize: function () {
10900 var s = this.options.tileSize;
10901 return s instanceof Point ? s : new Point(s, s);
10902 },
10903
10904 _updateZIndex: function () {
10905 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10906 this._container.style.zIndex = this.options.zIndex;
10907 }
10908 },
10909
10910 _setAutoZIndex: function (compare) {
10911 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10912
10913 var layers = this.getPane().children,
10914 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10915
10916 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10917
10918 zIndex = layers[i].style.zIndex;
10919
10920 if (layers[i] !== this._container && zIndex) {
10921 edgeZIndex = compare(edgeZIndex, +zIndex);
10922 }
10923 }
10924
10925 if (isFinite(edgeZIndex)) {
10926 this.options.zIndex = edgeZIndex + compare(-1, 1);
10927 this._updateZIndex();
10928 }
10929 },
10930
10931 _updateOpacity: function () {
10932 if (!this._map) { return; }
10933
10934 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10935 if (ielt9) { return; }
10936
10937 setOpacity(this._container, this.options.opacity);
10938
10939 var now = +new Date(),
10940 nextFrame = false,
10941 willPrune = false;
10942
10943 for (var key in this._tiles) {
10944 var tile = this._tiles[key];
10945 if (!tile.current || !tile.loaded) { continue; }
10946
10947 var fade = Math.min(1, (now - tile.loaded) / 200);
10948
10949 setOpacity(tile.el, fade);
10950 if (fade < 1) {
10951 nextFrame = true;
10952 } else {
10953 if (tile.active) {
10954 willPrune = true;
10955 } else {
10956 this._onOpaqueTile(tile);
10957 }
10958 tile.active = true;
10959 }
10960 }
10961
10962 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10963
10964 if (nextFrame) {
10965 cancelAnimFrame(this._fadeFrame);
10966 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10967 }
10968 },
10969
10970 _onOpaqueTile: falseFn,
10971
10972 _initContainer: function () {
10973 if (this._container) { return; }
10974
10975 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10976 this._updateZIndex();
10977
10978 if (this.options.opacity < 1) {
10979 this._updateOpacity();
10980 }
10981
10982 this.getPane().appendChild(this._container);
10983 },
10984
10985 _updateLevels: function () {
10986
10987 var zoom = this._tileZoom,
10988 maxZoom = this.options.maxZoom;
10989
10990 if (zoom === undefined) { return undefined; }
10991
10992 for (var z in this._levels) {
10993 z = Number(z);
10994 if (this._levels[z].el.children.length || z === zoom) {
10995 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10996 this._onUpdateLevel(z);
10997 } else {
10998 remove(this._levels[z].el);
10999 this._removeTilesAtZoom(z);
11000 this._onRemoveLevel(z);
11001 delete this._levels[z];
11002 }
11003 }
11004
11005 var level = this._levels[zoom],
11006 map = this._map;
11007
11008 if (!level) {
11009 level = this._levels[zoom] = {};
11010
11011 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11012 level.el.style.zIndex = maxZoom;
11013
11014 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11015 level.zoom = zoom;
11016
11017 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11018
11019 // force the browser to consider the newly added element for transition
11020 falseFn(level.el.offsetWidth);
11021
11022 this._onCreateLevel(level);
11023 }
11024
11025 this._level = level;
11026
11027 return level;
11028 },
11029
11030 _onUpdateLevel: falseFn,
11031
11032 _onRemoveLevel: falseFn,
11033
11034 _onCreateLevel: falseFn,
11035
11036 _pruneTiles: function () {
11037 if (!this._map) {
11038 return;
11039 }
11040
11041 var key, tile;
11042
11043 var zoom = this._map.getZoom();
11044 if (zoom > this.options.maxZoom ||
11045 zoom < this.options.minZoom) {
11046 this._removeAllTiles();
11047 return;
11048 }
11049
11050 for (key in this._tiles) {
11051 tile = this._tiles[key];
11052 tile.retain = tile.current;
11053 }
11054
11055 for (key in this._tiles) {
11056 tile = this._tiles[key];
11057 if (tile.current && !tile.active) {
11058 var coords = tile.coords;
11059 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11060 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11061 }
11062 }
11063 }
11064
11065 for (key in this._tiles) {
11066 if (!this._tiles[key].retain) {
11067 this._removeTile(key);
11068 }
11069 }
11070 },
11071
11072 _removeTilesAtZoom: function (zoom) {
11073 for (var key in this._tiles) {
11074 if (this._tiles[key].coords.z !== zoom) {
11075 continue;
11076 }
11077 this._removeTile(key);
11078 }
11079 },
11080
11081 _removeAllTiles: function () {
11082 for (var key in this._tiles) {
11083 this._removeTile(key);
11084 }
11085 },
11086
11087 _invalidateAll: function () {
11088 for (var z in this._levels) {
11089 remove(this._levels[z].el);
11090 this._onRemoveLevel(Number(z));
11091 delete this._levels[z];
11092 }
11093 this._removeAllTiles();
11094
11095 this._tileZoom = undefined;
11096 },
11097
11098 _retainParent: function (x, y, z, minZoom) {
11099 var x2 = Math.floor(x / 2),
11100 y2 = Math.floor(y / 2),
11101 z2 = z - 1,
11102 coords2 = new Point(+x2, +y2);
11103 coords2.z = +z2;
11104
11105 var key = this._tileCoordsToKey(coords2),
11106 tile = this._tiles[key];
11107
11108 if (tile && tile.active) {
11109 tile.retain = true;
11110 return true;
11111
11112 } else if (tile && tile.loaded) {
11113 tile.retain = true;
11114 }
11115
11116 if (z2 > minZoom) {
11117 return this._retainParent(x2, y2, z2, minZoom);
11118 }
11119
11120 return false;
11121 },
11122
11123 _retainChildren: function (x, y, z, maxZoom) {
11124
11125 for (var i = 2 * x; i < 2 * x + 2; i++) {
11126 for (var j = 2 * y; j < 2 * y + 2; j++) {
11127
11128 var coords = new Point(i, j);
11129 coords.z = z + 1;
11130
11131 var key = this._tileCoordsToKey(coords),
11132 tile = this._tiles[key];
11133
11134 if (tile && tile.active) {
11135 tile.retain = true;
11136 continue;
11137
11138 } else if (tile && tile.loaded) {
11139 tile.retain = true;
11140 }
11141
11142 if (z + 1 < maxZoom) {
11143 this._retainChildren(i, j, z + 1, maxZoom);
11144 }
11145 }
11146 }
11147 },
11148
11149 _resetView: function (e) {
11150 var animating = e && (e.pinch || e.flyTo);
11151 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11152 },
11153
11154 _animateZoom: function (e) {
11155 this._setView(e.center, e.zoom, true, e.noUpdate);
11156 },
11157
11158 _clampZoom: function (zoom) {
11159 var options = this.options;
11160
11161 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11162 return options.minNativeZoom;
11163 }
11164
11165 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11166 return options.maxNativeZoom;
11167 }
11168
11169 return zoom;
11170 },
11171
11172 _setView: function (center, zoom, noPrune, noUpdate) {
11173 var tileZoom = Math.round(zoom);
11174 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11175 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11176 tileZoom = undefined;
11177 } else {
11178 tileZoom = this._clampZoom(tileZoom);
11179 }
11180
11181 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11182
11183 if (!noUpdate || tileZoomChanged) {
11184
11185 this._tileZoom = tileZoom;
11186
11187 if (this._abortLoading) {
11188 this._abortLoading();
11189 }
11190
11191 this._updateLevels();
11192 this._resetGrid();
11193
11194 if (tileZoom !== undefined) {
11195 this._update(center);
11196 }
11197
11198 if (!noPrune) {
11199 this._pruneTiles();
11200 }
11201
11202 // Flag to prevent _updateOpacity from pruning tiles during
11203 // a zoom anim or a pinch gesture
11204 this._noPrune = !!noPrune;
11205 }
11206
11207 this._setZoomTransforms(center, zoom);
11208 },
11209
11210 _setZoomTransforms: function (center, zoom) {
11211 for (var i in this._levels) {
11212 this._setZoomTransform(this._levels[i], center, zoom);
11213 }
11214 },
11215
11216 _setZoomTransform: function (level, center, zoom) {
11217 var scale = this._map.getZoomScale(zoom, level.zoom),
11218 translate = level.origin.multiplyBy(scale)
11219 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11220
11221 if (any3d) {
11222 setTransform(level.el, translate, scale);
11223 } else {
11224 setPosition(level.el, translate);
11225 }
11226 },
11227
11228 _resetGrid: function () {
11229 var map = this._map,
11230 crs = map.options.crs,
11231 tileSize = this._tileSize = this.getTileSize(),
11232 tileZoom = this._tileZoom;
11233
11234 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11235 if (bounds) {
11236 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11237 }
11238
11239 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11240 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11241 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11242 ];
11243 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11244 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11245 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11246 ];
11247 },
11248
11249 _onMoveEnd: function () {
11250 if (!this._map || this._map._animatingZoom) { return; }
11251
11252 this._update();
11253 },
11254
11255 _getTiledPixelBounds: function (center) {
11256 var map = this._map,
11257 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11258 scale = map.getZoomScale(mapZoom, this._tileZoom),
11259 pixelCenter = map.project(center, this._tileZoom).floor(),
11260 halfSize = map.getSize().divideBy(scale * 2);
11261
11262 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11263 },
11264
11265 // Private method to load tiles in the grid's active zoom level according to map bounds
11266 _update: function (center) {
11267 var map = this._map;
11268 if (!map) { return; }
11269 var zoom = this._clampZoom(map.getZoom());
11270
11271 if (center === undefined) { center = map.getCenter(); }
11272 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11273
11274 var pixelBounds = this._getTiledPixelBounds(center),
11275 tileRange = this._pxBoundsToTileRange(pixelBounds),
11276 tileCenter = tileRange.getCenter(),
11277 queue = [],
11278 margin = this.options.keepBuffer,
11279 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11280 tileRange.getTopRight().add([margin, -margin]));
11281
11282 // Sanity check: panic if the tile range contains Infinity somewhere.
11283 if (!(isFinite(tileRange.min.x) &&
11284 isFinite(tileRange.min.y) &&
11285 isFinite(tileRange.max.x) &&
11286 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11287
11288 for (var key in this._tiles) {
11289 var c = this._tiles[key].coords;
11290 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11291 this._tiles[key].current = false;
11292 }
11293 }
11294
11295 // _update just loads more tiles. If the tile zoom level differs too much
11296 // from the map's, let _setView reset levels and prune old tiles.
11297 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11298
11299 // create a queue of coordinates to load tiles from
11300 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11301 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11302 var coords = new Point(i, j);
11303 coords.z = this._tileZoom;
11304
11305 if (!this._isValidTile(coords)) { continue; }
11306
11307 var tile = this._tiles[this._tileCoordsToKey(coords)];
11308 if (tile) {
11309 tile.current = true;
11310 } else {
11311 queue.push(coords);
11312 }
11313 }
11314 }
11315
11316 // sort tile queue to load tiles in order of their distance to center
11317 queue.sort(function (a, b) {
11318 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11319 });
11320
11321 if (queue.length !== 0) {
11322 // if it's the first batch of tiles to load
11323 if (!this._loading) {
11324 this._loading = true;
11325 // @event loading: Event
11326 // Fired when the grid layer starts loading tiles.
11327 this.fire('loading');
11328 }
11329
11330 // create DOM fragment to append tiles in one batch
11331 var fragment = document.createDocumentFragment();
11332
11333 for (i = 0; i < queue.length; i++) {
11334 this._addTile(queue[i], fragment);
11335 }
11336
11337 this._level.el.appendChild(fragment);
11338 }
11339 },
11340
11341 _isValidTile: function (coords) {
11342 var crs = this._map.options.crs;
11343
11344 if (!crs.infinite) {
11345 // don't load tile if it's out of bounds and not wrapped
11346 var bounds = this._globalTileRange;
11347 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11348 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11349 }
11350
11351 if (!this.options.bounds) { return true; }
11352
11353 // don't load tile if it doesn't intersect the bounds in options
11354 var tileBounds = this._tileCoordsToBounds(coords);
11355 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11356 },
11357
11358 _keyToBounds: function (key) {
11359 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11360 },
11361
11362 _tileCoordsToNwSe: function (coords) {
11363 var map = this._map,
11364 tileSize = this.getTileSize(),
11365 nwPoint = coords.scaleBy(tileSize),
11366 sePoint = nwPoint.add(tileSize),
11367 nw = map.unproject(nwPoint, coords.z),
11368 se = map.unproject(sePoint, coords.z);
11369 return [nw, se];
11370 },
11371
11372 // converts tile coordinates to its geographical bounds
11373 _tileCoordsToBounds: function (coords) {
11374 var bp = this._tileCoordsToNwSe(coords),
11375 bounds = new LatLngBounds(bp[0], bp[1]);
11376
11377 if (!this.options.noWrap) {
11378 bounds = this._map.wrapLatLngBounds(bounds);
11379 }
11380 return bounds;
11381 },
11382 // converts tile coordinates to key for the tile cache
11383 _tileCoordsToKey: function (coords) {
11384 return coords.x + ':' + coords.y + ':' + coords.z;
11385 },
11386
11387 // converts tile cache key to coordinates
11388 _keyToTileCoords: function (key) {
11389 var k = key.split(':'),
11390 coords = new Point(+k[0], +k[1]);
11391 coords.z = +k[2];
11392 return coords;
11393 },
11394
11395 _removeTile: function (key) {
11396 var tile = this._tiles[key];
11397 if (!tile) { return; }
11398
11399 remove(tile.el);
11400
11401 delete this._tiles[key];
11402
11403 // @event tileunload: TileEvent
11404 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11405 this.fire('tileunload', {
11406 tile: tile.el,
11407 coords: this._keyToTileCoords(key)
11408 });
11409 },
11410
11411 _initTile: function (tile) {
11412 addClass(tile, 'leaflet-tile');
11413
11414 var tileSize = this.getTileSize();
11415 tile.style.width = tileSize.x + 'px';
11416 tile.style.height = tileSize.y + 'px';
11417
11418 tile.onselectstart = falseFn;
11419 tile.onmousemove = falseFn;
11420
11421 // update opacity on tiles in IE7-8 because of filter inheritance problems
11422 if (ielt9 && this.options.opacity < 1) {
11423 setOpacity(tile, this.options.opacity);
11424 }
11425
11426 // without this hack, tiles disappear after zoom on Chrome for Android
11427 // https://github.com/Leaflet/Leaflet/issues/2078
11428 if (android && !android23) {
11429 tile.style.WebkitBackfaceVisibility = 'hidden';
11430 }
11431 },
11432
11433 _addTile: function (coords, container) {
11434 var tilePos = this._getTilePos(coords),
11435 key = this._tileCoordsToKey(coords);
11436
11437 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11438
11439 this._initTile(tile);
11440
11441 // if createTile is defined with a second argument ("done" callback),
11442 // we know that tile is async and will be ready later; otherwise
11443 if (this.createTile.length < 2) {
11444 // mark tile as ready, but delay one frame for opacity animation to happen
11445 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11446 }
11447
11448 setPosition(tile, tilePos);
11449
11450 // save tile in cache
11451 this._tiles[key] = {
11452 el: tile,
11453 coords: coords,
11454 current: true
11455 };
11456
11457 container.appendChild(tile);
11458 // @event tileloadstart: TileEvent
11459 // Fired when a tile is requested and starts loading.
11460 this.fire('tileloadstart', {
11461 tile: tile,
11462 coords: coords
11463 });
11464 },
11465
11466 _tileReady: function (coords, err, tile) {
11467 if (err) {
11468 // @event tileerror: TileErrorEvent
11469 // Fired when there is an error loading a tile.
11470 this.fire('tileerror', {
11471 error: err,
11472 tile: tile,
11473 coords: coords
11474 });
11475 }
11476
11477 var key = this._tileCoordsToKey(coords);
11478
11479 tile = this._tiles[key];
11480 if (!tile) { return; }
11481
11482 tile.loaded = +new Date();
11483 if (this._map._fadeAnimated) {
11484 setOpacity(tile.el, 0);
11485 cancelAnimFrame(this._fadeFrame);
11486 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11487 } else {
11488 tile.active = true;
11489 this._pruneTiles();
11490 }
11491
11492 if (!err) {
11493 addClass(tile.el, 'leaflet-tile-loaded');
11494
11495 // @event tileload: TileEvent
11496 // Fired when a tile loads.
11497 this.fire('tileload', {
11498 tile: tile.el,
11499 coords: coords
11500 });
11501 }
11502
11503 if (this._noTilesToLoad()) {
11504 this._loading = false;
11505 // @event load: Event
11506 // Fired when the grid layer loaded all visible tiles.
11507 this.fire('load');
11508
11509 if (ielt9 || !this._map._fadeAnimated) {
11510 requestAnimFrame(this._pruneTiles, this);
11511 } else {
11512 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11513 // to trigger a pruning.
11514 setTimeout(bind(this._pruneTiles, this), 250);
11515 }
11516 }
11517 },
11518
11519 _getTilePos: function (coords) {
11520 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11521 },
11522
11523 _wrapCoords: function (coords) {
11524 var newCoords = new Point(
11525 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11526 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11527 newCoords.z = coords.z;
11528 return newCoords;
11529 },
11530
11531 _pxBoundsToTileRange: function (bounds) {
11532 var tileSize = this.getTileSize();
11533 return new Bounds(
11534 bounds.min.unscaleBy(tileSize).floor(),
11535 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11536 },
11537
11538 _noTilesToLoad: function () {
11539 for (var key in this._tiles) {
11540 if (!this._tiles[key].loaded) { return false; }
11541 }
11542 return true;
11543 }
11544 });
11545
11546 // @factory L.gridLayer(options?: GridLayer options)
11547 // Creates a new instance of GridLayer with the supplied options.
11548 function gridLayer(options) {
11549 return new GridLayer(options);
11550 }
11551
11552 /*
11553 * @class TileLayer
11554 * @inherits GridLayer
11555 * @aka L.TileLayer
11556 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
11557 *
11558 * @example
11559 *
11560 * ```js
11561 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
11562 * ```
11563 *
11564 * @section URL template
11565 * @example
11566 *
11567 * A string of the following form:
11568 *
11569 * ```
11570 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11571 * ```
11572 *
11573 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "&commat;2x" to the URL to load retina tiles.
11574 *
11575 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11576 *
11577 * ```
11578 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11579 * ```
11580 */
11581
11582
11583 var TileLayer = GridLayer.extend({
11584
11585 // @section
11586 // @aka TileLayer options
11587 options: {
11588 // @option minZoom: Number = 0
11589 // The minimum zoom level down to which this layer will be displayed (inclusive).
11590 minZoom: 0,
11591
11592 // @option maxZoom: Number = 18
11593 // The maximum zoom level up to which this layer will be displayed (inclusive).
11594 maxZoom: 18,
11595
11596 // @option subdomains: String|String[] = 'abc'
11597 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
11598 subdomains: 'abc',
11599
11600 // @option errorTileUrl: String = ''
11601 // URL to the tile image to show in place of the tile that failed to load.
11602 errorTileUrl: '',
11603
11604 // @option zoomOffset: Number = 0
11605 // The zoom number used in tile URLs will be offset with this value.
11606 zoomOffset: 0,
11607
11608 // @option tms: Boolean = false
11609 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11610 tms: false,
11611
11612 // @option zoomReverse: Boolean = false
11613 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11614 zoomReverse: false,
11615
11616 // @option detectRetina: Boolean = false
11617 // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
11618 detectRetina: false,
11619
11620 // @option crossOrigin: Boolean|String = false
11621 // Whether the crossOrigin attribute will be added to the tiles.
11622 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
11623 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11624 crossOrigin: false
11625 },
11626
11627 initialize: function (url, options) {
11628
11629 this._url = url;
11630
11631 options = setOptions(this, options);
11632
11633 // detecting retina displays, adjusting tileSize and zoom levels
11634 if (options.detectRetina && retina && options.maxZoom > 0) {
11635
11636 options.tileSize = Math.floor(options.tileSize / 2);
11637
11638 if (!options.zoomReverse) {
11639 options.zoomOffset++;
11640 options.maxZoom--;
11641 } else {
11642 options.zoomOffset--;
11643 options.minZoom++;
11644 }
11645
11646 options.minZoom = Math.max(0, options.minZoom);
11647 }
11648
11649 if (typeof options.subdomains === 'string') {
11650 options.subdomains = options.subdomains.split('');
11651 }
11652
11653 // for https://github.com/Leaflet/Leaflet/issues/137
11654 if (!android) {
11655 this.on('tileunload', this._onTileRemove);
11656 }
11657 },
11658
11659 // @method setUrl(url: String, noRedraw?: Boolean): this
11660 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11661 // If the URL does not change, the layer will not be redrawn unless
11662 // the noRedraw parameter is set to false.
11663 setUrl: function (url, noRedraw) {
11664 if (this._url === url && noRedraw === undefined) {
11665 noRedraw = true;
11666 }
11667
11668 this._url = url;
11669
11670 if (!noRedraw) {
11671 this.redraw();
11672 }
11673 return this;
11674 },
11675
11676 // @method createTile(coords: Object, done?: Function): HTMLElement
11677 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11678 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11679 // callback is called when the tile has been loaded.
11680 createTile: function (coords, done) {
11681 var tile = document.createElement('img');
11682
11683 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11684 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11685
11686 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11687 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11688 }
11689
11690 /*
11691 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11692 http://www.w3.org/TR/WCAG20-TECHS/H67
11693 */
11694 tile.alt = '';
11695
11696 /*
11697 Set role="presentation" to force screen readers to ignore this
11698 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11699 */
11700 tile.setAttribute('role', 'presentation');
11701
11702 tile.src = this.getTileUrl(coords);
11703
11704 return tile;
11705 },
11706
11707 // @section Extension methods
11708 // @uninheritable
11709 // Layers extending `TileLayer` might reimplement the following method.
11710 // @method getTileUrl(coords: Object): String
11711 // Called only internally, returns the URL for a tile given its coordinates.
11712 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11713 getTileUrl: function (coords) {
11714 var data = {
11715 r: retina ? '@2x' : '',
11716 s: this._getSubdomain(coords),
11717 x: coords.x,
11718 y: coords.y,
11719 z: this._getZoomForUrl()
11720 };
11721 if (this._map && !this._map.options.crs.infinite) {
11722 var invertedY = this._globalTileRange.max.y - coords.y;
11723 if (this.options.tms) {
11724 data['y'] = invertedY;
11725 }
11726 data['-y'] = invertedY;
11727 }
11728
11729 return template(this._url, extend(data, this.options));
11730 },
11731
11732 _tileOnLoad: function (done, tile) {
11733 // For https://github.com/Leaflet/Leaflet/issues/3332
11734 if (ielt9) {
11735 setTimeout(bind(done, this, null, tile), 0);
11736 } else {
11737 done(null, tile);
11738 }
11739 },
11740
11741 _tileOnError: function (done, tile, e) {
11742 var errorUrl = this.options.errorTileUrl;
11743 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11744 tile.src = errorUrl;
11745 }
11746 done(e, tile);
11747 },
11748
11749 _onTileRemove: function (e) {
11750 e.tile.onload = null;
11751 },
11752
11753 _getZoomForUrl: function () {
11754 var zoom = this._tileZoom,
11755 maxZoom = this.options.maxZoom,
11756 zoomReverse = this.options.zoomReverse,
11757 zoomOffset = this.options.zoomOffset;
11758
11759 if (zoomReverse) {
11760 zoom = maxZoom - zoom;
11761 }
11762
11763 return zoom + zoomOffset;
11764 },
11765
11766 _getSubdomain: function (tilePoint) {
11767 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11768 return this.options.subdomains[index];
11769 },
11770
11771 // stops loading all tiles in the background layer
11772 _abortLoading: function () {
11773 var i, tile;
11774 for (i in this._tiles) {
11775 if (this._tiles[i].coords.z !== this._tileZoom) {
11776 tile = this._tiles[i].el;
11777
11778 tile.onload = falseFn;
11779 tile.onerror = falseFn;
11780
11781 if (!tile.complete) {
11782 tile.src = emptyImageUrl;
11783 remove(tile);
11784 delete this._tiles[i];
11785 }
11786 }
11787 }
11788 },
11789
11790 _removeTile: function (key) {
11791 var tile = this._tiles[key];
11792 if (!tile) { return; }
11793
11794 // Cancels any pending http requests associated with the tile
11795 // unless we're on Android's stock browser,
11796 // see https://github.com/Leaflet/Leaflet/issues/137
11797 if (!androidStock) {
11798 tile.el.setAttribute('src', emptyImageUrl);
11799 }
11800
11801 return GridLayer.prototype._removeTile.call(this, key);
11802 },
11803
11804 _tileReady: function (coords, err, tile) {
11805 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11806 return;
11807 }
11808
11809 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11810 }
11811 });
11812
11813
11814 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11815 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11816
11817 function tileLayer(url, options) {
11818 return new TileLayer(url, options);
11819 }
11820
11821 /*
11822 * @class TileLayer.WMS
11823 * @inherits TileLayer
11824 * @aka L.TileLayer.WMS
11825 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11826 *
11827 * @example
11828 *
11829 * ```js
11830 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11831 * layers: 'nexrad-n0r-900913',
11832 * format: 'image/png',
11833 * transparent: true,
11834 * attribution: "Weather data © 2012 IEM Nexrad"
11835 * });
11836 * ```
11837 */
11838
11839 var TileLayerWMS = TileLayer.extend({
11840
11841 // @section
11842 // @aka TileLayer.WMS options
11843 // If any custom options not documented here are used, they will be sent to the
11844 // WMS server as extra parameters in each request URL. This can be useful for
11845 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11846 defaultWmsParams: {
11847 service: 'WMS',
11848 request: 'GetMap',
11849
11850 // @option layers: String = ''
11851 // **(required)** Comma-separated list of WMS layers to show.
11852 layers: '',
11853
11854 // @option styles: String = ''
11855 // Comma-separated list of WMS styles.
11856 styles: '',
11857
11858 // @option format: String = 'image/jpeg'
11859 // WMS image format (use `'image/png'` for layers with transparency).
11860 format: 'image/jpeg',
11861
11862 // @option transparent: Boolean = false
11863 // If `true`, the WMS service will return images with transparency.
11864 transparent: false,
11865
11866 // @option version: String = '1.1.1'
11867 // Version of the WMS service to use
11868 version: '1.1.1'
11869 },
11870
11871 options: {
11872 // @option crs: CRS = null
11873 // Coordinate Reference System to use for the WMS requests, defaults to
11874 // map CRS. Don't change this if you're not sure what it means.
11875 crs: null,
11876
11877 // @option uppercase: Boolean = false
11878 // If `true`, WMS request parameter keys will be uppercase.
11879 uppercase: false
11880 },
11881
11882 initialize: function (url, options) {
11883
11884 this._url = url;
11885
11886 var wmsParams = extend({}, this.defaultWmsParams);
11887
11888 // all keys that are not TileLayer options go to WMS params
11889 for (var i in options) {
11890 if (!(i in this.options)) {
11891 wmsParams[i] = options[i];
11892 }
11893 }
11894
11895 options = setOptions(this, options);
11896
11897 var realRetina = options.detectRetina && retina ? 2 : 1;
11898 var tileSize = this.getTileSize();
11899 wmsParams.width = tileSize.x * realRetina;
11900 wmsParams.height = tileSize.y * realRetina;
11901
11902 this.wmsParams = wmsParams;
11903 },
11904
11905 onAdd: function (map) {
11906
11907 this._crs = this.options.crs || map.options.crs;
11908 this._wmsVersion = parseFloat(this.wmsParams.version);
11909
11910 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11911 this.wmsParams[projectionKey] = this._crs.code;
11912
11913 TileLayer.prototype.onAdd.call(this, map);
11914 },
11915
11916 getTileUrl: function (coords) {
11917
11918 var tileBounds = this._tileCoordsToNwSe(coords),
11919 crs = this._crs,
11920 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11921 min = bounds.min,
11922 max = bounds.max,
11923 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11924 [min.y, min.x, max.y, max.x] :
11925 [min.x, min.y, max.x, max.y]).join(','),
11926 url = TileLayer.prototype.getTileUrl.call(this, coords);
11927 return url +
11928 getParamString(this.wmsParams, url, this.options.uppercase) +
11929 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11930 },
11931
11932 // @method setParams(params: Object, noRedraw?: Boolean): this
11933 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11934 setParams: function (params, noRedraw) {
11935
11936 extend(this.wmsParams, params);
11937
11938 if (!noRedraw) {
11939 this.redraw();
11940 }
11941
11942 return this;
11943 }
11944 });
11945
11946
11947 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11948 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11949 function tileLayerWMS(url, options) {
11950 return new TileLayerWMS(url, options);
11951 }
11952
11953 TileLayer.WMS = TileLayerWMS;
11954 tileLayer.wms = tileLayerWMS;
11955
11956 /*
11957 * @class Renderer
11958 * @inherits Layer
11959 * @aka L.Renderer
11960 *
11961 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11962 * DOM container of the renderer, its bounds, and its zoom animation.
11963 *
11964 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11965 * itself can be added or removed to the map. All paths use a renderer, which can
11966 * be implicit (the map will decide the type of renderer and use it automatically)
11967 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11968 *
11969 * Do not use this class directly, use `SVG` and `Canvas` instead.
11970 *
11971 * @event update: Event
11972 * Fired when the renderer updates its bounds, center and zoom, for example when
11973 * its map has moved
11974 */
11975
11976 var Renderer = Layer.extend({
11977
11978 // @section
11979 // @aka Renderer options
11980 options: {
11981 // @option padding: Number = 0.1
11982 // How much to extend the clip area around the map view (relative to its size)
11983 // e.g. 0.1 would be 10% of map view in each direction
11984 padding: 0.1,
11985
11986 // @option tolerance: Number = 0
11987 // How much to extend click tolerance round a path/object on the map
11988 tolerance : 0
11989 },
11990
11991 initialize: function (options) {
11992 setOptions(this, options);
11993 stamp(this);
11994 this._layers = this._layers || {};
11995 },
11996
11997 onAdd: function () {
11998 if (!this._container) {
11999 this._initContainer(); // defined by renderer implementations
12000
12001 if (this._zoomAnimated) {
12002 addClass(this._container, 'leaflet-zoom-animated');
12003 }
12004 }
12005
12006 this.getPane().appendChild(this._container);
12007 this._update();
12008 this.on('update', this._updatePaths, this);
12009 },
12010
12011 onRemove: function () {
12012 this.off('update', this._updatePaths, this);
12013 this._destroyContainer();
12014 },
12015
12016 getEvents: function () {
12017 var events = {
12018 viewreset: this._reset,
12019 zoom: this._onZoom,
12020 moveend: this._update,
12021 zoomend: this._onZoomEnd
12022 };
12023 if (this._zoomAnimated) {
12024 events.zoomanim = this._onAnimZoom;
12025 }
12026 return events;
12027 },
12028
12029 _onAnimZoom: function (ev) {
12030 this._updateTransform(ev.center, ev.zoom);
12031 },
12032
12033 _onZoom: function () {
12034 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12035 },
12036
12037 _updateTransform: function (center, zoom) {
12038 var scale = this._map.getZoomScale(zoom, this._zoom),
12039 position = getPosition(this._container),
12040 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12041 currentCenterPoint = this._map.project(this._center, zoom),
12042 destCenterPoint = this._map.project(center, zoom),
12043 centerOffset = destCenterPoint.subtract(currentCenterPoint),
12044
12045 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12046
12047 if (any3d) {
12048 setTransform(this._container, topLeftOffset, scale);
12049 } else {
12050 setPosition(this._container, topLeftOffset);
12051 }
12052 },
12053
12054 _reset: function () {
12055 this._update();
12056 this._updateTransform(this._center, this._zoom);
12057
12058 for (var id in this._layers) {
12059 this._layers[id]._reset();
12060 }
12061 },
12062
12063 _onZoomEnd: function () {
12064 for (var id in this._layers) {
12065 this._layers[id]._project();
12066 }
12067 },
12068
12069 _updatePaths: function () {
12070 for (var id in this._layers) {
12071 this._layers[id]._update();
12072 }
12073 },
12074
12075 _update: function () {
12076 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12077 // Subclasses are responsible of firing the 'update' event.
12078 var p = this.options.padding,
12079 size = this._map.getSize(),
12080 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12081
12082 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12083
12084 this._center = this._map.getCenter();
12085 this._zoom = this._map.getZoom();
12086 }
12087 });
12088
12089 /*
12090 * @class Canvas
12091 * @inherits Renderer
12092 * @aka L.Canvas
12093 *
12094 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12095 * Inherits `Renderer`.
12096 *
12097 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12098 * available in all web browsers, notably IE8, and overlapping geometries might
12099 * not display properly in some edge cases.
12100 *
12101 * @example
12102 *
12103 * Use Canvas by default for all paths in the map:
12104 *
12105 * ```js
12106 * var map = L.map('map', {
12107 * renderer: L.canvas()
12108 * });
12109 * ```
12110 *
12111 * Use a Canvas renderer with extra padding for specific vector geometries:
12112 *
12113 * ```js
12114 * var map = L.map('map');
12115 * var myRenderer = L.canvas({ padding: 0.5 });
12116 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12117 * var circle = L.circle( center, { renderer: myRenderer } );
12118 * ```
12119 */
12120
12121 var Canvas = Renderer.extend({
12122 getEvents: function () {
12123 var events = Renderer.prototype.getEvents.call(this);
12124 events.viewprereset = this._onViewPreReset;
12125 return events;
12126 },
12127
12128 _onViewPreReset: function () {
12129 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12130 this._postponeUpdatePaths = true;
12131 },
12132
12133 onAdd: function () {
12134 Renderer.prototype.onAdd.call(this);
12135
12136 // Redraw vectors since canvas is cleared upon removal,
12137 // in case of removing the renderer itself from the map.
12138 this._draw();
12139 },
12140
12141 _initContainer: function () {
12142 var container = this._container = document.createElement('canvas');
12143
12144 on(container, 'mousemove', this._onMouseMove, this);
12145 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12146 on(container, 'mouseout', this._handleMouseOut, this);
12147
12148 this._ctx = container.getContext('2d');
12149 },
12150
12151 _destroyContainer: function () {
12152 cancelAnimFrame(this._redrawRequest);
12153 delete this._ctx;
12154 remove(this._container);
12155 off(this._container);
12156 delete this._container;
12157 },
12158
12159 _updatePaths: function () {
12160 if (this._postponeUpdatePaths) { return; }
12161
12162 var layer;
12163 this._redrawBounds = null;
12164 for (var id in this._layers) {
12165 layer = this._layers[id];
12166 layer._update();
12167 }
12168 this._redraw();
12169 },
12170
12171 _update: function () {
12172 if (this._map._animatingZoom && this._bounds) { return; }
12173
12174 Renderer.prototype._update.call(this);
12175
12176 var b = this._bounds,
12177 container = this._container,
12178 size = b.getSize(),
12179 m = retina ? 2 : 1;
12180
12181 setPosition(container, b.min);
12182
12183 // set canvas size (also clearing it); use double size on retina
12184 container.width = m * size.x;
12185 container.height = m * size.y;
12186 container.style.width = size.x + 'px';
12187 container.style.height = size.y + 'px';
12188
12189 if (retina) {
12190 this._ctx.scale(2, 2);
12191 }
12192
12193 // translate so we use the same path coordinates after canvas element moves
12194 this._ctx.translate(-b.min.x, -b.min.y);
12195
12196 // Tell paths to redraw themselves
12197 this.fire('update');
12198 },
12199
12200 _reset: function () {
12201 Renderer.prototype._reset.call(this);
12202
12203 if (this._postponeUpdatePaths) {
12204 this._postponeUpdatePaths = false;
12205 this._updatePaths();
12206 }
12207 },
12208
12209 _initPath: function (layer) {
12210 this._updateDashArray(layer);
12211 this._layers[stamp(layer)] = layer;
12212
12213 var order = layer._order = {
12214 layer: layer,
12215 prev: this._drawLast,
12216 next: null
12217 };
12218 if (this._drawLast) { this._drawLast.next = order; }
12219 this._drawLast = order;
12220 this._drawFirst = this._drawFirst || this._drawLast;
12221 },
12222
12223 _addPath: function (layer) {
12224 this._requestRedraw(layer);
12225 },
12226
12227 _removePath: function (layer) {
12228 var order = layer._order;
12229 var next = order.next;
12230 var prev = order.prev;
12231
12232 if (next) {
12233 next.prev = prev;
12234 } else {
12235 this._drawLast = prev;
12236 }
12237 if (prev) {
12238 prev.next = next;
12239 } else {
12240 this._drawFirst = next;
12241 }
12242
12243 delete layer._order;
12244
12245 delete this._layers[stamp(layer)];
12246
12247 this._requestRedraw(layer);
12248 },
12249
12250 _updatePath: function (layer) {
12251 // Redraw the union of the layer's old pixel
12252 // bounds and the new pixel bounds.
12253 this._extendRedrawBounds(layer);
12254 layer._project();
12255 layer._update();
12256 // The redraw will extend the redraw bounds
12257 // with the new pixel bounds.
12258 this._requestRedraw(layer);
12259 },
12260
12261 _updateStyle: function (layer) {
12262 this._updateDashArray(layer);
12263 this._requestRedraw(layer);
12264 },
12265
12266 _updateDashArray: function (layer) {
12267 if (typeof layer.options.dashArray === 'string') {
12268 var parts = layer.options.dashArray.split(/[, ]+/),
12269 dashArray = [],
12270 dashValue,
12271 i;
12272 for (i = 0; i < parts.length; i++) {
12273 dashValue = Number(parts[i]);
12274 // Ignore dash array containing invalid lengths
12275 if (isNaN(dashValue)) { return; }
12276 dashArray.push(dashValue);
12277 }
12278 layer.options._dashArray = dashArray;
12279 } else {
12280 layer.options._dashArray = layer.options.dashArray;
12281 }
12282 },
12283
12284 _requestRedraw: function (layer) {
12285 if (!this._map) { return; }
12286
12287 this._extendRedrawBounds(layer);
12288 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12289 },
12290
12291 _extendRedrawBounds: function (layer) {
12292 if (layer._pxBounds) {
12293 var padding = (layer.options.weight || 0) + 1;
12294 this._redrawBounds = this._redrawBounds || new Bounds();
12295 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12296 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12297 }
12298 },
12299
12300 _redraw: function () {
12301 this._redrawRequest = null;
12302
12303 if (this._redrawBounds) {
12304 this._redrawBounds.min._floor();
12305 this._redrawBounds.max._ceil();
12306 }
12307
12308 this._clear(); // clear layers in redraw bounds
12309 this._draw(); // draw layers
12310
12311 this._redrawBounds = null;
12312 },
12313
12314 _clear: function () {
12315 var bounds = this._redrawBounds;
12316 if (bounds) {
12317 var size = bounds.getSize();
12318 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12319 } else {
12320 this._ctx.save();
12321 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12322 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12323 this._ctx.restore();
12324 }
12325 },
12326
12327 _draw: function () {
12328 var layer, bounds = this._redrawBounds;
12329 this._ctx.save();
12330 if (bounds) {
12331 var size = bounds.getSize();
12332 this._ctx.beginPath();
12333 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12334 this._ctx.clip();
12335 }
12336
12337 this._drawing = true;
12338
12339 for (var order = this._drawFirst; order; order = order.next) {
12340 layer = order.layer;
12341 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12342 layer._updatePath();
12343 }
12344 }
12345
12346 this._drawing = false;
12347
12348 this._ctx.restore(); // Restore state before clipping.
12349 },
12350
12351 _updatePoly: function (layer, closed) {
12352 if (!this._drawing) { return; }
12353
12354 var i, j, len2, p,
12355 parts = layer._parts,
12356 len = parts.length,
12357 ctx = this._ctx;
12358
12359 if (!len) { return; }
12360
12361 ctx.beginPath();
12362
12363 for (i = 0; i < len; i++) {
12364 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12365 p = parts[i][j];
12366 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12367 }
12368 if (closed) {
12369 ctx.closePath();
12370 }
12371 }
12372
12373 this._fillStroke(ctx, layer);
12374
12375 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12376 },
12377
12378 _updateCircle: function (layer) {
12379
12380 if (!this._drawing || layer._empty()) { return; }
12381
12382 var p = layer._point,
12383 ctx = this._ctx,
12384 r = Math.max(Math.round(layer._radius), 1),
12385 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12386
12387 if (s !== 1) {
12388 ctx.save();
12389 ctx.scale(1, s);
12390 }
12391
12392 ctx.beginPath();
12393 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12394
12395 if (s !== 1) {
12396 ctx.restore();
12397 }
12398
12399 this._fillStroke(ctx, layer);
12400 },
12401
12402 _fillStroke: function (ctx, layer) {
12403 var options = layer.options;
12404
12405 if (options.fill) {
12406 ctx.globalAlpha = options.fillOpacity;
12407 ctx.fillStyle = options.fillColor || options.color;
12408 ctx.fill(options.fillRule || 'evenodd');
12409 }
12410
12411 if (options.stroke && options.weight !== 0) {
12412 if (ctx.setLineDash) {
12413 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12414 }
12415 ctx.globalAlpha = options.opacity;
12416 ctx.lineWidth = options.weight;
12417 ctx.strokeStyle = options.color;
12418 ctx.lineCap = options.lineCap;
12419 ctx.lineJoin = options.lineJoin;
12420 ctx.stroke();
12421 }
12422 },
12423
12424 // Canvas obviously doesn't have mouse events for individual drawn objects,
12425 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12426
12427 _onClick: function (e) {
12428 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12429
12430 for (var order = this._drawFirst; order; order = order.next) {
12431 layer = order.layer;
12432 if (layer.options.interactive && layer._containsPoint(point)) {
12433 if (!(e.type === 'click' || e.type !== 'preclick') || !this._map._draggableMoved(layer)) {
12434 clickedLayer = layer;
12435 }
12436 }
12437 }
12438 if (clickedLayer) {
12439 fakeStop(e);
12440 this._fireEvent([clickedLayer], e);
12441 }
12442 },
12443
12444 _onMouseMove: function (e) {
12445 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12446
12447 var point = this._map.mouseEventToLayerPoint(e);
12448 this._handleMouseHover(e, point);
12449 },
12450
12451
12452 _handleMouseOut: function (e) {
12453 var layer = this._hoveredLayer;
12454 if (layer) {
12455 // if we're leaving the layer, fire mouseout
12456 removeClass(this._container, 'leaflet-interactive');
12457 this._fireEvent([layer], e, 'mouseout');
12458 this._hoveredLayer = null;
12459 this._mouseHoverThrottled = false;
12460 }
12461 },
12462
12463 _handleMouseHover: function (e, point) {
12464 if (this._mouseHoverThrottled) {
12465 return;
12466 }
12467
12468 var layer, candidateHoveredLayer;
12469
12470 for (var order = this._drawFirst; order; order = order.next) {
12471 layer = order.layer;
12472 if (layer.options.interactive && layer._containsPoint(point)) {
12473 candidateHoveredLayer = layer;
12474 }
12475 }
12476
12477 if (candidateHoveredLayer !== this._hoveredLayer) {
12478 this._handleMouseOut(e);
12479
12480 if (candidateHoveredLayer) {
12481 addClass(this._container, 'leaflet-interactive'); // change cursor
12482 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12483 this._hoveredLayer = candidateHoveredLayer;
12484 }
12485 }
12486
12487 if (this._hoveredLayer) {
12488 this._fireEvent([this._hoveredLayer], e);
12489 }
12490
12491 this._mouseHoverThrottled = true;
12492 setTimeout(bind(function () {
12493 this._mouseHoverThrottled = false;
12494 }, this), 32);
12495 },
12496
12497 _fireEvent: function (layers, e, type) {
12498 this._map._fireDOMEvent(e, type || e.type, layers);
12499 },
12500
12501 _bringToFront: function (layer) {
12502 var order = layer._order;
12503
12504 if (!order) { return; }
12505
12506 var next = order.next;
12507 var prev = order.prev;
12508
12509 if (next) {
12510 next.prev = prev;
12511 } else {
12512 // Already last
12513 return;
12514 }
12515 if (prev) {
12516 prev.next = next;
12517 } else if (next) {
12518 // Update first entry unless this is the
12519 // single entry
12520 this._drawFirst = next;
12521 }
12522
12523 order.prev = this._drawLast;
12524 this._drawLast.next = order;
12525
12526 order.next = null;
12527 this._drawLast = order;
12528
12529 this._requestRedraw(layer);
12530 },
12531
12532 _bringToBack: function (layer) {
12533 var order = layer._order;
12534
12535 if (!order) { return; }
12536
12537 var next = order.next;
12538 var prev = order.prev;
12539
12540 if (prev) {
12541 prev.next = next;
12542 } else {
12543 // Already first
12544 return;
12545 }
12546 if (next) {
12547 next.prev = prev;
12548 } else if (prev) {
12549 // Update last entry unless this is the
12550 // single entry
12551 this._drawLast = prev;
12552 }
12553
12554 order.prev = null;
12555
12556 order.next = this._drawFirst;
12557 this._drawFirst.prev = order;
12558 this._drawFirst = order;
12559
12560 this._requestRedraw(layer);
12561 }
12562 });
12563
12564 // @factory L.canvas(options?: Renderer options)
12565 // Creates a Canvas renderer with the given options.
12566 function canvas$1(options) {
12567 return canvas ? new Canvas(options) : null;
12568 }
12569
12570 /*
12571 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12572 */
12573
12574
12575 var vmlCreate = (function () {
12576 try {
12577 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12578 return function (name) {
12579 return document.createElement('<lvml:' + name + ' class="lvml">');
12580 };
12581 } catch (e) {
12582 return function (name) {
12583 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12584 };
12585 }
12586 })();
12587
12588
12589 /*
12590 * @class SVG
12591 *
12592 *
12593 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12594 * with old versions of Internet Explorer.
12595 */
12596
12597 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12598 var vmlMixin = {
12599
12600 _initContainer: function () {
12601 this._container = create$1('div', 'leaflet-vml-container');
12602 },
12603
12604 _update: function () {
12605 if (this._map._animatingZoom) { return; }
12606 Renderer.prototype._update.call(this);
12607 this.fire('update');
12608 },
12609
12610 _initPath: function (layer) {
12611 var container = layer._container = vmlCreate('shape');
12612
12613 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12614
12615 container.coordsize = '1 1';
12616
12617 layer._path = vmlCreate('path');
12618 container.appendChild(layer._path);
12619
12620 this._updateStyle(layer);
12621 this._layers[stamp(layer)] = layer;
12622 },
12623
12624 _addPath: function (layer) {
12625 var container = layer._container;
12626 this._container.appendChild(container);
12627
12628 if (layer.options.interactive) {
12629 layer.addInteractiveTarget(container);
12630 }
12631 },
12632
12633 _removePath: function (layer) {
12634 var container = layer._container;
12635 remove(container);
12636 layer.removeInteractiveTarget(container);
12637 delete this._layers[stamp(layer)];
12638 },
12639
12640 _updateStyle: function (layer) {
12641 var stroke = layer._stroke,
12642 fill = layer._fill,
12643 options = layer.options,
12644 container = layer._container;
12645
12646 container.stroked = !!options.stroke;
12647 container.filled = !!options.fill;
12648
12649 if (options.stroke) {
12650 if (!stroke) {
12651 stroke = layer._stroke = vmlCreate('stroke');
12652 }
12653 container.appendChild(stroke);
12654 stroke.weight = options.weight + 'px';
12655 stroke.color = options.color;
12656 stroke.opacity = options.opacity;
12657
12658 if (options.dashArray) {
12659 stroke.dashStyle = isArray(options.dashArray) ?
12660 options.dashArray.join(' ') :
12661 options.dashArray.replace(/( *, *)/g, ' ');
12662 } else {
12663 stroke.dashStyle = '';
12664 }
12665 stroke.endcap = options.lineCap.replace('butt', 'flat');
12666 stroke.joinstyle = options.lineJoin;
12667
12668 } else if (stroke) {
12669 container.removeChild(stroke);
12670 layer._stroke = null;
12671 }
12672
12673 if (options.fill) {
12674 if (!fill) {
12675 fill = layer._fill = vmlCreate('fill');
12676 }
12677 container.appendChild(fill);
12678 fill.color = options.fillColor || options.color;
12679 fill.opacity = options.fillOpacity;
12680
12681 } else if (fill) {
12682 container.removeChild(fill);
12683 layer._fill = null;
12684 }
12685 },
12686
12687 _updateCircle: function (layer) {
12688 var p = layer._point.round(),
12689 r = Math.round(layer._radius),
12690 r2 = Math.round(layer._radiusY || r);
12691
12692 this._setPath(layer, layer._empty() ? 'M0 0' :
12693 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12694 },
12695
12696 _setPath: function (layer, path) {
12697 layer._path.v = path;
12698 },
12699
12700 _bringToFront: function (layer) {
12701 toFront(layer._container);
12702 },
12703
12704 _bringToBack: function (layer) {
12705 toBack(layer._container);
12706 }
12707 };
12708
12709 var create$2 = vml ? vmlCreate : svgCreate;
12710
12711 /*
12712 * @class SVG
12713 * @inherits Renderer
12714 * @aka L.SVG
12715 *
12716 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12717 * Inherits `Renderer`.
12718 *
12719 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12720 * available in all web browsers, notably Android 2.x and 3.x.
12721 *
12722 * Although SVG is not available on IE7 and IE8, these browsers support
12723 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12724 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12725 * this case.
12726 *
12727 * @example
12728 *
12729 * Use SVG by default for all paths in the map:
12730 *
12731 * ```js
12732 * var map = L.map('map', {
12733 * renderer: L.svg()
12734 * });
12735 * ```
12736 *
12737 * Use a SVG renderer with extra padding for specific vector geometries:
12738 *
12739 * ```js
12740 * var map = L.map('map');
12741 * var myRenderer = L.svg({ padding: 0.5 });
12742 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12743 * var circle = L.circle( center, { renderer: myRenderer } );
12744 * ```
12745 */
12746
12747 var SVG = Renderer.extend({
12748
12749 getEvents: function () {
12750 var events = Renderer.prototype.getEvents.call(this);
12751 events.zoomstart = this._onZoomStart;
12752 return events;
12753 },
12754
12755 _initContainer: function () {
12756 this._container = create$2('svg');
12757
12758 // makes it possible to click through svg root; we'll reset it back in individual paths
12759 this._container.setAttribute('pointer-events', 'none');
12760
12761 this._rootGroup = create$2('g');
12762 this._container.appendChild(this._rootGroup);
12763 },
12764
12765 _destroyContainer: function () {
12766 remove(this._container);
12767 off(this._container);
12768 delete this._container;
12769 delete this._rootGroup;
12770 delete this._svgSize;
12771 },
12772
12773 _onZoomStart: function () {
12774 // Drag-then-pinch interactions might mess up the center and zoom.
12775 // In this case, the easiest way to prevent this is re-do the renderer
12776 // bounds and padding when the zooming starts.
12777 this._update();
12778 },
12779
12780 _update: function () {
12781 if (this._map._animatingZoom && this._bounds) { return; }
12782
12783 Renderer.prototype._update.call(this);
12784
12785 var b = this._bounds,
12786 size = b.getSize(),
12787 container = this._container;
12788
12789 // set size of svg-container if changed
12790 if (!this._svgSize || !this._svgSize.equals(size)) {
12791 this._svgSize = size;
12792 container.setAttribute('width', size.x);
12793 container.setAttribute('height', size.y);
12794 }
12795
12796 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12797 setPosition(container, b.min);
12798 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12799
12800 this.fire('update');
12801 },
12802
12803 // methods below are called by vector layers implementations
12804
12805 _initPath: function (layer) {
12806 var path = layer._path = create$2('path');
12807
12808 // @namespace Path
12809 // @option className: String = null
12810 // Custom class name set on an element. Only for SVG renderer.
12811 if (layer.options.className) {
12812 addClass(path, layer.options.className);
12813 }
12814
12815 if (layer.options.interactive) {
12816 addClass(path, 'leaflet-interactive');
12817 }
12818
12819 this._updateStyle(layer);
12820 this._layers[stamp(layer)] = layer;
12821 },
12822
12823 _addPath: function (layer) {
12824 if (!this._rootGroup) { this._initContainer(); }
12825 this._rootGroup.appendChild(layer._path);
12826 layer.addInteractiveTarget(layer._path);
12827 },
12828
12829 _removePath: function (layer) {
12830 remove(layer._path);
12831 layer.removeInteractiveTarget(layer._path);
12832 delete this._layers[stamp(layer)];
12833 },
12834
12835 _updatePath: function (layer) {
12836 layer._project();
12837 layer._update();
12838 },
12839
12840 _updateStyle: function (layer) {
12841 var path = layer._path,
12842 options = layer.options;
12843
12844 if (!path) { return; }
12845
12846 if (options.stroke) {
12847 path.setAttribute('stroke', options.color);
12848 path.setAttribute('stroke-opacity', options.opacity);
12849 path.setAttribute('stroke-width', options.weight);
12850 path.setAttribute('stroke-linecap', options.lineCap);
12851 path.setAttribute('stroke-linejoin', options.lineJoin);
12852
12853 if (options.dashArray) {
12854 path.setAttribute('stroke-dasharray', options.dashArray);
12855 } else {
12856 path.removeAttribute('stroke-dasharray');
12857 }
12858
12859 if (options.dashOffset) {
12860 path.setAttribute('stroke-dashoffset', options.dashOffset);
12861 } else {
12862 path.removeAttribute('stroke-dashoffset');
12863 }
12864 } else {
12865 path.setAttribute('stroke', 'none');
12866 }
12867
12868 if (options.fill) {
12869 path.setAttribute('fill', options.fillColor || options.color);
12870 path.setAttribute('fill-opacity', options.fillOpacity);
12871 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12872 } else {
12873 path.setAttribute('fill', 'none');
12874 }
12875 },
12876
12877 _updatePoly: function (layer, closed) {
12878 this._setPath(layer, pointsToPath(layer._parts, closed));
12879 },
12880
12881 _updateCircle: function (layer) {
12882 var p = layer._point,
12883 r = Math.max(Math.round(layer._radius), 1),
12884 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12885 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12886
12887 // drawing a circle with two half-arcs
12888 var d = layer._empty() ? 'M0 0' :
12889 'M' + (p.x - r) + ',' + p.y +
12890 arc + (r * 2) + ',0 ' +
12891 arc + (-r * 2) + ',0 ';
12892
12893 this._setPath(layer, d);
12894 },
12895
12896 _setPath: function (layer, path) {
12897 layer._path.setAttribute('d', path);
12898 },
12899
12900 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12901 _bringToFront: function (layer) {
12902 toFront(layer._path);
12903 },
12904
12905 _bringToBack: function (layer) {
12906 toBack(layer._path);
12907 }
12908 });
12909
12910 if (vml) {
12911 SVG.include(vmlMixin);
12912 }
12913
12914 // @namespace SVG
12915 // @factory L.svg(options?: Renderer options)
12916 // Creates a SVG renderer with the given options.
12917 function svg$1(options) {
12918 return svg || vml ? new SVG(options) : null;
12919 }
12920
12921 Map.include({
12922 // @namespace Map; @method getRenderer(layer: Path): Renderer
12923 // Returns the instance of `Renderer` that should be used to render the given
12924 // `Path`. It will ensure that the `renderer` options of the map and paths
12925 // are respected, and that the renderers do exist on the map.
12926 getRenderer: function (layer) {
12927 // @namespace Path; @option renderer: Renderer
12928 // Use this specific instance of `Renderer` for this path. Takes
12929 // precedence over the map's [default renderer](#map-renderer).
12930 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12931
12932 if (!renderer) {
12933 renderer = this._renderer = this._createRenderer();
12934 }
12935
12936 if (!this.hasLayer(renderer)) {
12937 this.addLayer(renderer);
12938 }
12939 return renderer;
12940 },
12941
12942 _getPaneRenderer: function (name) {
12943 if (name === 'overlayPane' || name === undefined) {
12944 return false;
12945 }
12946
12947 var renderer = this._paneRenderers[name];
12948 if (renderer === undefined) {
12949 renderer = this._createRenderer({pane: name});
12950 this._paneRenderers[name] = renderer;
12951 }
12952 return renderer;
12953 },
12954
12955 _createRenderer: function (options) {
12956 // @namespace Map; @option preferCanvas: Boolean = false
12957 // Whether `Path`s should be rendered on a `Canvas` renderer.
12958 // By default, all `Path`s are rendered in a `SVG` renderer.
12959 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12960 }
12961 });
12962
12963 /*
12964 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12965 */
12966
12967 /*
12968 * @class Rectangle
12969 * @aka L.Rectangle
12970 * @inherits Polygon
12971 *
12972 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12973 *
12974 * @example
12975 *
12976 * ```js
12977 * // define rectangle geographical bounds
12978 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12979 *
12980 * // create an orange rectangle
12981 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12982 *
12983 * // zoom the map to the rectangle bounds
12984 * map.fitBounds(bounds);
12985 * ```
12986 *
12987 */
12988
12989
12990 var Rectangle = Polygon.extend({
12991 initialize: function (latLngBounds, options) {
12992 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12993 },
12994
12995 // @method setBounds(latLngBounds: LatLngBounds): this
12996 // Redraws the rectangle with the passed bounds.
12997 setBounds: function (latLngBounds) {
12998 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12999 },
13000
13001 _boundsToLatLngs: function (latLngBounds) {
13002 latLngBounds = toLatLngBounds(latLngBounds);
13003 return [
13004 latLngBounds.getSouthWest(),
13005 latLngBounds.getNorthWest(),
13006 latLngBounds.getNorthEast(),
13007 latLngBounds.getSouthEast()
13008 ];
13009 }
13010 });
13011
13012
13013 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13014 function rectangle(latLngBounds, options) {
13015 return new Rectangle(latLngBounds, options);
13016 }
13017
13018 SVG.create = create$2;
13019 SVG.pointsToPath = pointsToPath;
13020
13021 GeoJSON.geometryToLayer = geometryToLayer;
13022 GeoJSON.coordsToLatLng = coordsToLatLng;
13023 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13024 GeoJSON.latLngToCoords = latLngToCoords;
13025 GeoJSON.latLngsToCoords = latLngsToCoords;
13026 GeoJSON.getFeature = getFeature;
13027 GeoJSON.asFeature = asFeature;
13028
13029 /*
13030 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13031 * (zoom to a selected bounding box), enabled by default.
13032 */
13033
13034 // @namespace Map
13035 // @section Interaction Options
13036 Map.mergeOptions({
13037 // @option boxZoom: Boolean = true
13038 // Whether the map can be zoomed to a rectangular area specified by
13039 // dragging the mouse while pressing the shift key.
13040 boxZoom: true
13041 });
13042
13043 var BoxZoom = Handler.extend({
13044 initialize: function (map) {
13045 this._map = map;
13046 this._container = map._container;
13047 this._pane = map._panes.overlayPane;
13048 this._resetStateTimeout = 0;
13049 map.on('unload', this._destroy, this);
13050 },
13051
13052 addHooks: function () {
13053 on(this._container, 'mousedown', this._onMouseDown, this);
13054 },
13055
13056 removeHooks: function () {
13057 off(this._container, 'mousedown', this._onMouseDown, this);
13058 },
13059
13060 moved: function () {
13061 return this._moved;
13062 },
13063
13064 _destroy: function () {
13065 remove(this._pane);
13066 delete this._pane;
13067 },
13068
13069 _resetState: function () {
13070 this._resetStateTimeout = 0;
13071 this._moved = false;
13072 },
13073
13074 _clearDeferredResetState: function () {
13075 if (this._resetStateTimeout !== 0) {
13076 clearTimeout(this._resetStateTimeout);
13077 this._resetStateTimeout = 0;
13078 }
13079 },
13080
13081 _onMouseDown: function (e) {
13082 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13083
13084 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13085 // will interrupt the interaction and orphan a box element in the container.
13086 this._clearDeferredResetState();
13087 this._resetState();
13088
13089 disableTextSelection();
13090 disableImageDrag();
13091
13092 this._startPoint = this._map.mouseEventToContainerPoint(e);
13093
13094 on(document, {
13095 contextmenu: stop,
13096 mousemove: this._onMouseMove,
13097 mouseup: this._onMouseUp,
13098 keydown: this._onKeyDown
13099 }, this);
13100 },
13101
13102 _onMouseMove: function (e) {
13103 if (!this._moved) {
13104 this._moved = true;
13105
13106 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13107 addClass(this._container, 'leaflet-crosshair');
13108
13109 this._map.fire('boxzoomstart');
13110 }
13111
13112 this._point = this._map.mouseEventToContainerPoint(e);
13113
13114 var bounds = new Bounds(this._point, this._startPoint),
13115 size = bounds.getSize();
13116
13117 setPosition(this._box, bounds.min);
13118
13119 this._box.style.width = size.x + 'px';
13120 this._box.style.height = size.y + 'px';
13121 },
13122
13123 _finish: function () {
13124 if (this._moved) {
13125 remove(this._box);
13126 removeClass(this._container, 'leaflet-crosshair');
13127 }
13128
13129 enableTextSelection();
13130 enableImageDrag();
13131
13132 off(document, {
13133 contextmenu: stop,
13134 mousemove: this._onMouseMove,
13135 mouseup: this._onMouseUp,
13136 keydown: this._onKeyDown
13137 }, this);
13138 },
13139
13140 _onMouseUp: function (e) {
13141 if ((e.which !== 1) && (e.button !== 1)) { return; }
13142
13143 this._finish();
13144
13145 if (!this._moved) { return; }
13146 // Postpone to next JS tick so internal click event handling
13147 // still see it as "moved".
13148 this._clearDeferredResetState();
13149 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13150
13151 var bounds = new LatLngBounds(
13152 this._map.containerPointToLatLng(this._startPoint),
13153 this._map.containerPointToLatLng(this._point));
13154
13155 this._map
13156 .fitBounds(bounds)
13157 .fire('boxzoomend', {boxZoomBounds: bounds});
13158 },
13159
13160 _onKeyDown: function (e) {
13161 if (e.keyCode === 27) {
13162 this._finish();
13163 }
13164 }
13165 });
13166
13167 // @section Handlers
13168 // @property boxZoom: Handler
13169 // Box (shift-drag with mouse) zoom handler.
13170 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13171
13172 /*
13173 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13174 */
13175
13176 // @namespace Map
13177 // @section Interaction Options
13178
13179 Map.mergeOptions({
13180 // @option doubleClickZoom: Boolean|String = true
13181 // Whether the map can be zoomed in by double clicking on it and
13182 // zoomed out by double clicking while holding shift. If passed
13183 // `'center'`, double-click zoom will zoom to the center of the
13184 // view regardless of where the mouse was.
13185 doubleClickZoom: true
13186 });
13187
13188 var DoubleClickZoom = Handler.extend({
13189 addHooks: function () {
13190 this._map.on('dblclick', this._onDoubleClick, this);
13191 },
13192
13193 removeHooks: function () {
13194 this._map.off('dblclick', this._onDoubleClick, this);
13195 },
13196
13197 _onDoubleClick: function (e) {
13198 var map = this._map,
13199 oldZoom = map.getZoom(),
13200 delta = map.options.zoomDelta,
13201 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13202
13203 if (map.options.doubleClickZoom === 'center') {
13204 map.setZoom(zoom);
13205 } else {
13206 map.setZoomAround(e.containerPoint, zoom);
13207 }
13208 }
13209 });
13210
13211 // @section Handlers
13212 //
13213 // Map properties include interaction handlers that allow you to control
13214 // interaction behavior in runtime, enabling or disabling certain features such
13215 // as dragging or touch zoom (see `Handler` methods). For example:
13216 //
13217 // ```js
13218 // map.doubleClickZoom.disable();
13219 // ```
13220 //
13221 // @property doubleClickZoom: Handler
13222 // Double click zoom handler.
13223 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13224
13225 /*
13226 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13227 */
13228
13229 // @namespace Map
13230 // @section Interaction Options
13231 Map.mergeOptions({
13232 // @option dragging: Boolean = true
13233 // Whether the map be draggable with mouse/touch or not.
13234 dragging: true,
13235
13236 // @section Panning Inertia Options
13237 // @option inertia: Boolean = *
13238 // If enabled, panning of the map will have an inertia effect where
13239 // the map builds momentum while dragging and continues moving in
13240 // the same direction for some time. Feels especially nice on touch
13241 // devices. Enabled by default unless running on old Android devices.
13242 inertia: !android23,
13243
13244 // @option inertiaDeceleration: Number = 3000
13245 // The rate with which the inertial movement slows down, in pixels/second².
13246 inertiaDeceleration: 3400, // px/s^2
13247
13248 // @option inertiaMaxSpeed: Number = Infinity
13249 // Max speed of the inertial movement, in pixels/second.
13250 inertiaMaxSpeed: Infinity, // px/s
13251
13252 // @option easeLinearity: Number = 0.2
13253 easeLinearity: 0.2,
13254
13255 // TODO refactor, move to CRS
13256 // @option worldCopyJump: Boolean = false
13257 // With this option enabled, the map tracks when you pan to another "copy"
13258 // of the world and seamlessly jumps to the original one so that all overlays
13259 // like markers and vector layers are still visible.
13260 worldCopyJump: false,
13261
13262 // @option maxBoundsViscosity: Number = 0.0
13263 // If `maxBounds` is set, this option will control how solid the bounds
13264 // are when dragging the map around. The default value of `0.0` allows the
13265 // user to drag outside the bounds at normal speed, higher values will
13266 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13267 // solid, preventing the user from dragging outside the bounds.
13268 maxBoundsViscosity: 0.0
13269 });
13270
13271 var Drag = Handler.extend({
13272 addHooks: function () {
13273 if (!this._draggable) {
13274 var map = this._map;
13275
13276 this._draggable = new Draggable(map._mapPane, map._container);
13277
13278 this._draggable.on({
13279 dragstart: this._onDragStart,
13280 drag: this._onDrag,
13281 dragend: this._onDragEnd
13282 }, this);
13283
13284 this._draggable.on('predrag', this._onPreDragLimit, this);
13285 if (map.options.worldCopyJump) {
13286 this._draggable.on('predrag', this._onPreDragWrap, this);
13287 map.on('zoomend', this._onZoomEnd, this);
13288
13289 map.whenReady(this._onZoomEnd, this);
13290 }
13291 }
13292 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13293 this._draggable.enable();
13294 this._positions = [];
13295 this._times = [];
13296 },
13297
13298 removeHooks: function () {
13299 removeClass(this._map._container, 'leaflet-grab');
13300 removeClass(this._map._container, 'leaflet-touch-drag');
13301 this._draggable.disable();
13302 },
13303
13304 moved: function () {
13305 return this._draggable && this._draggable._moved;
13306 },
13307
13308 moving: function () {
13309 return this._draggable && this._draggable._moving;
13310 },
13311
13312 _onDragStart: function () {
13313 var map = this._map;
13314
13315 map._stop();
13316 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13317 var bounds = toLatLngBounds(this._map.options.maxBounds);
13318
13319 this._offsetLimit = toBounds(
13320 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13321 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13322 .add(this._map.getSize()));
13323
13324 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13325 } else {
13326 this._offsetLimit = null;
13327 }
13328
13329 map
13330 .fire('movestart')
13331 .fire('dragstart');
13332
13333 if (map.options.inertia) {
13334 this._positions = [];
13335 this._times = [];
13336 }
13337 },
13338
13339 _onDrag: function (e) {
13340 if (this._map.options.inertia) {
13341 var time = this._lastTime = +new Date(),
13342 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13343
13344 this._positions.push(pos);
13345 this._times.push(time);
13346
13347 this._prunePositions(time);
13348 }
13349
13350 this._map
13351 .fire('move', e)
13352 .fire('drag', e);
13353 },
13354
13355 _prunePositions: function (time) {
13356 while (this._positions.length > 1 && time - this._times[0] > 50) {
13357 this._positions.shift();
13358 this._times.shift();
13359 }
13360 },
13361
13362 _onZoomEnd: function () {
13363 var pxCenter = this._map.getSize().divideBy(2),
13364 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13365
13366 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13367 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13368 },
13369
13370 _viscousLimit: function (value, threshold) {
13371 return value - (value - threshold) * this._viscosity;
13372 },
13373
13374 _onPreDragLimit: function () {
13375 if (!this._viscosity || !this._offsetLimit) { return; }
13376
13377 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13378
13379 var limit = this._offsetLimit;
13380 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13381 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13382 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13383 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13384
13385 this._draggable._newPos = this._draggable._startPos.add(offset);
13386 },
13387
13388 _onPreDragWrap: function () {
13389 // TODO refactor to be able to adjust map pane position after zoom
13390 var worldWidth = this._worldWidth,
13391 halfWidth = Math.round(worldWidth / 2),
13392 dx = this._initialWorldOffset,
13393 x = this._draggable._newPos.x,
13394 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13395 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13396 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13397
13398 this._draggable._absPos = this._draggable._newPos.clone();
13399 this._draggable._newPos.x = newX;
13400 },
13401
13402 _onDragEnd: function (e) {
13403 var map = this._map,
13404 options = map.options,
13405
13406 noInertia = !options.inertia || this._times.length < 2;
13407
13408 map.fire('dragend', e);
13409
13410 if (noInertia) {
13411 map.fire('moveend');
13412
13413 } else {
13414 this._prunePositions(+new Date());
13415
13416 var direction = this._lastPos.subtract(this._positions[0]),
13417 duration = (this._lastTime - this._times[0]) / 1000,
13418 ease = options.easeLinearity,
13419
13420 speedVector = direction.multiplyBy(ease / duration),
13421 speed = speedVector.distanceTo([0, 0]),
13422
13423 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13424 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13425
13426 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13427 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13428
13429 if (!offset.x && !offset.y) {
13430 map.fire('moveend');
13431
13432 } else {
13433 offset = map._limitOffset(offset, map.options.maxBounds);
13434
13435 requestAnimFrame(function () {
13436 map.panBy(offset, {
13437 duration: decelerationDuration,
13438 easeLinearity: ease,
13439 noMoveStart: true,
13440 animate: true
13441 });
13442 });
13443 }
13444 }
13445 }
13446 });
13447
13448 // @section Handlers
13449 // @property dragging: Handler
13450 // Map dragging handler (by both mouse and touch).
13451 Map.addInitHook('addHandler', 'dragging', Drag);
13452
13453 /*
13454 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13455 */
13456
13457 // @namespace Map
13458 // @section Keyboard Navigation Options
13459 Map.mergeOptions({
13460 // @option keyboard: Boolean = true
13461 // Makes the map focusable and allows users to navigate the map with keyboard
13462 // arrows and `+`/`-` keys.
13463 keyboard: true,
13464
13465 // @option keyboardPanDelta: Number = 80
13466 // Amount of pixels to pan when pressing an arrow key.
13467 keyboardPanDelta: 80
13468 });
13469
13470 var Keyboard = Handler.extend({
13471
13472 keyCodes: {
13473 left: [37],
13474 right: [39],
13475 down: [40],
13476 up: [38],
13477 zoomIn: [187, 107, 61, 171],
13478 zoomOut: [189, 109, 54, 173]
13479 },
13480
13481 initialize: function (map) {
13482 this._map = map;
13483
13484 this._setPanDelta(map.options.keyboardPanDelta);
13485 this._setZoomDelta(map.options.zoomDelta);
13486 },
13487
13488 addHooks: function () {
13489 var container = this._map._container;
13490
13491 // make the container focusable by tabbing
13492 if (container.tabIndex <= 0) {
13493 container.tabIndex = '0';
13494 }
13495
13496 on(container, {
13497 focus: this._onFocus,
13498 blur: this._onBlur,
13499 mousedown: this._onMouseDown
13500 }, this);
13501
13502 this._map.on({
13503 focus: this._addHooks,
13504 blur: this._removeHooks
13505 }, this);
13506 },
13507
13508 removeHooks: function () {
13509 this._removeHooks();
13510
13511 off(this._map._container, {
13512 focus: this._onFocus,
13513 blur: this._onBlur,
13514 mousedown: this._onMouseDown
13515 }, this);
13516
13517 this._map.off({
13518 focus: this._addHooks,
13519 blur: this._removeHooks
13520 }, this);
13521 },
13522
13523 _onMouseDown: function () {
13524 if (this._focused) { return; }
13525
13526 var body = document.body,
13527 docEl = document.documentElement,
13528 top = body.scrollTop || docEl.scrollTop,
13529 left = body.scrollLeft || docEl.scrollLeft;
13530
13531 this._map._container.focus();
13532
13533 window.scrollTo(left, top);
13534 },
13535
13536 _onFocus: function () {
13537 this._focused = true;
13538 this._map.fire('focus');
13539 },
13540
13541 _onBlur: function () {
13542 this._focused = false;
13543 this._map.fire('blur');
13544 },
13545
13546 _setPanDelta: function (panDelta) {
13547 var keys = this._panKeys = {},
13548 codes = this.keyCodes,
13549 i, len;
13550
13551 for (i = 0, len = codes.left.length; i < len; i++) {
13552 keys[codes.left[i]] = [-1 * panDelta, 0];
13553 }
13554 for (i = 0, len = codes.right.length; i < len; i++) {
13555 keys[codes.right[i]] = [panDelta, 0];
13556 }
13557 for (i = 0, len = codes.down.length; i < len; i++) {
13558 keys[codes.down[i]] = [0, panDelta];
13559 }
13560 for (i = 0, len = codes.up.length; i < len; i++) {
13561 keys[codes.up[i]] = [0, -1 * panDelta];
13562 }
13563 },
13564
13565 _setZoomDelta: function (zoomDelta) {
13566 var keys = this._zoomKeys = {},
13567 codes = this.keyCodes,
13568 i, len;
13569
13570 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13571 keys[codes.zoomIn[i]] = zoomDelta;
13572 }
13573 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13574 keys[codes.zoomOut[i]] = -zoomDelta;
13575 }
13576 },
13577
13578 _addHooks: function () {
13579 on(document, 'keydown', this._onKeyDown, this);
13580 },
13581
13582 _removeHooks: function () {
13583 off(document, 'keydown', this._onKeyDown, this);
13584 },
13585
13586 _onKeyDown: function (e) {
13587 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13588
13589 var key = e.keyCode,
13590 map = this._map,
13591 offset;
13592
13593 if (key in this._panKeys) {
13594 if (!map._panAnim || !map._panAnim._inProgress) {
13595 offset = this._panKeys[key];
13596 if (e.shiftKey) {
13597 offset = toPoint(offset).multiplyBy(3);
13598 }
13599
13600 map.panBy(offset);
13601
13602 if (map.options.maxBounds) {
13603 map.panInsideBounds(map.options.maxBounds);
13604 }
13605 }
13606 } else if (key in this._zoomKeys) {
13607 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13608
13609 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13610 map.closePopup();
13611
13612 } else {
13613 return;
13614 }
13615
13616 stop(e);
13617 }
13618 });
13619
13620 // @section Handlers
13621 // @section Handlers
13622 // @property keyboard: Handler
13623 // Keyboard navigation handler.
13624 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13625
13626 /*
13627 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13628 */
13629
13630 // @namespace Map
13631 // @section Interaction Options
13632 Map.mergeOptions({
13633 // @section Mouse wheel options
13634 // @option scrollWheelZoom: Boolean|String = true
13635 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13636 // it will zoom to the center of the view regardless of where the mouse was.
13637 scrollWheelZoom: true,
13638
13639 // @option wheelDebounceTime: Number = 40
13640 // Limits the rate at which a wheel can fire (in milliseconds). By default
13641 // user can't zoom via wheel more often than once per 40 ms.
13642 wheelDebounceTime: 40,
13643
13644 // @option wheelPxPerZoomLevel: Number = 60
13645 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13646 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13647 // faster (and vice versa).
13648 wheelPxPerZoomLevel: 60
13649 });
13650
13651 var ScrollWheelZoom = Handler.extend({
13652 addHooks: function () {
13653 on(this._map._container, 'wheel', this._onWheelScroll, this);
13654
13655 this._delta = 0;
13656 },
13657
13658 removeHooks: function () {
13659 off(this._map._container, 'wheel', this._onWheelScroll, this);
13660 },
13661
13662 _onWheelScroll: function (e) {
13663 var delta = getWheelDelta(e);
13664
13665 var debounce = this._map.options.wheelDebounceTime;
13666
13667 this._delta += delta;
13668 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13669
13670 if (!this._startTime) {
13671 this._startTime = +new Date();
13672 }
13673
13674 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13675
13676 clearTimeout(this._timer);
13677 this._timer = setTimeout(bind(this._performZoom, this), left);
13678
13679 stop(e);
13680 },
13681
13682 _performZoom: function () {
13683 var map = this._map,
13684 zoom = map.getZoom(),
13685 snap = this._map.options.zoomSnap || 0;
13686
13687 map._stop(); // stop panning and fly animations if any
13688
13689 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13690 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13691 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13692 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13693 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13694
13695 this._delta = 0;
13696 this._startTime = null;
13697
13698 if (!delta) { return; }
13699
13700 if (map.options.scrollWheelZoom === 'center') {
13701 map.setZoom(zoom + delta);
13702 } else {
13703 map.setZoomAround(this._lastMousePos, zoom + delta);
13704 }
13705 }
13706 });
13707
13708 // @section Handlers
13709 // @property scrollWheelZoom: Handler
13710 // Scroll wheel zoom handler.
13711 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13712
13713 /*
13714 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13715 */
13716
13717 // @namespace Map
13718 // @section Interaction Options
13719 Map.mergeOptions({
13720 // @section Touch interaction options
13721 // @option tap: Boolean = true
13722 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13723 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13724 tap: true,
13725
13726 // @option tapTolerance: Number = 15
13727 // The max number of pixels a user can shift his finger during touch
13728 // for it to be considered a valid tap.
13729 tapTolerance: 15
13730 });
13731
13732 var Tap = Handler.extend({
13733 addHooks: function () {
13734 on(this._map._container, 'touchstart', this._onDown, this);
13735 },
13736
13737 removeHooks: function () {
13738 off(this._map._container, 'touchstart', this._onDown, this);
13739 },
13740
13741 _onDown: function (e) {
13742 if (!e.touches) { return; }
13743
13744 preventDefault(e);
13745
13746 this._fireClick = true;
13747
13748 // don't simulate click or track longpress if more than 1 touch
13749 if (e.touches.length > 1) {
13750 this._fireClick = false;
13751 clearTimeout(this._holdTimeout);
13752 return;
13753 }
13754
13755 var first = e.touches[0],
13756 el = first.target;
13757
13758 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13759
13760 // if touching a link, highlight it
13761 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13762 addClass(el, 'leaflet-active');
13763 }
13764
13765 // simulate long hold but setting a timeout
13766 this._holdTimeout = setTimeout(bind(function () {
13767 if (this._isTapValid()) {
13768 this._fireClick = false;
13769 this._onUp();
13770 this._simulateEvent('contextmenu', first);
13771 }
13772 }, this), 1000);
13773
13774 this._simulateEvent('mousedown', first);
13775
13776 on(document, {
13777 touchmove: this._onMove,
13778 touchend: this._onUp
13779 }, this);
13780 },
13781
13782 _onUp: function (e) {
13783 clearTimeout(this._holdTimeout);
13784
13785 off(document, {
13786 touchmove: this._onMove,
13787 touchend: this._onUp
13788 }, this);
13789
13790 if (this._fireClick && e && e.changedTouches) {
13791
13792 var first = e.changedTouches[0],
13793 el = first.target;
13794
13795 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13796 removeClass(el, 'leaflet-active');
13797 }
13798
13799 this._simulateEvent('mouseup', first);
13800
13801 // simulate click if the touch didn't move too much
13802 if (this._isTapValid()) {
13803 this._simulateEvent('click', first);
13804 }
13805 }
13806 },
13807
13808 _isTapValid: function () {
13809 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13810 },
13811
13812 _onMove: function (e) {
13813 var first = e.touches[0];
13814 this._newPos = new Point(first.clientX, first.clientY);
13815 this._simulateEvent('mousemove', first);
13816 },
13817
13818 _simulateEvent: function (type, e) {
13819 var simulatedEvent = document.createEvent('MouseEvents');
13820
13821 simulatedEvent._simulated = true;
13822 e.target._simulatedClick = true;
13823
13824 simulatedEvent.initMouseEvent(
13825 type, true, true, window, 1,
13826 e.screenX, e.screenY,
13827 e.clientX, e.clientY,
13828 false, false, false, false, 0, null);
13829
13830 e.target.dispatchEvent(simulatedEvent);
13831 }
13832 });
13833
13834 // @section Handlers
13835 // @property tap: Handler
13836 // Mobile touch hacks (quick tap and touch hold) handler.
13837 if (touch && (!pointer || safari)) {
13838 Map.addInitHook('addHandler', 'tap', Tap);
13839 }
13840
13841 /*
13842 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13843 */
13844
13845 // @namespace Map
13846 // @section Interaction Options
13847 Map.mergeOptions({
13848 // @section Touch interaction options
13849 // @option touchZoom: Boolean|String = *
13850 // Whether the map can be zoomed by touch-dragging with two fingers. If
13851 // passed `'center'`, it will zoom to the center of the view regardless of
13852 // where the touch events (fingers) were. Enabled for touch-capable web
13853 // browsers except for old Androids.
13854 touchZoom: touch && !android23,
13855
13856 // @option bounceAtZoomLimits: Boolean = true
13857 // Set it to false if you don't want the map to zoom beyond min/max zoom
13858 // and then bounce back when pinch-zooming.
13859 bounceAtZoomLimits: true
13860 });
13861
13862 var TouchZoom = Handler.extend({
13863 addHooks: function () {
13864 addClass(this._map._container, 'leaflet-touch-zoom');
13865 on(this._map._container, 'touchstart', this._onTouchStart, this);
13866 },
13867
13868 removeHooks: function () {
13869 removeClass(this._map._container, 'leaflet-touch-zoom');
13870 off(this._map._container, 'touchstart', this._onTouchStart, this);
13871 },
13872
13873 _onTouchStart: function (e) {
13874 var map = this._map;
13875 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13876
13877 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13878 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13879
13880 this._centerPoint = map.getSize()._divideBy(2);
13881 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13882 if (map.options.touchZoom !== 'center') {
13883 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13884 }
13885
13886 this._startDist = p1.distanceTo(p2);
13887 this._startZoom = map.getZoom();
13888
13889 this._moved = false;
13890 this._zooming = true;
13891
13892 map._stop();
13893
13894 on(document, 'touchmove', this._onTouchMove, this);
13895 on(document, 'touchend', this._onTouchEnd, this);
13896
13897 preventDefault(e);
13898 },
13899
13900 _onTouchMove: function (e) {
13901 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13902
13903 var map = this._map,
13904 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13905 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13906 scale = p1.distanceTo(p2) / this._startDist;
13907
13908 this._zoom = map.getScaleZoom(scale, this._startZoom);
13909
13910 if (!map.options.bounceAtZoomLimits && (
13911 (this._zoom < map.getMinZoom() && scale < 1) ||
13912 (this._zoom > map.getMaxZoom() && scale > 1))) {
13913 this._zoom = map._limitZoom(this._zoom);
13914 }
13915
13916 if (map.options.touchZoom === 'center') {
13917 this._center = this._startLatLng;
13918 if (scale === 1) { return; }
13919 } else {
13920 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13921 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13922 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13923 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13924 }
13925
13926 if (!this._moved) {
13927 map._moveStart(true, false);
13928 this._moved = true;
13929 }
13930
13931 cancelAnimFrame(this._animRequest);
13932
13933 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13934 this._animRequest = requestAnimFrame(moveFn, this, true);
13935
13936 preventDefault(e);
13937 },
13938
13939 _onTouchEnd: function () {
13940 if (!this._moved || !this._zooming) {
13941 this._zooming = false;
13942 return;
13943 }
13944
13945 this._zooming = false;
13946 cancelAnimFrame(this._animRequest);
13947
13948 off(document, 'touchmove', this._onTouchMove, this);
13949 off(document, 'touchend', this._onTouchEnd, this);
13950
13951 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13952 if (this._map.options.zoomAnimation) {
13953 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13954 } else {
13955 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13956 }
13957 }
13958 });
13959
13960 // @section Handlers
13961 // @property touchZoom: Handler
13962 // Touch zoom handler.
13963 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13964
13965 Map.BoxZoom = BoxZoom;
13966 Map.DoubleClickZoom = DoubleClickZoom;
13967 Map.Drag = Drag;
13968 Map.Keyboard = Keyboard;
13969 Map.ScrollWheelZoom = ScrollWheelZoom;
13970 Map.Tap = Tap;
13971 Map.TouchZoom = TouchZoom;
13972
13973 exports.version = version;
13974 exports.Control = Control;
13975 exports.control = control;
13976 exports.Browser = Browser;
13977 exports.Evented = Evented;
13978 exports.Mixin = Mixin;
13979 exports.Util = Util;
13980 exports.Class = Class;
13981 exports.Handler = Handler;
13982 exports.extend = extend;
13983 exports.bind = bind;
13984 exports.stamp = stamp;
13985 exports.setOptions = setOptions;
13986 exports.DomEvent = DomEvent;
13987 exports.DomUtil = DomUtil;
13988 exports.PosAnimation = PosAnimation;
13989 exports.Draggable = Draggable;
13990 exports.LineUtil = LineUtil;
13991 exports.PolyUtil = PolyUtil;
13992 exports.Point = Point;
13993 exports.point = toPoint;
13994 exports.Bounds = Bounds;
13995 exports.bounds = toBounds;
13996 exports.Transformation = Transformation;
13997 exports.transformation = toTransformation;
13998 exports.Projection = index;
13999 exports.LatLng = LatLng;
14000 exports.latLng = toLatLng;
14001 exports.LatLngBounds = LatLngBounds;
14002 exports.latLngBounds = toLatLngBounds;
14003 exports.CRS = CRS;
14004 exports.GeoJSON = GeoJSON;
14005 exports.geoJSON = geoJSON;
14006 exports.geoJson = geoJson;
14007 exports.Layer = Layer;
14008 exports.LayerGroup = LayerGroup;
14009 exports.layerGroup = layerGroup;
14010 exports.FeatureGroup = FeatureGroup;
14011 exports.featureGroup = featureGroup;
14012 exports.ImageOverlay = ImageOverlay;
14013 exports.imageOverlay = imageOverlay;
14014 exports.VideoOverlay = VideoOverlay;
14015 exports.videoOverlay = videoOverlay;
14016 exports.SVGOverlay = SVGOverlay;
14017 exports.svgOverlay = svgOverlay;
14018 exports.DivOverlay = DivOverlay;
14019 exports.Popup = Popup;
14020 exports.popup = popup;
14021 exports.Tooltip = Tooltip;
14022 exports.tooltip = tooltip;
14023 exports.Icon = Icon;
14024 exports.icon = icon;
14025 exports.DivIcon = DivIcon;
14026 exports.divIcon = divIcon;
14027 exports.Marker = Marker;
14028 exports.marker = marker;
14029 exports.TileLayer = TileLayer;
14030 exports.tileLayer = tileLayer;
14031 exports.GridLayer = GridLayer;
14032 exports.gridLayer = gridLayer;
14033 exports.SVG = SVG;
14034 exports.svg = svg$1;
14035 exports.Renderer = Renderer;
14036 exports.Canvas = Canvas;
14037 exports.canvas = canvas$1;
14038 exports.Path = Path;
14039 exports.CircleMarker = CircleMarker;
14040 exports.circleMarker = circleMarker;
14041 exports.Circle = Circle;
14042 exports.circle = circle;
14043 exports.Polyline = Polyline;
14044 exports.polyline = polyline;
14045 exports.Polygon = Polygon;
14046 exports.polygon = polygon;
14047 exports.Rectangle = Rectangle;
14048 exports.rectangle = rectangle;
14049 exports.Map = Map;
14050 exports.map = createMap;
14051
14052 var oldL = window.L;
14053 exports.noConflict = function() {
14054 window.L = oldL;
14055 return this;
14056 }
14057
14058 // Always export us to window global (see #2364)
14059 window.L = exports;
14060
14061})));
14062//# sourceMappingURL=leaflet-src.js.map