UNPKG

407 kBJavaScriptView Raw
1/* @preserve
2 * Leaflet 1.6.0, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6var version = "1.6.0";
7
8/*
9 * @namespace Util
10 *
11 * Various utility functions, used by Leaflet internally.
12 */
13
14var freeze = Object.freeze;
15Object.freeze = function (obj) { return obj; };
16
17// @function extend(dest: Object, src?: Object): Object
18// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
19function extend(dest) {
20 var i, j, len, src;
21
22 for (j = 1, len = arguments.length; j < len; j++) {
23 src = arguments[j];
24 for (i in src) {
25 dest[i] = src[i];
26 }
27 }
28 return dest;
29}
30
31// @function create(proto: Object, properties?: Object): Object
32// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
33var create = Object.create || (function () {
34 function F() {}
35 return function (proto) {
36 F.prototype = proto;
37 return new F();
38 };
39})();
40
41// @function bind(fn: Function, …): Function
42// 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).
43// Has a `L.bind()` shortcut.
44function bind(fn, obj) {
45 var slice = Array.prototype.slice;
46
47 if (fn.bind) {
48 return fn.bind.apply(fn, slice.call(arguments, 1));
49 }
50
51 var args = slice.call(arguments, 2);
52
53 return function () {
54 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
55 };
56}
57
58// @property lastId: Number
59// Last unique ID used by [`stamp()`](#util-stamp)
60var lastId = 0;
61
62// @function stamp(obj: Object): Number
63// Returns the unique ID of an object, assigning it one if it doesn't have it.
64function stamp(obj) {
65 /*eslint-disable */
66 obj._leaflet_id = obj._leaflet_id || ++lastId;
67 return obj._leaflet_id;
68 /* eslint-enable */
69}
70
71// @function throttle(fn: Function, time: Number, context: Object): Function
72// Returns a function which executes function `fn` with the given scope `context`
73// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
74// `fn` will be called no more than one time per given amount of `time`. The arguments
75// received by the bound function will be any arguments passed when binding the
76// function, followed by any arguments passed when invoking the bound function.
77// Has an `L.throttle` shortcut.
78function throttle(fn, time, context) {
79 var lock, args, wrapperFn, later;
80
81 later = function () {
82 // reset lock and call if queued
83 lock = false;
84 if (args) {
85 wrapperFn.apply(context, args);
86 args = false;
87 }
88 };
89
90 wrapperFn = function () {
91 if (lock) {
92 // called too soon, queue to call later
93 args = arguments;
94
95 } else {
96 // call and lock until later
97 fn.apply(context, arguments);
98 setTimeout(later, time);
99 lock = true;
100 }
101 };
102
103 return wrapperFn;
104}
105
106// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
107// Returns the number `num` modulo `range` in such a way so it lies within
108// `range[0]` and `range[1]`. The returned value will be always smaller than
109// `range[1]` unless `includeMax` is set to `true`.
110function wrapNum(x, range, includeMax) {
111 var max = range[1],
112 min = range[0],
113 d = max - min;
114 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
115}
116
117// @function falseFn(): Function
118// Returns a function which always returns `false`.
119function falseFn() { return false; }
120
121// @function formatNum(num: Number, digits?: Number): Number
122// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
123function formatNum(num, digits) {
124 var pow = Math.pow(10, (digits === undefined ? 6 : digits));
125 return Math.round(num * pow) / pow;
126}
127
128// @function trim(str: String): String
129// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
130function trim(str) {
131 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
132}
133
134// @function splitWords(str: String): String[]
135// Trims and splits the string on whitespace and returns the array of parts.
136function splitWords(str) {
137 return trim(str).split(/\s+/);
138}
139
140// @function setOptions(obj: Object, options: Object): Object
141// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
142function setOptions(obj, options) {
143 if (!obj.hasOwnProperty('options')) {
144 obj.options = obj.options ? create(obj.options) : {};
145 }
146 for (var i in options) {
147 obj.options[i] = options[i];
148 }
149 return obj.options;
150}
151
152// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
153// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
154// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
155// be appended at the end. If `uppercase` is `true`, the parameter names will
156// be uppercased (e.g. `'?A=foo&B=bar'`)
157function getParamString(obj, existingUrl, uppercase) {
158 var params = [];
159 for (var i in obj) {
160 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
161 }
162 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
163}
164
165var templateRe = /\{ *([\w_-]+) *\}/g;
166
167// @function template(str: String, data: Object): String
168// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
169// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
170// `('Hello foo, bar')`. You can also specify functions instead of strings for
171// data values — they will be evaluated passing `data` as an argument.
172function template(str, data) {
173 return str.replace(templateRe, function (str, key) {
174 var value = data[key];
175
176 if (value === undefined) {
177 throw new Error('No value provided for variable ' + str);
178
179 } else if (typeof value === 'function') {
180 value = value(data);
181 }
182 return value;
183 });
184}
185
186// @function isArray(obj): Boolean
187// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
188var isArray = Array.isArray || function (obj) {
189 return (Object.prototype.toString.call(obj) === '[object Array]');
190};
191
192// @function indexOf(array: Array, el: Object): Number
193// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
194function indexOf(array, el) {
195 for (var i = 0; i < array.length; i++) {
196 if (array[i] === el) { return i; }
197 }
198 return -1;
199}
200
201// @property emptyImageUrl: String
202// Data URI string containing a base64-encoded empty GIF image.
203// Used as a hack to free memory from unused images on WebKit-powered
204// mobile devices (by setting image `src` to this string).
205var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
206
207// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
208
209function getPrefixed(name) {
210 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
211}
212
213var lastTime = 0;
214
215// fallback for IE 7-8
216function timeoutDefer(fn) {
217 var time = +new Date(),
218 timeToCall = Math.max(0, 16 - (time - lastTime));
219
220 lastTime = time + timeToCall;
221 return window.setTimeout(fn, timeToCall);
222}
223
224var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
225var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
226 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
227
228// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
229// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
230// `context` if given. When `immediate` is set, `fn` is called immediately if
231// the browser doesn't have native support for
232// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
233// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
234function requestAnimFrame(fn, context, immediate) {
235 if (immediate && requestFn === timeoutDefer) {
236 fn.call(context);
237 } else {
238 return requestFn.call(window, bind(fn, context));
239 }
240}
241
242// @function cancelAnimFrame(id: Number): undefined
243// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
244function cancelAnimFrame(id) {
245 if (id) {
246 cancelFn.call(window, id);
247 }
248}
249
250
251var Util = (Object.freeze || Object)({
252 freeze: freeze,
253 extend: extend,
254 create: create,
255 bind: bind,
256 lastId: lastId,
257 stamp: stamp,
258 throttle: throttle,
259 wrapNum: wrapNum,
260 falseFn: falseFn,
261 formatNum: formatNum,
262 trim: trim,
263 splitWords: splitWords,
264 setOptions: setOptions,
265 getParamString: getParamString,
266 template: template,
267 isArray: isArray,
268 indexOf: indexOf,
269 emptyImageUrl: emptyImageUrl,
270 requestFn: requestFn,
271 cancelFn: cancelFn,
272 requestAnimFrame: requestAnimFrame,
273 cancelAnimFrame: cancelAnimFrame
274});
275
276// @class Class
277// @aka L.Class
278
279// @section
280// @uninheritable
281
282// Thanks to John Resig and Dean Edwards for inspiration!
283
284function Class() {}
285
286Class.extend = function (props) {
287
288 // @function extend(props: Object): Function
289 // [Extends the current class](#class-inheritance) given the properties to be included.
290 // Returns a Javascript function that is a class constructor (to be called with `new`).
291 var NewClass = function () {
292
293 // call the constructor
294 if (this.initialize) {
295 this.initialize.apply(this, arguments);
296 }
297
298 // call all constructor hooks
299 this.callInitHooks();
300 };
301
302 var parentProto = NewClass.__super__ = this.prototype;
303
304 var proto = create(parentProto);
305 proto.constructor = NewClass;
306
307 NewClass.prototype = proto;
308
309 // inherit parent's statics
310 for (var i in this) {
311 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
312 NewClass[i] = this[i];
313 }
314 }
315
316 // mix static properties into the class
317 if (props.statics) {
318 extend(NewClass, props.statics);
319 delete props.statics;
320 }
321
322 // mix includes into the prototype
323 if (props.includes) {
324 checkDeprecatedMixinEvents(props.includes);
325 extend.apply(null, [proto].concat(props.includes));
326 delete props.includes;
327 }
328
329 // merge options
330 if (proto.options) {
331 props.options = extend(create(proto.options), props.options);
332 }
333
334 // mix given properties into the prototype
335 extend(proto, props);
336
337 proto._initHooks = [];
338
339 // add method for calling all hooks
340 proto.callInitHooks = function () {
341
342 if (this._initHooksCalled) { return; }
343
344 if (parentProto.callInitHooks) {
345 parentProto.callInitHooks.call(this);
346 }
347
348 this._initHooksCalled = true;
349
350 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
351 proto._initHooks[i].call(this);
352 }
353 };
354
355 return NewClass;
356};
357
358
359// @function include(properties: Object): this
360// [Includes a mixin](#class-includes) into the current class.
361Class.include = function (props) {
362 extend(this.prototype, props);
363 return this;
364};
365
366// @function mergeOptions(options: Object): this
367// [Merges `options`](#class-options) into the defaults of the class.
368Class.mergeOptions = function (options) {
369 extend(this.prototype.options, options);
370 return this;
371};
372
373// @function addInitHook(fn: Function): this
374// Adds a [constructor hook](#class-constructor-hooks) to the class.
375Class.addInitHook = function (fn) { // (Function) || (String, args...)
376 var args = Array.prototype.slice.call(arguments, 1);
377
378 var init = typeof fn === 'function' ? fn : function () {
379 this[fn].apply(this, args);
380 };
381
382 this.prototype._initHooks = this.prototype._initHooks || [];
383 this.prototype._initHooks.push(init);
384 return this;
385};
386
387function checkDeprecatedMixinEvents(includes) {
388 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
389
390 includes = isArray(includes) ? includes : [includes];
391
392 for (var i = 0; i < includes.length; i++) {
393 if (includes[i] === L.Mixin.Events) {
394 console.warn('Deprecated include of L.Mixin.Events: ' +
395 'this property will be removed in future releases, ' +
396 'please inherit from L.Evented instead.', new Error().stack);
397 }
398 }
399}
400
401/*
402 * @class Evented
403 * @aka L.Evented
404 * @inherits Class
405 *
406 * 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).
407 *
408 * @example
409 *
410 * ```js
411 * map.on('click', function(e) {
412 * alert(e.latlng);
413 * } );
414 * ```
415 *
416 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
417 *
418 * ```js
419 * function onClick(e) { ... }
420 *
421 * map.on('click', onClick);
422 * map.off('click', onClick);
423 * ```
424 */
425
426var Events = {
427 /* @method on(type: String, fn: Function, context?: Object): this
428 * 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'`).
429 *
430 * @alternative
431 * @method on(eventMap: Object): this
432 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
433 */
434 on: function (types, fn, context) {
435
436 // types can be a map of types/handlers
437 if (typeof types === 'object') {
438 for (var type in types) {
439 // we don't process space-separated events here for performance;
440 // it's a hot path since Layer uses the on(obj) syntax
441 this._on(type, types[type], fn);
442 }
443
444 } else {
445 // types can be a string of space-separated words
446 types = splitWords(types);
447
448 for (var i = 0, len = types.length; i < len; i++) {
449 this._on(types[i], fn, context);
450 }
451 }
452
453 return this;
454 },
455
456 /* @method off(type: String, fn?: Function, context?: Object): this
457 * 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.
458 *
459 * @alternative
460 * @method off(eventMap: Object): this
461 * Removes a set of type/listener pairs.
462 *
463 * @alternative
464 * @method off: this
465 * Removes all listeners to all events on the object. This includes implicitly attached events.
466 */
467 off: function (types, fn, context) {
468
469 if (!types) {
470 // clear all listeners if called without arguments
471 delete this._events;
472
473 } else if (typeof types === 'object') {
474 for (var type in types) {
475 this._off(type, types[type], fn);
476 }
477
478 } else {
479 types = splitWords(types);
480
481 for (var i = 0, len = types.length; i < len; i++) {
482 this._off(types[i], fn, context);
483 }
484 }
485
486 return this;
487 },
488
489 // attach listener (without syntactic sugar now)
490 _on: function (type, fn, context) {
491 this._events = this._events || {};
492
493 /* get/init listeners for type */
494 var typeListeners = this._events[type];
495 if (!typeListeners) {
496 typeListeners = [];
497 this._events[type] = typeListeners;
498 }
499
500 if (context === this) {
501 // Less memory footprint.
502 context = undefined;
503 }
504 var newListener = {fn: fn, ctx: context},
505 listeners = typeListeners;
506
507 // check if fn already there
508 for (var i = 0, len = listeners.length; i < len; i++) {
509 if (listeners[i].fn === fn && listeners[i].ctx === context) {
510 return;
511 }
512 }
513
514 listeners.push(newListener);
515 },
516
517 _off: function (type, fn, context) {
518 var listeners,
519 i,
520 len;
521
522 if (!this._events) { return; }
523
524 listeners = this._events[type];
525
526 if (!listeners) {
527 return;
528 }
529
530 if (!fn) {
531 // Set all removed listeners to noop so they are not called if remove happens in fire
532 for (i = 0, len = listeners.length; i < len; i++) {
533 listeners[i].fn = falseFn;
534 }
535 // clear all listeners for a type if function isn't specified
536 delete this._events[type];
537 return;
538 }
539
540 if (context === this) {
541 context = undefined;
542 }
543
544 if (listeners) {
545
546 // find fn and remove it
547 for (i = 0, len = listeners.length; i < len; i++) {
548 var l = listeners[i];
549 if (l.ctx !== context) { continue; }
550 if (l.fn === fn) {
551
552 // set the removed listener to noop so that's not called if remove happens in fire
553 l.fn = falseFn;
554
555 if (this._firingCount) {
556 /* copy array in case events are being fired */
557 this._events[type] = listeners = listeners.slice();
558 }
559 listeners.splice(i, 1);
560
561 return;
562 }
563 }
564 }
565 },
566
567 // @method fire(type: String, data?: Object, propagate?: Boolean): this
568 // Fires an event of the specified type. You can optionally provide an data
569 // object — the first argument of the listener function will contain its
570 // properties. The event can optionally be propagated to event parents.
571 fire: function (type, data, propagate) {
572 if (!this.listens(type, propagate)) { return this; }
573
574 var event = extend({}, data, {
575 type: type,
576 target: this,
577 sourceTarget: data && data.sourceTarget || this
578 });
579
580 if (this._events) {
581 var listeners = this._events[type];
582
583 if (listeners) {
584 this._firingCount = (this._firingCount + 1) || 1;
585 for (var i = 0, len = listeners.length; i < len; i++) {
586 var l = listeners[i];
587 l.fn.call(l.ctx || this, event);
588 }
589
590 this._firingCount--;
591 }
592 }
593
594 if (propagate) {
595 // propagate the event to parents (set with addEventParent)
596 this._propagateEvent(event);
597 }
598
599 return this;
600 },
601
602 // @method listens(type: String): Boolean
603 // Returns `true` if a particular event type has any listeners attached to it.
604 listens: function (type, propagate) {
605 var listeners = this._events && this._events[type];
606 if (listeners && listeners.length) { return true; }
607
608 if (propagate) {
609 // also check parents for listeners if event propagates
610 for (var id in this._eventParents) {
611 if (this._eventParents[id].listens(type, propagate)) { return true; }
612 }
613 }
614 return false;
615 },
616
617 // @method once(…): this
618 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
619 once: function (types, fn, context) {
620
621 if (typeof types === 'object') {
622 for (var type in types) {
623 this.once(type, types[type], fn);
624 }
625 return this;
626 }
627
628 var handler = bind(function () {
629 this
630 .off(types, fn, context)
631 .off(types, handler, context);
632 }, this);
633
634 // add a listener that's executed once and removed after that
635 return this
636 .on(types, fn, context)
637 .on(types, handler, context);
638 },
639
640 // @method addEventParent(obj: Evented): this
641 // Adds an event parent - an `Evented` that will receive propagated events
642 addEventParent: function (obj) {
643 this._eventParents = this._eventParents || {};
644 this._eventParents[stamp(obj)] = obj;
645 return this;
646 },
647
648 // @method removeEventParent(obj: Evented): this
649 // Removes an event parent, so it will stop receiving propagated events
650 removeEventParent: function (obj) {
651 if (this._eventParents) {
652 delete this._eventParents[stamp(obj)];
653 }
654 return this;
655 },
656
657 _propagateEvent: function (e) {
658 for (var id in this._eventParents) {
659 this._eventParents[id].fire(e.type, extend({
660 layer: e.target,
661 propagatedFrom: e.target
662 }, e), true);
663 }
664 }
665};
666
667// aliases; we should ditch those eventually
668
669// @method addEventListener(…): this
670// Alias to [`on(…)`](#evented-on)
671Events.addEventListener = Events.on;
672
673// @method removeEventListener(…): this
674// Alias to [`off(…)`](#evented-off)
675
676// @method clearAllEventListeners(…): this
677// Alias to [`off()`](#evented-off)
678Events.removeEventListener = Events.clearAllEventListeners = Events.off;
679
680// @method addOneTimeEventListener(…): this
681// Alias to [`once(…)`](#evented-once)
682Events.addOneTimeEventListener = Events.once;
683
684// @method fireEvent(…): this
685// Alias to [`fire(…)`](#evented-fire)
686Events.fireEvent = Events.fire;
687
688// @method hasEventListeners(…): Boolean
689// Alias to [`listens(…)`](#evented-listens)
690Events.hasEventListeners = Events.listens;
691
692var Evented = Class.extend(Events);
693
694/*
695 * @class Point
696 * @aka L.Point
697 *
698 * Represents a point with `x` and `y` coordinates in pixels.
699 *
700 * @example
701 *
702 * ```js
703 * var point = L.point(200, 300);
704 * ```
705 *
706 * 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:
707 *
708 * ```js
709 * map.panBy([200, 300]);
710 * map.panBy(L.point(200, 300));
711 * ```
712 *
713 * Note that `Point` does not inherit from Leafet's `Class` object,
714 * which means new classes can't inherit from it, and new methods
715 * can't be added to it with the `include` function.
716 */
717
718function Point(x, y, round) {
719 // @property x: Number; The `x` coordinate of the point
720 this.x = (round ? Math.round(x) : x);
721 // @property y: Number; The `y` coordinate of the point
722 this.y = (round ? Math.round(y) : y);
723}
724
725var trunc = Math.trunc || function (v) {
726 return v > 0 ? Math.floor(v) : Math.ceil(v);
727};
728
729Point.prototype = {
730
731 // @method clone(): Point
732 // Returns a copy of the current point.
733 clone: function () {
734 return new Point(this.x, this.y);
735 },
736
737 // @method add(otherPoint: Point): Point
738 // Returns the result of addition of the current and the given points.
739 add: function (point) {
740 // non-destructive, returns a new point
741 return this.clone()._add(toPoint(point));
742 },
743
744 _add: function (point) {
745 // destructive, used directly for performance in situations where it's safe to modify existing point
746 this.x += point.x;
747 this.y += point.y;
748 return this;
749 },
750
751 // @method subtract(otherPoint: Point): Point
752 // Returns the result of subtraction of the given point from the current.
753 subtract: function (point) {
754 return this.clone()._subtract(toPoint(point));
755 },
756
757 _subtract: function (point) {
758 this.x -= point.x;
759 this.y -= point.y;
760 return this;
761 },
762
763 // @method divideBy(num: Number): Point
764 // Returns the result of division of the current point by the given number.
765 divideBy: function (num) {
766 return this.clone()._divideBy(num);
767 },
768
769 _divideBy: function (num) {
770 this.x /= num;
771 this.y /= num;
772 return this;
773 },
774
775 // @method multiplyBy(num: Number): Point
776 // Returns the result of multiplication of the current point by the given number.
777 multiplyBy: function (num) {
778 return this.clone()._multiplyBy(num);
779 },
780
781 _multiplyBy: function (num) {
782 this.x *= num;
783 this.y *= num;
784 return this;
785 },
786
787 // @method scaleBy(scale: Point): Point
788 // Multiply each coordinate of the current point by each coordinate of
789 // `scale`. In linear algebra terms, multiply the point by the
790 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
791 // defined by `scale`.
792 scaleBy: function (point) {
793 return new Point(this.x * point.x, this.y * point.y);
794 },
795
796 // @method unscaleBy(scale: Point): Point
797 // Inverse of `scaleBy`. Divide each coordinate of the current point by
798 // each coordinate of `scale`.
799 unscaleBy: function (point) {
800 return new Point(this.x / point.x, this.y / point.y);
801 },
802
803 // @method round(): Point
804 // Returns a copy of the current point with rounded coordinates.
805 round: function () {
806 return this.clone()._round();
807 },
808
809 _round: function () {
810 this.x = Math.round(this.x);
811 this.y = Math.round(this.y);
812 return this;
813 },
814
815 // @method floor(): Point
816 // Returns a copy of the current point with floored coordinates (rounded down).
817 floor: function () {
818 return this.clone()._floor();
819 },
820
821 _floor: function () {
822 this.x = Math.floor(this.x);
823 this.y = Math.floor(this.y);
824 return this;
825 },
826
827 // @method ceil(): Point
828 // Returns a copy of the current point with ceiled coordinates (rounded up).
829 ceil: function () {
830 return this.clone()._ceil();
831 },
832
833 _ceil: function () {
834 this.x = Math.ceil(this.x);
835 this.y = Math.ceil(this.y);
836 return this;
837 },
838
839 // @method trunc(): Point
840 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
841 trunc: function () {
842 return this.clone()._trunc();
843 },
844
845 _trunc: function () {
846 this.x = trunc(this.x);
847 this.y = trunc(this.y);
848 return this;
849 },
850
851 // @method distanceTo(otherPoint: Point): Number
852 // Returns the cartesian distance between the current and the given points.
853 distanceTo: function (point) {
854 point = toPoint(point);
855
856 var x = point.x - this.x,
857 y = point.y - this.y;
858
859 return Math.sqrt(x * x + y * y);
860 },
861
862 // @method equals(otherPoint: Point): Boolean
863 // Returns `true` if the given point has the same coordinates.
864 equals: function (point) {
865 point = toPoint(point);
866
867 return point.x === this.x &&
868 point.y === this.y;
869 },
870
871 // @method contains(otherPoint: Point): Boolean
872 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
873 contains: function (point) {
874 point = toPoint(point);
875
876 return Math.abs(point.x) <= Math.abs(this.x) &&
877 Math.abs(point.y) <= Math.abs(this.y);
878 },
879
880 // @method toString(): String
881 // Returns a string representation of the point for debugging purposes.
882 toString: function () {
883 return 'Point(' +
884 formatNum(this.x) + ', ' +
885 formatNum(this.y) + ')';
886 }
887};
888
889// @factory L.point(x: Number, y: Number, round?: Boolean)
890// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
891
892// @alternative
893// @factory L.point(coords: Number[])
894// Expects an array of the form `[x, y]` instead.
895
896// @alternative
897// @factory L.point(coords: Object)
898// Expects a plain object of the form `{x: Number, y: Number}` instead.
899function toPoint(x, y, round) {
900 if (x instanceof Point) {
901 return x;
902 }
903 if (isArray(x)) {
904 return new Point(x[0], x[1]);
905 }
906 if (x === undefined || x === null) {
907 return x;
908 }
909 if (typeof x === 'object' && 'x' in x && 'y' in x) {
910 return new Point(x.x, x.y);
911 }
912 return new Point(x, y, round);
913}
914
915/*
916 * @class Bounds
917 * @aka L.Bounds
918 *
919 * Represents a rectangular area in pixel coordinates.
920 *
921 * @example
922 *
923 * ```js
924 * var p1 = L.point(10, 10),
925 * p2 = L.point(40, 60),
926 * bounds = L.bounds(p1, p2);
927 * ```
928 *
929 * 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:
930 *
931 * ```js
932 * otherBounds.intersects([[10, 10], [40, 60]]);
933 * ```
934 *
935 * Note that `Bounds` does not inherit from Leafet's `Class` object,
936 * which means new classes can't inherit from it, and new methods
937 * can't be added to it with the `include` function.
938 */
939
940function Bounds(a, b) {
941 if (!a) { return; }
942
943 var points = b ? [a, b] : a;
944
945 for (var i = 0, len = points.length; i < len; i++) {
946 this.extend(points[i]);
947 }
948}
949
950Bounds.prototype = {
951 // @method extend(point: Point): this
952 // Extends the bounds to contain the given point.
953 extend: function (point) { // (Point)
954 point = toPoint(point);
955
956 // @property min: Point
957 // The top left corner of the rectangle.
958 // @property max: Point
959 // The bottom right corner of the rectangle.
960 if (!this.min && !this.max) {
961 this.min = point.clone();
962 this.max = point.clone();
963 } else {
964 this.min.x = Math.min(point.x, this.min.x);
965 this.max.x = Math.max(point.x, this.max.x);
966 this.min.y = Math.min(point.y, this.min.y);
967 this.max.y = Math.max(point.y, this.max.y);
968 }
969 return this;
970 },
971
972 // @method getCenter(round?: Boolean): Point
973 // Returns the center point of the bounds.
974 getCenter: function (round) {
975 return new Point(
976 (this.min.x + this.max.x) / 2,
977 (this.min.y + this.max.y) / 2, round);
978 },
979
980 // @method getBottomLeft(): Point
981 // Returns the bottom-left point of the bounds.
982 getBottomLeft: function () {
983 return new Point(this.min.x, this.max.y);
984 },
985
986 // @method getTopRight(): Point
987 // Returns the top-right point of the bounds.
988 getTopRight: function () { // -> Point
989 return new Point(this.max.x, this.min.y);
990 },
991
992 // @method getTopLeft(): Point
993 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
994 getTopLeft: function () {
995 return this.min; // left, top
996 },
997
998 // @method getBottomRight(): Point
999 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1000 getBottomRight: function () {
1001 return this.max; // right, bottom
1002 },
1003
1004 // @method getSize(): Point
1005 // Returns the size of the given bounds
1006 getSize: function () {
1007 return this.max.subtract(this.min);
1008 },
1009
1010 // @method contains(otherBounds: Bounds): Boolean
1011 // Returns `true` if the rectangle contains the given one.
1012 // @alternative
1013 // @method contains(point: Point): Boolean
1014 // Returns `true` if the rectangle contains the given point.
1015 contains: function (obj) {
1016 var min, max;
1017
1018 if (typeof obj[0] === 'number' || obj instanceof Point) {
1019 obj = toPoint(obj);
1020 } else {
1021 obj = toBounds(obj);
1022 }
1023
1024 if (obj instanceof Bounds) {
1025 min = obj.min;
1026 max = obj.max;
1027 } else {
1028 min = max = obj;
1029 }
1030
1031 return (min.x >= this.min.x) &&
1032 (max.x <= this.max.x) &&
1033 (min.y >= this.min.y) &&
1034 (max.y <= this.max.y);
1035 },
1036
1037 // @method intersects(otherBounds: Bounds): Boolean
1038 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1039 // intersect if they have at least one point in common.
1040 intersects: function (bounds) { // (Bounds) -> Boolean
1041 bounds = toBounds(bounds);
1042
1043 var min = this.min,
1044 max = this.max,
1045 min2 = bounds.min,
1046 max2 = bounds.max,
1047 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1048 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1049
1050 return xIntersects && yIntersects;
1051 },
1052
1053 // @method overlaps(otherBounds: Bounds): Boolean
1054 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1055 // overlap if their intersection is an area.
1056 overlaps: function (bounds) { // (Bounds) -> Boolean
1057 bounds = toBounds(bounds);
1058
1059 var min = this.min,
1060 max = this.max,
1061 min2 = bounds.min,
1062 max2 = bounds.max,
1063 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1064 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1065
1066 return xOverlaps && yOverlaps;
1067 },
1068
1069 isValid: function () {
1070 return !!(this.min && this.max);
1071 }
1072};
1073
1074
1075// @factory L.bounds(corner1: Point, corner2: Point)
1076// Creates a Bounds object from two corners coordinate pairs.
1077// @alternative
1078// @factory L.bounds(points: Point[])
1079// Creates a Bounds object from the given array of points.
1080function toBounds(a, b) {
1081 if (!a || a instanceof Bounds) {
1082 return a;
1083 }
1084 return new Bounds(a, b);
1085}
1086
1087/*
1088 * @class LatLngBounds
1089 * @aka L.LatLngBounds
1090 *
1091 * Represents a rectangular geographical area on a map.
1092 *
1093 * @example
1094 *
1095 * ```js
1096 * var corner1 = L.latLng(40.712, -74.227),
1097 * corner2 = L.latLng(40.774, -74.125),
1098 * bounds = L.latLngBounds(corner1, corner2);
1099 * ```
1100 *
1101 * 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:
1102 *
1103 * ```js
1104 * map.fitBounds([
1105 * [40.712, -74.227],
1106 * [40.774, -74.125]
1107 * ]);
1108 * ```
1109 *
1110 * 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.
1111 *
1112 * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
1113 * which means new classes can't inherit from it, and new methods
1114 * can't be added to it with the `include` function.
1115 */
1116
1117function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1118 if (!corner1) { return; }
1119
1120 var latlngs = corner2 ? [corner1, corner2] : corner1;
1121
1122 for (var i = 0, len = latlngs.length; i < len; i++) {
1123 this.extend(latlngs[i]);
1124 }
1125}
1126
1127LatLngBounds.prototype = {
1128
1129 // @method extend(latlng: LatLng): this
1130 // Extend the bounds to contain the given point
1131
1132 // @alternative
1133 // @method extend(otherBounds: LatLngBounds): this
1134 // Extend the bounds to contain the given bounds
1135 extend: function (obj) {
1136 var sw = this._southWest,
1137 ne = this._northEast,
1138 sw2, ne2;
1139
1140 if (obj instanceof LatLng) {
1141 sw2 = obj;
1142 ne2 = obj;
1143
1144 } else if (obj instanceof LatLngBounds) {
1145 sw2 = obj._southWest;
1146 ne2 = obj._northEast;
1147
1148 if (!sw2 || !ne2) { return this; }
1149
1150 } else {
1151 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1152 }
1153
1154 if (!sw && !ne) {
1155 this._southWest = new LatLng(sw2.lat, sw2.lng);
1156 this._northEast = new LatLng(ne2.lat, ne2.lng);
1157 } else {
1158 sw.lat = Math.min(sw2.lat, sw.lat);
1159 sw.lng = Math.min(sw2.lng, sw.lng);
1160 ne.lat = Math.max(ne2.lat, ne.lat);
1161 ne.lng = Math.max(ne2.lng, ne.lng);
1162 }
1163
1164 return this;
1165 },
1166
1167 // @method pad(bufferRatio: Number): LatLngBounds
1168 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1169 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1170 // Negative values will retract the bounds.
1171 pad: function (bufferRatio) {
1172 var sw = this._southWest,
1173 ne = this._northEast,
1174 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1175 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1176
1177 return new LatLngBounds(
1178 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1179 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1180 },
1181
1182 // @method getCenter(): LatLng
1183 // Returns the center point of the bounds.
1184 getCenter: function () {
1185 return new LatLng(
1186 (this._southWest.lat + this._northEast.lat) / 2,
1187 (this._southWest.lng + this._northEast.lng) / 2);
1188 },
1189
1190 // @method getSouthWest(): LatLng
1191 // Returns the south-west point of the bounds.
1192 getSouthWest: function () {
1193 return this._southWest;
1194 },
1195
1196 // @method getNorthEast(): LatLng
1197 // Returns the north-east point of the bounds.
1198 getNorthEast: function () {
1199 return this._northEast;
1200 },
1201
1202 // @method getNorthWest(): LatLng
1203 // Returns the north-west point of the bounds.
1204 getNorthWest: function () {
1205 return new LatLng(this.getNorth(), this.getWest());
1206 },
1207
1208 // @method getSouthEast(): LatLng
1209 // Returns the south-east point of the bounds.
1210 getSouthEast: function () {
1211 return new LatLng(this.getSouth(), this.getEast());
1212 },
1213
1214 // @method getWest(): Number
1215 // Returns the west longitude of the bounds
1216 getWest: function () {
1217 return this._southWest.lng;
1218 },
1219
1220 // @method getSouth(): Number
1221 // Returns the south latitude of the bounds
1222 getSouth: function () {
1223 return this._southWest.lat;
1224 },
1225
1226 // @method getEast(): Number
1227 // Returns the east longitude of the bounds
1228 getEast: function () {
1229 return this._northEast.lng;
1230 },
1231
1232 // @method getNorth(): Number
1233 // Returns the north latitude of the bounds
1234 getNorth: function () {
1235 return this._northEast.lat;
1236 },
1237
1238 // @method contains(otherBounds: LatLngBounds): Boolean
1239 // Returns `true` if the rectangle contains the given one.
1240
1241 // @alternative
1242 // @method contains (latlng: LatLng): Boolean
1243 // Returns `true` if the rectangle contains the given point.
1244 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1245 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1246 obj = toLatLng(obj);
1247 } else {
1248 obj = toLatLngBounds(obj);
1249 }
1250
1251 var sw = this._southWest,
1252 ne = this._northEast,
1253 sw2, ne2;
1254
1255 if (obj instanceof LatLngBounds) {
1256 sw2 = obj.getSouthWest();
1257 ne2 = obj.getNorthEast();
1258 } else {
1259 sw2 = ne2 = obj;
1260 }
1261
1262 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1263 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1264 },
1265
1266 // @method intersects(otherBounds: LatLngBounds): Boolean
1267 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1268 intersects: function (bounds) {
1269 bounds = toLatLngBounds(bounds);
1270
1271 var sw = this._southWest,
1272 ne = this._northEast,
1273 sw2 = bounds.getSouthWest(),
1274 ne2 = bounds.getNorthEast(),
1275
1276 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1277 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1278
1279 return latIntersects && lngIntersects;
1280 },
1281
1282 // @method overlaps(otherBounds: Bounds): Boolean
1283 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1284 overlaps: function (bounds) {
1285 bounds = toLatLngBounds(bounds);
1286
1287 var sw = this._southWest,
1288 ne = this._northEast,
1289 sw2 = bounds.getSouthWest(),
1290 ne2 = bounds.getNorthEast(),
1291
1292 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1293 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1294
1295 return latOverlaps && lngOverlaps;
1296 },
1297
1298 // @method toBBoxString(): String
1299 // 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.
1300 toBBoxString: function () {
1301 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1302 },
1303
1304 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1305 // 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.
1306 equals: function (bounds, maxMargin) {
1307 if (!bounds) { return false; }
1308
1309 bounds = toLatLngBounds(bounds);
1310
1311 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1312 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1313 },
1314
1315 // @method isValid(): Boolean
1316 // Returns `true` if the bounds are properly initialized.
1317 isValid: function () {
1318 return !!(this._southWest && this._northEast);
1319 }
1320};
1321
1322// TODO International date line?
1323
1324// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1325// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1326
1327// @alternative
1328// @factory L.latLngBounds(latlngs: LatLng[])
1329// 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).
1330function toLatLngBounds(a, b) {
1331 if (a instanceof LatLngBounds) {
1332 return a;
1333 }
1334 return new LatLngBounds(a, b);
1335}
1336
1337/* @class LatLng
1338 * @aka L.LatLng
1339 *
1340 * Represents a geographical point with a certain latitude and longitude.
1341 *
1342 * @example
1343 *
1344 * ```
1345 * var latlng = L.latLng(50.5, 30.5);
1346 * ```
1347 *
1348 * 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:
1349 *
1350 * ```
1351 * map.panTo([50, 30]);
1352 * map.panTo({lon: 30, lat: 50});
1353 * map.panTo({lat: 50, lng: 30});
1354 * map.panTo(L.latLng(50, 30));
1355 * ```
1356 *
1357 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1358 * which means new classes can't inherit from it, and new methods
1359 * can't be added to it with the `include` function.
1360 */
1361
1362function LatLng(lat, lng, alt) {
1363 if (isNaN(lat) || isNaN(lng)) {
1364 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1365 }
1366
1367 // @property lat: Number
1368 // Latitude in degrees
1369 this.lat = +lat;
1370
1371 // @property lng: Number
1372 // Longitude in degrees
1373 this.lng = +lng;
1374
1375 // @property alt: Number
1376 // Altitude in meters (optional)
1377 if (alt !== undefined) {
1378 this.alt = +alt;
1379 }
1380}
1381
1382LatLng.prototype = {
1383 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1384 // 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.
1385 equals: function (obj, maxMargin) {
1386 if (!obj) { return false; }
1387
1388 obj = toLatLng(obj);
1389
1390 var margin = Math.max(
1391 Math.abs(this.lat - obj.lat),
1392 Math.abs(this.lng - obj.lng));
1393
1394 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1395 },
1396
1397 // @method toString(): String
1398 // Returns a string representation of the point (for debugging purposes).
1399 toString: function (precision) {
1400 return 'LatLng(' +
1401 formatNum(this.lat, precision) + ', ' +
1402 formatNum(this.lng, precision) + ')';
1403 },
1404
1405 // @method distanceTo(otherLatLng: LatLng): Number
1406 // 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).
1407 distanceTo: function (other) {
1408 return Earth.distance(this, toLatLng(other));
1409 },
1410
1411 // @method wrap(): LatLng
1412 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1413 wrap: function () {
1414 return Earth.wrapLatLng(this);
1415 },
1416
1417 // @method toBounds(sizeInMeters: Number): LatLngBounds
1418 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1419 toBounds: function (sizeInMeters) {
1420 var latAccuracy = 180 * sizeInMeters / 40075017,
1421 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1422
1423 return toLatLngBounds(
1424 [this.lat - latAccuracy, this.lng - lngAccuracy],
1425 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1426 },
1427
1428 clone: function () {
1429 return new LatLng(this.lat, this.lng, this.alt);
1430 }
1431};
1432
1433
1434
1435// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1436// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1437
1438// @alternative
1439// @factory L.latLng(coords: Array): LatLng
1440// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1441
1442// @alternative
1443// @factory L.latLng(coords: Object): LatLng
1444// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1445
1446function toLatLng(a, b, c) {
1447 if (a instanceof LatLng) {
1448 return a;
1449 }
1450 if (isArray(a) && typeof a[0] !== 'object') {
1451 if (a.length === 3) {
1452 return new LatLng(a[0], a[1], a[2]);
1453 }
1454 if (a.length === 2) {
1455 return new LatLng(a[0], a[1]);
1456 }
1457 return null;
1458 }
1459 if (a === undefined || a === null) {
1460 return a;
1461 }
1462 if (typeof a === 'object' && 'lat' in a) {
1463 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1464 }
1465 if (b === undefined) {
1466 return null;
1467 }
1468 return new LatLng(a, b, c);
1469}
1470
1471/*
1472 * @namespace CRS
1473 * @crs L.CRS.Base
1474 * Object that defines coordinate reference systems for projecting
1475 * geographical points into pixel (screen) coordinates and back (and to
1476 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1477 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1478 *
1479 * Leaflet defines the most usual CRSs by default. If you want to use a
1480 * CRS not defined by default, take a look at the
1481 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1482 *
1483 * Note that the CRS instances do not inherit from Leafet's `Class` object,
1484 * and can't be instantiated. Also, new classes can't inherit from them,
1485 * and methods can't be added to them with the `include` function.
1486 */
1487
1488var CRS = {
1489 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1490 // Projects geographical coordinates into pixel coordinates for a given zoom.
1491 latLngToPoint: function (latlng, zoom) {
1492 var projectedPoint = this.projection.project(latlng),
1493 scale = this.scale(zoom);
1494
1495 return this.transformation._transform(projectedPoint, scale);
1496 },
1497
1498 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1499 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1500 // zoom into geographical coordinates.
1501 pointToLatLng: function (point, zoom) {
1502 var scale = this.scale(zoom),
1503 untransformedPoint = this.transformation.untransform(point, scale);
1504
1505 return this.projection.unproject(untransformedPoint);
1506 },
1507
1508 // @method project(latlng: LatLng): Point
1509 // Projects geographical coordinates into coordinates in units accepted for
1510 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1511 project: function (latlng) {
1512 return this.projection.project(latlng);
1513 },
1514
1515 // @method unproject(point: Point): LatLng
1516 // Given a projected coordinate returns the corresponding LatLng.
1517 // The inverse of `project`.
1518 unproject: function (point) {
1519 return this.projection.unproject(point);
1520 },
1521
1522 // @method scale(zoom: Number): Number
1523 // Returns the scale used when transforming projected coordinates into
1524 // pixel coordinates for a particular zoom. For example, it returns
1525 // `256 * 2^zoom` for Mercator-based CRS.
1526 scale: function (zoom) {
1527 return 256 * Math.pow(2, zoom);
1528 },
1529
1530 // @method zoom(scale: Number): Number
1531 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1532 // factor of `scale`.
1533 zoom: function (scale) {
1534 return Math.log(scale / 256) / Math.LN2;
1535 },
1536
1537 // @method getProjectedBounds(zoom: Number): Bounds
1538 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1539 getProjectedBounds: function (zoom) {
1540 if (this.infinite) { return null; }
1541
1542 var b = this.projection.bounds,
1543 s = this.scale(zoom),
1544 min = this.transformation.transform(b.min, s),
1545 max = this.transformation.transform(b.max, s);
1546
1547 return new Bounds(min, max);
1548 },
1549
1550 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1551 // Returns the distance between two geographical coordinates.
1552
1553 // @property code: String
1554 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1555 //
1556 // @property wrapLng: Number[]
1557 // An array of two numbers defining whether the longitude (horizontal) coordinate
1558 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1559 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1560 //
1561 // @property wrapLat: Number[]
1562 // Like `wrapLng`, but for the latitude (vertical) axis.
1563
1564 // wrapLng: [min, max],
1565 // wrapLat: [min, max],
1566
1567 // @property infinite: Boolean
1568 // If true, the coordinate space will be unbounded (infinite in both axes)
1569 infinite: false,
1570
1571 // @method wrapLatLng(latlng: LatLng): LatLng
1572 // Returns a `LatLng` where lat and lng has been wrapped according to the
1573 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1574 wrapLatLng: function (latlng) {
1575 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1576 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1577 alt = latlng.alt;
1578
1579 return new LatLng(lat, lng, alt);
1580 },
1581
1582 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1583 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1584 // that its center is within the CRS's bounds.
1585 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1586 wrapLatLngBounds: function (bounds) {
1587 var center = bounds.getCenter(),
1588 newCenter = this.wrapLatLng(center),
1589 latShift = center.lat - newCenter.lat,
1590 lngShift = center.lng - newCenter.lng;
1591
1592 if (latShift === 0 && lngShift === 0) {
1593 return bounds;
1594 }
1595
1596 var sw = bounds.getSouthWest(),
1597 ne = bounds.getNorthEast(),
1598 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1599 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1600
1601 return new LatLngBounds(newSw, newNe);
1602 }
1603};
1604
1605/*
1606 * @namespace CRS
1607 * @crs L.CRS.Earth
1608 *
1609 * Serves as the base for CRS that are global such that they cover the earth.
1610 * Can only be used as the base for other CRS and cannot be used directly,
1611 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1612 * meters.
1613 */
1614
1615var Earth = extend({}, CRS, {
1616 wrapLng: [-180, 180],
1617
1618 // Mean Earth Radius, as recommended for use by
1619 // the International Union of Geodesy and Geophysics,
1620 // see http://rosettacode.org/wiki/Haversine_formula
1621 R: 6371000,
1622
1623 // distance between two geographical points using spherical law of cosines approximation
1624 distance: function (latlng1, latlng2) {
1625 var rad = Math.PI / 180,
1626 lat1 = latlng1.lat * rad,
1627 lat2 = latlng2.lat * rad,
1628 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1629 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1630 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1631 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1632 return this.R * c;
1633 }
1634});
1635
1636/*
1637 * @namespace Projection
1638 * @projection L.Projection.SphericalMercator
1639 *
1640 * Spherical Mercator projection — the most common projection for online maps,
1641 * used by almost all free and commercial tile providers. Assumes that Earth is
1642 * a sphere. Used by the `EPSG:3857` CRS.
1643 */
1644
1645var earthRadius = 6378137;
1646
1647var SphericalMercator = {
1648
1649 R: earthRadius,
1650 MAX_LATITUDE: 85.0511287798,
1651
1652 project: function (latlng) {
1653 var d = Math.PI / 180,
1654 max = this.MAX_LATITUDE,
1655 lat = Math.max(Math.min(max, latlng.lat), -max),
1656 sin = Math.sin(lat * d);
1657
1658 return new Point(
1659 this.R * latlng.lng * d,
1660 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1661 },
1662
1663 unproject: function (point) {
1664 var d = 180 / Math.PI;
1665
1666 return new LatLng(
1667 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1668 point.x * d / this.R);
1669 },
1670
1671 bounds: (function () {
1672 var d = earthRadius * Math.PI;
1673 return new Bounds([-d, -d], [d, d]);
1674 })()
1675};
1676
1677/*
1678 * @class Transformation
1679 * @aka L.Transformation
1680 *
1681 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1682 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1683 * the reverse. Used by Leaflet in its projections code.
1684 *
1685 * @example
1686 *
1687 * ```js
1688 * var transformation = L.transformation(2, 5, -1, 10),
1689 * p = L.point(1, 2),
1690 * p2 = transformation.transform(p), // L.point(7, 8)
1691 * p3 = transformation.untransform(p2); // L.point(1, 2)
1692 * ```
1693 */
1694
1695
1696// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1697// Creates a `Transformation` object with the given coefficients.
1698function Transformation(a, b, c, d) {
1699 if (isArray(a)) {
1700 // use array properties
1701 this._a = a[0];
1702 this._b = a[1];
1703 this._c = a[2];
1704 this._d = a[3];
1705 return;
1706 }
1707 this._a = a;
1708 this._b = b;
1709 this._c = c;
1710 this._d = d;
1711}
1712
1713Transformation.prototype = {
1714 // @method transform(point: Point, scale?: Number): Point
1715 // Returns a transformed point, optionally multiplied by the given scale.
1716 // Only accepts actual `L.Point` instances, not arrays.
1717 transform: function (point, scale) { // (Point, Number) -> Point
1718 return this._transform(point.clone(), scale);
1719 },
1720
1721 // destructive transform (faster)
1722 _transform: function (point, scale) {
1723 scale = scale || 1;
1724 point.x = scale * (this._a * point.x + this._b);
1725 point.y = scale * (this._c * point.y + this._d);
1726 return point;
1727 },
1728
1729 // @method untransform(point: Point, scale?: Number): Point
1730 // Returns the reverse transformation of the given point, optionally divided
1731 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1732 untransform: function (point, scale) {
1733 scale = scale || 1;
1734 return new Point(
1735 (point.x / scale - this._b) / this._a,
1736 (point.y / scale - this._d) / this._c);
1737 }
1738};
1739
1740// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1741
1742// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1743// Instantiates a Transformation object with the given coefficients.
1744
1745// @alternative
1746// @factory L.transformation(coefficients: Array): Transformation
1747// Expects an coefficients array of the form
1748// `[a: Number, b: Number, c: Number, d: Number]`.
1749
1750function toTransformation(a, b, c, d) {
1751 return new Transformation(a, b, c, d);
1752}
1753
1754/*
1755 * @namespace CRS
1756 * @crs L.CRS.EPSG3857
1757 *
1758 * The most common CRS for online maps, used by almost all free and commercial
1759 * tile providers. Uses Spherical Mercator projection. Set in by default in
1760 * Map's `crs` option.
1761 */
1762
1763var EPSG3857 = extend({}, Earth, {
1764 code: 'EPSG:3857',
1765 projection: SphericalMercator,
1766
1767 transformation: (function () {
1768 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1769 return toTransformation(scale, 0.5, -scale, 0.5);
1770 }())
1771});
1772
1773var EPSG900913 = extend({}, EPSG3857, {
1774 code: 'EPSG:900913'
1775});
1776
1777// @namespace SVG; @section
1778// There are several static functions which can be called without instantiating L.SVG:
1779
1780// @function create(name: String): SVGElement
1781// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1782// corresponding to the class name passed. For example, using 'line' will return
1783// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1784function svgCreate(name) {
1785 return document.createElementNS('http://www.w3.org/2000/svg', name);
1786}
1787
1788// @function pointsToPath(rings: Point[], closed: Boolean): String
1789// Generates a SVG path string for multiple rings, with each ring turning
1790// into "M..L..L.." instructions
1791function pointsToPath(rings, closed) {
1792 var str = '',
1793 i, j, len, len2, points, p;
1794
1795 for (i = 0, len = rings.length; i < len; i++) {
1796 points = rings[i];
1797
1798 for (j = 0, len2 = points.length; j < len2; j++) {
1799 p = points[j];
1800 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1801 }
1802
1803 // closes the ring for polygons; "x" is VML syntax
1804 str += closed ? (svg ? 'z' : 'x') : '';
1805 }
1806
1807 // SVG complains about empty path strings
1808 return str || 'M0 0';
1809}
1810
1811/*
1812 * @namespace Browser
1813 * @aka L.Browser
1814 *
1815 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1816 *
1817 * @example
1818 *
1819 * ```js
1820 * if (L.Browser.ielt9) {
1821 * alert('Upgrade your browser, dude!');
1822 * }
1823 * ```
1824 */
1825
1826var style$1 = document.documentElement.style;
1827
1828// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1829var ie = 'ActiveXObject' in window;
1830
1831// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1832var ielt9 = ie && !document.addEventListener;
1833
1834// @property edge: Boolean; `true` for the Edge web browser.
1835var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1836
1837// @property webkit: Boolean;
1838// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1839var webkit = userAgentContains('webkit');
1840
1841// @property android: Boolean
1842// `true` for any browser running on an Android platform.
1843var android = userAgentContains('android');
1844
1845// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1846var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1847
1848/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1849var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1850// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1851var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1852
1853// @property opera: Boolean; `true` for the Opera browser
1854var opera = !!window.opera;
1855
1856// @property chrome: Boolean; `true` for the Chrome browser.
1857var chrome = userAgentContains('chrome');
1858
1859// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1860var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1861
1862// @property safari: Boolean; `true` for the Safari browser.
1863var safari = !chrome && userAgentContains('safari');
1864
1865var phantom = userAgentContains('phantom');
1866
1867// @property opera12: Boolean
1868// `true` for the Opera browser supporting CSS transforms (version 12 or later).
1869var opera12 = 'OTransition' in style$1;
1870
1871// @property win: Boolean; `true` when the browser is running in a Windows platform
1872var win = navigator.platform.indexOf('Win') === 0;
1873
1874// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1875var ie3d = ie && ('transition' in style$1);
1876
1877// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1878var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1879
1880// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1881var gecko3d = 'MozPerspective' in style$1;
1882
1883// @property any3d: Boolean
1884// `true` for all browsers supporting CSS transforms.
1885var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1886
1887// @property mobile: Boolean; `true` for all browsers running in a mobile device.
1888var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1889
1890// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1891var mobileWebkit = mobile && webkit;
1892
1893// @property mobileWebkit3d: Boolean
1894// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1895var mobileWebkit3d = mobile && webkit3d;
1896
1897// @property msPointer: Boolean
1898// `true` for browsers implementing the Microsoft touch events model (notably IE10).
1899var msPointer = !window.PointerEvent && window.MSPointerEvent;
1900
1901// @property pointer: Boolean
1902// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1903var pointer = !webkit && !!(window.PointerEvent || msPointer);
1904
1905// @property touch: Boolean
1906// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1907// This does not necessarily mean that the browser is running in a computer with
1908// a touchscreen, it only means that the browser is capable of understanding
1909// touch events.
1910var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1911 (window.DocumentTouch && document instanceof window.DocumentTouch));
1912
1913// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1914var mobileOpera = mobile && opera;
1915
1916// @property mobileGecko: Boolean
1917// `true` for gecko-based browsers running in a mobile device.
1918var mobileGecko = mobile && gecko;
1919
1920// @property retina: Boolean
1921// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1922var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1923
1924// @property passiveEvents: Boolean
1925// `true` for browsers that support passive events.
1926var passiveEvents = (function () {
1927 var supportsPassiveOption = false;
1928 try {
1929 var opts = Object.defineProperty({}, 'passive', {
1930 get: function () {
1931 supportsPassiveOption = true;
1932 }
1933 });
1934 window.addEventListener('testPassiveEventSupport', falseFn, opts);
1935 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
1936 } catch (e) {
1937 // Errors can safely be ignored since this is only a browser support test.
1938 }
1939 return supportsPassiveOption;
1940});
1941
1942// @property canvas: Boolean
1943// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1944var canvas = (function () {
1945 return !!document.createElement('canvas').getContext;
1946}());
1947
1948// @property svg: Boolean
1949// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1950var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1951
1952// @property vml: Boolean
1953// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1954var vml = !svg && (function () {
1955 try {
1956 var div = document.createElement('div');
1957 div.innerHTML = '<v:shape adj="1"/>';
1958
1959 var shape = div.firstChild;
1960 shape.style.behavior = 'url(#default#VML)';
1961
1962 return shape && (typeof shape.adj === 'object');
1963
1964 } catch (e) {
1965 return false;
1966 }
1967}());
1968
1969
1970function userAgentContains(str) {
1971 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1972}
1973
1974
1975var Browser = (Object.freeze || Object)({
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
2014var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2015var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2016var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2017var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2018var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2019
2020var _pointers = {};
2021var _pointerDocListener = false;
2022
2023// DomEvent.DoubleTap needs to know about this
2024var _pointersCount = 0;
2025
2026// Provides a touch events wrapper for (ms)pointer events.
2027// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2028
2029function addPointerListener(obj, type, handler, id) {
2030 if (type === 'touchstart') {
2031 _addPointerStart(obj, handler, id);
2032
2033 } else if (type === 'touchmove') {
2034 _addPointerMove(obj, handler, id);
2035
2036 } else if (type === 'touchend') {
2037 _addPointerEnd(obj, handler, id);
2038 }
2039
2040 return this;
2041}
2042
2043function removePointerListener(obj, type, id) {
2044 var handler = obj['_leaflet_' + type + id];
2045
2046 if (type === 'touchstart') {
2047 obj.removeEventListener(POINTER_DOWN, handler, false);
2048
2049 } else if (type === 'touchmove') {
2050 obj.removeEventListener(POINTER_MOVE, handler, false);
2051
2052 } else if (type === 'touchend') {
2053 obj.removeEventListener(POINTER_UP, handler, false);
2054 obj.removeEventListener(POINTER_CANCEL, handler, false);
2055 }
2056
2057 return this;
2058}
2059
2060function _addPointerStart(obj, handler, id) {
2061 var onDown = bind(function (e) {
2062 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2063 // In IE11, some touch events needs to fire for form controls, or
2064 // the controls will stop working. We keep a whitelist of tag names that
2065 // need these events. For other target tags, we prevent default on the event.
2066 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2067 preventDefault(e);
2068 } else {
2069 return;
2070 }
2071 }
2072
2073 _handlePointer(e, handler);
2074 });
2075
2076 obj['_leaflet_touchstart' + id] = onDown;
2077 obj.addEventListener(POINTER_DOWN, onDown, false);
2078
2079 // need to keep track of what pointers and how many are active to provide e.touches emulation
2080 if (!_pointerDocListener) {
2081 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2082 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2083 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2084 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2085 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2086
2087 _pointerDocListener = true;
2088 }
2089}
2090
2091function _globalPointerDown(e) {
2092 _pointers[e.pointerId] = e;
2093 _pointersCount++;
2094}
2095
2096function _globalPointerMove(e) {
2097 if (_pointers[e.pointerId]) {
2098 _pointers[e.pointerId] = e;
2099 }
2100}
2101
2102function _globalPointerUp(e) {
2103 delete _pointers[e.pointerId];
2104 _pointersCount--;
2105}
2106
2107function _handlePointer(e, handler) {
2108 e.touches = [];
2109 for (var i in _pointers) {
2110 e.touches.push(_pointers[i]);
2111 }
2112 e.changedTouches = [e];
2113
2114 handler(e);
2115}
2116
2117function _addPointerMove(obj, handler, id) {
2118 var onMove = function (e) {
2119 // don't fire touch moves when mouse isn't down
2120 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2121
2122 _handlePointer(e, handler);
2123 };
2124
2125 obj['_leaflet_touchmove' + id] = onMove;
2126 obj.addEventListener(POINTER_MOVE, onMove, false);
2127}
2128
2129function _addPointerEnd(obj, handler, id) {
2130 var onUp = function (e) {
2131 _handlePointer(e, handler);
2132 };
2133
2134 obj['_leaflet_touchend' + id] = onUp;
2135 obj.addEventListener(POINTER_UP, onUp, false);
2136 obj.addEventListener(POINTER_CANCEL, onUp, false);
2137}
2138
2139/*
2140 * Extends the event handling code with double tap support for mobile browsers.
2141 */
2142
2143var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2144var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2145var _pre = '_leaflet_';
2146
2147// inspired by Zepto touch code by Thomas Fuchs
2148function addDoubleTapListener(obj, handler, id) {
2149 var last, touch$$1,
2150 doubleTap = false,
2151 delay = 250;
2152
2153 function onTouchStart(e) {
2154 var count;
2155
2156 if (pointer) {
2157 if ((!edge) || e.pointerType === 'mouse') { return; }
2158 count = _pointersCount;
2159 } else {
2160 count = e.touches.length;
2161 }
2162
2163 if (count > 1) { return; }
2164
2165 var now = Date.now(),
2166 delta = now - (last || now);
2167
2168 touch$$1 = e.touches ? e.touches[0] : e;
2169 doubleTap = (delta > 0 && delta <= delay);
2170 last = now;
2171 }
2172
2173 function onTouchEnd(e) {
2174 if (doubleTap && !touch$$1.cancelBubble) {
2175 if (pointer) {
2176 if ((!edge) || e.pointerType === 'mouse') { return; }
2177 // work around .type being readonly with MSPointer* events
2178 var newTouch = {},
2179 prop, i;
2180
2181 for (i in touch$$1) {
2182 prop = touch$$1[i];
2183 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2184 }
2185 touch$$1 = newTouch;
2186 }
2187 touch$$1.type = 'dblclick';
2188 touch$$1.button = 0;
2189 handler(touch$$1);
2190 last = null;
2191 }
2192 }
2193
2194 obj[_pre + _touchstart + id] = onTouchStart;
2195 obj[_pre + _touchend + id] = onTouchEnd;
2196 obj[_pre + 'dblclick' + id] = handler;
2197
2198 obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
2199 obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
2200
2201 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2202 // the browser doesn't fire touchend/pointerup events but does fire
2203 // native dblclicks. See #4127.
2204 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2205 obj.addEventListener('dblclick', handler, false);
2206
2207 return this;
2208}
2209
2210function removeDoubleTapListener(obj, id) {
2211 var touchstart = obj[_pre + _touchstart + id],
2212 touchend = obj[_pre + _touchend + id],
2213 dblclick = obj[_pre + 'dblclick' + id];
2214
2215 obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
2216 obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
2217 if (!edge) {
2218 obj.removeEventListener('dblclick', dblclick, false);
2219 }
2220
2221 return this;
2222}
2223
2224/*
2225 * @namespace DomUtil
2226 *
2227 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2228 * tree, used by Leaflet internally.
2229 *
2230 * Most functions expecting or returning a `HTMLElement` also work for
2231 * SVG elements. The only difference is that classes refer to CSS classes
2232 * in HTML and SVG classes in SVG.
2233 */
2234
2235
2236// @property TRANSFORM: String
2237// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2238var TRANSFORM = testProp(
2239 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2240
2241// webkitTransition comes first because some browser versions that drop vendor prefix don't do
2242// the same for the transitionend event, in particular the Android 4.1 stock browser
2243
2244// @property TRANSITION: String
2245// Vendor-prefixed transition style name.
2246var TRANSITION = testProp(
2247 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2248
2249// @property TRANSITION_END: String
2250// Vendor-prefixed transitionend event name.
2251var TRANSITION_END =
2252 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2253
2254
2255// @function get(id: String|HTMLElement): HTMLElement
2256// Returns an element given its DOM id, or returns the element itself
2257// if it was passed directly.
2258function get(id) {
2259 return typeof id === 'string' ? document.getElementById(id) : id;
2260}
2261
2262// @function getStyle(el: HTMLElement, styleAttrib: String): String
2263// Returns the value for a certain style attribute on an element,
2264// including computed values or values set through CSS.
2265function getStyle(el, style) {
2266 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2267
2268 if ((!value || value === 'auto') && document.defaultView) {
2269 var css = document.defaultView.getComputedStyle(el, null);
2270 value = css ? css[style] : null;
2271 }
2272 return value === 'auto' ? null : value;
2273}
2274
2275// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2276// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2277function create$1(tagName, className, container) {
2278 var el = document.createElement(tagName);
2279 el.className = className || '';
2280
2281 if (container) {
2282 container.appendChild(el);
2283 }
2284 return el;
2285}
2286
2287// @function remove(el: HTMLElement)
2288// Removes `el` from its parent element
2289function remove(el) {
2290 var parent = el.parentNode;
2291 if (parent) {
2292 parent.removeChild(el);
2293 }
2294}
2295
2296// @function empty(el: HTMLElement)
2297// Removes all of `el`'s children elements from `el`
2298function empty(el) {
2299 while (el.firstChild) {
2300 el.removeChild(el.firstChild);
2301 }
2302}
2303
2304// @function toFront(el: HTMLElement)
2305// Makes `el` the last child of its parent, so it renders in front of the other children.
2306function toFront(el) {
2307 var parent = el.parentNode;
2308 if (parent && parent.lastChild !== el) {
2309 parent.appendChild(el);
2310 }
2311}
2312
2313// @function toBack(el: HTMLElement)
2314// Makes `el` the first child of its parent, so it renders behind the other children.
2315function toBack(el) {
2316 var parent = el.parentNode;
2317 if (parent && parent.firstChild !== el) {
2318 parent.insertBefore(el, parent.firstChild);
2319 }
2320}
2321
2322// @function hasClass(el: HTMLElement, name: String): Boolean
2323// Returns `true` if the element's class attribute contains `name`.
2324function hasClass(el, name) {
2325 if (el.classList !== undefined) {
2326 return el.classList.contains(name);
2327 }
2328 var className = getClass(el);
2329 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2330}
2331
2332// @function addClass(el: HTMLElement, name: String)
2333// Adds `name` to the element's class attribute.
2334function addClass(el, name) {
2335 if (el.classList !== undefined) {
2336 var classes = splitWords(name);
2337 for (var i = 0, len = classes.length; i < len; i++) {
2338 el.classList.add(classes[i]);
2339 }
2340 } else if (!hasClass(el, name)) {
2341 var className = getClass(el);
2342 setClass(el, (className ? className + ' ' : '') + name);
2343 }
2344}
2345
2346// @function removeClass(el: HTMLElement, name: String)
2347// Removes `name` from the element's class attribute.
2348function removeClass(el, name) {
2349 if (el.classList !== undefined) {
2350 el.classList.remove(name);
2351 } else {
2352 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2353 }
2354}
2355
2356// @function setClass(el: HTMLElement, name: String)
2357// Sets the element's class.
2358function setClass(el, name) {
2359 if (el.className.baseVal === undefined) {
2360 el.className = name;
2361 } else {
2362 // in case of SVG element
2363 el.className.baseVal = name;
2364 }
2365}
2366
2367// @function getClass(el: HTMLElement): String
2368// Returns the element's class.
2369function getClass(el) {
2370 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2371 // (Required for linked SVG elements in IE11.)
2372 if (el.correspondingElement) {
2373 el = el.correspondingElement;
2374 }
2375 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2376}
2377
2378// @function setOpacity(el: HTMLElement, opacity: Number)
2379// Set the opacity of an element (including old IE support).
2380// `opacity` must be a number from `0` to `1`.
2381function setOpacity(el, value) {
2382 if ('opacity' in el.style) {
2383 el.style.opacity = value;
2384 } else if ('filter' in el.style) {
2385 _setOpacityIE(el, value);
2386 }
2387}
2388
2389function _setOpacityIE(el, value) {
2390 var filter = false,
2391 filterName = 'DXImageTransform.Microsoft.Alpha';
2392
2393 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2394 try {
2395 filter = el.filters.item(filterName);
2396 } catch (e) {
2397 // don't set opacity to 1 if we haven't already set an opacity,
2398 // it isn't needed and breaks transparent pngs.
2399 if (value === 1) { return; }
2400 }
2401
2402 value = Math.round(value * 100);
2403
2404 if (filter) {
2405 filter.Enabled = (value !== 100);
2406 filter.Opacity = value;
2407 } else {
2408 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2409 }
2410}
2411
2412// @function testProp(props: String[]): String|false
2413// Goes through the array of style names and returns the first name
2414// that is a valid style name for an element. If no such name is found,
2415// it returns false. Useful for vendor-prefixed styles like `transform`.
2416function testProp(props) {
2417 var style = document.documentElement.style;
2418
2419 for (var i = 0; i < props.length; i++) {
2420 if (props[i] in style) {
2421 return props[i];
2422 }
2423 }
2424 return false;
2425}
2426
2427// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2428// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2429// and optionally scaled by `scale`. Does not have an effect if the
2430// browser doesn't support 3D CSS transforms.
2431function setTransform(el, offset, scale) {
2432 var pos = offset || new Point(0, 0);
2433
2434 el.style[TRANSFORM] =
2435 (ie3d ?
2436 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2437 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2438 (scale ? ' scale(' + scale + ')' : '');
2439}
2440
2441// @function setPosition(el: HTMLElement, position: Point)
2442// Sets the position of `el` to coordinates specified by `position`,
2443// using CSS translate or top/left positioning depending on the browser
2444// (used by Leaflet internally to position its layers).
2445function setPosition(el, point) {
2446
2447 /*eslint-disable */
2448 el._leaflet_pos = point;
2449 /* eslint-enable */
2450
2451 if (any3d) {
2452 setTransform(el, point);
2453 } else {
2454 el.style.left = point.x + 'px';
2455 el.style.top = point.y + 'px';
2456 }
2457}
2458
2459// @function getPosition(el: HTMLElement): Point
2460// Returns the coordinates of an element previously positioned with setPosition.
2461function getPosition(el) {
2462 // this method is only used for elements previously positioned using setPosition,
2463 // so it's safe to cache the position for performance
2464
2465 return el._leaflet_pos || new Point(0, 0);
2466}
2467
2468// @function disableTextSelection()
2469// Prevents the user from generating `selectstart` DOM events, usually generated
2470// when the user drags the mouse through a page with text. Used internally
2471// by Leaflet to override the behaviour of any click-and-drag interaction on
2472// the map. Affects drag interactions on the whole document.
2473
2474// @function enableTextSelection()
2475// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2476var disableTextSelection;
2477var enableTextSelection;
2478var _userSelect;
2479if ('onselectstart' in document) {
2480 disableTextSelection = function () {
2481 on(window, 'selectstart', preventDefault);
2482 };
2483 enableTextSelection = function () {
2484 off(window, 'selectstart', preventDefault);
2485 };
2486} else {
2487 var userSelectProperty = testProp(
2488 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2489
2490 disableTextSelection = function () {
2491 if (userSelectProperty) {
2492 var style = document.documentElement.style;
2493 _userSelect = style[userSelectProperty];
2494 style[userSelectProperty] = 'none';
2495 }
2496 };
2497 enableTextSelection = function () {
2498 if (userSelectProperty) {
2499 document.documentElement.style[userSelectProperty] = _userSelect;
2500 _userSelect = undefined;
2501 }
2502 };
2503}
2504
2505// @function disableImageDrag()
2506// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2507// for `dragstart` DOM events, usually generated when the user drags an image.
2508function disableImageDrag() {
2509 on(window, 'dragstart', preventDefault);
2510}
2511
2512// @function enableImageDrag()
2513// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2514function enableImageDrag() {
2515 off(window, 'dragstart', preventDefault);
2516}
2517
2518var _outlineElement;
2519var _outlineStyle;
2520// @function preventOutline(el: HTMLElement)
2521// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2522// of the element `el` invisible. Used internally by Leaflet to prevent
2523// focusable elements from displaying an outline when the user performs a
2524// drag interaction on them.
2525function preventOutline(element) {
2526 while (element.tabIndex === -1) {
2527 element = element.parentNode;
2528 }
2529 if (!element.style) { return; }
2530 restoreOutline();
2531 _outlineElement = element;
2532 _outlineStyle = element.style.outline;
2533 element.style.outline = 'none';
2534 on(window, 'keydown', restoreOutline);
2535}
2536
2537// @function restoreOutline()
2538// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2539function restoreOutline() {
2540 if (!_outlineElement) { return; }
2541 _outlineElement.style.outline = _outlineStyle;
2542 _outlineElement = undefined;
2543 _outlineStyle = undefined;
2544 off(window, 'keydown', restoreOutline);
2545}
2546
2547// @function getSizedParentNode(el: HTMLElement): HTMLElement
2548// Finds the closest parent node which size (width and height) is not null.
2549function getSizedParentNode(element) {
2550 do {
2551 element = element.parentNode;
2552 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2553 return element;
2554}
2555
2556// @function getScale(el: HTMLElement): Object
2557// Computes the CSS scale currently applied on the element.
2558// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2559// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2560function getScale(element) {
2561 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2562
2563 return {
2564 x: rect.width / element.offsetWidth || 1,
2565 y: rect.height / element.offsetHeight || 1,
2566 boundingClientRect: rect
2567 };
2568}
2569
2570
2571var DomUtil = (Object.freeze || Object)({
2572 TRANSFORM: TRANSFORM,
2573 TRANSITION: TRANSITION,
2574 TRANSITION_END: TRANSITION_END,
2575 get: get,
2576 getStyle: getStyle,
2577 create: create$1,
2578 remove: remove,
2579 empty: empty,
2580 toFront: toFront,
2581 toBack: toBack,
2582 hasClass: hasClass,
2583 addClass: addClass,
2584 removeClass: removeClass,
2585 setClass: setClass,
2586 getClass: getClass,
2587 setOpacity: setOpacity,
2588 testProp: testProp,
2589 setTransform: setTransform,
2590 setPosition: setPosition,
2591 getPosition: getPosition,
2592 disableTextSelection: disableTextSelection,
2593 enableTextSelection: enableTextSelection,
2594 disableImageDrag: disableImageDrag,
2595 enableImageDrag: enableImageDrag,
2596 preventOutline: preventOutline,
2597 restoreOutline: restoreOutline,
2598 getSizedParentNode: getSizedParentNode,
2599 getScale: getScale
2600});
2601
2602/*
2603 * @namespace DomEvent
2604 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2605 */
2606
2607// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2608
2609// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2610// Adds a listener function (`fn`) to a particular DOM event type of the
2611// element `el`. You can optionally specify the context of the listener
2612// (object the `this` keyword will point to). You can also pass several
2613// space-separated types (e.g. `'click dblclick'`).
2614
2615// @alternative
2616// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2617// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2618function on(obj, types, fn, context) {
2619
2620 if (typeof types === 'object') {
2621 for (var type in types) {
2622 addOne(obj, type, types[type], fn);
2623 }
2624 } else {
2625 types = splitWords(types);
2626
2627 for (var i = 0, len = types.length; i < len; i++) {
2628 addOne(obj, types[i], fn, context);
2629 }
2630 }
2631
2632 return this;
2633}
2634
2635var eventsKey = '_leaflet_events';
2636
2637// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2638// Removes a previously added listener function.
2639// Note that if you passed a custom context to on, you must pass the same
2640// context to `off` in order to remove the listener.
2641
2642// @alternative
2643// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2644// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2645function off(obj, types, fn, context) {
2646
2647 if (typeof types === 'object') {
2648 for (var type in types) {
2649 removeOne(obj, type, types[type], fn);
2650 }
2651 } else if (types) {
2652 types = splitWords(types);
2653
2654 for (var i = 0, len = types.length; i < len; i++) {
2655 removeOne(obj, types[i], fn, context);
2656 }
2657 } else {
2658 for (var j in obj[eventsKey]) {
2659 removeOne(obj, j, obj[eventsKey][j]);
2660 }
2661 delete obj[eventsKey];
2662 }
2663
2664 return this;
2665}
2666
2667function addOne(obj, type, fn, context) {
2668 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2669
2670 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2671
2672 var handler = function (e) {
2673 return fn.call(context || obj, e || window.event);
2674 };
2675
2676 var originalHandler = handler;
2677
2678 if (pointer && type.indexOf('touch') === 0) {
2679 // Needs DomEvent.Pointer.js
2680 addPointerListener(obj, type, handler, id);
2681
2682 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2683 !(pointer && chrome)) {
2684 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2685 // See #5180
2686 addDoubleTapListener(obj, handler, id);
2687
2688 } else if ('addEventListener' in obj) {
2689
2690 if (type === 'mousewheel') {
2691 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2692
2693 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2694 handler = function (e) {
2695 e = e || window.event;
2696 if (isExternalTarget(obj, e)) {
2697 originalHandler(e);
2698 }
2699 };
2700 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2701
2702 } else {
2703 if (type === 'click' && android) {
2704 handler = function (e) {
2705 filterClick(e, originalHandler);
2706 };
2707 }
2708 obj.addEventListener(type, handler, false);
2709 }
2710
2711 } else if ('attachEvent' in obj) {
2712 obj.attachEvent('on' + type, handler);
2713 }
2714
2715 obj[eventsKey] = obj[eventsKey] || {};
2716 obj[eventsKey][id] = handler;
2717}
2718
2719function removeOne(obj, type, fn, context) {
2720
2721 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2722 handler = obj[eventsKey] && obj[eventsKey][id];
2723
2724 if (!handler) { return this; }
2725
2726 if (pointer && type.indexOf('touch') === 0) {
2727 removePointerListener(obj, type, id);
2728
2729 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2730 !(pointer && chrome)) {
2731 removeDoubleTapListener(obj, id);
2732
2733 } else if ('removeEventListener' in obj) {
2734
2735 if (type === 'mousewheel') {
2736 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2737
2738 } else {
2739 obj.removeEventListener(
2740 type === 'mouseenter' ? 'mouseover' :
2741 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2742 }
2743
2744 } else if ('detachEvent' in obj) {
2745 obj.detachEvent('on' + type, handler);
2746 }
2747
2748 obj[eventsKey][id] = null;
2749}
2750
2751// @function stopPropagation(ev: DOMEvent): this
2752// Stop the given event from propagation to parent elements. Used inside the listener functions:
2753// ```js
2754// L.DomEvent.on(div, 'click', function (ev) {
2755// L.DomEvent.stopPropagation(ev);
2756// });
2757// ```
2758function stopPropagation(e) {
2759
2760 if (e.stopPropagation) {
2761 e.stopPropagation();
2762 } else if (e.originalEvent) { // In case of Leaflet event.
2763 e.originalEvent._stopped = true;
2764 } else {
2765 e.cancelBubble = true;
2766 }
2767 skipped(e);
2768
2769 return this;
2770}
2771
2772// @function disableScrollPropagation(el: HTMLElement): this
2773// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2774function disableScrollPropagation(el) {
2775 addOne(el, 'mousewheel', stopPropagation);
2776 return this;
2777}
2778
2779// @function disableClickPropagation(el: HTMLElement): this
2780// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2781// `'mousedown'` and `'touchstart'` events (plus browser variants).
2782function disableClickPropagation(el) {
2783 on(el, 'mousedown touchstart dblclick', stopPropagation);
2784 addOne(el, 'click', fakeStop);
2785 return this;
2786}
2787
2788// @function preventDefault(ev: DOMEvent): this
2789// Prevents the default action of the DOM Event `ev` from happening (such as
2790// following a link in the href of the a element, or doing a POST request
2791// with page reload when a `<form>` is submitted).
2792// Use it inside listener functions.
2793function preventDefault(e) {
2794 if (e.preventDefault) {
2795 e.preventDefault();
2796 } else {
2797 e.returnValue = false;
2798 }
2799 return this;
2800}
2801
2802// @function stop(ev: DOMEvent): this
2803// Does `stopPropagation` and `preventDefault` at the same time.
2804function stop(e) {
2805 preventDefault(e);
2806 stopPropagation(e);
2807 return this;
2808}
2809
2810// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2811// Gets normalized mouse position from a DOM event relative to the
2812// `container` (border excluded) or to the whole page if not specified.
2813function getMousePosition(e, container) {
2814 if (!container) {
2815 return new Point(e.clientX, e.clientY);
2816 }
2817
2818 var scale = getScale(container),
2819 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2820
2821 return new Point(
2822 // offset.left/top values are in page scale (like clientX/Y),
2823 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2824 (e.clientX - offset.left) / scale.x - container.clientLeft,
2825 (e.clientY - offset.top) / scale.y - container.clientTop
2826 );
2827}
2828
2829// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2830// and Firefox scrolls device pixels, not CSS pixels
2831var wheelPxFactor =
2832 (win && chrome) ? 2 * window.devicePixelRatio :
2833 gecko ? window.devicePixelRatio : 1;
2834
2835// @function getWheelDelta(ev: DOMEvent): Number
2836// Gets normalized wheel delta from a mousewheel DOM event, in vertical
2837// pixels scrolled (negative if scrolling down).
2838// Events from pointing devices without precise scrolling are mapped to
2839// a best guess of 60 pixels.
2840function getWheelDelta(e) {
2841 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2842 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2843 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2844 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2845 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2846 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2847 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2848 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2849 0;
2850}
2851
2852var skipEvents = {};
2853
2854function fakeStop(e) {
2855 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2856 skipEvents[e.type] = true;
2857}
2858
2859function skipped(e) {
2860 var events = skipEvents[e.type];
2861 // reset when checking, as it's only used in map container and propagates outside of the map
2862 skipEvents[e.type] = false;
2863 return events;
2864}
2865
2866// check if element really left/entered the event target (for mouseenter/mouseleave)
2867function isExternalTarget(el, e) {
2868
2869 var related = e.relatedTarget;
2870
2871 if (!related) { return true; }
2872
2873 try {
2874 while (related && (related !== el)) {
2875 related = related.parentNode;
2876 }
2877 } catch (err) {
2878 return false;
2879 }
2880 return (related !== el);
2881}
2882
2883var lastClick;
2884
2885// this is a horrible workaround for a bug in Android where a single touch triggers two click events
2886function filterClick(e, handler) {
2887 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2888 elapsed = lastClick && (timeStamp - lastClick);
2889
2890 // are they closer together than 500ms yet more than 100ms?
2891 // Android typically triggers them ~300ms apart while multiple listeners
2892 // on the same event should be triggered far faster;
2893 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2894
2895 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2896 stop(e);
2897 return;
2898 }
2899 lastClick = timeStamp;
2900
2901 handler(e);
2902}
2903
2904
2905
2906
2907var DomEvent = (Object.freeze || Object)({
2908 on: on,
2909 off: off,
2910 stopPropagation: stopPropagation,
2911 disableScrollPropagation: disableScrollPropagation,
2912 disableClickPropagation: disableClickPropagation,
2913 preventDefault: preventDefault,
2914 stop: stop,
2915 getMousePosition: getMousePosition,
2916 getWheelDelta: getWheelDelta,
2917 fakeStop: fakeStop,
2918 skipped: skipped,
2919 isExternalTarget: isExternalTarget,
2920 addListener: on,
2921 removeListener: off
2922});
2923
2924/*
2925 * @class PosAnimation
2926 * @aka L.PosAnimation
2927 * @inherits Evented
2928 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2929 *
2930 * @example
2931 * ```js
2932 * var fx = new L.PosAnimation();
2933 * fx.run(el, [300, 500], 0.5);
2934 * ```
2935 *
2936 * @constructor L.PosAnimation()
2937 * Creates a `PosAnimation` object.
2938 *
2939 */
2940
2941var PosAnimation = Evented.extend({
2942
2943 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2944 // Run an animation of a given element to a new position, optionally setting
2945 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2946 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2947 // `0.5` by default).
2948 run: function (el, newPos, duration, easeLinearity) {
2949 this.stop();
2950
2951 this._el = el;
2952 this._inProgress = true;
2953 this._duration = duration || 0.25;
2954 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2955
2956 this._startPos = getPosition(el);
2957 this._offset = newPos.subtract(this._startPos);
2958 this._startTime = +new Date();
2959
2960 // @event start: Event
2961 // Fired when the animation starts
2962 this.fire('start');
2963
2964 this._animate();
2965 },
2966
2967 // @method stop()
2968 // Stops the animation (if currently running).
2969 stop: function () {
2970 if (!this._inProgress) { return; }
2971
2972 this._step(true);
2973 this._complete();
2974 },
2975
2976 _animate: function () {
2977 // animation loop
2978 this._animId = requestAnimFrame(this._animate, this);
2979 this._step();
2980 },
2981
2982 _step: function (round) {
2983 var elapsed = (+new Date()) - this._startTime,
2984 duration = this._duration * 1000;
2985
2986 if (elapsed < duration) {
2987 this._runFrame(this._easeOut(elapsed / duration), round);
2988 } else {
2989 this._runFrame(1);
2990 this._complete();
2991 }
2992 },
2993
2994 _runFrame: function (progress, round) {
2995 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2996 if (round) {
2997 pos._round();
2998 }
2999 setPosition(this._el, pos);
3000
3001 // @event step: Event
3002 // Fired continuously during the animation.
3003 this.fire('step');
3004 },
3005
3006 _complete: function () {
3007 cancelAnimFrame(this._animId);
3008
3009 this._inProgress = false;
3010 // @event end: Event
3011 // Fired when the animation ends.
3012 this.fire('end');
3013 },
3014
3015 _easeOut: function (t) {
3016 return 1 - Math.pow(1 - t, this._easeOutPower);
3017 }
3018});
3019
3020/*
3021 * @class Map
3022 * @aka L.Map
3023 * @inherits Evented
3024 *
3025 * The central class of the API — it is used to create a map on a page and manipulate it.
3026 *
3027 * @example
3028 *
3029 * ```js
3030 * // initialize the map on the "map" div with a given center and zoom
3031 * var map = L.map('map', {
3032 * center: [51.505, -0.09],
3033 * zoom: 13
3034 * });
3035 * ```
3036 *
3037 */
3038
3039var Map = Evented.extend({
3040
3041 options: {
3042 // @section Map State Options
3043 // @option crs: CRS = L.CRS.EPSG3857
3044 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3045 // sure what it means.
3046 crs: EPSG3857,
3047
3048 // @option center: LatLng = undefined
3049 // Initial geographic center of the map
3050 center: undefined,
3051
3052 // @option zoom: Number = undefined
3053 // Initial map zoom level
3054 zoom: undefined,
3055
3056 // @option minZoom: Number = *
3057 // Minimum zoom level of the map.
3058 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3059 // the lowest of their `minZoom` options will be used instead.
3060 minZoom: undefined,
3061
3062 // @option maxZoom: Number = *
3063 // Maximum zoom level of the map.
3064 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3065 // the highest of their `maxZoom` options will be used instead.
3066 maxZoom: undefined,
3067
3068 // @option layers: Layer[] = []
3069 // Array of layers that will be added to the map initially
3070 layers: [],
3071
3072 // @option maxBounds: LatLngBounds = null
3073 // When this option is set, the map restricts the view to the given
3074 // geographical bounds, bouncing the user back if the user tries to pan
3075 // outside the view. To set the restriction dynamically, use
3076 // [`setMaxBounds`](#map-setmaxbounds) method.
3077 maxBounds: undefined,
3078
3079 // @option renderer: Renderer = *
3080 // The default method for drawing vector layers on the map. `L.SVG`
3081 // or `L.Canvas` by default depending on browser support.
3082 renderer: undefined,
3083
3084
3085 // @section Animation Options
3086 // @option zoomAnimation: Boolean = true
3087 // Whether the map zoom animation is enabled. By default it's enabled
3088 // in all browsers that support CSS3 Transitions except Android.
3089 zoomAnimation: true,
3090
3091 // @option zoomAnimationThreshold: Number = 4
3092 // Won't animate zoom if the zoom difference exceeds this value.
3093 zoomAnimationThreshold: 4,
3094
3095 // @option fadeAnimation: Boolean = true
3096 // Whether the tile fade animation is enabled. By default it's enabled
3097 // in all browsers that support CSS3 Transitions except Android.
3098 fadeAnimation: true,
3099
3100 // @option markerZoomAnimation: Boolean = true
3101 // Whether markers animate their zoom with the zoom animation, if disabled
3102 // they will disappear for the length of the animation. By default it's
3103 // enabled in all browsers that support CSS3 Transitions except Android.
3104 markerZoomAnimation: true,
3105
3106 // @option transform3DLimit: Number = 2^23
3107 // Defines the maximum size of a CSS translation transform. The default
3108 // value should not be changed unless a web browser positions layers in
3109 // the wrong place after doing a large `panBy`.
3110 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3111
3112 // @section Interaction Options
3113 // @option zoomSnap: Number = 1
3114 // Forces the map's zoom level to always be a multiple of this, particularly
3115 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3116 // By default, the zoom level snaps to the nearest integer; lower values
3117 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3118 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3119 zoomSnap: 1,
3120
3121 // @option zoomDelta: Number = 1
3122 // Controls how much the map's zoom level will change after a
3123 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3124 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3125 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3126 zoomDelta: 1,
3127
3128 // @option trackResize: Boolean = true
3129 // Whether the map automatically handles browser window resize to update itself.
3130 trackResize: true
3131 },
3132
3133 initialize: function (id, options) { // (HTMLElement or String, Object)
3134 options = setOptions(this, options);
3135
3136 // Make sure to assign internal flags at the beginning,
3137 // to avoid inconsistent state in some edge cases.
3138 this._handlers = [];
3139 this._layers = {};
3140 this._zoomBoundLayers = {};
3141 this._sizeChanged = true;
3142
3143 this._initContainer(id);
3144 this._initLayout();
3145
3146 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3147 this._onResize = bind(this._onResize, this);
3148
3149 this._initEvents();
3150
3151 if (options.maxBounds) {
3152 this.setMaxBounds(options.maxBounds);
3153 }
3154
3155 if (options.zoom !== undefined) {
3156 this._zoom = this._limitZoom(options.zoom);
3157 }
3158
3159 if (options.center && options.zoom !== undefined) {
3160 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3161 }
3162
3163 this.callInitHooks();
3164
3165 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3166 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3167 this.options.zoomAnimation;
3168
3169 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3170 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3171 if (this._zoomAnimated) {
3172 this._createAnimProxy();
3173 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3174 }
3175
3176 this._addLayers(this.options.layers);
3177 },
3178
3179
3180 // @section Methods for modifying map state
3181
3182 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3183 // Sets the view of the map (geographical center and zoom) with the given
3184 // animation options.
3185 setView: function (center, zoom, options) {
3186
3187 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3188 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3189 options = options || {};
3190
3191 this._stop();
3192
3193 if (this._loaded && !options.reset && options !== true) {
3194
3195 if (options.animate !== undefined) {
3196 options.zoom = extend({animate: options.animate}, options.zoom);
3197 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3198 }
3199
3200 // try animating pan or zoom
3201 var moved = (this._zoom !== zoom) ?
3202 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3203 this._tryAnimatedPan(center, options.pan);
3204
3205 if (moved) {
3206 // prevent resize handler call, the view will refresh after animation anyway
3207 clearTimeout(this._sizeTimer);
3208 return this;
3209 }
3210 }
3211
3212 // animation didn't start, just reset the map view
3213 this._resetView(center, zoom);
3214
3215 return this;
3216 },
3217
3218 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3219 // Sets the zoom of the map.
3220 setZoom: function (zoom, options) {
3221 if (!this._loaded) {
3222 this._zoom = zoom;
3223 return this;
3224 }
3225 return this.setView(this.getCenter(), zoom, {zoom: options});
3226 },
3227
3228 // @method zoomIn(delta?: Number, options?: Zoom options): this
3229 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3230 zoomIn: function (delta, options) {
3231 delta = delta || (any3d ? this.options.zoomDelta : 1);
3232 return this.setZoom(this._zoom + delta, options);
3233 },
3234
3235 // @method zoomOut(delta?: Number, options?: Zoom options): this
3236 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3237 zoomOut: function (delta, options) {
3238 delta = delta || (any3d ? this.options.zoomDelta : 1);
3239 return this.setZoom(this._zoom - delta, options);
3240 },
3241
3242 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3243 // Zooms the map while keeping a specified geographical point on the map
3244 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3245 // @alternative
3246 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3247 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3248 setZoomAround: function (latlng, zoom, options) {
3249 var scale = this.getZoomScale(zoom),
3250 viewHalf = this.getSize().divideBy(2),
3251 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3252
3253 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3254 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3255
3256 return this.setView(newCenter, zoom, {zoom: options});
3257 },
3258
3259 _getBoundsCenterZoom: function (bounds, options) {
3260
3261 options = options || {};
3262 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3263
3264 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3265 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3266
3267 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3268
3269 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3270
3271 if (zoom === Infinity) {
3272 return {
3273 center: bounds.getCenter(),
3274 zoom: zoom
3275 };
3276 }
3277
3278 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3279
3280 swPoint = this.project(bounds.getSouthWest(), zoom),
3281 nePoint = this.project(bounds.getNorthEast(), zoom),
3282 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3283
3284 return {
3285 center: center,
3286 zoom: zoom
3287 };
3288 },
3289
3290 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3291 // Sets a map view that contains the given geographical bounds with the
3292 // maximum zoom level possible.
3293 fitBounds: function (bounds, options) {
3294
3295 bounds = toLatLngBounds(bounds);
3296
3297 if (!bounds.isValid()) {
3298 throw new Error('Bounds are not valid.');
3299 }
3300
3301 var target = this._getBoundsCenterZoom(bounds, options);
3302 return this.setView(target.center, target.zoom, options);
3303 },
3304
3305 // @method fitWorld(options?: fitBounds options): this
3306 // Sets a map view that mostly contains the whole world with the maximum
3307 // zoom level possible.
3308 fitWorld: function (options) {
3309 return this.fitBounds([[-90, -180], [90, 180]], options);
3310 },
3311
3312 // @method panTo(latlng: LatLng, options?: Pan options): this
3313 // Pans the map to a given center.
3314 panTo: function (center, options) { // (LatLng)
3315 return this.setView(center, this._zoom, {pan: options});
3316 },
3317
3318 // @method panBy(offset: Point, options?: Pan options): this
3319 // Pans the map by a given number of pixels (animated).
3320 panBy: function (offset, options) {
3321 offset = toPoint(offset).round();
3322 options = options || {};
3323
3324 if (!offset.x && !offset.y) {
3325 return this.fire('moveend');
3326 }
3327 // If we pan too far, Chrome gets issues with tiles
3328 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3329 if (options.animate !== true && !this.getSize().contains(offset)) {
3330 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3331 return this;
3332 }
3333
3334 if (!this._panAnim) {
3335 this._panAnim = new PosAnimation();
3336
3337 this._panAnim.on({
3338 'step': this._onPanTransitionStep,
3339 'end': this._onPanTransitionEnd
3340 }, this);
3341 }
3342
3343 // don't fire movestart if animating inertia
3344 if (!options.noMoveStart) {
3345 this.fire('movestart');
3346 }
3347
3348 // animate pan unless animate: false specified
3349 if (options.animate !== false) {
3350 addClass(this._mapPane, 'leaflet-pan-anim');
3351
3352 var newPos = this._getMapPanePos().subtract(offset).round();
3353 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3354 } else {
3355 this._rawPanBy(offset);
3356 this.fire('move').fire('moveend');
3357 }
3358
3359 return this;
3360 },
3361
3362 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3363 // Sets the view of the map (geographical center and zoom) performing a smooth
3364 // pan-zoom animation.
3365 flyTo: function (targetCenter, targetZoom, options) {
3366
3367 options = options || {};
3368 if (options.animate === false || !any3d) {
3369 return this.setView(targetCenter, targetZoom, options);
3370 }
3371
3372 this._stop();
3373
3374 var from = this.project(this.getCenter()),
3375 to = this.project(targetCenter),
3376 size = this.getSize(),
3377 startZoom = this._zoom;
3378
3379 targetCenter = toLatLng(targetCenter);
3380 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3381
3382 var w0 = Math.max(size.x, size.y),
3383 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3384 u1 = (to.distanceTo(from)) || 1,
3385 rho = 1.42,
3386 rho2 = rho * rho;
3387
3388 function r(i) {
3389 var s1 = i ? -1 : 1,
3390 s2 = i ? w1 : w0,
3391 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3392 b1 = 2 * s2 * rho2 * u1,
3393 b = t1 / b1,
3394 sq = Math.sqrt(b * b + 1) - b;
3395
3396 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3397 // thus triggering an infinite loop in flyTo
3398 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3399
3400 return log;
3401 }
3402
3403 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3404 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3405 function tanh(n) { return sinh(n) / cosh(n); }
3406
3407 var r0 = r(0);
3408
3409 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3410 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3411
3412 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3413
3414 var start = Date.now(),
3415 S = (r(1) - r0) / rho,
3416 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3417
3418 function frame() {
3419 var t = (Date.now() - start) / duration,
3420 s = easeOut(t) * S;
3421
3422 if (t <= 1) {
3423 this._flyToFrame = requestAnimFrame(frame, this);
3424
3425 this._move(
3426 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3427 this.getScaleZoom(w0 / w(s), startZoom),
3428 {flyTo: true});
3429
3430 } else {
3431 this
3432 ._move(targetCenter, targetZoom)
3433 ._moveEnd(true);
3434 }
3435 }
3436
3437 this._moveStart(true, options.noMoveStart);
3438
3439 frame.call(this);
3440 return this;
3441 },
3442
3443 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3444 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3445 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3446 flyToBounds: function (bounds, options) {
3447 var target = this._getBoundsCenterZoom(bounds, options);
3448 return this.flyTo(target.center, target.zoom, options);
3449 },
3450
3451 // @method setMaxBounds(bounds: Bounds): this
3452 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3453 setMaxBounds: function (bounds) {
3454 bounds = toLatLngBounds(bounds);
3455
3456 if (!bounds.isValid()) {
3457 this.options.maxBounds = null;
3458 return this.off('moveend', this._panInsideMaxBounds);
3459 } else if (this.options.maxBounds) {
3460 this.off('moveend', this._panInsideMaxBounds);
3461 }
3462
3463 this.options.maxBounds = bounds;
3464
3465 if (this._loaded) {
3466 this._panInsideMaxBounds();
3467 }
3468
3469 return this.on('moveend', this._panInsideMaxBounds);
3470 },
3471
3472 // @method setMinZoom(zoom: Number): this
3473 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3474 setMinZoom: function (zoom) {
3475 var oldZoom = this.options.minZoom;
3476 this.options.minZoom = zoom;
3477
3478 if (this._loaded && oldZoom !== zoom) {
3479 this.fire('zoomlevelschange');
3480
3481 if (this.getZoom() < this.options.minZoom) {
3482 return this.setZoom(zoom);
3483 }
3484 }
3485
3486 return this;
3487 },
3488
3489 // @method setMaxZoom(zoom: Number): this
3490 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3491 setMaxZoom: function (zoom) {
3492 var oldZoom = this.options.maxZoom;
3493 this.options.maxZoom = zoom;
3494
3495 if (this._loaded && oldZoom !== zoom) {
3496 this.fire('zoomlevelschange');
3497
3498 if (this.getZoom() > this.options.maxZoom) {
3499 return this.setZoom(zoom);
3500 }
3501 }
3502
3503 return this;
3504 },
3505
3506 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3507 // 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.
3508 panInsideBounds: function (bounds, options) {
3509 this._enforcingBounds = true;
3510 var center = this.getCenter(),
3511 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3512
3513 if (!center.equals(newCenter)) {
3514 this.panTo(newCenter, options);
3515 }
3516
3517 this._enforcingBounds = false;
3518 return this;
3519 },
3520
3521 // @method panInside(latlng: LatLng, options?: options): this
3522 // Pans the map the minimum amount to make the `latlng` visible. Use
3523 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3524 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3525 // If `latlng` is already within the (optionally padded) display bounds,
3526 // the map will not be panned.
3527 panInside: function (latlng, options) {
3528 options = options || {};
3529
3530 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3531 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3532 center = this.getCenter(),
3533 pixelCenter = this.project(center),
3534 pixelPoint = this.project(latlng),
3535 pixelBounds = this.getPixelBounds(),
3536 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3537 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3538
3539 if (!paddedBounds.contains(pixelPoint)) {
3540 this._enforcingBounds = true;
3541 var diff = pixelCenter.subtract(pixelPoint),
3542 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3543
3544 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3545 newCenter.x = pixelCenter.x - diff.x;
3546 if (diff.x > 0) {
3547 newCenter.x += halfPixelBounds.x - paddingTL.x;
3548 } else {
3549 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3550 }
3551 }
3552 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3553 newCenter.y = pixelCenter.y - diff.y;
3554 if (diff.y > 0) {
3555 newCenter.y += halfPixelBounds.y - paddingTL.y;
3556 } else {
3557 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3558 }
3559 }
3560 this.panTo(this.unproject(newCenter), options);
3561 this._enforcingBounds = false;
3562 }
3563 return this;
3564 },
3565
3566 // @method invalidateSize(options: Zoom/pan options): this
3567 // Checks if the map container size changed and updates the map if so —
3568 // call it after you've changed the map size dynamically, also animating
3569 // pan by default. If `options.pan` is `false`, panning will not occur.
3570 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3571 // that it doesn't happen often even if the method is called many
3572 // times in a row.
3573
3574 // @alternative
3575 // @method invalidateSize(animate: Boolean): this
3576 // Checks if the map container size changed and updates the map if so —
3577 // call it after you've changed the map size dynamically, also animating
3578 // pan by default.
3579 invalidateSize: function (options) {
3580 if (!this._loaded) { return this; }
3581
3582 options = extend({
3583 animate: false,
3584 pan: true
3585 }, options === true ? {animate: true} : options);
3586
3587 var oldSize = this.getSize();
3588 this._sizeChanged = true;
3589 this._lastCenter = null;
3590
3591 var newSize = this.getSize(),
3592 oldCenter = oldSize.divideBy(2).round(),
3593 newCenter = newSize.divideBy(2).round(),
3594 offset = oldCenter.subtract(newCenter);
3595
3596 if (!offset.x && !offset.y) { return this; }
3597
3598 if (options.animate && options.pan) {
3599 this.panBy(offset);
3600
3601 } else {
3602 if (options.pan) {
3603 this._rawPanBy(offset);
3604 }
3605
3606 this.fire('move');
3607
3608 if (options.debounceMoveend) {
3609 clearTimeout(this._sizeTimer);
3610 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3611 } else {
3612 this.fire('moveend');
3613 }
3614 }
3615
3616 // @section Map state change events
3617 // @event resize: ResizeEvent
3618 // Fired when the map is resized.
3619 return this.fire('resize', {
3620 oldSize: oldSize,
3621 newSize: newSize
3622 });
3623 },
3624
3625 // @section Methods for modifying map state
3626 // @method stop(): this
3627 // Stops the currently running `panTo` or `flyTo` animation, if any.
3628 stop: function () {
3629 this.setZoom(this._limitZoom(this._zoom));
3630 if (!this.options.zoomSnap) {
3631 this.fire('viewreset');
3632 }
3633 return this._stop();
3634 },
3635
3636 // @section Geolocation methods
3637 // @method locate(options?: Locate options): this
3638 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3639 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3640 // and optionally sets the map view to the user's location with respect to
3641 // detection accuracy (or to the world view if geolocation failed).
3642 // Note that, if your page doesn't use HTTPS, this method will fail in
3643 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3644 // See `Locate options` for more details.
3645 locate: function (options) {
3646
3647 options = this._locateOptions = extend({
3648 timeout: 10000,
3649 watch: false
3650 // setView: false
3651 // maxZoom: <Number>
3652 // maximumAge: 0
3653 // enableHighAccuracy: false
3654 }, options);
3655
3656 if (!('geolocation' in navigator)) {
3657 this._handleGeolocationError({
3658 code: 0,
3659 message: 'Geolocation not supported.'
3660 });
3661 return this;
3662 }
3663
3664 var onResponse = bind(this._handleGeolocationResponse, this),
3665 onError = bind(this._handleGeolocationError, this);
3666
3667 if (options.watch) {
3668 this._locationWatchId =
3669 navigator.geolocation.watchPosition(onResponse, onError, options);
3670 } else {
3671 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3672 }
3673 return this;
3674 },
3675
3676 // @method stopLocate(): this
3677 // Stops watching location previously initiated by `map.locate({watch: true})`
3678 // and aborts resetting the map view if map.locate was called with
3679 // `{setView: true}`.
3680 stopLocate: function () {
3681 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3682 navigator.geolocation.clearWatch(this._locationWatchId);
3683 }
3684 if (this._locateOptions) {
3685 this._locateOptions.setView = false;
3686 }
3687 return this;
3688 },
3689
3690 _handleGeolocationError: function (error) {
3691 var c = error.code,
3692 message = error.message ||
3693 (c === 1 ? 'permission denied' :
3694 (c === 2 ? 'position unavailable' : 'timeout'));
3695
3696 if (this._locateOptions.setView && !this._loaded) {
3697 this.fitWorld();
3698 }
3699
3700 // @section Location events
3701 // @event locationerror: ErrorEvent
3702 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3703 this.fire('locationerror', {
3704 code: c,
3705 message: 'Geolocation error: ' + message + '.'
3706 });
3707 },
3708
3709 _handleGeolocationResponse: function (pos) {
3710 var lat = pos.coords.latitude,
3711 lng = pos.coords.longitude,
3712 latlng = new LatLng(lat, lng),
3713 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3714 options = this._locateOptions;
3715
3716 if (options.setView) {
3717 var zoom = this.getBoundsZoom(bounds);
3718 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3719 }
3720
3721 var data = {
3722 latlng: latlng,
3723 bounds: bounds,
3724 timestamp: pos.timestamp
3725 };
3726
3727 for (var i in pos.coords) {
3728 if (typeof pos.coords[i] === 'number') {
3729 data[i] = pos.coords[i];
3730 }
3731 }
3732
3733 // @event locationfound: LocationEvent
3734 // Fired when geolocation (using the [`locate`](#map-locate) method)
3735 // went successfully.
3736 this.fire('locationfound', data);
3737 },
3738
3739 // TODO Appropriate docs section?
3740 // @section Other Methods
3741 // @method addHandler(name: String, HandlerClass: Function): this
3742 // Adds a new `Handler` to the map, given its name and constructor function.
3743 addHandler: function (name, HandlerClass) {
3744 if (!HandlerClass) { return this; }
3745
3746 var handler = this[name] = new HandlerClass(this);
3747
3748 this._handlers.push(handler);
3749
3750 if (this.options[name]) {
3751 handler.enable();
3752 }
3753
3754 return this;
3755 },
3756
3757 // @method remove(): this
3758 // Destroys the map and clears all related event listeners.
3759 remove: function () {
3760
3761 this._initEvents(true);
3762
3763 if (this._containerId !== this._container._leaflet_id) {
3764 throw new Error('Map container is being reused by another instance');
3765 }
3766
3767 try {
3768 // throws error in IE6-8
3769 delete this._container._leaflet_id;
3770 delete this._containerId;
3771 } catch (e) {
3772 /*eslint-disable */
3773 this._container._leaflet_id = undefined;
3774 /* eslint-enable */
3775 this._containerId = undefined;
3776 }
3777
3778 if (this._locationWatchId !== undefined) {
3779 this.stopLocate();
3780 }
3781
3782 this._stop();
3783
3784 remove(this._mapPane);
3785
3786 if (this._clearControlPos) {
3787 this._clearControlPos();
3788 }
3789 if (this._resizeRequest) {
3790 cancelAnimFrame(this._resizeRequest);
3791 this._resizeRequest = null;
3792 }
3793
3794 this._clearHandlers();
3795
3796 if (this._loaded) {
3797 // @section Map state change events
3798 // @event unload: Event
3799 // Fired when the map is destroyed with [remove](#map-remove) method.
3800 this.fire('unload');
3801 }
3802
3803 var i;
3804 for (i in this._layers) {
3805 this._layers[i].remove();
3806 }
3807 for (i in this._panes) {
3808 remove(this._panes[i]);
3809 }
3810
3811 this._layers = [];
3812 this._panes = [];
3813 delete this._mapPane;
3814 delete this._renderer;
3815
3816 return this;
3817 },
3818
3819 // @section Other Methods
3820 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3821 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3822 // then returns it. The pane is created as a child of `container`, or
3823 // as a child of the main map pane if not set.
3824 createPane: function (name, container) {
3825 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3826 pane = create$1('div', className, container || this._mapPane);
3827
3828 if (name) {
3829 this._panes[name] = pane;
3830 }
3831 return pane;
3832 },
3833
3834 // @section Methods for Getting Map State
3835
3836 // @method getCenter(): LatLng
3837 // Returns the geographical center of the map view
3838 getCenter: function () {
3839 this._checkIfLoaded();
3840
3841 if (this._lastCenter && !this._moved()) {
3842 return this._lastCenter;
3843 }
3844 return this.layerPointToLatLng(this._getCenterLayerPoint());
3845 },
3846
3847 // @method getZoom(): Number
3848 // Returns the current zoom level of the map view
3849 getZoom: function () {
3850 return this._zoom;
3851 },
3852
3853 // @method getBounds(): LatLngBounds
3854 // Returns the geographical bounds visible in the current map view
3855 getBounds: function () {
3856 var bounds = this.getPixelBounds(),
3857 sw = this.unproject(bounds.getBottomLeft()),
3858 ne = this.unproject(bounds.getTopRight());
3859
3860 return new LatLngBounds(sw, ne);
3861 },
3862
3863 // @method getMinZoom(): Number
3864 // 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.
3865 getMinZoom: function () {
3866 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3867 },
3868
3869 // @method getMaxZoom(): Number
3870 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3871 getMaxZoom: function () {
3872 return this.options.maxZoom === undefined ?
3873 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3874 this.options.maxZoom;
3875 },
3876
3877 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3878 // Returns the maximum zoom level on which the given bounds fit to the map
3879 // view in its entirety. If `inside` (optional) is set to `true`, the method
3880 // instead returns the minimum zoom level on which the map view fits into
3881 // the given bounds in its entirety.
3882 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3883 bounds = toLatLngBounds(bounds);
3884 padding = toPoint(padding || [0, 0]);
3885
3886 var zoom = this.getZoom() || 0,
3887 min = this.getMinZoom(),
3888 max = this.getMaxZoom(),
3889 nw = bounds.getNorthWest(),
3890 se = bounds.getSouthEast(),
3891 size = this.getSize().subtract(padding),
3892 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3893 snap = any3d ? this.options.zoomSnap : 1,
3894 scalex = size.x / boundsSize.x,
3895 scaley = size.y / boundsSize.y,
3896 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3897
3898 zoom = this.getScaleZoom(scale, zoom);
3899
3900 if (snap) {
3901 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3902 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3903 }
3904
3905 return Math.max(min, Math.min(max, zoom));
3906 },
3907
3908 // @method getSize(): Point
3909 // Returns the current size of the map container (in pixels).
3910 getSize: function () {
3911 if (!this._size || this._sizeChanged) {
3912 this._size = new Point(
3913 this._container.clientWidth || 0,
3914 this._container.clientHeight || 0);
3915
3916 this._sizeChanged = false;
3917 }
3918 return this._size.clone();
3919 },
3920
3921 // @method getPixelBounds(): Bounds
3922 // Returns the bounds of the current map view in projected pixel
3923 // coordinates (sometimes useful in layer and overlay implementations).
3924 getPixelBounds: function (center, zoom) {
3925 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3926 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3927 },
3928
3929 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3930 // the map pane? "left point of the map layer" can be confusing, specially
3931 // since there can be negative offsets.
3932 // @method getPixelOrigin(): Point
3933 // Returns the projected pixel coordinates of the top left point of
3934 // the map layer (useful in custom layer and overlay implementations).
3935 getPixelOrigin: function () {
3936 this._checkIfLoaded();
3937 return this._pixelOrigin;
3938 },
3939
3940 // @method getPixelWorldBounds(zoom?: Number): Bounds
3941 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3942 // If `zoom` is omitted, the map's current zoom level is used.
3943 getPixelWorldBounds: function (zoom) {
3944 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3945 },
3946
3947 // @section Other Methods
3948
3949 // @method getPane(pane: String|HTMLElement): HTMLElement
3950 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3951 getPane: function (pane) {
3952 return typeof pane === 'string' ? this._panes[pane] : pane;
3953 },
3954
3955 // @method getPanes(): Object
3956 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3957 // the panes as values.
3958 getPanes: function () {
3959 return this._panes;
3960 },
3961
3962 // @method getContainer: HTMLElement
3963 // Returns the HTML element that contains the map.
3964 getContainer: function () {
3965 return this._container;
3966 },
3967
3968
3969 // @section Conversion Methods
3970
3971 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3972 // Returns the scale factor to be applied to a map transition from zoom level
3973 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3974 getZoomScale: function (toZoom, fromZoom) {
3975 // TODO replace with universal implementation after refactoring projections
3976 var crs = this.options.crs;
3977 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3978 return crs.scale(toZoom) / crs.scale(fromZoom);
3979 },
3980
3981 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3982 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3983 // level and everything is scaled by a factor of `scale`. Inverse of
3984 // [`getZoomScale`](#map-getZoomScale).
3985 getScaleZoom: function (scale, fromZoom) {
3986 var crs = this.options.crs;
3987 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3988 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3989 return isNaN(zoom) ? Infinity : zoom;
3990 },
3991
3992 // @method project(latlng: LatLng, zoom: Number): Point
3993 // Projects a geographical coordinate `LatLng` according to the projection
3994 // of the map's CRS, then scales it according to `zoom` and the CRS's
3995 // `Transformation`. The result is pixel coordinate relative to
3996 // the CRS origin.
3997 project: function (latlng, zoom) {
3998 zoom = zoom === undefined ? this._zoom : zoom;
3999 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
4000 },
4001
4002 // @method unproject(point: Point, zoom: Number): LatLng
4003 // Inverse of [`project`](#map-project).
4004 unproject: function (point, zoom) {
4005 zoom = zoom === undefined ? this._zoom : zoom;
4006 return this.options.crs.pointToLatLng(toPoint(point), zoom);
4007 },
4008
4009 // @method layerPointToLatLng(point: Point): LatLng
4010 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4011 // returns the corresponding geographical coordinate (for the current zoom level).
4012 layerPointToLatLng: function (point) {
4013 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4014 return this.unproject(projectedPoint);
4015 },
4016
4017 // @method latLngToLayerPoint(latlng: LatLng): Point
4018 // Given a geographical coordinate, returns the corresponding pixel coordinate
4019 // relative to the [origin pixel](#map-getpixelorigin).
4020 latLngToLayerPoint: function (latlng) {
4021 var projectedPoint = this.project(toLatLng(latlng))._round();
4022 return projectedPoint._subtract(this.getPixelOrigin());
4023 },
4024
4025 // @method wrapLatLng(latlng: LatLng): LatLng
4026 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4027 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4028 // CRS's bounds.
4029 // By default this means longitude is wrapped around the dateline so its
4030 // value is between -180 and +180 degrees.
4031 wrapLatLng: function (latlng) {
4032 return this.options.crs.wrapLatLng(toLatLng(latlng));
4033 },
4034
4035 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4036 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4037 // its center is within the CRS's bounds.
4038 // By default this means the center longitude is wrapped around the dateline so its
4039 // value is between -180 and +180 degrees, and the majority of the bounds
4040 // overlaps the CRS's bounds.
4041 wrapLatLngBounds: function (latlng) {
4042 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4043 },
4044
4045 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4046 // Returns the distance between two geographical coordinates according to
4047 // the map's CRS. By default this measures distance in meters.
4048 distance: function (latlng1, latlng2) {
4049 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4050 },
4051
4052 // @method containerPointToLayerPoint(point: Point): Point
4053 // Given a pixel coordinate relative to the map container, returns the corresponding
4054 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4055 containerPointToLayerPoint: function (point) { // (Point)
4056 return toPoint(point).subtract(this._getMapPanePos());
4057 },
4058
4059 // @method layerPointToContainerPoint(point: Point): Point
4060 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4061 // returns the corresponding pixel coordinate relative to the map container.
4062 layerPointToContainerPoint: function (point) { // (Point)
4063 return toPoint(point).add(this._getMapPanePos());
4064 },
4065
4066 // @method containerPointToLatLng(point: Point): LatLng
4067 // Given a pixel coordinate relative to the map container, returns
4068 // the corresponding geographical coordinate (for the current zoom level).
4069 containerPointToLatLng: function (point) {
4070 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4071 return this.layerPointToLatLng(layerPoint);
4072 },
4073
4074 // @method latLngToContainerPoint(latlng: LatLng): Point
4075 // Given a geographical coordinate, returns the corresponding pixel coordinate
4076 // relative to the map container.
4077 latLngToContainerPoint: function (latlng) {
4078 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4079 },
4080
4081 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4082 // Given a MouseEvent object, returns the pixel coordinate relative to the
4083 // map container where the event took place.
4084 mouseEventToContainerPoint: function (e) {
4085 return getMousePosition(e, this._container);
4086 },
4087
4088 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4089 // Given a MouseEvent object, returns the pixel coordinate relative to
4090 // the [origin pixel](#map-getpixelorigin) where the event took place.
4091 mouseEventToLayerPoint: function (e) {
4092 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4093 },
4094
4095 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4096 // Given a MouseEvent object, returns geographical coordinate where the
4097 // event took place.
4098 mouseEventToLatLng: function (e) { // (MouseEvent)
4099 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4100 },
4101
4102
4103 // map initialization methods
4104
4105 _initContainer: function (id) {
4106 var container = this._container = get(id);
4107
4108 if (!container) {
4109 throw new Error('Map container not found.');
4110 } else if (container._leaflet_id) {
4111 throw new Error('Map container is already initialized.');
4112 }
4113
4114 on(container, 'scroll', this._onScroll, this);
4115 this._containerId = stamp(container);
4116 },
4117
4118 _initLayout: function () {
4119 var container = this._container;
4120
4121 this._fadeAnimated = this.options.fadeAnimation && any3d;
4122
4123 addClass(container, 'leaflet-container' +
4124 (touch ? ' leaflet-touch' : '') +
4125 (retina ? ' leaflet-retina' : '') +
4126 (ielt9 ? ' leaflet-oldie' : '') +
4127 (safari ? ' leaflet-safari' : '') +
4128 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4129
4130 var position = getStyle(container, 'position');
4131
4132 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4133 container.style.position = 'relative';
4134 }
4135
4136 this._initPanes();
4137
4138 if (this._initControlPos) {
4139 this._initControlPos();
4140 }
4141 },
4142
4143 _initPanes: function () {
4144 var panes = this._panes = {};
4145 this._paneRenderers = {};
4146
4147 // @section
4148 //
4149 // Panes are DOM elements used to control the ordering of layers on the map. You
4150 // can access panes with [`map.getPane`](#map-getpane) or
4151 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4152 // [`map.createPane`](#map-createpane) method.
4153 //
4154 // Every map has the following default panes that differ only in zIndex.
4155 //
4156 // @pane mapPane: HTMLElement = 'auto'
4157 // Pane that contains all other map panes
4158
4159 this._mapPane = this.createPane('mapPane', this._container);
4160 setPosition(this._mapPane, new Point(0, 0));
4161
4162 // @pane tilePane: HTMLElement = 200
4163 // Pane for `GridLayer`s and `TileLayer`s
4164 this.createPane('tilePane');
4165 // @pane overlayPane: HTMLElement = 400
4166 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4167 this.createPane('shadowPane');
4168 // @pane shadowPane: HTMLElement = 500
4169 // Pane for overlay shadows (e.g. `Marker` shadows)
4170 this.createPane('overlayPane');
4171 // @pane markerPane: HTMLElement = 600
4172 // Pane for `Icon`s of `Marker`s
4173 this.createPane('markerPane');
4174 // @pane tooltipPane: HTMLElement = 650
4175 // Pane for `Tooltip`s.
4176 this.createPane('tooltipPane');
4177 // @pane popupPane: HTMLElement = 700
4178 // Pane for `Popup`s.
4179 this.createPane('popupPane');
4180
4181 if (!this.options.markerZoomAnimation) {
4182 addClass(panes.markerPane, 'leaflet-zoom-hide');
4183 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4184 }
4185 },
4186
4187
4188 // private methods that modify map state
4189
4190 // @section Map state change events
4191 _resetView: function (center, zoom) {
4192 setPosition(this._mapPane, new Point(0, 0));
4193
4194 var loading = !this._loaded;
4195 this._loaded = true;
4196 zoom = this._limitZoom(zoom);
4197
4198 this.fire('viewprereset');
4199
4200 var zoomChanged = this._zoom !== zoom;
4201 this
4202 ._moveStart(zoomChanged, false)
4203 ._move(center, zoom)
4204 ._moveEnd(zoomChanged);
4205
4206 // @event viewreset: Event
4207 // Fired when the map needs to redraw its content (this usually happens
4208 // on map zoom or load). Very useful for creating custom overlays.
4209 this.fire('viewreset');
4210
4211 // @event load: Event
4212 // Fired when the map is initialized (when its center and zoom are set
4213 // for the first time).
4214 if (loading) {
4215 this.fire('load');
4216 }
4217 },
4218
4219 _moveStart: function (zoomChanged, noMoveStart) {
4220 // @event zoomstart: Event
4221 // Fired when the map zoom is about to change (e.g. before zoom animation).
4222 // @event movestart: Event
4223 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4224 if (zoomChanged) {
4225 this.fire('zoomstart');
4226 }
4227 if (!noMoveStart) {
4228 this.fire('movestart');
4229 }
4230 return this;
4231 },
4232
4233 _move: function (center, zoom, data) {
4234 if (zoom === undefined) {
4235 zoom = this._zoom;
4236 }
4237 var zoomChanged = this._zoom !== zoom;
4238
4239 this._zoom = zoom;
4240 this._lastCenter = center;
4241 this._pixelOrigin = this._getNewPixelOrigin(center);
4242
4243 // @event zoom: Event
4244 // Fired repeatedly during any change in zoom level, including zoom
4245 // and fly animations.
4246 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4247 this.fire('zoom', data);
4248 }
4249
4250 // @event move: Event
4251 // Fired repeatedly during any movement of the map, including pan and
4252 // fly animations.
4253 return this.fire('move', data);
4254 },
4255
4256 _moveEnd: function (zoomChanged) {
4257 // @event zoomend: Event
4258 // Fired when the map has changed, after any animations.
4259 if (zoomChanged) {
4260 this.fire('zoomend');
4261 }
4262
4263 // @event moveend: Event
4264 // Fired when the center of the map stops changing (e.g. user stopped
4265 // dragging the map).
4266 return this.fire('moveend');
4267 },
4268
4269 _stop: function () {
4270 cancelAnimFrame(this._flyToFrame);
4271 if (this._panAnim) {
4272 this._panAnim.stop();
4273 }
4274 return this;
4275 },
4276
4277 _rawPanBy: function (offset) {
4278 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4279 },
4280
4281 _getZoomSpan: function () {
4282 return this.getMaxZoom() - this.getMinZoom();
4283 },
4284
4285 _panInsideMaxBounds: function () {
4286 if (!this._enforcingBounds) {
4287 this.panInsideBounds(this.options.maxBounds);
4288 }
4289 },
4290
4291 _checkIfLoaded: function () {
4292 if (!this._loaded) {
4293 throw new Error('Set map center and zoom first.');
4294 }
4295 },
4296
4297 // DOM event handling
4298
4299 // @section Interaction events
4300 _initEvents: function (remove$$1) {
4301 this._targets = {};
4302 this._targets[stamp(this._container)] = this;
4303
4304 var onOff = remove$$1 ? off : on;
4305
4306 // @event click: MouseEvent
4307 // Fired when the user clicks (or taps) the map.
4308 // @event dblclick: MouseEvent
4309 // Fired when the user double-clicks (or double-taps) the map.
4310 // @event mousedown: MouseEvent
4311 // Fired when the user pushes the mouse button on the map.
4312 // @event mouseup: MouseEvent
4313 // Fired when the user releases the mouse button on the map.
4314 // @event mouseover: MouseEvent
4315 // Fired when the mouse enters the map.
4316 // @event mouseout: MouseEvent
4317 // Fired when the mouse leaves the map.
4318 // @event mousemove: MouseEvent
4319 // Fired while the mouse moves over the map.
4320 // @event contextmenu: MouseEvent
4321 // Fired when the user pushes the right mouse button on the map, prevents
4322 // default browser context menu from showing if there are listeners on
4323 // this event. Also fired on mobile when the user holds a single touch
4324 // for a second (also called long press).
4325 // @event keypress: KeyboardEvent
4326 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4327 // @event keydown: KeyboardEvent
4328 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4329 // the `keydown` event is fired for keys that produce a character value and for keys
4330 // that do not produce a character value.
4331 // @event keyup: KeyboardEvent
4332 // Fired when the user releases a key from the keyboard while the map is focused.
4333 onOff(this._container, 'click dblclick mousedown mouseup ' +
4334 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4335
4336 if (this.options.trackResize) {
4337 onOff(window, 'resize', this._onResize, this);
4338 }
4339
4340 if (any3d && this.options.transform3DLimit) {
4341 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4342 }
4343 },
4344
4345 _onResize: function () {
4346 cancelAnimFrame(this._resizeRequest);
4347 this._resizeRequest = requestAnimFrame(
4348 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4349 },
4350
4351 _onScroll: function () {
4352 this._container.scrollTop = 0;
4353 this._container.scrollLeft = 0;
4354 },
4355
4356 _onMoveEnd: function () {
4357 var pos = this._getMapPanePos();
4358 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4359 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4360 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4361 this._resetView(this.getCenter(), this.getZoom());
4362 }
4363 },
4364
4365 _findEventTargets: function (e, type) {
4366 var targets = [],
4367 target,
4368 isHover = type === 'mouseout' || type === 'mouseover',
4369 src = e.target || e.srcElement,
4370 dragging = false;
4371
4372 while (src) {
4373 target = this._targets[stamp(src)];
4374 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4375 // Prevent firing click after you just dragged an object.
4376 dragging = true;
4377 break;
4378 }
4379 if (target && target.listens(type, true)) {
4380 if (isHover && !isExternalTarget(src, e)) { break; }
4381 targets.push(target);
4382 if (isHover) { break; }
4383 }
4384 if (src === this._container) { break; }
4385 src = src.parentNode;
4386 }
4387 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4388 targets = [this];
4389 }
4390 return targets;
4391 },
4392
4393 _handleDOMEvent: function (e) {
4394 if (!this._loaded || skipped(e)) { return; }
4395
4396 var type = e.type;
4397
4398 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4399 // prevents outline when clicking on keyboard-focusable element
4400 preventOutline(e.target || e.srcElement);
4401 }
4402
4403 this._fireDOMEvent(e, type);
4404 },
4405
4406 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4407
4408 _fireDOMEvent: function (e, type, targets) {
4409
4410 if (e.type === 'click') {
4411 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4412 // @event preclick: MouseEvent
4413 // Fired before mouse click on the map (sometimes useful when you
4414 // want something to happen on click before any existing click
4415 // handlers start running).
4416 var synth = extend({}, e);
4417 synth.type = 'preclick';
4418 this._fireDOMEvent(synth, synth.type, targets);
4419 }
4420
4421 if (e._stopped) { return; }
4422
4423 // Find the layer the event is propagating from and its parents.
4424 targets = (targets || []).concat(this._findEventTargets(e, type));
4425
4426 if (!targets.length) { return; }
4427
4428 var target = targets[0];
4429 if (type === 'contextmenu' && target.listens(type, true)) {
4430 preventDefault(e);
4431 }
4432
4433 var data = {
4434 originalEvent: e
4435 };
4436
4437 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4438 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4439 data.containerPoint = isMarker ?
4440 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4441 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4442 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4443 }
4444
4445 for (var i = 0; i < targets.length; i++) {
4446 targets[i].fire(type, data, true);
4447 if (data.originalEvent._stopped ||
4448 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4449 }
4450 },
4451
4452 _draggableMoved: function (obj) {
4453 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4454 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4455 },
4456
4457 _clearHandlers: function () {
4458 for (var i = 0, len = this._handlers.length; i < len; i++) {
4459 this._handlers[i].disable();
4460 }
4461 },
4462
4463 // @section Other Methods
4464
4465 // @method whenReady(fn: Function, context?: Object): this
4466 // Runs the given function `fn` when the map gets initialized with
4467 // a view (center and zoom) and at least one layer, or immediately
4468 // if it's already initialized, optionally passing a function context.
4469 whenReady: function (callback, context) {
4470 if (this._loaded) {
4471 callback.call(context || this, {target: this});
4472 } else {
4473 this.on('load', callback, context);
4474 }
4475 return this;
4476 },
4477
4478
4479 // private methods for getting map state
4480
4481 _getMapPanePos: function () {
4482 return getPosition(this._mapPane) || new Point(0, 0);
4483 },
4484
4485 _moved: function () {
4486 var pos = this._getMapPanePos();
4487 return pos && !pos.equals([0, 0]);
4488 },
4489
4490 _getTopLeftPoint: function (center, zoom) {
4491 var pixelOrigin = center && zoom !== undefined ?
4492 this._getNewPixelOrigin(center, zoom) :
4493 this.getPixelOrigin();
4494 return pixelOrigin.subtract(this._getMapPanePos());
4495 },
4496
4497 _getNewPixelOrigin: function (center, zoom) {
4498 var viewHalf = this.getSize()._divideBy(2);
4499 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4500 },
4501
4502 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4503 var topLeft = this._getNewPixelOrigin(center, zoom);
4504 return this.project(latlng, zoom)._subtract(topLeft);
4505 },
4506
4507 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4508 var topLeft = this._getNewPixelOrigin(center, zoom);
4509 return toBounds([
4510 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4511 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4512 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4513 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4514 ]);
4515 },
4516
4517 // layer point of the current center
4518 _getCenterLayerPoint: function () {
4519 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4520 },
4521
4522 // offset of the specified place to the current center in pixels
4523 _getCenterOffset: function (latlng) {
4524 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4525 },
4526
4527 // adjust center for view to get inside bounds
4528 _limitCenter: function (center, zoom, bounds) {
4529
4530 if (!bounds) { return center; }
4531
4532 var centerPoint = this.project(center, zoom),
4533 viewHalf = this.getSize().divideBy(2),
4534 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4535 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4536
4537 // If offset is less than a pixel, ignore.
4538 // This prevents unstable projections from getting into
4539 // an infinite loop of tiny offsets.
4540 if (offset.round().equals([0, 0])) {
4541 return center;
4542 }
4543
4544 return this.unproject(centerPoint.add(offset), zoom);
4545 },
4546
4547 // adjust offset for view to get inside bounds
4548 _limitOffset: function (offset, bounds) {
4549 if (!bounds) { return offset; }
4550
4551 var viewBounds = this.getPixelBounds(),
4552 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4553
4554 return offset.add(this._getBoundsOffset(newBounds, bounds));
4555 },
4556
4557 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4558 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4559 var projectedMaxBounds = toBounds(
4560 this.project(maxBounds.getNorthEast(), zoom),
4561 this.project(maxBounds.getSouthWest(), zoom)
4562 ),
4563 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4564 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4565
4566 dx = this._rebound(minOffset.x, -maxOffset.x),
4567 dy = this._rebound(minOffset.y, -maxOffset.y);
4568
4569 return new Point(dx, dy);
4570 },
4571
4572 _rebound: function (left, right) {
4573 return left + right > 0 ?
4574 Math.round(left - right) / 2 :
4575 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4576 },
4577
4578 _limitZoom: function (zoom) {
4579 var min = this.getMinZoom(),
4580 max = this.getMaxZoom(),
4581 snap = any3d ? this.options.zoomSnap : 1;
4582 if (snap) {
4583 zoom = Math.round(zoom / snap) * snap;
4584 }
4585 return Math.max(min, Math.min(max, zoom));
4586 },
4587
4588 _onPanTransitionStep: function () {
4589 this.fire('move');
4590 },
4591
4592 _onPanTransitionEnd: function () {
4593 removeClass(this._mapPane, 'leaflet-pan-anim');
4594 this.fire('moveend');
4595 },
4596
4597 _tryAnimatedPan: function (center, options) {
4598 // difference between the new and current centers in pixels
4599 var offset = this._getCenterOffset(center)._trunc();
4600
4601 // don't animate too far unless animate: true specified in options
4602 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4603
4604 this.panBy(offset, options);
4605
4606 return true;
4607 },
4608
4609 _createAnimProxy: function () {
4610
4611 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4612 this._panes.mapPane.appendChild(proxy);
4613
4614 this.on('zoomanim', function (e) {
4615 var prop = TRANSFORM,
4616 transform = this._proxy.style[prop];
4617
4618 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4619
4620 // workaround for case when transform is the same and so transitionend event is not fired
4621 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4622 this._onZoomTransitionEnd();
4623 }
4624 }, this);
4625
4626 this.on('load moveend', this._animMoveEnd, this);
4627
4628 this._on('unload', this._destroyAnimProxy, this);
4629 },
4630
4631 _destroyAnimProxy: function () {
4632 remove(this._proxy);
4633 this.off('load moveend', this._animMoveEnd, this);
4634 delete this._proxy;
4635 },
4636
4637 _animMoveEnd: function () {
4638 var c = this.getCenter(),
4639 z = this.getZoom();
4640 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4641 },
4642
4643 _catchTransitionEnd: function (e) {
4644 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4645 this._onZoomTransitionEnd();
4646 }
4647 },
4648
4649 _nothingToAnimate: function () {
4650 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4651 },
4652
4653 _tryAnimatedZoom: function (center, zoom, options) {
4654
4655 if (this._animatingZoom) { return true; }
4656
4657 options = options || {};
4658
4659 // don't animate if disabled, not supported or zoom difference is too large
4660 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4661 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4662
4663 // offset is the pixel coords of the zoom origin relative to the current center
4664 var scale = this.getZoomScale(zoom),
4665 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4666
4667 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4668 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4669
4670 requestAnimFrame(function () {
4671 this
4672 ._moveStart(true, false)
4673 ._animateZoom(center, zoom, true);
4674 }, this);
4675
4676 return true;
4677 },
4678
4679 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4680 if (!this._mapPane) { return; }
4681
4682 if (startAnim) {
4683 this._animatingZoom = true;
4684
4685 // remember what center/zoom to set after animation
4686 this._animateToCenter = center;
4687 this._animateToZoom = zoom;
4688
4689 addClass(this._mapPane, 'leaflet-zoom-anim');
4690 }
4691
4692 // @section Other Events
4693 // @event zoomanim: ZoomAnimEvent
4694 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4695 this.fire('zoomanim', {
4696 center: center,
4697 zoom: zoom,
4698 noUpdate: noUpdate
4699 });
4700
4701 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4702 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4703 },
4704
4705 _onZoomTransitionEnd: function () {
4706 if (!this._animatingZoom) { return; }
4707
4708 if (this._mapPane) {
4709 removeClass(this._mapPane, 'leaflet-zoom-anim');
4710 }
4711
4712 this._animatingZoom = false;
4713
4714 this._move(this._animateToCenter, this._animateToZoom);
4715
4716 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4717 requestAnimFrame(function () {
4718 this._moveEnd(true);
4719 }, this);
4720 }
4721});
4722
4723// @section
4724
4725// @factory L.map(id: String, options?: Map options)
4726// Instantiates a map object given the DOM ID of a `<div>` element
4727// and optionally an object literal with `Map options`.
4728//
4729// @alternative
4730// @factory L.map(el: HTMLElement, options?: Map options)
4731// Instantiates a map object given an instance of a `<div>` HTML element
4732// and optionally an object literal with `Map options`.
4733function createMap(id, options) {
4734 return new Map(id, options);
4735}
4736
4737/*
4738 * @class Control
4739 * @aka L.Control
4740 * @inherits Class
4741 *
4742 * L.Control is a base class for implementing map controls. Handles positioning.
4743 * All other controls extend from this class.
4744 */
4745
4746var Control = Class.extend({
4747 // @section
4748 // @aka Control options
4749 options: {
4750 // @option position: String = 'topright'
4751 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4752 // `'topright'`, `'bottomleft'` or `'bottomright'`
4753 position: 'topright'
4754 },
4755
4756 initialize: function (options) {
4757 setOptions(this, options);
4758 },
4759
4760 /* @section
4761 * Classes extending L.Control will inherit the following methods:
4762 *
4763 * @method getPosition: string
4764 * Returns the position of the control.
4765 */
4766 getPosition: function () {
4767 return this.options.position;
4768 },
4769
4770 // @method setPosition(position: string): this
4771 // Sets the position of the control.
4772 setPosition: function (position) {
4773 var map = this._map;
4774
4775 if (map) {
4776 map.removeControl(this);
4777 }
4778
4779 this.options.position = position;
4780
4781 if (map) {
4782 map.addControl(this);
4783 }
4784
4785 return this;
4786 },
4787
4788 // @method getContainer: HTMLElement
4789 // Returns the HTMLElement that contains the control.
4790 getContainer: function () {
4791 return this._container;
4792 },
4793
4794 // @method addTo(map: Map): this
4795 // Adds the control to the given map.
4796 addTo: function (map) {
4797 this.remove();
4798 this._map = map;
4799
4800 var container = this._container = this.onAdd(map),
4801 pos = this.getPosition(),
4802 corner = map._controlCorners[pos];
4803
4804 addClass(container, 'leaflet-control');
4805
4806 if (pos.indexOf('bottom') !== -1) {
4807 corner.insertBefore(container, corner.firstChild);
4808 } else {
4809 corner.appendChild(container);
4810 }
4811
4812 this._map.on('unload', this.remove, this);
4813
4814 return this;
4815 },
4816
4817 // @method remove: this
4818 // Removes the control from the map it is currently active on.
4819 remove: function () {
4820 if (!this._map) {
4821 return this;
4822 }
4823
4824 remove(this._container);
4825
4826 if (this.onRemove) {
4827 this.onRemove(this._map);
4828 }
4829
4830 this._map.off('unload', this.remove, this);
4831 this._map = null;
4832
4833 return this;
4834 },
4835
4836 _refocusOnMap: function (e) {
4837 // if map exists and event is not a keyboard event
4838 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4839 this._map.getContainer().focus();
4840 }
4841 }
4842});
4843
4844var control = function (options) {
4845 return new Control(options);
4846};
4847
4848/* @section Extension methods
4849 * @uninheritable
4850 *
4851 * Every control should extend from `L.Control` and (re-)implement the following methods.
4852 *
4853 * @method onAdd(map: Map): HTMLElement
4854 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4855 *
4856 * @method onRemove(map: Map)
4857 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4858 */
4859
4860/* @namespace Map
4861 * @section Methods for Layers and Controls
4862 */
4863Map.include({
4864 // @method addControl(control: Control): this
4865 // Adds the given control to the map
4866 addControl: function (control) {
4867 control.addTo(this);
4868 return this;
4869 },
4870
4871 // @method removeControl(control: Control): this
4872 // Removes the given control from the map
4873 removeControl: function (control) {
4874 control.remove();
4875 return this;
4876 },
4877
4878 _initControlPos: function () {
4879 var corners = this._controlCorners = {},
4880 l = 'leaflet-',
4881 container = this._controlContainer =
4882 create$1('div', l + 'control-container', this._container);
4883
4884 function createCorner(vSide, hSide) {
4885 var className = l + vSide + ' ' + l + hSide;
4886
4887 corners[vSide + hSide] = create$1('div', className, container);
4888 }
4889
4890 createCorner('top', 'left');
4891 createCorner('top', 'right');
4892 createCorner('bottom', 'left');
4893 createCorner('bottom', 'right');
4894 },
4895
4896 _clearControlPos: function () {
4897 for (var i in this._controlCorners) {
4898 remove(this._controlCorners[i]);
4899 }
4900 remove(this._controlContainer);
4901 delete this._controlCorners;
4902 delete this._controlContainer;
4903 }
4904});
4905
4906/*
4907 * @class Control.Layers
4908 * @aka L.Control.Layers
4909 * @inherits Control
4910 *
4911 * 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`.
4912 *
4913 * @example
4914 *
4915 * ```js
4916 * var baseLayers = {
4917 * "Mapbox": mapbox,
4918 * "OpenStreetMap": osm
4919 * };
4920 *
4921 * var overlays = {
4922 * "Marker": marker,
4923 * "Roads": roadsLayer
4924 * };
4925 *
4926 * L.control.layers(baseLayers, overlays).addTo(map);
4927 * ```
4928 *
4929 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4930 *
4931 * ```js
4932 * {
4933 * "<someName1>": layer1,
4934 * "<someName2>": layer2
4935 * }
4936 * ```
4937 *
4938 * The layer names can contain HTML, which allows you to add additional styling to the items:
4939 *
4940 * ```js
4941 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4942 * ```
4943 */
4944
4945var Layers = Control.extend({
4946 // @section
4947 // @aka Control.Layers options
4948 options: {
4949 // @option collapsed: Boolean = true
4950 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4951 collapsed: true,
4952 position: 'topright',
4953
4954 // @option autoZIndex: Boolean = true
4955 // 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.
4956 autoZIndex: true,
4957
4958 // @option hideSingleBase: Boolean = false
4959 // If `true`, the base layers in the control will be hidden when there is only one.
4960 hideSingleBase: false,
4961
4962 // @option sortLayers: Boolean = false
4963 // Whether to sort the layers. When `false`, layers will keep the order
4964 // in which they were added to the control.
4965 sortLayers: false,
4966
4967 // @option sortFunction: Function = *
4968 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4969 // that will be used for sorting the layers, when `sortLayers` is `true`.
4970 // The function receives both the `L.Layer` instances and their names, as in
4971 // `sortFunction(layerA, layerB, nameA, nameB)`.
4972 // By default, it sorts layers alphabetically by their name.
4973 sortFunction: function (layerA, layerB, nameA, nameB) {
4974 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4975 }
4976 },
4977
4978 initialize: function (baseLayers, overlays, options) {
4979 setOptions(this, options);
4980
4981 this._layerControlInputs = [];
4982 this._layers = [];
4983 this._lastZIndex = 0;
4984 this._handlingClick = false;
4985
4986 for (var i in baseLayers) {
4987 this._addLayer(baseLayers[i], i);
4988 }
4989
4990 for (i in overlays) {
4991 this._addLayer(overlays[i], i, true);
4992 }
4993 },
4994
4995 onAdd: function (map) {
4996 this._initLayout();
4997 this._update();
4998
4999 this._map = map;
5000 map.on('zoomend', this._checkDisabledLayers, this);
5001
5002 for (var i = 0; i < this._layers.length; i++) {
5003 this._layers[i].layer.on('add remove', this._onLayerChange, this);
5004 }
5005
5006 return this._container;
5007 },
5008
5009 addTo: function (map) {
5010 Control.prototype.addTo.call(this, map);
5011 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
5012 return this._expandIfNotCollapsed();
5013 },
5014
5015 onRemove: function () {
5016 this._map.off('zoomend', this._checkDisabledLayers, this);
5017
5018 for (var i = 0; i < this._layers.length; i++) {
5019 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5020 }
5021 },
5022
5023 // @method addBaseLayer(layer: Layer, name: String): this
5024 // Adds a base layer (radio button entry) with the given name to the control.
5025 addBaseLayer: function (layer, name) {
5026 this._addLayer(layer, name);
5027 return (this._map) ? this._update() : this;
5028 },
5029
5030 // @method addOverlay(layer: Layer, name: String): this
5031 // Adds an overlay (checkbox entry) with the given name to the control.
5032 addOverlay: function (layer, name) {
5033 this._addLayer(layer, name, true);
5034 return (this._map) ? this._update() : this;
5035 },
5036
5037 // @method removeLayer(layer: Layer): this
5038 // Remove the given layer from the control.
5039 removeLayer: function (layer) {
5040 layer.off('add remove', this._onLayerChange, this);
5041
5042 var obj = this._getLayer(stamp(layer));
5043 if (obj) {
5044 this._layers.splice(this._layers.indexOf(obj), 1);
5045 }
5046 return (this._map) ? this._update() : this;
5047 },
5048
5049 // @method expand(): this
5050 // Expand the control container if collapsed.
5051 expand: function () {
5052 addClass(this._container, 'leaflet-control-layers-expanded');
5053 this._section.style.height = null;
5054 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5055 if (acceptableHeight < this._section.clientHeight) {
5056 addClass(this._section, 'leaflet-control-layers-scrollbar');
5057 this._section.style.height = acceptableHeight + 'px';
5058 } else {
5059 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5060 }
5061 this._checkDisabledLayers();
5062 return this;
5063 },
5064
5065 // @method collapse(): this
5066 // Collapse the control container if expanded.
5067 collapse: function () {
5068 removeClass(this._container, 'leaflet-control-layers-expanded');
5069 return this;
5070 },
5071
5072 _initLayout: function () {
5073 var className = 'leaflet-control-layers',
5074 container = this._container = create$1('div', className),
5075 collapsed = this.options.collapsed;
5076
5077 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5078 container.setAttribute('aria-haspopup', true);
5079
5080 disableClickPropagation(container);
5081 disableScrollPropagation(container);
5082
5083 var section = this._section = create$1('section', className + '-list');
5084
5085 if (collapsed) {
5086 this._map.on('click', this.collapse, this);
5087
5088 if (!android) {
5089 on(container, {
5090 mouseenter: this.expand,
5091 mouseleave: this.collapse
5092 }, this);
5093 }
5094 }
5095
5096 var link = this._layersLink = create$1('a', className + '-toggle', container);
5097 link.href = '#';
5098 link.title = 'Layers';
5099
5100 if (touch) {
5101 on(link, 'click', stop);
5102 on(link, 'click', this.expand, this);
5103 } else {
5104 on(link, 'focus', this.expand, this);
5105 }
5106
5107 if (!collapsed) {
5108 this.expand();
5109 }
5110
5111 this._baseLayersList = create$1('div', className + '-base', section);
5112 this._separator = create$1('div', className + '-separator', section);
5113 this._overlaysList = create$1('div', className + '-overlays', section);
5114
5115 container.appendChild(section);
5116 },
5117
5118 _getLayer: function (id) {
5119 for (var i = 0; i < this._layers.length; i++) {
5120
5121 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5122 return this._layers[i];
5123 }
5124 }
5125 },
5126
5127 _addLayer: function (layer, name, overlay) {
5128 if (this._map) {
5129 layer.on('add remove', this._onLayerChange, this);
5130 }
5131
5132 this._layers.push({
5133 layer: layer,
5134 name: name,
5135 overlay: overlay
5136 });
5137
5138 if (this.options.sortLayers) {
5139 this._layers.sort(bind(function (a, b) {
5140 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5141 }, this));
5142 }
5143
5144 if (this.options.autoZIndex && layer.setZIndex) {
5145 this._lastZIndex++;
5146 layer.setZIndex(this._lastZIndex);
5147 }
5148
5149 this._expandIfNotCollapsed();
5150 },
5151
5152 _update: function () {
5153 if (!this._container) { return this; }
5154
5155 empty(this._baseLayersList);
5156 empty(this._overlaysList);
5157
5158 this._layerControlInputs = [];
5159 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5160
5161 for (i = 0; i < this._layers.length; i++) {
5162 obj = this._layers[i];
5163 this._addItem(obj);
5164 overlaysPresent = overlaysPresent || obj.overlay;
5165 baseLayersPresent = baseLayersPresent || !obj.overlay;
5166 baseLayersCount += !obj.overlay ? 1 : 0;
5167 }
5168
5169 // Hide base layers section if there's only one layer.
5170 if (this.options.hideSingleBase) {
5171 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5172 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5173 }
5174
5175 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5176
5177 return this;
5178 },
5179
5180 _onLayerChange: function (e) {
5181 if (!this._handlingClick) {
5182 this._update();
5183 }
5184
5185 var obj = this._getLayer(stamp(e.target));
5186
5187 // @namespace Map
5188 // @section Layer events
5189 // @event baselayerchange: LayersControlEvent
5190 // Fired when the base layer is changed through the [layer control](#control-layers).
5191 // @event overlayadd: LayersControlEvent
5192 // Fired when an overlay is selected through the [layer control](#control-layers).
5193 // @event overlayremove: LayersControlEvent
5194 // Fired when an overlay is deselected through the [layer control](#control-layers).
5195 // @namespace Control.Layers
5196 var type = obj.overlay ?
5197 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5198 (e.type === 'add' ? 'baselayerchange' : null);
5199
5200 if (type) {
5201 this._map.fire(type, obj);
5202 }
5203 },
5204
5205 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5206 _createRadioElement: function (name, checked) {
5207
5208 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5209 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5210
5211 var radioFragment = document.createElement('div');
5212 radioFragment.innerHTML = radioHtml;
5213
5214 return radioFragment.firstChild;
5215 },
5216
5217 _addItem: function (obj) {
5218 var label = document.createElement('label'),
5219 checked = this._map.hasLayer(obj.layer),
5220 input;
5221
5222 if (obj.overlay) {
5223 input = document.createElement('input');
5224 input.type = 'checkbox';
5225 input.className = 'leaflet-control-layers-selector';
5226 input.defaultChecked = checked;
5227 } else {
5228 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5229 }
5230
5231 this._layerControlInputs.push(input);
5232 input.layerId = stamp(obj.layer);
5233
5234 on(input, 'click', this._onInputClick, this);
5235
5236 var name = document.createElement('span');
5237 name.innerHTML = ' ' + obj.name;
5238
5239 // Helps from preventing layer control flicker when checkboxes are disabled
5240 // https://github.com/Leaflet/Leaflet/issues/2771
5241 var holder = document.createElement('div');
5242
5243 label.appendChild(holder);
5244 holder.appendChild(input);
5245 holder.appendChild(name);
5246
5247 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5248 container.appendChild(label);
5249
5250 this._checkDisabledLayers();
5251 return label;
5252 },
5253
5254 _onInputClick: function () {
5255 var inputs = this._layerControlInputs,
5256 input, layer;
5257 var addedLayers = [],
5258 removedLayers = [];
5259
5260 this._handlingClick = true;
5261
5262 for (var i = inputs.length - 1; i >= 0; i--) {
5263 input = inputs[i];
5264 layer = this._getLayer(input.layerId).layer;
5265
5266 if (input.checked) {
5267 addedLayers.push(layer);
5268 } else if (!input.checked) {
5269 removedLayers.push(layer);
5270 }
5271 }
5272
5273 // Bugfix issue 2318: Should remove all old layers before readding new ones
5274 for (i = 0; i < removedLayers.length; i++) {
5275 if (this._map.hasLayer(removedLayers[i])) {
5276 this._map.removeLayer(removedLayers[i]);
5277 }
5278 }
5279 for (i = 0; i < addedLayers.length; i++) {
5280 if (!this._map.hasLayer(addedLayers[i])) {
5281 this._map.addLayer(addedLayers[i]);
5282 }
5283 }
5284
5285 this._handlingClick = false;
5286
5287 this._refocusOnMap();
5288 },
5289
5290 _checkDisabledLayers: function () {
5291 var inputs = this._layerControlInputs,
5292 input,
5293 layer,
5294 zoom = this._map.getZoom();
5295
5296 for (var i = inputs.length - 1; i >= 0; i--) {
5297 input = inputs[i];
5298 layer = this._getLayer(input.layerId).layer;
5299 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5300 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5301
5302 }
5303 },
5304
5305 _expandIfNotCollapsed: function () {
5306 if (this._map && !this.options.collapsed) {
5307 this.expand();
5308 }
5309 return this;
5310 },
5311
5312 _expand: function () {
5313 // Backward compatibility, remove me in 1.1.
5314 return this.expand();
5315 },
5316
5317 _collapse: function () {
5318 // Backward compatibility, remove me in 1.1.
5319 return this.collapse();
5320 }
5321
5322});
5323
5324
5325// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5326// 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.
5327var layers = function (baseLayers, overlays, options) {
5328 return new Layers(baseLayers, overlays, options);
5329};
5330
5331/*
5332 * @class Control.Zoom
5333 * @aka L.Control.Zoom
5334 * @inherits Control
5335 *
5336 * 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`.
5337 */
5338
5339var Zoom = Control.extend({
5340 // @section
5341 // @aka Control.Zoom options
5342 options: {
5343 position: 'topleft',
5344
5345 // @option zoomInText: String = '+'
5346 // The text set on the 'zoom in' button.
5347 zoomInText: '+',
5348
5349 // @option zoomInTitle: String = 'Zoom in'
5350 // The title set on the 'zoom in' button.
5351 zoomInTitle: 'Zoom in',
5352
5353 // @option zoomOutText: String = '&#x2212;'
5354 // The text set on the 'zoom out' button.
5355 zoomOutText: '&#x2212;',
5356
5357 // @option zoomOutTitle: String = 'Zoom out'
5358 // The title set on the 'zoom out' button.
5359 zoomOutTitle: 'Zoom out'
5360 },
5361
5362 onAdd: function (map) {
5363 var zoomName = 'leaflet-control-zoom',
5364 container = create$1('div', zoomName + ' leaflet-bar'),
5365 options = this.options;
5366
5367 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5368 zoomName + '-in', container, this._zoomIn);
5369 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5370 zoomName + '-out', container, this._zoomOut);
5371
5372 this._updateDisabled();
5373 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5374
5375 return container;
5376 },
5377
5378 onRemove: function (map) {
5379 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5380 },
5381
5382 disable: function () {
5383 this._disabled = true;
5384 this._updateDisabled();
5385 return this;
5386 },
5387
5388 enable: function () {
5389 this._disabled = false;
5390 this._updateDisabled();
5391 return this;
5392 },
5393
5394 _zoomIn: function (e) {
5395 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5396 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5397 }
5398 },
5399
5400 _zoomOut: function (e) {
5401 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5402 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5403 }
5404 },
5405
5406 _createButton: function (html, title, className, container, fn) {
5407 var link = create$1('a', className, container);
5408 link.innerHTML = html;
5409 link.href = '#';
5410 link.title = title;
5411
5412 /*
5413 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5414 */
5415 link.setAttribute('role', 'button');
5416 link.setAttribute('aria-label', title);
5417
5418 disableClickPropagation(link);
5419 on(link, 'click', stop);
5420 on(link, 'click', fn, this);
5421 on(link, 'click', this._refocusOnMap, this);
5422
5423 return link;
5424 },
5425
5426 _updateDisabled: function () {
5427 var map = this._map,
5428 className = 'leaflet-disabled';
5429
5430 removeClass(this._zoomInButton, className);
5431 removeClass(this._zoomOutButton, className);
5432
5433 if (this._disabled || map._zoom === map.getMinZoom()) {
5434 addClass(this._zoomOutButton, className);
5435 }
5436 if (this._disabled || map._zoom === map.getMaxZoom()) {
5437 addClass(this._zoomInButton, className);
5438 }
5439 }
5440});
5441
5442// @namespace Map
5443// @section Control options
5444// @option zoomControl: Boolean = true
5445// Whether a [zoom control](#control-zoom) is added to the map by default.
5446Map.mergeOptions({
5447 zoomControl: true
5448});
5449
5450Map.addInitHook(function () {
5451 if (this.options.zoomControl) {
5452 // @section Controls
5453 // @property zoomControl: Control.Zoom
5454 // The default zoom control (only available if the
5455 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5456 this.zoomControl = new Zoom();
5457 this.addControl(this.zoomControl);
5458 }
5459});
5460
5461// @namespace Control.Zoom
5462// @factory L.control.zoom(options: Control.Zoom options)
5463// Creates a zoom control
5464var zoom = function (options) {
5465 return new Zoom(options);
5466};
5467
5468/*
5469 * @class Control.Scale
5470 * @aka L.Control.Scale
5471 * @inherits Control
5472 *
5473 * 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`.
5474 *
5475 * @example
5476 *
5477 * ```js
5478 * L.control.scale().addTo(map);
5479 * ```
5480 */
5481
5482var Scale = Control.extend({
5483 // @section
5484 // @aka Control.Scale options
5485 options: {
5486 position: 'bottomleft',
5487
5488 // @option maxWidth: Number = 100
5489 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5490 maxWidth: 100,
5491
5492 // @option metric: Boolean = True
5493 // Whether to show the metric scale line (m/km).
5494 metric: true,
5495
5496 // @option imperial: Boolean = True
5497 // Whether to show the imperial scale line (mi/ft).
5498 imperial: true
5499
5500 // @option updateWhenIdle: Boolean = false
5501 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5502 },
5503
5504 onAdd: function (map) {
5505 var className = 'leaflet-control-scale',
5506 container = create$1('div', className),
5507 options = this.options;
5508
5509 this._addScales(options, className + '-line', container);
5510
5511 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5512 map.whenReady(this._update, this);
5513
5514 return container;
5515 },
5516
5517 onRemove: function (map) {
5518 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5519 },
5520
5521 _addScales: function (options, className, container) {
5522 if (options.metric) {
5523 this._mScale = create$1('div', className, container);
5524 }
5525 if (options.imperial) {
5526 this._iScale = create$1('div', className, container);
5527 }
5528 },
5529
5530 _update: function () {
5531 var map = this._map,
5532 y = map.getSize().y / 2;
5533
5534 var maxMeters = map.distance(
5535 map.containerPointToLatLng([0, y]),
5536 map.containerPointToLatLng([this.options.maxWidth, y]));
5537
5538 this._updateScales(maxMeters);
5539 },
5540
5541 _updateScales: function (maxMeters) {
5542 if (this.options.metric && maxMeters) {
5543 this._updateMetric(maxMeters);
5544 }
5545 if (this.options.imperial && maxMeters) {
5546 this._updateImperial(maxMeters);
5547 }
5548 },
5549
5550 _updateMetric: function (maxMeters) {
5551 var meters = this._getRoundNum(maxMeters),
5552 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5553
5554 this._updateScale(this._mScale, label, meters / maxMeters);
5555 },
5556
5557 _updateImperial: function (maxMeters) {
5558 var maxFeet = maxMeters * 3.2808399,
5559 maxMiles, miles, feet;
5560
5561 if (maxFeet > 5280) {
5562 maxMiles = maxFeet / 5280;
5563 miles = this._getRoundNum(maxMiles);
5564 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5565
5566 } else {
5567 feet = this._getRoundNum(maxFeet);
5568 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5569 }
5570 },
5571
5572 _updateScale: function (scale, text, ratio) {
5573 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5574 scale.innerHTML = text;
5575 },
5576
5577 _getRoundNum: function (num) {
5578 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5579 d = num / pow10;
5580
5581 d = d >= 10 ? 10 :
5582 d >= 5 ? 5 :
5583 d >= 3 ? 3 :
5584 d >= 2 ? 2 : 1;
5585
5586 return pow10 * d;
5587 }
5588});
5589
5590
5591// @factory L.control.scale(options?: Control.Scale options)
5592// Creates an scale control with the given options.
5593var scale = function (options) {
5594 return new Scale(options);
5595};
5596
5597/*
5598 * @class Control.Attribution
5599 * @aka L.Control.Attribution
5600 * @inherits Control
5601 *
5602 * 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.
5603 */
5604
5605var Attribution = Control.extend({
5606 // @section
5607 // @aka Control.Attribution options
5608 options: {
5609 position: 'bottomright',
5610
5611 // @option prefix: String = 'Leaflet'
5612 // The HTML text shown before the attributions. Pass `false` to disable.
5613 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5614 },
5615
5616 initialize: function (options) {
5617 setOptions(this, options);
5618
5619 this._attributions = {};
5620 },
5621
5622 onAdd: function (map) {
5623 map.attributionControl = this;
5624 this._container = create$1('div', 'leaflet-control-attribution');
5625 disableClickPropagation(this._container);
5626
5627 // TODO ugly, refactor
5628 for (var i in map._layers) {
5629 if (map._layers[i].getAttribution) {
5630 this.addAttribution(map._layers[i].getAttribution());
5631 }
5632 }
5633
5634 this._update();
5635
5636 return this._container;
5637 },
5638
5639 // @method setPrefix(prefix: String): this
5640 // Sets the text before the attributions.
5641 setPrefix: function (prefix) {
5642 this.options.prefix = prefix;
5643 this._update();
5644 return this;
5645 },
5646
5647 // @method addAttribution(text: String): this
5648 // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5649 addAttribution: function (text) {
5650 if (!text) { return this; }
5651
5652 if (!this._attributions[text]) {
5653 this._attributions[text] = 0;
5654 }
5655 this._attributions[text]++;
5656
5657 this._update();
5658
5659 return this;
5660 },
5661
5662 // @method removeAttribution(text: String): this
5663 // Removes an attribution text.
5664 removeAttribution: function (text) {
5665 if (!text) { return this; }
5666
5667 if (this._attributions[text]) {
5668 this._attributions[text]--;
5669 this._update();
5670 }
5671
5672 return this;
5673 },
5674
5675 _update: function () {
5676 if (!this._map) { return; }
5677
5678 var attribs = [];
5679
5680 for (var i in this._attributions) {
5681 if (this._attributions[i]) {
5682 attribs.push(i);
5683 }
5684 }
5685
5686 var prefixAndAttribs = [];
5687
5688 if (this.options.prefix) {
5689 prefixAndAttribs.push(this.options.prefix);
5690 }
5691 if (attribs.length) {
5692 prefixAndAttribs.push(attribs.join(', '));
5693 }
5694
5695 this._container.innerHTML = prefixAndAttribs.join(' | ');
5696 }
5697});
5698
5699// @namespace Map
5700// @section Control options
5701// @option attributionControl: Boolean = true
5702// Whether a [attribution control](#control-attribution) is added to the map by default.
5703Map.mergeOptions({
5704 attributionControl: true
5705});
5706
5707Map.addInitHook(function () {
5708 if (this.options.attributionControl) {
5709 new Attribution().addTo(this);
5710 }
5711});
5712
5713// @namespace Control.Attribution
5714// @factory L.control.attribution(options: Control.Attribution options)
5715// Creates an attribution control.
5716var attribution = function (options) {
5717 return new Attribution(options);
5718};
5719
5720Control.Layers = Layers;
5721Control.Zoom = Zoom;
5722Control.Scale = Scale;
5723Control.Attribution = Attribution;
5724
5725control.layers = layers;
5726control.zoom = zoom;
5727control.scale = scale;
5728control.attribution = attribution;
5729
5730/*
5731 L.Handler is a base class for handler classes that are used internally to inject
5732 interaction features like dragging to classes like Map and Marker.
5733*/
5734
5735// @class Handler
5736// @aka L.Handler
5737// Abstract class for map interaction handlers
5738
5739var Handler = Class.extend({
5740 initialize: function (map) {
5741 this._map = map;
5742 },
5743
5744 // @method enable(): this
5745 // Enables the handler
5746 enable: function () {
5747 if (this._enabled) { return this; }
5748
5749 this._enabled = true;
5750 this.addHooks();
5751 return this;
5752 },
5753
5754 // @method disable(): this
5755 // Disables the handler
5756 disable: function () {
5757 if (!this._enabled) { return this; }
5758
5759 this._enabled = false;
5760 this.removeHooks();
5761 return this;
5762 },
5763
5764 // @method enabled(): Boolean
5765 // Returns `true` if the handler is enabled
5766 enabled: function () {
5767 return !!this._enabled;
5768 }
5769
5770 // @section Extension methods
5771 // Classes inheriting from `Handler` must implement the two following methods:
5772 // @method addHooks()
5773 // Called when the handler is enabled, should add event hooks.
5774 // @method removeHooks()
5775 // Called when the handler is disabled, should remove the event hooks added previously.
5776});
5777
5778// @section There is static function which can be called without instantiating L.Handler:
5779// @function addTo(map: Map, name: String): this
5780// Adds a new Handler to the given map with the given name.
5781Handler.addTo = function (map, name) {
5782 map.addHandler(name, this);
5783 return this;
5784};
5785
5786var Mixin = {Events: Events};
5787
5788/*
5789 * @class Draggable
5790 * @aka L.Draggable
5791 * @inherits Evented
5792 *
5793 * A class for making DOM elements draggable (including touch support).
5794 * Used internally for map and marker dragging. Only works for elements
5795 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5796 *
5797 * @example
5798 * ```js
5799 * var draggable = new L.Draggable(elementToDrag);
5800 * draggable.enable();
5801 * ```
5802 */
5803
5804var START = touch ? 'touchstart mousedown' : 'mousedown';
5805var END = {
5806 mousedown: 'mouseup',
5807 touchstart: 'touchend',
5808 pointerdown: 'touchend',
5809 MSPointerDown: 'touchend'
5810};
5811var MOVE = {
5812 mousedown: 'mousemove',
5813 touchstart: 'touchmove',
5814 pointerdown: 'touchmove',
5815 MSPointerDown: 'touchmove'
5816};
5817
5818
5819var Draggable = Evented.extend({
5820
5821 options: {
5822 // @section
5823 // @aka Draggable options
5824 // @option clickTolerance: Number = 3
5825 // The max number of pixels a user can shift the mouse pointer during a click
5826 // for it to be considered a valid click (as opposed to a mouse drag).
5827 clickTolerance: 3
5828 },
5829
5830 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5831 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5832 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5833 setOptions(this, options);
5834
5835 this._element = element;
5836 this._dragStartTarget = dragStartTarget || element;
5837 this._preventOutline = preventOutline$$1;
5838 },
5839
5840 // @method enable()
5841 // Enables the dragging ability
5842 enable: function () {
5843 if (this._enabled) { return; }
5844
5845 on(this._dragStartTarget, START, this._onDown, this);
5846
5847 this._enabled = true;
5848 },
5849
5850 // @method disable()
5851 // Disables the dragging ability
5852 disable: function () {
5853 if (!this._enabled) { return; }
5854
5855 // If we're currently dragging this draggable,
5856 // disabling it counts as first ending the drag.
5857 if (Draggable._dragging === this) {
5858 this.finishDrag();
5859 }
5860
5861 off(this._dragStartTarget, START, this._onDown, this);
5862
5863 this._enabled = false;
5864 this._moved = false;
5865 },
5866
5867 _onDown: function (e) {
5868 // Ignore simulated events, since we handle both touch and
5869 // mouse explicitly; otherwise we risk getting duplicates of
5870 // touch events, see #4315.
5871 // Also ignore the event if disabled; this happens in IE11
5872 // under some circumstances, see #3666.
5873 if (e._simulated || !this._enabled) { return; }
5874
5875 this._moved = false;
5876
5877 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5878
5879 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5880 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5881
5882 if (this._preventOutline) {
5883 preventOutline(this._element);
5884 }
5885
5886 disableImageDrag();
5887 disableTextSelection();
5888
5889 if (this._moving) { return; }
5890
5891 // @event down: Event
5892 // Fired when a drag is about to start.
5893 this.fire('down');
5894
5895 var first = e.touches ? e.touches[0] : e,
5896 sizedParent = getSizedParentNode(this._element);
5897
5898 this._startPoint = new Point(first.clientX, first.clientY);
5899
5900 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5901 this._parentScale = getScale(sizedParent);
5902
5903 on(document, MOVE[e.type], this._onMove, this);
5904 on(document, END[e.type], this._onUp, this);
5905 },
5906
5907 _onMove: function (e) {
5908 // Ignore simulated events, since we handle both touch and
5909 // mouse explicitly; otherwise we risk getting duplicates of
5910 // touch events, see #4315.
5911 // Also ignore the event if disabled; this happens in IE11
5912 // under some circumstances, see #3666.
5913 if (e._simulated || !this._enabled) { return; }
5914
5915 if (e.touches && e.touches.length > 1) {
5916 this._moved = true;
5917 return;
5918 }
5919
5920 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5921 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5922
5923 if (!offset.x && !offset.y) { return; }
5924 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5925
5926 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5927 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5928 // and we can use the cached value for the scale.
5929 offset.x /= this._parentScale.x;
5930 offset.y /= this._parentScale.y;
5931
5932 preventDefault(e);
5933
5934 if (!this._moved) {
5935 // @event dragstart: Event
5936 // Fired when a drag starts
5937 this.fire('dragstart');
5938
5939 this._moved = true;
5940 this._startPos = getPosition(this._element).subtract(offset);
5941
5942 addClass(document.body, 'leaflet-dragging');
5943
5944 this._lastTarget = e.target || e.srcElement;
5945 // IE and Edge do not give the <use> element, so fetch it
5946 // if necessary
5947 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5948 this._lastTarget = this._lastTarget.correspondingUseElement;
5949 }
5950 addClass(this._lastTarget, 'leaflet-drag-target');
5951 }
5952
5953 this._newPos = this._startPos.add(offset);
5954 this._moving = true;
5955
5956 cancelAnimFrame(this._animRequest);
5957 this._lastEvent = e;
5958 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5959 },
5960
5961 _updatePosition: function () {
5962 var e = {originalEvent: this._lastEvent};
5963
5964 // @event predrag: Event
5965 // Fired continuously during dragging *before* each corresponding
5966 // update of the element's position.
5967 this.fire('predrag', e);
5968 setPosition(this._element, this._newPos);
5969
5970 // @event drag: Event
5971 // Fired continuously during dragging.
5972 this.fire('drag', e);
5973 },
5974
5975 _onUp: function (e) {
5976 // Ignore simulated events, since we handle both touch and
5977 // mouse explicitly; otherwise we risk getting duplicates of
5978 // touch events, see #4315.
5979 // Also ignore the event if disabled; this happens in IE11
5980 // under some circumstances, see #3666.
5981 if (e._simulated || !this._enabled) { return; }
5982 this.finishDrag();
5983 },
5984
5985 finishDrag: function () {
5986 removeClass(document.body, 'leaflet-dragging');
5987
5988 if (this._lastTarget) {
5989 removeClass(this._lastTarget, 'leaflet-drag-target');
5990 this._lastTarget = null;
5991 }
5992
5993 for (var i in MOVE) {
5994 off(document, MOVE[i], this._onMove, this);
5995 off(document, END[i], this._onUp, this);
5996 }
5997
5998 enableImageDrag();
5999 enableTextSelection();
6000
6001 if (this._moved && this._moving) {
6002 // ensure drag is not fired after dragend
6003 cancelAnimFrame(this._animRequest);
6004
6005 // @event dragend: DragEndEvent
6006 // Fired when the drag ends.
6007 this.fire('dragend', {
6008 distance: this._newPos.distanceTo(this._startPos)
6009 });
6010 }
6011
6012 this._moving = false;
6013 Draggable._dragging = false;
6014 }
6015
6016});
6017
6018/*
6019 * @namespace LineUtil
6020 *
6021 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6022 */
6023
6024// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6025// Improves rendering performance dramatically by lessening the number of points to draw.
6026
6027// @function simplify(points: Point[], tolerance: Number): Point[]
6028// Dramatically reduces the number of points in a polyline while retaining
6029// its shape and returns a new array of simplified points, using the
6030// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6031// Used for a huge performance boost when processing/displaying Leaflet polylines for
6032// each zoom level and also reducing visual noise. tolerance affects the amount of
6033// simplification (lesser value means higher quality but slower and with more points).
6034// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6035function simplify(points, tolerance) {
6036 if (!tolerance || !points.length) {
6037 return points.slice();
6038 }
6039
6040 var sqTolerance = tolerance * tolerance;
6041
6042 // stage 1: vertex reduction
6043 points = _reducePoints(points, sqTolerance);
6044
6045 // stage 2: Douglas-Peucker simplification
6046 points = _simplifyDP(points, sqTolerance);
6047
6048 return points;
6049}
6050
6051// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6052// Returns the distance between point `p` and segment `p1` to `p2`.
6053function pointToSegmentDistance(p, p1, p2) {
6054 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6055}
6056
6057// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6058// Returns the closest point from a point `p` on a segment `p1` to `p2`.
6059function closestPointOnSegment(p, p1, p2) {
6060 return _sqClosestPointOnSegment(p, p1, p2);
6061}
6062
6063// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6064function _simplifyDP(points, sqTolerance) {
6065
6066 var len = points.length,
6067 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6068 markers = new ArrayConstructor(len);
6069
6070 markers[0] = markers[len - 1] = 1;
6071
6072 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6073
6074 var i,
6075 newPoints = [];
6076
6077 for (i = 0; i < len; i++) {
6078 if (markers[i]) {
6079 newPoints.push(points[i]);
6080 }
6081 }
6082
6083 return newPoints;
6084}
6085
6086function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6087
6088 var maxSqDist = 0,
6089 index, i, sqDist;
6090
6091 for (i = first + 1; i <= last - 1; i++) {
6092 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6093
6094 if (sqDist > maxSqDist) {
6095 index = i;
6096 maxSqDist = sqDist;
6097 }
6098 }
6099
6100 if (maxSqDist > sqTolerance) {
6101 markers[index] = 1;
6102
6103 _simplifyDPStep(points, markers, sqTolerance, first, index);
6104 _simplifyDPStep(points, markers, sqTolerance, index, last);
6105 }
6106}
6107
6108// reduce points that are too close to each other to a single point
6109function _reducePoints(points, sqTolerance) {
6110 var reducedPoints = [points[0]];
6111
6112 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6113 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6114 reducedPoints.push(points[i]);
6115 prev = i;
6116 }
6117 }
6118 if (prev < len - 1) {
6119 reducedPoints.push(points[len - 1]);
6120 }
6121 return reducedPoints;
6122}
6123
6124var _lastCode;
6125
6126// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6127// Clips the segment a to b by rectangular bounds with the
6128// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6129// (modifying the segment points directly!). Used by Leaflet to only show polyline
6130// points that are on the screen or near, increasing performance.
6131function clipSegment(a, b, bounds, useLastCode, round) {
6132 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6133 codeB = _getBitCode(b, bounds),
6134
6135 codeOut, p, newCode;
6136
6137 // save 2nd code to avoid calculating it on the next segment
6138 _lastCode = codeB;
6139
6140 while (true) {
6141 // if a,b is inside the clip window (trivial accept)
6142 if (!(codeA | codeB)) {
6143 return [a, b];
6144 }
6145
6146 // if a,b is outside the clip window (trivial reject)
6147 if (codeA & codeB) {
6148 return false;
6149 }
6150
6151 // other cases
6152 codeOut = codeA || codeB;
6153 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6154 newCode = _getBitCode(p, bounds);
6155
6156 if (codeOut === codeA) {
6157 a = p;
6158 codeA = newCode;
6159 } else {
6160 b = p;
6161 codeB = newCode;
6162 }
6163 }
6164}
6165
6166function _getEdgeIntersection(a, b, code, bounds, round) {
6167 var dx = b.x - a.x,
6168 dy = b.y - a.y,
6169 min = bounds.min,
6170 max = bounds.max,
6171 x, y;
6172
6173 if (code & 8) { // top
6174 x = a.x + dx * (max.y - a.y) / dy;
6175 y = max.y;
6176
6177 } else if (code & 4) { // bottom
6178 x = a.x + dx * (min.y - a.y) / dy;
6179 y = min.y;
6180
6181 } else if (code & 2) { // right
6182 x = max.x;
6183 y = a.y + dy * (max.x - a.x) / dx;
6184
6185 } else if (code & 1) { // left
6186 x = min.x;
6187 y = a.y + dy * (min.x - a.x) / dx;
6188 }
6189
6190 return new Point(x, y, round);
6191}
6192
6193function _getBitCode(p, bounds) {
6194 var code = 0;
6195
6196 if (p.x < bounds.min.x) { // left
6197 code |= 1;
6198 } else if (p.x > bounds.max.x) { // right
6199 code |= 2;
6200 }
6201
6202 if (p.y < bounds.min.y) { // bottom
6203 code |= 4;
6204 } else if (p.y > bounds.max.y) { // top
6205 code |= 8;
6206 }
6207
6208 return code;
6209}
6210
6211// square distance (to avoid unnecessary Math.sqrt calls)
6212function _sqDist(p1, p2) {
6213 var dx = p2.x - p1.x,
6214 dy = p2.y - p1.y;
6215 return dx * dx + dy * dy;
6216}
6217
6218// return closest point on segment or distance to that point
6219function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6220 var x = p1.x,
6221 y = p1.y,
6222 dx = p2.x - x,
6223 dy = p2.y - y,
6224 dot = dx * dx + dy * dy,
6225 t;
6226
6227 if (dot > 0) {
6228 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6229
6230 if (t > 1) {
6231 x = p2.x;
6232 y = p2.y;
6233 } else if (t > 0) {
6234 x += dx * t;
6235 y += dy * t;
6236 }
6237 }
6238
6239 dx = p.x - x;
6240 dy = p.y - y;
6241
6242 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6243}
6244
6245
6246// @function isFlat(latlngs: LatLng[]): Boolean
6247// Returns true if `latlngs` is a flat array, false is nested.
6248function isFlat(latlngs) {
6249 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6250}
6251
6252function _flat(latlngs) {
6253 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6254 return isFlat(latlngs);
6255}
6256
6257
6258var LineUtil = (Object.freeze || Object)({
6259 simplify: simplify,
6260 pointToSegmentDistance: pointToSegmentDistance,
6261 closestPointOnSegment: closestPointOnSegment,
6262 clipSegment: clipSegment,
6263 _getEdgeIntersection: _getEdgeIntersection,
6264 _getBitCode: _getBitCode,
6265 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6266 isFlat: isFlat,
6267 _flat: _flat
6268});
6269
6270/*
6271 * @namespace PolyUtil
6272 * Various utility functions for polygon geometries.
6273 */
6274
6275/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6276 * 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)).
6277 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6278 * performance. Note that polygon points needs different algorithm for clipping
6279 * than polyline, so there's a separate method for it.
6280 */
6281function clipPolygon(points, bounds, round) {
6282 var clippedPoints,
6283 edges = [1, 4, 2, 8],
6284 i, j, k,
6285 a, b,
6286 len, edge, p;
6287
6288 for (i = 0, len = points.length; i < len; i++) {
6289 points[i]._code = _getBitCode(points[i], bounds);
6290 }
6291
6292 // for each edge (left, bottom, right, top)
6293 for (k = 0; k < 4; k++) {
6294 edge = edges[k];
6295 clippedPoints = [];
6296
6297 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6298 a = points[i];
6299 b = points[j];
6300
6301 // if a is inside the clip window
6302 if (!(a._code & edge)) {
6303 // if b is outside the clip window (a->b goes out of screen)
6304 if (b._code & edge) {
6305 p = _getEdgeIntersection(b, a, edge, bounds, round);
6306 p._code = _getBitCode(p, bounds);
6307 clippedPoints.push(p);
6308 }
6309 clippedPoints.push(a);
6310
6311 // else if b is inside the clip window (a->b enters the screen)
6312 } else if (!(b._code & edge)) {
6313 p = _getEdgeIntersection(b, a, edge, bounds, round);
6314 p._code = _getBitCode(p, bounds);
6315 clippedPoints.push(p);
6316 }
6317 }
6318 points = clippedPoints;
6319 }
6320
6321 return points;
6322}
6323
6324
6325var PolyUtil = (Object.freeze || Object)({
6326 clipPolygon: clipPolygon
6327});
6328
6329/*
6330 * @namespace Projection
6331 * @section
6332 * Leaflet comes with a set of already defined Projections out of the box:
6333 *
6334 * @projection L.Projection.LonLat
6335 *
6336 * Equirectangular, or Plate Carree projection — the most simple projection,
6337 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6338 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6339 * `EPSG:4326` and `Simple` CRS.
6340 */
6341
6342var LonLat = {
6343 project: function (latlng) {
6344 return new Point(latlng.lng, latlng.lat);
6345 },
6346
6347 unproject: function (point) {
6348 return new LatLng(point.y, point.x);
6349 },
6350
6351 bounds: new Bounds([-180, -90], [180, 90])
6352};
6353
6354/*
6355 * @namespace Projection
6356 * @projection L.Projection.Mercator
6357 *
6358 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6359 */
6360
6361var Mercator = {
6362 R: 6378137,
6363 R_MINOR: 6356752.314245179,
6364
6365 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6366
6367 project: function (latlng) {
6368 var d = Math.PI / 180,
6369 r = this.R,
6370 y = latlng.lat * d,
6371 tmp = this.R_MINOR / r,
6372 e = Math.sqrt(1 - tmp * tmp),
6373 con = e * Math.sin(y);
6374
6375 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6376 y = -r * Math.log(Math.max(ts, 1E-10));
6377
6378 return new Point(latlng.lng * d * r, y);
6379 },
6380
6381 unproject: function (point) {
6382 var d = 180 / Math.PI,
6383 r = this.R,
6384 tmp = this.R_MINOR / r,
6385 e = Math.sqrt(1 - tmp * tmp),
6386 ts = Math.exp(-point.y / r),
6387 phi = Math.PI / 2 - 2 * Math.atan(ts);
6388
6389 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6390 con = e * Math.sin(phi);
6391 con = Math.pow((1 - con) / (1 + con), e / 2);
6392 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6393 phi += dphi;
6394 }
6395
6396 return new LatLng(phi * d, point.x * d / r);
6397 }
6398};
6399
6400/*
6401 * @class Projection
6402
6403 * An object with methods for projecting geographical coordinates of the world onto
6404 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6405
6406 * @property bounds: Bounds
6407 * The bounds (specified in CRS units) where the projection is valid
6408
6409 * @method project(latlng: LatLng): Point
6410 * Projects geographical coordinates into a 2D point.
6411 * Only accepts actual `L.LatLng` instances, not arrays.
6412
6413 * @method unproject(point: Point): LatLng
6414 * The inverse of `project`. Projects a 2D point into a geographical location.
6415 * Only accepts actual `L.Point` instances, not arrays.
6416
6417 * Note that the projection instances do not inherit from Leafet's `Class` object,
6418 * and can't be instantiated. Also, new classes can't inherit from them,
6419 * and methods can't be added to them with the `include` function.
6420
6421 */
6422
6423
6424
6425
6426var index = (Object.freeze || Object)({
6427 LonLat: LonLat,
6428 Mercator: Mercator,
6429 SphericalMercator: SphericalMercator
6430});
6431
6432/*
6433 * @namespace CRS
6434 * @crs L.CRS.EPSG3395
6435 *
6436 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6437 */
6438var EPSG3395 = extend({}, Earth, {
6439 code: 'EPSG:3395',
6440 projection: Mercator,
6441
6442 transformation: (function () {
6443 var scale = 0.5 / (Math.PI * Mercator.R);
6444 return toTransformation(scale, 0.5, -scale, 0.5);
6445 }())
6446});
6447
6448/*
6449 * @namespace CRS
6450 * @crs L.CRS.EPSG4326
6451 *
6452 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6453 *
6454 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6455 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6456 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6457 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6458 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6459 */
6460
6461var EPSG4326 = extend({}, Earth, {
6462 code: 'EPSG:4326',
6463 projection: LonLat,
6464 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6465});
6466
6467/*
6468 * @namespace CRS
6469 * @crs L.CRS.Simple
6470 *
6471 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6472 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6473 * axis should still be inverted (going from bottom to top). `distance()` returns
6474 * simple euclidean distance.
6475 */
6476
6477var Simple = extend({}, CRS, {
6478 projection: LonLat,
6479 transformation: toTransformation(1, 0, -1, 0),
6480
6481 scale: function (zoom) {
6482 return Math.pow(2, zoom);
6483 },
6484
6485 zoom: function (scale) {
6486 return Math.log(scale) / Math.LN2;
6487 },
6488
6489 distance: function (latlng1, latlng2) {
6490 var dx = latlng2.lng - latlng1.lng,
6491 dy = latlng2.lat - latlng1.lat;
6492
6493 return Math.sqrt(dx * dx + dy * dy);
6494 },
6495
6496 infinite: true
6497});
6498
6499CRS.Earth = Earth;
6500CRS.EPSG3395 = EPSG3395;
6501CRS.EPSG3857 = EPSG3857;
6502CRS.EPSG900913 = EPSG900913;
6503CRS.EPSG4326 = EPSG4326;
6504CRS.Simple = Simple;
6505
6506/*
6507 * @class Layer
6508 * @inherits Evented
6509 * @aka L.Layer
6510 * @aka ILayer
6511 *
6512 * A set of methods from the Layer base class that all Leaflet layers use.
6513 * Inherits all methods, options and events from `L.Evented`.
6514 *
6515 * @example
6516 *
6517 * ```js
6518 * var layer = L.marker(latlng).addTo(map);
6519 * layer.addTo(map);
6520 * layer.remove();
6521 * ```
6522 *
6523 * @event add: Event
6524 * Fired after the layer is added to a map
6525 *
6526 * @event remove: Event
6527 * Fired after the layer is removed from a map
6528 */
6529
6530
6531var Layer = Evented.extend({
6532
6533 // Classes extending `L.Layer` will inherit the following options:
6534 options: {
6535 // @option pane: String = 'overlayPane'
6536 // 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.
6537 pane: 'overlayPane',
6538
6539 // @option attribution: String = null
6540 // 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.
6541 attribution: null,
6542
6543 bubblingMouseEvents: true
6544 },
6545
6546 /* @section
6547 * Classes extending `L.Layer` will inherit the following methods:
6548 *
6549 * @method addTo(map: Map|LayerGroup): this
6550 * Adds the layer to the given map or layer group.
6551 */
6552 addTo: function (map) {
6553 map.addLayer(this);
6554 return this;
6555 },
6556
6557 // @method remove: this
6558 // Removes the layer from the map it is currently active on.
6559 remove: function () {
6560 return this.removeFrom(this._map || this._mapToAdd);
6561 },
6562
6563 // @method removeFrom(map: Map): this
6564 // Removes the layer from the given map
6565 removeFrom: function (obj) {
6566 if (obj) {
6567 obj.removeLayer(this);
6568 }
6569 return this;
6570 },
6571
6572 // @method getPane(name? : String): HTMLElement
6573 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6574 getPane: function (name) {
6575 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6576 },
6577
6578 addInteractiveTarget: function (targetEl) {
6579 this._map._targets[stamp(targetEl)] = this;
6580 return this;
6581 },
6582
6583 removeInteractiveTarget: function (targetEl) {
6584 delete this._map._targets[stamp(targetEl)];
6585 return this;
6586 },
6587
6588 // @method getAttribution: String
6589 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6590 getAttribution: function () {
6591 return this.options.attribution;
6592 },
6593
6594 _layerAdd: function (e) {
6595 var map = e.target;
6596
6597 // check in case layer gets added and then removed before the map is ready
6598 if (!map.hasLayer(this)) { return; }
6599
6600 this._map = map;
6601 this._zoomAnimated = map._zoomAnimated;
6602
6603 if (this.getEvents) {
6604 var events = this.getEvents();
6605 map.on(events, this);
6606 this.once('remove', function () {
6607 map.off(events, this);
6608 }, this);
6609 }
6610
6611 this.onAdd(map);
6612
6613 if (this.getAttribution && map.attributionControl) {
6614 map.attributionControl.addAttribution(this.getAttribution());
6615 }
6616
6617 this.fire('add');
6618 map.fire('layeradd', {layer: this});
6619 }
6620});
6621
6622/* @section Extension methods
6623 * @uninheritable
6624 *
6625 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6626 *
6627 * @method onAdd(map: Map): this
6628 * 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).
6629 *
6630 * @method onRemove(map: Map): this
6631 * 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).
6632 *
6633 * @method getEvents(): Object
6634 * 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.
6635 *
6636 * @method getAttribution(): String
6637 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6638 *
6639 * @method beforeAdd(map: Map): this
6640 * 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.
6641 */
6642
6643
6644/* @namespace Map
6645 * @section Layer events
6646 *
6647 * @event layeradd: LayerEvent
6648 * Fired when a new layer is added to the map.
6649 *
6650 * @event layerremove: LayerEvent
6651 * Fired when some layer is removed from the map
6652 *
6653 * @section Methods for Layers and Controls
6654 */
6655Map.include({
6656 // @method addLayer(layer: Layer): this
6657 // Adds the given layer to the map
6658 addLayer: function (layer) {
6659 if (!layer._layerAdd) {
6660 throw new Error('The provided object is not a Layer.');
6661 }
6662
6663 var id = stamp(layer);
6664 if (this._layers[id]) { return this; }
6665 this._layers[id] = layer;
6666
6667 layer._mapToAdd = this;
6668
6669 if (layer.beforeAdd) {
6670 layer.beforeAdd(this);
6671 }
6672
6673 this.whenReady(layer._layerAdd, layer);
6674
6675 return this;
6676 },
6677
6678 // @method removeLayer(layer: Layer): this
6679 // Removes the given layer from the map.
6680 removeLayer: function (layer) {
6681 var id = stamp(layer);
6682
6683 if (!this._layers[id]) { return this; }
6684
6685 if (this._loaded) {
6686 layer.onRemove(this);
6687 }
6688
6689 if (layer.getAttribution && this.attributionControl) {
6690 this.attributionControl.removeAttribution(layer.getAttribution());
6691 }
6692
6693 delete this._layers[id];
6694
6695 if (this._loaded) {
6696 this.fire('layerremove', {layer: layer});
6697 layer.fire('remove');
6698 }
6699
6700 layer._map = layer._mapToAdd = null;
6701
6702 return this;
6703 },
6704
6705 // @method hasLayer(layer: Layer): Boolean
6706 // Returns `true` if the given layer is currently added to the map
6707 hasLayer: function (layer) {
6708 return !!layer && (stamp(layer) in this._layers);
6709 },
6710
6711 /* @method eachLayer(fn: Function, context?: Object): this
6712 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6713 * ```
6714 * map.eachLayer(function(layer){
6715 * layer.bindPopup('Hello');
6716 * });
6717 * ```
6718 */
6719 eachLayer: function (method, context) {
6720 for (var i in this._layers) {
6721 method.call(context, this._layers[i]);
6722 }
6723 return this;
6724 },
6725
6726 _addLayers: function (layers) {
6727 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6728
6729 for (var i = 0, len = layers.length; i < len; i++) {
6730 this.addLayer(layers[i]);
6731 }
6732 },
6733
6734 _addZoomLimit: function (layer) {
6735 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6736 this._zoomBoundLayers[stamp(layer)] = layer;
6737 this._updateZoomLevels();
6738 }
6739 },
6740
6741 _removeZoomLimit: function (layer) {
6742 var id = stamp(layer);
6743
6744 if (this._zoomBoundLayers[id]) {
6745 delete this._zoomBoundLayers[id];
6746 this._updateZoomLevels();
6747 }
6748 },
6749
6750 _updateZoomLevels: function () {
6751 var minZoom = Infinity,
6752 maxZoom = -Infinity,
6753 oldZoomSpan = this._getZoomSpan();
6754
6755 for (var i in this._zoomBoundLayers) {
6756 var options = this._zoomBoundLayers[i].options;
6757
6758 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6759 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6760 }
6761
6762 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6763 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6764
6765 // @section Map state change events
6766 // @event zoomlevelschange: Event
6767 // Fired when the number of zoomlevels on the map is changed due
6768 // to adding or removing a layer.
6769 if (oldZoomSpan !== this._getZoomSpan()) {
6770 this.fire('zoomlevelschange');
6771 }
6772
6773 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6774 this.setZoom(this._layersMaxZoom);
6775 }
6776 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6777 this.setZoom(this._layersMinZoom);
6778 }
6779 }
6780});
6781
6782/*
6783 * @class LayerGroup
6784 * @aka L.LayerGroup
6785 * @inherits Layer
6786 *
6787 * Used to group several layers and handle them as one. If you add it to the map,
6788 * any layers added or removed from the group will be added/removed on the map as
6789 * well. Extends `Layer`.
6790 *
6791 * @example
6792 *
6793 * ```js
6794 * L.layerGroup([marker1, marker2])
6795 * .addLayer(polyline)
6796 * .addTo(map);
6797 * ```
6798 */
6799
6800var LayerGroup = Layer.extend({
6801
6802 initialize: function (layers, options) {
6803 setOptions(this, options);
6804
6805 this._layers = {};
6806
6807 var i, len;
6808
6809 if (layers) {
6810 for (i = 0, len = layers.length; i < len; i++) {
6811 this.addLayer(layers[i]);
6812 }
6813 }
6814 },
6815
6816 // @method addLayer(layer: Layer): this
6817 // Adds the given layer to the group.
6818 addLayer: function (layer) {
6819 var id = this.getLayerId(layer);
6820
6821 this._layers[id] = layer;
6822
6823 if (this._map) {
6824 this._map.addLayer(layer);
6825 }
6826
6827 return this;
6828 },
6829
6830 // @method removeLayer(layer: Layer): this
6831 // Removes the given layer from the group.
6832 // @alternative
6833 // @method removeLayer(id: Number): this
6834 // Removes the layer with the given internal ID from the group.
6835 removeLayer: function (layer) {
6836 var id = layer in this._layers ? layer : this.getLayerId(layer);
6837
6838 if (this._map && this._layers[id]) {
6839 this._map.removeLayer(this._layers[id]);
6840 }
6841
6842 delete this._layers[id];
6843
6844 return this;
6845 },
6846
6847 // @method hasLayer(layer: Layer): Boolean
6848 // Returns `true` if the given layer is currently added to the group.
6849 // @alternative
6850 // @method hasLayer(id: Number): Boolean
6851 // Returns `true` if the given internal ID is currently added to the group.
6852 hasLayer: function (layer) {
6853 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6854 },
6855
6856 // @method clearLayers(): this
6857 // Removes all the layers from the group.
6858 clearLayers: function () {
6859 return this.eachLayer(this.removeLayer, this);
6860 },
6861
6862 // @method invoke(methodName: String, …): this
6863 // Calls `methodName` on every layer contained in this group, passing any
6864 // additional parameters. Has no effect if the layers contained do not
6865 // implement `methodName`.
6866 invoke: function (methodName) {
6867 var args = Array.prototype.slice.call(arguments, 1),
6868 i, layer;
6869
6870 for (i in this._layers) {
6871 layer = this._layers[i];
6872
6873 if (layer[methodName]) {
6874 layer[methodName].apply(layer, args);
6875 }
6876 }
6877
6878 return this;
6879 },
6880
6881 onAdd: function (map) {
6882 this.eachLayer(map.addLayer, map);
6883 },
6884
6885 onRemove: function (map) {
6886 this.eachLayer(map.removeLayer, map);
6887 },
6888
6889 // @method eachLayer(fn: Function, context?: Object): this
6890 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6891 // ```js
6892 // group.eachLayer(function (layer) {
6893 // layer.bindPopup('Hello');
6894 // });
6895 // ```
6896 eachLayer: function (method, context) {
6897 for (var i in this._layers) {
6898 method.call(context, this._layers[i]);
6899 }
6900 return this;
6901 },
6902
6903 // @method getLayer(id: Number): Layer
6904 // Returns the layer with the given internal ID.
6905 getLayer: function (id) {
6906 return this._layers[id];
6907 },
6908
6909 // @method getLayers(): Layer[]
6910 // Returns an array of all the layers added to the group.
6911 getLayers: function () {
6912 var layers = [];
6913 this.eachLayer(layers.push, layers);
6914 return layers;
6915 },
6916
6917 // @method setZIndex(zIndex: Number): this
6918 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6919 setZIndex: function (zIndex) {
6920 return this.invoke('setZIndex', zIndex);
6921 },
6922
6923 // @method getLayerId(layer: Layer): Number
6924 // Returns the internal ID for a layer
6925 getLayerId: function (layer) {
6926 return stamp(layer);
6927 }
6928});
6929
6930
6931// @factory L.layerGroup(layers?: Layer[], options?: Object)
6932// Create a layer group, optionally given an initial set of layers and an `options` object.
6933var layerGroup = function (layers, options) {
6934 return new LayerGroup(layers, options);
6935};
6936
6937/*
6938 * @class FeatureGroup
6939 * @aka L.FeatureGroup
6940 * @inherits LayerGroup
6941 *
6942 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6943 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6944 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6945 * handler, it will handle events from any of the layers. This includes mouse events
6946 * and custom events.
6947 * * Has `layeradd` and `layerremove` events
6948 *
6949 * @example
6950 *
6951 * ```js
6952 * L.featureGroup([marker1, marker2, polyline])
6953 * .bindPopup('Hello world!')
6954 * .on('click', function() { alert('Clicked on a member of the group!'); })
6955 * .addTo(map);
6956 * ```
6957 */
6958
6959var FeatureGroup = LayerGroup.extend({
6960
6961 addLayer: function (layer) {
6962 if (this.hasLayer(layer)) {
6963 return this;
6964 }
6965
6966 layer.addEventParent(this);
6967
6968 LayerGroup.prototype.addLayer.call(this, layer);
6969
6970 // @event layeradd: LayerEvent
6971 // Fired when a layer is added to this `FeatureGroup`
6972 return this.fire('layeradd', {layer: layer});
6973 },
6974
6975 removeLayer: function (layer) {
6976 if (!this.hasLayer(layer)) {
6977 return this;
6978 }
6979 if (layer in this._layers) {
6980 layer = this._layers[layer];
6981 }
6982
6983 layer.removeEventParent(this);
6984
6985 LayerGroup.prototype.removeLayer.call(this, layer);
6986
6987 // @event layerremove: LayerEvent
6988 // Fired when a layer is removed from this `FeatureGroup`
6989 return this.fire('layerremove', {layer: layer});
6990 },
6991
6992 // @method setStyle(style: Path options): this
6993 // Sets the given path options to each layer of the group that has a `setStyle` method.
6994 setStyle: function (style) {
6995 return this.invoke('setStyle', style);
6996 },
6997
6998 // @method bringToFront(): this
6999 // Brings the layer group to the top of all other layers
7000 bringToFront: function () {
7001 return this.invoke('bringToFront');
7002 },
7003
7004 // @method bringToBack(): this
7005 // Brings the layer group to the back of all other layers
7006 bringToBack: function () {
7007 return this.invoke('bringToBack');
7008 },
7009
7010 // @method getBounds(): LatLngBounds
7011 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7012 getBounds: function () {
7013 var bounds = new LatLngBounds();
7014
7015 for (var id in this._layers) {
7016 var layer = this._layers[id];
7017 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7018 }
7019 return bounds;
7020 }
7021});
7022
7023// @factory L.featureGroup(layers: Layer[])
7024// Create a feature group, optionally given an initial set of layers.
7025var featureGroup = function (layers) {
7026 return new FeatureGroup(layers);
7027};
7028
7029/*
7030 * @class Icon
7031 * @aka L.Icon
7032 *
7033 * Represents an icon to provide when creating a marker.
7034 *
7035 * @example
7036 *
7037 * ```js
7038 * var myIcon = L.icon({
7039 * iconUrl: 'my-icon.png',
7040 * iconRetinaUrl: 'my-icon@2x.png',
7041 * iconSize: [38, 95],
7042 * iconAnchor: [22, 94],
7043 * popupAnchor: [-3, -76],
7044 * shadowUrl: 'my-icon-shadow.png',
7045 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7046 * shadowSize: [68, 95],
7047 * shadowAnchor: [22, 94]
7048 * });
7049 *
7050 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7051 * ```
7052 *
7053 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7054 *
7055 */
7056
7057var Icon = Class.extend({
7058
7059 /* @section
7060 * @aka Icon options
7061 *
7062 * @option iconUrl: String = null
7063 * **(required)** The URL to the icon image (absolute or relative to your script path).
7064 *
7065 * @option iconRetinaUrl: String = null
7066 * The URL to a retina sized version of the icon image (absolute or relative to your
7067 * script path). Used for Retina screen devices.
7068 *
7069 * @option iconSize: Point = null
7070 * Size of the icon image in pixels.
7071 *
7072 * @option iconAnchor: Point = null
7073 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7074 * will be aligned so that this point is at the marker's geographical location. Centered
7075 * by default if size is specified, also can be set in CSS with negative margins.
7076 *
7077 * @option popupAnchor: Point = [0, 0]
7078 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7079 *
7080 * @option tooltipAnchor: Point = [0, 0]
7081 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7082 *
7083 * @option shadowUrl: String = null
7084 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7085 *
7086 * @option shadowRetinaUrl: String = null
7087 *
7088 * @option shadowSize: Point = null
7089 * Size of the shadow image in pixels.
7090 *
7091 * @option shadowAnchor: Point = null
7092 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7093 * as iconAnchor if not specified).
7094 *
7095 * @option className: String = ''
7096 * A custom class name to assign to both icon and shadow images. Empty by default.
7097 */
7098
7099 options: {
7100 popupAnchor: [0, 0],
7101 tooltipAnchor: [0, 0]
7102 },
7103
7104 initialize: function (options) {
7105 setOptions(this, options);
7106 },
7107
7108 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7109 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7110 // styled according to the options.
7111 createIcon: function (oldIcon) {
7112 return this._createIcon('icon', oldIcon);
7113 },
7114
7115 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7116 // As `createIcon`, but for the shadow beneath it.
7117 createShadow: function (oldIcon) {
7118 return this._createIcon('shadow', oldIcon);
7119 },
7120
7121 _createIcon: function (name, oldIcon) {
7122 var src = this._getIconUrl(name);
7123
7124 if (!src) {
7125 if (name === 'icon') {
7126 throw new Error('iconUrl not set in Icon options (see the docs).');
7127 }
7128 return null;
7129 }
7130
7131 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7132 this._setIconStyles(img, name);
7133
7134 return img;
7135 },
7136
7137 _setIconStyles: function (img, name) {
7138 var options = this.options;
7139 var sizeOption = options[name + 'Size'];
7140
7141 if (typeof sizeOption === 'number') {
7142 sizeOption = [sizeOption, sizeOption];
7143 }
7144
7145 var size = toPoint(sizeOption),
7146 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7147 size && size.divideBy(2, true));
7148
7149 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7150
7151 if (anchor) {
7152 img.style.marginLeft = (-anchor.x) + 'px';
7153 img.style.marginTop = (-anchor.y) + 'px';
7154 }
7155
7156 if (size) {
7157 img.style.width = size.x + 'px';
7158 img.style.height = size.y + 'px';
7159 }
7160 },
7161
7162 _createImg: function (src, el) {
7163 el = el || document.createElement('img');
7164 el.src = src;
7165 return el;
7166 },
7167
7168 _getIconUrl: function (name) {
7169 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7170 }
7171});
7172
7173
7174// @factory L.icon(options: Icon options)
7175// Creates an icon instance with the given options.
7176function icon(options) {
7177 return new Icon(options);
7178}
7179
7180/*
7181 * @miniclass Icon.Default (Icon)
7182 * @aka L.Icon.Default
7183 * @section
7184 *
7185 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7186 * no icon is specified. Points to the blue marker image distributed with Leaflet
7187 * releases.
7188 *
7189 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7190 * (which is a set of `Icon options`).
7191 *
7192 * If you want to _completely_ replace the default icon, override the
7193 * `L.Marker.prototype.options.icon` with your own icon instead.
7194 */
7195
7196var IconDefault = Icon.extend({
7197
7198 options: {
7199 iconUrl: 'marker-icon.png',
7200 iconRetinaUrl: 'marker-icon-2x.png',
7201 shadowUrl: 'marker-shadow.png',
7202 iconSize: [25, 41],
7203 iconAnchor: [12, 41],
7204 popupAnchor: [1, -34],
7205 tooltipAnchor: [16, -28],
7206 shadowSize: [41, 41]
7207 },
7208
7209 _getIconUrl: function (name) {
7210 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7211 IconDefault.imagePath = this._detectIconPath();
7212 }
7213
7214 // @option imagePath: String
7215 // `Icon.Default` will try to auto-detect the location of the
7216 // blue icon images. If you are placing these images in a non-standard
7217 // way, set this option to point to the right path.
7218 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7219 },
7220
7221 _detectIconPath: function () {
7222 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7223 var path = getStyle(el, 'background-image') ||
7224 getStyle(el, 'backgroundImage'); // IE8
7225
7226 document.body.removeChild(el);
7227
7228 if (path === null || path.indexOf('url') !== 0) {
7229 path = '';
7230 } else {
7231 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7232 }
7233
7234 return path;
7235 }
7236});
7237
7238/*
7239 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7240 */
7241
7242
7243/* @namespace Marker
7244 * @section Interaction handlers
7245 *
7246 * 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:
7247 *
7248 * ```js
7249 * marker.dragging.disable();
7250 * ```
7251 *
7252 * @property dragging: Handler
7253 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7254 */
7255
7256var MarkerDrag = Handler.extend({
7257 initialize: function (marker) {
7258 this._marker = marker;
7259 },
7260
7261 addHooks: function () {
7262 var icon = this._marker._icon;
7263
7264 if (!this._draggable) {
7265 this._draggable = new Draggable(icon, icon, true);
7266 }
7267
7268 this._draggable.on({
7269 dragstart: this._onDragStart,
7270 predrag: this._onPreDrag,
7271 drag: this._onDrag,
7272 dragend: this._onDragEnd
7273 }, this).enable();
7274
7275 addClass(icon, 'leaflet-marker-draggable');
7276 },
7277
7278 removeHooks: function () {
7279 this._draggable.off({
7280 dragstart: this._onDragStart,
7281 predrag: this._onPreDrag,
7282 drag: this._onDrag,
7283 dragend: this._onDragEnd
7284 }, this).disable();
7285
7286 if (this._marker._icon) {
7287 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7288 }
7289 },
7290
7291 moved: function () {
7292 return this._draggable && this._draggable._moved;
7293 },
7294
7295 _adjustPan: function (e) {
7296 var marker = this._marker,
7297 map = marker._map,
7298 speed = this._marker.options.autoPanSpeed,
7299 padding = this._marker.options.autoPanPadding,
7300 iconPos = getPosition(marker._icon),
7301 bounds = map.getPixelBounds(),
7302 origin = map.getPixelOrigin();
7303
7304 var panBounds = toBounds(
7305 bounds.min._subtract(origin).add(padding),
7306 bounds.max._subtract(origin).subtract(padding)
7307 );
7308
7309 if (!panBounds.contains(iconPos)) {
7310 // Compute incremental movement
7311 var movement = toPoint(
7312 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7313 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7314
7315 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7316 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7317 ).multiplyBy(speed);
7318
7319 map.panBy(movement, {animate: false});
7320
7321 this._draggable._newPos._add(movement);
7322 this._draggable._startPos._add(movement);
7323
7324 setPosition(marker._icon, this._draggable._newPos);
7325 this._onDrag(e);
7326
7327 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7328 }
7329 },
7330
7331 _onDragStart: function () {
7332 // @section Dragging events
7333 // @event dragstart: Event
7334 // Fired when the user starts dragging the marker.
7335
7336 // @event movestart: Event
7337 // Fired when the marker starts moving (because of dragging).
7338
7339 this._oldLatLng = this._marker.getLatLng();
7340 this._marker
7341 .closePopup()
7342 .fire('movestart')
7343 .fire('dragstart');
7344 },
7345
7346 _onPreDrag: function (e) {
7347 if (this._marker.options.autoPan) {
7348 cancelAnimFrame(this._panRequest);
7349 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7350 }
7351 },
7352
7353 _onDrag: function (e) {
7354 var marker = this._marker,
7355 shadow = marker._shadow,
7356 iconPos = getPosition(marker._icon),
7357 latlng = marker._map.layerPointToLatLng(iconPos);
7358
7359 // update shadow position
7360 if (shadow) {
7361 setPosition(shadow, iconPos);
7362 }
7363
7364 marker._latlng = latlng;
7365 e.latlng = latlng;
7366 e.oldLatLng = this._oldLatLng;
7367
7368 // @event drag: Event
7369 // Fired repeatedly while the user drags the marker.
7370 marker
7371 .fire('move', e)
7372 .fire('drag', e);
7373 },
7374
7375 _onDragEnd: function (e) {
7376 // @event dragend: DragEndEvent
7377 // Fired when the user stops dragging the marker.
7378
7379 cancelAnimFrame(this._panRequest);
7380
7381 // @event moveend: Event
7382 // Fired when the marker stops moving (because of dragging).
7383 delete this._oldLatLng;
7384 this._marker
7385 .fire('moveend')
7386 .fire('dragend', e);
7387 }
7388});
7389
7390/*
7391 * @class Marker
7392 * @inherits Interactive layer
7393 * @aka L.Marker
7394 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7395 *
7396 * @example
7397 *
7398 * ```js
7399 * L.marker([50.5, 30.5]).addTo(map);
7400 * ```
7401 */
7402
7403var Marker = Layer.extend({
7404
7405 // @section
7406 // @aka Marker options
7407 options: {
7408 // @option icon: Icon = *
7409 // Icon instance to use for rendering the marker.
7410 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7411 // If not specified, a common instance of `L.Icon.Default` is used.
7412 icon: new IconDefault(),
7413
7414 // Option inherited from "Interactive layer" abstract class
7415 interactive: true,
7416
7417 // @option keyboard: Boolean = true
7418 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7419 keyboard: true,
7420
7421 // @option title: String = ''
7422 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7423 title: '',
7424
7425 // @option alt: String = ''
7426 // Text for the `alt` attribute of the icon image (useful for accessibility).
7427 alt: '',
7428
7429 // @option zIndexOffset: Number = 0
7430 // 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).
7431 zIndexOffset: 0,
7432
7433 // @option opacity: Number = 1.0
7434 // The opacity of the marker.
7435 opacity: 1,
7436
7437 // @option riseOnHover: Boolean = false
7438 // If `true`, the marker will get on top of others when you hover the mouse over it.
7439 riseOnHover: false,
7440
7441 // @option riseOffset: Number = 250
7442 // The z-index offset used for the `riseOnHover` feature.
7443 riseOffset: 250,
7444
7445 // @option pane: String = 'markerPane'
7446 // `Map pane` where the markers icon will be added.
7447 pane: 'markerPane',
7448
7449 // @option pane: String = 'shadowPane'
7450 // `Map pane` where the markers shadow will be added.
7451 shadowPane: 'shadowPane',
7452
7453 // @option bubblingMouseEvents: Boolean = false
7454 // When `true`, a mouse event on this marker will trigger the same event on the map
7455 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7456 bubblingMouseEvents: false,
7457
7458 // @section Draggable marker options
7459 // @option draggable: Boolean = false
7460 // Whether the marker is draggable with mouse/touch or not.
7461 draggable: false,
7462
7463 // @option autoPan: Boolean = false
7464 // Whether to pan the map when dragging this marker near its edge or not.
7465 autoPan: false,
7466
7467 // @option autoPanPadding: Point = Point(50, 50)
7468 // Distance (in pixels to the left/right and to the top/bottom) of the
7469 // map edge to start panning the map.
7470 autoPanPadding: [50, 50],
7471
7472 // @option autoPanSpeed: Number = 10
7473 // Number of pixels the map should pan by.
7474 autoPanSpeed: 10
7475 },
7476
7477 /* @section
7478 *
7479 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7480 */
7481
7482 initialize: function (latlng, options) {
7483 setOptions(this, options);
7484 this._latlng = toLatLng(latlng);
7485 },
7486
7487 onAdd: function (map) {
7488 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7489
7490 if (this._zoomAnimated) {
7491 map.on('zoomanim', this._animateZoom, this);
7492 }
7493
7494 this._initIcon();
7495 this.update();
7496 },
7497
7498 onRemove: function (map) {
7499 if (this.dragging && this.dragging.enabled()) {
7500 this.options.draggable = true;
7501 this.dragging.removeHooks();
7502 }
7503 delete this.dragging;
7504
7505 if (this._zoomAnimated) {
7506 map.off('zoomanim', this._animateZoom, this);
7507 }
7508
7509 this._removeIcon();
7510 this._removeShadow();
7511 },
7512
7513 getEvents: function () {
7514 return {
7515 zoom: this.update,
7516 viewreset: this.update
7517 };
7518 },
7519
7520 // @method getLatLng: LatLng
7521 // Returns the current geographical position of the marker.
7522 getLatLng: function () {
7523 return this._latlng;
7524 },
7525
7526 // @method setLatLng(latlng: LatLng): this
7527 // Changes the marker position to the given point.
7528 setLatLng: function (latlng) {
7529 var oldLatLng = this._latlng;
7530 this._latlng = toLatLng(latlng);
7531 this.update();
7532
7533 // @event move: Event
7534 // 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`.
7535 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7536 },
7537
7538 // @method setZIndexOffset(offset: Number): this
7539 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7540 setZIndexOffset: function (offset) {
7541 this.options.zIndexOffset = offset;
7542 return this.update();
7543 },
7544
7545 // @method getIcon: Icon
7546 // Returns the current icon used by the marker
7547 getIcon: function () {
7548 return this.options.icon;
7549 },
7550
7551 // @method setIcon(icon: Icon): this
7552 // Changes the marker icon.
7553 setIcon: function (icon) {
7554
7555 this.options.icon = icon;
7556
7557 if (this._map) {
7558 this._initIcon();
7559 this.update();
7560 }
7561
7562 if (this._popup) {
7563 this.bindPopup(this._popup, this._popup.options);
7564 }
7565
7566 return this;
7567 },
7568
7569 getElement: function () {
7570 return this._icon;
7571 },
7572
7573 update: function () {
7574
7575 if (this._icon && this._map) {
7576 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7577 this._setPos(pos);
7578 }
7579
7580 return this;
7581 },
7582
7583 _initIcon: function () {
7584 var options = this.options,
7585 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7586
7587 var icon = options.icon.createIcon(this._icon),
7588 addIcon = false;
7589
7590 // if we're not reusing the icon, remove the old one and init new one
7591 if (icon !== this._icon) {
7592 if (this._icon) {
7593 this._removeIcon();
7594 }
7595 addIcon = true;
7596
7597 if (options.title) {
7598 icon.title = options.title;
7599 }
7600
7601 if (icon.tagName === 'IMG') {
7602 icon.alt = options.alt || '';
7603 }
7604 }
7605
7606 addClass(icon, classToAdd);
7607
7608 if (options.keyboard) {
7609 icon.tabIndex = '0';
7610 }
7611
7612 this._icon = icon;
7613
7614 if (options.riseOnHover) {
7615 this.on({
7616 mouseover: this._bringToFront,
7617 mouseout: this._resetZIndex
7618 });
7619 }
7620
7621 var newShadow = options.icon.createShadow(this._shadow),
7622 addShadow = false;
7623
7624 if (newShadow !== this._shadow) {
7625 this._removeShadow();
7626 addShadow = true;
7627 }
7628
7629 if (newShadow) {
7630 addClass(newShadow, classToAdd);
7631 newShadow.alt = '';
7632 }
7633 this._shadow = newShadow;
7634
7635
7636 if (options.opacity < 1) {
7637 this._updateOpacity();
7638 }
7639
7640
7641 if (addIcon) {
7642 this.getPane().appendChild(this._icon);
7643 }
7644 this._initInteraction();
7645 if (newShadow && addShadow) {
7646 this.getPane(options.shadowPane).appendChild(this._shadow);
7647 }
7648 },
7649
7650 _removeIcon: function () {
7651 if (this.options.riseOnHover) {
7652 this.off({
7653 mouseover: this._bringToFront,
7654 mouseout: this._resetZIndex
7655 });
7656 }
7657
7658 remove(this._icon);
7659 this.removeInteractiveTarget(this._icon);
7660
7661 this._icon = null;
7662 },
7663
7664 _removeShadow: function () {
7665 if (this._shadow) {
7666 remove(this._shadow);
7667 }
7668 this._shadow = null;
7669 },
7670
7671 _setPos: function (pos) {
7672
7673 if (this._icon) {
7674 setPosition(this._icon, pos);
7675 }
7676
7677 if (this._shadow) {
7678 setPosition(this._shadow, pos);
7679 }
7680
7681 this._zIndex = pos.y + this.options.zIndexOffset;
7682
7683 this._resetZIndex();
7684 },
7685
7686 _updateZIndex: function (offset) {
7687 if (this._icon) {
7688 this._icon.style.zIndex = this._zIndex + offset;
7689 }
7690 },
7691
7692 _animateZoom: function (opt) {
7693 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7694
7695 this._setPos(pos);
7696 },
7697
7698 _initInteraction: function () {
7699
7700 if (!this.options.interactive) { return; }
7701
7702 addClass(this._icon, 'leaflet-interactive');
7703
7704 this.addInteractiveTarget(this._icon);
7705
7706 if (MarkerDrag) {
7707 var draggable = this.options.draggable;
7708 if (this.dragging) {
7709 draggable = this.dragging.enabled();
7710 this.dragging.disable();
7711 }
7712
7713 this.dragging = new MarkerDrag(this);
7714
7715 if (draggable) {
7716 this.dragging.enable();
7717 }
7718 }
7719 },
7720
7721 // @method setOpacity(opacity: Number): this
7722 // Changes the opacity of the marker.
7723 setOpacity: function (opacity) {
7724 this.options.opacity = opacity;
7725 if (this._map) {
7726 this._updateOpacity();
7727 }
7728
7729 return this;
7730 },
7731
7732 _updateOpacity: function () {
7733 var opacity = this.options.opacity;
7734
7735 if (this._icon) {
7736 setOpacity(this._icon, opacity);
7737 }
7738
7739 if (this._shadow) {
7740 setOpacity(this._shadow, opacity);
7741 }
7742 },
7743
7744 _bringToFront: function () {
7745 this._updateZIndex(this.options.riseOffset);
7746 },
7747
7748 _resetZIndex: function () {
7749 this._updateZIndex(0);
7750 },
7751
7752 _getPopupAnchor: function () {
7753 return this.options.icon.options.popupAnchor;
7754 },
7755
7756 _getTooltipAnchor: function () {
7757 return this.options.icon.options.tooltipAnchor;
7758 }
7759});
7760
7761
7762// factory L.marker(latlng: LatLng, options? : Marker options)
7763
7764// @factory L.marker(latlng: LatLng, options? : Marker options)
7765// Instantiates a Marker object given a geographical point and optionally an options object.
7766function marker(latlng, options) {
7767 return new Marker(latlng, options);
7768}
7769
7770/*
7771 * @class Path
7772 * @aka L.Path
7773 * @inherits Interactive layer
7774 *
7775 * An abstract class that contains options and constants shared between vector
7776 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7777 */
7778
7779var Path = Layer.extend({
7780
7781 // @section
7782 // @aka Path options
7783 options: {
7784 // @option stroke: Boolean = true
7785 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7786 stroke: true,
7787
7788 // @option color: String = '#3388ff'
7789 // Stroke color
7790 color: '#3388ff',
7791
7792 // @option weight: Number = 3
7793 // Stroke width in pixels
7794 weight: 3,
7795
7796 // @option opacity: Number = 1.0
7797 // Stroke opacity
7798 opacity: 1,
7799
7800 // @option lineCap: String= 'round'
7801 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7802 lineCap: 'round',
7803
7804 // @option lineJoin: String = 'round'
7805 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7806 lineJoin: 'round',
7807
7808 // @option dashArray: String = null
7809 // 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).
7810 dashArray: null,
7811
7812 // @option dashOffset: String = null
7813 // 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).
7814 dashOffset: null,
7815
7816 // @option fill: Boolean = depends
7817 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7818 fill: false,
7819
7820 // @option fillColor: String = *
7821 // Fill color. Defaults to the value of the [`color`](#path-color) option
7822 fillColor: null,
7823
7824 // @option fillOpacity: Number = 0.2
7825 // Fill opacity.
7826 fillOpacity: 0.2,
7827
7828 // @option fillRule: String = 'evenodd'
7829 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7830 fillRule: 'evenodd',
7831
7832 // className: '',
7833
7834 // Option inherited from "Interactive layer" abstract class
7835 interactive: true,
7836
7837 // @option bubblingMouseEvents: Boolean = true
7838 // When `true`, a mouse event on this path will trigger the same event on the map
7839 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7840 bubblingMouseEvents: true
7841 },
7842
7843 beforeAdd: function (map) {
7844 // Renderer is set here because we need to call renderer.getEvents
7845 // before this.getEvents.
7846 this._renderer = map.getRenderer(this);
7847 },
7848
7849 onAdd: function () {
7850 this._renderer._initPath(this);
7851 this._reset();
7852 this._renderer._addPath(this);
7853 },
7854
7855 onRemove: function () {
7856 this._renderer._removePath(this);
7857 },
7858
7859 // @method redraw(): this
7860 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7861 redraw: function () {
7862 if (this._map) {
7863 this._renderer._updatePath(this);
7864 }
7865 return this;
7866 },
7867
7868 // @method setStyle(style: Path options): this
7869 // Changes the appearance of a Path based on the options in the `Path options` object.
7870 setStyle: function (style) {
7871 setOptions(this, style);
7872 if (this._renderer) {
7873 this._renderer._updateStyle(this);
7874 if (this.options.stroke && style && style.hasOwnProperty('weight')) {
7875 this._updateBounds();
7876 }
7877 }
7878 return this;
7879 },
7880
7881 // @method bringToFront(): this
7882 // Brings the layer to the top of all path layers.
7883 bringToFront: function () {
7884 if (this._renderer) {
7885 this._renderer._bringToFront(this);
7886 }
7887 return this;
7888 },
7889
7890 // @method bringToBack(): this
7891 // Brings the layer to the bottom of all path layers.
7892 bringToBack: function () {
7893 if (this._renderer) {
7894 this._renderer._bringToBack(this);
7895 }
7896 return this;
7897 },
7898
7899 getElement: function () {
7900 return this._path;
7901 },
7902
7903 _reset: function () {
7904 // defined in child classes
7905 this._project();
7906 this._update();
7907 },
7908
7909 _clickTolerance: function () {
7910 // used when doing hit detection for Canvas layers
7911 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7912 }
7913});
7914
7915/*
7916 * @class CircleMarker
7917 * @aka L.CircleMarker
7918 * @inherits Path
7919 *
7920 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7921 */
7922
7923var CircleMarker = Path.extend({
7924
7925 // @section
7926 // @aka CircleMarker options
7927 options: {
7928 fill: true,
7929
7930 // @option radius: Number = 10
7931 // Radius of the circle marker, in pixels
7932 radius: 10
7933 },
7934
7935 initialize: function (latlng, options) {
7936 setOptions(this, options);
7937 this._latlng = toLatLng(latlng);
7938 this._radius = this.options.radius;
7939 },
7940
7941 // @method setLatLng(latLng: LatLng): this
7942 // Sets the position of a circle marker to a new location.
7943 setLatLng: function (latlng) {
7944 var oldLatLng = this._latlng;
7945 this._latlng = toLatLng(latlng);
7946 this.redraw();
7947
7948 // @event move: Event
7949 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7950 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7951 },
7952
7953 // @method getLatLng(): LatLng
7954 // Returns the current geographical position of the circle marker
7955 getLatLng: function () {
7956 return this._latlng;
7957 },
7958
7959 // @method setRadius(radius: Number): this
7960 // Sets the radius of a circle marker. Units are in pixels.
7961 setRadius: function (radius) {
7962 this.options.radius = this._radius = radius;
7963 return this.redraw();
7964 },
7965
7966 // @method getRadius(): Number
7967 // Returns the current radius of the circle
7968 getRadius: function () {
7969 return this._radius;
7970 },
7971
7972 setStyle : function (options) {
7973 var radius = options && options.radius || this._radius;
7974 Path.prototype.setStyle.call(this, options);
7975 this.setRadius(radius);
7976 return this;
7977 },
7978
7979 _project: function () {
7980 this._point = this._map.latLngToLayerPoint(this._latlng);
7981 this._updateBounds();
7982 },
7983
7984 _updateBounds: function () {
7985 var r = this._radius,
7986 r2 = this._radiusY || r,
7987 w = this._clickTolerance(),
7988 p = [r + w, r2 + w];
7989 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7990 },
7991
7992 _update: function () {
7993 if (this._map) {
7994 this._updatePath();
7995 }
7996 },
7997
7998 _updatePath: function () {
7999 this._renderer._updateCircle(this);
8000 },
8001
8002 _empty: function () {
8003 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8004 },
8005
8006 // Needed by the `Canvas` renderer for interactivity
8007 _containsPoint: function (p) {
8008 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8009 }
8010});
8011
8012
8013// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8014// Instantiates a circle marker object given a geographical point, and an optional options object.
8015function circleMarker(latlng, options) {
8016 return new CircleMarker(latlng, options);
8017}
8018
8019/*
8020 * @class Circle
8021 * @aka L.Circle
8022 * @inherits CircleMarker
8023 *
8024 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8025 *
8026 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8027 *
8028 * @example
8029 *
8030 * ```js
8031 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8032 * ```
8033 */
8034
8035var Circle = CircleMarker.extend({
8036
8037 initialize: function (latlng, options, legacyOptions) {
8038 if (typeof options === 'number') {
8039 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8040 options = extend({}, legacyOptions, {radius: options});
8041 }
8042 setOptions(this, options);
8043 this._latlng = toLatLng(latlng);
8044
8045 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8046
8047 // @section
8048 // @aka Circle options
8049 // @option radius: Number; Radius of the circle, in meters.
8050 this._mRadius = this.options.radius;
8051 },
8052
8053 // @method setRadius(radius: Number): this
8054 // Sets the radius of a circle. Units are in meters.
8055 setRadius: function (radius) {
8056 this._mRadius = radius;
8057 return this.redraw();
8058 },
8059
8060 // @method getRadius(): Number
8061 // Returns the current radius of a circle. Units are in meters.
8062 getRadius: function () {
8063 return this._mRadius;
8064 },
8065
8066 // @method getBounds(): LatLngBounds
8067 // Returns the `LatLngBounds` of the path.
8068 getBounds: function () {
8069 var half = [this._radius, this._radiusY || this._radius];
8070
8071 return new LatLngBounds(
8072 this._map.layerPointToLatLng(this._point.subtract(half)),
8073 this._map.layerPointToLatLng(this._point.add(half)));
8074 },
8075
8076 setStyle: Path.prototype.setStyle,
8077
8078 _project: function () {
8079
8080 var lng = this._latlng.lng,
8081 lat = this._latlng.lat,
8082 map = this._map,
8083 crs = map.options.crs;
8084
8085 if (crs.distance === Earth.distance) {
8086 var d = Math.PI / 180,
8087 latR = (this._mRadius / Earth.R) / d,
8088 top = map.project([lat + latR, lng]),
8089 bottom = map.project([lat - latR, lng]),
8090 p = top.add(bottom).divideBy(2),
8091 lat2 = map.unproject(p).lat,
8092 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8093 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8094
8095 if (isNaN(lngR) || lngR === 0) {
8096 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8097 }
8098
8099 this._point = p.subtract(map.getPixelOrigin());
8100 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8101 this._radiusY = p.y - top.y;
8102
8103 } else {
8104 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8105
8106 this._point = map.latLngToLayerPoint(this._latlng);
8107 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8108 }
8109
8110 this._updateBounds();
8111 }
8112});
8113
8114// @factory L.circle(latlng: LatLng, options?: Circle options)
8115// Instantiates a circle object given a geographical point, and an options object
8116// which contains the circle radius.
8117// @alternative
8118// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8119// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8120// Do not use in new applications or plugins.
8121function circle(latlng, options, legacyOptions) {
8122 return new Circle(latlng, options, legacyOptions);
8123}
8124
8125/*
8126 * @class Polyline
8127 * @aka L.Polyline
8128 * @inherits Path
8129 *
8130 * A class for drawing polyline overlays on a map. Extends `Path`.
8131 *
8132 * @example
8133 *
8134 * ```js
8135 * // create a red polyline from an array of LatLng points
8136 * var latlngs = [
8137 * [45.51, -122.68],
8138 * [37.77, -122.43],
8139 * [34.04, -118.2]
8140 * ];
8141 *
8142 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8143 *
8144 * // zoom the map to the polyline
8145 * map.fitBounds(polyline.getBounds());
8146 * ```
8147 *
8148 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8149 *
8150 * ```js
8151 * // create a red polyline from an array of arrays of LatLng points
8152 * var latlngs = [
8153 * [[45.51, -122.68],
8154 * [37.77, -122.43],
8155 * [34.04, -118.2]],
8156 * [[40.78, -73.91],
8157 * [41.83, -87.62],
8158 * [32.76, -96.72]]
8159 * ];
8160 * ```
8161 */
8162
8163
8164var Polyline = Path.extend({
8165
8166 // @section
8167 // @aka Polyline options
8168 options: {
8169 // @option smoothFactor: Number = 1.0
8170 // How much to simplify the polyline on each zoom level. More means
8171 // better performance and smoother look, and less means more accurate representation.
8172 smoothFactor: 1.0,
8173
8174 // @option noClip: Boolean = false
8175 // Disable polyline clipping.
8176 noClip: false
8177 },
8178
8179 initialize: function (latlngs, options) {
8180 setOptions(this, options);
8181 this._setLatLngs(latlngs);
8182 },
8183
8184 // @method getLatLngs(): LatLng[]
8185 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8186 getLatLngs: function () {
8187 return this._latlngs;
8188 },
8189
8190 // @method setLatLngs(latlngs: LatLng[]): this
8191 // Replaces all the points in the polyline with the given array of geographical points.
8192 setLatLngs: function (latlngs) {
8193 this._setLatLngs(latlngs);
8194 return this.redraw();
8195 },
8196
8197 // @method isEmpty(): Boolean
8198 // Returns `true` if the Polyline has no LatLngs.
8199 isEmpty: function () {
8200 return !this._latlngs.length;
8201 },
8202
8203 // @method closestLayerPoint(p: Point): Point
8204 // Returns the point closest to `p` on the Polyline.
8205 closestLayerPoint: function (p) {
8206 var minDistance = Infinity,
8207 minPoint = null,
8208 closest = _sqClosestPointOnSegment,
8209 p1, p2;
8210
8211 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8212 var points = this._parts[j];
8213
8214 for (var i = 1, len = points.length; i < len; i++) {
8215 p1 = points[i - 1];
8216 p2 = points[i];
8217
8218 var sqDist = closest(p, p1, p2, true);
8219
8220 if (sqDist < minDistance) {
8221 minDistance = sqDist;
8222 minPoint = closest(p, p1, p2);
8223 }
8224 }
8225 }
8226 if (minPoint) {
8227 minPoint.distance = Math.sqrt(minDistance);
8228 }
8229 return minPoint;
8230 },
8231
8232 // @method getCenter(): LatLng
8233 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8234 getCenter: function () {
8235 // throws error when not yet added to map as this center calculation requires projected coordinates
8236 if (!this._map) {
8237 throw new Error('Must add layer to map before using getCenter()');
8238 }
8239
8240 var i, halfDist, segDist, dist, p1, p2, ratio,
8241 points = this._rings[0],
8242 len = points.length;
8243
8244 if (!len) { return null; }
8245
8246 // polyline centroid algorithm; only uses the first ring if there are multiple
8247
8248 for (i = 0, halfDist = 0; i < len - 1; i++) {
8249 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8250 }
8251
8252 // The line is so small in the current view that all points are on the same pixel.
8253 if (halfDist === 0) {
8254 return this._map.layerPointToLatLng(points[0]);
8255 }
8256
8257 for (i = 0, dist = 0; i < len - 1; i++) {
8258 p1 = points[i];
8259 p2 = points[i + 1];
8260 segDist = p1.distanceTo(p2);
8261 dist += segDist;
8262
8263 if (dist > halfDist) {
8264 ratio = (dist - halfDist) / segDist;
8265 return this._map.layerPointToLatLng([
8266 p2.x - ratio * (p2.x - p1.x),
8267 p2.y - ratio * (p2.y - p1.y)
8268 ]);
8269 }
8270 }
8271 },
8272
8273 // @method getBounds(): LatLngBounds
8274 // Returns the `LatLngBounds` of the path.
8275 getBounds: function () {
8276 return this._bounds;
8277 },
8278
8279 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8280 // Adds a given point to the polyline. By default, adds to the first ring of
8281 // the polyline in case of a multi-polyline, but can be overridden by passing
8282 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8283 addLatLng: function (latlng, latlngs) {
8284 latlngs = latlngs || this._defaultShape();
8285 latlng = toLatLng(latlng);
8286 latlngs.push(latlng);
8287 this._bounds.extend(latlng);
8288 return this.redraw();
8289 },
8290
8291 _setLatLngs: function (latlngs) {
8292 this._bounds = new LatLngBounds();
8293 this._latlngs = this._convertLatLngs(latlngs);
8294 },
8295
8296 _defaultShape: function () {
8297 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8298 },
8299
8300 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8301 _convertLatLngs: function (latlngs) {
8302 var result = [],
8303 flat = isFlat(latlngs);
8304
8305 for (var i = 0, len = latlngs.length; i < len; i++) {
8306 if (flat) {
8307 result[i] = toLatLng(latlngs[i]);
8308 this._bounds.extend(result[i]);
8309 } else {
8310 result[i] = this._convertLatLngs(latlngs[i]);
8311 }
8312 }
8313
8314 return result;
8315 },
8316
8317 _project: function () {
8318 var pxBounds = new Bounds();
8319 this._rings = [];
8320 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8321
8322 if (this._bounds.isValid() && pxBounds.isValid()) {
8323 this._rawPxBounds = pxBounds;
8324 this._updateBounds();
8325 }
8326 },
8327
8328 _updateBounds: function () {
8329 var w = this._clickTolerance(),
8330 p = new Point(w, w);
8331 this._pxBounds = new Bounds([
8332 this._rawPxBounds.min.subtract(p),
8333 this._rawPxBounds.max.add(p)
8334 ]);
8335 },
8336
8337 // recursively turns latlngs into a set of rings with projected coordinates
8338 _projectLatlngs: function (latlngs, result, projectedBounds) {
8339 var flat = latlngs[0] instanceof LatLng,
8340 len = latlngs.length,
8341 i, ring;
8342
8343 if (flat) {
8344 ring = [];
8345 for (i = 0; i < len; i++) {
8346 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8347 projectedBounds.extend(ring[i]);
8348 }
8349 result.push(ring);
8350 } else {
8351 for (i = 0; i < len; i++) {
8352 this._projectLatlngs(latlngs[i], result, projectedBounds);
8353 }
8354 }
8355 },
8356
8357 // clip polyline by renderer bounds so that we have less to render for performance
8358 _clipPoints: function () {
8359 var bounds = this._renderer._bounds;
8360
8361 this._parts = [];
8362 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8363 return;
8364 }
8365
8366 if (this.options.noClip) {
8367 this._parts = this._rings;
8368 return;
8369 }
8370
8371 var parts = this._parts,
8372 i, j, k, len, len2, segment, points;
8373
8374 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8375 points = this._rings[i];
8376
8377 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8378 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8379
8380 if (!segment) { continue; }
8381
8382 parts[k] = parts[k] || [];
8383 parts[k].push(segment[0]);
8384
8385 // if segment goes out of screen, or it's the last one, it's the end of the line part
8386 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8387 parts[k].push(segment[1]);
8388 k++;
8389 }
8390 }
8391 }
8392 },
8393
8394 // simplify each clipped part of the polyline for performance
8395 _simplifyPoints: function () {
8396 var parts = this._parts,
8397 tolerance = this.options.smoothFactor;
8398
8399 for (var i = 0, len = parts.length; i < len; i++) {
8400 parts[i] = simplify(parts[i], tolerance);
8401 }
8402 },
8403
8404 _update: function () {
8405 if (!this._map) { return; }
8406
8407 this._clipPoints();
8408 this._simplifyPoints();
8409 this._updatePath();
8410 },
8411
8412 _updatePath: function () {
8413 this._renderer._updatePoly(this);
8414 },
8415
8416 // Needed by the `Canvas` renderer for interactivity
8417 _containsPoint: function (p, closed) {
8418 var i, j, k, len, len2, part,
8419 w = this._clickTolerance();
8420
8421 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8422
8423 // hit detection for polylines
8424 for (i = 0, len = this._parts.length; i < len; i++) {
8425 part = this._parts[i];
8426
8427 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8428 if (!closed && (j === 0)) { continue; }
8429
8430 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8431 return true;
8432 }
8433 }
8434 }
8435 return false;
8436 }
8437});
8438
8439// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8440// Instantiates a polyline object given an array of geographical points and
8441// optionally an options object. You can create a `Polyline` object with
8442// multiple separate lines (`MultiPolyline`) by passing an array of arrays
8443// of geographic points.
8444function polyline(latlngs, options) {
8445 return new Polyline(latlngs, options);
8446}
8447
8448// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8449Polyline._flat = _flat;
8450
8451/*
8452 * @class Polygon
8453 * @aka L.Polygon
8454 * @inherits Polyline
8455 *
8456 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8457 *
8458 * 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.
8459 *
8460 *
8461 * @example
8462 *
8463 * ```js
8464 * // create a red polygon from an array of LatLng points
8465 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8466 *
8467 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8468 *
8469 * // zoom the map to the polygon
8470 * map.fitBounds(polygon.getBounds());
8471 * ```
8472 *
8473 * 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:
8474 *
8475 * ```js
8476 * var latlngs = [
8477 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8478 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8479 * ];
8480 * ```
8481 *
8482 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8483 *
8484 * ```js
8485 * var latlngs = [
8486 * [ // first polygon
8487 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8488 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8489 * ],
8490 * [ // second polygon
8491 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8492 * ]
8493 * ];
8494 * ```
8495 */
8496
8497var Polygon = Polyline.extend({
8498
8499 options: {
8500 fill: true
8501 },
8502
8503 isEmpty: function () {
8504 return !this._latlngs.length || !this._latlngs[0].length;
8505 },
8506
8507 getCenter: function () {
8508 // throws error when not yet added to map as this center calculation requires projected coordinates
8509 if (!this._map) {
8510 throw new Error('Must add layer to map before using getCenter()');
8511 }
8512
8513 var i, j, p1, p2, f, area, x, y, center,
8514 points = this._rings[0],
8515 len = points.length;
8516
8517 if (!len) { return null; }
8518
8519 // polygon centroid algorithm; only uses the first ring if there are multiple
8520
8521 area = x = y = 0;
8522
8523 for (i = 0, j = len - 1; i < len; j = i++) {
8524 p1 = points[i];
8525 p2 = points[j];
8526
8527 f = p1.y * p2.x - p2.y * p1.x;
8528 x += (p1.x + p2.x) * f;
8529 y += (p1.y + p2.y) * f;
8530 area += f * 3;
8531 }
8532
8533 if (area === 0) {
8534 // Polygon is so small that all points are on same pixel.
8535 center = points[0];
8536 } else {
8537 center = [x / area, y / area];
8538 }
8539 return this._map.layerPointToLatLng(center);
8540 },
8541
8542 _convertLatLngs: function (latlngs) {
8543 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8544 len = result.length;
8545
8546 // remove last point if it equals first one
8547 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8548 result.pop();
8549 }
8550 return result;
8551 },
8552
8553 _setLatLngs: function (latlngs) {
8554 Polyline.prototype._setLatLngs.call(this, latlngs);
8555 if (isFlat(this._latlngs)) {
8556 this._latlngs = [this._latlngs];
8557 }
8558 },
8559
8560 _defaultShape: function () {
8561 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8562 },
8563
8564 _clipPoints: function () {
8565 // polygons need a different clipping algorithm so we redefine that
8566
8567 var bounds = this._renderer._bounds,
8568 w = this.options.weight,
8569 p = new Point(w, w);
8570
8571 // increase clip padding by stroke width to avoid stroke on clip edges
8572 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8573
8574 this._parts = [];
8575 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8576 return;
8577 }
8578
8579 if (this.options.noClip) {
8580 this._parts = this._rings;
8581 return;
8582 }
8583
8584 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8585 clipped = clipPolygon(this._rings[i], bounds, true);
8586 if (clipped.length) {
8587 this._parts.push(clipped);
8588 }
8589 }
8590 },
8591
8592 _updatePath: function () {
8593 this._renderer._updatePoly(this, true);
8594 },
8595
8596 // Needed by the `Canvas` renderer for interactivity
8597 _containsPoint: function (p) {
8598 var inside = false,
8599 part, p1, p2, i, j, k, len, len2;
8600
8601 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8602
8603 // ray casting algorithm for detecting if point is in polygon
8604 for (i = 0, len = this._parts.length; i < len; i++) {
8605 part = this._parts[i];
8606
8607 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8608 p1 = part[j];
8609 p2 = part[k];
8610
8611 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)) {
8612 inside = !inside;
8613 }
8614 }
8615 }
8616
8617 // also check if it's on polygon stroke
8618 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8619 }
8620
8621});
8622
8623
8624// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8625function polygon(latlngs, options) {
8626 return new Polygon(latlngs, options);
8627}
8628
8629/*
8630 * @class GeoJSON
8631 * @aka L.GeoJSON
8632 * @inherits FeatureGroup
8633 *
8634 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8635 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8636 *
8637 * @example
8638 *
8639 * ```js
8640 * L.geoJSON(data, {
8641 * style: function (feature) {
8642 * return {color: feature.properties.color};
8643 * }
8644 * }).bindPopup(function (layer) {
8645 * return layer.feature.properties.description;
8646 * }).addTo(map);
8647 * ```
8648 */
8649
8650var GeoJSON = FeatureGroup.extend({
8651
8652 /* @section
8653 * @aka GeoJSON options
8654 *
8655 * @option pointToLayer: Function = *
8656 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8657 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8658 * The default is to spawn a default `Marker`:
8659 * ```js
8660 * function(geoJsonPoint, latlng) {
8661 * return L.marker(latlng);
8662 * }
8663 * ```
8664 *
8665 * @option style: Function = *
8666 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8667 * called internally when data is added.
8668 * The default value is to not override any defaults:
8669 * ```js
8670 * function (geoJsonFeature) {
8671 * return {}
8672 * }
8673 * ```
8674 *
8675 * @option onEachFeature: Function = *
8676 * A `Function` that will be called once for each created `Feature`, after it has
8677 * been created and styled. Useful for attaching events and popups to features.
8678 * The default is to do nothing with the newly created layers:
8679 * ```js
8680 * function (feature, layer) {}
8681 * ```
8682 *
8683 * @option filter: Function = *
8684 * A `Function` that will be used to decide whether to include a feature or not.
8685 * The default is to include all features:
8686 * ```js
8687 * function (geoJsonFeature) {
8688 * return true;
8689 * }
8690 * ```
8691 * Note: dynamically changing the `filter` option will have effect only on newly
8692 * added data. It will _not_ re-evaluate already included features.
8693 *
8694 * @option coordsToLatLng: Function = *
8695 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8696 * The default is the `coordsToLatLng` static method.
8697 *
8698 * @option markersInheritOptions: Boolean = false
8699 * Whether default Markers for "Point" type Features inherit from group options.
8700 */
8701
8702 initialize: function (geojson, options) {
8703 setOptions(this, options);
8704
8705 this._layers = {};
8706
8707 if (geojson) {
8708 this.addData(geojson);
8709 }
8710 },
8711
8712 // @method addData( <GeoJSON> data ): this
8713 // Adds a GeoJSON object to the layer.
8714 addData: function (geojson) {
8715 var features = isArray(geojson) ? geojson : geojson.features,
8716 i, len, feature;
8717
8718 if (features) {
8719 for (i = 0, len = features.length; i < len; i++) {
8720 // only add this if geometry or geometries are set and not null
8721 feature = features[i];
8722 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8723 this.addData(feature);
8724 }
8725 }
8726 return this;
8727 }
8728
8729 var options = this.options;
8730
8731 if (options.filter && !options.filter(geojson)) { return this; }
8732
8733 var layer = geometryToLayer(geojson, options);
8734 if (!layer) {
8735 return this;
8736 }
8737 layer.feature = asFeature(geojson);
8738
8739 layer.defaultOptions = layer.options;
8740 this.resetStyle(layer);
8741
8742 if (options.onEachFeature) {
8743 options.onEachFeature(geojson, layer);
8744 }
8745
8746 return this.addLayer(layer);
8747 },
8748
8749 // @method resetStyle( <Path> layer? ): this
8750 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8751 // If `layer` is omitted, the style of all features in the current layer is reset.
8752 resetStyle: function (layer) {
8753 if (layer === undefined) {
8754 return this.eachLayer(this.resetStyle, this);
8755 }
8756 // reset any custom styles
8757 layer.options = extend({}, layer.defaultOptions);
8758 this._setLayerStyle(layer, this.options.style);
8759 return this;
8760 },
8761
8762 // @method setStyle( <Function> style ): this
8763 // Changes styles of GeoJSON vector layers with the given style function.
8764 setStyle: function (style) {
8765 return this.eachLayer(function (layer) {
8766 this._setLayerStyle(layer, style);
8767 }, this);
8768 },
8769
8770 _setLayerStyle: function (layer, style) {
8771 if (layer.setStyle) {
8772 if (typeof style === 'function') {
8773 style = style(layer.feature);
8774 }
8775 layer.setStyle(style);
8776 }
8777 }
8778});
8779
8780// @section
8781// There are several static functions which can be called without instantiating L.GeoJSON:
8782
8783// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8784// Creates a `Layer` from a given GeoJSON feature. Can use a custom
8785// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8786// functions if provided as options.
8787function geometryToLayer(geojson, options) {
8788
8789 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8790 coords = geometry ? geometry.coordinates : null,
8791 layers = [],
8792 pointToLayer = options && options.pointToLayer,
8793 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8794 latlng, latlngs, i, len;
8795
8796 if (!coords && !geometry) {
8797 return null;
8798 }
8799
8800 switch (geometry.type) {
8801 case 'Point':
8802 latlng = _coordsToLatLng(coords);
8803 return _pointToLayer(pointToLayer, geojson, latlng, options);
8804
8805 case 'MultiPoint':
8806 for (i = 0, len = coords.length; i < len; i++) {
8807 latlng = _coordsToLatLng(coords[i]);
8808 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
8809 }
8810 return new FeatureGroup(layers);
8811
8812 case 'LineString':
8813 case 'MultiLineString':
8814 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8815 return new Polyline(latlngs, options);
8816
8817 case 'Polygon':
8818 case 'MultiPolygon':
8819 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8820 return new Polygon(latlngs, options);
8821
8822 case 'GeometryCollection':
8823 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8824 var layer = geometryToLayer({
8825 geometry: geometry.geometries[i],
8826 type: 'Feature',
8827 properties: geojson.properties
8828 }, options);
8829
8830 if (layer) {
8831 layers.push(layer);
8832 }
8833 }
8834 return new FeatureGroup(layers);
8835
8836 default:
8837 throw new Error('Invalid GeoJSON object.');
8838 }
8839}
8840
8841function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
8842 return pointToLayerFn ?
8843 pointToLayerFn(geojson, latlng) :
8844 new Marker(latlng, options && options.markersInheritOptions && options);
8845}
8846
8847// @function coordsToLatLng(coords: Array): LatLng
8848// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8849// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8850function coordsToLatLng(coords) {
8851 return new LatLng(coords[1], coords[0], coords[2]);
8852}
8853
8854// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8855// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8856// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8857// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8858function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8859 var latlngs = [];
8860
8861 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8862 latlng = levelsDeep ?
8863 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8864 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8865
8866 latlngs.push(latlng);
8867 }
8868
8869 return latlngs;
8870}
8871
8872// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8873// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8874function latLngToCoords(latlng, precision) {
8875 precision = typeof precision === 'number' ? precision : 6;
8876 return latlng.alt !== undefined ?
8877 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8878 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8879}
8880
8881// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8882// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8883// `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.
8884function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8885 var coords = [];
8886
8887 for (var i = 0, len = latlngs.length; i < len; i++) {
8888 coords.push(levelsDeep ?
8889 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8890 latLngToCoords(latlngs[i], precision));
8891 }
8892
8893 if (!levelsDeep && closed) {
8894 coords.push(coords[0]);
8895 }
8896
8897 return coords;
8898}
8899
8900function getFeature(layer, newGeometry) {
8901 return layer.feature ?
8902 extend({}, layer.feature, {geometry: newGeometry}) :
8903 asFeature(newGeometry);
8904}
8905
8906// @function asFeature(geojson: Object): Object
8907// Normalize GeoJSON geometries/features into GeoJSON features.
8908function asFeature(geojson) {
8909 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8910 return geojson;
8911 }
8912
8913 return {
8914 type: 'Feature',
8915 properties: {},
8916 geometry: geojson
8917 };
8918}
8919
8920var PointToGeoJSON = {
8921 toGeoJSON: function (precision) {
8922 return getFeature(this, {
8923 type: 'Point',
8924 coordinates: latLngToCoords(this.getLatLng(), precision)
8925 });
8926 }
8927};
8928
8929// @namespace Marker
8930// @section Other methods
8931// @method toGeoJSON(precision?: Number): Object
8932// `precision` is the number of decimal places for coordinates.
8933// The default value is 6 places.
8934// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8935Marker.include(PointToGeoJSON);
8936
8937// @namespace CircleMarker
8938// @method toGeoJSON(precision?: Number): Object
8939// `precision` is the number of decimal places for coordinates.
8940// The default value is 6 places.
8941// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8942Circle.include(PointToGeoJSON);
8943CircleMarker.include(PointToGeoJSON);
8944
8945
8946// @namespace Polyline
8947// @method toGeoJSON(precision?: Number): Object
8948// `precision` is the number of decimal places for coordinates.
8949// The default value is 6 places.
8950// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8951Polyline.include({
8952 toGeoJSON: function (precision) {
8953 var multi = !isFlat(this._latlngs);
8954
8955 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8956
8957 return getFeature(this, {
8958 type: (multi ? 'Multi' : '') + 'LineString',
8959 coordinates: coords
8960 });
8961 }
8962});
8963
8964// @namespace Polygon
8965// @method toGeoJSON(precision?: Number): Object
8966// `precision` is the number of decimal places for coordinates.
8967// The default value is 6 places.
8968// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8969Polygon.include({
8970 toGeoJSON: function (precision) {
8971 var holes = !isFlat(this._latlngs),
8972 multi = holes && !isFlat(this._latlngs[0]);
8973
8974 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8975
8976 if (!holes) {
8977 coords = [coords];
8978 }
8979
8980 return getFeature(this, {
8981 type: (multi ? 'Multi' : '') + 'Polygon',
8982 coordinates: coords
8983 });
8984 }
8985});
8986
8987
8988// @namespace LayerGroup
8989LayerGroup.include({
8990 toMultiPoint: function (precision) {
8991 var coords = [];
8992
8993 this.eachLayer(function (layer) {
8994 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8995 });
8996
8997 return getFeature(this, {
8998 type: 'MultiPoint',
8999 coordinates: coords
9000 });
9001 },
9002
9003 // @method toGeoJSON(precision?: Number): Object
9004 // `precision` is the number of decimal places for coordinates.
9005 // The default value is 6 places.
9006 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9007 toGeoJSON: function (precision) {
9008
9009 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9010
9011 if (type === 'MultiPoint') {
9012 return this.toMultiPoint(precision);
9013 }
9014
9015 var isGeometryCollection = type === 'GeometryCollection',
9016 jsons = [];
9017
9018 this.eachLayer(function (layer) {
9019 if (layer.toGeoJSON) {
9020 var json = layer.toGeoJSON(precision);
9021 if (isGeometryCollection) {
9022 jsons.push(json.geometry);
9023 } else {
9024 var feature = asFeature(json);
9025 // Squash nested feature collections
9026 if (feature.type === 'FeatureCollection') {
9027 jsons.push.apply(jsons, feature.features);
9028 } else {
9029 jsons.push(feature);
9030 }
9031 }
9032 }
9033 });
9034
9035 if (isGeometryCollection) {
9036 return getFeature(this, {
9037 geometries: jsons,
9038 type: 'GeometryCollection'
9039 });
9040 }
9041
9042 return {
9043 type: 'FeatureCollection',
9044 features: jsons
9045 };
9046 }
9047});
9048
9049// @namespace GeoJSON
9050// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9051// Creates a GeoJSON layer. Optionally accepts an object in
9052// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9053// (you can alternatively add it later with `addData` method) and an `options` object.
9054function geoJSON(geojson, options) {
9055 return new GeoJSON(geojson, options);
9056}
9057
9058// Backward compatibility.
9059var geoJson = geoJSON;
9060
9061/*
9062 * @class ImageOverlay
9063 * @aka L.ImageOverlay
9064 * @inherits Interactive layer
9065 *
9066 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9067 *
9068 * @example
9069 *
9070 * ```js
9071 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9072 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9073 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9074 * ```
9075 */
9076
9077var ImageOverlay = Layer.extend({
9078
9079 // @section
9080 // @aka ImageOverlay options
9081 options: {
9082 // @option opacity: Number = 1.0
9083 // The opacity of the image overlay.
9084 opacity: 1,
9085
9086 // @option alt: String = ''
9087 // Text for the `alt` attribute of the image (useful for accessibility).
9088 alt: '',
9089
9090 // @option interactive: Boolean = false
9091 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9092 interactive: false,
9093
9094 // @option crossOrigin: Boolean|String = false
9095 // Whether the crossOrigin attribute will be added to the image.
9096 // 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.
9097 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9098 crossOrigin: false,
9099
9100 // @option errorOverlayUrl: String = ''
9101 // URL to the overlay image to show in place of the overlay that failed to load.
9102 errorOverlayUrl: '',
9103
9104 // @option zIndex: Number = 1
9105 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9106 zIndex: 1,
9107
9108 // @option className: String = ''
9109 // A custom class name to assign to the image. Empty by default.
9110 className: ''
9111 },
9112
9113 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9114 this._url = url;
9115 this._bounds = toLatLngBounds(bounds);
9116
9117 setOptions(this, options);
9118 },
9119
9120 onAdd: function () {
9121 if (!this._image) {
9122 this._initImage();
9123
9124 if (this.options.opacity < 1) {
9125 this._updateOpacity();
9126 }
9127 }
9128
9129 if (this.options.interactive) {
9130 addClass(this._image, 'leaflet-interactive');
9131 this.addInteractiveTarget(this._image);
9132 }
9133
9134 this.getPane().appendChild(this._image);
9135 this._reset();
9136 },
9137
9138 onRemove: function () {
9139 remove(this._image);
9140 if (this.options.interactive) {
9141 this.removeInteractiveTarget(this._image);
9142 }
9143 },
9144
9145 // @method setOpacity(opacity: Number): this
9146 // Sets the opacity of the overlay.
9147 setOpacity: function (opacity) {
9148 this.options.opacity = opacity;
9149
9150 if (this._image) {
9151 this._updateOpacity();
9152 }
9153 return this;
9154 },
9155
9156 setStyle: function (styleOpts) {
9157 if (styleOpts.opacity) {
9158 this.setOpacity(styleOpts.opacity);
9159 }
9160 return this;
9161 },
9162
9163 // @method bringToFront(): this
9164 // Brings the layer to the top of all overlays.
9165 bringToFront: function () {
9166 if (this._map) {
9167 toFront(this._image);
9168 }
9169 return this;
9170 },
9171
9172 // @method bringToBack(): this
9173 // Brings the layer to the bottom of all overlays.
9174 bringToBack: function () {
9175 if (this._map) {
9176 toBack(this._image);
9177 }
9178 return this;
9179 },
9180
9181 // @method setUrl(url: String): this
9182 // Changes the URL of the image.
9183 setUrl: function (url) {
9184 this._url = url;
9185
9186 if (this._image) {
9187 this._image.src = url;
9188 }
9189 return this;
9190 },
9191
9192 // @method setBounds(bounds: LatLngBounds): this
9193 // Update the bounds that this ImageOverlay covers
9194 setBounds: function (bounds) {
9195 this._bounds = toLatLngBounds(bounds);
9196
9197 if (this._map) {
9198 this._reset();
9199 }
9200 return this;
9201 },
9202
9203 getEvents: function () {
9204 var events = {
9205 zoom: this._reset,
9206 viewreset: this._reset
9207 };
9208
9209 if (this._zoomAnimated) {
9210 events.zoomanim = this._animateZoom;
9211 }
9212
9213 return events;
9214 },
9215
9216 // @method setZIndex(value: Number): this
9217 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9218 setZIndex: function (value) {
9219 this.options.zIndex = value;
9220 this._updateZIndex();
9221 return this;
9222 },
9223
9224 // @method getBounds(): LatLngBounds
9225 // Get the bounds that this ImageOverlay covers
9226 getBounds: function () {
9227 return this._bounds;
9228 },
9229
9230 // @method getElement(): HTMLElement
9231 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9232 // used by this overlay.
9233 getElement: function () {
9234 return this._image;
9235 },
9236
9237 _initImage: function () {
9238 var wasElementSupplied = this._url.tagName === 'IMG';
9239 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9240
9241 addClass(img, 'leaflet-image-layer');
9242 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9243 if (this.options.className) { addClass(img, this.options.className); }
9244
9245 img.onselectstart = falseFn;
9246 img.onmousemove = falseFn;
9247
9248 // @event load: Event
9249 // Fired when the ImageOverlay layer has loaded its image
9250 img.onload = bind(this.fire, this, 'load');
9251 img.onerror = bind(this._overlayOnError, this, 'error');
9252
9253 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9254 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9255 }
9256
9257 if (this.options.zIndex) {
9258 this._updateZIndex();
9259 }
9260
9261 if (wasElementSupplied) {
9262 this._url = img.src;
9263 return;
9264 }
9265
9266 img.src = this._url;
9267 img.alt = this.options.alt;
9268 },
9269
9270 _animateZoom: function (e) {
9271 var scale = this._map.getZoomScale(e.zoom),
9272 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9273
9274 setTransform(this._image, offset, scale);
9275 },
9276
9277 _reset: function () {
9278 var image = this._image,
9279 bounds = new Bounds(
9280 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9281 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9282 size = bounds.getSize();
9283
9284 setPosition(image, bounds.min);
9285
9286 image.style.width = size.x + 'px';
9287 image.style.height = size.y + 'px';
9288 },
9289
9290 _updateOpacity: function () {
9291 setOpacity(this._image, this.options.opacity);
9292 },
9293
9294 _updateZIndex: function () {
9295 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9296 this._image.style.zIndex = this.options.zIndex;
9297 }
9298 },
9299
9300 _overlayOnError: function () {
9301 // @event error: Event
9302 // Fired when the ImageOverlay layer fails to load its image
9303 this.fire('error');
9304
9305 var errorUrl = this.options.errorOverlayUrl;
9306 if (errorUrl && this._url !== errorUrl) {
9307 this._url = errorUrl;
9308 this._image.src = errorUrl;
9309 }
9310 }
9311});
9312
9313// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9314// Instantiates an image overlay object given the URL of the image and the
9315// geographical bounds it is tied to.
9316var imageOverlay = function (url, bounds, options) {
9317 return new ImageOverlay(url, bounds, options);
9318};
9319
9320/*
9321 * @class VideoOverlay
9322 * @aka L.VideoOverlay
9323 * @inherits ImageOverlay
9324 *
9325 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9326 *
9327 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9328 * HTML5 element.
9329 *
9330 * @example
9331 *
9332 * ```js
9333 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9334 * videoBounds = [[ 32, -130], [ 13, -100]];
9335 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9336 * ```
9337 */
9338
9339var VideoOverlay = ImageOverlay.extend({
9340
9341 // @section
9342 // @aka VideoOverlay options
9343 options: {
9344 // @option autoplay: Boolean = true
9345 // Whether the video starts playing automatically when loaded.
9346 autoplay: true,
9347
9348 // @option loop: Boolean = true
9349 // Whether the video will loop back to the beginning when played.
9350 loop: true,
9351
9352 // @option keepAspectRatio: Boolean = true
9353 // Whether the video will save aspect ratio after the projection.
9354 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9355 keepAspectRatio: true
9356 },
9357
9358 _initImage: function () {
9359 var wasElementSupplied = this._url.tagName === 'VIDEO';
9360 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9361
9362 addClass(vid, 'leaflet-image-layer');
9363 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9364 if (this.options.className) { addClass(vid, this.options.className); }
9365
9366 vid.onselectstart = falseFn;
9367 vid.onmousemove = falseFn;
9368
9369 // @event load: Event
9370 // Fired when the video has finished loading the first frame
9371 vid.onloadeddata = bind(this.fire, this, 'load');
9372
9373 if (wasElementSupplied) {
9374 var sourceElements = vid.getElementsByTagName('source');
9375 var sources = [];
9376 for (var j = 0; j < sourceElements.length; j++) {
9377 sources.push(sourceElements[j].src);
9378 }
9379
9380 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9381 return;
9382 }
9383
9384 if (!isArray(this._url)) { this._url = [this._url]; }
9385
9386 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
9387 vid.autoplay = !!this.options.autoplay;
9388 vid.loop = !!this.options.loop;
9389 for (var i = 0; i < this._url.length; i++) {
9390 var source = create$1('source');
9391 source.src = this._url[i];
9392 vid.appendChild(source);
9393 }
9394 }
9395
9396 // @method getElement(): HTMLVideoElement
9397 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9398 // used by this overlay.
9399});
9400
9401
9402// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9403// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9404// geographical bounds it is tied to.
9405
9406function videoOverlay(video, bounds, options) {
9407 return new VideoOverlay(video, bounds, options);
9408}
9409
9410/*
9411 * @class SVGOverlay
9412 * @aka L.SVGOverlay
9413 * @inherits ImageOverlay
9414 *
9415 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9416 *
9417 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9418 *
9419 * @example
9420 *
9421 * ```js
9422 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9423 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9424 * svgElement.setAttribute('viewBox', "0 0 200 200");
9425 * 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"/>';
9426 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9427 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9428 * ```
9429 */
9430
9431var SVGOverlay = ImageOverlay.extend({
9432 _initImage: function () {
9433 var el = this._image = this._url;
9434
9435 addClass(el, 'leaflet-image-layer');
9436 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9437 if (this.options.className) { addClass(el, this.options.className); }
9438
9439 el.onselectstart = falseFn;
9440 el.onmousemove = falseFn;
9441 }
9442
9443 // @method getElement(): SVGElement
9444 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9445 // used by this overlay.
9446});
9447
9448
9449// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9450// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9451// A viewBox attribute is required on the SVG element to zoom in and out properly.
9452
9453function svgOverlay(el, bounds, options) {
9454 return new SVGOverlay(el, bounds, options);
9455}
9456
9457/*
9458 * @class DivOverlay
9459 * @inherits Layer
9460 * @aka L.DivOverlay
9461 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9462 */
9463
9464// @namespace DivOverlay
9465var DivOverlay = Layer.extend({
9466
9467 // @section
9468 // @aka DivOverlay options
9469 options: {
9470 // @option offset: Point = Point(0, 7)
9471 // The offset of the popup position. Useful to control the anchor
9472 // of the popup when opening it on some overlays.
9473 offset: [0, 7],
9474
9475 // @option className: String = ''
9476 // A custom CSS class name to assign to the popup.
9477 className: '',
9478
9479 // @option pane: String = 'popupPane'
9480 // `Map pane` where the popup will be added.
9481 pane: 'popupPane'
9482 },
9483
9484 initialize: function (options, source) {
9485 setOptions(this, options);
9486
9487 this._source = source;
9488 },
9489
9490 onAdd: function (map) {
9491 this._zoomAnimated = map._zoomAnimated;
9492
9493 if (!this._container) {
9494 this._initLayout();
9495 }
9496
9497 if (map._fadeAnimated) {
9498 setOpacity(this._container, 0);
9499 }
9500
9501 clearTimeout(this._removeTimeout);
9502 this.getPane().appendChild(this._container);
9503 this.update();
9504
9505 if (map._fadeAnimated) {
9506 setOpacity(this._container, 1);
9507 }
9508
9509 this.bringToFront();
9510 },
9511
9512 onRemove: function (map) {
9513 if (map._fadeAnimated) {
9514 setOpacity(this._container, 0);
9515 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9516 } else {
9517 remove(this._container);
9518 }
9519 },
9520
9521 // @namespace Popup
9522 // @method getLatLng: LatLng
9523 // Returns the geographical point of popup.
9524 getLatLng: function () {
9525 return this._latlng;
9526 },
9527
9528 // @method setLatLng(latlng: LatLng): this
9529 // Sets the geographical point where the popup will open.
9530 setLatLng: function (latlng) {
9531 this._latlng = toLatLng(latlng);
9532 if (this._map) {
9533 this._updatePosition();
9534 this._adjustPan();
9535 }
9536 return this;
9537 },
9538
9539 // @method getContent: String|HTMLElement
9540 // Returns the content of the popup.
9541 getContent: function () {
9542 return this._content;
9543 },
9544
9545 // @method setContent(htmlContent: String|HTMLElement|Function): this
9546 // 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.
9547 setContent: function (content) {
9548 this._content = content;
9549 this.update();
9550 return this;
9551 },
9552
9553 // @method getElement: String|HTMLElement
9554 // Alias for [getContent()](#popup-getcontent)
9555 getElement: function () {
9556 return this._container;
9557 },
9558
9559 // @method update: null
9560 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9561 update: function () {
9562 if (!this._map) { return; }
9563
9564 this._container.style.visibility = 'hidden';
9565
9566 this._updateContent();
9567 this._updateLayout();
9568 this._updatePosition();
9569
9570 this._container.style.visibility = '';
9571
9572 this._adjustPan();
9573 },
9574
9575 getEvents: function () {
9576 var events = {
9577 zoom: this._updatePosition,
9578 viewreset: this._updatePosition
9579 };
9580
9581 if (this._zoomAnimated) {
9582 events.zoomanim = this._animateZoom;
9583 }
9584 return events;
9585 },
9586
9587 // @method isOpen: Boolean
9588 // Returns `true` when the popup is visible on the map.
9589 isOpen: function () {
9590 return !!this._map && this._map.hasLayer(this);
9591 },
9592
9593 // @method bringToFront: this
9594 // Brings this popup in front of other popups (in the same map pane).
9595 bringToFront: function () {
9596 if (this._map) {
9597 toFront(this._container);
9598 }
9599 return this;
9600 },
9601
9602 // @method bringToBack: this
9603 // Brings this popup to the back of other popups (in the same map pane).
9604 bringToBack: function () {
9605 if (this._map) {
9606 toBack(this._container);
9607 }
9608 return this;
9609 },
9610
9611 _prepareOpen: function (parent, layer, latlng) {
9612 if (!(layer instanceof Layer)) {
9613 latlng = layer;
9614 layer = parent;
9615 }
9616
9617 if (layer instanceof FeatureGroup) {
9618 for (var id in parent._layers) {
9619 layer = parent._layers[id];
9620 break;
9621 }
9622 }
9623
9624 if (!latlng) {
9625 if (layer.getCenter) {
9626 latlng = layer.getCenter();
9627 } else if (layer.getLatLng) {
9628 latlng = layer.getLatLng();
9629 } else {
9630 throw new Error('Unable to get source layer LatLng.');
9631 }
9632 }
9633
9634 // set overlay source to this layer
9635 this._source = layer;
9636
9637 // update the overlay (content, layout, ect...)
9638 this.update();
9639
9640 return latlng;
9641 },
9642
9643 _updateContent: function () {
9644 if (!this._content) { return; }
9645
9646 var node = this._contentNode;
9647 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9648
9649 if (typeof content === 'string') {
9650 node.innerHTML = content;
9651 } else {
9652 while (node.hasChildNodes()) {
9653 node.removeChild(node.firstChild);
9654 }
9655 node.appendChild(content);
9656 }
9657 this.fire('contentupdate');
9658 },
9659
9660 _updatePosition: function () {
9661 if (!this._map) { return; }
9662
9663 var pos = this._map.latLngToLayerPoint(this._latlng),
9664 offset = toPoint(this.options.offset),
9665 anchor = this._getAnchor();
9666
9667 if (this._zoomAnimated) {
9668 setPosition(this._container, pos.add(anchor));
9669 } else {
9670 offset = offset.add(pos).add(anchor);
9671 }
9672
9673 var bottom = this._containerBottom = -offset.y,
9674 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9675
9676 // bottom position the popup in case the height of the popup changes (images loading etc)
9677 this._container.style.bottom = bottom + 'px';
9678 this._container.style.left = left + 'px';
9679 },
9680
9681 _getAnchor: function () {
9682 return [0, 0];
9683 }
9684
9685});
9686
9687/*
9688 * @class Popup
9689 * @inherits DivOverlay
9690 * @aka L.Popup
9691 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9692 * open popups while making sure that only one popup is open at one time
9693 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9694 *
9695 * @example
9696 *
9697 * If you want to just bind a popup to marker click and then open it, it's really easy:
9698 *
9699 * ```js
9700 * marker.bindPopup(popupContent).openPopup();
9701 * ```
9702 * Path overlays like polylines also have a `bindPopup` method.
9703 * Here's a more complicated way to open a popup on a map:
9704 *
9705 * ```js
9706 * var popup = L.popup()
9707 * .setLatLng(latlng)
9708 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9709 * .openOn(map);
9710 * ```
9711 */
9712
9713
9714// @namespace Popup
9715var Popup = DivOverlay.extend({
9716
9717 // @section
9718 // @aka Popup options
9719 options: {
9720 // @option maxWidth: Number = 300
9721 // Max width of the popup, in pixels.
9722 maxWidth: 300,
9723
9724 // @option minWidth: Number = 50
9725 // Min width of the popup, in pixels.
9726 minWidth: 50,
9727
9728 // @option maxHeight: Number = null
9729 // If set, creates a scrollable container of the given height
9730 // inside a popup if its content exceeds it.
9731 maxHeight: null,
9732
9733 // @option autoPan: Boolean = true
9734 // Set it to `false` if you don't want the map to do panning animation
9735 // to fit the opened popup.
9736 autoPan: true,
9737
9738 // @option autoPanPaddingTopLeft: Point = null
9739 // The margin between the popup and the top left corner of the map
9740 // view after autopanning was performed.
9741 autoPanPaddingTopLeft: null,
9742
9743 // @option autoPanPaddingBottomRight: Point = null
9744 // The margin between the popup and the bottom right corner of the map
9745 // view after autopanning was performed.
9746 autoPanPaddingBottomRight: null,
9747
9748 // @option autoPanPadding: Point = Point(5, 5)
9749 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9750 autoPanPadding: [5, 5],
9751
9752 // @option keepInView: Boolean = false
9753 // Set it to `true` if you want to prevent users from panning the popup
9754 // off of the screen while it is open.
9755 keepInView: false,
9756
9757 // @option closeButton: Boolean = true
9758 // Controls the presence of a close button in the popup.
9759 closeButton: true,
9760
9761 // @option autoClose: Boolean = true
9762 // Set it to `false` if you want to override the default behavior of
9763 // the popup closing when another popup is opened.
9764 autoClose: true,
9765
9766 // @option closeOnEscapeKey: Boolean = true
9767 // Set it to `false` if you want to override the default behavior of
9768 // the ESC key for closing of the popup.
9769 closeOnEscapeKey: true,
9770
9771 // @option closeOnClick: Boolean = *
9772 // Set it if you want to override the default behavior of the popup closing when user clicks
9773 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9774
9775 // @option className: String = ''
9776 // A custom CSS class name to assign to the popup.
9777 className: ''
9778 },
9779
9780 // @namespace Popup
9781 // @method openOn(map: Map): this
9782 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9783 openOn: function (map) {
9784 map.openPopup(this);
9785 return this;
9786 },
9787
9788 onAdd: function (map) {
9789 DivOverlay.prototype.onAdd.call(this, map);
9790
9791 // @namespace Map
9792 // @section Popup events
9793 // @event popupopen: PopupEvent
9794 // Fired when a popup is opened in the map
9795 map.fire('popupopen', {popup: this});
9796
9797 if (this._source) {
9798 // @namespace Layer
9799 // @section Popup events
9800 // @event popupopen: PopupEvent
9801 // Fired when a popup bound to this layer is opened
9802 this._source.fire('popupopen', {popup: this}, true);
9803 // For non-path layers, we toggle the popup when clicking
9804 // again the layer, so prevent the map to reopen it.
9805 if (!(this._source instanceof Path)) {
9806 this._source.on('preclick', stopPropagation);
9807 }
9808 }
9809 },
9810
9811 onRemove: function (map) {
9812 DivOverlay.prototype.onRemove.call(this, map);
9813
9814 // @namespace Map
9815 // @section Popup events
9816 // @event popupclose: PopupEvent
9817 // Fired when a popup in the map is closed
9818 map.fire('popupclose', {popup: this});
9819
9820 if (this._source) {
9821 // @namespace Layer
9822 // @section Popup events
9823 // @event popupclose: PopupEvent
9824 // Fired when a popup bound to this layer is closed
9825 this._source.fire('popupclose', {popup: this}, true);
9826 if (!(this._source instanceof Path)) {
9827 this._source.off('preclick', stopPropagation);
9828 }
9829 }
9830 },
9831
9832 getEvents: function () {
9833 var events = DivOverlay.prototype.getEvents.call(this);
9834
9835 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9836 events.preclick = this._close;
9837 }
9838
9839 if (this.options.keepInView) {
9840 events.moveend = this._adjustPan;
9841 }
9842
9843 return events;
9844 },
9845
9846 _close: function () {
9847 if (this._map) {
9848 this._map.closePopup(this);
9849 }
9850 },
9851
9852 _initLayout: function () {
9853 var prefix = 'leaflet-popup',
9854 container = this._container = create$1('div',
9855 prefix + ' ' + (this.options.className || '') +
9856 ' leaflet-zoom-animated');
9857
9858 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9859 this._contentNode = create$1('div', prefix + '-content', wrapper);
9860
9861 disableClickPropagation(wrapper);
9862 disableScrollPropagation(this._contentNode);
9863 on(wrapper, 'contextmenu', stopPropagation);
9864
9865 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9866 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9867
9868 if (this.options.closeButton) {
9869 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9870 closeButton.href = '#close';
9871 closeButton.innerHTML = '&#215;';
9872
9873 on(closeButton, 'click', this._onCloseButtonClick, this);
9874 }
9875 },
9876
9877 _updateLayout: function () {
9878 var container = this._contentNode,
9879 style = container.style;
9880
9881 style.width = '';
9882 style.whiteSpace = 'nowrap';
9883
9884 var width = container.offsetWidth;
9885 width = Math.min(width, this.options.maxWidth);
9886 width = Math.max(width, this.options.minWidth);
9887
9888 style.width = (width + 1) + 'px';
9889 style.whiteSpace = '';
9890
9891 style.height = '';
9892
9893 var height = container.offsetHeight,
9894 maxHeight = this.options.maxHeight,
9895 scrolledClass = 'leaflet-popup-scrolled';
9896
9897 if (maxHeight && height > maxHeight) {
9898 style.height = maxHeight + 'px';
9899 addClass(container, scrolledClass);
9900 } else {
9901 removeClass(container, scrolledClass);
9902 }
9903
9904 this._containerWidth = this._container.offsetWidth;
9905 },
9906
9907 _animateZoom: function (e) {
9908 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9909 anchor = this._getAnchor();
9910 setPosition(this._container, pos.add(anchor));
9911 },
9912
9913 _adjustPan: function () {
9914 if (!this.options.autoPan) { return; }
9915 if (this._map._panAnim) { this._map._panAnim.stop(); }
9916
9917 var map = this._map,
9918 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9919 containerHeight = this._container.offsetHeight + marginBottom,
9920 containerWidth = this._containerWidth,
9921 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9922
9923 layerPos._add(getPosition(this._container));
9924
9925 var containerPos = map.layerPointToContainerPoint(layerPos),
9926 padding = toPoint(this.options.autoPanPadding),
9927 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9928 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9929 size = map.getSize(),
9930 dx = 0,
9931 dy = 0;
9932
9933 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9934 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9935 }
9936 if (containerPos.x - dx - paddingTL.x < 0) { // left
9937 dx = containerPos.x - paddingTL.x;
9938 }
9939 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9940 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9941 }
9942 if (containerPos.y - dy - paddingTL.y < 0) { // top
9943 dy = containerPos.y - paddingTL.y;
9944 }
9945
9946 // @namespace Map
9947 // @section Popup events
9948 // @event autopanstart: Event
9949 // Fired when the map starts autopanning when opening a popup.
9950 if (dx || dy) {
9951 map
9952 .fire('autopanstart')
9953 .panBy([dx, dy]);
9954 }
9955 },
9956
9957 _onCloseButtonClick: function (e) {
9958 this._close();
9959 stop(e);
9960 },
9961
9962 _getAnchor: function () {
9963 // Where should we anchor the popup on the source layer?
9964 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9965 }
9966
9967});
9968
9969// @namespace Popup
9970// @factory L.popup(options?: Popup options, source?: Layer)
9971// 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.
9972var popup = function (options, source) {
9973 return new Popup(options, source);
9974};
9975
9976
9977/* @namespace Map
9978 * @section Interaction Options
9979 * @option closePopupOnClick: Boolean = true
9980 * Set it to `false` if you don't want popups to close when user clicks the map.
9981 */
9982Map.mergeOptions({
9983 closePopupOnClick: true
9984});
9985
9986
9987// @namespace Map
9988// @section Methods for Layers and Controls
9989Map.include({
9990 // @method openPopup(popup: Popup): this
9991 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9992 // @alternative
9993 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9994 // Creates a popup with the specified content and options and opens it in the given point on a map.
9995 openPopup: function (popup, latlng, options) {
9996 if (!(popup instanceof Popup)) {
9997 popup = new Popup(options).setContent(popup);
9998 }
9999
10000 if (latlng) {
10001 popup.setLatLng(latlng);
10002 }
10003
10004 if (this.hasLayer(popup)) {
10005 return this;
10006 }
10007
10008 if (this._popup && this._popup.options.autoClose) {
10009 this.closePopup();
10010 }
10011
10012 this._popup = popup;
10013 return this.addLayer(popup);
10014 },
10015
10016 // @method closePopup(popup?: Popup): this
10017 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10018 closePopup: function (popup) {
10019 if (!popup || popup === this._popup) {
10020 popup = this._popup;
10021 this._popup = null;
10022 }
10023 if (popup) {
10024 this.removeLayer(popup);
10025 }
10026 return this;
10027 }
10028});
10029
10030/*
10031 * @namespace Layer
10032 * @section Popup methods example
10033 *
10034 * All layers share a set of methods convenient for binding popups to it.
10035 *
10036 * ```js
10037 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10038 * layer.openPopup();
10039 * layer.closePopup();
10040 * ```
10041 *
10042 * 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.
10043 */
10044
10045// @section Popup methods
10046Layer.include({
10047
10048 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10049 // Binds a popup to the layer with the passed `content` and sets up the
10050 // necessary event listeners. If a `Function` is passed it will receive
10051 // the layer as the first argument and should return a `String` or `HTMLElement`.
10052 bindPopup: function (content, options) {
10053
10054 if (content instanceof Popup) {
10055 setOptions(content, options);
10056 this._popup = content;
10057 content._source = this;
10058 } else {
10059 if (!this._popup || options) {
10060 this._popup = new Popup(options, this);
10061 }
10062 this._popup.setContent(content);
10063 }
10064
10065 if (!this._popupHandlersAdded) {
10066 this.on({
10067 click: this._openPopup,
10068 keypress: this._onKeyPress,
10069 remove: this.closePopup,
10070 move: this._movePopup
10071 });
10072 this._popupHandlersAdded = true;
10073 }
10074
10075 return this;
10076 },
10077
10078 // @method unbindPopup(): this
10079 // Removes the popup previously bound with `bindPopup`.
10080 unbindPopup: function () {
10081 if (this._popup) {
10082 this.off({
10083 click: this._openPopup,
10084 keypress: this._onKeyPress,
10085 remove: this.closePopup,
10086 move: this._movePopup
10087 });
10088 this._popupHandlersAdded = false;
10089 this._popup = null;
10090 }
10091 return this;
10092 },
10093
10094 // @method openPopup(latlng?: LatLng): this
10095 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10096 openPopup: function (layer, latlng) {
10097 if (this._popup && this._map) {
10098 latlng = this._popup._prepareOpen(this, layer, latlng);
10099
10100 // open the popup on the map
10101 this._map.openPopup(this._popup, latlng);
10102 }
10103
10104 return this;
10105 },
10106
10107 // @method closePopup(): this
10108 // Closes the popup bound to this layer if it is open.
10109 closePopup: function () {
10110 if (this._popup) {
10111 this._popup._close();
10112 }
10113 return this;
10114 },
10115
10116 // @method togglePopup(): this
10117 // Opens or closes the popup bound to this layer depending on its current state.
10118 togglePopup: function (target) {
10119 if (this._popup) {
10120 if (this._popup._map) {
10121 this.closePopup();
10122 } else {
10123 this.openPopup(target);
10124 }
10125 }
10126 return this;
10127 },
10128
10129 // @method isPopupOpen(): boolean
10130 // Returns `true` if the popup bound to this layer is currently open.
10131 isPopupOpen: function () {
10132 return (this._popup ? this._popup.isOpen() : false);
10133 },
10134
10135 // @method setPopupContent(content: String|HTMLElement|Popup): this
10136 // Sets the content of the popup bound to this layer.
10137 setPopupContent: function (content) {
10138 if (this._popup) {
10139 this._popup.setContent(content);
10140 }
10141 return this;
10142 },
10143
10144 // @method getPopup(): Popup
10145 // Returns the popup bound to this layer.
10146 getPopup: function () {
10147 return this._popup;
10148 },
10149
10150 _openPopup: function (e) {
10151 var layer = e.layer || e.target;
10152
10153 if (!this._popup) {
10154 return;
10155 }
10156
10157 if (!this._map) {
10158 return;
10159 }
10160
10161 // prevent map click
10162 stop(e);
10163
10164 // if this inherits from Path its a vector and we can just
10165 // open the popup at the new location
10166 if (layer instanceof Path) {
10167 this.openPopup(e.layer || e.target, e.latlng);
10168 return;
10169 }
10170
10171 // otherwise treat it like a marker and figure out
10172 // if we should toggle it open/closed
10173 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10174 this.closePopup();
10175 } else {
10176 this.openPopup(layer, e.latlng);
10177 }
10178 },
10179
10180 _movePopup: function (e) {
10181 this._popup.setLatLng(e.latlng);
10182 },
10183
10184 _onKeyPress: function (e) {
10185 if (e.originalEvent.keyCode === 13) {
10186 this._openPopup(e);
10187 }
10188 }
10189});
10190
10191/*
10192 * @class Tooltip
10193 * @inherits DivOverlay
10194 * @aka L.Tooltip
10195 * Used to display small texts on top of map layers.
10196 *
10197 * @example
10198 *
10199 * ```js
10200 * marker.bindTooltip("my tooltip text").openTooltip();
10201 * ```
10202 * Note about tooltip offset. Leaflet takes two options in consideration
10203 * for computing tooltip offsetting:
10204 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10205 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10206 * move it to the bottom. Negatives will move to the left and top.
10207 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10208 * should adapt this value if you use a custom icon.
10209 */
10210
10211
10212// @namespace Tooltip
10213var Tooltip = DivOverlay.extend({
10214
10215 // @section
10216 // @aka Tooltip options
10217 options: {
10218 // @option pane: String = 'tooltipPane'
10219 // `Map pane` where the tooltip will be added.
10220 pane: 'tooltipPane',
10221
10222 // @option offset: Point = Point(0, 0)
10223 // Optional offset of the tooltip position.
10224 offset: [0, 0],
10225
10226 // @option direction: String = 'auto'
10227 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10228 // `top`, `bottom`, `center`, `auto`.
10229 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10230 // position on the map.
10231 direction: 'auto',
10232
10233 // @option permanent: Boolean = false
10234 // Whether to open the tooltip permanently or only on mouseover.
10235 permanent: false,
10236
10237 // @option sticky: Boolean = false
10238 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10239 sticky: false,
10240
10241 // @option interactive: Boolean = false
10242 // If true, the tooltip will listen to the feature events.
10243 interactive: false,
10244
10245 // @option opacity: Number = 0.9
10246 // Tooltip container opacity.
10247 opacity: 0.9
10248 },
10249
10250 onAdd: function (map) {
10251 DivOverlay.prototype.onAdd.call(this, map);
10252 this.setOpacity(this.options.opacity);
10253
10254 // @namespace Map
10255 // @section Tooltip events
10256 // @event tooltipopen: TooltipEvent
10257 // Fired when a tooltip is opened in the map.
10258 map.fire('tooltipopen', {tooltip: this});
10259
10260 if (this._source) {
10261 // @namespace Layer
10262 // @section Tooltip events
10263 // @event tooltipopen: TooltipEvent
10264 // Fired when a tooltip bound to this layer is opened.
10265 this._source.fire('tooltipopen', {tooltip: this}, true);
10266 }
10267 },
10268
10269 onRemove: function (map) {
10270 DivOverlay.prototype.onRemove.call(this, map);
10271
10272 // @namespace Map
10273 // @section Tooltip events
10274 // @event tooltipclose: TooltipEvent
10275 // Fired when a tooltip in the map is closed.
10276 map.fire('tooltipclose', {tooltip: this});
10277
10278 if (this._source) {
10279 // @namespace Layer
10280 // @section Tooltip events
10281 // @event tooltipclose: TooltipEvent
10282 // Fired when a tooltip bound to this layer is closed.
10283 this._source.fire('tooltipclose', {tooltip: this}, true);
10284 }
10285 },
10286
10287 getEvents: function () {
10288 var events = DivOverlay.prototype.getEvents.call(this);
10289
10290 if (touch && !this.options.permanent) {
10291 events.preclick = this._close;
10292 }
10293
10294 return events;
10295 },
10296
10297 _close: function () {
10298 if (this._map) {
10299 this._map.closeTooltip(this);
10300 }
10301 },
10302
10303 _initLayout: function () {
10304 var prefix = 'leaflet-tooltip',
10305 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10306
10307 this._contentNode = this._container = create$1('div', className);
10308 },
10309
10310 _updateLayout: function () {},
10311
10312 _adjustPan: function () {},
10313
10314 _setPosition: function (pos) {
10315 var map = this._map,
10316 container = this._container,
10317 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10318 tooltipPoint = map.layerPointToContainerPoint(pos),
10319 direction = this.options.direction,
10320 tooltipWidth = container.offsetWidth,
10321 tooltipHeight = container.offsetHeight,
10322 offset = toPoint(this.options.offset),
10323 anchor = this._getAnchor();
10324
10325 if (direction === 'top') {
10326 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10327 } else if (direction === 'bottom') {
10328 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10329 } else if (direction === 'center') {
10330 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10331 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10332 direction = 'right';
10333 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10334 } else {
10335 direction = 'left';
10336 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10337 }
10338
10339 removeClass(container, 'leaflet-tooltip-right');
10340 removeClass(container, 'leaflet-tooltip-left');
10341 removeClass(container, 'leaflet-tooltip-top');
10342 removeClass(container, 'leaflet-tooltip-bottom');
10343 addClass(container, 'leaflet-tooltip-' + direction);
10344 setPosition(container, pos);
10345 },
10346
10347 _updatePosition: function () {
10348 var pos = this._map.latLngToLayerPoint(this._latlng);
10349 this._setPosition(pos);
10350 },
10351
10352 setOpacity: function (opacity) {
10353 this.options.opacity = opacity;
10354
10355 if (this._container) {
10356 setOpacity(this._container, opacity);
10357 }
10358 },
10359
10360 _animateZoom: function (e) {
10361 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10362 this._setPosition(pos);
10363 },
10364
10365 _getAnchor: function () {
10366 // Where should we anchor the tooltip on the source layer?
10367 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10368 }
10369
10370});
10371
10372// @namespace Tooltip
10373// @factory L.tooltip(options?: Tooltip options, source?: Layer)
10374// 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.
10375var tooltip = function (options, source) {
10376 return new Tooltip(options, source);
10377};
10378
10379// @namespace Map
10380// @section Methods for Layers and Controls
10381Map.include({
10382
10383 // @method openTooltip(tooltip: Tooltip): this
10384 // Opens the specified tooltip.
10385 // @alternative
10386 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10387 // Creates a tooltip with the specified content and options and open it.
10388 openTooltip: function (tooltip, latlng, options) {
10389 if (!(tooltip instanceof Tooltip)) {
10390 tooltip = new Tooltip(options).setContent(tooltip);
10391 }
10392
10393 if (latlng) {
10394 tooltip.setLatLng(latlng);
10395 }
10396
10397 if (this.hasLayer(tooltip)) {
10398 return this;
10399 }
10400
10401 return this.addLayer(tooltip);
10402 },
10403
10404 // @method closeTooltip(tooltip?: Tooltip): this
10405 // Closes the tooltip given as parameter.
10406 closeTooltip: function (tooltip) {
10407 if (tooltip) {
10408 this.removeLayer(tooltip);
10409 }
10410 return this;
10411 }
10412
10413});
10414
10415/*
10416 * @namespace Layer
10417 * @section Tooltip methods example
10418 *
10419 * All layers share a set of methods convenient for binding tooltips to it.
10420 *
10421 * ```js
10422 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10423 * layer.openTooltip();
10424 * layer.closeTooltip();
10425 * ```
10426 */
10427
10428// @section Tooltip methods
10429Layer.include({
10430
10431 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10432 // Binds a tooltip to the layer with the passed `content` and sets up the
10433 // necessary event listeners. If a `Function` is passed it will receive
10434 // the layer as the first argument and should return a `String` or `HTMLElement`.
10435 bindTooltip: function (content, options) {
10436
10437 if (content instanceof Tooltip) {
10438 setOptions(content, options);
10439 this._tooltip = content;
10440 content._source = this;
10441 } else {
10442 if (!this._tooltip || options) {
10443 this._tooltip = new Tooltip(options, this);
10444 }
10445 this._tooltip.setContent(content);
10446
10447 }
10448
10449 this._initTooltipInteractions();
10450
10451 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10452 this.openTooltip();
10453 }
10454
10455 return this;
10456 },
10457
10458 // @method unbindTooltip(): this
10459 // Removes the tooltip previously bound with `bindTooltip`.
10460 unbindTooltip: function () {
10461 if (this._tooltip) {
10462 this._initTooltipInteractions(true);
10463 this.closeTooltip();
10464 this._tooltip = null;
10465 }
10466 return this;
10467 },
10468
10469 _initTooltipInteractions: function (remove$$1) {
10470 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10471 var onOff = remove$$1 ? 'off' : 'on',
10472 events = {
10473 remove: this.closeTooltip,
10474 move: this._moveTooltip
10475 };
10476 if (!this._tooltip.options.permanent) {
10477 events.mouseover = this._openTooltip;
10478 events.mouseout = this.closeTooltip;
10479 if (this._tooltip.options.sticky) {
10480 events.mousemove = this._moveTooltip;
10481 }
10482 if (touch) {
10483 events.click = this._openTooltip;
10484 }
10485 } else {
10486 events.add = this._openTooltip;
10487 }
10488 this[onOff](events);
10489 this._tooltipHandlersAdded = !remove$$1;
10490 },
10491
10492 // @method openTooltip(latlng?: LatLng): this
10493 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10494 openTooltip: function (layer, latlng) {
10495 if (this._tooltip && this._map) {
10496 latlng = this._tooltip._prepareOpen(this, layer, latlng);
10497
10498 // open the tooltip on the map
10499 this._map.openTooltip(this._tooltip, latlng);
10500
10501 // Tooltip container may not be defined if not permanent and never
10502 // opened.
10503 if (this._tooltip.options.interactive && this._tooltip._container) {
10504 addClass(this._tooltip._container, 'leaflet-clickable');
10505 this.addInteractiveTarget(this._tooltip._container);
10506 }
10507 }
10508
10509 return this;
10510 },
10511
10512 // @method closeTooltip(): this
10513 // Closes the tooltip bound to this layer if it is open.
10514 closeTooltip: function () {
10515 if (this._tooltip) {
10516 this._tooltip._close();
10517 if (this._tooltip.options.interactive && this._tooltip._container) {
10518 removeClass(this._tooltip._container, 'leaflet-clickable');
10519 this.removeInteractiveTarget(this._tooltip._container);
10520 }
10521 }
10522 return this;
10523 },
10524
10525 // @method toggleTooltip(): this
10526 // Opens or closes the tooltip bound to this layer depending on its current state.
10527 toggleTooltip: function (target) {
10528 if (this._tooltip) {
10529 if (this._tooltip._map) {
10530 this.closeTooltip();
10531 } else {
10532 this.openTooltip(target);
10533 }
10534 }
10535 return this;
10536 },
10537
10538 // @method isTooltipOpen(): boolean
10539 // Returns `true` if the tooltip bound to this layer is currently open.
10540 isTooltipOpen: function () {
10541 return this._tooltip.isOpen();
10542 },
10543
10544 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10545 // Sets the content of the tooltip bound to this layer.
10546 setTooltipContent: function (content) {
10547 if (this._tooltip) {
10548 this._tooltip.setContent(content);
10549 }
10550 return this;
10551 },
10552
10553 // @method getTooltip(): Tooltip
10554 // Returns the tooltip bound to this layer.
10555 getTooltip: function () {
10556 return this._tooltip;
10557 },
10558
10559 _openTooltip: function (e) {
10560 var layer = e.layer || e.target;
10561
10562 if (!this._tooltip || !this._map) {
10563 return;
10564 }
10565 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10566 },
10567
10568 _moveTooltip: function (e) {
10569 var latlng = e.latlng, containerPoint, layerPoint;
10570 if (this._tooltip.options.sticky && e.originalEvent) {
10571 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10572 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10573 latlng = this._map.layerPointToLatLng(layerPoint);
10574 }
10575 this._tooltip.setLatLng(latlng);
10576 }
10577});
10578
10579/*
10580 * @class DivIcon
10581 * @aka L.DivIcon
10582 * @inherits Icon
10583 *
10584 * Represents a lightweight icon for markers that uses a simple `<div>`
10585 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10586 *
10587 * @example
10588 * ```js
10589 * var myIcon = L.divIcon({className: 'my-div-icon'});
10590 * // you can set .my-div-icon styles in CSS
10591 *
10592 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10593 * ```
10594 *
10595 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10596 */
10597
10598var DivIcon = Icon.extend({
10599 options: {
10600 // @section
10601 // @aka DivIcon options
10602 iconSize: [12, 12], // also can be set through CSS
10603
10604 // iconAnchor: (Point),
10605 // popupAnchor: (Point),
10606
10607 // @option html: String|HTMLElement = ''
10608 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10609 // an instance of `HTMLElement`.
10610 html: false,
10611
10612 // @option bgPos: Point = [0, 0]
10613 // Optional relative position of the background, in pixels
10614 bgPos: null,
10615
10616 className: 'leaflet-div-icon'
10617 },
10618
10619 createIcon: function (oldIcon) {
10620 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10621 options = this.options;
10622
10623 if (options.html instanceof Element) {
10624 empty(div);
10625 div.appendChild(options.html);
10626 } else {
10627 div.innerHTML = options.html !== false ? options.html : '';
10628 }
10629
10630 if (options.bgPos) {
10631 var bgPos = toPoint(options.bgPos);
10632 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10633 }
10634 this._setIconStyles(div, 'icon');
10635
10636 return div;
10637 },
10638
10639 createShadow: function () {
10640 return null;
10641 }
10642});
10643
10644// @factory L.divIcon(options: DivIcon options)
10645// Creates a `DivIcon` instance with the given options.
10646function divIcon(options) {
10647 return new DivIcon(options);
10648}
10649
10650Icon.Default = IconDefault;
10651
10652/*
10653 * @class GridLayer
10654 * @inherits Layer
10655 * @aka L.GridLayer
10656 *
10657 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10658 * 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.
10659 *
10660 *
10661 * @section Synchronous usage
10662 * @example
10663 *
10664 * 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.
10665 *
10666 * ```js
10667 * var CanvasLayer = L.GridLayer.extend({
10668 * createTile: function(coords){
10669 * // create a <canvas> element for drawing
10670 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10671 *
10672 * // setup tile width and height according to the options
10673 * var size = this.getTileSize();
10674 * tile.width = size.x;
10675 * tile.height = size.y;
10676 *
10677 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10678 * var ctx = tile.getContext('2d');
10679 *
10680 * // return the tile so it can be rendered on screen
10681 * return tile;
10682 * }
10683 * });
10684 * ```
10685 *
10686 * @section Asynchronous usage
10687 * @example
10688 *
10689 * 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.
10690 *
10691 * ```js
10692 * var CanvasLayer = L.GridLayer.extend({
10693 * createTile: function(coords, done){
10694 * var error;
10695 *
10696 * // create a <canvas> element for drawing
10697 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10698 *
10699 * // setup tile width and height according to the options
10700 * var size = this.getTileSize();
10701 * tile.width = size.x;
10702 * tile.height = size.y;
10703 *
10704 * // draw something asynchronously and pass the tile to the done() callback
10705 * setTimeout(function() {
10706 * done(error, tile);
10707 * }, 1000);
10708 *
10709 * return tile;
10710 * }
10711 * });
10712 * ```
10713 *
10714 * @section
10715 */
10716
10717
10718var GridLayer = Layer.extend({
10719
10720 // @section
10721 // @aka GridLayer options
10722 options: {
10723 // @option tileSize: Number|Point = 256
10724 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10725 tileSize: 256,
10726
10727 // @option opacity: Number = 1.0
10728 // Opacity of the tiles. Can be used in the `createTile()` function.
10729 opacity: 1,
10730
10731 // @option updateWhenIdle: Boolean = (depends)
10732 // Load new tiles only when panning ends.
10733 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10734 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10735 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10736 updateWhenIdle: mobile,
10737
10738 // @option updateWhenZooming: Boolean = true
10739 // 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.
10740 updateWhenZooming: true,
10741
10742 // @option updateInterval: Number = 200
10743 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10744 updateInterval: 200,
10745
10746 // @option zIndex: Number = 1
10747 // The explicit zIndex of the tile layer.
10748 zIndex: 1,
10749
10750 // @option bounds: LatLngBounds = undefined
10751 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10752 bounds: null,
10753
10754 // @option minZoom: Number = 0
10755 // The minimum zoom level down to which this layer will be displayed (inclusive).
10756 minZoom: 0,
10757
10758 // @option maxZoom: Number = undefined
10759 // The maximum zoom level up to which this layer will be displayed (inclusive).
10760 maxZoom: undefined,
10761
10762 // @option maxNativeZoom: Number = undefined
10763 // Maximum zoom number the tile source has available. If it is specified,
10764 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10765 // from `maxNativeZoom` level and auto-scaled.
10766 maxNativeZoom: undefined,
10767
10768 // @option minNativeZoom: Number = undefined
10769 // Minimum zoom number the tile source has available. If it is specified,
10770 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10771 // from `minNativeZoom` level and auto-scaled.
10772 minNativeZoom: undefined,
10773
10774 // @option noWrap: Boolean = false
10775 // Whether the layer is wrapped around the antimeridian. If `true`, the
10776 // GridLayer will only be displayed once at low zoom levels. Has no
10777 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10778 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10779 // tiles outside the CRS limits.
10780 noWrap: false,
10781
10782 // @option pane: String = 'tilePane'
10783 // `Map pane` where the grid layer will be added.
10784 pane: 'tilePane',
10785
10786 // @option className: String = ''
10787 // A custom class name to assign to the tile layer. Empty by default.
10788 className: '',
10789
10790 // @option keepBuffer: Number = 2
10791 // When panning the map, keep this many rows and columns of tiles before unloading them.
10792 keepBuffer: 2
10793 },
10794
10795 initialize: function (options) {
10796 setOptions(this, options);
10797 },
10798
10799 onAdd: function () {
10800 this._initContainer();
10801
10802 this._levels = {};
10803 this._tiles = {};
10804
10805 this._resetView();
10806 this._update();
10807 },
10808
10809 beforeAdd: function (map) {
10810 map._addZoomLimit(this);
10811 },
10812
10813 onRemove: function (map) {
10814 this._removeAllTiles();
10815 remove(this._container);
10816 map._removeZoomLimit(this);
10817 this._container = null;
10818 this._tileZoom = undefined;
10819 },
10820
10821 // @method bringToFront: this
10822 // Brings the tile layer to the top of all tile layers.
10823 bringToFront: function () {
10824 if (this._map) {
10825 toFront(this._container);
10826 this._setAutoZIndex(Math.max);
10827 }
10828 return this;
10829 },
10830
10831 // @method bringToBack: this
10832 // Brings the tile layer to the bottom of all tile layers.
10833 bringToBack: function () {
10834 if (this._map) {
10835 toBack(this._container);
10836 this._setAutoZIndex(Math.min);
10837 }
10838 return this;
10839 },
10840
10841 // @method getContainer: HTMLElement
10842 // Returns the HTML element that contains the tiles for this layer.
10843 getContainer: function () {
10844 return this._container;
10845 },
10846
10847 // @method setOpacity(opacity: Number): this
10848 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10849 setOpacity: function (opacity) {
10850 this.options.opacity = opacity;
10851 this._updateOpacity();
10852 return this;
10853 },
10854
10855 // @method setZIndex(zIndex: Number): this
10856 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10857 setZIndex: function (zIndex) {
10858 this.options.zIndex = zIndex;
10859 this._updateZIndex();
10860
10861 return this;
10862 },
10863
10864 // @method isLoading: Boolean
10865 // Returns `true` if any tile in the grid layer has not finished loading.
10866 isLoading: function () {
10867 return this._loading;
10868 },
10869
10870 // @method redraw: this
10871 // Causes the layer to clear all the tiles and request them again.
10872 redraw: function () {
10873 if (this._map) {
10874 this._removeAllTiles();
10875 this._update();
10876 }
10877 return this;
10878 },
10879
10880 getEvents: function () {
10881 var events = {
10882 viewprereset: this._invalidateAll,
10883 viewreset: this._resetView,
10884 zoom: this._resetView,
10885 moveend: this._onMoveEnd
10886 };
10887
10888 if (!this.options.updateWhenIdle) {
10889 // update tiles on move, but not more often than once per given interval
10890 if (!this._onMove) {
10891 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10892 }
10893
10894 events.move = this._onMove;
10895 }
10896
10897 if (this._zoomAnimated) {
10898 events.zoomanim = this._animateZoom;
10899 }
10900
10901 return events;
10902 },
10903
10904 // @section Extension methods
10905 // Layers extending `GridLayer` shall reimplement the following method.
10906 // @method createTile(coords: Object, done?: Function): HTMLElement
10907 // Called only internally, must be overridden by classes extending `GridLayer`.
10908 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10909 // is specified, it must be called when the tile has finished loading and drawing.
10910 createTile: function () {
10911 return document.createElement('div');
10912 },
10913
10914 // @section
10915 // @method getTileSize: Point
10916 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10917 getTileSize: function () {
10918 var s = this.options.tileSize;
10919 return s instanceof Point ? s : new Point(s, s);
10920 },
10921
10922 _updateZIndex: function () {
10923 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10924 this._container.style.zIndex = this.options.zIndex;
10925 }
10926 },
10927
10928 _setAutoZIndex: function (compare) {
10929 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10930
10931 var layers = this.getPane().children,
10932 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10933
10934 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10935
10936 zIndex = layers[i].style.zIndex;
10937
10938 if (layers[i] !== this._container && zIndex) {
10939 edgeZIndex = compare(edgeZIndex, +zIndex);
10940 }
10941 }
10942
10943 if (isFinite(edgeZIndex)) {
10944 this.options.zIndex = edgeZIndex + compare(-1, 1);
10945 this._updateZIndex();
10946 }
10947 },
10948
10949 _updateOpacity: function () {
10950 if (!this._map) { return; }
10951
10952 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10953 if (ielt9) { return; }
10954
10955 setOpacity(this._container, this.options.opacity);
10956
10957 var now = +new Date(),
10958 nextFrame = false,
10959 willPrune = false;
10960
10961 for (var key in this._tiles) {
10962 var tile = this._tiles[key];
10963 if (!tile.current || !tile.loaded) { continue; }
10964
10965 var fade = Math.min(1, (now - tile.loaded) / 200);
10966
10967 setOpacity(tile.el, fade);
10968 if (fade < 1) {
10969 nextFrame = true;
10970 } else {
10971 if (tile.active) {
10972 willPrune = true;
10973 } else {
10974 this._onOpaqueTile(tile);
10975 }
10976 tile.active = true;
10977 }
10978 }
10979
10980 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10981
10982 if (nextFrame) {
10983 cancelAnimFrame(this._fadeFrame);
10984 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10985 }
10986 },
10987
10988 _onOpaqueTile: falseFn,
10989
10990 _initContainer: function () {
10991 if (this._container) { return; }
10992
10993 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10994 this._updateZIndex();
10995
10996 if (this.options.opacity < 1) {
10997 this._updateOpacity();
10998 }
10999
11000 this.getPane().appendChild(this._container);
11001 },
11002
11003 _updateLevels: function () {
11004
11005 var zoom = this._tileZoom,
11006 maxZoom = this.options.maxZoom;
11007
11008 if (zoom === undefined) { return undefined; }
11009
11010 for (var z in this._levels) {
11011 if (this._levels[z].el.children.length || z === zoom) {
11012 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11013 this._onUpdateLevel(z);
11014 } else {
11015 remove(this._levels[z].el);
11016 this._removeTilesAtZoom(z);
11017 this._onRemoveLevel(z);
11018 delete this._levels[z];
11019 }
11020 }
11021
11022 var level = this._levels[zoom],
11023 map = this._map;
11024
11025 if (!level) {
11026 level = this._levels[zoom] = {};
11027
11028 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11029 level.el.style.zIndex = maxZoom;
11030
11031 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11032 level.zoom = zoom;
11033
11034 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11035
11036 // force the browser to consider the newly added element for transition
11037 falseFn(level.el.offsetWidth);
11038
11039 this._onCreateLevel(level);
11040 }
11041
11042 this._level = level;
11043
11044 return level;
11045 },
11046
11047 _onUpdateLevel: falseFn,
11048
11049 _onRemoveLevel: falseFn,
11050
11051 _onCreateLevel: falseFn,
11052
11053 _pruneTiles: function () {
11054 if (!this._map) {
11055 return;
11056 }
11057
11058 var key, tile;
11059
11060 var zoom = this._map.getZoom();
11061 if (zoom > this.options.maxZoom ||
11062 zoom < this.options.minZoom) {
11063 this._removeAllTiles();
11064 return;
11065 }
11066
11067 for (key in this._tiles) {
11068 tile = this._tiles[key];
11069 tile.retain = tile.current;
11070 }
11071
11072 for (key in this._tiles) {
11073 tile = this._tiles[key];
11074 if (tile.current && !tile.active) {
11075 var coords = tile.coords;
11076 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11077 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11078 }
11079 }
11080 }
11081
11082 for (key in this._tiles) {
11083 if (!this._tiles[key].retain) {
11084 this._removeTile(key);
11085 }
11086 }
11087 },
11088
11089 _removeTilesAtZoom: function (zoom) {
11090 for (var key in this._tiles) {
11091 if (this._tiles[key].coords.z !== zoom) {
11092 continue;
11093 }
11094 this._removeTile(key);
11095 }
11096 },
11097
11098 _removeAllTiles: function () {
11099 for (var key in this._tiles) {
11100 this._removeTile(key);
11101 }
11102 },
11103
11104 _invalidateAll: function () {
11105 for (var z in this._levels) {
11106 remove(this._levels[z].el);
11107 this._onRemoveLevel(z);
11108 delete this._levels[z];
11109 }
11110 this._removeAllTiles();
11111
11112 this._tileZoom = undefined;
11113 },
11114
11115 _retainParent: function (x, y, z, minZoom) {
11116 var x2 = Math.floor(x / 2),
11117 y2 = Math.floor(y / 2),
11118 z2 = z - 1,
11119 coords2 = new Point(+x2, +y2);
11120 coords2.z = +z2;
11121
11122 var key = this._tileCoordsToKey(coords2),
11123 tile = this._tiles[key];
11124
11125 if (tile && tile.active) {
11126 tile.retain = true;
11127 return true;
11128
11129 } else if (tile && tile.loaded) {
11130 tile.retain = true;
11131 }
11132
11133 if (z2 > minZoom) {
11134 return this._retainParent(x2, y2, z2, minZoom);
11135 }
11136
11137 return false;
11138 },
11139
11140 _retainChildren: function (x, y, z, maxZoom) {
11141
11142 for (var i = 2 * x; i < 2 * x + 2; i++) {
11143 for (var j = 2 * y; j < 2 * y + 2; j++) {
11144
11145 var coords = new Point(i, j);
11146 coords.z = z + 1;
11147
11148 var key = this._tileCoordsToKey(coords),
11149 tile = this._tiles[key];
11150
11151 if (tile && tile.active) {
11152 tile.retain = true;
11153 continue;
11154
11155 } else if (tile && tile.loaded) {
11156 tile.retain = true;
11157 }
11158
11159 if (z + 1 < maxZoom) {
11160 this._retainChildren(i, j, z + 1, maxZoom);
11161 }
11162 }
11163 }
11164 },
11165
11166 _resetView: function (e) {
11167 var animating = e && (e.pinch || e.flyTo);
11168 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11169 },
11170
11171 _animateZoom: function (e) {
11172 this._setView(e.center, e.zoom, true, e.noUpdate);
11173 },
11174
11175 _clampZoom: function (zoom) {
11176 var options = this.options;
11177
11178 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11179 return options.minNativeZoom;
11180 }
11181
11182 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11183 return options.maxNativeZoom;
11184 }
11185
11186 return zoom;
11187 },
11188
11189 _setView: function (center, zoom, noPrune, noUpdate) {
11190 var tileZoom = this._clampZoom(Math.round(zoom));
11191 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11192 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11193 tileZoom = undefined;
11194 }
11195
11196 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11197
11198 if (!noUpdate || tileZoomChanged) {
11199
11200 this._tileZoom = tileZoom;
11201
11202 if (this._abortLoading) {
11203 this._abortLoading();
11204 }
11205
11206 this._updateLevels();
11207 this._resetGrid();
11208
11209 if (tileZoom !== undefined) {
11210 this._update(center);
11211 }
11212
11213 if (!noPrune) {
11214 this._pruneTiles();
11215 }
11216
11217 // Flag to prevent _updateOpacity from pruning tiles during
11218 // a zoom anim or a pinch gesture
11219 this._noPrune = !!noPrune;
11220 }
11221
11222 this._setZoomTransforms(center, zoom);
11223 },
11224
11225 _setZoomTransforms: function (center, zoom) {
11226 for (var i in this._levels) {
11227 this._setZoomTransform(this._levels[i], center, zoom);
11228 }
11229 },
11230
11231 _setZoomTransform: function (level, center, zoom) {
11232 var scale = this._map.getZoomScale(zoom, level.zoom),
11233 translate = level.origin.multiplyBy(scale)
11234 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11235
11236 if (any3d) {
11237 setTransform(level.el, translate, scale);
11238 } else {
11239 setPosition(level.el, translate);
11240 }
11241 },
11242
11243 _resetGrid: function () {
11244 var map = this._map,
11245 crs = map.options.crs,
11246 tileSize = this._tileSize = this.getTileSize(),
11247 tileZoom = this._tileZoom;
11248
11249 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11250 if (bounds) {
11251 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11252 }
11253
11254 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11255 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11256 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11257 ];
11258 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11259 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11260 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11261 ];
11262 },
11263
11264 _onMoveEnd: function () {
11265 if (!this._map || this._map._animatingZoom) { return; }
11266
11267 this._update();
11268 },
11269
11270 _getTiledPixelBounds: function (center) {
11271 var map = this._map,
11272 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11273 scale = map.getZoomScale(mapZoom, this._tileZoom),
11274 pixelCenter = map.project(center, this._tileZoom).floor(),
11275 halfSize = map.getSize().divideBy(scale * 2);
11276
11277 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11278 },
11279
11280 // Private method to load tiles in the grid's active zoom level according to map bounds
11281 _update: function (center) {
11282 var map = this._map;
11283 if (!map) { return; }
11284 var zoom = this._clampZoom(map.getZoom());
11285
11286 if (center === undefined) { center = map.getCenter(); }
11287 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11288
11289 var pixelBounds = this._getTiledPixelBounds(center),
11290 tileRange = this._pxBoundsToTileRange(pixelBounds),
11291 tileCenter = tileRange.getCenter(),
11292 queue = [],
11293 margin = this.options.keepBuffer,
11294 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11295 tileRange.getTopRight().add([margin, -margin]));
11296
11297 // Sanity check: panic if the tile range contains Infinity somewhere.
11298 if (!(isFinite(tileRange.min.x) &&
11299 isFinite(tileRange.min.y) &&
11300 isFinite(tileRange.max.x) &&
11301 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11302
11303 for (var key in this._tiles) {
11304 var c = this._tiles[key].coords;
11305 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11306 this._tiles[key].current = false;
11307 }
11308 }
11309
11310 // _update just loads more tiles. If the tile zoom level differs too much
11311 // from the map's, let _setView reset levels and prune old tiles.
11312 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11313
11314 // create a queue of coordinates to load tiles from
11315 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11316 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11317 var coords = new Point(i, j);
11318 coords.z = this._tileZoom;
11319
11320 if (!this._isValidTile(coords)) { continue; }
11321
11322 var tile = this._tiles[this._tileCoordsToKey(coords)];
11323 if (tile) {
11324 tile.current = true;
11325 } else {
11326 queue.push(coords);
11327 }
11328 }
11329 }
11330
11331 // sort tile queue to load tiles in order of their distance to center
11332 queue.sort(function (a, b) {
11333 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11334 });
11335
11336 if (queue.length !== 0) {
11337 // if it's the first batch of tiles to load
11338 if (!this._loading) {
11339 this._loading = true;
11340 // @event loading: Event
11341 // Fired when the grid layer starts loading tiles.
11342 this.fire('loading');
11343 }
11344
11345 // create DOM fragment to append tiles in one batch
11346 var fragment = document.createDocumentFragment();
11347
11348 for (i = 0; i < queue.length; i++) {
11349 this._addTile(queue[i], fragment);
11350 }
11351
11352 this._level.el.appendChild(fragment);
11353 }
11354 },
11355
11356 _isValidTile: function (coords) {
11357 var crs = this._map.options.crs;
11358
11359 if (!crs.infinite) {
11360 // don't load tile if it's out of bounds and not wrapped
11361 var bounds = this._globalTileRange;
11362 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11363 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11364 }
11365
11366 if (!this.options.bounds) { return true; }
11367
11368 // don't load tile if it doesn't intersect the bounds in options
11369 var tileBounds = this._tileCoordsToBounds(coords);
11370 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11371 },
11372
11373 _keyToBounds: function (key) {
11374 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11375 },
11376
11377 _tileCoordsToNwSe: function (coords) {
11378 var map = this._map,
11379 tileSize = this.getTileSize(),
11380 nwPoint = coords.scaleBy(tileSize),
11381 sePoint = nwPoint.add(tileSize),
11382 nw = map.unproject(nwPoint, coords.z),
11383 se = map.unproject(sePoint, coords.z);
11384 return [nw, se];
11385 },
11386
11387 // converts tile coordinates to its geographical bounds
11388 _tileCoordsToBounds: function (coords) {
11389 var bp = this._tileCoordsToNwSe(coords),
11390 bounds = new LatLngBounds(bp[0], bp[1]);
11391
11392 if (!this.options.noWrap) {
11393 bounds = this._map.wrapLatLngBounds(bounds);
11394 }
11395 return bounds;
11396 },
11397 // converts tile coordinates to key for the tile cache
11398 _tileCoordsToKey: function (coords) {
11399 return coords.x + ':' + coords.y + ':' + coords.z;
11400 },
11401
11402 // converts tile cache key to coordinates
11403 _keyToTileCoords: function (key) {
11404 var k = key.split(':'),
11405 coords = new Point(+k[0], +k[1]);
11406 coords.z = +k[2];
11407 return coords;
11408 },
11409
11410 _removeTile: function (key) {
11411 var tile = this._tiles[key];
11412 if (!tile) { return; }
11413
11414 remove(tile.el);
11415
11416 delete this._tiles[key];
11417
11418 // @event tileunload: TileEvent
11419 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11420 this.fire('tileunload', {
11421 tile: tile.el,
11422 coords: this._keyToTileCoords(key)
11423 });
11424 },
11425
11426 _initTile: function (tile) {
11427 addClass(tile, 'leaflet-tile');
11428
11429 var tileSize = this.getTileSize();
11430 tile.style.width = tileSize.x + 'px';
11431 tile.style.height = tileSize.y + 'px';
11432
11433 tile.onselectstart = falseFn;
11434 tile.onmousemove = falseFn;
11435
11436 // update opacity on tiles in IE7-8 because of filter inheritance problems
11437 if (ielt9 && this.options.opacity < 1) {
11438 setOpacity(tile, this.options.opacity);
11439 }
11440
11441 // without this hack, tiles disappear after zoom on Chrome for Android
11442 // https://github.com/Leaflet/Leaflet/issues/2078
11443 if (android && !android23) {
11444 tile.style.WebkitBackfaceVisibility = 'hidden';
11445 }
11446 },
11447
11448 _addTile: function (coords, container) {
11449 var tilePos = this._getTilePos(coords),
11450 key = this._tileCoordsToKey(coords);
11451
11452 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11453
11454 this._initTile(tile);
11455
11456 // if createTile is defined with a second argument ("done" callback),
11457 // we know that tile is async and will be ready later; otherwise
11458 if (this.createTile.length < 2) {
11459 // mark tile as ready, but delay one frame for opacity animation to happen
11460 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11461 }
11462
11463 setPosition(tile, tilePos);
11464
11465 // save tile in cache
11466 this._tiles[key] = {
11467 el: tile,
11468 coords: coords,
11469 current: true
11470 };
11471
11472 container.appendChild(tile);
11473 // @event tileloadstart: TileEvent
11474 // Fired when a tile is requested and starts loading.
11475 this.fire('tileloadstart', {
11476 tile: tile,
11477 coords: coords
11478 });
11479 },
11480
11481 _tileReady: function (coords, err, tile) {
11482 if (err) {
11483 // @event tileerror: TileErrorEvent
11484 // Fired when there is an error loading a tile.
11485 this.fire('tileerror', {
11486 error: err,
11487 tile: tile,
11488 coords: coords
11489 });
11490 }
11491
11492 var key = this._tileCoordsToKey(coords);
11493
11494 tile = this._tiles[key];
11495 if (!tile) { return; }
11496
11497 tile.loaded = +new Date();
11498 if (this._map._fadeAnimated) {
11499 setOpacity(tile.el, 0);
11500 cancelAnimFrame(this._fadeFrame);
11501 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11502 } else {
11503 tile.active = true;
11504 this._pruneTiles();
11505 }
11506
11507 if (!err) {
11508 addClass(tile.el, 'leaflet-tile-loaded');
11509
11510 // @event tileload: TileEvent
11511 // Fired when a tile loads.
11512 this.fire('tileload', {
11513 tile: tile.el,
11514 coords: coords
11515 });
11516 }
11517
11518 if (this._noTilesToLoad()) {
11519 this._loading = false;
11520 // @event load: Event
11521 // Fired when the grid layer loaded all visible tiles.
11522 this.fire('load');
11523
11524 if (ielt9 || !this._map._fadeAnimated) {
11525 requestAnimFrame(this._pruneTiles, this);
11526 } else {
11527 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11528 // to trigger a pruning.
11529 setTimeout(bind(this._pruneTiles, this), 250);
11530 }
11531 }
11532 },
11533
11534 _getTilePos: function (coords) {
11535 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11536 },
11537
11538 _wrapCoords: function (coords) {
11539 var newCoords = new Point(
11540 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11541 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11542 newCoords.z = coords.z;
11543 return newCoords;
11544 },
11545
11546 _pxBoundsToTileRange: function (bounds) {
11547 var tileSize = this.getTileSize();
11548 return new Bounds(
11549 bounds.min.unscaleBy(tileSize).floor(),
11550 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11551 },
11552
11553 _noTilesToLoad: function () {
11554 for (var key in this._tiles) {
11555 if (!this._tiles[key].loaded) { return false; }
11556 }
11557 return true;
11558 }
11559});
11560
11561// @factory L.gridLayer(options?: GridLayer options)
11562// Creates a new instance of GridLayer with the supplied options.
11563function gridLayer(options) {
11564 return new GridLayer(options);
11565}
11566
11567/*
11568 * @class TileLayer
11569 * @inherits GridLayer
11570 * @aka L.TileLayer
11571 * 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`.
11572 *
11573 * @example
11574 *
11575 * ```js
11576 * 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);
11577 * ```
11578 *
11579 * @section URL template
11580 * @example
11581 *
11582 * A string of the following form:
11583 *
11584 * ```
11585 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11586 * ```
11587 *
11588 * `{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.
11589 *
11590 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11591 *
11592 * ```
11593 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11594 * ```
11595 */
11596
11597
11598var TileLayer = GridLayer.extend({
11599
11600 // @section
11601 // @aka TileLayer options
11602 options: {
11603 // @option minZoom: Number = 0
11604 // The minimum zoom level down to which this layer will be displayed (inclusive).
11605 minZoom: 0,
11606
11607 // @option maxZoom: Number = 18
11608 // The maximum zoom level up to which this layer will be displayed (inclusive).
11609 maxZoom: 18,
11610
11611 // @option subdomains: String|String[] = 'abc'
11612 // 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.
11613 subdomains: 'abc',
11614
11615 // @option errorTileUrl: String = ''
11616 // URL to the tile image to show in place of the tile that failed to load.
11617 errorTileUrl: '',
11618
11619 // @option zoomOffset: Number = 0
11620 // The zoom number used in tile URLs will be offset with this value.
11621 zoomOffset: 0,
11622
11623 // @option tms: Boolean = false
11624 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11625 tms: false,
11626
11627 // @option zoomReverse: Boolean = false
11628 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11629 zoomReverse: false,
11630
11631 // @option detectRetina: Boolean = false
11632 // 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.
11633 detectRetina: false,
11634
11635 // @option crossOrigin: Boolean|String = false
11636 // Whether the crossOrigin attribute will be added to the tiles.
11637 // 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.
11638 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11639 crossOrigin: false
11640 },
11641
11642 initialize: function (url, options) {
11643
11644 this._url = url;
11645
11646 options = setOptions(this, options);
11647
11648 // detecting retina displays, adjusting tileSize and zoom levels
11649 if (options.detectRetina && retina && options.maxZoom > 0) {
11650
11651 options.tileSize = Math.floor(options.tileSize / 2);
11652
11653 if (!options.zoomReverse) {
11654 options.zoomOffset++;
11655 options.maxZoom--;
11656 } else {
11657 options.zoomOffset--;
11658 options.minZoom++;
11659 }
11660
11661 options.minZoom = Math.max(0, options.minZoom);
11662 }
11663
11664 if (typeof options.subdomains === 'string') {
11665 options.subdomains = options.subdomains.split('');
11666 }
11667
11668 // for https://github.com/Leaflet/Leaflet/issues/137
11669 if (!android) {
11670 this.on('tileunload', this._onTileRemove);
11671 }
11672 },
11673
11674 // @method setUrl(url: String, noRedraw?: Boolean): this
11675 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11676 // If the URL does not change, the layer will not be redrawn unless
11677 // the noRedraw parameter is set to false.
11678 setUrl: function (url, noRedraw) {
11679 if (this._url === url && noRedraw === undefined) {
11680 noRedraw = true;
11681 }
11682
11683 this._url = url;
11684
11685 if (!noRedraw) {
11686 this.redraw();
11687 }
11688 return this;
11689 },
11690
11691 // @method createTile(coords: Object, done?: Function): HTMLElement
11692 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11693 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11694 // callback is called when the tile has been loaded.
11695 createTile: function (coords, done) {
11696 var tile = document.createElement('img');
11697
11698 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11699 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11700
11701 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11702 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11703 }
11704
11705 /*
11706 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11707 http://www.w3.org/TR/WCAG20-TECHS/H67
11708 */
11709 tile.alt = '';
11710
11711 /*
11712 Set role="presentation" to force screen readers to ignore this
11713 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11714 */
11715 tile.setAttribute('role', 'presentation');
11716
11717 tile.src = this.getTileUrl(coords);
11718
11719 return tile;
11720 },
11721
11722 // @section Extension methods
11723 // @uninheritable
11724 // Layers extending `TileLayer` might reimplement the following method.
11725 // @method getTileUrl(coords: Object): String
11726 // Called only internally, returns the URL for a tile given its coordinates.
11727 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11728 getTileUrl: function (coords) {
11729 var data = {
11730 r: retina ? '@2x' : '',
11731 s: this._getSubdomain(coords),
11732 x: coords.x,
11733 y: coords.y,
11734 z: this._getZoomForUrl()
11735 };
11736 if (this._map && !this._map.options.crs.infinite) {
11737 var invertedY = this._globalTileRange.max.y - coords.y;
11738 if (this.options.tms) {
11739 data['y'] = invertedY;
11740 }
11741 data['-y'] = invertedY;
11742 }
11743
11744 return template(this._url, extend(data, this.options));
11745 },
11746
11747 _tileOnLoad: function (done, tile) {
11748 // For https://github.com/Leaflet/Leaflet/issues/3332
11749 if (ielt9) {
11750 setTimeout(bind(done, this, null, tile), 0);
11751 } else {
11752 done(null, tile);
11753 }
11754 },
11755
11756 _tileOnError: function (done, tile, e) {
11757 var errorUrl = this.options.errorTileUrl;
11758 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11759 tile.src = errorUrl;
11760 }
11761 done(e, tile);
11762 },
11763
11764 _onTileRemove: function (e) {
11765 e.tile.onload = null;
11766 },
11767
11768 _getZoomForUrl: function () {
11769 var zoom = this._tileZoom,
11770 maxZoom = this.options.maxZoom,
11771 zoomReverse = this.options.zoomReverse,
11772 zoomOffset = this.options.zoomOffset;
11773
11774 if (zoomReverse) {
11775 zoom = maxZoom - zoom;
11776 }
11777
11778 return zoom + zoomOffset;
11779 },
11780
11781 _getSubdomain: function (tilePoint) {
11782 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11783 return this.options.subdomains[index];
11784 },
11785
11786 // stops loading all tiles in the background layer
11787 _abortLoading: function () {
11788 var i, tile;
11789 for (i in this._tiles) {
11790 if (this._tiles[i].coords.z !== this._tileZoom) {
11791 tile = this._tiles[i].el;
11792
11793 tile.onload = falseFn;
11794 tile.onerror = falseFn;
11795
11796 if (!tile.complete) {
11797 tile.src = emptyImageUrl;
11798 remove(tile);
11799 delete this._tiles[i];
11800 }
11801 }
11802 }
11803 },
11804
11805 _removeTile: function (key) {
11806 var tile = this._tiles[key];
11807 if (!tile) { return; }
11808
11809 // Cancels any pending http requests associated with the tile
11810 // unless we're on Android's stock browser,
11811 // see https://github.com/Leaflet/Leaflet/issues/137
11812 if (!androidStock) {
11813 tile.el.setAttribute('src', emptyImageUrl);
11814 }
11815
11816 return GridLayer.prototype._removeTile.call(this, key);
11817 },
11818
11819 _tileReady: function (coords, err, tile) {
11820 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11821 return;
11822 }
11823
11824 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11825 }
11826});
11827
11828
11829// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11830// Instantiates a tile layer object given a `URL template` and optionally an options object.
11831
11832function tileLayer(url, options) {
11833 return new TileLayer(url, options);
11834}
11835
11836/*
11837 * @class TileLayer.WMS
11838 * @inherits TileLayer
11839 * @aka L.TileLayer.WMS
11840 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11841 *
11842 * @example
11843 *
11844 * ```js
11845 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11846 * layers: 'nexrad-n0r-900913',
11847 * format: 'image/png',
11848 * transparent: true,
11849 * attribution: "Weather data © 2012 IEM Nexrad"
11850 * });
11851 * ```
11852 */
11853
11854var TileLayerWMS = TileLayer.extend({
11855
11856 // @section
11857 // @aka TileLayer.WMS options
11858 // If any custom options not documented here are used, they will be sent to the
11859 // WMS server as extra parameters in each request URL. This can be useful for
11860 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11861 defaultWmsParams: {
11862 service: 'WMS',
11863 request: 'GetMap',
11864
11865 // @option layers: String = ''
11866 // **(required)** Comma-separated list of WMS layers to show.
11867 layers: '',
11868
11869 // @option styles: String = ''
11870 // Comma-separated list of WMS styles.
11871 styles: '',
11872
11873 // @option format: String = 'image/jpeg'
11874 // WMS image format (use `'image/png'` for layers with transparency).
11875 format: 'image/jpeg',
11876
11877 // @option transparent: Boolean = false
11878 // If `true`, the WMS service will return images with transparency.
11879 transparent: false,
11880
11881 // @option version: String = '1.1.1'
11882 // Version of the WMS service to use
11883 version: '1.1.1'
11884 },
11885
11886 options: {
11887 // @option crs: CRS = null
11888 // Coordinate Reference System to use for the WMS requests, defaults to
11889 // map CRS. Don't change this if you're not sure what it means.
11890 crs: null,
11891
11892 // @option uppercase: Boolean = false
11893 // If `true`, WMS request parameter keys will be uppercase.
11894 uppercase: false
11895 },
11896
11897 initialize: function (url, options) {
11898
11899 this._url = url;
11900
11901 var wmsParams = extend({}, this.defaultWmsParams);
11902
11903 // all keys that are not TileLayer options go to WMS params
11904 for (var i in options) {
11905 if (!(i in this.options)) {
11906 wmsParams[i] = options[i];
11907 }
11908 }
11909
11910 options = setOptions(this, options);
11911
11912 var realRetina = options.detectRetina && retina ? 2 : 1;
11913 var tileSize = this.getTileSize();
11914 wmsParams.width = tileSize.x * realRetina;
11915 wmsParams.height = tileSize.y * realRetina;
11916
11917 this.wmsParams = wmsParams;
11918 },
11919
11920 onAdd: function (map) {
11921
11922 this._crs = this.options.crs || map.options.crs;
11923 this._wmsVersion = parseFloat(this.wmsParams.version);
11924
11925 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11926 this.wmsParams[projectionKey] = this._crs.code;
11927
11928 TileLayer.prototype.onAdd.call(this, map);
11929 },
11930
11931 getTileUrl: function (coords) {
11932
11933 var tileBounds = this._tileCoordsToNwSe(coords),
11934 crs = this._crs,
11935 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11936 min = bounds.min,
11937 max = bounds.max,
11938 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11939 [min.y, min.x, max.y, max.x] :
11940 [min.x, min.y, max.x, max.y]).join(','),
11941 url = TileLayer.prototype.getTileUrl.call(this, coords);
11942 return url +
11943 getParamString(this.wmsParams, url, this.options.uppercase) +
11944 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11945 },
11946
11947 // @method setParams(params: Object, noRedraw?: Boolean): this
11948 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11949 setParams: function (params, noRedraw) {
11950
11951 extend(this.wmsParams, params);
11952
11953 if (!noRedraw) {
11954 this.redraw();
11955 }
11956
11957 return this;
11958 }
11959});
11960
11961
11962// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11963// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11964function tileLayerWMS(url, options) {
11965 return new TileLayerWMS(url, options);
11966}
11967
11968TileLayer.WMS = TileLayerWMS;
11969tileLayer.wms = tileLayerWMS;
11970
11971/*
11972 * @class Renderer
11973 * @inherits Layer
11974 * @aka L.Renderer
11975 *
11976 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11977 * DOM container of the renderer, its bounds, and its zoom animation.
11978 *
11979 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11980 * itself can be added or removed to the map. All paths use a renderer, which can
11981 * be implicit (the map will decide the type of renderer and use it automatically)
11982 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11983 *
11984 * Do not use this class directly, use `SVG` and `Canvas` instead.
11985 *
11986 * @event update: Event
11987 * Fired when the renderer updates its bounds, center and zoom, for example when
11988 * its map has moved
11989 */
11990
11991var Renderer = Layer.extend({
11992
11993 // @section
11994 // @aka Renderer options
11995 options: {
11996 // @option padding: Number = 0.1
11997 // How much to extend the clip area around the map view (relative to its size)
11998 // e.g. 0.1 would be 10% of map view in each direction
11999 padding: 0.1,
12000
12001 // @option tolerance: Number = 0
12002 // How much to extend click tolerance round a path/object on the map
12003 tolerance : 0
12004 },
12005
12006 initialize: function (options) {
12007 setOptions(this, options);
12008 stamp(this);
12009 this._layers = this._layers || {};
12010 },
12011
12012 onAdd: function () {
12013 if (!this._container) {
12014 this._initContainer(); // defined by renderer implementations
12015
12016 if (this._zoomAnimated) {
12017 addClass(this._container, 'leaflet-zoom-animated');
12018 }
12019 }
12020
12021 this.getPane().appendChild(this._container);
12022 this._update();
12023 this.on('update', this._updatePaths, this);
12024 },
12025
12026 onRemove: function () {
12027 this.off('update', this._updatePaths, this);
12028 this._destroyContainer();
12029 },
12030
12031 getEvents: function () {
12032 var events = {
12033 viewreset: this._reset,
12034 zoom: this._onZoom,
12035 moveend: this._update,
12036 zoomend: this._onZoomEnd
12037 };
12038 if (this._zoomAnimated) {
12039 events.zoomanim = this._onAnimZoom;
12040 }
12041 return events;
12042 },
12043
12044 _onAnimZoom: function (ev) {
12045 this._updateTransform(ev.center, ev.zoom);
12046 },
12047
12048 _onZoom: function () {
12049 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12050 },
12051
12052 _updateTransform: function (center, zoom) {
12053 var scale = this._map.getZoomScale(zoom, this._zoom),
12054 position = getPosition(this._container),
12055 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12056 currentCenterPoint = this._map.project(this._center, zoom),
12057 destCenterPoint = this._map.project(center, zoom),
12058 centerOffset = destCenterPoint.subtract(currentCenterPoint),
12059
12060 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12061
12062 if (any3d) {
12063 setTransform(this._container, topLeftOffset, scale);
12064 } else {
12065 setPosition(this._container, topLeftOffset);
12066 }
12067 },
12068
12069 _reset: function () {
12070 this._update();
12071 this._updateTransform(this._center, this._zoom);
12072
12073 for (var id in this._layers) {
12074 this._layers[id]._reset();
12075 }
12076 },
12077
12078 _onZoomEnd: function () {
12079 for (var id in this._layers) {
12080 this._layers[id]._project();
12081 }
12082 },
12083
12084 _updatePaths: function () {
12085 for (var id in this._layers) {
12086 this._layers[id]._update();
12087 }
12088 },
12089
12090 _update: function () {
12091 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12092 // Subclasses are responsible of firing the 'update' event.
12093 var p = this.options.padding,
12094 size = this._map.getSize(),
12095 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12096
12097 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12098
12099 this._center = this._map.getCenter();
12100 this._zoom = this._map.getZoom();
12101 }
12102});
12103
12104/*
12105 * @class Canvas
12106 * @inherits Renderer
12107 * @aka L.Canvas
12108 *
12109 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12110 * Inherits `Renderer`.
12111 *
12112 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12113 * available in all web browsers, notably IE8, and overlapping geometries might
12114 * not display properly in some edge cases.
12115 *
12116 * @example
12117 *
12118 * Use Canvas by default for all paths in the map:
12119 *
12120 * ```js
12121 * var map = L.map('map', {
12122 * renderer: L.canvas()
12123 * });
12124 * ```
12125 *
12126 * Use a Canvas renderer with extra padding for specific vector geometries:
12127 *
12128 * ```js
12129 * var map = L.map('map');
12130 * var myRenderer = L.canvas({ padding: 0.5 });
12131 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12132 * var circle = L.circle( center, { renderer: myRenderer } );
12133 * ```
12134 */
12135
12136var Canvas = Renderer.extend({
12137 getEvents: function () {
12138 var events = Renderer.prototype.getEvents.call(this);
12139 events.viewprereset = this._onViewPreReset;
12140 return events;
12141 },
12142
12143 _onViewPreReset: function () {
12144 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12145 this._postponeUpdatePaths = true;
12146 },
12147
12148 onAdd: function () {
12149 Renderer.prototype.onAdd.call(this);
12150
12151 // Redraw vectors since canvas is cleared upon removal,
12152 // in case of removing the renderer itself from the map.
12153 this._draw();
12154 },
12155
12156 _initContainer: function () {
12157 var container = this._container = document.createElement('canvas');
12158
12159 on(container, 'mousemove', this._onMouseMove, this);
12160 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12161 on(container, 'mouseout', this._handleMouseOut, this);
12162
12163 this._ctx = container.getContext('2d');
12164 },
12165
12166 _destroyContainer: function () {
12167 cancelAnimFrame(this._redrawRequest);
12168 delete this._ctx;
12169 remove(this._container);
12170 off(this._container);
12171 delete this._container;
12172 },
12173
12174 _updatePaths: function () {
12175 if (this._postponeUpdatePaths) { return; }
12176
12177 var layer;
12178 this._redrawBounds = null;
12179 for (var id in this._layers) {
12180 layer = this._layers[id];
12181 layer._update();
12182 }
12183 this._redraw();
12184 },
12185
12186 _update: function () {
12187 if (this._map._animatingZoom && this._bounds) { return; }
12188
12189 Renderer.prototype._update.call(this);
12190
12191 var b = this._bounds,
12192 container = this._container,
12193 size = b.getSize(),
12194 m = retina ? 2 : 1;
12195
12196 setPosition(container, b.min);
12197
12198 // set canvas size (also clearing it); use double size on retina
12199 container.width = m * size.x;
12200 container.height = m * size.y;
12201 container.style.width = size.x + 'px';
12202 container.style.height = size.y + 'px';
12203
12204 if (retina) {
12205 this._ctx.scale(2, 2);
12206 }
12207
12208 // translate so we use the same path coordinates after canvas element moves
12209 this._ctx.translate(-b.min.x, -b.min.y);
12210
12211 // Tell paths to redraw themselves
12212 this.fire('update');
12213 },
12214
12215 _reset: function () {
12216 Renderer.prototype._reset.call(this);
12217
12218 if (this._postponeUpdatePaths) {
12219 this._postponeUpdatePaths = false;
12220 this._updatePaths();
12221 }
12222 },
12223
12224 _initPath: function (layer) {
12225 this._updateDashArray(layer);
12226 this._layers[stamp(layer)] = layer;
12227
12228 var order = layer._order = {
12229 layer: layer,
12230 prev: this._drawLast,
12231 next: null
12232 };
12233 if (this._drawLast) { this._drawLast.next = order; }
12234 this._drawLast = order;
12235 this._drawFirst = this._drawFirst || this._drawLast;
12236 },
12237
12238 _addPath: function (layer) {
12239 this._requestRedraw(layer);
12240 },
12241
12242 _removePath: function (layer) {
12243 var order = layer._order;
12244 var next = order.next;
12245 var prev = order.prev;
12246
12247 if (next) {
12248 next.prev = prev;
12249 } else {
12250 this._drawLast = prev;
12251 }
12252 if (prev) {
12253 prev.next = next;
12254 } else {
12255 this._drawFirst = next;
12256 }
12257
12258 delete layer._order;
12259
12260 delete this._layers[stamp(layer)];
12261
12262 this._requestRedraw(layer);
12263 },
12264
12265 _updatePath: function (layer) {
12266 // Redraw the union of the layer's old pixel
12267 // bounds and the new pixel bounds.
12268 this._extendRedrawBounds(layer);
12269 layer._project();
12270 layer._update();
12271 // The redraw will extend the redraw bounds
12272 // with the new pixel bounds.
12273 this._requestRedraw(layer);
12274 },
12275
12276 _updateStyle: function (layer) {
12277 this._updateDashArray(layer);
12278 this._requestRedraw(layer);
12279 },
12280
12281 _updateDashArray: function (layer) {
12282 if (typeof layer.options.dashArray === 'string') {
12283 var parts = layer.options.dashArray.split(/[, ]+/),
12284 dashArray = [],
12285 dashValue,
12286 i;
12287 for (i = 0; i < parts.length; i++) {
12288 dashValue = Number(parts[i]);
12289 // Ignore dash array containing invalid lengths
12290 if (isNaN(dashValue)) { return; }
12291 dashArray.push(dashValue);
12292 }
12293 layer.options._dashArray = dashArray;
12294 } else {
12295 layer.options._dashArray = layer.options.dashArray;
12296 }
12297 },
12298
12299 _requestRedraw: function (layer) {
12300 if (!this._map) { return; }
12301
12302 this._extendRedrawBounds(layer);
12303 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12304 },
12305
12306 _extendRedrawBounds: function (layer) {
12307 if (layer._pxBounds) {
12308 var padding = (layer.options.weight || 0) + 1;
12309 this._redrawBounds = this._redrawBounds || new Bounds();
12310 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12311 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12312 }
12313 },
12314
12315 _redraw: function () {
12316 this._redrawRequest = null;
12317
12318 if (this._redrawBounds) {
12319 this._redrawBounds.min._floor();
12320 this._redrawBounds.max._ceil();
12321 }
12322
12323 this._clear(); // clear layers in redraw bounds
12324 this._draw(); // draw layers
12325
12326 this._redrawBounds = null;
12327 },
12328
12329 _clear: function () {
12330 var bounds = this._redrawBounds;
12331 if (bounds) {
12332 var size = bounds.getSize();
12333 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12334 } else {
12335 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12336 }
12337 },
12338
12339 _draw: function () {
12340 var layer, bounds = this._redrawBounds;
12341 this._ctx.save();
12342 if (bounds) {
12343 var size = bounds.getSize();
12344 this._ctx.beginPath();
12345 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12346 this._ctx.clip();
12347 }
12348
12349 this._drawing = true;
12350
12351 for (var order = this._drawFirst; order; order = order.next) {
12352 layer = order.layer;
12353 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12354 layer._updatePath();
12355 }
12356 }
12357
12358 this._drawing = false;
12359
12360 this._ctx.restore(); // Restore state before clipping.
12361 },
12362
12363 _updatePoly: function (layer, closed) {
12364 if (!this._drawing) { return; }
12365
12366 var i, j, len2, p,
12367 parts = layer._parts,
12368 len = parts.length,
12369 ctx = this._ctx;
12370
12371 if (!len) { return; }
12372
12373 ctx.beginPath();
12374
12375 for (i = 0; i < len; i++) {
12376 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12377 p = parts[i][j];
12378 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12379 }
12380 if (closed) {
12381 ctx.closePath();
12382 }
12383 }
12384
12385 this._fillStroke(ctx, layer);
12386
12387 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12388 },
12389
12390 _updateCircle: function (layer) {
12391
12392 if (!this._drawing || layer._empty()) { return; }
12393
12394 var p = layer._point,
12395 ctx = this._ctx,
12396 r = Math.max(Math.round(layer._radius), 1),
12397 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12398
12399 if (s !== 1) {
12400 ctx.save();
12401 ctx.scale(1, s);
12402 }
12403
12404 ctx.beginPath();
12405 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12406
12407 if (s !== 1) {
12408 ctx.restore();
12409 }
12410
12411 this._fillStroke(ctx, layer);
12412 },
12413
12414 _fillStroke: function (ctx, layer) {
12415 var options = layer.options;
12416
12417 if (options.fill) {
12418 ctx.globalAlpha = options.fillOpacity;
12419 ctx.fillStyle = options.fillColor || options.color;
12420 ctx.fill(options.fillRule || 'evenodd');
12421 }
12422
12423 if (options.stroke && options.weight !== 0) {
12424 if (ctx.setLineDash) {
12425 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12426 }
12427 ctx.globalAlpha = options.opacity;
12428 ctx.lineWidth = options.weight;
12429 ctx.strokeStyle = options.color;
12430 ctx.lineCap = options.lineCap;
12431 ctx.lineJoin = options.lineJoin;
12432 ctx.stroke();
12433 }
12434 },
12435
12436 // Canvas obviously doesn't have mouse events for individual drawn objects,
12437 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12438
12439 _onClick: function (e) {
12440 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12441
12442 for (var order = this._drawFirst; order; order = order.next) {
12443 layer = order.layer;
12444 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12445 clickedLayer = layer;
12446 }
12447 }
12448 if (clickedLayer) {
12449 fakeStop(e);
12450 this._fireEvent([clickedLayer], e);
12451 }
12452 },
12453
12454 _onMouseMove: function (e) {
12455 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12456
12457 var point = this._map.mouseEventToLayerPoint(e);
12458 this._handleMouseHover(e, point);
12459 },
12460
12461
12462 _handleMouseOut: function (e) {
12463 var layer = this._hoveredLayer;
12464 if (layer) {
12465 // if we're leaving the layer, fire mouseout
12466 removeClass(this._container, 'leaflet-interactive');
12467 this._fireEvent([layer], e, 'mouseout');
12468 this._hoveredLayer = null;
12469 this._mouseHoverThrottled = false;
12470 }
12471 },
12472
12473 _handleMouseHover: function (e, point) {
12474 if (this._mouseHoverThrottled) {
12475 return;
12476 }
12477
12478 var layer, candidateHoveredLayer;
12479
12480 for (var order = this._drawFirst; order; order = order.next) {
12481 layer = order.layer;
12482 if (layer.options.interactive && layer._containsPoint(point)) {
12483 candidateHoveredLayer = layer;
12484 }
12485 }
12486
12487 if (candidateHoveredLayer !== this._hoveredLayer) {
12488 this._handleMouseOut(e);
12489
12490 if (candidateHoveredLayer) {
12491 addClass(this._container, 'leaflet-interactive'); // change cursor
12492 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12493 this._hoveredLayer = candidateHoveredLayer;
12494 }
12495 }
12496
12497 if (this._hoveredLayer) {
12498 this._fireEvent([this._hoveredLayer], e);
12499 }
12500
12501 this._mouseHoverThrottled = true;
12502 setTimeout(L.bind(function () {
12503 this._mouseHoverThrottled = false;
12504 }, this), 32);
12505 },
12506
12507 _fireEvent: function (layers, e, type) {
12508 this._map._fireDOMEvent(e, type || e.type, layers);
12509 },
12510
12511 _bringToFront: function (layer) {
12512 var order = layer._order;
12513
12514 if (!order) { return; }
12515
12516 var next = order.next;
12517 var prev = order.prev;
12518
12519 if (next) {
12520 next.prev = prev;
12521 } else {
12522 // Already last
12523 return;
12524 }
12525 if (prev) {
12526 prev.next = next;
12527 } else if (next) {
12528 // Update first entry unless this is the
12529 // single entry
12530 this._drawFirst = next;
12531 }
12532
12533 order.prev = this._drawLast;
12534 this._drawLast.next = order;
12535
12536 order.next = null;
12537 this._drawLast = order;
12538
12539 this._requestRedraw(layer);
12540 },
12541
12542 _bringToBack: function (layer) {
12543 var order = layer._order;
12544
12545 if (!order) { return; }
12546
12547 var next = order.next;
12548 var prev = order.prev;
12549
12550 if (prev) {
12551 prev.next = next;
12552 } else {
12553 // Already first
12554 return;
12555 }
12556 if (next) {
12557 next.prev = prev;
12558 } else if (prev) {
12559 // Update last entry unless this is the
12560 // single entry
12561 this._drawLast = prev;
12562 }
12563
12564 order.prev = null;
12565
12566 order.next = this._drawFirst;
12567 this._drawFirst.prev = order;
12568 this._drawFirst = order;
12569
12570 this._requestRedraw(layer);
12571 }
12572});
12573
12574// @factory L.canvas(options?: Renderer options)
12575// Creates a Canvas renderer with the given options.
12576function canvas$1(options) {
12577 return canvas ? new Canvas(options) : null;
12578}
12579
12580/*
12581 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12582 */
12583
12584
12585var vmlCreate = (function () {
12586 try {
12587 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12588 return function (name) {
12589 return document.createElement('<lvml:' + name + ' class="lvml">');
12590 };
12591 } catch (e) {
12592 return function (name) {
12593 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12594 };
12595 }
12596})();
12597
12598
12599/*
12600 * @class SVG
12601 *
12602 *
12603 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12604 * with old versions of Internet Explorer.
12605 */
12606
12607// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12608var vmlMixin = {
12609
12610 _initContainer: function () {
12611 this._container = create$1('div', 'leaflet-vml-container');
12612 },
12613
12614 _update: function () {
12615 if (this._map._animatingZoom) { return; }
12616 Renderer.prototype._update.call(this);
12617 this.fire('update');
12618 },
12619
12620 _initPath: function (layer) {
12621 var container = layer._container = vmlCreate('shape');
12622
12623 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12624
12625 container.coordsize = '1 1';
12626
12627 layer._path = vmlCreate('path');
12628 container.appendChild(layer._path);
12629
12630 this._updateStyle(layer);
12631 this._layers[stamp(layer)] = layer;
12632 },
12633
12634 _addPath: function (layer) {
12635 var container = layer._container;
12636 this._container.appendChild(container);
12637
12638 if (layer.options.interactive) {
12639 layer.addInteractiveTarget(container);
12640 }
12641 },
12642
12643 _removePath: function (layer) {
12644 var container = layer._container;
12645 remove(container);
12646 layer.removeInteractiveTarget(container);
12647 delete this._layers[stamp(layer)];
12648 },
12649
12650 _updateStyle: function (layer) {
12651 var stroke = layer._stroke,
12652 fill = layer._fill,
12653 options = layer.options,
12654 container = layer._container;
12655
12656 container.stroked = !!options.stroke;
12657 container.filled = !!options.fill;
12658
12659 if (options.stroke) {
12660 if (!stroke) {
12661 stroke = layer._stroke = vmlCreate('stroke');
12662 }
12663 container.appendChild(stroke);
12664 stroke.weight = options.weight + 'px';
12665 stroke.color = options.color;
12666 stroke.opacity = options.opacity;
12667
12668 if (options.dashArray) {
12669 stroke.dashStyle = isArray(options.dashArray) ?
12670 options.dashArray.join(' ') :
12671 options.dashArray.replace(/( *, *)/g, ' ');
12672 } else {
12673 stroke.dashStyle = '';
12674 }
12675 stroke.endcap = options.lineCap.replace('butt', 'flat');
12676 stroke.joinstyle = options.lineJoin;
12677
12678 } else if (stroke) {
12679 container.removeChild(stroke);
12680 layer._stroke = null;
12681 }
12682
12683 if (options.fill) {
12684 if (!fill) {
12685 fill = layer._fill = vmlCreate('fill');
12686 }
12687 container.appendChild(fill);
12688 fill.color = options.fillColor || options.color;
12689 fill.opacity = options.fillOpacity;
12690
12691 } else if (fill) {
12692 container.removeChild(fill);
12693 layer._fill = null;
12694 }
12695 },
12696
12697 _updateCircle: function (layer) {
12698 var p = layer._point.round(),
12699 r = Math.round(layer._radius),
12700 r2 = Math.round(layer._radiusY || r);
12701
12702 this._setPath(layer, layer._empty() ? 'M0 0' :
12703 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12704 },
12705
12706 _setPath: function (layer, path) {
12707 layer._path.v = path;
12708 },
12709
12710 _bringToFront: function (layer) {
12711 toFront(layer._container);
12712 },
12713
12714 _bringToBack: function (layer) {
12715 toBack(layer._container);
12716 }
12717};
12718
12719var create$2 = vml ? vmlCreate : svgCreate;
12720
12721/*
12722 * @class SVG
12723 * @inherits Renderer
12724 * @aka L.SVG
12725 *
12726 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12727 * Inherits `Renderer`.
12728 *
12729 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12730 * available in all web browsers, notably Android 2.x and 3.x.
12731 *
12732 * Although SVG is not available on IE7 and IE8, these browsers support
12733 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12734 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12735 * this case.
12736 *
12737 * @example
12738 *
12739 * Use SVG by default for all paths in the map:
12740 *
12741 * ```js
12742 * var map = L.map('map', {
12743 * renderer: L.svg()
12744 * });
12745 * ```
12746 *
12747 * Use a SVG renderer with extra padding for specific vector geometries:
12748 *
12749 * ```js
12750 * var map = L.map('map');
12751 * var myRenderer = L.svg({ padding: 0.5 });
12752 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12753 * var circle = L.circle( center, { renderer: myRenderer } );
12754 * ```
12755 */
12756
12757var SVG = Renderer.extend({
12758
12759 getEvents: function () {
12760 var events = Renderer.prototype.getEvents.call(this);
12761 events.zoomstart = this._onZoomStart;
12762 return events;
12763 },
12764
12765 _initContainer: function () {
12766 this._container = create$2('svg');
12767
12768 // makes it possible to click through svg root; we'll reset it back in individual paths
12769 this._container.setAttribute('pointer-events', 'none');
12770
12771 this._rootGroup = create$2('g');
12772 this._container.appendChild(this._rootGroup);
12773 },
12774
12775 _destroyContainer: function () {
12776 remove(this._container);
12777 off(this._container);
12778 delete this._container;
12779 delete this._rootGroup;
12780 delete this._svgSize;
12781 },
12782
12783 _onZoomStart: function () {
12784 // Drag-then-pinch interactions might mess up the center and zoom.
12785 // In this case, the easiest way to prevent this is re-do the renderer
12786 // bounds and padding when the zooming starts.
12787 this._update();
12788 },
12789
12790 _update: function () {
12791 if (this._map._animatingZoom && this._bounds) { return; }
12792
12793 Renderer.prototype._update.call(this);
12794
12795 var b = this._bounds,
12796 size = b.getSize(),
12797 container = this._container;
12798
12799 // set size of svg-container if changed
12800 if (!this._svgSize || !this._svgSize.equals(size)) {
12801 this._svgSize = size;
12802 container.setAttribute('width', size.x);
12803 container.setAttribute('height', size.y);
12804 }
12805
12806 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12807 setPosition(container, b.min);
12808 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12809
12810 this.fire('update');
12811 },
12812
12813 // methods below are called by vector layers implementations
12814
12815 _initPath: function (layer) {
12816 var path = layer._path = create$2('path');
12817
12818 // @namespace Path
12819 // @option className: String = null
12820 // Custom class name set on an element. Only for SVG renderer.
12821 if (layer.options.className) {
12822 addClass(path, layer.options.className);
12823 }
12824
12825 if (layer.options.interactive) {
12826 addClass(path, 'leaflet-interactive');
12827 }
12828
12829 this._updateStyle(layer);
12830 this._layers[stamp(layer)] = layer;
12831 },
12832
12833 _addPath: function (layer) {
12834 if (!this._rootGroup) { this._initContainer(); }
12835 this._rootGroup.appendChild(layer._path);
12836 layer.addInteractiveTarget(layer._path);
12837 },
12838
12839 _removePath: function (layer) {
12840 remove(layer._path);
12841 layer.removeInteractiveTarget(layer._path);
12842 delete this._layers[stamp(layer)];
12843 },
12844
12845 _updatePath: function (layer) {
12846 layer._project();
12847 layer._update();
12848 },
12849
12850 _updateStyle: function (layer) {
12851 var path = layer._path,
12852 options = layer.options;
12853
12854 if (!path) { return; }
12855
12856 if (options.stroke) {
12857 path.setAttribute('stroke', options.color);
12858 path.setAttribute('stroke-opacity', options.opacity);
12859 path.setAttribute('stroke-width', options.weight);
12860 path.setAttribute('stroke-linecap', options.lineCap);
12861 path.setAttribute('stroke-linejoin', options.lineJoin);
12862
12863 if (options.dashArray) {
12864 path.setAttribute('stroke-dasharray', options.dashArray);
12865 } else {
12866 path.removeAttribute('stroke-dasharray');
12867 }
12868
12869 if (options.dashOffset) {
12870 path.setAttribute('stroke-dashoffset', options.dashOffset);
12871 } else {
12872 path.removeAttribute('stroke-dashoffset');
12873 }
12874 } else {
12875 path.setAttribute('stroke', 'none');
12876 }
12877
12878 if (options.fill) {
12879 path.setAttribute('fill', options.fillColor || options.color);
12880 path.setAttribute('fill-opacity', options.fillOpacity);
12881 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12882 } else {
12883 path.setAttribute('fill', 'none');
12884 }
12885 },
12886
12887 _updatePoly: function (layer, closed) {
12888 this._setPath(layer, pointsToPath(layer._parts, closed));
12889 },
12890
12891 _updateCircle: function (layer) {
12892 var p = layer._point,
12893 r = Math.max(Math.round(layer._radius), 1),
12894 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12895 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12896
12897 // drawing a circle with two half-arcs
12898 var d = layer._empty() ? 'M0 0' :
12899 'M' + (p.x - r) + ',' + p.y +
12900 arc + (r * 2) + ',0 ' +
12901 arc + (-r * 2) + ',0 ';
12902
12903 this._setPath(layer, d);
12904 },
12905
12906 _setPath: function (layer, path) {
12907 layer._path.setAttribute('d', path);
12908 },
12909
12910 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12911 _bringToFront: function (layer) {
12912 toFront(layer._path);
12913 },
12914
12915 _bringToBack: function (layer) {
12916 toBack(layer._path);
12917 }
12918});
12919
12920if (vml) {
12921 SVG.include(vmlMixin);
12922}
12923
12924// @namespace SVG
12925// @factory L.svg(options?: Renderer options)
12926// Creates a SVG renderer with the given options.
12927function svg$1(options) {
12928 return svg || vml ? new SVG(options) : null;
12929}
12930
12931Map.include({
12932 // @namespace Map; @method getRenderer(layer: Path): Renderer
12933 // Returns the instance of `Renderer` that should be used to render the given
12934 // `Path`. It will ensure that the `renderer` options of the map and paths
12935 // are respected, and that the renderers do exist on the map.
12936 getRenderer: function (layer) {
12937 // @namespace Path; @option renderer: Renderer
12938 // Use this specific instance of `Renderer` for this path. Takes
12939 // precedence over the map's [default renderer](#map-renderer).
12940 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12941
12942 if (!renderer) {
12943 renderer = this._renderer = this._createRenderer();
12944 }
12945
12946 if (!this.hasLayer(renderer)) {
12947 this.addLayer(renderer);
12948 }
12949 return renderer;
12950 },
12951
12952 _getPaneRenderer: function (name) {
12953 if (name === 'overlayPane' || name === undefined) {
12954 return false;
12955 }
12956
12957 var renderer = this._paneRenderers[name];
12958 if (renderer === undefined) {
12959 renderer = this._createRenderer({pane: name});
12960 this._paneRenderers[name] = renderer;
12961 }
12962 return renderer;
12963 },
12964
12965 _createRenderer: function (options) {
12966 // @namespace Map; @option preferCanvas: Boolean = false
12967 // Whether `Path`s should be rendered on a `Canvas` renderer.
12968 // By default, all `Path`s are rendered in a `SVG` renderer.
12969 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12970 }
12971});
12972
12973/*
12974 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12975 */
12976
12977/*
12978 * @class Rectangle
12979 * @aka L.Rectangle
12980 * @inherits Polygon
12981 *
12982 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12983 *
12984 * @example
12985 *
12986 * ```js
12987 * // define rectangle geographical bounds
12988 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12989 *
12990 * // create an orange rectangle
12991 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12992 *
12993 * // zoom the map to the rectangle bounds
12994 * map.fitBounds(bounds);
12995 * ```
12996 *
12997 */
12998
12999
13000var Rectangle = Polygon.extend({
13001 initialize: function (latLngBounds, options) {
13002 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13003 },
13004
13005 // @method setBounds(latLngBounds: LatLngBounds): this
13006 // Redraws the rectangle with the passed bounds.
13007 setBounds: function (latLngBounds) {
13008 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13009 },
13010
13011 _boundsToLatLngs: function (latLngBounds) {
13012 latLngBounds = toLatLngBounds(latLngBounds);
13013 return [
13014 latLngBounds.getSouthWest(),
13015 latLngBounds.getNorthWest(),
13016 latLngBounds.getNorthEast(),
13017 latLngBounds.getSouthEast()
13018 ];
13019 }
13020});
13021
13022
13023// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13024function rectangle(latLngBounds, options) {
13025 return new Rectangle(latLngBounds, options);
13026}
13027
13028SVG.create = create$2;
13029SVG.pointsToPath = pointsToPath;
13030
13031GeoJSON.geometryToLayer = geometryToLayer;
13032GeoJSON.coordsToLatLng = coordsToLatLng;
13033GeoJSON.coordsToLatLngs = coordsToLatLngs;
13034GeoJSON.latLngToCoords = latLngToCoords;
13035GeoJSON.latLngsToCoords = latLngsToCoords;
13036GeoJSON.getFeature = getFeature;
13037GeoJSON.asFeature = asFeature;
13038
13039/*
13040 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13041 * (zoom to a selected bounding box), enabled by default.
13042 */
13043
13044// @namespace Map
13045// @section Interaction Options
13046Map.mergeOptions({
13047 // @option boxZoom: Boolean = true
13048 // Whether the map can be zoomed to a rectangular area specified by
13049 // dragging the mouse while pressing the shift key.
13050 boxZoom: true
13051});
13052
13053var BoxZoom = Handler.extend({
13054 initialize: function (map) {
13055 this._map = map;
13056 this._container = map._container;
13057 this._pane = map._panes.overlayPane;
13058 this._resetStateTimeout = 0;
13059 map.on('unload', this._destroy, this);
13060 },
13061
13062 addHooks: function () {
13063 on(this._container, 'mousedown', this._onMouseDown, this);
13064 },
13065
13066 removeHooks: function () {
13067 off(this._container, 'mousedown', this._onMouseDown, this);
13068 },
13069
13070 moved: function () {
13071 return this._moved;
13072 },
13073
13074 _destroy: function () {
13075 remove(this._pane);
13076 delete this._pane;
13077 },
13078
13079 _resetState: function () {
13080 this._resetStateTimeout = 0;
13081 this._moved = false;
13082 },
13083
13084 _clearDeferredResetState: function () {
13085 if (this._resetStateTimeout !== 0) {
13086 clearTimeout(this._resetStateTimeout);
13087 this._resetStateTimeout = 0;
13088 }
13089 },
13090
13091 _onMouseDown: function (e) {
13092 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13093
13094 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13095 // will interrupt the interaction and orphan a box element in the container.
13096 this._clearDeferredResetState();
13097 this._resetState();
13098
13099 disableTextSelection();
13100 disableImageDrag();
13101
13102 this._startPoint = this._map.mouseEventToContainerPoint(e);
13103
13104 on(document, {
13105 contextmenu: stop,
13106 mousemove: this._onMouseMove,
13107 mouseup: this._onMouseUp,
13108 keydown: this._onKeyDown
13109 }, this);
13110 },
13111
13112 _onMouseMove: function (e) {
13113 if (!this._moved) {
13114 this._moved = true;
13115
13116 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13117 addClass(this._container, 'leaflet-crosshair');
13118
13119 this._map.fire('boxzoomstart');
13120 }
13121
13122 this._point = this._map.mouseEventToContainerPoint(e);
13123
13124 var bounds = new Bounds(this._point, this._startPoint),
13125 size = bounds.getSize();
13126
13127 setPosition(this._box, bounds.min);
13128
13129 this._box.style.width = size.x + 'px';
13130 this._box.style.height = size.y + 'px';
13131 },
13132
13133 _finish: function () {
13134 if (this._moved) {
13135 remove(this._box);
13136 removeClass(this._container, 'leaflet-crosshair');
13137 }
13138
13139 enableTextSelection();
13140 enableImageDrag();
13141
13142 off(document, {
13143 contextmenu: stop,
13144 mousemove: this._onMouseMove,
13145 mouseup: this._onMouseUp,
13146 keydown: this._onKeyDown
13147 }, this);
13148 },
13149
13150 _onMouseUp: function (e) {
13151 if ((e.which !== 1) && (e.button !== 1)) { return; }
13152
13153 this._finish();
13154
13155 if (!this._moved) { return; }
13156 // Postpone to next JS tick so internal click event handling
13157 // still see it as "moved".
13158 this._clearDeferredResetState();
13159 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13160
13161 var bounds = new LatLngBounds(
13162 this._map.containerPointToLatLng(this._startPoint),
13163 this._map.containerPointToLatLng(this._point));
13164
13165 this._map
13166 .fitBounds(bounds)
13167 .fire('boxzoomend', {boxZoomBounds: bounds});
13168 },
13169
13170 _onKeyDown: function (e) {
13171 if (e.keyCode === 27) {
13172 this._finish();
13173 }
13174 }
13175});
13176
13177// @section Handlers
13178// @property boxZoom: Handler
13179// Box (shift-drag with mouse) zoom handler.
13180Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13181
13182/*
13183 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13184 */
13185
13186// @namespace Map
13187// @section Interaction Options
13188
13189Map.mergeOptions({
13190 // @option doubleClickZoom: Boolean|String = true
13191 // Whether the map can be zoomed in by double clicking on it and
13192 // zoomed out by double clicking while holding shift. If passed
13193 // `'center'`, double-click zoom will zoom to the center of the
13194 // view regardless of where the mouse was.
13195 doubleClickZoom: true
13196});
13197
13198var DoubleClickZoom = Handler.extend({
13199 addHooks: function () {
13200 this._map.on('dblclick', this._onDoubleClick, this);
13201 },
13202
13203 removeHooks: function () {
13204 this._map.off('dblclick', this._onDoubleClick, this);
13205 },
13206
13207 _onDoubleClick: function (e) {
13208 var map = this._map,
13209 oldZoom = map.getZoom(),
13210 delta = map.options.zoomDelta,
13211 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13212
13213 if (map.options.doubleClickZoom === 'center') {
13214 map.setZoom(zoom);
13215 } else {
13216 map.setZoomAround(e.containerPoint, zoom);
13217 }
13218 }
13219});
13220
13221// @section Handlers
13222//
13223// Map properties include interaction handlers that allow you to control
13224// interaction behavior in runtime, enabling or disabling certain features such
13225// as dragging or touch zoom (see `Handler` methods). For example:
13226//
13227// ```js
13228// map.doubleClickZoom.disable();
13229// ```
13230//
13231// @property doubleClickZoom: Handler
13232// Double click zoom handler.
13233Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13234
13235/*
13236 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13237 */
13238
13239// @namespace Map
13240// @section Interaction Options
13241Map.mergeOptions({
13242 // @option dragging: Boolean = true
13243 // Whether the map be draggable with mouse/touch or not.
13244 dragging: true,
13245
13246 // @section Panning Inertia Options
13247 // @option inertia: Boolean = *
13248 // If enabled, panning of the map will have an inertia effect where
13249 // the map builds momentum while dragging and continues moving in
13250 // the same direction for some time. Feels especially nice on touch
13251 // devices. Enabled by default unless running on old Android devices.
13252 inertia: !android23,
13253
13254 // @option inertiaDeceleration: Number = 3000
13255 // The rate with which the inertial movement slows down, in pixels/second².
13256 inertiaDeceleration: 3400, // px/s^2
13257
13258 // @option inertiaMaxSpeed: Number = Infinity
13259 // Max speed of the inertial movement, in pixels/second.
13260 inertiaMaxSpeed: Infinity, // px/s
13261
13262 // @option easeLinearity: Number = 0.2
13263 easeLinearity: 0.2,
13264
13265 // TODO refactor, move to CRS
13266 // @option worldCopyJump: Boolean = false
13267 // With this option enabled, the map tracks when you pan to another "copy"
13268 // of the world and seamlessly jumps to the original one so that all overlays
13269 // like markers and vector layers are still visible.
13270 worldCopyJump: false,
13271
13272 // @option maxBoundsViscosity: Number = 0.0
13273 // If `maxBounds` is set, this option will control how solid the bounds
13274 // are when dragging the map around. The default value of `0.0` allows the
13275 // user to drag outside the bounds at normal speed, higher values will
13276 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13277 // solid, preventing the user from dragging outside the bounds.
13278 maxBoundsViscosity: 0.0
13279});
13280
13281var Drag = Handler.extend({
13282 addHooks: function () {
13283 if (!this._draggable) {
13284 var map = this._map;
13285
13286 this._draggable = new Draggable(map._mapPane, map._container);
13287
13288 this._draggable.on({
13289 dragstart: this._onDragStart,
13290 drag: this._onDrag,
13291 dragend: this._onDragEnd
13292 }, this);
13293
13294 this._draggable.on('predrag', this._onPreDragLimit, this);
13295 if (map.options.worldCopyJump) {
13296 this._draggable.on('predrag', this._onPreDragWrap, this);
13297 map.on('zoomend', this._onZoomEnd, this);
13298
13299 map.whenReady(this._onZoomEnd, this);
13300 }
13301 }
13302 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13303 this._draggable.enable();
13304 this._positions = [];
13305 this._times = [];
13306 },
13307
13308 removeHooks: function () {
13309 removeClass(this._map._container, 'leaflet-grab');
13310 removeClass(this._map._container, 'leaflet-touch-drag');
13311 this._draggable.disable();
13312 },
13313
13314 moved: function () {
13315 return this._draggable && this._draggable._moved;
13316 },
13317
13318 moving: function () {
13319 return this._draggable && this._draggable._moving;
13320 },
13321
13322 _onDragStart: function () {
13323 var map = this._map;
13324
13325 map._stop();
13326 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13327 var bounds = toLatLngBounds(this._map.options.maxBounds);
13328
13329 this._offsetLimit = toBounds(
13330 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13331 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13332 .add(this._map.getSize()));
13333
13334 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13335 } else {
13336 this._offsetLimit = null;
13337 }
13338
13339 map
13340 .fire('movestart')
13341 .fire('dragstart');
13342
13343 if (map.options.inertia) {
13344 this._positions = [];
13345 this._times = [];
13346 }
13347 },
13348
13349 _onDrag: function (e) {
13350 if (this._map.options.inertia) {
13351 var time = this._lastTime = +new Date(),
13352 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13353
13354 this._positions.push(pos);
13355 this._times.push(time);
13356
13357 this._prunePositions(time);
13358 }
13359
13360 this._map
13361 .fire('move', e)
13362 .fire('drag', e);
13363 },
13364
13365 _prunePositions: function (time) {
13366 while (this._positions.length > 1 && time - this._times[0] > 50) {
13367 this._positions.shift();
13368 this._times.shift();
13369 }
13370 },
13371
13372 _onZoomEnd: function () {
13373 var pxCenter = this._map.getSize().divideBy(2),
13374 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13375
13376 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13377 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13378 },
13379
13380 _viscousLimit: function (value, threshold) {
13381 return value - (value - threshold) * this._viscosity;
13382 },
13383
13384 _onPreDragLimit: function () {
13385 if (!this._viscosity || !this._offsetLimit) { return; }
13386
13387 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13388
13389 var limit = this._offsetLimit;
13390 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13391 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13392 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13393 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13394
13395 this._draggable._newPos = this._draggable._startPos.add(offset);
13396 },
13397
13398 _onPreDragWrap: function () {
13399 // TODO refactor to be able to adjust map pane position after zoom
13400 var worldWidth = this._worldWidth,
13401 halfWidth = Math.round(worldWidth / 2),
13402 dx = this._initialWorldOffset,
13403 x = this._draggable._newPos.x,
13404 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13405 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13406 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13407
13408 this._draggable._absPos = this._draggable._newPos.clone();
13409 this._draggable._newPos.x = newX;
13410 },
13411
13412 _onDragEnd: function (e) {
13413 var map = this._map,
13414 options = map.options,
13415
13416 noInertia = !options.inertia || this._times.length < 2;
13417
13418 map.fire('dragend', e);
13419
13420 if (noInertia) {
13421 map.fire('moveend');
13422
13423 } else {
13424 this._prunePositions(+new Date());
13425
13426 var direction = this._lastPos.subtract(this._positions[0]),
13427 duration = (this._lastTime - this._times[0]) / 1000,
13428 ease = options.easeLinearity,
13429
13430 speedVector = direction.multiplyBy(ease / duration),
13431 speed = speedVector.distanceTo([0, 0]),
13432
13433 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13434 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13435
13436 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13437 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13438
13439 if (!offset.x && !offset.y) {
13440 map.fire('moveend');
13441
13442 } else {
13443 offset = map._limitOffset(offset, map.options.maxBounds);
13444
13445 requestAnimFrame(function () {
13446 map.panBy(offset, {
13447 duration: decelerationDuration,
13448 easeLinearity: ease,
13449 noMoveStart: true,
13450 animate: true
13451 });
13452 });
13453 }
13454 }
13455 }
13456});
13457
13458// @section Handlers
13459// @property dragging: Handler
13460// Map dragging handler (by both mouse and touch).
13461Map.addInitHook('addHandler', 'dragging', Drag);
13462
13463/*
13464 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13465 */
13466
13467// @namespace Map
13468// @section Keyboard Navigation Options
13469Map.mergeOptions({
13470 // @option keyboard: Boolean = true
13471 // Makes the map focusable and allows users to navigate the map with keyboard
13472 // arrows and `+`/`-` keys.
13473 keyboard: true,
13474
13475 // @option keyboardPanDelta: Number = 80
13476 // Amount of pixels to pan when pressing an arrow key.
13477 keyboardPanDelta: 80
13478});
13479
13480var Keyboard = Handler.extend({
13481
13482 keyCodes: {
13483 left: [37],
13484 right: [39],
13485 down: [40],
13486 up: [38],
13487 zoomIn: [187, 107, 61, 171],
13488 zoomOut: [189, 109, 54, 173]
13489 },
13490
13491 initialize: function (map) {
13492 this._map = map;
13493
13494 this._setPanDelta(map.options.keyboardPanDelta);
13495 this._setZoomDelta(map.options.zoomDelta);
13496 },
13497
13498 addHooks: function () {
13499 var container = this._map._container;
13500
13501 // make the container focusable by tabbing
13502 if (container.tabIndex <= 0) {
13503 container.tabIndex = '0';
13504 }
13505
13506 on(container, {
13507 focus: this._onFocus,
13508 blur: this._onBlur,
13509 mousedown: this._onMouseDown
13510 }, this);
13511
13512 this._map.on({
13513 focus: this._addHooks,
13514 blur: this._removeHooks
13515 }, this);
13516 },
13517
13518 removeHooks: function () {
13519 this._removeHooks();
13520
13521 off(this._map._container, {
13522 focus: this._onFocus,
13523 blur: this._onBlur,
13524 mousedown: this._onMouseDown
13525 }, this);
13526
13527 this._map.off({
13528 focus: this._addHooks,
13529 blur: this._removeHooks
13530 }, this);
13531 },
13532
13533 _onMouseDown: function () {
13534 if (this._focused) { return; }
13535
13536 var body = document.body,
13537 docEl = document.documentElement,
13538 top = body.scrollTop || docEl.scrollTop,
13539 left = body.scrollLeft || docEl.scrollLeft;
13540
13541 this._map._container.focus();
13542
13543 window.scrollTo(left, top);
13544 },
13545
13546 _onFocus: function () {
13547 this._focused = true;
13548 this._map.fire('focus');
13549 },
13550
13551 _onBlur: function () {
13552 this._focused = false;
13553 this._map.fire('blur');
13554 },
13555
13556 _setPanDelta: function (panDelta) {
13557 var keys = this._panKeys = {},
13558 codes = this.keyCodes,
13559 i, len;
13560
13561 for (i = 0, len = codes.left.length; i < len; i++) {
13562 keys[codes.left[i]] = [-1 * panDelta, 0];
13563 }
13564 for (i = 0, len = codes.right.length; i < len; i++) {
13565 keys[codes.right[i]] = [panDelta, 0];
13566 }
13567 for (i = 0, len = codes.down.length; i < len; i++) {
13568 keys[codes.down[i]] = [0, panDelta];
13569 }
13570 for (i = 0, len = codes.up.length; i < len; i++) {
13571 keys[codes.up[i]] = [0, -1 * panDelta];
13572 }
13573 },
13574
13575 _setZoomDelta: function (zoomDelta) {
13576 var keys = this._zoomKeys = {},
13577 codes = this.keyCodes,
13578 i, len;
13579
13580 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13581 keys[codes.zoomIn[i]] = zoomDelta;
13582 }
13583 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13584 keys[codes.zoomOut[i]] = -zoomDelta;
13585 }
13586 },
13587
13588 _addHooks: function () {
13589 on(document, 'keydown', this._onKeyDown, this);
13590 },
13591
13592 _removeHooks: function () {
13593 off(document, 'keydown', this._onKeyDown, this);
13594 },
13595
13596 _onKeyDown: function (e) {
13597 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13598
13599 var key = e.keyCode,
13600 map = this._map,
13601 offset;
13602
13603 if (key in this._panKeys) {
13604 if (!map._panAnim || !map._panAnim._inProgress) {
13605 offset = this._panKeys[key];
13606 if (e.shiftKey) {
13607 offset = toPoint(offset).multiplyBy(3);
13608 }
13609
13610 map.panBy(offset);
13611
13612 if (map.options.maxBounds) {
13613 map.panInsideBounds(map.options.maxBounds);
13614 }
13615 }
13616 } else if (key in this._zoomKeys) {
13617 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13618
13619 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13620 map.closePopup();
13621
13622 } else {
13623 return;
13624 }
13625
13626 stop(e);
13627 }
13628});
13629
13630// @section Handlers
13631// @section Handlers
13632// @property keyboard: Handler
13633// Keyboard navigation handler.
13634Map.addInitHook('addHandler', 'keyboard', Keyboard);
13635
13636/*
13637 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13638 */
13639
13640// @namespace Map
13641// @section Interaction Options
13642Map.mergeOptions({
13643 // @section Mousewheel options
13644 // @option scrollWheelZoom: Boolean|String = true
13645 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13646 // it will zoom to the center of the view regardless of where the mouse was.
13647 scrollWheelZoom: true,
13648
13649 // @option wheelDebounceTime: Number = 40
13650 // Limits the rate at which a wheel can fire (in milliseconds). By default
13651 // user can't zoom via wheel more often than once per 40 ms.
13652 wheelDebounceTime: 40,
13653
13654 // @option wheelPxPerZoomLevel: Number = 60
13655 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13656 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13657 // faster (and vice versa).
13658 wheelPxPerZoomLevel: 60
13659});
13660
13661var ScrollWheelZoom = Handler.extend({
13662 addHooks: function () {
13663 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13664
13665 this._delta = 0;
13666 },
13667
13668 removeHooks: function () {
13669 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13670 },
13671
13672 _onWheelScroll: function (e) {
13673 var delta = getWheelDelta(e);
13674
13675 var debounce = this._map.options.wheelDebounceTime;
13676
13677 this._delta += delta;
13678 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13679
13680 if (!this._startTime) {
13681 this._startTime = +new Date();
13682 }
13683
13684 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13685
13686 clearTimeout(this._timer);
13687 this._timer = setTimeout(bind(this._performZoom, this), left);
13688
13689 stop(e);
13690 },
13691
13692 _performZoom: function () {
13693 var map = this._map,
13694 zoom = map.getZoom(),
13695 snap = this._map.options.zoomSnap || 0;
13696
13697 map._stop(); // stop panning and fly animations if any
13698
13699 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13700 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13701 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13702 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13703 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13704
13705 this._delta = 0;
13706 this._startTime = null;
13707
13708 if (!delta) { return; }
13709
13710 if (map.options.scrollWheelZoom === 'center') {
13711 map.setZoom(zoom + delta);
13712 } else {
13713 map.setZoomAround(this._lastMousePos, zoom + delta);
13714 }
13715 }
13716});
13717
13718// @section Handlers
13719// @property scrollWheelZoom: Handler
13720// Scroll wheel zoom handler.
13721Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13722
13723/*
13724 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13725 */
13726
13727// @namespace Map
13728// @section Interaction Options
13729Map.mergeOptions({
13730 // @section Touch interaction options
13731 // @option tap: Boolean = true
13732 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13733 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13734 tap: true,
13735
13736 // @option tapTolerance: Number = 15
13737 // The max number of pixels a user can shift his finger during touch
13738 // for it to be considered a valid tap.
13739 tapTolerance: 15
13740});
13741
13742var Tap = Handler.extend({
13743 addHooks: function () {
13744 on(this._map._container, 'touchstart', this._onDown, this);
13745 },
13746
13747 removeHooks: function () {
13748 off(this._map._container, 'touchstart', this._onDown, this);
13749 },
13750
13751 _onDown: function (e) {
13752 if (!e.touches) { return; }
13753
13754 preventDefault(e);
13755
13756 this._fireClick = true;
13757
13758 // don't simulate click or track longpress if more than 1 touch
13759 if (e.touches.length > 1) {
13760 this._fireClick = false;
13761 clearTimeout(this._holdTimeout);
13762 return;
13763 }
13764
13765 var first = e.touches[0],
13766 el = first.target;
13767
13768 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13769
13770 // if touching a link, highlight it
13771 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13772 addClass(el, 'leaflet-active');
13773 }
13774
13775 // simulate long hold but setting a timeout
13776 this._holdTimeout = setTimeout(bind(function () {
13777 if (this._isTapValid()) {
13778 this._fireClick = false;
13779 this._onUp();
13780 this._simulateEvent('contextmenu', first);
13781 }
13782 }, this), 1000);
13783
13784 this._simulateEvent('mousedown', first);
13785
13786 on(document, {
13787 touchmove: this._onMove,
13788 touchend: this._onUp
13789 }, this);
13790 },
13791
13792 _onUp: function (e) {
13793 clearTimeout(this._holdTimeout);
13794
13795 off(document, {
13796 touchmove: this._onMove,
13797 touchend: this._onUp
13798 }, this);
13799
13800 if (this._fireClick && e && e.changedTouches) {
13801
13802 var first = e.changedTouches[0],
13803 el = first.target;
13804
13805 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13806 removeClass(el, 'leaflet-active');
13807 }
13808
13809 this._simulateEvent('mouseup', first);
13810
13811 // simulate click if the touch didn't move too much
13812 if (this._isTapValid()) {
13813 this._simulateEvent('click', first);
13814 }
13815 }
13816 },
13817
13818 _isTapValid: function () {
13819 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13820 },
13821
13822 _onMove: function (e) {
13823 var first = e.touches[0];
13824 this._newPos = new Point(first.clientX, first.clientY);
13825 this._simulateEvent('mousemove', first);
13826 },
13827
13828 _simulateEvent: function (type, e) {
13829 var simulatedEvent = document.createEvent('MouseEvents');
13830
13831 simulatedEvent._simulated = true;
13832 e.target._simulatedClick = true;
13833
13834 simulatedEvent.initMouseEvent(
13835 type, true, true, window, 1,
13836 e.screenX, e.screenY,
13837 e.clientX, e.clientY,
13838 false, false, false, false, 0, null);
13839
13840 e.target.dispatchEvent(simulatedEvent);
13841 }
13842});
13843
13844// @section Handlers
13845// @property tap: Handler
13846// Mobile touch hacks (quick tap and touch hold) handler.
13847if (touch && !pointer) {
13848 Map.addInitHook('addHandler', 'tap', Tap);
13849}
13850
13851/*
13852 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13853 */
13854
13855// @namespace Map
13856// @section Interaction Options
13857Map.mergeOptions({
13858 // @section Touch interaction options
13859 // @option touchZoom: Boolean|String = *
13860 // Whether the map can be zoomed by touch-dragging with two fingers. If
13861 // passed `'center'`, it will zoom to the center of the view regardless of
13862 // where the touch events (fingers) were. Enabled for touch-capable web
13863 // browsers except for old Androids.
13864 touchZoom: touch && !android23,
13865
13866 // @option bounceAtZoomLimits: Boolean = true
13867 // Set it to false if you don't want the map to zoom beyond min/max zoom
13868 // and then bounce back when pinch-zooming.
13869 bounceAtZoomLimits: true
13870});
13871
13872var TouchZoom = Handler.extend({
13873 addHooks: function () {
13874 addClass(this._map._container, 'leaflet-touch-zoom');
13875 on(this._map._container, 'touchstart', this._onTouchStart, this);
13876 },
13877
13878 removeHooks: function () {
13879 removeClass(this._map._container, 'leaflet-touch-zoom');
13880 off(this._map._container, 'touchstart', this._onTouchStart, this);
13881 },
13882
13883 _onTouchStart: function (e) {
13884 var map = this._map;
13885 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13886
13887 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13888 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13889
13890 this._centerPoint = map.getSize()._divideBy(2);
13891 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13892 if (map.options.touchZoom !== 'center') {
13893 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13894 }
13895
13896 this._startDist = p1.distanceTo(p2);
13897 this._startZoom = map.getZoom();
13898
13899 this._moved = false;
13900 this._zooming = true;
13901
13902 map._stop();
13903
13904 on(document, 'touchmove', this._onTouchMove, this);
13905 on(document, 'touchend', this._onTouchEnd, this);
13906
13907 preventDefault(e);
13908 },
13909
13910 _onTouchMove: function (e) {
13911 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13912
13913 var map = this._map,
13914 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13915 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13916 scale = p1.distanceTo(p2) / this._startDist;
13917
13918 this._zoom = map.getScaleZoom(scale, this._startZoom);
13919
13920 if (!map.options.bounceAtZoomLimits && (
13921 (this._zoom < map.getMinZoom() && scale < 1) ||
13922 (this._zoom > map.getMaxZoom() && scale > 1))) {
13923 this._zoom = map._limitZoom(this._zoom);
13924 }
13925
13926 if (map.options.touchZoom === 'center') {
13927 this._center = this._startLatLng;
13928 if (scale === 1) { return; }
13929 } else {
13930 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13931 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13932 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13933 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13934 }
13935
13936 if (!this._moved) {
13937 map._moveStart(true, false);
13938 this._moved = true;
13939 }
13940
13941 cancelAnimFrame(this._animRequest);
13942
13943 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13944 this._animRequest = requestAnimFrame(moveFn, this, true);
13945
13946 preventDefault(e);
13947 },
13948
13949 _onTouchEnd: function () {
13950 if (!this._moved || !this._zooming) {
13951 this._zooming = false;
13952 return;
13953 }
13954
13955 this._zooming = false;
13956 cancelAnimFrame(this._animRequest);
13957
13958 off(document, 'touchmove', this._onTouchMove);
13959 off(document, 'touchend', this._onTouchEnd);
13960
13961 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13962 if (this._map.options.zoomAnimation) {
13963 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13964 } else {
13965 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13966 }
13967 }
13968});
13969
13970// @section Handlers
13971// @property touchZoom: Handler
13972// Touch zoom handler.
13973Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13974
13975Map.BoxZoom = BoxZoom;
13976Map.DoubleClickZoom = DoubleClickZoom;
13977Map.Drag = Drag;
13978Map.Keyboard = Keyboard;
13979Map.ScrollWheelZoom = ScrollWheelZoom;
13980Map.Tap = Tap;
13981Map.TouchZoom = TouchZoom;
13982
13983Object.freeze = freeze;
13984
13985export { version, Control, control, Browser, Evented, Mixin, Util, Class, Handler, extend, bind, stamp, setOptions, DomEvent, DomUtil, PosAnimation, Draggable, LineUtil, PolyUtil, Point, toPoint as point, Bounds, toBounds as bounds, Transformation, toTransformation as transformation, index as Projection, LatLng, toLatLng as latLng, LatLngBounds, toLatLngBounds as latLngBounds, CRS, GeoJSON, geoJSON, geoJson, Layer, LayerGroup, layerGroup, FeatureGroup, featureGroup, ImageOverlay, imageOverlay, VideoOverlay, videoOverlay, SVGOverlay, svgOverlay, DivOverlay, Popup, popup, Tooltip, tooltip, Icon, icon, DivIcon, divIcon, Marker, marker, TileLayer, tileLayer, GridLayer, gridLayer, SVG, svg$1 as svg, Renderer, Canvas, canvas$1 as canvas, Path, CircleMarker, circleMarker, Circle, circle, Polyline, polyline, Polygon, polygon, Rectangle, rectangle, Map, createMap as map };
13986//# sourceMappingURL=leaflet-src.esm.js.map