UNPKG

407 kBJavaScriptView Raw
1/* @preserve
2 * Leaflet 1.5.1+build.2e3e0ff, a JS library for interactive maps. http://leafletjs.com
3 * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6(function (global, factory) {
7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8 typeof define === 'function' && define.amd ? define(['exports'], factory) :
9 (factory((global.L = {})));
10}(this, (function (exports) { 'use strict';
11
12var version = "1.5.1+build.2e3e0ffb";
13
14/*
15 * @namespace Util
16 *
17 * Various utility functions, used by Leaflet internally.
18 */
19
20var freeze = Object.freeze;
21Object.freeze = function (obj) { return obj; };
22
23// @function extend(dest: Object, src?: Object): Object
24// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25function extend(dest) {
26 var i, j, len, src;
27
28 for (j = 1, len = arguments.length; j < len; j++) {
29 src = arguments[j];
30 for (i in src) {
31 dest[i] = src[i];
32 }
33 }
34 return dest;
35}
36
37// @function create(proto: Object, properties?: Object): Object
38// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39var create = Object.create || (function () {
40 function F() {}
41 return function (proto) {
42 F.prototype = proto;
43 return new F();
44 };
45})();
46
47// @function bind(fn: Function, …): Function
48// 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).
49// Has a `L.bind()` shortcut.
50function bind(fn, obj) {
51 var slice = Array.prototype.slice;
52
53 if (fn.bind) {
54 return fn.bind.apply(fn, slice.call(arguments, 1));
55 }
56
57 var args = slice.call(arguments, 2);
58
59 return function () {
60 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
61 };
62}
63
64// @property lastId: Number
65// Last unique ID used by [`stamp()`](#util-stamp)
66var lastId = 0;
67
68// @function stamp(obj: Object): Number
69// Returns the unique ID of an object, assigning it one if it doesn't have it.
70function stamp(obj) {
71 /*eslint-disable */
72 obj._leaflet_id = obj._leaflet_id || ++lastId;
73 return obj._leaflet_id;
74 /* eslint-enable */
75}
76
77// @function throttle(fn: Function, time: Number, context: Object): Function
78// Returns a function which executes function `fn` with the given scope `context`
79// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80// `fn` will be called no more than one time per given amount of `time`. The arguments
81// received by the bound function will be any arguments passed when binding the
82// function, followed by any arguments passed when invoking the bound function.
83// Has an `L.throttle` shortcut.
84function throttle(fn, time, context) {
85 var lock, args, wrapperFn, later;
86
87 later = function () {
88 // reset lock and call if queued
89 lock = false;
90 if (args) {
91 wrapperFn.apply(context, args);
92 args = false;
93 }
94 };
95
96 wrapperFn = function () {
97 if (lock) {
98 // called too soon, queue to call later
99 args = arguments;
100
101 } else {
102 // call and lock until later
103 fn.apply(context, arguments);
104 setTimeout(later, time);
105 lock = true;
106 }
107 };
108
109 return wrapperFn;
110}
111
112// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113// Returns the number `num` modulo `range` in such a way so it lies within
114// `range[0]` and `range[1]`. The returned value will be always smaller than
115// `range[1]` unless `includeMax` is set to `true`.
116function wrapNum(x, range, includeMax) {
117 var max = range[1],
118 min = range[0],
119 d = max - min;
120 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
121}
122
123// @function falseFn(): Function
124// Returns a function which always returns `false`.
125function falseFn() { return false; }
126
127// @function formatNum(num: Number, digits?: Number): Number
128// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129function formatNum(num, digits) {
130 digits = (digits === undefined ? 6 : digits);
131 return +(Math.round(num + ('e+' + digits)) + ('e-' + digits));
132}
133
134// @function trim(str: String): String
135// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136function trim(str) {
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
138}
139
140// @function splitWords(str: String): String[]
141// Trims and splits the string on whitespace and returns the array of parts.
142function splitWords(str) {
143 return trim(str).split(/\s+/);
144}
145
146// @function setOptions(obj: Object, options: Object): Object
147// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148function setOptions(obj, options) {
149 if (!obj.hasOwnProperty('options')) {
150 obj.options = obj.options ? create(obj.options) : {};
151 }
152 for (var i in options) {
153 obj.options[i] = options[i];
154 }
155 return obj.options;
156}
157
158// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161// be appended at the end. If `uppercase` is `true`, the parameter names will
162// be uppercased (e.g. `'?A=foo&B=bar'`)
163function getParamString(obj, existingUrl, uppercase) {
164 var params = [];
165 for (var i in obj) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167 }
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
169}
170
171var templateRe = /\{ *([\w_-]+) *\}/g;
172
173// @function template(str: String, data: Object): String
174// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176// `('Hello foo, bar')`. You can also specify functions instead of strings for
177// data values — they will be evaluated passing `data` as an argument.
178function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
181
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
184
185 } else if (typeof value === 'function') {
186 value = value(data);
187 }
188 return value;
189 });
190}
191
192// @function isArray(obj): Boolean
193// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
196};
197
198// @function indexOf(array: Array, el: Object): Number
199// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
203 }
204 return -1;
205}
206
207// @property emptyImageUrl: String
208// Data URI string containing a base64-encoded empty GIF image.
209// Used as a hack to free memory from unused images on WebKit-powered
210// mobile devices (by setting image `src` to this string).
211var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212
213// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
214
215function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217}
218
219var lastTime = 0;
220
221// fallback for IE 7-8
222function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
225
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
228}
229
230var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233
234// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236// `context` if given. When `immediate` is set, `fn` is called immediately if
237// the browser doesn't have native support for
238// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
242 fn.call(context);
243 } else {
244 return requestFn.call(window, bind(fn, context));
245 }
246}
247
248// @function cancelAnimFrame(id: Number): undefined
249// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250function cancelAnimFrame(id) {
251 if (id) {
252 cancelFn.call(window, id);
253 }
254}
255
256
257var Util = (Object.freeze || Object)({
258 freeze: freeze,
259 extend: extend,
260 create: create,
261 bind: bind,
262 lastId: lastId,
263 stamp: stamp,
264 throttle: throttle,
265 wrapNum: wrapNum,
266 falseFn: falseFn,
267 formatNum: formatNum,
268 trim: trim,
269 splitWords: splitWords,
270 setOptions: setOptions,
271 getParamString: getParamString,
272 template: template,
273 isArray: isArray,
274 indexOf: indexOf,
275 emptyImageUrl: emptyImageUrl,
276 requestFn: requestFn,
277 cancelFn: cancelFn,
278 requestAnimFrame: requestAnimFrame,
279 cancelAnimFrame: cancelAnimFrame
280});
281
282// @class Class
283// @aka L.Class
284
285// @section
286// @uninheritable
287
288// Thanks to John Resig and Dean Edwards for inspiration!
289
290function Class() {}
291
292Class.extend = function (props) {
293
294 // @function extend(props: Object): Function
295 // [Extends the current class](#class-inheritance) given the properties to be included.
296 // Returns a Javascript function that is a class constructor (to be called with `new`).
297 var NewClass = function () {
298
299 // call the constructor
300 if (this.initialize) {
301 this.initialize.apply(this, arguments);
302 }
303
304 // call all constructor hooks
305 this.callInitHooks();
306 };
307
308 var parentProto = NewClass.__super__ = this.prototype;
309
310 var proto = create(parentProto);
311 proto.constructor = NewClass;
312
313 NewClass.prototype = proto;
314
315 // inherit parent's statics
316 for (var i in this) {
317 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318 NewClass[i] = this[i];
319 }
320 }
321
322 // mix static properties into the class
323 if (props.statics) {
324 extend(NewClass, props.statics);
325 delete props.statics;
326 }
327
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 delete props.includes;
333 }
334
335 // merge options
336 if (proto.options) {
337 props.options = extend(create(proto.options), props.options);
338 }
339
340 // mix given properties into the prototype
341 extend(proto, props);
342
343 proto._initHooks = [];
344
345 // add method for calling all hooks
346 proto.callInitHooks = function () {
347
348 if (this._initHooksCalled) { return; }
349
350 if (parentProto.callInitHooks) {
351 parentProto.callInitHooks.call(this);
352 }
353
354 this._initHooksCalled = true;
355
356 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357 proto._initHooks[i].call(this);
358 }
359 };
360
361 return NewClass;
362};
363
364
365// @function include(properties: Object): this
366// [Includes a mixin](#class-includes) into the current class.
367Class.include = function (props) {
368 extend(this.prototype, props);
369 return this;
370};
371
372// @function mergeOptions(options: Object): this
373// [Merges `options`](#class-options) into the defaults of the class.
374Class.mergeOptions = function (options) {
375 extend(this.prototype.options, options);
376 return this;
377};
378
379// @function addInitHook(fn: Function): this
380// Adds a [constructor hook](#class-constructor-hooks) to the class.
381Class.addInitHook = function (fn) { // (Function) || (String, args...)
382 var args = Array.prototype.slice.call(arguments, 1);
383
384 var init = typeof fn === 'function' ? fn : function () {
385 this[fn].apply(this, args);
386 };
387
388 this.prototype._initHooks = this.prototype._initHooks || [];
389 this.prototype._initHooks.push(init);
390 return this;
391};
392
393function checkDeprecatedMixinEvents(includes) {
394 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
395
396 includes = isArray(includes) ? includes : [includes];
397
398 for (var i = 0; i < includes.length; i++) {
399 if (includes[i] === L.Mixin.Events) {
400 console.warn('Deprecated include of L.Mixin.Events: ' +
401 'this property will be removed in future releases, ' +
402 'please inherit from L.Evented instead.', new Error().stack);
403 }
404 }
405}
406
407/*
408 * @class Evented
409 * @aka L.Evented
410 * @inherits Class
411 *
412 * 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).
413 *
414 * @example
415 *
416 * ```js
417 * map.on('click', function(e) {
418 * alert(e.latlng);
419 * } );
420 * ```
421 *
422 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
423 *
424 * ```js
425 * function onClick(e) { ... }
426 *
427 * map.on('click', onClick);
428 * map.off('click', onClick);
429 * ```
430 */
431
432var Events = {
433 /* @method on(type: String, fn: Function, context?: Object): this
434 * 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'`).
435 *
436 * @alternative
437 * @method on(eventMap: Object): this
438 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
439 */
440 on: function (types, fn, context) {
441
442 // types can be a map of types/handlers
443 if (typeof types === 'object') {
444 for (var type in types) {
445 // we don't process space-separated events here for performance;
446 // it's a hot path since Layer uses the on(obj) syntax
447 this._on(type, types[type], fn);
448 }
449
450 } else {
451 // types can be a string of space-separated words
452 types = splitWords(types);
453
454 for (var i = 0, len = types.length; i < len; i++) {
455 this._on(types[i], fn, context);
456 }
457 }
458
459 return this;
460 },
461
462 /* @method off(type: String, fn?: Function, context?: Object): this
463 * 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.
464 *
465 * @alternative
466 * @method off(eventMap: Object): this
467 * Removes a set of type/listener pairs.
468 *
469 * @alternative
470 * @method off: this
471 * Removes all listeners to all events on the object. This includes implicitly attached events.
472 */
473 off: function (types, fn, context) {
474
475 if (!types) {
476 // clear all listeners if called without arguments
477 delete this._events;
478
479 } else if (typeof types === 'object') {
480 for (var type in types) {
481 this._off(type, types[type], fn);
482 }
483
484 } else {
485 types = splitWords(types);
486
487 for (var i = 0, len = types.length; i < len; i++) {
488 this._off(types[i], fn, context);
489 }
490 }
491
492 return this;
493 },
494
495 // attach listener (without syntactic sugar now)
496 _on: function (type, fn, context) {
497 this._events = this._events || {};
498
499 /* get/init listeners for type */
500 var typeListeners = this._events[type];
501 if (!typeListeners) {
502 typeListeners = [];
503 this._events[type] = typeListeners;
504 }
505
506 if (context === this) {
507 // Less memory footprint.
508 context = undefined;
509 }
510 var newListener = {fn: fn, ctx: context},
511 listeners = typeListeners;
512
513 // check if fn already there
514 for (var i = 0, len = listeners.length; i < len; i++) {
515 if (listeners[i].fn === fn && listeners[i].ctx === context) {
516 return;
517 }
518 }
519
520 listeners.push(newListener);
521 },
522
523 _off: function (type, fn, context) {
524 var listeners,
525 i,
526 len;
527
528 if (!this._events) { return; }
529
530 listeners = this._events[type];
531
532 if (!listeners) {
533 return;
534 }
535
536 if (!fn) {
537 // Set all removed listeners to noop so they are not called if remove happens in fire
538 for (i = 0, len = listeners.length; i < len; i++) {
539 listeners[i].fn = falseFn;
540 }
541 // clear all listeners for a type if function isn't specified
542 delete this._events[type];
543 return;
544 }
545
546 if (context === this) {
547 context = undefined;
548 }
549
550 if (listeners) {
551
552 // find fn and remove it
553 for (i = 0, len = listeners.length; i < len; i++) {
554 var l = listeners[i];
555 if (l.ctx !== context) { continue; }
556 if (l.fn === fn) {
557
558 // set the removed listener to noop so that's not called if remove happens in fire
559 l.fn = falseFn;
560
561 if (this._firingCount) {
562 /* copy array in case events are being fired */
563 this._events[type] = listeners = listeners.slice();
564 }
565 listeners.splice(i, 1);
566
567 return;
568 }
569 }
570 }
571 },
572
573 // @method fire(type: String, data?: Object, propagate?: Boolean): this
574 // Fires an event of the specified type. You can optionally provide an data
575 // object — the first argument of the listener function will contain its
576 // properties. The event can optionally be propagated to event parents.
577 fire: function (type, data, propagate) {
578 if (!this.listens(type, propagate)) { return this; }
579
580 var event = extend({}, data, {
581 type: type,
582 target: this,
583 sourceTarget: data && data.sourceTarget || this
584 });
585
586 if (this._events) {
587 var listeners = this._events[type];
588
589 if (listeners) {
590 this._firingCount = (this._firingCount + 1) || 1;
591 for (var i = 0, len = listeners.length; i < len; i++) {
592 var l = listeners[i];
593 l.fn.call(l.ctx || this, event);
594 }
595
596 this._firingCount--;
597 }
598 }
599
600 if (propagate) {
601 // propagate the event to parents (set with addEventParent)
602 this._propagateEvent(event);
603 }
604
605 return this;
606 },
607
608 // @method listens(type: String): Boolean
609 // Returns `true` if a particular event type has any listeners attached to it.
610 listens: function (type, propagate) {
611 var listeners = this._events && this._events[type];
612 if (listeners && listeners.length) { return true; }
613
614 if (propagate) {
615 // also check parents for listeners if event propagates
616 for (var id in this._eventParents) {
617 if (this._eventParents[id].listens(type, propagate)) { return true; }
618 }
619 }
620 return false;
621 },
622
623 // @method once(…): this
624 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
625 once: function (types, fn, context) {
626
627 if (typeof types === 'object') {
628 for (var type in types) {
629 this.once(type, types[type], fn);
630 }
631 return this;
632 }
633
634 var handler = bind(function () {
635 this
636 .off(types, fn, context)
637 .off(types, handler, context);
638 }, this);
639
640 // add a listener that's executed once and removed after that
641 return this
642 .on(types, fn, context)
643 .on(types, handler, context);
644 },
645
646 // @method addEventParent(obj: Evented): this
647 // Adds an event parent - an `Evented` that will receive propagated events
648 addEventParent: function (obj) {
649 this._eventParents = this._eventParents || {};
650 this._eventParents[stamp(obj)] = obj;
651 return this;
652 },
653
654 // @method removeEventParent(obj: Evented): this
655 // Removes an event parent, so it will stop receiving propagated events
656 removeEventParent: function (obj) {
657 if (this._eventParents) {
658 delete this._eventParents[stamp(obj)];
659 }
660 return this;
661 },
662
663 _propagateEvent: function (e) {
664 for (var id in this._eventParents) {
665 this._eventParents[id].fire(e.type, extend({
666 layer: e.target,
667 propagatedFrom: e.target
668 }, e), true);
669 }
670 }
671};
672
673// aliases; we should ditch those eventually
674
675// @method addEventListener(…): this
676// Alias to [`on(…)`](#evented-on)
677Events.addEventListener = Events.on;
678
679// @method removeEventListener(…): this
680// Alias to [`off(…)`](#evented-off)
681
682// @method clearAllEventListeners(…): this
683// Alias to [`off()`](#evented-off)
684Events.removeEventListener = Events.clearAllEventListeners = Events.off;
685
686// @method addOneTimeEventListener(…): this
687// Alias to [`once(…)`](#evented-once)
688Events.addOneTimeEventListener = Events.once;
689
690// @method fireEvent(…): this
691// Alias to [`fire(…)`](#evented-fire)
692Events.fireEvent = Events.fire;
693
694// @method hasEventListeners(…): Boolean
695// Alias to [`listens(…)`](#evented-listens)
696Events.hasEventListeners = Events.listens;
697
698var Evented = Class.extend(Events);
699
700/*
701 * @class Point
702 * @aka L.Point
703 *
704 * Represents a point with `x` and `y` coordinates in pixels.
705 *
706 * @example
707 *
708 * ```js
709 * var point = L.point(200, 300);
710 * ```
711 *
712 * 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:
713 *
714 * ```js
715 * map.panBy([200, 300]);
716 * map.panBy(L.point(200, 300));
717 * ```
718 *
719 * Note that `Point` does not inherit from Leafet's `Class` object,
720 * which means new classes can't inherit from it, and new methods
721 * can't be added to it with the `include` function.
722 */
723
724function Point(x, y, round) {
725 // @property x: Number; The `x` coordinate of the point
726 this.x = (round ? Math.round(x) : x);
727 // @property y: Number; The `y` coordinate of the point
728 this.y = (round ? Math.round(y) : y);
729}
730
731var trunc = Math.trunc || function (v) {
732 return v > 0 ? Math.floor(v) : Math.ceil(v);
733};
734
735Point.prototype = {
736
737 // @method clone(): Point
738 // Returns a copy of the current point.
739 clone: function () {
740 return new Point(this.x, this.y);
741 },
742
743 // @method add(otherPoint: Point): Point
744 // Returns the result of addition of the current and the given points.
745 add: function (point) {
746 // non-destructive, returns a new point
747 return this.clone()._add(toPoint(point));
748 },
749
750 _add: function (point) {
751 // destructive, used directly for performance in situations where it's safe to modify existing point
752 this.x += point.x;
753 this.y += point.y;
754 return this;
755 },
756
757 // @method subtract(otherPoint: Point): Point
758 // Returns the result of subtraction of the given point from the current.
759 subtract: function (point) {
760 return this.clone()._subtract(toPoint(point));
761 },
762
763 _subtract: function (point) {
764 this.x -= point.x;
765 this.y -= point.y;
766 return this;
767 },
768
769 // @method divideBy(num: Number): Point
770 // Returns the result of division of the current point by the given number.
771 divideBy: function (num) {
772 return this.clone()._divideBy(num);
773 },
774
775 _divideBy: function (num) {
776 this.x /= num;
777 this.y /= num;
778 return this;
779 },
780
781 // @method multiplyBy(num: Number): Point
782 // Returns the result of multiplication of the current point by the given number.
783 multiplyBy: function (num) {
784 return this.clone()._multiplyBy(num);
785 },
786
787 _multiplyBy: function (num) {
788 this.x *= num;
789 this.y *= num;
790 return this;
791 },
792
793 // @method scaleBy(scale: Point): Point
794 // Multiply each coordinate of the current point by each coordinate of
795 // `scale`. In linear algebra terms, multiply the point by the
796 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
797 // defined by `scale`.
798 scaleBy: function (point) {
799 return new Point(this.x * point.x, this.y * point.y);
800 },
801
802 // @method unscaleBy(scale: Point): Point
803 // Inverse of `scaleBy`. Divide each coordinate of the current point by
804 // each coordinate of `scale`.
805 unscaleBy: function (point) {
806 return new Point(this.x / point.x, this.y / point.y);
807 },
808
809 // @method round(): Point
810 // Returns a copy of the current point with rounded coordinates.
811 round: function () {
812 return this.clone()._round();
813 },
814
815 _round: function () {
816 this.x = Math.round(this.x);
817 this.y = Math.round(this.y);
818 return this;
819 },
820
821 // @method floor(): Point
822 // Returns a copy of the current point with floored coordinates (rounded down).
823 floor: function () {
824 return this.clone()._floor();
825 },
826
827 _floor: function () {
828 this.x = Math.floor(this.x);
829 this.y = Math.floor(this.y);
830 return this;
831 },
832
833 // @method ceil(): Point
834 // Returns a copy of the current point with ceiled coordinates (rounded up).
835 ceil: function () {
836 return this.clone()._ceil();
837 },
838
839 _ceil: function () {
840 this.x = Math.ceil(this.x);
841 this.y = Math.ceil(this.y);
842 return this;
843 },
844
845 // @method trunc(): Point
846 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
847 trunc: function () {
848 return this.clone()._trunc();
849 },
850
851 _trunc: function () {
852 this.x = trunc(this.x);
853 this.y = trunc(this.y);
854 return this;
855 },
856
857 // @method distanceTo(otherPoint: Point): Number
858 // Returns the cartesian distance between the current and the given points.
859 distanceTo: function (point) {
860 point = toPoint(point);
861
862 var x = point.x - this.x,
863 y = point.y - this.y;
864
865 return Math.sqrt(x * x + y * y);
866 },
867
868 // @method equals(otherPoint: Point): Boolean
869 // Returns `true` if the given point has the same coordinates.
870 equals: function (point) {
871 point = toPoint(point);
872
873 return point.x === this.x &&
874 point.y === this.y;
875 },
876
877 // @method contains(otherPoint: Point): Boolean
878 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
879 contains: function (point) {
880 point = toPoint(point);
881
882 return Math.abs(point.x) <= Math.abs(this.x) &&
883 Math.abs(point.y) <= Math.abs(this.y);
884 },
885
886 // @method toString(): String
887 // Returns a string representation of the point for debugging purposes.
888 toString: function () {
889 return 'Point(' +
890 formatNum(this.x) + ', ' +
891 formatNum(this.y) + ')';
892 }
893};
894
895// @factory L.point(x: Number, y: Number, round?: Boolean)
896// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
897
898// @alternative
899// @factory L.point(coords: Number[])
900// Expects an array of the form `[x, y]` instead.
901
902// @alternative
903// @factory L.point(coords: Object)
904// Expects a plain object of the form `{x: Number, y: Number}` instead.
905function toPoint(x, y, round) {
906 if (x instanceof Point) {
907 return x;
908 }
909 if (isArray(x)) {
910 return new Point(x[0], x[1]);
911 }
912 if (x === undefined || x === null) {
913 return x;
914 }
915 if (typeof x === 'object' && 'x' in x && 'y' in x) {
916 return new Point(x.x, x.y);
917 }
918 return new Point(x, y, round);
919}
920
921/*
922 * @class Bounds
923 * @aka L.Bounds
924 *
925 * Represents a rectangular area in pixel coordinates.
926 *
927 * @example
928 *
929 * ```js
930 * var p1 = L.point(10, 10),
931 * p2 = L.point(40, 60),
932 * bounds = L.bounds(p1, p2);
933 * ```
934 *
935 * 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:
936 *
937 * ```js
938 * otherBounds.intersects([[10, 10], [40, 60]]);
939 * ```
940 *
941 * Note that `Bounds` does not inherit from Leafet's `Class` object,
942 * which means new classes can't inherit from it, and new methods
943 * can't be added to it with the `include` function.
944 */
945
946function Bounds(a, b) {
947 if (!a) { return; }
948
949 var points = b ? [a, b] : a;
950
951 for (var i = 0, len = points.length; i < len; i++) {
952 this.extend(points[i]);
953 }
954}
955
956Bounds.prototype = {
957 // @method extend(point: Point): this
958 // Extends the bounds to contain the given point.
959 extend: function (point) { // (Point)
960 point = toPoint(point);
961
962 // @property min: Point
963 // The top left corner of the rectangle.
964 // @property max: Point
965 // The bottom right corner of the rectangle.
966 if (!this.min && !this.max) {
967 this.min = point.clone();
968 this.max = point.clone();
969 } else {
970 this.min.x = Math.min(point.x, this.min.x);
971 this.max.x = Math.max(point.x, this.max.x);
972 this.min.y = Math.min(point.y, this.min.y);
973 this.max.y = Math.max(point.y, this.max.y);
974 }
975 return this;
976 },
977
978 // @method getCenter(round?: Boolean): Point
979 // Returns the center point of the bounds.
980 getCenter: function (round) {
981 return new Point(
982 (this.min.x + this.max.x) / 2,
983 (this.min.y + this.max.y) / 2, round);
984 },
985
986 // @method getBottomLeft(): Point
987 // Returns the bottom-left point of the bounds.
988 getBottomLeft: function () {
989 return new Point(this.min.x, this.max.y);
990 },
991
992 // @method getTopRight(): Point
993 // Returns the top-right point of the bounds.
994 getTopRight: function () { // -> Point
995 return new Point(this.max.x, this.min.y);
996 },
997
998 // @method getTopLeft(): Point
999 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1000 getTopLeft: function () {
1001 return this.min; // left, top
1002 },
1003
1004 // @method getBottomRight(): Point
1005 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1006 getBottomRight: function () {
1007 return this.max; // right, bottom
1008 },
1009
1010 // @method getSize(): Point
1011 // Returns the size of the given bounds
1012 getSize: function () {
1013 return this.max.subtract(this.min);
1014 },
1015
1016 // @method contains(otherBounds: Bounds): Boolean
1017 // Returns `true` if the rectangle contains the given one.
1018 // @alternative
1019 // @method contains(point: Point): Boolean
1020 // Returns `true` if the rectangle contains the given point.
1021 contains: function (obj) {
1022 var min, max;
1023
1024 if (typeof obj[0] === 'number' || obj instanceof Point) {
1025 obj = toPoint(obj);
1026 } else {
1027 obj = toBounds(obj);
1028 }
1029
1030 if (obj instanceof Bounds) {
1031 min = obj.min;
1032 max = obj.max;
1033 } else {
1034 min = max = obj;
1035 }
1036
1037 return (min.x >= this.min.x) &&
1038 (max.x <= this.max.x) &&
1039 (min.y >= this.min.y) &&
1040 (max.y <= this.max.y);
1041 },
1042
1043 // @method intersects(otherBounds: Bounds): Boolean
1044 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1045 // intersect if they have at least one point in common.
1046 intersects: function (bounds) { // (Bounds) -> Boolean
1047 bounds = toBounds(bounds);
1048
1049 var min = this.min,
1050 max = this.max,
1051 min2 = bounds.min,
1052 max2 = bounds.max,
1053 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1054 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1055
1056 return xIntersects && yIntersects;
1057 },
1058
1059 // @method overlaps(otherBounds: Bounds): Boolean
1060 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1061 // overlap if their intersection is an area.
1062 overlaps: function (bounds) { // (Bounds) -> Boolean
1063 bounds = toBounds(bounds);
1064
1065 var min = this.min,
1066 max = this.max,
1067 min2 = bounds.min,
1068 max2 = bounds.max,
1069 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1070 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1071
1072 return xOverlaps && yOverlaps;
1073 },
1074
1075 isValid: function () {
1076 return !!(this.min && this.max);
1077 }
1078};
1079
1080
1081// @factory L.bounds(corner1: Point, corner2: Point)
1082// Creates a Bounds object from two corners coordinate pairs.
1083// @alternative
1084// @factory L.bounds(points: Point[])
1085// Creates a Bounds object from the given array of points.
1086function toBounds(a, b) {
1087 if (!a || a instanceof Bounds) {
1088 return a;
1089 }
1090 return new Bounds(a, b);
1091}
1092
1093/*
1094 * @class LatLngBounds
1095 * @aka L.LatLngBounds
1096 *
1097 * Represents a rectangular geographical area on a map.
1098 *
1099 * @example
1100 *
1101 * ```js
1102 * var corner1 = L.latLng(40.712, -74.227),
1103 * corner2 = L.latLng(40.774, -74.125),
1104 * bounds = L.latLngBounds(corner1, corner2);
1105 * ```
1106 *
1107 * 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:
1108 *
1109 * ```js
1110 * map.fitBounds([
1111 * [40.712, -74.227],
1112 * [40.774, -74.125]
1113 * ]);
1114 * ```
1115 *
1116 * 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.
1117 *
1118 * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
1119 * which means new classes can't inherit from it, and new methods
1120 * can't be added to it with the `include` function.
1121 */
1122
1123function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1124 if (!corner1) { return; }
1125
1126 var latlngs = corner2 ? [corner1, corner2] : corner1;
1127
1128 for (var i = 0, len = latlngs.length; i < len; i++) {
1129 this.extend(latlngs[i]);
1130 }
1131}
1132
1133LatLngBounds.prototype = {
1134
1135 // @method extend(latlng: LatLng): this
1136 // Extend the bounds to contain the given point
1137
1138 // @alternative
1139 // @method extend(otherBounds: LatLngBounds): this
1140 // Extend the bounds to contain the given bounds
1141 extend: function (obj) {
1142 var sw = this._southWest,
1143 ne = this._northEast,
1144 sw2, ne2;
1145
1146 if (obj instanceof LatLng) {
1147 sw2 = obj;
1148 ne2 = obj;
1149
1150 } else if (obj instanceof LatLngBounds) {
1151 sw2 = obj._southWest;
1152 ne2 = obj._northEast;
1153
1154 if (!sw2 || !ne2) { return this; }
1155
1156 } else {
1157 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1158 }
1159
1160 if (!sw && !ne) {
1161 this._southWest = new LatLng(sw2.lat, sw2.lng);
1162 this._northEast = new LatLng(ne2.lat, ne2.lng);
1163 } else {
1164 sw.lat = Math.min(sw2.lat, sw.lat);
1165 sw.lng = Math.min(sw2.lng, sw.lng);
1166 ne.lat = Math.max(ne2.lat, ne.lat);
1167 ne.lng = Math.max(ne2.lng, ne.lng);
1168 }
1169
1170 return this;
1171 },
1172
1173 // @method pad(bufferRatio: Number): LatLngBounds
1174 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1175 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1176 // Negative values will retract the bounds.
1177 pad: function (bufferRatio) {
1178 var sw = this._southWest,
1179 ne = this._northEast,
1180 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1181 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1182
1183 return new LatLngBounds(
1184 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1185 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1186 },
1187
1188 // @method getCenter(): LatLng
1189 // Returns the center point of the bounds.
1190 getCenter: function () {
1191 return new LatLng(
1192 (this._southWest.lat + this._northEast.lat) / 2,
1193 (this._southWest.lng + this._northEast.lng) / 2);
1194 },
1195
1196 // @method getSouthWest(): LatLng
1197 // Returns the south-west point of the bounds.
1198 getSouthWest: function () {
1199 return this._southWest;
1200 },
1201
1202 // @method getNorthEast(): LatLng
1203 // Returns the north-east point of the bounds.
1204 getNorthEast: function () {
1205 return this._northEast;
1206 },
1207
1208 // @method getNorthWest(): LatLng
1209 // Returns the north-west point of the bounds.
1210 getNorthWest: function () {
1211 return new LatLng(this.getNorth(), this.getWest());
1212 },
1213
1214 // @method getSouthEast(): LatLng
1215 // Returns the south-east point of the bounds.
1216 getSouthEast: function () {
1217 return new LatLng(this.getSouth(), this.getEast());
1218 },
1219
1220 // @method getWest(): Number
1221 // Returns the west longitude of the bounds
1222 getWest: function () {
1223 return this._southWest.lng;
1224 },
1225
1226 // @method getSouth(): Number
1227 // Returns the south latitude of the bounds
1228 getSouth: function () {
1229 return this._southWest.lat;
1230 },
1231
1232 // @method getEast(): Number
1233 // Returns the east longitude of the bounds
1234 getEast: function () {
1235 return this._northEast.lng;
1236 },
1237
1238 // @method getNorth(): Number
1239 // Returns the north latitude of the bounds
1240 getNorth: function () {
1241 return this._northEast.lat;
1242 },
1243
1244 // @method contains(otherBounds: LatLngBounds): Boolean
1245 // Returns `true` if the rectangle contains the given one.
1246
1247 // @alternative
1248 // @method contains (latlng: LatLng): Boolean
1249 // Returns `true` if the rectangle contains the given point.
1250 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1251 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1252 obj = toLatLng(obj);
1253 } else {
1254 obj = toLatLngBounds(obj);
1255 }
1256
1257 var sw = this._southWest,
1258 ne = this._northEast,
1259 sw2, ne2;
1260
1261 if (obj instanceof LatLngBounds) {
1262 sw2 = obj.getSouthWest();
1263 ne2 = obj.getNorthEast();
1264 } else {
1265 sw2 = ne2 = obj;
1266 }
1267
1268 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1269 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1270 },
1271
1272 // @method intersects(otherBounds: LatLngBounds): Boolean
1273 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1274 intersects: function (bounds) {
1275 bounds = toLatLngBounds(bounds);
1276
1277 var sw = this._southWest,
1278 ne = this._northEast,
1279 sw2 = bounds.getSouthWest(),
1280 ne2 = bounds.getNorthEast(),
1281
1282 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1283 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1284
1285 return latIntersects && lngIntersects;
1286 },
1287
1288 // @method overlaps(otherBounds: Bounds): Boolean
1289 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1290 overlaps: function (bounds) {
1291 bounds = toLatLngBounds(bounds);
1292
1293 var sw = this._southWest,
1294 ne = this._northEast,
1295 sw2 = bounds.getSouthWest(),
1296 ne2 = bounds.getNorthEast(),
1297
1298 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1299 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1300
1301 return latOverlaps && lngOverlaps;
1302 },
1303
1304 // @method toBBoxString(): String
1305 // 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.
1306 toBBoxString: function () {
1307 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1308 },
1309
1310 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1311 // 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.
1312 equals: function (bounds, maxMargin) {
1313 if (!bounds) { return false; }
1314
1315 bounds = toLatLngBounds(bounds);
1316
1317 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1318 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1319 },
1320
1321 // @method isValid(): Boolean
1322 // Returns `true` if the bounds are properly initialized.
1323 isValid: function () {
1324 return !!(this._southWest && this._northEast);
1325 }
1326};
1327
1328// TODO International date line?
1329
1330// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1331// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1332
1333// @alternative
1334// @factory L.latLngBounds(latlngs: LatLng[])
1335// 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).
1336function toLatLngBounds(a, b) {
1337 if (a instanceof LatLngBounds) {
1338 return a;
1339 }
1340 return new LatLngBounds(a, b);
1341}
1342
1343/* @class LatLng
1344 * @aka L.LatLng
1345 *
1346 * Represents a geographical point with a certain latitude and longitude.
1347 *
1348 * @example
1349 *
1350 * ```
1351 * var latlng = L.latLng(50.5, 30.5);
1352 * ```
1353 *
1354 * 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:
1355 *
1356 * ```
1357 * map.panTo([50, 30]);
1358 * map.panTo({lon: 30, lat: 50});
1359 * map.panTo({lat: 50, lng: 30});
1360 * map.panTo(L.latLng(50, 30));
1361 * ```
1362 *
1363 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1364 * which means new classes can't inherit from it, and new methods
1365 * can't be added to it with the `include` function.
1366 */
1367
1368function LatLng(lat, lng, alt) {
1369 if (isNaN(lat) || isNaN(lng)) {
1370 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1371 }
1372
1373 // @property lat: Number
1374 // Latitude in degrees
1375 this.lat = +lat;
1376
1377 // @property lng: Number
1378 // Longitude in degrees
1379 this.lng = +lng;
1380
1381 // @property alt: Number
1382 // Altitude in meters (optional)
1383 if (alt !== undefined) {
1384 this.alt = +alt;
1385 }
1386}
1387
1388LatLng.prototype = {
1389 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1390 // 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.
1391 equals: function (obj, maxMargin) {
1392 if (!obj) { return false; }
1393
1394 obj = toLatLng(obj);
1395
1396 var margin = Math.max(
1397 Math.abs(this.lat - obj.lat),
1398 Math.abs(this.lng - obj.lng));
1399
1400 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1401 },
1402
1403 // @method toString(): String
1404 // Returns a string representation of the point (for debugging purposes).
1405 toString: function (precision) {
1406 return 'LatLng(' +
1407 formatNum(this.lat, precision) + ', ' +
1408 formatNum(this.lng, precision) + ')';
1409 },
1410
1411 // @method distanceTo(otherLatLng: LatLng): Number
1412 // 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).
1413 distanceTo: function (other) {
1414 return Earth.distance(this, toLatLng(other));
1415 },
1416
1417 // @method wrap(): LatLng
1418 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1419 wrap: function () {
1420 return Earth.wrapLatLng(this);
1421 },
1422
1423 // @method toBounds(sizeInMeters: Number): LatLngBounds
1424 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1425 toBounds: function (sizeInMeters) {
1426 var latAccuracy = 180 * sizeInMeters / 40075017,
1427 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1428
1429 return toLatLngBounds(
1430 [this.lat - latAccuracy, this.lng - lngAccuracy],
1431 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1432 },
1433
1434 clone: function () {
1435 return new LatLng(this.lat, this.lng, this.alt);
1436 }
1437};
1438
1439
1440
1441// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1442// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1443
1444// @alternative
1445// @factory L.latLng(coords: Array): LatLng
1446// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1447
1448// @alternative
1449// @factory L.latLng(coords: Object): LatLng
1450// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1451
1452function toLatLng(a, b, c) {
1453 if (a instanceof LatLng) {
1454 return a;
1455 }
1456 if (isArray(a) && typeof a[0] !== 'object') {
1457 if (a.length === 3) {
1458 return new LatLng(a[0], a[1], a[2]);
1459 }
1460 if (a.length === 2) {
1461 return new LatLng(a[0], a[1]);
1462 }
1463 return null;
1464 }
1465 if (a === undefined || a === null) {
1466 return a;
1467 }
1468 if (typeof a === 'object' && 'lat' in a) {
1469 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1470 }
1471 if (b === undefined) {
1472 return null;
1473 }
1474 return new LatLng(a, b, c);
1475}
1476
1477/*
1478 * @namespace CRS
1479 * @crs L.CRS.Base
1480 * Object that defines coordinate reference systems for projecting
1481 * geographical points into pixel (screen) coordinates and back (and to
1482 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1483 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1484 *
1485 * Leaflet defines the most usual CRSs by default. If you want to use a
1486 * CRS not defined by default, take a look at the
1487 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1488 *
1489 * Note that the CRS instances do not inherit from Leafet's `Class` object,
1490 * and can't be instantiated. Also, new classes can't inherit from them,
1491 * and methods can't be added to them with the `include` function.
1492 */
1493
1494var CRS = {
1495 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1496 // Projects geographical coordinates into pixel coordinates for a given zoom.
1497 latLngToPoint: function (latlng, zoom) {
1498 var projectedPoint = this.projection.project(latlng),
1499 scale = this.scale(zoom);
1500
1501 return this.transformation._transform(projectedPoint, scale);
1502 },
1503
1504 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1505 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1506 // zoom into geographical coordinates.
1507 pointToLatLng: function (point, zoom) {
1508 var scale = this.scale(zoom),
1509 untransformedPoint = this.transformation.untransform(point, scale);
1510
1511 return this.projection.unproject(untransformedPoint);
1512 },
1513
1514 // @method project(latlng: LatLng): Point
1515 // Projects geographical coordinates into coordinates in units accepted for
1516 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1517 project: function (latlng) {
1518 return this.projection.project(latlng);
1519 },
1520
1521 // @method unproject(point: Point): LatLng
1522 // Given a projected coordinate returns the corresponding LatLng.
1523 // The inverse of `project`.
1524 unproject: function (point) {
1525 return this.projection.unproject(point);
1526 },
1527
1528 // @method scale(zoom: Number): Number
1529 // Returns the scale used when transforming projected coordinates into
1530 // pixel coordinates for a particular zoom. For example, it returns
1531 // `256 * 2^zoom` for Mercator-based CRS.
1532 scale: function (zoom) {
1533 return 256 * Math.pow(2, zoom);
1534 },
1535
1536 // @method zoom(scale: Number): Number
1537 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1538 // factor of `scale`.
1539 zoom: function (scale) {
1540 return Math.log(scale / 256) / Math.LN2;
1541 },
1542
1543 // @method getProjectedBounds(zoom: Number): Bounds
1544 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1545 getProjectedBounds: function (zoom) {
1546 if (this.infinite) { return null; }
1547
1548 var b = this.projection.bounds,
1549 s = this.scale(zoom),
1550 min = this.transformation.transform(b.min, s),
1551 max = this.transformation.transform(b.max, s);
1552
1553 return new Bounds(min, max);
1554 },
1555
1556 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1557 // Returns the distance between two geographical coordinates.
1558
1559 // @property code: String
1560 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1561 //
1562 // @property wrapLng: Number[]
1563 // An array of two numbers defining whether the longitude (horizontal) coordinate
1564 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1565 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1566 //
1567 // @property wrapLat: Number[]
1568 // Like `wrapLng`, but for the latitude (vertical) axis.
1569
1570 // wrapLng: [min, max],
1571 // wrapLat: [min, max],
1572
1573 // @property infinite: Boolean
1574 // If true, the coordinate space will be unbounded (infinite in both axes)
1575 infinite: false,
1576
1577 // @method wrapLatLng(latlng: LatLng): LatLng
1578 // Returns a `LatLng` where lat and lng has been wrapped according to the
1579 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1580 wrapLatLng: function (latlng) {
1581 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1582 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1583 alt = latlng.alt;
1584
1585 return new LatLng(lat, lng, alt);
1586 },
1587
1588 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1589 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1590 // that its center is within the CRS's bounds.
1591 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1592 wrapLatLngBounds: function (bounds) {
1593 var center = bounds.getCenter(),
1594 newCenter = this.wrapLatLng(center),
1595 latShift = center.lat - newCenter.lat,
1596 lngShift = center.lng - newCenter.lng;
1597
1598 if (latShift === 0 && lngShift === 0) {
1599 return bounds;
1600 }
1601
1602 var sw = bounds.getSouthWest(),
1603 ne = bounds.getNorthEast(),
1604 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1605 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1606
1607 return new LatLngBounds(newSw, newNe);
1608 }
1609};
1610
1611/*
1612 * @namespace CRS
1613 * @crs L.CRS.Earth
1614 *
1615 * Serves as the base for CRS that are global such that they cover the earth.
1616 * Can only be used as the base for other CRS and cannot be used directly,
1617 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1618 * meters.
1619 */
1620
1621var Earth = extend({}, CRS, {
1622 wrapLng: [-180, 180],
1623
1624 // Mean Earth Radius, as recommended for use by
1625 // the International Union of Geodesy and Geophysics,
1626 // see http://rosettacode.org/wiki/Haversine_formula
1627 R: 6371000,
1628
1629 // distance between two geographical points using spherical law of cosines approximation
1630 distance: function (latlng1, latlng2) {
1631 var rad = Math.PI / 180,
1632 lat1 = latlng1.lat * rad,
1633 lat2 = latlng2.lat * rad,
1634 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1635 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1636 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1637 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1638 return this.R * c;
1639 }
1640});
1641
1642/*
1643 * @namespace Projection
1644 * @projection L.Projection.SphericalMercator
1645 *
1646 * Spherical Mercator projection — the most common projection for online maps,
1647 * used by almost all free and commercial tile providers. Assumes that Earth is
1648 * a sphere. Used by the `EPSG:3857` CRS.
1649 */
1650
1651var earthRadius = 6378137;
1652
1653var SphericalMercator = {
1654
1655 R: earthRadius,
1656 MAX_LATITUDE: 85.0511287798,
1657
1658 project: function (latlng) {
1659 var d = Math.PI / 180,
1660 max = this.MAX_LATITUDE,
1661 lat = Math.max(Math.min(max, latlng.lat), -max),
1662 sin = Math.sin(lat * d);
1663
1664 return new Point(
1665 this.R * latlng.lng * d,
1666 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1667 },
1668
1669 unproject: function (point) {
1670 var d = 180 / Math.PI;
1671
1672 return new LatLng(
1673 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1674 point.x * d / this.R);
1675 },
1676
1677 bounds: (function () {
1678 var d = earthRadius * Math.PI;
1679 return new Bounds([-d, -d], [d, d]);
1680 })()
1681};
1682
1683/*
1684 * @class Transformation
1685 * @aka L.Transformation
1686 *
1687 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1688 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1689 * the reverse. Used by Leaflet in its projections code.
1690 *
1691 * @example
1692 *
1693 * ```js
1694 * var transformation = L.transformation(2, 5, -1, 10),
1695 * p = L.point(1, 2),
1696 * p2 = transformation.transform(p), // L.point(7, 8)
1697 * p3 = transformation.untransform(p2); // L.point(1, 2)
1698 * ```
1699 */
1700
1701
1702// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1703// Creates a `Transformation` object with the given coefficients.
1704function Transformation(a, b, c, d) {
1705 if (isArray(a)) {
1706 // use array properties
1707 this._a = a[0];
1708 this._b = a[1];
1709 this._c = a[2];
1710 this._d = a[3];
1711 return;
1712 }
1713 this._a = a;
1714 this._b = b;
1715 this._c = c;
1716 this._d = d;
1717}
1718
1719Transformation.prototype = {
1720 // @method transform(point: Point, scale?: Number): Point
1721 // Returns a transformed point, optionally multiplied by the given scale.
1722 // Only accepts actual `L.Point` instances, not arrays.
1723 transform: function (point, scale) { // (Point, Number) -> Point
1724 return this._transform(point.clone(), scale);
1725 },
1726
1727 // destructive transform (faster)
1728 _transform: function (point, scale) {
1729 scale = scale || 1;
1730 point.x = scale * (this._a * point.x + this._b);
1731 point.y = scale * (this._c * point.y + this._d);
1732 return point;
1733 },
1734
1735 // @method untransform(point: Point, scale?: Number): Point
1736 // Returns the reverse transformation of the given point, optionally divided
1737 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1738 untransform: function (point, scale) {
1739 scale = scale || 1;
1740 return new Point(
1741 (point.x / scale - this._b) / this._a,
1742 (point.y / scale - this._d) / this._c);
1743 }
1744};
1745
1746// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747
1748// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1749// Instantiates a Transformation object with the given coefficients.
1750
1751// @alternative
1752// @factory L.transformation(coefficients: Array): Transformation
1753// Expects an coefficients array of the form
1754// `[a: Number, b: Number, c: Number, d: Number]`.
1755
1756function toTransformation(a, b, c, d) {
1757 return new Transformation(a, b, c, d);
1758}
1759
1760/*
1761 * @namespace CRS
1762 * @crs L.CRS.EPSG3857
1763 *
1764 * The most common CRS for online maps, used by almost all free and commercial
1765 * tile providers. Uses Spherical Mercator projection. Set in by default in
1766 * Map's `crs` option.
1767 */
1768
1769var EPSG3857 = extend({}, Earth, {
1770 code: 'EPSG:3857',
1771 projection: SphericalMercator,
1772
1773 transformation: (function () {
1774 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1775 return toTransformation(scale, 0.5, -scale, 0.5);
1776 }())
1777});
1778
1779var EPSG900913 = extend({}, EPSG3857, {
1780 code: 'EPSG:900913'
1781});
1782
1783// @namespace SVG; @section
1784// There are several static functions which can be called without instantiating L.SVG:
1785
1786// @function create(name: String): SVGElement
1787// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1788// corresponding to the class name passed. For example, using 'line' will return
1789// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1790function svgCreate(name) {
1791 return document.createElementNS('http://www.w3.org/2000/svg', name);
1792}
1793
1794// @function pointsToPath(rings: Point[], closed: Boolean): String
1795// Generates a SVG path string for multiple rings, with each ring turning
1796// into "M..L..L.." instructions
1797function pointsToPath(rings, closed) {
1798 var str = '',
1799 i, j, len, len2, points, p;
1800
1801 for (i = 0, len = rings.length; i < len; i++) {
1802 points = rings[i];
1803
1804 for (j = 0, len2 = points.length; j < len2; j++) {
1805 p = points[j];
1806 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1807 }
1808
1809 // closes the ring for polygons; "x" is VML syntax
1810 str += closed ? (svg ? 'z' : 'x') : '';
1811 }
1812
1813 // SVG complains about empty path strings
1814 return str || 'M0 0';
1815}
1816
1817/*
1818 * @namespace Browser
1819 * @aka L.Browser
1820 *
1821 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1822 *
1823 * @example
1824 *
1825 * ```js
1826 * if (L.Browser.ielt9) {
1827 * alert('Upgrade your browser, dude!');
1828 * }
1829 * ```
1830 */
1831
1832var style$1 = document.documentElement.style;
1833
1834// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1835var ie = 'ActiveXObject' in window;
1836
1837// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1838var ielt9 = ie && !document.addEventListener;
1839
1840// @property edge: Boolean; `true` for the Edge web browser.
1841var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1842
1843// @property webkit: Boolean;
1844// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1845var webkit = userAgentContains('webkit');
1846
1847// @property android: Boolean
1848// `true` for any browser running on an Android platform.
1849var android = userAgentContains('android');
1850
1851// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1852var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1853
1854/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1855var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1856// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1857var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1858
1859// @property opera: Boolean; `true` for the Opera browser
1860var opera = !!window.opera;
1861
1862// @property chrome: Boolean; `true` for the Chrome browser.
1863var chrome = userAgentContains('chrome');
1864
1865// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1866var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1867
1868// @property safari: Boolean; `true` for the Safari browser.
1869var safari = !chrome && userAgentContains('safari');
1870
1871var phantom = userAgentContains('phantom');
1872
1873// @property opera12: Boolean
1874// `true` for the Opera browser supporting CSS transforms (version 12 or later).
1875var opera12 = 'OTransition' in style$1;
1876
1877// @property win: Boolean; `true` when the browser is running in a Windows platform
1878var win = navigator.platform.indexOf('Win') === 0;
1879
1880// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1881var ie3d = ie && ('transition' in style$1);
1882
1883// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1884var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1885
1886// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1887var gecko3d = 'MozPerspective' in style$1;
1888
1889// @property any3d: Boolean
1890// `true` for all browsers supporting CSS transforms.
1891var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1892
1893// @property mobile: Boolean; `true` for all browsers running in a mobile device.
1894var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1895
1896// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1897var mobileWebkit = mobile && webkit;
1898
1899// @property mobileWebkit3d: Boolean
1900// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1901var mobileWebkit3d = mobile && webkit3d;
1902
1903// @property msPointer: Boolean
1904// `true` for browsers implementing the Microsoft touch events model (notably IE10).
1905var msPointer = !window.PointerEvent && window.MSPointerEvent;
1906
1907// @property pointer: Boolean
1908// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1909var pointer = !!(window.PointerEvent || msPointer);
1910
1911// @property touch: Boolean
1912// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1913// This does not necessarily mean that the browser is running in a computer with
1914// a touchscreen, it only means that the browser is capable of understanding
1915// touch events.
1916var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1917 (window.DocumentTouch && document instanceof window.DocumentTouch));
1918
1919// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1920var mobileOpera = mobile && opera;
1921
1922// @property mobileGecko: Boolean
1923// `true` for gecko-based browsers running in a mobile device.
1924var mobileGecko = mobile && gecko;
1925
1926// @property retina: Boolean
1927// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1928var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1929
1930
1931// @property canvas: Boolean
1932// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1933var canvas = (function () {
1934 return !!document.createElement('canvas').getContext;
1935}());
1936
1937// @property svg: Boolean
1938// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1939var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1940
1941// @property vml: Boolean
1942// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1943var vml = !svg && (function () {
1944 try {
1945 var div = document.createElement('div');
1946 div.innerHTML = '<v:shape adj="1"/>';
1947
1948 var shape = div.firstChild;
1949 shape.style.behavior = 'url(#default#VML)';
1950
1951 return shape && (typeof shape.adj === 'object');
1952
1953 } catch (e) {
1954 return false;
1955 }
1956}());
1957
1958
1959function userAgentContains(str) {
1960 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1961}
1962
1963
1964var Browser = (Object.freeze || Object)({
1965 ie: ie,
1966 ielt9: ielt9,
1967 edge: edge,
1968 webkit: webkit,
1969 android: android,
1970 android23: android23,
1971 androidStock: androidStock,
1972 opera: opera,
1973 chrome: chrome,
1974 gecko: gecko,
1975 safari: safari,
1976 phantom: phantom,
1977 opera12: opera12,
1978 win: win,
1979 ie3d: ie3d,
1980 webkit3d: webkit3d,
1981 gecko3d: gecko3d,
1982 any3d: any3d,
1983 mobile: mobile,
1984 mobileWebkit: mobileWebkit,
1985 mobileWebkit3d: mobileWebkit3d,
1986 msPointer: msPointer,
1987 pointer: pointer,
1988 touch: touch,
1989 mobileOpera: mobileOpera,
1990 mobileGecko: mobileGecko,
1991 retina: retina,
1992 canvas: canvas,
1993 svg: svg,
1994 vml: vml
1995});
1996
1997/*
1998 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1999 */
2000
2001
2002var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
2003var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
2004var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
2005var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2006var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2007
2008var _pointers = {};
2009var _pointerDocListener = false;
2010
2011// DomEvent.DoubleTap needs to know about this
2012var _pointersCount = 0;
2013
2014// Provides a touch events wrapper for (ms)pointer events.
2015// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2016
2017function addPointerListener(obj, type, handler, id) {
2018 if (type === 'touchstart') {
2019 _addPointerStart(obj, handler, id);
2020
2021 } else if (type === 'touchmove') {
2022 _addPointerMove(obj, handler, id);
2023
2024 } else if (type === 'touchend') {
2025 _addPointerEnd(obj, handler, id);
2026 }
2027
2028 return this;
2029}
2030
2031function removePointerListener(obj, type, id) {
2032 var handler = obj['_leaflet_' + type + id];
2033
2034 if (type === 'touchstart') {
2035 obj.removeEventListener(POINTER_DOWN, handler, false);
2036
2037 } else if (type === 'touchmove') {
2038 obj.removeEventListener(POINTER_MOVE, handler, false);
2039
2040 } else if (type === 'touchend') {
2041 obj.removeEventListener(POINTER_UP, handler, false);
2042 obj.removeEventListener(POINTER_CANCEL, handler, false);
2043 }
2044
2045 return this;
2046}
2047
2048function _addPointerStart(obj, handler, id) {
2049 var onDown = bind(function (e) {
2050 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2051 // In IE11, some touch events needs to fire for form controls, or
2052 // the controls will stop working. We keep a whitelist of tag names that
2053 // need these events. For other target tags, we prevent default on the event.
2054 if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2055 preventDefault(e);
2056 } else {
2057 return;
2058 }
2059 }
2060
2061 _handlePointer(e, handler);
2062 });
2063
2064 obj['_leaflet_touchstart' + id] = onDown;
2065 obj.addEventListener(POINTER_DOWN, onDown, false);
2066
2067 // need to keep track of what pointers and how many are active to provide e.touches emulation
2068 if (!_pointerDocListener) {
2069 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2070 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2071 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2072 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2073 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2074
2075 _pointerDocListener = true;
2076 }
2077}
2078
2079function _globalPointerDown(e) {
2080 _pointers[e.pointerId] = e;
2081 _pointersCount++;
2082}
2083
2084function _globalPointerMove(e) {
2085 if (_pointers[e.pointerId]) {
2086 _pointers[e.pointerId] = e;
2087 }
2088}
2089
2090function _globalPointerUp(e) {
2091 delete _pointers[e.pointerId];
2092 _pointersCount--;
2093}
2094
2095function _handlePointer(e, handler) {
2096 e.touches = [];
2097 for (var i in _pointers) {
2098 e.touches.push(_pointers[i]);
2099 }
2100 e.changedTouches = [e];
2101
2102 handler(e);
2103}
2104
2105function _addPointerMove(obj, handler, id) {
2106 var onMove = function (e) {
2107 // don't fire touch moves when mouse isn't down
2108 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2109
2110 _handlePointer(e, handler);
2111 };
2112
2113 obj['_leaflet_touchmove' + id] = onMove;
2114 obj.addEventListener(POINTER_MOVE, onMove, false);
2115}
2116
2117function _addPointerEnd(obj, handler, id) {
2118 var onUp = function (e) {
2119 _handlePointer(e, handler);
2120 };
2121
2122 obj['_leaflet_touchend' + id] = onUp;
2123 obj.addEventListener(POINTER_UP, onUp, false);
2124 obj.addEventListener(POINTER_CANCEL, onUp, false);
2125}
2126
2127/*
2128 * Extends the event handling code with double tap support for mobile browsers.
2129 */
2130
2131var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2132var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2133var _pre = '_leaflet_';
2134
2135// inspired by Zepto touch code by Thomas Fuchs
2136function addDoubleTapListener(obj, handler, id) {
2137 var last, touch$$1,
2138 doubleTap = false,
2139 delay = 250;
2140
2141 function onTouchStart(e) {
2142 var count;
2143
2144 if (pointer) {
2145 if ((!edge) || e.pointerType === 'mouse') { return; }
2146 count = _pointersCount;
2147 } else {
2148 count = e.touches.length;
2149 }
2150
2151 if (count > 1) { return; }
2152
2153 var now = Date.now(),
2154 delta = now - (last || now);
2155
2156 touch$$1 = e.touches ? e.touches[0] : e;
2157 doubleTap = (delta > 0 && delta <= delay);
2158 last = now;
2159 }
2160
2161 function onTouchEnd(e) {
2162 if (doubleTap && !touch$$1.cancelBubble) {
2163 if (pointer) {
2164 if ((!edge) || e.pointerType === 'mouse') { return; }
2165 // work around .type being readonly with MSPointer* events
2166 var newTouch = {},
2167 prop, i;
2168
2169 for (i in touch$$1) {
2170 prop = touch$$1[i];
2171 newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2172 }
2173 touch$$1 = newTouch;
2174 }
2175 touch$$1.type = 'dblclick';
2176 touch$$1.button = 0;
2177 handler(touch$$1);
2178 last = null;
2179 }
2180 }
2181
2182 obj[_pre + _touchstart + id] = onTouchStart;
2183 obj[_pre + _touchend + id] = onTouchEnd;
2184 obj[_pre + 'dblclick' + id] = handler;
2185
2186 obj.addEventListener(_touchstart, onTouchStart, false);
2187 obj.addEventListener(_touchend, onTouchEnd, false);
2188
2189 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2190 // the browser doesn't fire touchend/pointerup events but does fire
2191 // native dblclicks. See #4127.
2192 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2193 obj.addEventListener('dblclick', handler, false);
2194
2195 return this;
2196}
2197
2198function removeDoubleTapListener(obj, id) {
2199 var touchstart = obj[_pre + _touchstart + id],
2200 touchend = obj[_pre + _touchend + id],
2201 dblclick = obj[_pre + 'dblclick' + id];
2202
2203 obj.removeEventListener(_touchstart, touchstart, false);
2204 obj.removeEventListener(_touchend, touchend, false);
2205 if (!edge) {
2206 obj.removeEventListener('dblclick', dblclick, false);
2207 }
2208
2209 return this;
2210}
2211
2212/*
2213 * @namespace DomUtil
2214 *
2215 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2216 * tree, used by Leaflet internally.
2217 *
2218 * Most functions expecting or returning a `HTMLElement` also work for
2219 * SVG elements. The only difference is that classes refer to CSS classes
2220 * in HTML and SVG classes in SVG.
2221 */
2222
2223
2224// @property TRANSFORM: String
2225// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2226var TRANSFORM = testProp(
2227 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2228
2229// webkitTransition comes first because some browser versions that drop vendor prefix don't do
2230// the same for the transitionend event, in particular the Android 4.1 stock browser
2231
2232// @property TRANSITION: String
2233// Vendor-prefixed transition style name.
2234var TRANSITION = testProp(
2235 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2236
2237// @property TRANSITION_END: String
2238// Vendor-prefixed transitionend event name.
2239var TRANSITION_END =
2240 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2241
2242
2243// @function get(id: String|HTMLElement): HTMLElement
2244// Returns an element given its DOM id, or returns the element itself
2245// if it was passed directly.
2246function get(id) {
2247 return typeof id === 'string' ? document.getElementById(id) : id;
2248}
2249
2250// @function getStyle(el: HTMLElement, styleAttrib: String): String
2251// Returns the value for a certain style attribute on an element,
2252// including computed values or values set through CSS.
2253function getStyle(el, style) {
2254 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2255
2256 if ((!value || value === 'auto') && document.defaultView) {
2257 var css = document.defaultView.getComputedStyle(el, null);
2258 value = css ? css[style] : null;
2259 }
2260 return value === 'auto' ? null : value;
2261}
2262
2263// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2264// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2265function create$1(tagName, className, container) {
2266 var el = document.createElement(tagName);
2267 el.className = className || '';
2268
2269 if (container) {
2270 container.appendChild(el);
2271 }
2272 return el;
2273}
2274
2275// @function remove(el: HTMLElement)
2276// Removes `el` from its parent element
2277function remove(el) {
2278 var parent = el.parentNode;
2279 if (parent) {
2280 parent.removeChild(el);
2281 }
2282}
2283
2284// @function empty(el: HTMLElement)
2285// Removes all of `el`'s children elements from `el`
2286function empty(el) {
2287 while (el.firstChild) {
2288 el.removeChild(el.firstChild);
2289 }
2290}
2291
2292// @function toFront(el: HTMLElement)
2293// Makes `el` the last child of its parent, so it renders in front of the other children.
2294function toFront(el) {
2295 var parent = el.parentNode;
2296 if (parent && parent.lastChild !== el) {
2297 parent.appendChild(el);
2298 }
2299}
2300
2301// @function toBack(el: HTMLElement)
2302// Makes `el` the first child of its parent, so it renders behind the other children.
2303function toBack(el) {
2304 var parent = el.parentNode;
2305 if (parent && parent.firstChild !== el) {
2306 parent.insertBefore(el, parent.firstChild);
2307 }
2308}
2309
2310// @function hasClass(el: HTMLElement, name: String): Boolean
2311// Returns `true` if the element's class attribute contains `name`.
2312function hasClass(el, name) {
2313 if (el.classList !== undefined) {
2314 return el.classList.contains(name);
2315 }
2316 var className = getClass(el);
2317 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2318}
2319
2320// @function addClass(el: HTMLElement, name: String)
2321// Adds `name` to the element's class attribute.
2322function addClass(el, name) {
2323 if (el.classList !== undefined) {
2324 var classes = splitWords(name);
2325 for (var i = 0, len = classes.length; i < len; i++) {
2326 el.classList.add(classes[i]);
2327 }
2328 } else if (!hasClass(el, name)) {
2329 var className = getClass(el);
2330 setClass(el, (className ? className + ' ' : '') + name);
2331 }
2332}
2333
2334// @function removeClass(el: HTMLElement, name: String)
2335// Removes `name` from the element's class attribute.
2336function removeClass(el, name) {
2337 if (el.classList !== undefined) {
2338 el.classList.remove(name);
2339 } else {
2340 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2341 }
2342}
2343
2344// @function setClass(el: HTMLElement, name: String)
2345// Sets the element's class.
2346function setClass(el, name) {
2347 if (el.className.baseVal === undefined) {
2348 el.className = name;
2349 } else {
2350 // in case of SVG element
2351 el.className.baseVal = name;
2352 }
2353}
2354
2355// @function getClass(el: HTMLElement): String
2356// Returns the element's class.
2357function getClass(el) {
2358 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2359 // (Required for linked SVG elements in IE11.)
2360 if (el.correspondingElement) {
2361 el = el.correspondingElement;
2362 }
2363 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2364}
2365
2366// @function setOpacity(el: HTMLElement, opacity: Number)
2367// Set the opacity of an element (including old IE support).
2368// `opacity` must be a number from `0` to `1`.
2369function setOpacity(el, value) {
2370 if ('opacity' in el.style) {
2371 el.style.opacity = value;
2372 } else if ('filter' in el.style) {
2373 _setOpacityIE(el, value);
2374 }
2375}
2376
2377function _setOpacityIE(el, value) {
2378 var filter = false,
2379 filterName = 'DXImageTransform.Microsoft.Alpha';
2380
2381 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2382 try {
2383 filter = el.filters.item(filterName);
2384 } catch (e) {
2385 // don't set opacity to 1 if we haven't already set an opacity,
2386 // it isn't needed and breaks transparent pngs.
2387 if (value === 1) { return; }
2388 }
2389
2390 value = Math.round(value * 100);
2391
2392 if (filter) {
2393 filter.Enabled = (value !== 100);
2394 filter.Opacity = value;
2395 } else {
2396 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2397 }
2398}
2399
2400// @function testProp(props: String[]): String|false
2401// Goes through the array of style names and returns the first name
2402// that is a valid style name for an element. If no such name is found,
2403// it returns false. Useful for vendor-prefixed styles like `transform`.
2404function testProp(props) {
2405 var style = document.documentElement.style;
2406
2407 for (var i = 0; i < props.length; i++) {
2408 if (props[i] in style) {
2409 return props[i];
2410 }
2411 }
2412 return false;
2413}
2414
2415// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2416// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2417// and optionally scaled by `scale`. Does not have an effect if the
2418// browser doesn't support 3D CSS transforms.
2419function setTransform(el, offset, scale) {
2420 var pos = offset || new Point(0, 0);
2421
2422 el.style[TRANSFORM] =
2423 (ie3d ?
2424 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2425 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2426 (scale ? ' scale(' + scale + ')' : '');
2427}
2428
2429// @function setPosition(el: HTMLElement, position: Point)
2430// Sets the position of `el` to coordinates specified by `position`,
2431// using CSS translate or top/left positioning depending on the browser
2432// (used by Leaflet internally to position its layers).
2433function setPosition(el, point) {
2434
2435 /*eslint-disable */
2436 el._leaflet_pos = point;
2437 /* eslint-enable */
2438
2439 if (any3d) {
2440 setTransform(el, point);
2441 } else {
2442 el.style.left = point.x + 'px';
2443 el.style.top = point.y + 'px';
2444 }
2445}
2446
2447// @function getPosition(el: HTMLElement): Point
2448// Returns the coordinates of an element previously positioned with setPosition.
2449function getPosition(el) {
2450 // this method is only used for elements previously positioned using setPosition,
2451 // so it's safe to cache the position for performance
2452
2453 return el._leaflet_pos || new Point(0, 0);
2454}
2455
2456// @function disableTextSelection()
2457// Prevents the user from generating `selectstart` DOM events, usually generated
2458// when the user drags the mouse through a page with text. Used internally
2459// by Leaflet to override the behaviour of any click-and-drag interaction on
2460// the map. Affects drag interactions on the whole document.
2461
2462// @function enableTextSelection()
2463// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2464var disableTextSelection;
2465var enableTextSelection;
2466var _userSelect;
2467if ('onselectstart' in document) {
2468 disableTextSelection = function () {
2469 on(window, 'selectstart', preventDefault);
2470 };
2471 enableTextSelection = function () {
2472 off(window, 'selectstart', preventDefault);
2473 };
2474} else {
2475 var userSelectProperty = testProp(
2476 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2477
2478 disableTextSelection = function () {
2479 if (userSelectProperty) {
2480 var style = document.documentElement.style;
2481 _userSelect = style[userSelectProperty];
2482 style[userSelectProperty] = 'none';
2483 }
2484 };
2485 enableTextSelection = function () {
2486 if (userSelectProperty) {
2487 document.documentElement.style[userSelectProperty] = _userSelect;
2488 _userSelect = undefined;
2489 }
2490 };
2491}
2492
2493// @function disableImageDrag()
2494// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2495// for `dragstart` DOM events, usually generated when the user drags an image.
2496function disableImageDrag() {
2497 on(window, 'dragstart', preventDefault);
2498}
2499
2500// @function enableImageDrag()
2501// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2502function enableImageDrag() {
2503 off(window, 'dragstart', preventDefault);
2504}
2505
2506var _outlineElement;
2507var _outlineStyle;
2508// @function preventOutline(el: HTMLElement)
2509// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2510// of the element `el` invisible. Used internally by Leaflet to prevent
2511// focusable elements from displaying an outline when the user performs a
2512// drag interaction on them.
2513function preventOutline(element) {
2514 while (element.tabIndex === -1) {
2515 element = element.parentNode;
2516 }
2517 if (!element.style) { return; }
2518 restoreOutline();
2519 _outlineElement = element;
2520 _outlineStyle = element.style.outline;
2521 element.style.outline = 'none';
2522 on(window, 'keydown', restoreOutline);
2523}
2524
2525// @function restoreOutline()
2526// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2527function restoreOutline() {
2528 if (!_outlineElement) { return; }
2529 _outlineElement.style.outline = _outlineStyle;
2530 _outlineElement = undefined;
2531 _outlineStyle = undefined;
2532 off(window, 'keydown', restoreOutline);
2533}
2534
2535// @function getSizedParentNode(el: HTMLElement): HTMLElement
2536// Finds the closest parent node which size (width and height) is not null.
2537function getSizedParentNode(element) {
2538 do {
2539 element = element.parentNode;
2540 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2541 return element;
2542}
2543
2544// @function getScale(el: HTMLElement): Object
2545// Computes the CSS scale currently applied on the element.
2546// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2547// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2548function getScale(element) {
2549 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2550
2551 return {
2552 x: rect.width / element.offsetWidth || 1,
2553 y: rect.height / element.offsetHeight || 1,
2554 boundingClientRect: rect
2555 };
2556}
2557
2558
2559var DomUtil = (Object.freeze || Object)({
2560 TRANSFORM: TRANSFORM,
2561 TRANSITION: TRANSITION,
2562 TRANSITION_END: TRANSITION_END,
2563 get: get,
2564 getStyle: getStyle,
2565 create: create$1,
2566 remove: remove,
2567 empty: empty,
2568 toFront: toFront,
2569 toBack: toBack,
2570 hasClass: hasClass,
2571 addClass: addClass,
2572 removeClass: removeClass,
2573 setClass: setClass,
2574 getClass: getClass,
2575 setOpacity: setOpacity,
2576 testProp: testProp,
2577 setTransform: setTransform,
2578 setPosition: setPosition,
2579 getPosition: getPosition,
2580 disableTextSelection: disableTextSelection,
2581 enableTextSelection: enableTextSelection,
2582 disableImageDrag: disableImageDrag,
2583 enableImageDrag: enableImageDrag,
2584 preventOutline: preventOutline,
2585 restoreOutline: restoreOutline,
2586 getSizedParentNode: getSizedParentNode,
2587 getScale: getScale
2588});
2589
2590/*
2591 * @namespace DomEvent
2592 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2593 */
2594
2595// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2596
2597// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2598// Adds a listener function (`fn`) to a particular DOM event type of the
2599// element `el`. You can optionally specify the context of the listener
2600// (object the `this` keyword will point to). You can also pass several
2601// space-separated types (e.g. `'click dblclick'`).
2602
2603// @alternative
2604// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2605// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2606function on(obj, types, fn, context) {
2607
2608 if (typeof types === 'object') {
2609 for (var type in types) {
2610 addOne(obj, type, types[type], fn);
2611 }
2612 } else {
2613 types = splitWords(types);
2614
2615 for (var i = 0, len = types.length; i < len; i++) {
2616 addOne(obj, types[i], fn, context);
2617 }
2618 }
2619
2620 return this;
2621}
2622
2623var eventsKey = '_leaflet_events';
2624
2625// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2626// Removes a previously added listener function.
2627// Note that if you passed a custom context to on, you must pass the same
2628// context to `off` in order to remove the listener.
2629
2630// @alternative
2631// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2632// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2633function off(obj, types, fn, context) {
2634
2635 if (typeof types === 'object') {
2636 for (var type in types) {
2637 removeOne(obj, type, types[type], fn);
2638 }
2639 } else if (types) {
2640 types = splitWords(types);
2641
2642 for (var i = 0, len = types.length; i < len; i++) {
2643 removeOne(obj, types[i], fn, context);
2644 }
2645 } else {
2646 for (var j in obj[eventsKey]) {
2647 removeOne(obj, j, obj[eventsKey][j]);
2648 }
2649 delete obj[eventsKey];
2650 }
2651
2652 return this;
2653}
2654
2655function addOne(obj, type, fn, context) {
2656 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2657
2658 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2659
2660 var handler = function (e) {
2661 return fn.call(context || obj, e || window.event);
2662 };
2663
2664 var originalHandler = handler;
2665
2666 if (pointer && type.indexOf('touch') === 0) {
2667 // Needs DomEvent.Pointer.js
2668 addPointerListener(obj, type, handler, id);
2669
2670 } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2671 !(pointer && chrome)) {
2672 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2673 // See #5180
2674 addDoubleTapListener(obj, handler, id);
2675
2676 } else if ('addEventListener' in obj) {
2677
2678 if (type === 'mousewheel') {
2679 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2680
2681 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2682 handler = function (e) {
2683 e = e || window.event;
2684 if (isExternalTarget(obj, e)) {
2685 originalHandler(e);
2686 }
2687 };
2688 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2689
2690 } else {
2691 if (type === 'click' && android) {
2692 handler = function (e) {
2693 filterClick(e, originalHandler);
2694 };
2695 }
2696 obj.addEventListener(type, handler, false);
2697 }
2698
2699 } else if ('attachEvent' in obj) {
2700 obj.attachEvent('on' + type, handler);
2701 }
2702
2703 obj[eventsKey] = obj[eventsKey] || {};
2704 obj[eventsKey][id] = handler;
2705}
2706
2707function removeOne(obj, type, fn, context) {
2708
2709 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2710 handler = obj[eventsKey] && obj[eventsKey][id];
2711
2712 if (!handler) { return this; }
2713
2714 if (pointer && type.indexOf('touch') === 0) {
2715 removePointerListener(obj, type, id);
2716
2717 } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2718 !(pointer && chrome)) {
2719 removeDoubleTapListener(obj, id);
2720
2721 } else if ('removeEventListener' in obj) {
2722
2723 if (type === 'mousewheel') {
2724 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2725
2726 } else {
2727 obj.removeEventListener(
2728 type === 'mouseenter' ? 'mouseover' :
2729 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2730 }
2731
2732 } else if ('detachEvent' in obj) {
2733 obj.detachEvent('on' + type, handler);
2734 }
2735
2736 obj[eventsKey][id] = null;
2737}
2738
2739// @function stopPropagation(ev: DOMEvent): this
2740// Stop the given event from propagation to parent elements. Used inside the listener functions:
2741// ```js
2742// L.DomEvent.on(div, 'click', function (ev) {
2743// L.DomEvent.stopPropagation(ev);
2744// });
2745// ```
2746function stopPropagation(e) {
2747
2748 if (e.stopPropagation) {
2749 e.stopPropagation();
2750 } else if (e.originalEvent) { // In case of Leaflet event.
2751 e.originalEvent._stopped = true;
2752 } else {
2753 e.cancelBubble = true;
2754 }
2755 skipped(e);
2756
2757 return this;
2758}
2759
2760// @function disableScrollPropagation(el: HTMLElement): this
2761// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2762function disableScrollPropagation(el) {
2763 addOne(el, 'mousewheel', stopPropagation);
2764 return this;
2765}
2766
2767// @function disableClickPropagation(el: HTMLElement): this
2768// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2769// `'mousedown'` and `'touchstart'` events (plus browser variants).
2770function disableClickPropagation(el) {
2771 on(el, 'mousedown touchstart dblclick', stopPropagation);
2772 addOne(el, 'click', fakeStop);
2773 return this;
2774}
2775
2776// @function preventDefault(ev: DOMEvent): this
2777// Prevents the default action of the DOM Event `ev` from happening (such as
2778// following a link in the href of the a element, or doing a POST request
2779// with page reload when a `<form>` is submitted).
2780// Use it inside listener functions.
2781function preventDefault(e) {
2782 if (e.preventDefault) {
2783 e.preventDefault();
2784 } else {
2785 e.returnValue = false;
2786 }
2787 return this;
2788}
2789
2790// @function stop(ev: DOMEvent): this
2791// Does `stopPropagation` and `preventDefault` at the same time.
2792function stop(e) {
2793 preventDefault(e);
2794 stopPropagation(e);
2795 return this;
2796}
2797
2798// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2799// Gets normalized mouse position from a DOM event relative to the
2800// `container` (border excluded) or to the whole page if not specified.
2801function getMousePosition(e, container) {
2802 if (!container) {
2803 return new Point(e.clientX, e.clientY);
2804 }
2805
2806 var scale = getScale(container),
2807 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2808
2809 return new Point(
2810 // offset.left/top values are in page scale (like clientX/Y),
2811 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2812 (e.clientX - offset.left) / scale.x - container.clientLeft,
2813 (e.clientY - offset.top) / scale.y - container.clientTop
2814 );
2815}
2816
2817// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2818// and Firefox scrolls device pixels, not CSS pixels
2819var wheelPxFactor =
2820 (win && chrome) ? 2 * window.devicePixelRatio :
2821 gecko ? window.devicePixelRatio : 1;
2822
2823// @function getWheelDelta(ev: DOMEvent): Number
2824// Gets normalized wheel delta from a mousewheel DOM event, in vertical
2825// pixels scrolled (negative if scrolling down).
2826// Events from pointing devices without precise scrolling are mapped to
2827// a best guess of 60 pixels.
2828function getWheelDelta(e) {
2829 return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2830 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2831 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2832 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2833 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2834 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2835 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2836 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2837 0;
2838}
2839
2840var skipEvents = {};
2841
2842function fakeStop(e) {
2843 // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2844 skipEvents[e.type] = true;
2845}
2846
2847function skipped(e) {
2848 var events = skipEvents[e.type];
2849 // reset when checking, as it's only used in map container and propagates outside of the map
2850 skipEvents[e.type] = false;
2851 return events;
2852}
2853
2854// check if element really left/entered the event target (for mouseenter/mouseleave)
2855function isExternalTarget(el, e) {
2856
2857 var related = e.relatedTarget;
2858
2859 if (!related) { return true; }
2860
2861 try {
2862 while (related && (related !== el)) {
2863 related = related.parentNode;
2864 }
2865 } catch (err) {
2866 return false;
2867 }
2868 return (related !== el);
2869}
2870
2871var lastClick;
2872
2873// this is a horrible workaround for a bug in Android where a single touch triggers two click events
2874function filterClick(e, handler) {
2875 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2876 elapsed = lastClick && (timeStamp - lastClick);
2877
2878 // are they closer together than 500ms yet more than 100ms?
2879 // Android typically triggers them ~300ms apart while multiple listeners
2880 // on the same event should be triggered far faster;
2881 // or check if click is simulated on the element, and if it is, reject any non-simulated events
2882
2883 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2884 stop(e);
2885 return;
2886 }
2887 lastClick = timeStamp;
2888
2889 handler(e);
2890}
2891
2892
2893
2894
2895var DomEvent = (Object.freeze || Object)({
2896 on: on,
2897 off: off,
2898 stopPropagation: stopPropagation,
2899 disableScrollPropagation: disableScrollPropagation,
2900 disableClickPropagation: disableClickPropagation,
2901 preventDefault: preventDefault,
2902 stop: stop,
2903 getMousePosition: getMousePosition,
2904 getWheelDelta: getWheelDelta,
2905 fakeStop: fakeStop,
2906 skipped: skipped,
2907 isExternalTarget: isExternalTarget,
2908 addListener: on,
2909 removeListener: off
2910});
2911
2912/*
2913 * @class PosAnimation
2914 * @aka L.PosAnimation
2915 * @inherits Evented
2916 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2917 *
2918 * @example
2919 * ```js
2920 * var fx = new L.PosAnimation();
2921 * fx.run(el, [300, 500], 0.5);
2922 * ```
2923 *
2924 * @constructor L.PosAnimation()
2925 * Creates a `PosAnimation` object.
2926 *
2927 */
2928
2929var PosAnimation = Evented.extend({
2930
2931 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2932 // Run an animation of a given element to a new position, optionally setting
2933 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2934 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2935 // `0.5` by default).
2936 run: function (el, newPos, duration, easeLinearity) {
2937 this.stop();
2938
2939 this._el = el;
2940 this._inProgress = true;
2941 this._duration = duration || 0.25;
2942 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2943
2944 this._startPos = getPosition(el);
2945 this._offset = newPos.subtract(this._startPos);
2946 this._startTime = +new Date();
2947
2948 // @event start: Event
2949 // Fired when the animation starts
2950 this.fire('start');
2951
2952 this._animate();
2953 },
2954
2955 // @method stop()
2956 // Stops the animation (if currently running).
2957 stop: function () {
2958 if (!this._inProgress) { return; }
2959
2960 this._step(true);
2961 this._complete();
2962 },
2963
2964 _animate: function () {
2965 // animation loop
2966 this._animId = requestAnimFrame(this._animate, this);
2967 this._step();
2968 },
2969
2970 _step: function (round) {
2971 var elapsed = (+new Date()) - this._startTime,
2972 duration = this._duration * 1000;
2973
2974 if (elapsed < duration) {
2975 this._runFrame(this._easeOut(elapsed / duration), round);
2976 } else {
2977 this._runFrame(1);
2978 this._complete();
2979 }
2980 },
2981
2982 _runFrame: function (progress, round) {
2983 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2984 if (round) {
2985 pos._round();
2986 }
2987 setPosition(this._el, pos);
2988
2989 // @event step: Event
2990 // Fired continuously during the animation.
2991 this.fire('step');
2992 },
2993
2994 _complete: function () {
2995 cancelAnimFrame(this._animId);
2996
2997 this._inProgress = false;
2998 // @event end: Event
2999 // Fired when the animation ends.
3000 this.fire('end');
3001 },
3002
3003 _easeOut: function (t) {
3004 return 1 - Math.pow(1 - t, this._easeOutPower);
3005 }
3006});
3007
3008/*
3009 * @class Map
3010 * @aka L.Map
3011 * @inherits Evented
3012 *
3013 * The central class of the API — it is used to create a map on a page and manipulate it.
3014 *
3015 * @example
3016 *
3017 * ```js
3018 * // initialize the map on the "map" div with a given center and zoom
3019 * var map = L.map('map', {
3020 * center: [51.505, -0.09],
3021 * zoom: 13
3022 * });
3023 * ```
3024 *
3025 */
3026
3027var Map = Evented.extend({
3028
3029 options: {
3030 // @section Map State Options
3031 // @option crs: CRS = L.CRS.EPSG3857
3032 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3033 // sure what it means.
3034 crs: EPSG3857,
3035
3036 // @option center: LatLng = undefined
3037 // Initial geographic center of the map
3038 center: undefined,
3039
3040 // @option zoom: Number = undefined
3041 // Initial map zoom level
3042 zoom: undefined,
3043
3044 // @option minZoom: Number = *
3045 // Minimum zoom level of the map.
3046 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3047 // the lowest of their `minZoom` options will be used instead.
3048 minZoom: undefined,
3049
3050 // @option maxZoom: Number = *
3051 // Maximum zoom level of the map.
3052 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3053 // the highest of their `maxZoom` options will be used instead.
3054 maxZoom: undefined,
3055
3056 // @option layers: Layer[] = []
3057 // Array of layers that will be added to the map initially
3058 layers: [],
3059
3060 // @option maxBounds: LatLngBounds = null
3061 // When this option is set, the map restricts the view to the given
3062 // geographical bounds, bouncing the user back if the user tries to pan
3063 // outside the view. To set the restriction dynamically, use
3064 // [`setMaxBounds`](#map-setmaxbounds) method.
3065 maxBounds: undefined,
3066
3067 // @option renderer: Renderer = *
3068 // The default method for drawing vector layers on the map. `L.SVG`
3069 // or `L.Canvas` by default depending on browser support.
3070 renderer: undefined,
3071
3072
3073 // @section Animation Options
3074 // @option zoomAnimation: Boolean = true
3075 // Whether the map zoom animation is enabled. By default it's enabled
3076 // in all browsers that support CSS3 Transitions except Android.
3077 zoomAnimation: true,
3078
3079 // @option zoomAnimationThreshold: Number = 4
3080 // Won't animate zoom if the zoom difference exceeds this value.
3081 zoomAnimationThreshold: 4,
3082
3083 // @option fadeAnimation: Boolean = true
3084 // Whether the tile fade animation is enabled. By default it's enabled
3085 // in all browsers that support CSS3 Transitions except Android.
3086 fadeAnimation: true,
3087
3088 // @option markerZoomAnimation: Boolean = true
3089 // Whether markers animate their zoom with the zoom animation, if disabled
3090 // they will disappear for the length of the animation. By default it's
3091 // enabled in all browsers that support CSS3 Transitions except Android.
3092 markerZoomAnimation: true,
3093
3094 // @option transform3DLimit: Number = 2^23
3095 // Defines the maximum size of a CSS translation transform. The default
3096 // value should not be changed unless a web browser positions layers in
3097 // the wrong place after doing a large `panBy`.
3098 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3099
3100 // @section Interaction Options
3101 // @option zoomSnap: Number = 1
3102 // Forces the map's zoom level to always be a multiple of this, particularly
3103 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3104 // By default, the zoom level snaps to the nearest integer; lower values
3105 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3106 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3107 zoomSnap: 1,
3108
3109 // @option zoomDelta: Number = 1
3110 // Controls how much the map's zoom level will change after a
3111 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3112 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3113 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3114 zoomDelta: 1,
3115
3116 // @option trackResize: Boolean = true
3117 // Whether the map automatically handles browser window resize to update itself.
3118 trackResize: true
3119 },
3120
3121 initialize: function (id, options) { // (HTMLElement or String, Object)
3122 options = setOptions(this, options);
3123
3124 // Make sure to assign internal flags at the beginning,
3125 // to avoid inconsistent state in some edge cases.
3126 this._handlers = [];
3127 this._layers = {};
3128 this._zoomBoundLayers = {};
3129 this._sizeChanged = true;
3130
3131 this._initContainer(id);
3132 this._initLayout();
3133
3134 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3135 this._onResize = bind(this._onResize, this);
3136
3137 this._initEvents();
3138
3139 if (options.maxBounds) {
3140 this.setMaxBounds(options.maxBounds);
3141 }
3142
3143 if (options.zoom !== undefined) {
3144 this._zoom = this._limitZoom(options.zoom);
3145 }
3146
3147 if (options.center && options.zoom !== undefined) {
3148 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3149 }
3150
3151 this.callInitHooks();
3152
3153 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3154 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3155 this.options.zoomAnimation;
3156
3157 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3158 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3159 if (this._zoomAnimated) {
3160 this._createAnimProxy();
3161 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3162 }
3163
3164 this._addLayers(this.options.layers);
3165 },
3166
3167
3168 // @section Methods for modifying map state
3169
3170 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3171 // Sets the view of the map (geographical center and zoom) with the given
3172 // animation options.
3173 setView: function (center, zoom, options) {
3174
3175 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3176 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3177 options = options || {};
3178
3179 this._stop();
3180
3181 if (this._loaded && !options.reset && options !== true) {
3182
3183 if (options.animate !== undefined) {
3184 options.zoom = extend({animate: options.animate}, options.zoom);
3185 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3186 }
3187
3188 // try animating pan or zoom
3189 var moved = (this._zoom !== zoom) ?
3190 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3191 this._tryAnimatedPan(center, options.pan);
3192
3193 if (moved) {
3194 // prevent resize handler call, the view will refresh after animation anyway
3195 clearTimeout(this._sizeTimer);
3196 return this;
3197 }
3198 }
3199
3200 // animation didn't start, just reset the map view
3201 this._resetView(center, zoom);
3202
3203 return this;
3204 },
3205
3206 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3207 // Sets the zoom of the map.
3208 setZoom: function (zoom, options) {
3209 if (!this._loaded) {
3210 this._zoom = zoom;
3211 return this;
3212 }
3213 return this.setView(this.getCenter(), zoom, {zoom: options});
3214 },
3215
3216 // @method zoomIn(delta?: Number, options?: Zoom options): this
3217 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3218 zoomIn: function (delta, options) {
3219 delta = delta || (any3d ? this.options.zoomDelta : 1);
3220 return this.setZoom(this._zoom + delta, options);
3221 },
3222
3223 // @method zoomOut(delta?: Number, options?: Zoom options): this
3224 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3225 zoomOut: function (delta, options) {
3226 delta = delta || (any3d ? this.options.zoomDelta : 1);
3227 return this.setZoom(this._zoom - delta, options);
3228 },
3229
3230 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3231 // Zooms the map while keeping a specified geographical point on the map
3232 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3233 // @alternative
3234 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3235 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3236 setZoomAround: function (latlng, zoom, options) {
3237 var scale = this.getZoomScale(zoom),
3238 viewHalf = this.getSize().divideBy(2),
3239 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3240
3241 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3242 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3243
3244 return this.setView(newCenter, zoom, {zoom: options});
3245 },
3246
3247 _getBoundsCenterZoom: function (bounds, options) {
3248
3249 options = options || {};
3250 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3251
3252 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3253 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3254
3255 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3256
3257 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3258
3259 if (zoom === Infinity) {
3260 return {
3261 center: bounds.getCenter(),
3262 zoom: zoom
3263 };
3264 }
3265
3266 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3267
3268 swPoint = this.project(bounds.getSouthWest(), zoom),
3269 nePoint = this.project(bounds.getNorthEast(), zoom),
3270 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3271
3272 return {
3273 center: center,
3274 zoom: zoom
3275 };
3276 },
3277
3278 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3279 // Sets a map view that contains the given geographical bounds with the
3280 // maximum zoom level possible.
3281 fitBounds: function (bounds, options) {
3282
3283 bounds = toLatLngBounds(bounds);
3284
3285 if (!bounds.isValid()) {
3286 throw new Error('Bounds are not valid.');
3287 }
3288
3289 var target = this._getBoundsCenterZoom(bounds, options);
3290 return this.setView(target.center, target.zoom, options);
3291 },
3292
3293 // @method fitWorld(options?: fitBounds options): this
3294 // Sets a map view that mostly contains the whole world with the maximum
3295 // zoom level possible.
3296 fitWorld: function (options) {
3297 return this.fitBounds([[-90, -180], [90, 180]], options);
3298 },
3299
3300 // @method panTo(latlng: LatLng, options?: Pan options): this
3301 // Pans the map to a given center.
3302 panTo: function (center, options) { // (LatLng)
3303 return this.setView(center, this._zoom, {pan: options});
3304 },
3305
3306 // @method panBy(offset: Point, options?: Pan options): this
3307 // Pans the map by a given number of pixels (animated).
3308 panBy: function (offset, options) {
3309 offset = toPoint(offset).round();
3310 options = options || {};
3311
3312 if (!offset.x && !offset.y) {
3313 return this.fire('moveend');
3314 }
3315 // If we pan too far, Chrome gets issues with tiles
3316 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3317 if (options.animate !== true && !this.getSize().contains(offset)) {
3318 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3319 return this;
3320 }
3321
3322 if (!this._panAnim) {
3323 this._panAnim = new PosAnimation();
3324
3325 this._panAnim.on({
3326 'step': this._onPanTransitionStep,
3327 'end': this._onPanTransitionEnd
3328 }, this);
3329 }
3330
3331 // don't fire movestart if animating inertia
3332 if (!options.noMoveStart) {
3333 this.fire('movestart');
3334 }
3335
3336 // animate pan unless animate: false specified
3337 if (options.animate !== false) {
3338 addClass(this._mapPane, 'leaflet-pan-anim');
3339
3340 var newPos = this._getMapPanePos().subtract(offset).round();
3341 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3342 } else {
3343 this._rawPanBy(offset);
3344 this.fire('move').fire('moveend');
3345 }
3346
3347 return this;
3348 },
3349
3350 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3351 // Sets the view of the map (geographical center and zoom) performing a smooth
3352 // pan-zoom animation.
3353 flyTo: function (targetCenter, targetZoom, options) {
3354
3355 options = options || {};
3356 if (options.animate === false || !any3d) {
3357 return this.setView(targetCenter, targetZoom, options);
3358 }
3359
3360 this._stop();
3361
3362 var from = this.project(this.getCenter()),
3363 to = this.project(targetCenter),
3364 size = this.getSize(),
3365 startZoom = this._zoom;
3366
3367 targetCenter = toLatLng(targetCenter);
3368 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3369
3370 var w0 = Math.max(size.x, size.y),
3371 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3372 u1 = (to.distanceTo(from)) || 1,
3373 rho = 1.42,
3374 rho2 = rho * rho;
3375
3376 function r(i) {
3377 var s1 = i ? -1 : 1,
3378 s2 = i ? w1 : w0,
3379 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3380 b1 = 2 * s2 * rho2 * u1,
3381 b = t1 / b1,
3382 sq = Math.sqrt(b * b + 1) - b;
3383
3384 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3385 // thus triggering an infinite loop in flyTo
3386 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3387
3388 return log;
3389 }
3390
3391 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3392 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3393 function tanh(n) { return sinh(n) / cosh(n); }
3394
3395 var r0 = r(0);
3396
3397 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3398 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3399
3400 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3401
3402 var start = Date.now(),
3403 S = (r(1) - r0) / rho,
3404 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3405
3406 function frame() {
3407 var t = (Date.now() - start) / duration,
3408 s = easeOut(t) * S;
3409
3410 if (t <= 1) {
3411 this._flyToFrame = requestAnimFrame(frame, this);
3412
3413 this._move(
3414 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3415 this.getScaleZoom(w0 / w(s), startZoom),
3416 {flyTo: true});
3417
3418 } else {
3419 this
3420 ._move(targetCenter, targetZoom)
3421 ._moveEnd(true);
3422 }
3423 }
3424
3425 this._moveStart(true, options.noMoveStart);
3426
3427 frame.call(this);
3428 return this;
3429 },
3430
3431 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3432 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3433 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3434 flyToBounds: function (bounds, options) {
3435 var target = this._getBoundsCenterZoom(bounds, options);
3436 return this.flyTo(target.center, target.zoom, options);
3437 },
3438
3439 // @method setMaxBounds(bounds: Bounds): this
3440 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3441 setMaxBounds: function (bounds) {
3442 bounds = toLatLngBounds(bounds);
3443
3444 if (!bounds.isValid()) {
3445 this.options.maxBounds = null;
3446 return this.off('moveend', this._panInsideMaxBounds);
3447 } else if (this.options.maxBounds) {
3448 this.off('moveend', this._panInsideMaxBounds);
3449 }
3450
3451 this.options.maxBounds = bounds;
3452
3453 if (this._loaded) {
3454 this._panInsideMaxBounds();
3455 }
3456
3457 return this.on('moveend', this._panInsideMaxBounds);
3458 },
3459
3460 // @method setMinZoom(zoom: Number): this
3461 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3462 setMinZoom: function (zoom) {
3463 var oldZoom = this.options.minZoom;
3464 this.options.minZoom = zoom;
3465
3466 if (this._loaded && oldZoom !== zoom) {
3467 this.fire('zoomlevelschange');
3468
3469 if (this.getZoom() < this.options.minZoom) {
3470 return this.setZoom(zoom);
3471 }
3472 }
3473
3474 return this;
3475 },
3476
3477 // @method setMaxZoom(zoom: Number): this
3478 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3479 setMaxZoom: function (zoom) {
3480 var oldZoom = this.options.maxZoom;
3481 this.options.maxZoom = zoom;
3482
3483 if (this._loaded && oldZoom !== zoom) {
3484 this.fire('zoomlevelschange');
3485
3486 if (this.getZoom() > this.options.maxZoom) {
3487 return this.setZoom(zoom);
3488 }
3489 }
3490
3491 return this;
3492 },
3493
3494 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3495 // 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.
3496 panInsideBounds: function (bounds, options) {
3497 this._enforcingBounds = true;
3498 var center = this.getCenter(),
3499 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3500
3501 if (!center.equals(newCenter)) {
3502 this.panTo(newCenter, options);
3503 }
3504
3505 this._enforcingBounds = false;
3506 return this;
3507 },
3508
3509 // @method panInside(latlng: LatLng, options?: options): this
3510 // Pans the map the minimum amount to make the `latlng` visible. Use
3511 // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3512 // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3513 // If `latlng` is already within the (optionally padded) display bounds,
3514 // the map will not be panned.
3515 panInside: function (latlng, options) {
3516 options = options || {};
3517
3518 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3519 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3520 center = this.getCenter(),
3521 pixelCenter = this.project(center),
3522 pixelPoint = this.project(latlng),
3523 pixelBounds = this.getPixelBounds(),
3524 halfPixelBounds = pixelBounds.getSize().divideBy(2),
3525 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3526
3527 if (!paddedBounds.contains(pixelPoint)) {
3528 this._enforcingBounds = true;
3529 var diff = pixelCenter.subtract(pixelPoint),
3530 newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3531
3532 if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3533 newCenter.x = pixelCenter.x - diff.x;
3534 if (diff.x > 0) {
3535 newCenter.x += halfPixelBounds.x - paddingTL.x;
3536 } else {
3537 newCenter.x -= halfPixelBounds.x - paddingBR.x;
3538 }
3539 }
3540 if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3541 newCenter.y = pixelCenter.y - diff.y;
3542 if (diff.y > 0) {
3543 newCenter.y += halfPixelBounds.y - paddingTL.y;
3544 } else {
3545 newCenter.y -= halfPixelBounds.y - paddingBR.y;
3546 }
3547 }
3548 this.panTo(this.unproject(newCenter), options);
3549 this._enforcingBounds = false;
3550 }
3551 return this;
3552 },
3553
3554 // @method invalidateSize(options: Zoom/pan options): this
3555 // Checks if the map container size changed and updates the map if so —
3556 // call it after you've changed the map size dynamically, also animating
3557 // pan by default. If `options.pan` is `false`, panning will not occur.
3558 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3559 // that it doesn't happen often even if the method is called many
3560 // times in a row.
3561
3562 // @alternative
3563 // @method invalidateSize(animate: Boolean): this
3564 // Checks if the map container size changed and updates the map if so —
3565 // call it after you've changed the map size dynamically, also animating
3566 // pan by default.
3567 invalidateSize: function (options) {
3568 if (!this._loaded) { return this; }
3569
3570 options = extend({
3571 animate: false,
3572 pan: true
3573 }, options === true ? {animate: true} : options);
3574
3575 var oldSize = this.getSize();
3576 this._sizeChanged = true;
3577 this._lastCenter = null;
3578
3579 var newSize = this.getSize(),
3580 oldCenter = oldSize.divideBy(2).round(),
3581 newCenter = newSize.divideBy(2).round(),
3582 offset = oldCenter.subtract(newCenter);
3583
3584 if (!offset.x && !offset.y) { return this; }
3585
3586 if (options.animate && options.pan) {
3587 this.panBy(offset);
3588
3589 } else {
3590 if (options.pan) {
3591 this._rawPanBy(offset);
3592 }
3593
3594 this.fire('move');
3595
3596 if (options.debounceMoveend) {
3597 clearTimeout(this._sizeTimer);
3598 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3599 } else {
3600 this.fire('moveend');
3601 }
3602 }
3603
3604 // @section Map state change events
3605 // @event resize: ResizeEvent
3606 // Fired when the map is resized.
3607 return this.fire('resize', {
3608 oldSize: oldSize,
3609 newSize: newSize
3610 });
3611 },
3612
3613 // @section Methods for modifying map state
3614 // @method stop(): this
3615 // Stops the currently running `panTo` or `flyTo` animation, if any.
3616 stop: function () {
3617 this.setZoom(this._limitZoom(this._zoom));
3618 if (!this.options.zoomSnap) {
3619 this.fire('viewreset');
3620 }
3621 return this._stop();
3622 },
3623
3624 // @section Geolocation methods
3625 // @method locate(options?: Locate options): this
3626 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3627 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3628 // and optionally sets the map view to the user's location with respect to
3629 // detection accuracy (or to the world view if geolocation failed).
3630 // Note that, if your page doesn't use HTTPS, this method will fail in
3631 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3632 // See `Locate options` for more details.
3633 locate: function (options) {
3634
3635 options = this._locateOptions = extend({
3636 timeout: 10000,
3637 watch: false
3638 // setView: false
3639 // maxZoom: <Number>
3640 // maximumAge: 0
3641 // enableHighAccuracy: false
3642 }, options);
3643
3644 if (!('geolocation' in navigator)) {
3645 this._handleGeolocationError({
3646 code: 0,
3647 message: 'Geolocation not supported.'
3648 });
3649 return this;
3650 }
3651
3652 var onResponse = bind(this._handleGeolocationResponse, this),
3653 onError = bind(this._handleGeolocationError, this);
3654
3655 if (options.watch) {
3656 this._locationWatchId =
3657 navigator.geolocation.watchPosition(onResponse, onError, options);
3658 } else {
3659 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3660 }
3661 return this;
3662 },
3663
3664 // @method stopLocate(): this
3665 // Stops watching location previously initiated by `map.locate({watch: true})`
3666 // and aborts resetting the map view if map.locate was called with
3667 // `{setView: true}`.
3668 stopLocate: function () {
3669 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3670 navigator.geolocation.clearWatch(this._locationWatchId);
3671 }
3672 if (this._locateOptions) {
3673 this._locateOptions.setView = false;
3674 }
3675 return this;
3676 },
3677
3678 _handleGeolocationError: function (error) {
3679 var c = error.code,
3680 message = error.message ||
3681 (c === 1 ? 'permission denied' :
3682 (c === 2 ? 'position unavailable' : 'timeout'));
3683
3684 if (this._locateOptions.setView && !this._loaded) {
3685 this.fitWorld();
3686 }
3687
3688 // @section Location events
3689 // @event locationerror: ErrorEvent
3690 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3691 this.fire('locationerror', {
3692 code: c,
3693 message: 'Geolocation error: ' + message + '.'
3694 });
3695 },
3696
3697 _handleGeolocationResponse: function (pos) {
3698 var lat = pos.coords.latitude,
3699 lng = pos.coords.longitude,
3700 latlng = new LatLng(lat, lng),
3701 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3702 options = this._locateOptions;
3703
3704 if (options.setView) {
3705 var zoom = this.getBoundsZoom(bounds);
3706 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3707 }
3708
3709 var data = {
3710 latlng: latlng,
3711 bounds: bounds,
3712 timestamp: pos.timestamp
3713 };
3714
3715 for (var i in pos.coords) {
3716 if (typeof pos.coords[i] === 'number') {
3717 data[i] = pos.coords[i];
3718 }
3719 }
3720
3721 // @event locationfound: LocationEvent
3722 // Fired when geolocation (using the [`locate`](#map-locate) method)
3723 // went successfully.
3724 this.fire('locationfound', data);
3725 },
3726
3727 // TODO Appropriate docs section?
3728 // @section Other Methods
3729 // @method addHandler(name: String, HandlerClass: Function): this
3730 // Adds a new `Handler` to the map, given its name and constructor function.
3731 addHandler: function (name, HandlerClass) {
3732 if (!HandlerClass) { return this; }
3733
3734 var handler = this[name] = new HandlerClass(this);
3735
3736 this._handlers.push(handler);
3737
3738 if (this.options[name]) {
3739 handler.enable();
3740 }
3741
3742 return this;
3743 },
3744
3745 // @method remove(): this
3746 // Destroys the map and clears all related event listeners.
3747 remove: function () {
3748
3749 this._initEvents(true);
3750
3751 if (this._containerId !== this._container._leaflet_id) {
3752 throw new Error('Map container is being reused by another instance');
3753 }
3754
3755 try {
3756 // throws error in IE6-8
3757 delete this._container._leaflet_id;
3758 delete this._containerId;
3759 } catch (e) {
3760 /*eslint-disable */
3761 this._container._leaflet_id = undefined;
3762 /* eslint-enable */
3763 this._containerId = undefined;
3764 }
3765
3766 if (this._locationWatchId !== undefined) {
3767 this.stopLocate();
3768 }
3769
3770 this._stop();
3771
3772 remove(this._mapPane);
3773
3774 if (this._clearControlPos) {
3775 this._clearControlPos();
3776 }
3777 if (this._resizeRequest) {
3778 cancelAnimFrame(this._resizeRequest);
3779 this._resizeRequest = null;
3780 }
3781
3782 this._clearHandlers();
3783
3784 if (this._loaded) {
3785 // @section Map state change events
3786 // @event unload: Event
3787 // Fired when the map is destroyed with [remove](#map-remove) method.
3788 this.fire('unload');
3789 }
3790
3791 var i;
3792 for (i in this._layers) {
3793 this._layers[i].remove();
3794 }
3795 for (i in this._panes) {
3796 remove(this._panes[i]);
3797 }
3798
3799 this._layers = [];
3800 this._panes = [];
3801 delete this._mapPane;
3802 delete this._renderer;
3803
3804 return this;
3805 },
3806
3807 // @section Other Methods
3808 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3809 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3810 // then returns it. The pane is created as a child of `container`, or
3811 // as a child of the main map pane if not set.
3812 createPane: function (name, container) {
3813 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3814 pane = create$1('div', className, container || this._mapPane);
3815
3816 if (name) {
3817 this._panes[name] = pane;
3818 }
3819 return pane;
3820 },
3821
3822 // @section Methods for Getting Map State
3823
3824 // @method getCenter(): LatLng
3825 // Returns the geographical center of the map view
3826 getCenter: function () {
3827 this._checkIfLoaded();
3828
3829 if (this._lastCenter && !this._moved()) {
3830 return this._lastCenter;
3831 }
3832 return this.layerPointToLatLng(this._getCenterLayerPoint());
3833 },
3834
3835 // @method getZoom(): Number
3836 // Returns the current zoom level of the map view
3837 getZoom: function () {
3838 return this._zoom;
3839 },
3840
3841 // @method getBounds(): LatLngBounds
3842 // Returns the geographical bounds visible in the current map view
3843 getBounds: function () {
3844 var bounds = this.getPixelBounds(),
3845 sw = this.unproject(bounds.getBottomLeft()),
3846 ne = this.unproject(bounds.getTopRight());
3847
3848 return new LatLngBounds(sw, ne);
3849 },
3850
3851 // @method getMinZoom(): Number
3852 // 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.
3853 getMinZoom: function () {
3854 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3855 },
3856
3857 // @method getMaxZoom(): Number
3858 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3859 getMaxZoom: function () {
3860 return this.options.maxZoom === undefined ?
3861 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3862 this.options.maxZoom;
3863 },
3864
3865 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3866 // Returns the maximum zoom level on which the given bounds fit to the map
3867 // view in its entirety. If `inside` (optional) is set to `true`, the method
3868 // instead returns the minimum zoom level on which the map view fits into
3869 // the given bounds in its entirety.
3870 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3871 bounds = toLatLngBounds(bounds);
3872 padding = toPoint(padding || [0, 0]);
3873
3874 var zoom = this.getZoom() || 0,
3875 min = this.getMinZoom(),
3876 max = this.getMaxZoom(),
3877 nw = bounds.getNorthWest(),
3878 se = bounds.getSouthEast(),
3879 size = this.getSize().subtract(padding),
3880 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3881 snap = any3d ? this.options.zoomSnap : 1,
3882 scalex = size.x / boundsSize.x,
3883 scaley = size.y / boundsSize.y,
3884 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3885
3886 zoom = this.getScaleZoom(scale, zoom);
3887
3888 if (snap) {
3889 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3890 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3891 }
3892
3893 return Math.max(min, Math.min(max, zoom));
3894 },
3895
3896 // @method getSize(): Point
3897 // Returns the current size of the map container (in pixels).
3898 getSize: function () {
3899 if (!this._size || this._sizeChanged) {
3900 this._size = new Point(
3901 this._container.clientWidth || 0,
3902 this._container.clientHeight || 0);
3903
3904 this._sizeChanged = false;
3905 }
3906 return this._size.clone();
3907 },
3908
3909 // @method getPixelBounds(): Bounds
3910 // Returns the bounds of the current map view in projected pixel
3911 // coordinates (sometimes useful in layer and overlay implementations).
3912 getPixelBounds: function (center, zoom) {
3913 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3914 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3915 },
3916
3917 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3918 // the map pane? "left point of the map layer" can be confusing, specially
3919 // since there can be negative offsets.
3920 // @method getPixelOrigin(): Point
3921 // Returns the projected pixel coordinates of the top left point of
3922 // the map layer (useful in custom layer and overlay implementations).
3923 getPixelOrigin: function () {
3924 this._checkIfLoaded();
3925 return this._pixelOrigin;
3926 },
3927
3928 // @method getPixelWorldBounds(zoom?: Number): Bounds
3929 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3930 // If `zoom` is omitted, the map's current zoom level is used.
3931 getPixelWorldBounds: function (zoom) {
3932 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3933 },
3934
3935 // @section Other Methods
3936
3937 // @method getPane(pane: String|HTMLElement): HTMLElement
3938 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3939 getPane: function (pane) {
3940 return typeof pane === 'string' ? this._panes[pane] : pane;
3941 },
3942
3943 // @method getPanes(): Object
3944 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3945 // the panes as values.
3946 getPanes: function () {
3947 return this._panes;
3948 },
3949
3950 // @method getContainer: HTMLElement
3951 // Returns the HTML element that contains the map.
3952 getContainer: function () {
3953 return this._container;
3954 },
3955
3956
3957 // @section Conversion Methods
3958
3959 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3960 // Returns the scale factor to be applied to a map transition from zoom level
3961 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3962 getZoomScale: function (toZoom, fromZoom) {
3963 // TODO replace with universal implementation after refactoring projections
3964 var crs = this.options.crs;
3965 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3966 return crs.scale(toZoom) / crs.scale(fromZoom);
3967 },
3968
3969 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3970 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3971 // level and everything is scaled by a factor of `scale`. Inverse of
3972 // [`getZoomScale`](#map-getZoomScale).
3973 getScaleZoom: function (scale, fromZoom) {
3974 var crs = this.options.crs;
3975 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3976 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3977 return isNaN(zoom) ? Infinity : zoom;
3978 },
3979
3980 // @method project(latlng: LatLng, zoom: Number): Point
3981 // Projects a geographical coordinate `LatLng` according to the projection
3982 // of the map's CRS, then scales it according to `zoom` and the CRS's
3983 // `Transformation`. The result is pixel coordinate relative to
3984 // the CRS origin.
3985 project: function (latlng, zoom) {
3986 zoom = zoom === undefined ? this._zoom : zoom;
3987 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3988 },
3989
3990 // @method unproject(point: Point, zoom: Number): LatLng
3991 // Inverse of [`project`](#map-project).
3992 unproject: function (point, zoom) {
3993 zoom = zoom === undefined ? this._zoom : zoom;
3994 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3995 },
3996
3997 // @method layerPointToLatLng(point: Point): LatLng
3998 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3999 // returns the corresponding geographical coordinate (for the current zoom level).
4000 layerPointToLatLng: function (point) {
4001 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4002 return this.unproject(projectedPoint);
4003 },
4004
4005 // @method latLngToLayerPoint(latlng: LatLng): Point
4006 // Given a geographical coordinate, returns the corresponding pixel coordinate
4007 // relative to the [origin pixel](#map-getpixelorigin).
4008 latLngToLayerPoint: function (latlng) {
4009 var projectedPoint = this.project(toLatLng(latlng))._round();
4010 return projectedPoint._subtract(this.getPixelOrigin());
4011 },
4012
4013 // @method wrapLatLng(latlng: LatLng): LatLng
4014 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4015 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4016 // CRS's bounds.
4017 // By default this means longitude is wrapped around the dateline so its
4018 // value is between -180 and +180 degrees.
4019 wrapLatLng: function (latlng) {
4020 return this.options.crs.wrapLatLng(toLatLng(latlng));
4021 },
4022
4023 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4024 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4025 // its center is within the CRS's bounds.
4026 // By default this means the center longitude is wrapped around the dateline so its
4027 // value is between -180 and +180 degrees, and the majority of the bounds
4028 // overlaps the CRS's bounds.
4029 wrapLatLngBounds: function (latlng) {
4030 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4031 },
4032
4033 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4034 // Returns the distance between two geographical coordinates according to
4035 // the map's CRS. By default this measures distance in meters.
4036 distance: function (latlng1, latlng2) {
4037 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4038 },
4039
4040 // @method containerPointToLayerPoint(point: Point): Point
4041 // Given a pixel coordinate relative to the map container, returns the corresponding
4042 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4043 containerPointToLayerPoint: function (point) { // (Point)
4044 return toPoint(point).subtract(this._getMapPanePos());
4045 },
4046
4047 // @method layerPointToContainerPoint(point: Point): Point
4048 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4049 // returns the corresponding pixel coordinate relative to the map container.
4050 layerPointToContainerPoint: function (point) { // (Point)
4051 return toPoint(point).add(this._getMapPanePos());
4052 },
4053
4054 // @method containerPointToLatLng(point: Point): LatLng
4055 // Given a pixel coordinate relative to the map container, returns
4056 // the corresponding geographical coordinate (for the current zoom level).
4057 containerPointToLatLng: function (point) {
4058 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4059 return this.layerPointToLatLng(layerPoint);
4060 },
4061
4062 // @method latLngToContainerPoint(latlng: LatLng): Point
4063 // Given a geographical coordinate, returns the corresponding pixel coordinate
4064 // relative to the map container.
4065 latLngToContainerPoint: function (latlng) {
4066 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4067 },
4068
4069 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4070 // Given a MouseEvent object, returns the pixel coordinate relative to the
4071 // map container where the event took place.
4072 mouseEventToContainerPoint: function (e) {
4073 return getMousePosition(e, this._container);
4074 },
4075
4076 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4077 // Given a MouseEvent object, returns the pixel coordinate relative to
4078 // the [origin pixel](#map-getpixelorigin) where the event took place.
4079 mouseEventToLayerPoint: function (e) {
4080 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4081 },
4082
4083 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4084 // Given a MouseEvent object, returns geographical coordinate where the
4085 // event took place.
4086 mouseEventToLatLng: function (e) { // (MouseEvent)
4087 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4088 },
4089
4090
4091 // map initialization methods
4092
4093 _initContainer: function (id) {
4094 var container = this._container = get(id);
4095
4096 if (!container) {
4097 throw new Error('Map container not found.');
4098 } else if (container._leaflet_id) {
4099 throw new Error('Map container is already initialized.');
4100 }
4101
4102 on(container, 'scroll', this._onScroll, this);
4103 this._containerId = stamp(container);
4104 },
4105
4106 _initLayout: function () {
4107 var container = this._container;
4108
4109 this._fadeAnimated = this.options.fadeAnimation && any3d;
4110
4111 addClass(container, 'leaflet-container' +
4112 (touch ? ' leaflet-touch' : '') +
4113 (retina ? ' leaflet-retina' : '') +
4114 (ielt9 ? ' leaflet-oldie' : '') +
4115 (safari ? ' leaflet-safari' : '') +
4116 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4117
4118 var position = getStyle(container, 'position');
4119
4120 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4121 container.style.position = 'relative';
4122 }
4123
4124 this._initPanes();
4125
4126 if (this._initControlPos) {
4127 this._initControlPos();
4128 }
4129 },
4130
4131 _initPanes: function () {
4132 var panes = this._panes = {};
4133 this._paneRenderers = {};
4134
4135 // @section
4136 //
4137 // Panes are DOM elements used to control the ordering of layers on the map. You
4138 // can access panes with [`map.getPane`](#map-getpane) or
4139 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4140 // [`map.createPane`](#map-createpane) method.
4141 //
4142 // Every map has the following default panes that differ only in zIndex.
4143 //
4144 // @pane mapPane: HTMLElement = 'auto'
4145 // Pane that contains all other map panes
4146
4147 this._mapPane = this.createPane('mapPane', this._container);
4148 setPosition(this._mapPane, new Point(0, 0));
4149
4150 // @pane tilePane: HTMLElement = 200
4151 // Pane for `GridLayer`s and `TileLayer`s
4152 this.createPane('tilePane');
4153 // @pane overlayPane: HTMLElement = 400
4154 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4155 this.createPane('shadowPane');
4156 // @pane shadowPane: HTMLElement = 500
4157 // Pane for overlay shadows (e.g. `Marker` shadows)
4158 this.createPane('overlayPane');
4159 // @pane markerPane: HTMLElement = 600
4160 // Pane for `Icon`s of `Marker`s
4161 this.createPane('markerPane');
4162 // @pane tooltipPane: HTMLElement = 650
4163 // Pane for `Tooltip`s.
4164 this.createPane('tooltipPane');
4165 // @pane popupPane: HTMLElement = 700
4166 // Pane for `Popup`s.
4167 this.createPane('popupPane');
4168
4169 if (!this.options.markerZoomAnimation) {
4170 addClass(panes.markerPane, 'leaflet-zoom-hide');
4171 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4172 }
4173 },
4174
4175
4176 // private methods that modify map state
4177
4178 // @section Map state change events
4179 _resetView: function (center, zoom) {
4180 setPosition(this._mapPane, new Point(0, 0));
4181
4182 var loading = !this._loaded;
4183 this._loaded = true;
4184 zoom = this._limitZoom(zoom);
4185
4186 this.fire('viewprereset');
4187
4188 var zoomChanged = this._zoom !== zoom;
4189 this
4190 ._moveStart(zoomChanged, false)
4191 ._move(center, zoom)
4192 ._moveEnd(zoomChanged);
4193
4194 // @event viewreset: Event
4195 // Fired when the map needs to redraw its content (this usually happens
4196 // on map zoom or load). Very useful for creating custom overlays.
4197 this.fire('viewreset');
4198
4199 // @event load: Event
4200 // Fired when the map is initialized (when its center and zoom are set
4201 // for the first time).
4202 if (loading) {
4203 this.fire('load');
4204 }
4205 },
4206
4207 _moveStart: function (zoomChanged, noMoveStart) {
4208 // @event zoomstart: Event
4209 // Fired when the map zoom is about to change (e.g. before zoom animation).
4210 // @event movestart: Event
4211 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4212 if (zoomChanged) {
4213 this.fire('zoomstart');
4214 }
4215 if (!noMoveStart) {
4216 this.fire('movestart');
4217 }
4218 return this;
4219 },
4220
4221 _move: function (center, zoom, data) {
4222 if (zoom === undefined) {
4223 zoom = this._zoom;
4224 }
4225 var zoomChanged = this._zoom !== zoom;
4226
4227 this._zoom = zoom;
4228 this._lastCenter = center;
4229 this._pixelOrigin = this._getNewPixelOrigin(center);
4230
4231 // @event zoom: Event
4232 // Fired repeatedly during any change in zoom level, including zoom
4233 // and fly animations.
4234 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4235 this.fire('zoom', data);
4236 }
4237
4238 // @event move: Event
4239 // Fired repeatedly during any movement of the map, including pan and
4240 // fly animations.
4241 return this.fire('move', data);
4242 },
4243
4244 _moveEnd: function (zoomChanged) {
4245 // @event zoomend: Event
4246 // Fired when the map has changed, after any animations.
4247 if (zoomChanged) {
4248 this.fire('zoomend');
4249 }
4250
4251 // @event moveend: Event
4252 // Fired when the center of the map stops changing (e.g. user stopped
4253 // dragging the map).
4254 return this.fire('moveend');
4255 },
4256
4257 _stop: function () {
4258 cancelAnimFrame(this._flyToFrame);
4259 if (this._panAnim) {
4260 this._panAnim.stop();
4261 }
4262 return this;
4263 },
4264
4265 _rawPanBy: function (offset) {
4266 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4267 },
4268
4269 _getZoomSpan: function () {
4270 return this.getMaxZoom() - this.getMinZoom();
4271 },
4272
4273 _panInsideMaxBounds: function () {
4274 if (!this._enforcingBounds) {
4275 this.panInsideBounds(this.options.maxBounds);
4276 }
4277 },
4278
4279 _checkIfLoaded: function () {
4280 if (!this._loaded) {
4281 throw new Error('Set map center and zoom first.');
4282 }
4283 },
4284
4285 // DOM event handling
4286
4287 // @section Interaction events
4288 _initEvents: function (remove$$1) {
4289 this._targets = {};
4290 this._targets[stamp(this._container)] = this;
4291
4292 var onOff = remove$$1 ? off : on;
4293
4294 // @event click: MouseEvent
4295 // Fired when the user clicks (or taps) the map.
4296 // @event dblclick: MouseEvent
4297 // Fired when the user double-clicks (or double-taps) the map.
4298 // @event mousedown: MouseEvent
4299 // Fired when the user pushes the mouse button on the map.
4300 // @event mouseup: MouseEvent
4301 // Fired when the user releases the mouse button on the map.
4302 // @event mouseover: MouseEvent
4303 // Fired when the mouse enters the map.
4304 // @event mouseout: MouseEvent
4305 // Fired when the mouse leaves the map.
4306 // @event mousemove: MouseEvent
4307 // Fired while the mouse moves over the map.
4308 // @event contextmenu: MouseEvent
4309 // Fired when the user pushes the right mouse button on the map, prevents
4310 // default browser context menu from showing if there are listeners on
4311 // this event. Also fired on mobile when the user holds a single touch
4312 // for a second (also called long press).
4313 // @event keypress: KeyboardEvent
4314 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4315 // @event keydown: KeyboardEvent
4316 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4317 // the `keydown` event is fired for keys that produce a character value and for keys
4318 // that do not produce a character value.
4319 // @event keyup: KeyboardEvent
4320 // Fired when the user releases a key from the keyboard while the map is focused.
4321 onOff(this._container, 'click dblclick mousedown mouseup ' +
4322 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4323
4324 if (this.options.trackResize) {
4325 onOff(window, 'resize', this._onResize, this);
4326 }
4327
4328 if (any3d && this.options.transform3DLimit) {
4329 (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4330 }
4331 },
4332
4333 _onResize: function () {
4334 cancelAnimFrame(this._resizeRequest);
4335 this._resizeRequest = requestAnimFrame(
4336 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4337 },
4338
4339 _onScroll: function () {
4340 this._container.scrollTop = 0;
4341 this._container.scrollLeft = 0;
4342 },
4343
4344 _onMoveEnd: function () {
4345 var pos = this._getMapPanePos();
4346 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4347 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4348 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4349 this._resetView(this.getCenter(), this.getZoom());
4350 }
4351 },
4352
4353 _findEventTargets: function (e, type) {
4354 var targets = [],
4355 target,
4356 isHover = type === 'mouseout' || type === 'mouseover',
4357 src = e.target || e.srcElement,
4358 dragging = false;
4359
4360 while (src) {
4361 target = this._targets[stamp(src)];
4362 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4363 // Prevent firing click after you just dragged an object.
4364 dragging = true;
4365 break;
4366 }
4367 if (target && target.listens(type, true)) {
4368 if (isHover && !isExternalTarget(src, e)) { break; }
4369 targets.push(target);
4370 if (isHover) { break; }
4371 }
4372 if (src === this._container) { break; }
4373 src = src.parentNode;
4374 }
4375 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4376 targets = [this];
4377 }
4378 return targets;
4379 },
4380
4381 _handleDOMEvent: function (e) {
4382 if (!this._loaded || skipped(e)) { return; }
4383
4384 var type = e.type;
4385
4386 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4387 // prevents outline when clicking on keyboard-focusable element
4388 preventOutline(e.target || e.srcElement);
4389 }
4390
4391 this._fireDOMEvent(e, type);
4392 },
4393
4394 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4395
4396 _fireDOMEvent: function (e, type, targets) {
4397
4398 if (e.type === 'click') {
4399 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4400 // @event preclick: MouseEvent
4401 // Fired before mouse click on the map (sometimes useful when you
4402 // want something to happen on click before any existing click
4403 // handlers start running).
4404 var synth = extend({}, e);
4405 synth.type = 'preclick';
4406 this._fireDOMEvent(synth, synth.type, targets);
4407 }
4408
4409 if (e._stopped) { return; }
4410
4411 // Find the layer the event is propagating from and its parents.
4412 targets = (targets || []).concat(this._findEventTargets(e, type));
4413
4414 if (!targets.length) { return; }
4415
4416 var target = targets[0];
4417 if (type === 'contextmenu' && target.listens(type, true)) {
4418 preventDefault(e);
4419 }
4420
4421 var data = {
4422 originalEvent: e
4423 };
4424
4425 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4426 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4427 data.containerPoint = isMarker ?
4428 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4429 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4430 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4431 }
4432
4433 for (var i = 0; i < targets.length; i++) {
4434 targets[i].fire(type, data, true);
4435 if (data.originalEvent._stopped ||
4436 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4437 }
4438 },
4439
4440 _draggableMoved: function (obj) {
4441 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4442 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4443 },
4444
4445 _clearHandlers: function () {
4446 for (var i = 0, len = this._handlers.length; i < len; i++) {
4447 this._handlers[i].disable();
4448 }
4449 },
4450
4451 // @section Other Methods
4452
4453 // @method whenReady(fn: Function, context?: Object): this
4454 // Runs the given function `fn` when the map gets initialized with
4455 // a view (center and zoom) and at least one layer, or immediately
4456 // if it's already initialized, optionally passing a function context.
4457 whenReady: function (callback, context) {
4458 if (this._loaded) {
4459 callback.call(context || this, {target: this});
4460 } else {
4461 this.on('load', callback, context);
4462 }
4463 return this;
4464 },
4465
4466
4467 // private methods for getting map state
4468
4469 _getMapPanePos: function () {
4470 return getPosition(this._mapPane) || new Point(0, 0);
4471 },
4472
4473 _moved: function () {
4474 var pos = this._getMapPanePos();
4475 return pos && !pos.equals([0, 0]);
4476 },
4477
4478 _getTopLeftPoint: function (center, zoom) {
4479 var pixelOrigin = center && zoom !== undefined ?
4480 this._getNewPixelOrigin(center, zoom) :
4481 this.getPixelOrigin();
4482 return pixelOrigin.subtract(this._getMapPanePos());
4483 },
4484
4485 _getNewPixelOrigin: function (center, zoom) {
4486 var viewHalf = this.getSize()._divideBy(2);
4487 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4488 },
4489
4490 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4491 var topLeft = this._getNewPixelOrigin(center, zoom);
4492 return this.project(latlng, zoom)._subtract(topLeft);
4493 },
4494
4495 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4496 var topLeft = this._getNewPixelOrigin(center, zoom);
4497 return toBounds([
4498 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4499 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4500 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4501 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4502 ]);
4503 },
4504
4505 // layer point of the current center
4506 _getCenterLayerPoint: function () {
4507 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4508 },
4509
4510 // offset of the specified place to the current center in pixels
4511 _getCenterOffset: function (latlng) {
4512 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4513 },
4514
4515 // adjust center for view to get inside bounds
4516 _limitCenter: function (center, zoom, bounds) {
4517
4518 if (!bounds) { return center; }
4519
4520 var centerPoint = this.project(center, zoom),
4521 viewHalf = this.getSize().divideBy(2),
4522 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4523 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4524
4525 // If offset is less than a pixel, ignore.
4526 // This prevents unstable projections from getting into
4527 // an infinite loop of tiny offsets.
4528 if (offset.round().equals([0, 0])) {
4529 return center;
4530 }
4531
4532 return this.unproject(centerPoint.add(offset), zoom);
4533 },
4534
4535 // adjust offset for view to get inside bounds
4536 _limitOffset: function (offset, bounds) {
4537 if (!bounds) { return offset; }
4538
4539 var viewBounds = this.getPixelBounds(),
4540 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4541
4542 return offset.add(this._getBoundsOffset(newBounds, bounds));
4543 },
4544
4545 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4546 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4547 var projectedMaxBounds = toBounds(
4548 this.project(maxBounds.getNorthEast(), zoom),
4549 this.project(maxBounds.getSouthWest(), zoom)
4550 ),
4551 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4552 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4553
4554 dx = this._rebound(minOffset.x, -maxOffset.x),
4555 dy = this._rebound(minOffset.y, -maxOffset.y);
4556
4557 return new Point(dx, dy);
4558 },
4559
4560 _rebound: function (left, right) {
4561 return left + right > 0 ?
4562 Math.round(left - right) / 2 :
4563 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4564 },
4565
4566 _limitZoom: function (zoom) {
4567 var min = this.getMinZoom(),
4568 max = this.getMaxZoom(),
4569 snap = any3d ? this.options.zoomSnap : 1;
4570 if (snap) {
4571 zoom = Math.round(zoom / snap) * snap;
4572 }
4573 return Math.max(min, Math.min(max, zoom));
4574 },
4575
4576 _onPanTransitionStep: function () {
4577 this.fire('move');
4578 },
4579
4580 _onPanTransitionEnd: function () {
4581 removeClass(this._mapPane, 'leaflet-pan-anim');
4582 this.fire('moveend');
4583 },
4584
4585 _tryAnimatedPan: function (center, options) {
4586 // difference between the new and current centers in pixels
4587 var offset = this._getCenterOffset(center)._trunc();
4588
4589 // don't animate too far unless animate: true specified in options
4590 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4591
4592 this.panBy(offset, options);
4593
4594 return true;
4595 },
4596
4597 _createAnimProxy: function () {
4598
4599 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4600 this._panes.mapPane.appendChild(proxy);
4601
4602 this.on('zoomanim', function (e) {
4603 var prop = TRANSFORM,
4604 transform = this._proxy.style[prop];
4605
4606 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4607
4608 // workaround for case when transform is the same and so transitionend event is not fired
4609 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4610 this._onZoomTransitionEnd();
4611 }
4612 }, this);
4613
4614 this.on('load moveend', function () {
4615 var c = this.getCenter(),
4616 z = this.getZoom();
4617 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4618 }, this);
4619
4620 this._on('unload', this._destroyAnimProxy, this);
4621 },
4622
4623 _destroyAnimProxy: function () {
4624 remove(this._proxy);
4625 delete this._proxy;
4626 },
4627
4628 _catchTransitionEnd: function (e) {
4629 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4630 this._onZoomTransitionEnd();
4631 }
4632 },
4633
4634 _nothingToAnimate: function () {
4635 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4636 },
4637
4638 _tryAnimatedZoom: function (center, zoom, options) {
4639
4640 if (this._animatingZoom) { return true; }
4641
4642 options = options || {};
4643
4644 // don't animate if disabled, not supported or zoom difference is too large
4645 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4646 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4647
4648 // offset is the pixel coords of the zoom origin relative to the current center
4649 var scale = this.getZoomScale(zoom),
4650 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4651
4652 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4653 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4654
4655 requestAnimFrame(function () {
4656 this
4657 ._moveStart(true, false)
4658 ._animateZoom(center, zoom, true);
4659 }, this);
4660
4661 return true;
4662 },
4663
4664 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4665 if (!this._mapPane) { return; }
4666
4667 if (startAnim) {
4668 this._animatingZoom = true;
4669
4670 // remember what center/zoom to set after animation
4671 this._animateToCenter = center;
4672 this._animateToZoom = zoom;
4673
4674 addClass(this._mapPane, 'leaflet-zoom-anim');
4675 }
4676
4677 // @event zoomanim: ZoomAnimEvent
4678 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4679 this.fire('zoomanim', {
4680 center: center,
4681 zoom: zoom,
4682 noUpdate: noUpdate
4683 });
4684
4685 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4686 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4687 },
4688
4689 _onZoomTransitionEnd: function () {
4690 if (!this._animatingZoom) { return; }
4691
4692 if (this._mapPane) {
4693 removeClass(this._mapPane, 'leaflet-zoom-anim');
4694 }
4695
4696 this._animatingZoom = false;
4697
4698 this._move(this._animateToCenter, this._animateToZoom);
4699
4700 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4701 requestAnimFrame(function () {
4702 this._moveEnd(true);
4703 }, this);
4704 }
4705});
4706
4707// @section
4708
4709// @factory L.map(id: String, options?: Map options)
4710// Instantiates a map object given the DOM ID of a `<div>` element
4711// and optionally an object literal with `Map options`.
4712//
4713// @alternative
4714// @factory L.map(el: HTMLElement, options?: Map options)
4715// Instantiates a map object given an instance of a `<div>` HTML element
4716// and optionally an object literal with `Map options`.
4717function createMap(id, options) {
4718 return new Map(id, options);
4719}
4720
4721/*
4722 * @class Control
4723 * @aka L.Control
4724 * @inherits Class
4725 *
4726 * L.Control is a base class for implementing map controls. Handles positioning.
4727 * All other controls extend from this class.
4728 */
4729
4730var Control = Class.extend({
4731 // @section
4732 // @aka Control options
4733 options: {
4734 // @option position: String = 'topright'
4735 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4736 // `'topright'`, `'bottomleft'` or `'bottomright'`
4737 position: 'topright'
4738 },
4739
4740 initialize: function (options) {
4741 setOptions(this, options);
4742 },
4743
4744 /* @section
4745 * Classes extending L.Control will inherit the following methods:
4746 *
4747 * @method getPosition: string
4748 * Returns the position of the control.
4749 */
4750 getPosition: function () {
4751 return this.options.position;
4752 },
4753
4754 // @method setPosition(position: string): this
4755 // Sets the position of the control.
4756 setPosition: function (position) {
4757 var map = this._map;
4758
4759 if (map) {
4760 map.removeControl(this);
4761 }
4762
4763 this.options.position = position;
4764
4765 if (map) {
4766 map.addControl(this);
4767 }
4768
4769 return this;
4770 },
4771
4772 // @method getContainer: HTMLElement
4773 // Returns the HTMLElement that contains the control.
4774 getContainer: function () {
4775 return this._container;
4776 },
4777
4778 // @method addTo(map: Map): this
4779 // Adds the control to the given map.
4780 addTo: function (map) {
4781 this.remove();
4782 this._map = map;
4783
4784 var container = this._container = this.onAdd(map),
4785 pos = this.getPosition(),
4786 corner = map._controlCorners[pos];
4787
4788 addClass(container, 'leaflet-control');
4789
4790 if (pos.indexOf('bottom') !== -1) {
4791 corner.insertBefore(container, corner.firstChild);
4792 } else {
4793 corner.appendChild(container);
4794 }
4795
4796 this._map.on('unload', this.remove, this);
4797
4798 return this;
4799 },
4800
4801 // @method remove: this
4802 // Removes the control from the map it is currently active on.
4803 remove: function () {
4804 if (!this._map) {
4805 return this;
4806 }
4807
4808 remove(this._container);
4809
4810 if (this.onRemove) {
4811 this.onRemove(this._map);
4812 }
4813
4814 this._map.off('unload', this.remove, this);
4815 this._map = null;
4816
4817 return this;
4818 },
4819
4820 _refocusOnMap: function (e) {
4821 // if map exists and event is not a keyboard event
4822 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4823 this._map.getContainer().focus();
4824 }
4825 }
4826});
4827
4828var control = function (options) {
4829 return new Control(options);
4830};
4831
4832/* @section Extension methods
4833 * @uninheritable
4834 *
4835 * Every control should extend from `L.Control` and (re-)implement the following methods.
4836 *
4837 * @method onAdd(map: Map): HTMLElement
4838 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4839 *
4840 * @method onRemove(map: Map)
4841 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4842 */
4843
4844/* @namespace Map
4845 * @section Methods for Layers and Controls
4846 */
4847Map.include({
4848 // @method addControl(control: Control): this
4849 // Adds the given control to the map
4850 addControl: function (control) {
4851 control.addTo(this);
4852 return this;
4853 },
4854
4855 // @method removeControl(control: Control): this
4856 // Removes the given control from the map
4857 removeControl: function (control) {
4858 control.remove();
4859 return this;
4860 },
4861
4862 _initControlPos: function () {
4863 var corners = this._controlCorners = {},
4864 l = 'leaflet-',
4865 container = this._controlContainer =
4866 create$1('div', l + 'control-container', this._container);
4867
4868 function createCorner(vSide, hSide) {
4869 var className = l + vSide + ' ' + l + hSide;
4870
4871 corners[vSide + hSide] = create$1('div', className, container);
4872 }
4873
4874 createCorner('top', 'left');
4875 createCorner('top', 'right');
4876 createCorner('bottom', 'left');
4877 createCorner('bottom', 'right');
4878 },
4879
4880 _clearControlPos: function () {
4881 for (var i in this._controlCorners) {
4882 remove(this._controlCorners[i]);
4883 }
4884 remove(this._controlContainer);
4885 delete this._controlCorners;
4886 delete this._controlContainer;
4887 }
4888});
4889
4890/*
4891 * @class Control.Layers
4892 * @aka L.Control.Layers
4893 * @inherits Control
4894 *
4895 * 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`.
4896 *
4897 * @example
4898 *
4899 * ```js
4900 * var baseLayers = {
4901 * "Mapbox": mapbox,
4902 * "OpenStreetMap": osm
4903 * };
4904 *
4905 * var overlays = {
4906 * "Marker": marker,
4907 * "Roads": roadsLayer
4908 * };
4909 *
4910 * L.control.layers(baseLayers, overlays).addTo(map);
4911 * ```
4912 *
4913 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4914 *
4915 * ```js
4916 * {
4917 * "<someName1>": layer1,
4918 * "<someName2>": layer2
4919 * }
4920 * ```
4921 *
4922 * The layer names can contain HTML, which allows you to add additional styling to the items:
4923 *
4924 * ```js
4925 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4926 * ```
4927 */
4928
4929var Layers = Control.extend({
4930 // @section
4931 // @aka Control.Layers options
4932 options: {
4933 // @option collapsed: Boolean = true
4934 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4935 collapsed: true,
4936 position: 'topright',
4937
4938 // @option autoZIndex: Boolean = true
4939 // 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.
4940 autoZIndex: true,
4941
4942 // @option hideSingleBase: Boolean = false
4943 // If `true`, the base layers in the control will be hidden when there is only one.
4944 hideSingleBase: false,
4945
4946 // @option sortLayers: Boolean = false
4947 // Whether to sort the layers. When `false`, layers will keep the order
4948 // in which they were added to the control.
4949 sortLayers: false,
4950
4951 // @option sortFunction: Function = *
4952 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4953 // that will be used for sorting the layers, when `sortLayers` is `true`.
4954 // The function receives both the `L.Layer` instances and their names, as in
4955 // `sortFunction(layerA, layerB, nameA, nameB)`.
4956 // By default, it sorts layers alphabetically by their name.
4957 sortFunction: function (layerA, layerB, nameA, nameB) {
4958 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4959 }
4960 },
4961
4962 initialize: function (baseLayers, overlays, options) {
4963 setOptions(this, options);
4964
4965 this._layerControlInputs = [];
4966 this._layers = [];
4967 this._lastZIndex = 0;
4968 this._handlingClick = false;
4969
4970 for (var i in baseLayers) {
4971 this._addLayer(baseLayers[i], i);
4972 }
4973
4974 for (i in overlays) {
4975 this._addLayer(overlays[i], i, true);
4976 }
4977 },
4978
4979 onAdd: function (map) {
4980 this._initLayout();
4981 this._update();
4982
4983 this._map = map;
4984 map.on('zoomend', this._checkDisabledLayers, this);
4985
4986 for (var i = 0; i < this._layers.length; i++) {
4987 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4988 }
4989
4990 return this._container;
4991 },
4992
4993 addTo: function (map) {
4994 Control.prototype.addTo.call(this, map);
4995 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4996 return this._expandIfNotCollapsed();
4997 },
4998
4999 onRemove: function () {
5000 this._map.off('zoomend', this._checkDisabledLayers, this);
5001
5002 for (var i = 0; i < this._layers.length; i++) {
5003 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5004 }
5005 },
5006
5007 // @method addBaseLayer(layer: Layer, name: String): this
5008 // Adds a base layer (radio button entry) with the given name to the control.
5009 addBaseLayer: function (layer, name) {
5010 this._addLayer(layer, name);
5011 return (this._map) ? this._update() : this;
5012 },
5013
5014 // @method addOverlay(layer: Layer, name: String): this
5015 // Adds an overlay (checkbox entry) with the given name to the control.
5016 addOverlay: function (layer, name) {
5017 this._addLayer(layer, name, true);
5018 return (this._map) ? this._update() : this;
5019 },
5020
5021 // @method removeLayer(layer: Layer): this
5022 // Remove the given layer from the control.
5023 removeLayer: function (layer) {
5024 layer.off('add remove', this._onLayerChange, this);
5025
5026 var obj = this._getLayer(stamp(layer));
5027 if (obj) {
5028 this._layers.splice(this._layers.indexOf(obj), 1);
5029 }
5030 return (this._map) ? this._update() : this;
5031 },
5032
5033 // @method expand(): this
5034 // Expand the control container if collapsed.
5035 expand: function () {
5036 addClass(this._container, 'leaflet-control-layers-expanded');
5037 this._section.style.height = null;
5038 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5039 if (acceptableHeight < this._section.clientHeight) {
5040 addClass(this._section, 'leaflet-control-layers-scrollbar');
5041 this._section.style.height = acceptableHeight + 'px';
5042 } else {
5043 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5044 }
5045 this._checkDisabledLayers();
5046 return this;
5047 },
5048
5049 // @method collapse(): this
5050 // Collapse the control container if expanded.
5051 collapse: function () {
5052 removeClass(this._container, 'leaflet-control-layers-expanded');
5053 return this;
5054 },
5055
5056 _initLayout: function () {
5057 var className = 'leaflet-control-layers',
5058 container = this._container = create$1('div', className),
5059 collapsed = this.options.collapsed;
5060
5061 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5062 container.setAttribute('aria-haspopup', true);
5063
5064 disableClickPropagation(container);
5065 disableScrollPropagation(container);
5066
5067 var section = this._section = create$1('section', className + '-list');
5068
5069 if (collapsed) {
5070 this._map.on('click', this.collapse, this);
5071
5072 if (!android) {
5073 on(container, {
5074 mouseenter: this.expand,
5075 mouseleave: this.collapse
5076 }, this);
5077 }
5078 }
5079
5080 var link = this._layersLink = create$1('a', className + '-toggle', container);
5081 link.href = '#';
5082 link.title = 'Layers';
5083
5084 if (touch) {
5085 on(link, 'click', stop);
5086 on(link, 'click', this.expand, this);
5087 } else {
5088 on(link, 'focus', this.expand, this);
5089 }
5090
5091 if (!collapsed) {
5092 this.expand();
5093 }
5094
5095 this._baseLayersList = create$1('div', className + '-base', section);
5096 this._separator = create$1('div', className + '-separator', section);
5097 this._overlaysList = create$1('div', className + '-overlays', section);
5098
5099 container.appendChild(section);
5100 },
5101
5102 _getLayer: function (id) {
5103 for (var i = 0; i < this._layers.length; i++) {
5104
5105 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5106 return this._layers[i];
5107 }
5108 }
5109 },
5110
5111 _addLayer: function (layer, name, overlay) {
5112 if (this._map) {
5113 layer.on('add remove', this._onLayerChange, this);
5114 }
5115
5116 this._layers.push({
5117 layer: layer,
5118 name: name,
5119 overlay: overlay
5120 });
5121
5122 if (this.options.sortLayers) {
5123 this._layers.sort(bind(function (a, b) {
5124 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5125 }, this));
5126 }
5127
5128 if (this.options.autoZIndex && layer.setZIndex) {
5129 this._lastZIndex++;
5130 layer.setZIndex(this._lastZIndex);
5131 }
5132
5133 this._expandIfNotCollapsed();
5134 },
5135
5136 _update: function () {
5137 if (!this._container) { return this; }
5138
5139 empty(this._baseLayersList);
5140 empty(this._overlaysList);
5141
5142 this._layerControlInputs = [];
5143 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5144
5145 for (i = 0; i < this._layers.length; i++) {
5146 obj = this._layers[i];
5147 this._addItem(obj);
5148 overlaysPresent = overlaysPresent || obj.overlay;
5149 baseLayersPresent = baseLayersPresent || !obj.overlay;
5150 baseLayersCount += !obj.overlay ? 1 : 0;
5151 }
5152
5153 // Hide base layers section if there's only one layer.
5154 if (this.options.hideSingleBase) {
5155 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5156 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5157 }
5158
5159 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5160
5161 return this;
5162 },
5163
5164 _onLayerChange: function (e) {
5165 if (!this._handlingClick) {
5166 this._update();
5167 }
5168
5169 var obj = this._getLayer(stamp(e.target));
5170
5171 // @namespace Map
5172 // @section Layer events
5173 // @event baselayerchange: LayersControlEvent
5174 // Fired when the base layer is changed through the [layer control](#control-layers).
5175 // @event overlayadd: LayersControlEvent
5176 // Fired when an overlay is selected through the [layer control](#control-layers).
5177 // @event overlayremove: LayersControlEvent
5178 // Fired when an overlay is deselected through the [layer control](#control-layers).
5179 // @namespace Control.Layers
5180 var type = obj.overlay ?
5181 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5182 (e.type === 'add' ? 'baselayerchange' : null);
5183
5184 if (type) {
5185 this._map.fire(type, obj);
5186 }
5187 },
5188
5189 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5190 _createRadioElement: function (name, checked) {
5191
5192 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5193 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5194
5195 var radioFragment = document.createElement('div');
5196 radioFragment.innerHTML = radioHtml;
5197
5198 return radioFragment.firstChild;
5199 },
5200
5201 _addItem: function (obj) {
5202 var label = document.createElement('label'),
5203 checked = this._map.hasLayer(obj.layer),
5204 input;
5205
5206 if (obj.overlay) {
5207 input = document.createElement('input');
5208 input.type = 'checkbox';
5209 input.className = 'leaflet-control-layers-selector';
5210 input.defaultChecked = checked;
5211 } else {
5212 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5213 }
5214
5215 this._layerControlInputs.push(input);
5216 input.layerId = stamp(obj.layer);
5217
5218 on(input, 'click', this._onInputClick, this);
5219
5220 var name = document.createElement('span');
5221 name.innerHTML = ' ' + obj.name;
5222
5223 // Helps from preventing layer control flicker when checkboxes are disabled
5224 // https://github.com/Leaflet/Leaflet/issues/2771
5225 var holder = document.createElement('div');
5226
5227 label.appendChild(holder);
5228 holder.appendChild(input);
5229 holder.appendChild(name);
5230
5231 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5232 container.appendChild(label);
5233
5234 this._checkDisabledLayers();
5235 return label;
5236 },
5237
5238 _onInputClick: function () {
5239 var inputs = this._layerControlInputs,
5240 input, layer;
5241 var addedLayers = [],
5242 removedLayers = [];
5243
5244 this._handlingClick = true;
5245
5246 for (var i = inputs.length - 1; i >= 0; i--) {
5247 input = inputs[i];
5248 layer = this._getLayer(input.layerId).layer;
5249
5250 if (input.checked) {
5251 addedLayers.push(layer);
5252 } else if (!input.checked) {
5253 removedLayers.push(layer);
5254 }
5255 }
5256
5257 // Bugfix issue 2318: Should remove all old layers before readding new ones
5258 for (i = 0; i < removedLayers.length; i++) {
5259 if (this._map.hasLayer(removedLayers[i])) {
5260 this._map.removeLayer(removedLayers[i]);
5261 }
5262 }
5263 for (i = 0; i < addedLayers.length; i++) {
5264 if (!this._map.hasLayer(addedLayers[i])) {
5265 this._map.addLayer(addedLayers[i]);
5266 }
5267 }
5268
5269 this._handlingClick = false;
5270
5271 this._refocusOnMap();
5272 },
5273
5274 _checkDisabledLayers: function () {
5275 var inputs = this._layerControlInputs,
5276 input,
5277 layer,
5278 zoom = this._map.getZoom();
5279
5280 for (var i = inputs.length - 1; i >= 0; i--) {
5281 input = inputs[i];
5282 layer = this._getLayer(input.layerId).layer;
5283 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5284 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5285
5286 }
5287 },
5288
5289 _expandIfNotCollapsed: function () {
5290 if (this._map && !this.options.collapsed) {
5291 this.expand();
5292 }
5293 return this;
5294 },
5295
5296 _expand: function () {
5297 // Backward compatibility, remove me in 1.1.
5298 return this.expand();
5299 },
5300
5301 _collapse: function () {
5302 // Backward compatibility, remove me in 1.1.
5303 return this.collapse();
5304 }
5305
5306});
5307
5308
5309// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5310// Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
5311var layers = function (baseLayers, overlays, options) {
5312 return new Layers(baseLayers, overlays, options);
5313};
5314
5315/*
5316 * @class Control.Zoom
5317 * @aka L.Control.Zoom
5318 * @inherits Control
5319 *
5320 * 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`.
5321 */
5322
5323var Zoom = Control.extend({
5324 // @section
5325 // @aka Control.Zoom options
5326 options: {
5327 position: 'topleft',
5328
5329 // @option zoomInText: String = '+'
5330 // The text set on the 'zoom in' button.
5331 zoomInText: '+',
5332
5333 // @option zoomInTitle: String = 'Zoom in'
5334 // The title set on the 'zoom in' button.
5335 zoomInTitle: 'Zoom in',
5336
5337 // @option zoomOutText: String = '&#x2212;'
5338 // The text set on the 'zoom out' button.
5339 zoomOutText: '&#x2212;',
5340
5341 // @option zoomOutTitle: String = 'Zoom out'
5342 // The title set on the 'zoom out' button.
5343 zoomOutTitle: 'Zoom out'
5344 },
5345
5346 onAdd: function (map) {
5347 var zoomName = 'leaflet-control-zoom',
5348 container = create$1('div', zoomName + ' leaflet-bar'),
5349 options = this.options;
5350
5351 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5352 zoomName + '-in', container, this._zoomIn);
5353 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5354 zoomName + '-out', container, this._zoomOut);
5355
5356 this._updateDisabled();
5357 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5358
5359 return container;
5360 },
5361
5362 onRemove: function (map) {
5363 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5364 },
5365
5366 disable: function () {
5367 this._disabled = true;
5368 this._updateDisabled();
5369 return this;
5370 },
5371
5372 enable: function () {
5373 this._disabled = false;
5374 this._updateDisabled();
5375 return this;
5376 },
5377
5378 _zoomIn: function (e) {
5379 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5380 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5381 }
5382 },
5383
5384 _zoomOut: function (e) {
5385 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5386 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5387 }
5388 },
5389
5390 _createButton: function (html, title, className, container, fn) {
5391 var link = create$1('a', className, container);
5392 link.innerHTML = html;
5393 link.href = '#';
5394 link.title = title;
5395
5396 /*
5397 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5398 */
5399 link.setAttribute('role', 'button');
5400 link.setAttribute('aria-label', title);
5401
5402 disableClickPropagation(link);
5403 on(link, 'click', stop);
5404 on(link, 'click', fn, this);
5405 on(link, 'click', this._refocusOnMap, this);
5406
5407 return link;
5408 },
5409
5410 _updateDisabled: function () {
5411 var map = this._map,
5412 className = 'leaflet-disabled';
5413
5414 removeClass(this._zoomInButton, className);
5415 removeClass(this._zoomOutButton, className);
5416
5417 if (this._disabled || map._zoom === map.getMinZoom()) {
5418 addClass(this._zoomOutButton, className);
5419 }
5420 if (this._disabled || map._zoom === map.getMaxZoom()) {
5421 addClass(this._zoomInButton, className);
5422 }
5423 }
5424});
5425
5426// @namespace Map
5427// @section Control options
5428// @option zoomControl: Boolean = true
5429// Whether a [zoom control](#control-zoom) is added to the map by default.
5430Map.mergeOptions({
5431 zoomControl: true
5432});
5433
5434Map.addInitHook(function () {
5435 if (this.options.zoomControl) {
5436 // @section Controls
5437 // @property zoomControl: Control.Zoom
5438 // The default zoom control (only available if the
5439 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5440 this.zoomControl = new Zoom();
5441 this.addControl(this.zoomControl);
5442 }
5443});
5444
5445// @namespace Control.Zoom
5446// @factory L.control.zoom(options: Control.Zoom options)
5447// Creates a zoom control
5448var zoom = function (options) {
5449 return new Zoom(options);
5450};
5451
5452/*
5453 * @class Control.Scale
5454 * @aka L.Control.Scale
5455 * @inherits Control
5456 *
5457 * 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`.
5458 *
5459 * @example
5460 *
5461 * ```js
5462 * L.control.scale().addTo(map);
5463 * ```
5464 */
5465
5466var Scale = Control.extend({
5467 // @section
5468 // @aka Control.Scale options
5469 options: {
5470 position: 'bottomleft',
5471
5472 // @option maxWidth: Number = 100
5473 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5474 maxWidth: 100,
5475
5476 // @option metric: Boolean = True
5477 // Whether to show the metric scale line (m/km).
5478 metric: true,
5479
5480 // @option imperial: Boolean = True
5481 // Whether to show the imperial scale line (mi/ft).
5482 imperial: true
5483
5484 // @option updateWhenIdle: Boolean = false
5485 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5486 },
5487
5488 onAdd: function (map) {
5489 var className = 'leaflet-control-scale',
5490 container = create$1('div', className),
5491 options = this.options;
5492
5493 this._addScales(options, className + '-line', container);
5494
5495 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5496 map.whenReady(this._update, this);
5497
5498 return container;
5499 },
5500
5501 onRemove: function (map) {
5502 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5503 },
5504
5505 _addScales: function (options, className, container) {
5506 if (options.metric) {
5507 this._mScale = create$1('div', className, container);
5508 }
5509 if (options.imperial) {
5510 this._iScale = create$1('div', className, container);
5511 }
5512 },
5513
5514 _update: function () {
5515 var map = this._map,
5516 y = map.getSize().y / 2;
5517
5518 var maxMeters = map.distance(
5519 map.containerPointToLatLng([0, y]),
5520 map.containerPointToLatLng([this.options.maxWidth, y]));
5521
5522 this._updateScales(maxMeters);
5523 },
5524
5525 _updateScales: function (maxMeters) {
5526 if (this.options.metric && maxMeters) {
5527 this._updateMetric(maxMeters);
5528 }
5529 if (this.options.imperial && maxMeters) {
5530 this._updateImperial(maxMeters);
5531 }
5532 },
5533
5534 _updateMetric: function (maxMeters) {
5535 var meters = this._getRoundNum(maxMeters),
5536 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5537
5538 this._updateScale(this._mScale, label, meters / maxMeters);
5539 },
5540
5541 _updateImperial: function (maxMeters) {
5542 var maxFeet = maxMeters * 3.2808399,
5543 maxMiles, miles, feet;
5544
5545 if (maxFeet > 5280) {
5546 maxMiles = maxFeet / 5280;
5547 miles = this._getRoundNum(maxMiles);
5548 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5549
5550 } else {
5551 feet = this._getRoundNum(maxFeet);
5552 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5553 }
5554 },
5555
5556 _updateScale: function (scale, text, ratio) {
5557 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5558 scale.innerHTML = text;
5559 },
5560
5561 _getRoundNum: function (num) {
5562 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5563 d = num / pow10;
5564
5565 d = d >= 10 ? 10 :
5566 d >= 5 ? 5 :
5567 d >= 3 ? 3 :
5568 d >= 2 ? 2 : 1;
5569
5570 return pow10 * d;
5571 }
5572});
5573
5574
5575// @factory L.control.scale(options?: Control.Scale options)
5576// Creates an scale control with the given options.
5577var scale = function (options) {
5578 return new Scale(options);
5579};
5580
5581/*
5582 * @class Control.Attribution
5583 * @aka L.Control.Attribution
5584 * @inherits Control
5585 *
5586 * 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.
5587 */
5588
5589var Attribution = Control.extend({
5590 // @section
5591 // @aka Control.Attribution options
5592 options: {
5593 position: 'bottomright',
5594
5595 // @option prefix: String = 'Leaflet'
5596 // The HTML text shown before the attributions. Pass `false` to disable.
5597 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5598 },
5599
5600 initialize: function (options) {
5601 setOptions(this, options);
5602
5603 this._attributions = {};
5604 },
5605
5606 onAdd: function (map) {
5607 map.attributionControl = this;
5608 this._container = create$1('div', 'leaflet-control-attribution');
5609 disableClickPropagation(this._container);
5610
5611 // TODO ugly, refactor
5612 for (var i in map._layers) {
5613 if (map._layers[i].getAttribution) {
5614 this.addAttribution(map._layers[i].getAttribution());
5615 }
5616 }
5617
5618 this._update();
5619
5620 return this._container;
5621 },
5622
5623 // @method setPrefix(prefix: String): this
5624 // Sets the text before the attributions.
5625 setPrefix: function (prefix) {
5626 this.options.prefix = prefix;
5627 this._update();
5628 return this;
5629 },
5630
5631 // @method addAttribution(text: String): this
5632 // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5633 addAttribution: function (text) {
5634 if (!text) { return this; }
5635
5636 if (!this._attributions[text]) {
5637 this._attributions[text] = 0;
5638 }
5639 this._attributions[text]++;
5640
5641 this._update();
5642
5643 return this;
5644 },
5645
5646 // @method removeAttribution(text: String): this
5647 // Removes an attribution text.
5648 removeAttribution: function (text) {
5649 if (!text) { return this; }
5650
5651 if (this._attributions[text]) {
5652 this._attributions[text]--;
5653 this._update();
5654 }
5655
5656 return this;
5657 },
5658
5659 _update: function () {
5660 if (!this._map) { return; }
5661
5662 var attribs = [];
5663
5664 for (var i in this._attributions) {
5665 if (this._attributions[i]) {
5666 attribs.push(i);
5667 }
5668 }
5669
5670 var prefixAndAttribs = [];
5671
5672 if (this.options.prefix) {
5673 prefixAndAttribs.push(this.options.prefix);
5674 }
5675 if (attribs.length) {
5676 prefixAndAttribs.push(attribs.join(', '));
5677 }
5678
5679 this._container.innerHTML = prefixAndAttribs.join(' | ');
5680 }
5681});
5682
5683// @namespace Map
5684// @section Control options
5685// @option attributionControl: Boolean = true
5686// Whether a [attribution control](#control-attribution) is added to the map by default.
5687Map.mergeOptions({
5688 attributionControl: true
5689});
5690
5691Map.addInitHook(function () {
5692 if (this.options.attributionControl) {
5693 new Attribution().addTo(this);
5694 }
5695});
5696
5697// @namespace Control.Attribution
5698// @factory L.control.attribution(options: Control.Attribution options)
5699// Creates an attribution control.
5700var attribution = function (options) {
5701 return new Attribution(options);
5702};
5703
5704Control.Layers = Layers;
5705Control.Zoom = Zoom;
5706Control.Scale = Scale;
5707Control.Attribution = Attribution;
5708
5709control.layers = layers;
5710control.zoom = zoom;
5711control.scale = scale;
5712control.attribution = attribution;
5713
5714/*
5715 L.Handler is a base class for handler classes that are used internally to inject
5716 interaction features like dragging to classes like Map and Marker.
5717*/
5718
5719// @class Handler
5720// @aka L.Handler
5721// Abstract class for map interaction handlers
5722
5723var Handler = Class.extend({
5724 initialize: function (map) {
5725 this._map = map;
5726 },
5727
5728 // @method enable(): this
5729 // Enables the handler
5730 enable: function () {
5731 if (this._enabled) { return this; }
5732
5733 this._enabled = true;
5734 this.addHooks();
5735 return this;
5736 },
5737
5738 // @method disable(): this
5739 // Disables the handler
5740 disable: function () {
5741 if (!this._enabled) { return this; }
5742
5743 this._enabled = false;
5744 this.removeHooks();
5745 return this;
5746 },
5747
5748 // @method enabled(): Boolean
5749 // Returns `true` if the handler is enabled
5750 enabled: function () {
5751 return !!this._enabled;
5752 }
5753
5754 // @section Extension methods
5755 // Classes inheriting from `Handler` must implement the two following methods:
5756 // @method addHooks()
5757 // Called when the handler is enabled, should add event hooks.
5758 // @method removeHooks()
5759 // Called when the handler is disabled, should remove the event hooks added previously.
5760});
5761
5762// @section There is static function which can be called without instantiating L.Handler:
5763// @function addTo(map: Map, name: String): this
5764// Adds a new Handler to the given map with the given name.
5765Handler.addTo = function (map, name) {
5766 map.addHandler(name, this);
5767 return this;
5768};
5769
5770var Mixin = {Events: Events};
5771
5772/*
5773 * @class Draggable
5774 * @aka L.Draggable
5775 * @inherits Evented
5776 *
5777 * A class for making DOM elements draggable (including touch support).
5778 * Used internally for map and marker dragging. Only works for elements
5779 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5780 *
5781 * @example
5782 * ```js
5783 * var draggable = new L.Draggable(elementToDrag);
5784 * draggable.enable();
5785 * ```
5786 */
5787
5788var START = touch ? 'touchstart mousedown' : 'mousedown';
5789var END = {
5790 mousedown: 'mouseup',
5791 touchstart: 'touchend',
5792 pointerdown: 'touchend',
5793 MSPointerDown: 'touchend'
5794};
5795var MOVE = {
5796 mousedown: 'mousemove',
5797 touchstart: 'touchmove',
5798 pointerdown: 'touchmove',
5799 MSPointerDown: 'touchmove'
5800};
5801
5802
5803var Draggable = Evented.extend({
5804
5805 options: {
5806 // @section
5807 // @aka Draggable options
5808 // @option clickTolerance: Number = 3
5809 // The max number of pixels a user can shift the mouse pointer during a click
5810 // for it to be considered a valid click (as opposed to a mouse drag).
5811 clickTolerance: 3
5812 },
5813
5814 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5815 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5816 initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5817 setOptions(this, options);
5818
5819 this._element = element;
5820 this._dragStartTarget = dragStartTarget || element;
5821 this._preventOutline = preventOutline$$1;
5822 },
5823
5824 // @method enable()
5825 // Enables the dragging ability
5826 enable: function () {
5827 if (this._enabled) { return; }
5828
5829 on(this._dragStartTarget, START, this._onDown, this);
5830
5831 this._enabled = true;
5832 },
5833
5834 // @method disable()
5835 // Disables the dragging ability
5836 disable: function () {
5837 if (!this._enabled) { return; }
5838
5839 // If we're currently dragging this draggable,
5840 // disabling it counts as first ending the drag.
5841 if (Draggable._dragging === this) {
5842 this.finishDrag();
5843 }
5844
5845 off(this._dragStartTarget, START, this._onDown, this);
5846
5847 this._enabled = false;
5848 this._moved = false;
5849 },
5850
5851 _onDown: function (e) {
5852 // Ignore simulated events, since we handle both touch and
5853 // mouse explicitly; otherwise we risk getting duplicates of
5854 // touch events, see #4315.
5855 // Also ignore the event if disabled; this happens in IE11
5856 // under some circumstances, see #3666.
5857 if (e._simulated || !this._enabled) { return; }
5858
5859 this._moved = false;
5860
5861 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5862
5863 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5864 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5865
5866 if (this._preventOutline) {
5867 preventOutline(this._element);
5868 }
5869
5870 disableImageDrag();
5871 disableTextSelection();
5872
5873 if (this._moving) { return; }
5874
5875 // @event down: Event
5876 // Fired when a drag is about to start.
5877 this.fire('down');
5878
5879 var first = e.touches ? e.touches[0] : e,
5880 sizedParent = getSizedParentNode(this._element);
5881
5882 this._startPoint = new Point(first.clientX, first.clientY);
5883
5884 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5885 this._parentScale = getScale(sizedParent);
5886
5887 on(document, MOVE[e.type], this._onMove, this);
5888 on(document, END[e.type], this._onUp, this);
5889 },
5890
5891 _onMove: function (e) {
5892 // Ignore simulated events, since we handle both touch and
5893 // mouse explicitly; otherwise we risk getting duplicates of
5894 // touch events, see #4315.
5895 // Also ignore the event if disabled; this happens in IE11
5896 // under some circumstances, see #3666.
5897 if (e._simulated || !this._enabled) { return; }
5898
5899 if (e.touches && e.touches.length > 1) {
5900 this._moved = true;
5901 return;
5902 }
5903
5904 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5905 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5906
5907 if (!offset.x && !offset.y) { return; }
5908 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5909
5910 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5911 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5912 // and we can use the cached value for the scale.
5913 offset.x /= this._parentScale.x;
5914 offset.y /= this._parentScale.y;
5915
5916 preventDefault(e);
5917
5918 if (!this._moved) {
5919 // @event dragstart: Event
5920 // Fired when a drag starts
5921 this.fire('dragstart');
5922
5923 this._moved = true;
5924 this._startPos = getPosition(this._element).subtract(offset);
5925
5926 addClass(document.body, 'leaflet-dragging');
5927
5928 this._lastTarget = e.target || e.srcElement;
5929 // IE and Edge do not give the <use> element, so fetch it
5930 // if necessary
5931 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5932 this._lastTarget = this._lastTarget.correspondingUseElement;
5933 }
5934 addClass(this._lastTarget, 'leaflet-drag-target');
5935 }
5936
5937 this._newPos = this._startPos.add(offset);
5938 this._moving = true;
5939
5940 cancelAnimFrame(this._animRequest);
5941 this._lastEvent = e;
5942 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5943 },
5944
5945 _updatePosition: function () {
5946 var e = {originalEvent: this._lastEvent};
5947
5948 // @event predrag: Event
5949 // Fired continuously during dragging *before* each corresponding
5950 // update of the element's position.
5951 this.fire('predrag', e);
5952 setPosition(this._element, this._newPos);
5953
5954 // @event drag: Event
5955 // Fired continuously during dragging.
5956 this.fire('drag', e);
5957 },
5958
5959 _onUp: function (e) {
5960 // Ignore simulated events, since we handle both touch and
5961 // mouse explicitly; otherwise we risk getting duplicates of
5962 // touch events, see #4315.
5963 // Also ignore the event if disabled; this happens in IE11
5964 // under some circumstances, see #3666.
5965 if (e._simulated || !this._enabled) { return; }
5966 this.finishDrag();
5967 },
5968
5969 finishDrag: function () {
5970 removeClass(document.body, 'leaflet-dragging');
5971
5972 if (this._lastTarget) {
5973 removeClass(this._lastTarget, 'leaflet-drag-target');
5974 this._lastTarget = null;
5975 }
5976
5977 for (var i in MOVE) {
5978 off(document, MOVE[i], this._onMove, this);
5979 off(document, END[i], this._onUp, this);
5980 }
5981
5982 enableImageDrag();
5983 enableTextSelection();
5984
5985 if (this._moved && this._moving) {
5986 // ensure drag is not fired after dragend
5987 cancelAnimFrame(this._animRequest);
5988
5989 // @event dragend: DragEndEvent
5990 // Fired when the drag ends.
5991 this.fire('dragend', {
5992 distance: this._newPos.distanceTo(this._startPos)
5993 });
5994 }
5995
5996 this._moving = false;
5997 Draggable._dragging = false;
5998 }
5999
6000});
6001
6002/*
6003 * @namespace LineUtil
6004 *
6005 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6006 */
6007
6008// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6009// Improves rendering performance dramatically by lessening the number of points to draw.
6010
6011// @function simplify(points: Point[], tolerance: Number): Point[]
6012// Dramatically reduces the number of points in a polyline while retaining
6013// its shape and returns a new array of simplified points, using the
6014// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6015// Used for a huge performance boost when processing/displaying Leaflet polylines for
6016// each zoom level and also reducing visual noise. tolerance affects the amount of
6017// simplification (lesser value means higher quality but slower and with more points).
6018// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6019function simplify(points, tolerance) {
6020 if (!tolerance || !points.length) {
6021 return points.slice();
6022 }
6023
6024 var sqTolerance = tolerance * tolerance;
6025
6026 // stage 1: vertex reduction
6027 points = _reducePoints(points, sqTolerance);
6028
6029 // stage 2: Douglas-Peucker simplification
6030 points = _simplifyDP(points, sqTolerance);
6031
6032 return points;
6033}
6034
6035// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6036// Returns the distance between point `p` and segment `p1` to `p2`.
6037function pointToSegmentDistance(p, p1, p2) {
6038 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6039}
6040
6041// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6042// Returns the closest point from a point `p` on a segment `p1` to `p2`.
6043function closestPointOnSegment(p, p1, p2) {
6044 return _sqClosestPointOnSegment(p, p1, p2);
6045}
6046
6047// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6048function _simplifyDP(points, sqTolerance) {
6049
6050 var len = points.length,
6051 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6052 markers = new ArrayConstructor(len);
6053
6054 markers[0] = markers[len - 1] = 1;
6055
6056 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6057
6058 var i,
6059 newPoints = [];
6060
6061 for (i = 0; i < len; i++) {
6062 if (markers[i]) {
6063 newPoints.push(points[i]);
6064 }
6065 }
6066
6067 return newPoints;
6068}
6069
6070function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6071
6072 var maxSqDist = 0,
6073 index, i, sqDist;
6074
6075 for (i = first + 1; i <= last - 1; i++) {
6076 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6077
6078 if (sqDist > maxSqDist) {
6079 index = i;
6080 maxSqDist = sqDist;
6081 }
6082 }
6083
6084 if (maxSqDist > sqTolerance) {
6085 markers[index] = 1;
6086
6087 _simplifyDPStep(points, markers, sqTolerance, first, index);
6088 _simplifyDPStep(points, markers, sqTolerance, index, last);
6089 }
6090}
6091
6092// reduce points that are too close to each other to a single point
6093function _reducePoints(points, sqTolerance) {
6094 var reducedPoints = [points[0]];
6095
6096 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6097 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6098 reducedPoints.push(points[i]);
6099 prev = i;
6100 }
6101 }
6102 if (prev < len - 1) {
6103 reducedPoints.push(points[len - 1]);
6104 }
6105 return reducedPoints;
6106}
6107
6108var _lastCode;
6109
6110// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6111// Clips the segment a to b by rectangular bounds with the
6112// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6113// (modifying the segment points directly!). Used by Leaflet to only show polyline
6114// points that are on the screen or near, increasing performance.
6115function clipSegment(a, b, bounds, useLastCode, round) {
6116 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6117 codeB = _getBitCode(b, bounds),
6118
6119 codeOut, p, newCode;
6120
6121 // save 2nd code to avoid calculating it on the next segment
6122 _lastCode = codeB;
6123
6124 while (true) {
6125 // if a,b is inside the clip window (trivial accept)
6126 if (!(codeA | codeB)) {
6127 return [a, b];
6128 }
6129
6130 // if a,b is outside the clip window (trivial reject)
6131 if (codeA & codeB) {
6132 return false;
6133 }
6134
6135 // other cases
6136 codeOut = codeA || codeB;
6137 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6138 newCode = _getBitCode(p, bounds);
6139
6140 if (codeOut === codeA) {
6141 a = p;
6142 codeA = newCode;
6143 } else {
6144 b = p;
6145 codeB = newCode;
6146 }
6147 }
6148}
6149
6150function _getEdgeIntersection(a, b, code, bounds, round) {
6151 var dx = b.x - a.x,
6152 dy = b.y - a.y,
6153 min = bounds.min,
6154 max = bounds.max,
6155 x, y;
6156
6157 if (code & 8) { // top
6158 x = a.x + dx * (max.y - a.y) / dy;
6159 y = max.y;
6160
6161 } else if (code & 4) { // bottom
6162 x = a.x + dx * (min.y - a.y) / dy;
6163 y = min.y;
6164
6165 } else if (code & 2) { // right
6166 x = max.x;
6167 y = a.y + dy * (max.x - a.x) / dx;
6168
6169 } else if (code & 1) { // left
6170 x = min.x;
6171 y = a.y + dy * (min.x - a.x) / dx;
6172 }
6173
6174 return new Point(x, y, round);
6175}
6176
6177function _getBitCode(p, bounds) {
6178 var code = 0;
6179
6180 if (p.x < bounds.min.x) { // left
6181 code |= 1;
6182 } else if (p.x > bounds.max.x) { // right
6183 code |= 2;
6184 }
6185
6186 if (p.y < bounds.min.y) { // bottom
6187 code |= 4;
6188 } else if (p.y > bounds.max.y) { // top
6189 code |= 8;
6190 }
6191
6192 return code;
6193}
6194
6195// square distance (to avoid unnecessary Math.sqrt calls)
6196function _sqDist(p1, p2) {
6197 var dx = p2.x - p1.x,
6198 dy = p2.y - p1.y;
6199 return dx * dx + dy * dy;
6200}
6201
6202// return closest point on segment or distance to that point
6203function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6204 var x = p1.x,
6205 y = p1.y,
6206 dx = p2.x - x,
6207 dy = p2.y - y,
6208 dot = dx * dx + dy * dy,
6209 t;
6210
6211 if (dot > 0) {
6212 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6213
6214 if (t > 1) {
6215 x = p2.x;
6216 y = p2.y;
6217 } else if (t > 0) {
6218 x += dx * t;
6219 y += dy * t;
6220 }
6221 }
6222
6223 dx = p.x - x;
6224 dy = p.y - y;
6225
6226 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6227}
6228
6229
6230// @function isFlat(latlngs: LatLng[]): Boolean
6231// Returns true if `latlngs` is a flat array, false is nested.
6232function isFlat(latlngs) {
6233 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6234}
6235
6236function _flat(latlngs) {
6237 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6238 return isFlat(latlngs);
6239}
6240
6241
6242var LineUtil = (Object.freeze || Object)({
6243 simplify: simplify,
6244 pointToSegmentDistance: pointToSegmentDistance,
6245 closestPointOnSegment: closestPointOnSegment,
6246 clipSegment: clipSegment,
6247 _getEdgeIntersection: _getEdgeIntersection,
6248 _getBitCode: _getBitCode,
6249 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6250 isFlat: isFlat,
6251 _flat: _flat
6252});
6253
6254/*
6255 * @namespace PolyUtil
6256 * Various utility functions for polygon geometries.
6257 */
6258
6259/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6260 * 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)).
6261 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6262 * performance. Note that polygon points needs different algorithm for clipping
6263 * than polyline, so there's a separate method for it.
6264 */
6265function clipPolygon(points, bounds, round) {
6266 var clippedPoints,
6267 edges = [1, 4, 2, 8],
6268 i, j, k,
6269 a, b,
6270 len, edge, p;
6271
6272 for (i = 0, len = points.length; i < len; i++) {
6273 points[i]._code = _getBitCode(points[i], bounds);
6274 }
6275
6276 // for each edge (left, bottom, right, top)
6277 for (k = 0; k < 4; k++) {
6278 edge = edges[k];
6279 clippedPoints = [];
6280
6281 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6282 a = points[i];
6283 b = points[j];
6284
6285 // if a is inside the clip window
6286 if (!(a._code & edge)) {
6287 // if b is outside the clip window (a->b goes out of screen)
6288 if (b._code & edge) {
6289 p = _getEdgeIntersection(b, a, edge, bounds, round);
6290 p._code = _getBitCode(p, bounds);
6291 clippedPoints.push(p);
6292 }
6293 clippedPoints.push(a);
6294
6295 // else if b is inside the clip window (a->b enters the screen)
6296 } else if (!(b._code & edge)) {
6297 p = _getEdgeIntersection(b, a, edge, bounds, round);
6298 p._code = _getBitCode(p, bounds);
6299 clippedPoints.push(p);
6300 }
6301 }
6302 points = clippedPoints;
6303 }
6304
6305 return points;
6306}
6307
6308
6309var PolyUtil = (Object.freeze || Object)({
6310 clipPolygon: clipPolygon
6311});
6312
6313/*
6314 * @namespace Projection
6315 * @section
6316 * Leaflet comes with a set of already defined Projections out of the box:
6317 *
6318 * @projection L.Projection.LonLat
6319 *
6320 * Equirectangular, or Plate Carree projection — the most simple projection,
6321 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6322 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6323 * `EPSG:4326` and `Simple` CRS.
6324 */
6325
6326var LonLat = {
6327 project: function (latlng) {
6328 return new Point(latlng.lng, latlng.lat);
6329 },
6330
6331 unproject: function (point) {
6332 return new LatLng(point.y, point.x);
6333 },
6334
6335 bounds: new Bounds([-180, -90], [180, 90])
6336};
6337
6338/*
6339 * @namespace Projection
6340 * @projection L.Projection.Mercator
6341 *
6342 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6343 */
6344
6345var Mercator = {
6346 R: 6378137,
6347 R_MINOR: 6356752.314245179,
6348
6349 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6350
6351 project: function (latlng) {
6352 var d = Math.PI / 180,
6353 r = this.R,
6354 y = latlng.lat * d,
6355 tmp = this.R_MINOR / r,
6356 e = Math.sqrt(1 - tmp * tmp),
6357 con = e * Math.sin(y);
6358
6359 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6360 y = -r * Math.log(Math.max(ts, 1E-10));
6361
6362 return new Point(latlng.lng * d * r, y);
6363 },
6364
6365 unproject: function (point) {
6366 var d = 180 / Math.PI,
6367 r = this.R,
6368 tmp = this.R_MINOR / r,
6369 e = Math.sqrt(1 - tmp * tmp),
6370 ts = Math.exp(-point.y / r),
6371 phi = Math.PI / 2 - 2 * Math.atan(ts);
6372
6373 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6374 con = e * Math.sin(phi);
6375 con = Math.pow((1 - con) / (1 + con), e / 2);
6376 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6377 phi += dphi;
6378 }
6379
6380 return new LatLng(phi * d, point.x * d / r);
6381 }
6382};
6383
6384/*
6385 * @class Projection
6386
6387 * An object with methods for projecting geographical coordinates of the world onto
6388 * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6389
6390 * @property bounds: Bounds
6391 * The bounds (specified in CRS units) where the projection is valid
6392
6393 * @method project(latlng: LatLng): Point
6394 * Projects geographical coordinates into a 2D point.
6395 * Only accepts actual `L.LatLng` instances, not arrays.
6396
6397 * @method unproject(point: Point): LatLng
6398 * The inverse of `project`. Projects a 2D point into a geographical location.
6399 * Only accepts actual `L.Point` instances, not arrays.
6400
6401 * Note that the projection instances do not inherit from Leafet's `Class` object,
6402 * and can't be instantiated. Also, new classes can't inherit from them,
6403 * and methods can't be added to them with the `include` function.
6404
6405 */
6406
6407
6408
6409
6410var index = (Object.freeze || Object)({
6411 LonLat: LonLat,
6412 Mercator: Mercator,
6413 SphericalMercator: SphericalMercator
6414});
6415
6416/*
6417 * @namespace CRS
6418 * @crs L.CRS.EPSG3395
6419 *
6420 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6421 */
6422var EPSG3395 = extend({}, Earth, {
6423 code: 'EPSG:3395',
6424 projection: Mercator,
6425
6426 transformation: (function () {
6427 var scale = 0.5 / (Math.PI * Mercator.R);
6428 return toTransformation(scale, 0.5, -scale, 0.5);
6429 }())
6430});
6431
6432/*
6433 * @namespace CRS
6434 * @crs L.CRS.EPSG4326
6435 *
6436 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6437 *
6438 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6439 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6440 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6441 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6442 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6443 */
6444
6445var EPSG4326 = extend({}, Earth, {
6446 code: 'EPSG:4326',
6447 projection: LonLat,
6448 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6449});
6450
6451/*
6452 * @namespace CRS
6453 * @crs L.CRS.Simple
6454 *
6455 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6456 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6457 * axis should still be inverted (going from bottom to top). `distance()` returns
6458 * simple euclidean distance.
6459 */
6460
6461var Simple = extend({}, CRS, {
6462 projection: LonLat,
6463 transformation: toTransformation(1, 0, -1, 0),
6464
6465 scale: function (zoom) {
6466 return Math.pow(2, zoom);
6467 },
6468
6469 zoom: function (scale) {
6470 return Math.log(scale) / Math.LN2;
6471 },
6472
6473 distance: function (latlng1, latlng2) {
6474 var dx = latlng2.lng - latlng1.lng,
6475 dy = latlng2.lat - latlng1.lat;
6476
6477 return Math.sqrt(dx * dx + dy * dy);
6478 },
6479
6480 infinite: true
6481});
6482
6483CRS.Earth = Earth;
6484CRS.EPSG3395 = EPSG3395;
6485CRS.EPSG3857 = EPSG3857;
6486CRS.EPSG900913 = EPSG900913;
6487CRS.EPSG4326 = EPSG4326;
6488CRS.Simple = Simple;
6489
6490/*
6491 * @class Layer
6492 * @inherits Evented
6493 * @aka L.Layer
6494 * @aka ILayer
6495 *
6496 * A set of methods from the Layer base class that all Leaflet layers use.
6497 * Inherits all methods, options and events from `L.Evented`.
6498 *
6499 * @example
6500 *
6501 * ```js
6502 * var layer = L.marker(latlng).addTo(map);
6503 * layer.addTo(map);
6504 * layer.remove();
6505 * ```
6506 *
6507 * @event add: Event
6508 * Fired after the layer is added to a map
6509 *
6510 * @event remove: Event
6511 * Fired after the layer is removed from a map
6512 */
6513
6514
6515var Layer = Evented.extend({
6516
6517 // Classes extending `L.Layer` will inherit the following options:
6518 options: {
6519 // @option pane: String = 'overlayPane'
6520 // 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.
6521 pane: 'overlayPane',
6522
6523 // @option attribution: String = null
6524 // 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.
6525 attribution: null,
6526
6527 bubblingMouseEvents: true
6528 },
6529
6530 /* @section
6531 * Classes extending `L.Layer` will inherit the following methods:
6532 *
6533 * @method addTo(map: Map|LayerGroup): this
6534 * Adds the layer to the given map or layer group.
6535 */
6536 addTo: function (map) {
6537 map.addLayer(this);
6538 return this;
6539 },
6540
6541 // @method remove: this
6542 // Removes the layer from the map it is currently active on.
6543 remove: function () {
6544 return this.removeFrom(this._map || this._mapToAdd);
6545 },
6546
6547 // @method removeFrom(map: Map): this
6548 // Removes the layer from the given map
6549 removeFrom: function (obj) {
6550 if (obj) {
6551 obj.removeLayer(this);
6552 }
6553 return this;
6554 },
6555
6556 // @method getPane(name? : String): HTMLElement
6557 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6558 getPane: function (name) {
6559 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6560 },
6561
6562 addInteractiveTarget: function (targetEl) {
6563 this._map._targets[stamp(targetEl)] = this;
6564 return this;
6565 },
6566
6567 removeInteractiveTarget: function (targetEl) {
6568 delete this._map._targets[stamp(targetEl)];
6569 return this;
6570 },
6571
6572 // @method getAttribution: String
6573 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6574 getAttribution: function () {
6575 return this.options.attribution;
6576 },
6577
6578 _layerAdd: function (e) {
6579 var map = e.target;
6580
6581 // check in case layer gets added and then removed before the map is ready
6582 if (!map.hasLayer(this)) { return; }
6583
6584 this._map = map;
6585 this._zoomAnimated = map._zoomAnimated;
6586
6587 if (this.getEvents) {
6588 var events = this.getEvents();
6589 map.on(events, this);
6590 this.once('remove', function () {
6591 map.off(events, this);
6592 }, this);
6593 }
6594
6595 this.onAdd(map);
6596
6597 if (this.getAttribution && map.attributionControl) {
6598 map.attributionControl.addAttribution(this.getAttribution());
6599 }
6600
6601 this.fire('add');
6602 map.fire('layeradd', {layer: this});
6603 }
6604});
6605
6606/* @section Extension methods
6607 * @uninheritable
6608 *
6609 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6610 *
6611 * @method onAdd(map: Map): this
6612 * 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).
6613 *
6614 * @method onRemove(map: Map): this
6615 * 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).
6616 *
6617 * @method getEvents(): Object
6618 * 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.
6619 *
6620 * @method getAttribution(): String
6621 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6622 *
6623 * @method beforeAdd(map: Map): this
6624 * 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.
6625 */
6626
6627
6628/* @namespace Map
6629 * @section Layer events
6630 *
6631 * @event layeradd: LayerEvent
6632 * Fired when a new layer is added to the map.
6633 *
6634 * @event layerremove: LayerEvent
6635 * Fired when some layer is removed from the map
6636 *
6637 * @section Methods for Layers and Controls
6638 */
6639Map.include({
6640 // @method addLayer(layer: Layer): this
6641 // Adds the given layer to the map
6642 addLayer: function (layer) {
6643 if (!layer._layerAdd) {
6644 throw new Error('The provided object is not a Layer.');
6645 }
6646
6647 var id = stamp(layer);
6648 if (this._layers[id]) { return this; }
6649 this._layers[id] = layer;
6650
6651 layer._mapToAdd = this;
6652
6653 if (layer.beforeAdd) {
6654 layer.beforeAdd(this);
6655 }
6656
6657 this.whenReady(layer._layerAdd, layer);
6658
6659 return this;
6660 },
6661
6662 // @method removeLayer(layer: Layer): this
6663 // Removes the given layer from the map.
6664 removeLayer: function (layer) {
6665 var id = stamp(layer);
6666
6667 if (!this._layers[id]) { return this; }
6668
6669 if (this._loaded) {
6670 layer.onRemove(this);
6671 }
6672
6673 if (layer.getAttribution && this.attributionControl) {
6674 this.attributionControl.removeAttribution(layer.getAttribution());
6675 }
6676
6677 delete this._layers[id];
6678
6679 if (this._loaded) {
6680 this.fire('layerremove', {layer: layer});
6681 layer.fire('remove');
6682 }
6683
6684 layer._map = layer._mapToAdd = null;
6685
6686 return this;
6687 },
6688
6689 // @method hasLayer(layer: Layer): Boolean
6690 // Returns `true` if the given layer is currently added to the map
6691 hasLayer: function (layer) {
6692 return !!layer && (stamp(layer) in this._layers);
6693 },
6694
6695 /* @method eachLayer(fn: Function, context?: Object): this
6696 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6697 * ```
6698 * map.eachLayer(function(layer){
6699 * layer.bindPopup('Hello');
6700 * });
6701 * ```
6702 */
6703 eachLayer: function (method, context) {
6704 for (var i in this._layers) {
6705 method.call(context, this._layers[i]);
6706 }
6707 return this;
6708 },
6709
6710 _addLayers: function (layers) {
6711 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6712
6713 for (var i = 0, len = layers.length; i < len; i++) {
6714 this.addLayer(layers[i]);
6715 }
6716 },
6717
6718 _addZoomLimit: function (layer) {
6719 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6720 this._zoomBoundLayers[stamp(layer)] = layer;
6721 this._updateZoomLevels();
6722 }
6723 },
6724
6725 _removeZoomLimit: function (layer) {
6726 var id = stamp(layer);
6727
6728 if (this._zoomBoundLayers[id]) {
6729 delete this._zoomBoundLayers[id];
6730 this._updateZoomLevels();
6731 }
6732 },
6733
6734 _updateZoomLevels: function () {
6735 var minZoom = Infinity,
6736 maxZoom = -Infinity,
6737 oldZoomSpan = this._getZoomSpan();
6738
6739 for (var i in this._zoomBoundLayers) {
6740 var options = this._zoomBoundLayers[i].options;
6741
6742 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6743 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6744 }
6745
6746 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6747 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6748
6749 // @section Map state change events
6750 // @event zoomlevelschange: Event
6751 // Fired when the number of zoomlevels on the map is changed due
6752 // to adding or removing a layer.
6753 if (oldZoomSpan !== this._getZoomSpan()) {
6754 this.fire('zoomlevelschange');
6755 }
6756
6757 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6758 this.setZoom(this._layersMaxZoom);
6759 }
6760 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6761 this.setZoom(this._layersMinZoom);
6762 }
6763 }
6764});
6765
6766/*
6767 * @class LayerGroup
6768 * @aka L.LayerGroup
6769 * @inherits Layer
6770 *
6771 * Used to group several layers and handle them as one. If you add it to the map,
6772 * any layers added or removed from the group will be added/removed on the map as
6773 * well. Extends `Layer`.
6774 *
6775 * @example
6776 *
6777 * ```js
6778 * L.layerGroup([marker1, marker2])
6779 * .addLayer(polyline)
6780 * .addTo(map);
6781 * ```
6782 */
6783
6784var LayerGroup = Layer.extend({
6785
6786 initialize: function (layers, options) {
6787 setOptions(this, options);
6788
6789 this._layers = {};
6790
6791 var i, len;
6792
6793 if (layers) {
6794 for (i = 0, len = layers.length; i < len; i++) {
6795 this.addLayer(layers[i]);
6796 }
6797 }
6798 },
6799
6800 // @method addLayer(layer: Layer): this
6801 // Adds the given layer to the group.
6802 addLayer: function (layer) {
6803 var id = this.getLayerId(layer);
6804
6805 this._layers[id] = layer;
6806
6807 if (this._map) {
6808 this._map.addLayer(layer);
6809 }
6810
6811 return this;
6812 },
6813
6814 // @method removeLayer(layer: Layer): this
6815 // Removes the given layer from the group.
6816 // @alternative
6817 // @method removeLayer(id: Number): this
6818 // Removes the layer with the given internal ID from the group.
6819 removeLayer: function (layer) {
6820 var id = layer in this._layers ? layer : this.getLayerId(layer);
6821
6822 if (this._map && this._layers[id]) {
6823 this._map.removeLayer(this._layers[id]);
6824 }
6825
6826 delete this._layers[id];
6827
6828 return this;
6829 },
6830
6831 // @method hasLayer(layer: Layer): Boolean
6832 // Returns `true` if the given layer is currently added to the group.
6833 // @alternative
6834 // @method hasLayer(id: Number): Boolean
6835 // Returns `true` if the given internal ID is currently added to the group.
6836 hasLayer: function (layer) {
6837 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6838 },
6839
6840 // @method clearLayers(): this
6841 // Removes all the layers from the group.
6842 clearLayers: function () {
6843 return this.eachLayer(this.removeLayer, this);
6844 },
6845
6846 // @method invoke(methodName: String, …): this
6847 // Calls `methodName` on every layer contained in this group, passing any
6848 // additional parameters. Has no effect if the layers contained do not
6849 // implement `methodName`.
6850 invoke: function (methodName) {
6851 var args = Array.prototype.slice.call(arguments, 1),
6852 i, layer;
6853
6854 for (i in this._layers) {
6855 layer = this._layers[i];
6856
6857 if (layer[methodName]) {
6858 layer[methodName].apply(layer, args);
6859 }
6860 }
6861
6862 return this;
6863 },
6864
6865 onAdd: function (map) {
6866 this.eachLayer(map.addLayer, map);
6867 },
6868
6869 onRemove: function (map) {
6870 this.eachLayer(map.removeLayer, map);
6871 },
6872
6873 // @method eachLayer(fn: Function, context?: Object): this
6874 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6875 // ```js
6876 // group.eachLayer(function (layer) {
6877 // layer.bindPopup('Hello');
6878 // });
6879 // ```
6880 eachLayer: function (method, context) {
6881 for (var i in this._layers) {
6882 method.call(context, this._layers[i]);
6883 }
6884 return this;
6885 },
6886
6887 // @method getLayer(id: Number): Layer
6888 // Returns the layer with the given internal ID.
6889 getLayer: function (id) {
6890 return this._layers[id];
6891 },
6892
6893 // @method getLayers(): Layer[]
6894 // Returns an array of all the layers added to the group.
6895 getLayers: function () {
6896 var layers = [];
6897 this.eachLayer(layers.push, layers);
6898 return layers;
6899 },
6900
6901 // @method setZIndex(zIndex: Number): this
6902 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6903 setZIndex: function (zIndex) {
6904 return this.invoke('setZIndex', zIndex);
6905 },
6906
6907 // @method getLayerId(layer: Layer): Number
6908 // Returns the internal ID for a layer
6909 getLayerId: function (layer) {
6910 return stamp(layer);
6911 }
6912});
6913
6914
6915// @factory L.layerGroup(layers?: Layer[], options?: Object)
6916// Create a layer group, optionally given an initial set of layers and an `options` object.
6917var layerGroup = function (layers, options) {
6918 return new LayerGroup(layers, options);
6919};
6920
6921/*
6922 * @class FeatureGroup
6923 * @aka L.FeatureGroup
6924 * @inherits LayerGroup
6925 *
6926 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6927 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6928 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6929 * handler, it will handle events from any of the layers. This includes mouse events
6930 * and custom events.
6931 * * Has `layeradd` and `layerremove` events
6932 *
6933 * @example
6934 *
6935 * ```js
6936 * L.featureGroup([marker1, marker2, polyline])
6937 * .bindPopup('Hello world!')
6938 * .on('click', function() { alert('Clicked on a member of the group!'); })
6939 * .addTo(map);
6940 * ```
6941 */
6942
6943var FeatureGroup = LayerGroup.extend({
6944
6945 addLayer: function (layer) {
6946 if (this.hasLayer(layer)) {
6947 return this;
6948 }
6949
6950 layer.addEventParent(this);
6951
6952 LayerGroup.prototype.addLayer.call(this, layer);
6953
6954 // @event layeradd: LayerEvent
6955 // Fired when a layer is added to this `FeatureGroup`
6956 return this.fire('layeradd', {layer: layer});
6957 },
6958
6959 removeLayer: function (layer) {
6960 if (!this.hasLayer(layer)) {
6961 return this;
6962 }
6963 if (layer in this._layers) {
6964 layer = this._layers[layer];
6965 }
6966
6967 layer.removeEventParent(this);
6968
6969 LayerGroup.prototype.removeLayer.call(this, layer);
6970
6971 // @event layerremove: LayerEvent
6972 // Fired when a layer is removed from this `FeatureGroup`
6973 return this.fire('layerremove', {layer: layer});
6974 },
6975
6976 // @method setStyle(style: Path options): this
6977 // Sets the given path options to each layer of the group that has a `setStyle` method.
6978 setStyle: function (style) {
6979 return this.invoke('setStyle', style);
6980 },
6981
6982 // @method bringToFront(): this
6983 // Brings the layer group to the top of all other layers
6984 bringToFront: function () {
6985 return this.invoke('bringToFront');
6986 },
6987
6988 // @method bringToBack(): this
6989 // Brings the layer group to the back of all other layers
6990 bringToBack: function () {
6991 return this.invoke('bringToBack');
6992 },
6993
6994 // @method getBounds(): LatLngBounds
6995 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6996 getBounds: function () {
6997 var bounds = new LatLngBounds();
6998
6999 for (var id in this._layers) {
7000 var layer = this._layers[id];
7001 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7002 }
7003 return bounds;
7004 }
7005});
7006
7007// @factory L.featureGroup(layers: Layer[])
7008// Create a feature group, optionally given an initial set of layers.
7009var featureGroup = function (layers) {
7010 return new FeatureGroup(layers);
7011};
7012
7013/*
7014 * @class Icon
7015 * @aka L.Icon
7016 *
7017 * Represents an icon to provide when creating a marker.
7018 *
7019 * @example
7020 *
7021 * ```js
7022 * var myIcon = L.icon({
7023 * iconUrl: 'my-icon.png',
7024 * iconRetinaUrl: 'my-icon@2x.png',
7025 * iconSize: [38, 95],
7026 * iconAnchor: [22, 94],
7027 * popupAnchor: [-3, -76],
7028 * shadowUrl: 'my-icon-shadow.png',
7029 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7030 * shadowSize: [68, 95],
7031 * shadowAnchor: [22, 94]
7032 * });
7033 *
7034 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7035 * ```
7036 *
7037 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7038 *
7039 */
7040
7041var Icon = Class.extend({
7042
7043 /* @section
7044 * @aka Icon options
7045 *
7046 * @option iconUrl: String = null
7047 * **(required)** The URL to the icon image (absolute or relative to your script path).
7048 *
7049 * @option iconRetinaUrl: String = null
7050 * The URL to a retina sized version of the icon image (absolute or relative to your
7051 * script path). Used for Retina screen devices.
7052 *
7053 * @option iconSize: Point = null
7054 * Size of the icon image in pixels.
7055 *
7056 * @option iconAnchor: Point = null
7057 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7058 * will be aligned so that this point is at the marker's geographical location. Centered
7059 * by default if size is specified, also can be set in CSS with negative margins.
7060 *
7061 * @option popupAnchor: Point = [0, 0]
7062 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7063 *
7064 * @option tooltipAnchor: Point = [0, 0]
7065 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7066 *
7067 * @option shadowUrl: String = null
7068 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7069 *
7070 * @option shadowRetinaUrl: String = null
7071 *
7072 * @option shadowSize: Point = null
7073 * Size of the shadow image in pixels.
7074 *
7075 * @option shadowAnchor: Point = null
7076 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7077 * as iconAnchor if not specified).
7078 *
7079 * @option className: String = ''
7080 * A custom class name to assign to both icon and shadow images. Empty by default.
7081 */
7082
7083 options: {
7084 popupAnchor: [0, 0],
7085 tooltipAnchor: [0, 0]
7086 },
7087
7088 initialize: function (options) {
7089 setOptions(this, options);
7090 },
7091
7092 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7093 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7094 // styled according to the options.
7095 createIcon: function (oldIcon) {
7096 return this._createIcon('icon', oldIcon);
7097 },
7098
7099 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7100 // As `createIcon`, but for the shadow beneath it.
7101 createShadow: function (oldIcon) {
7102 return this._createIcon('shadow', oldIcon);
7103 },
7104
7105 _createIcon: function (name, oldIcon) {
7106 var src = this._getIconUrl(name);
7107
7108 if (!src) {
7109 if (name === 'icon') {
7110 throw new Error('iconUrl not set in Icon options (see the docs).');
7111 }
7112 return null;
7113 }
7114
7115 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7116 this._setIconStyles(img, name);
7117
7118 return img;
7119 },
7120
7121 _setIconStyles: function (img, name) {
7122 var options = this.options;
7123 var sizeOption = options[name + 'Size'];
7124
7125 if (typeof sizeOption === 'number') {
7126 sizeOption = [sizeOption, sizeOption];
7127 }
7128
7129 var size = toPoint(sizeOption),
7130 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7131 size && size.divideBy(2, true));
7132
7133 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7134
7135 if (anchor) {
7136 img.style.marginLeft = (-anchor.x) + 'px';
7137 img.style.marginTop = (-anchor.y) + 'px';
7138 }
7139
7140 if (size) {
7141 img.style.width = size.x + 'px';
7142 img.style.height = size.y + 'px';
7143 }
7144 },
7145
7146 _createImg: function (src, el) {
7147 el = el || document.createElement('img');
7148 el.src = src;
7149 return el;
7150 },
7151
7152 _getIconUrl: function (name) {
7153 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7154 }
7155});
7156
7157
7158// @factory L.icon(options: Icon options)
7159// Creates an icon instance with the given options.
7160function icon(options) {
7161 return new Icon(options);
7162}
7163
7164/*
7165 * @miniclass Icon.Default (Icon)
7166 * @aka L.Icon.Default
7167 * @section
7168 *
7169 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7170 * no icon is specified. Points to the blue marker image distributed with Leaflet
7171 * releases.
7172 *
7173 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7174 * (which is a set of `Icon options`).
7175 *
7176 * If you want to _completely_ replace the default icon, override the
7177 * `L.Marker.prototype.options.icon` with your own icon instead.
7178 */
7179
7180var IconDefault = Icon.extend({
7181
7182 options: {
7183 iconUrl: 'marker-icon.png',
7184 iconRetinaUrl: 'marker-icon-2x.png',
7185 shadowUrl: 'marker-shadow.png',
7186 iconSize: [25, 41],
7187 iconAnchor: [12, 41],
7188 popupAnchor: [1, -34],
7189 tooltipAnchor: [16, -28],
7190 shadowSize: [41, 41]
7191 },
7192
7193 _getIconUrl: function (name) {
7194 if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
7195 IconDefault.imagePath = this._detectIconPath();
7196 }
7197
7198 // @option imagePath: String
7199 // `Icon.Default` will try to auto-detect the location of the
7200 // blue icon images. If you are placing these images in a non-standard
7201 // way, set this option to point to the right path.
7202 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7203 },
7204
7205 _detectIconPath: function () {
7206 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7207 var path = getStyle(el, 'background-image') ||
7208 getStyle(el, 'backgroundImage'); // IE8
7209
7210 document.body.removeChild(el);
7211
7212 if (path === null || path.indexOf('url') !== 0) {
7213 path = '';
7214 } else {
7215 path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7216 }
7217
7218 return path;
7219 }
7220});
7221
7222/*
7223 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7224 */
7225
7226
7227/* @namespace Marker
7228 * @section Interaction handlers
7229 *
7230 * 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:
7231 *
7232 * ```js
7233 * marker.dragging.disable();
7234 * ```
7235 *
7236 * @property dragging: Handler
7237 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7238 */
7239
7240var MarkerDrag = Handler.extend({
7241 initialize: function (marker) {
7242 this._marker = marker;
7243 },
7244
7245 addHooks: function () {
7246 var icon = this._marker._icon;
7247
7248 if (!this._draggable) {
7249 this._draggable = new Draggable(icon, icon, true);
7250 }
7251
7252 this._draggable.on({
7253 dragstart: this._onDragStart,
7254 predrag: this._onPreDrag,
7255 drag: this._onDrag,
7256 dragend: this._onDragEnd
7257 }, this).enable();
7258
7259 addClass(icon, 'leaflet-marker-draggable');
7260 },
7261
7262 removeHooks: function () {
7263 this._draggable.off({
7264 dragstart: this._onDragStart,
7265 predrag: this._onPreDrag,
7266 drag: this._onDrag,
7267 dragend: this._onDragEnd
7268 }, this).disable();
7269
7270 if (this._marker._icon) {
7271 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7272 }
7273 },
7274
7275 moved: function () {
7276 return this._draggable && this._draggable._moved;
7277 },
7278
7279 _adjustPan: function (e) {
7280 var marker = this._marker,
7281 map = marker._map,
7282 speed = this._marker.options.autoPanSpeed,
7283 padding = this._marker.options.autoPanPadding,
7284 iconPos = getPosition(marker._icon),
7285 bounds = map.getPixelBounds(),
7286 origin = map.getPixelOrigin();
7287
7288 var panBounds = toBounds(
7289 bounds.min._subtract(origin).add(padding),
7290 bounds.max._subtract(origin).subtract(padding)
7291 );
7292
7293 if (!panBounds.contains(iconPos)) {
7294 // Compute incremental movement
7295 var movement = toPoint(
7296 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7297 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7298
7299 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7300 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7301 ).multiplyBy(speed);
7302
7303 map.panBy(movement, {animate: false});
7304
7305 this._draggable._newPos._add(movement);
7306 this._draggable._startPos._add(movement);
7307
7308 setPosition(marker._icon, this._draggable._newPos);
7309 this._onDrag(e);
7310
7311 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7312 }
7313 },
7314
7315 _onDragStart: function () {
7316 // @section Dragging events
7317 // @event dragstart: Event
7318 // Fired when the user starts dragging the marker.
7319
7320 // @event movestart: Event
7321 // Fired when the marker starts moving (because of dragging).
7322
7323 this._oldLatLng = this._marker.getLatLng();
7324 this._marker
7325 .closePopup()
7326 .fire('movestart')
7327 .fire('dragstart');
7328 },
7329
7330 _onPreDrag: function (e) {
7331 if (this._marker.options.autoPan) {
7332 cancelAnimFrame(this._panRequest);
7333 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7334 }
7335 },
7336
7337 _onDrag: function (e) {
7338 var marker = this._marker,
7339 shadow = marker._shadow,
7340 iconPos = getPosition(marker._icon),
7341 latlng = marker._map.layerPointToLatLng(iconPos);
7342
7343 // update shadow position
7344 if (shadow) {
7345 setPosition(shadow, iconPos);
7346 }
7347
7348 marker._latlng = latlng;
7349 e.latlng = latlng;
7350 e.oldLatLng = this._oldLatLng;
7351
7352 // @event drag: Event
7353 // Fired repeatedly while the user drags the marker.
7354 marker
7355 .fire('move', e)
7356 .fire('drag', e);
7357 },
7358
7359 _onDragEnd: function (e) {
7360 // @event dragend: DragEndEvent
7361 // Fired when the user stops dragging the marker.
7362
7363 cancelAnimFrame(this._panRequest);
7364
7365 // @event moveend: Event
7366 // Fired when the marker stops moving (because of dragging).
7367 delete this._oldLatLng;
7368 this._marker
7369 .fire('moveend')
7370 .fire('dragend', e);
7371 }
7372});
7373
7374/*
7375 * @class Marker
7376 * @inherits Interactive layer
7377 * @aka L.Marker
7378 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7379 *
7380 * @example
7381 *
7382 * ```js
7383 * L.marker([50.5, 30.5]).addTo(map);
7384 * ```
7385 */
7386
7387var Marker = Layer.extend({
7388
7389 // @section
7390 // @aka Marker options
7391 options: {
7392 // @option icon: Icon = *
7393 // Icon instance to use for rendering the marker.
7394 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7395 // If not specified, a common instance of `L.Icon.Default` is used.
7396 icon: new IconDefault(),
7397
7398 // Option inherited from "Interactive layer" abstract class
7399 interactive: true,
7400
7401 // @option keyboard: Boolean = true
7402 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7403 keyboard: true,
7404
7405 // @option title: String = ''
7406 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7407 title: '',
7408
7409 // @option alt: String = ''
7410 // Text for the `alt` attribute of the icon image (useful for accessibility).
7411 alt: '',
7412
7413 // @option zIndexOffset: Number = 0
7414 // 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).
7415 zIndexOffset: 0,
7416
7417 // @option opacity: Number = 1.0
7418 // The opacity of the marker.
7419 opacity: 1,
7420
7421 // @option riseOnHover: Boolean = false
7422 // If `true`, the marker will get on top of others when you hover the mouse over it.
7423 riseOnHover: false,
7424
7425 // @option riseOffset: Number = 250
7426 // The z-index offset used for the `riseOnHover` feature.
7427 riseOffset: 250,
7428
7429 // @option pane: String = 'markerPane'
7430 // `Map pane` where the markers icon will be added.
7431 pane: 'markerPane',
7432
7433 // @option pane: String = 'shadowPane'
7434 // `Map pane` where the markers shadow will be added.
7435 shadowPane: 'shadowPane',
7436
7437 // @option bubblingMouseEvents: Boolean = false
7438 // When `true`, a mouse event on this marker will trigger the same event on the map
7439 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7440 bubblingMouseEvents: false,
7441
7442 // @section Draggable marker options
7443 // @option draggable: Boolean = false
7444 // Whether the marker is draggable with mouse/touch or not.
7445 draggable: false,
7446
7447 // @option autoPan: Boolean = false
7448 // Whether to pan the map when dragging this marker near its edge or not.
7449 autoPan: false,
7450
7451 // @option autoPanPadding: Point = Point(50, 50)
7452 // Distance (in pixels to the left/right and to the top/bottom) of the
7453 // map edge to start panning the map.
7454 autoPanPadding: [50, 50],
7455
7456 // @option autoPanSpeed: Number = 10
7457 // Number of pixels the map should pan by.
7458 autoPanSpeed: 10
7459 },
7460
7461 /* @section
7462 *
7463 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7464 */
7465
7466 initialize: function (latlng, options) {
7467 setOptions(this, options);
7468 this._latlng = toLatLng(latlng);
7469 },
7470
7471 onAdd: function (map) {
7472 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7473
7474 if (this._zoomAnimated) {
7475 map.on('zoomanim', this._animateZoom, this);
7476 }
7477
7478 this._initIcon();
7479 this.update();
7480 },
7481
7482 onRemove: function (map) {
7483 if (this.dragging && this.dragging.enabled()) {
7484 this.options.draggable = true;
7485 this.dragging.removeHooks();
7486 }
7487 delete this.dragging;
7488
7489 if (this._zoomAnimated) {
7490 map.off('zoomanim', this._animateZoom, this);
7491 }
7492
7493 this._removeIcon();
7494 this._removeShadow();
7495 },
7496
7497 getEvents: function () {
7498 return {
7499 zoom: this.update,
7500 viewreset: this.update
7501 };
7502 },
7503
7504 // @method getLatLng: LatLng
7505 // Returns the current geographical position of the marker.
7506 getLatLng: function () {
7507 return this._latlng;
7508 },
7509
7510 // @method setLatLng(latlng: LatLng): this
7511 // Changes the marker position to the given point.
7512 setLatLng: function (latlng) {
7513 var oldLatLng = this._latlng;
7514 this._latlng = toLatLng(latlng);
7515 this.update();
7516
7517 // @event move: Event
7518 // 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`.
7519 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7520 },
7521
7522 // @method setZIndexOffset(offset: Number): this
7523 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7524 setZIndexOffset: function (offset) {
7525 this.options.zIndexOffset = offset;
7526 return this.update();
7527 },
7528
7529 // @method getIcon: Icon
7530 // Returns the current icon used by the marker
7531 getIcon: function () {
7532 return this.options.icon;
7533 },
7534
7535 // @method setIcon(icon: Icon): this
7536 // Changes the marker icon.
7537 setIcon: function (icon) {
7538
7539 this.options.icon = icon;
7540
7541 if (this._map) {
7542 this._initIcon();
7543 this.update();
7544 }
7545
7546 if (this._popup) {
7547 this.bindPopup(this._popup, this._popup.options);
7548 }
7549
7550 return this;
7551 },
7552
7553 getElement: function () {
7554 return this._icon;
7555 },
7556
7557 update: function () {
7558
7559 if (this._icon && this._map) {
7560 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7561 this._setPos(pos);
7562 }
7563
7564 return this;
7565 },
7566
7567 _initIcon: function () {
7568 var options = this.options,
7569 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7570
7571 var icon = options.icon.createIcon(this._icon),
7572 addIcon = false;
7573
7574 // if we're not reusing the icon, remove the old one and init new one
7575 if (icon !== this._icon) {
7576 if (this._icon) {
7577 this._removeIcon();
7578 }
7579 addIcon = true;
7580
7581 if (options.title) {
7582 icon.title = options.title;
7583 }
7584
7585 if (icon.tagName === 'IMG') {
7586 icon.alt = options.alt || '';
7587 }
7588 }
7589
7590 addClass(icon, classToAdd);
7591
7592 if (options.keyboard) {
7593 icon.tabIndex = '0';
7594 }
7595
7596 this._icon = icon;
7597
7598 if (options.riseOnHover) {
7599 this.on({
7600 mouseover: this._bringToFront,
7601 mouseout: this._resetZIndex
7602 });
7603 }
7604
7605 var newShadow = options.icon.createShadow(this._shadow),
7606 addShadow = false;
7607
7608 if (newShadow !== this._shadow) {
7609 this._removeShadow();
7610 addShadow = true;
7611 }
7612
7613 if (newShadow) {
7614 addClass(newShadow, classToAdd);
7615 newShadow.alt = '';
7616 }
7617 this._shadow = newShadow;
7618
7619
7620 if (options.opacity < 1) {
7621 this._updateOpacity();
7622 }
7623
7624
7625 if (addIcon) {
7626 this.getPane().appendChild(this._icon);
7627 }
7628 this._initInteraction();
7629 if (newShadow && addShadow) {
7630 this.getPane(options.shadowPane).appendChild(this._shadow);
7631 }
7632 },
7633
7634 _removeIcon: function () {
7635 if (this.options.riseOnHover) {
7636 this.off({
7637 mouseover: this._bringToFront,
7638 mouseout: this._resetZIndex
7639 });
7640 }
7641
7642 remove(this._icon);
7643 this.removeInteractiveTarget(this._icon);
7644
7645 this._icon = null;
7646 },
7647
7648 _removeShadow: function () {
7649 if (this._shadow) {
7650 remove(this._shadow);
7651 }
7652 this._shadow = null;
7653 },
7654
7655 _setPos: function (pos) {
7656 setPosition(this._icon, pos);
7657
7658 if (this._shadow) {
7659 setPosition(this._shadow, pos);
7660 }
7661
7662 this._zIndex = pos.y + this.options.zIndexOffset;
7663
7664 this._resetZIndex();
7665 },
7666
7667 _updateZIndex: function (offset) {
7668 this._icon.style.zIndex = this._zIndex + offset;
7669 },
7670
7671 _animateZoom: function (opt) {
7672 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7673
7674 this._setPos(pos);
7675 },
7676
7677 _initInteraction: function () {
7678
7679 if (!this.options.interactive) { return; }
7680
7681 addClass(this._icon, 'leaflet-interactive');
7682
7683 this.addInteractiveTarget(this._icon);
7684
7685 if (MarkerDrag) {
7686 var draggable = this.options.draggable;
7687 if (this.dragging) {
7688 draggable = this.dragging.enabled();
7689 this.dragging.disable();
7690 }
7691
7692 this.dragging = new MarkerDrag(this);
7693
7694 if (draggable) {
7695 this.dragging.enable();
7696 }
7697 }
7698 },
7699
7700 // @method setOpacity(opacity: Number): this
7701 // Changes the opacity of the marker.
7702 setOpacity: function (opacity) {
7703 this.options.opacity = opacity;
7704 if (this._map) {
7705 this._updateOpacity();
7706 }
7707
7708 return this;
7709 },
7710
7711 _updateOpacity: function () {
7712 var opacity = this.options.opacity;
7713
7714 if (this._icon) {
7715 setOpacity(this._icon, opacity);
7716 }
7717
7718 if (this._shadow) {
7719 setOpacity(this._shadow, opacity);
7720 }
7721 },
7722
7723 _bringToFront: function () {
7724 this._updateZIndex(this.options.riseOffset);
7725 },
7726
7727 _resetZIndex: function () {
7728 this._updateZIndex(0);
7729 },
7730
7731 _getPopupAnchor: function () {
7732 return this.options.icon.options.popupAnchor;
7733 },
7734
7735 _getTooltipAnchor: function () {
7736 return this.options.icon.options.tooltipAnchor;
7737 }
7738});
7739
7740
7741// factory L.marker(latlng: LatLng, options? : Marker options)
7742
7743// @factory L.marker(latlng: LatLng, options? : Marker options)
7744// Instantiates a Marker object given a geographical point and optionally an options object.
7745function marker(latlng, options) {
7746 return new Marker(latlng, options);
7747}
7748
7749/*
7750 * @class Path
7751 * @aka L.Path
7752 * @inherits Interactive layer
7753 *
7754 * An abstract class that contains options and constants shared between vector
7755 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7756 */
7757
7758var Path = Layer.extend({
7759
7760 // @section
7761 // @aka Path options
7762 options: {
7763 // @option stroke: Boolean = true
7764 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7765 stroke: true,
7766
7767 // @option color: String = '#3388ff'
7768 // Stroke color
7769 color: '#3388ff',
7770
7771 // @option weight: Number = 3
7772 // Stroke width in pixels
7773 weight: 3,
7774
7775 // @option opacity: Number = 1.0
7776 // Stroke opacity
7777 opacity: 1,
7778
7779 // @option lineCap: String= 'round'
7780 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7781 lineCap: 'round',
7782
7783 // @option lineJoin: String = 'round'
7784 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7785 lineJoin: 'round',
7786
7787 // @option dashArray: String = null
7788 // 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).
7789 dashArray: null,
7790
7791 // @option dashOffset: String = null
7792 // 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).
7793 dashOffset: null,
7794
7795 // @option fill: Boolean = depends
7796 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7797 fill: false,
7798
7799 // @option fillColor: String = *
7800 // Fill color. Defaults to the value of the [`color`](#path-color) option
7801 fillColor: null,
7802
7803 // @option fillOpacity: Number = 0.2
7804 // Fill opacity.
7805 fillOpacity: 0.2,
7806
7807 // @option fillRule: String = 'evenodd'
7808 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7809 fillRule: 'evenodd',
7810
7811 // className: '',
7812
7813 // Option inherited from "Interactive layer" abstract class
7814 interactive: true,
7815
7816 // @option bubblingMouseEvents: Boolean = true
7817 // When `true`, a mouse event on this path will trigger the same event on the map
7818 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7819 bubblingMouseEvents: true
7820 },
7821
7822 beforeAdd: function (map) {
7823 // Renderer is set here because we need to call renderer.getEvents
7824 // before this.getEvents.
7825 this._renderer = map.getRenderer(this);
7826 },
7827
7828 onAdd: function () {
7829 this._renderer._initPath(this);
7830 this._reset();
7831 this._renderer._addPath(this);
7832 },
7833
7834 onRemove: function () {
7835 this._renderer._removePath(this);
7836 },
7837
7838 // @method redraw(): this
7839 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7840 redraw: function () {
7841 if (this._map) {
7842 this._renderer._updatePath(this);
7843 }
7844 return this;
7845 },
7846
7847 // @method setStyle(style: Path options): this
7848 // Changes the appearance of a Path based on the options in the `Path options` object.
7849 setStyle: function (style) {
7850 setOptions(this, style);
7851 if (this._renderer) {
7852 this._renderer._updateStyle(this);
7853 if (this.options.stroke && style.hasOwnProperty('weight')) {
7854 this._updateBounds();
7855 }
7856 }
7857 return this;
7858 },
7859
7860 // @method bringToFront(): this
7861 // Brings the layer to the top of all path layers.
7862 bringToFront: function () {
7863 if (this._renderer) {
7864 this._renderer._bringToFront(this);
7865 }
7866 return this;
7867 },
7868
7869 // @method bringToBack(): this
7870 // Brings the layer to the bottom of all path layers.
7871 bringToBack: function () {
7872 if (this._renderer) {
7873 this._renderer._bringToBack(this);
7874 }
7875 return this;
7876 },
7877
7878 getElement: function () {
7879 return this._path;
7880 },
7881
7882 _reset: function () {
7883 // defined in child classes
7884 this._project();
7885 this._update();
7886 },
7887
7888 _clickTolerance: function () {
7889 // used when doing hit detection for Canvas layers
7890 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7891 }
7892});
7893
7894/*
7895 * @class CircleMarker
7896 * @aka L.CircleMarker
7897 * @inherits Path
7898 *
7899 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7900 */
7901
7902var CircleMarker = Path.extend({
7903
7904 // @section
7905 // @aka CircleMarker options
7906 options: {
7907 fill: true,
7908
7909 // @option radius: Number = 10
7910 // Radius of the circle marker, in pixels
7911 radius: 10
7912 },
7913
7914 initialize: function (latlng, options) {
7915 setOptions(this, options);
7916 this._latlng = toLatLng(latlng);
7917 this._radius = this.options.radius;
7918 },
7919
7920 // @method setLatLng(latLng: LatLng): this
7921 // Sets the position of a circle marker to a new location.
7922 setLatLng: function (latlng) {
7923 this._latlng = toLatLng(latlng);
7924 this.redraw();
7925 return this.fire('move', {latlng: this._latlng});
7926 },
7927
7928 // @method getLatLng(): LatLng
7929 // Returns the current geographical position of the circle marker
7930 getLatLng: function () {
7931 return this._latlng;
7932 },
7933
7934 // @method setRadius(radius: Number): this
7935 // Sets the radius of a circle marker. Units are in pixels.
7936 setRadius: function (radius) {
7937 this.options.radius = this._radius = radius;
7938 return this.redraw();
7939 },
7940
7941 // @method getRadius(): Number
7942 // Returns the current radius of the circle
7943 getRadius: function () {
7944 return this._radius;
7945 },
7946
7947 setStyle : function (options) {
7948 var radius = options && options.radius || this._radius;
7949 Path.prototype.setStyle.call(this, options);
7950 this.setRadius(radius);
7951 return this;
7952 },
7953
7954 _project: function () {
7955 this._point = this._map.latLngToLayerPoint(this._latlng);
7956 this._updateBounds();
7957 },
7958
7959 _updateBounds: function () {
7960 var r = this._radius,
7961 r2 = this._radiusY || r,
7962 w = this._clickTolerance(),
7963 p = [r + w, r2 + w];
7964 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7965 },
7966
7967 _update: function () {
7968 if (this._map) {
7969 this._updatePath();
7970 }
7971 },
7972
7973 _updatePath: function () {
7974 this._renderer._updateCircle(this);
7975 },
7976
7977 _empty: function () {
7978 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7979 },
7980
7981 // Needed by the `Canvas` renderer for interactivity
7982 _containsPoint: function (p) {
7983 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7984 }
7985});
7986
7987
7988// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7989// Instantiates a circle marker object given a geographical point, and an optional options object.
7990function circleMarker(latlng, options) {
7991 return new CircleMarker(latlng, options);
7992}
7993
7994/*
7995 * @class Circle
7996 * @aka L.Circle
7997 * @inherits CircleMarker
7998 *
7999 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8000 *
8001 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8002 *
8003 * @example
8004 *
8005 * ```js
8006 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8007 * ```
8008 */
8009
8010var Circle = CircleMarker.extend({
8011
8012 initialize: function (latlng, options, legacyOptions) {
8013 if (typeof options === 'number') {
8014 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8015 options = extend({}, legacyOptions, {radius: options});
8016 }
8017 setOptions(this, options);
8018 this._latlng = toLatLng(latlng);
8019
8020 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8021
8022 // @section
8023 // @aka Circle options
8024 // @option radius: Number; Radius of the circle, in meters.
8025 this._mRadius = this.options.radius;
8026 },
8027
8028 // @method setRadius(radius: Number): this
8029 // Sets the radius of a circle. Units are in meters.
8030 setRadius: function (radius) {
8031 this._mRadius = radius;
8032 return this.redraw();
8033 },
8034
8035 // @method getRadius(): Number
8036 // Returns the current radius of a circle. Units are in meters.
8037 getRadius: function () {
8038 return this._mRadius;
8039 },
8040
8041 // @method getBounds(): LatLngBounds
8042 // Returns the `LatLngBounds` of the path.
8043 getBounds: function () {
8044 var half = [this._radius, this._radiusY || this._radius];
8045
8046 return new LatLngBounds(
8047 this._map.layerPointToLatLng(this._point.subtract(half)),
8048 this._map.layerPointToLatLng(this._point.add(half)));
8049 },
8050
8051 setStyle: Path.prototype.setStyle,
8052
8053 _project: function () {
8054
8055 var lng = this._latlng.lng,
8056 lat = this._latlng.lat,
8057 map = this._map,
8058 crs = map.options.crs;
8059
8060 if (crs.distance === Earth.distance) {
8061 var d = Math.PI / 180,
8062 latR = (this._mRadius / Earth.R) / d,
8063 top = map.project([lat + latR, lng]),
8064 bottom = map.project([lat - latR, lng]),
8065 p = top.add(bottom).divideBy(2),
8066 lat2 = map.unproject(p).lat,
8067 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8068 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8069
8070 if (isNaN(lngR) || lngR === 0) {
8071 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8072 }
8073
8074 this._point = p.subtract(map.getPixelOrigin());
8075 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8076 this._radiusY = p.y - top.y;
8077
8078 } else {
8079 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8080
8081 this._point = map.latLngToLayerPoint(this._latlng);
8082 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8083 }
8084
8085 this._updateBounds();
8086 }
8087});
8088
8089// @factory L.circle(latlng: LatLng, options?: Circle options)
8090// Instantiates a circle object given a geographical point, and an options object
8091// which contains the circle radius.
8092// @alternative
8093// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8094// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8095// Do not use in new applications or plugins.
8096function circle(latlng, options, legacyOptions) {
8097 return new Circle(latlng, options, legacyOptions);
8098}
8099
8100/*
8101 * @class Polyline
8102 * @aka L.Polyline
8103 * @inherits Path
8104 *
8105 * A class for drawing polyline overlays on a map. Extends `Path`.
8106 *
8107 * @example
8108 *
8109 * ```js
8110 * // create a red polyline from an array of LatLng points
8111 * var latlngs = [
8112 * [45.51, -122.68],
8113 * [37.77, -122.43],
8114 * [34.04, -118.2]
8115 * ];
8116 *
8117 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8118 *
8119 * // zoom the map to the polyline
8120 * map.fitBounds(polyline.getBounds());
8121 * ```
8122 *
8123 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8124 *
8125 * ```js
8126 * // create a red polyline from an array of arrays of LatLng points
8127 * var latlngs = [
8128 * [[45.51, -122.68],
8129 * [37.77, -122.43],
8130 * [34.04, -118.2]],
8131 * [[40.78, -73.91],
8132 * [41.83, -87.62],
8133 * [32.76, -96.72]]
8134 * ];
8135 * ```
8136 */
8137
8138
8139var Polyline = Path.extend({
8140
8141 // @section
8142 // @aka Polyline options
8143 options: {
8144 // @option smoothFactor: Number = 1.0
8145 // How much to simplify the polyline on each zoom level. More means
8146 // better performance and smoother look, and less means more accurate representation.
8147 smoothFactor: 1.0,
8148
8149 // @option noClip: Boolean = false
8150 // Disable polyline clipping.
8151 noClip: false
8152 },
8153
8154 initialize: function (latlngs, options) {
8155 setOptions(this, options);
8156 this._setLatLngs(latlngs);
8157 },
8158
8159 // @method getLatLngs(): LatLng[]
8160 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8161 getLatLngs: function () {
8162 return this._latlngs;
8163 },
8164
8165 // @method setLatLngs(latlngs: LatLng[]): this
8166 // Replaces all the points in the polyline with the given array of geographical points.
8167 setLatLngs: function (latlngs) {
8168 this._setLatLngs(latlngs);
8169 return this.redraw();
8170 },
8171
8172 // @method isEmpty(): Boolean
8173 // Returns `true` if the Polyline has no LatLngs.
8174 isEmpty: function () {
8175 return !this._latlngs.length;
8176 },
8177
8178 // @method closestLayerPoint(p: Point): Point
8179 // Returns the point closest to `p` on the Polyline.
8180 closestLayerPoint: function (p) {
8181 var minDistance = Infinity,
8182 minPoint = null,
8183 closest = _sqClosestPointOnSegment,
8184 p1, p2;
8185
8186 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8187 var points = this._parts[j];
8188
8189 for (var i = 1, len = points.length; i < len; i++) {
8190 p1 = points[i - 1];
8191 p2 = points[i];
8192
8193 var sqDist = closest(p, p1, p2, true);
8194
8195 if (sqDist < minDistance) {
8196 minDistance = sqDist;
8197 minPoint = closest(p, p1, p2);
8198 }
8199 }
8200 }
8201 if (minPoint) {
8202 minPoint.distance = Math.sqrt(minDistance);
8203 }
8204 return minPoint;
8205 },
8206
8207 // @method getCenter(): LatLng
8208 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8209 getCenter: function () {
8210 // throws error when not yet added to map as this center calculation requires projected coordinates
8211 if (!this._map) {
8212 throw new Error('Must add layer to map before using getCenter()');
8213 }
8214
8215 var i, halfDist, segDist, dist, p1, p2, ratio,
8216 points = this._rings[0],
8217 len = points.length;
8218
8219 if (!len) { return null; }
8220
8221 // polyline centroid algorithm; only uses the first ring if there are multiple
8222
8223 for (i = 0, halfDist = 0; i < len - 1; i++) {
8224 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8225 }
8226
8227 // The line is so small in the current view that all points are on the same pixel.
8228 if (halfDist === 0) {
8229 return this._map.layerPointToLatLng(points[0]);
8230 }
8231
8232 for (i = 0, dist = 0; i < len - 1; i++) {
8233 p1 = points[i];
8234 p2 = points[i + 1];
8235 segDist = p1.distanceTo(p2);
8236 dist += segDist;
8237
8238 if (dist > halfDist) {
8239 ratio = (dist - halfDist) / segDist;
8240 return this._map.layerPointToLatLng([
8241 p2.x - ratio * (p2.x - p1.x),
8242 p2.y - ratio * (p2.y - p1.y)
8243 ]);
8244 }
8245 }
8246 },
8247
8248 // @method getBounds(): LatLngBounds
8249 // Returns the `LatLngBounds` of the path.
8250 getBounds: function () {
8251 return this._bounds;
8252 },
8253
8254 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8255 // Adds a given point to the polyline. By default, adds to the first ring of
8256 // the polyline in case of a multi-polyline, but can be overridden by passing
8257 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8258 addLatLng: function (latlng, latlngs) {
8259 latlngs = latlngs || this._defaultShape();
8260 latlng = toLatLng(latlng);
8261 latlngs.push(latlng);
8262 this._bounds.extend(latlng);
8263 return this.redraw();
8264 },
8265
8266 _setLatLngs: function (latlngs) {
8267 this._bounds = new LatLngBounds();
8268 this._latlngs = this._convertLatLngs(latlngs);
8269 },
8270
8271 _defaultShape: function () {
8272 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8273 },
8274
8275 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8276 _convertLatLngs: function (latlngs) {
8277 var result = [],
8278 flat = isFlat(latlngs);
8279
8280 for (var i = 0, len = latlngs.length; i < len; i++) {
8281 if (flat) {
8282 result[i] = toLatLng(latlngs[i]);
8283 this._bounds.extend(result[i]);
8284 } else {
8285 result[i] = this._convertLatLngs(latlngs[i]);
8286 }
8287 }
8288
8289 return result;
8290 },
8291
8292 _project: function () {
8293 var pxBounds = new Bounds();
8294 this._rings = [];
8295 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8296
8297 if (this._bounds.isValid() && pxBounds.isValid()) {
8298 this._rawPxBounds = pxBounds;
8299 this._updateBounds();
8300 }
8301 },
8302
8303 _updateBounds: function () {
8304 var w = this._clickTolerance(),
8305 p = new Point(w, w);
8306 this._pxBounds = new Bounds([
8307 this._rawPxBounds.min.subtract(p),
8308 this._rawPxBounds.max.add(p)
8309 ]);
8310 },
8311
8312 // recursively turns latlngs into a set of rings with projected coordinates
8313 _projectLatlngs: function (latlngs, result, projectedBounds) {
8314 var flat = latlngs[0] instanceof LatLng,
8315 len = latlngs.length,
8316 i, ring;
8317
8318 if (flat) {
8319 ring = [];
8320 for (i = 0; i < len; i++) {
8321 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8322 projectedBounds.extend(ring[i]);
8323 }
8324 result.push(ring);
8325 } else {
8326 for (i = 0; i < len; i++) {
8327 this._projectLatlngs(latlngs[i], result, projectedBounds);
8328 }
8329 }
8330 },
8331
8332 // clip polyline by renderer bounds so that we have less to render for performance
8333 _clipPoints: function () {
8334 var bounds = this._renderer._bounds;
8335
8336 this._parts = [];
8337 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8338 return;
8339 }
8340
8341 if (this.options.noClip) {
8342 this._parts = this._rings;
8343 return;
8344 }
8345
8346 var parts = this._parts,
8347 i, j, k, len, len2, segment, points;
8348
8349 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8350 points = this._rings[i];
8351
8352 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8353 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8354
8355 if (!segment) { continue; }
8356
8357 parts[k] = parts[k] || [];
8358 parts[k].push(segment[0]);
8359
8360 // if segment goes out of screen, or it's the last one, it's the end of the line part
8361 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8362 parts[k].push(segment[1]);
8363 k++;
8364 }
8365 }
8366 }
8367 },
8368
8369 // simplify each clipped part of the polyline for performance
8370 _simplifyPoints: function () {
8371 var parts = this._parts,
8372 tolerance = this.options.smoothFactor;
8373
8374 for (var i = 0, len = parts.length; i < len; i++) {
8375 parts[i] = simplify(parts[i], tolerance);
8376 }
8377 },
8378
8379 _update: function () {
8380 if (!this._map) { return; }
8381
8382 this._clipPoints();
8383 this._simplifyPoints();
8384 this._updatePath();
8385 },
8386
8387 _updatePath: function () {
8388 this._renderer._updatePoly(this);
8389 },
8390
8391 // Needed by the `Canvas` renderer for interactivity
8392 _containsPoint: function (p, closed) {
8393 var i, j, k, len, len2, part,
8394 w = this._clickTolerance();
8395
8396 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8397
8398 // hit detection for polylines
8399 for (i = 0, len = this._parts.length; i < len; i++) {
8400 part = this._parts[i];
8401
8402 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8403 if (!closed && (j === 0)) { continue; }
8404
8405 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8406 return true;
8407 }
8408 }
8409 }
8410 return false;
8411 }
8412});
8413
8414// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8415// Instantiates a polyline object given an array of geographical points and
8416// optionally an options object. You can create a `Polyline` object with
8417// multiple separate lines (`MultiPolyline`) by passing an array of arrays
8418// of geographic points.
8419function polyline(latlngs, options) {
8420 return new Polyline(latlngs, options);
8421}
8422
8423// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8424Polyline._flat = _flat;
8425
8426/*
8427 * @class Polygon
8428 * @aka L.Polygon
8429 * @inherits Polyline
8430 *
8431 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8432 *
8433 * 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.
8434 *
8435 *
8436 * @example
8437 *
8438 * ```js
8439 * // create a red polygon from an array of LatLng points
8440 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8441 *
8442 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8443 *
8444 * // zoom the map to the polygon
8445 * map.fitBounds(polygon.getBounds());
8446 * ```
8447 *
8448 * 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:
8449 *
8450 * ```js
8451 * var latlngs = [
8452 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8453 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8454 * ];
8455 * ```
8456 *
8457 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8458 *
8459 * ```js
8460 * var latlngs = [
8461 * [ // first polygon
8462 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8463 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8464 * ],
8465 * [ // second polygon
8466 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8467 * ]
8468 * ];
8469 * ```
8470 */
8471
8472var Polygon = Polyline.extend({
8473
8474 options: {
8475 fill: true
8476 },
8477
8478 isEmpty: function () {
8479 return !this._latlngs.length || !this._latlngs[0].length;
8480 },
8481
8482 getCenter: function () {
8483 // throws error when not yet added to map as this center calculation requires projected coordinates
8484 if (!this._map) {
8485 throw new Error('Must add layer to map before using getCenter()');
8486 }
8487
8488 var i, j, p1, p2, f, area, x, y, center,
8489 points = this._rings[0],
8490 len = points.length;
8491
8492 if (!len) { return null; }
8493
8494 // polygon centroid algorithm; only uses the first ring if there are multiple
8495
8496 area = x = y = 0;
8497
8498 for (i = 0, j = len - 1; i < len; j = i++) {
8499 p1 = points[i];
8500 p2 = points[j];
8501
8502 f = p1.y * p2.x - p2.y * p1.x;
8503 x += (p1.x + p2.x) * f;
8504 y += (p1.y + p2.y) * f;
8505 area += f * 3;
8506 }
8507
8508 if (area === 0) {
8509 // Polygon is so small that all points are on same pixel.
8510 center = points[0];
8511 } else {
8512 center = [x / area, y / area];
8513 }
8514 return this._map.layerPointToLatLng(center);
8515 },
8516
8517 _convertLatLngs: function (latlngs) {
8518 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8519 len = result.length;
8520
8521 // remove last point if it equals first one
8522 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8523 result.pop();
8524 }
8525 return result;
8526 },
8527
8528 _setLatLngs: function (latlngs) {
8529 Polyline.prototype._setLatLngs.call(this, latlngs);
8530 if (isFlat(this._latlngs)) {
8531 this._latlngs = [this._latlngs];
8532 }
8533 },
8534
8535 _defaultShape: function () {
8536 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8537 },
8538
8539 _clipPoints: function () {
8540 // polygons need a different clipping algorithm so we redefine that
8541
8542 var bounds = this._renderer._bounds,
8543 w = this.options.weight,
8544 p = new Point(w, w);
8545
8546 // increase clip padding by stroke width to avoid stroke on clip edges
8547 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8548
8549 this._parts = [];
8550 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8551 return;
8552 }
8553
8554 if (this.options.noClip) {
8555 this._parts = this._rings;
8556 return;
8557 }
8558
8559 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8560 clipped = clipPolygon(this._rings[i], bounds, true);
8561 if (clipped.length) {
8562 this._parts.push(clipped);
8563 }
8564 }
8565 },
8566
8567 _updatePath: function () {
8568 this._renderer._updatePoly(this, true);
8569 },
8570
8571 // Needed by the `Canvas` renderer for interactivity
8572 _containsPoint: function (p) {
8573 var inside = false,
8574 part, p1, p2, i, j, k, len, len2;
8575
8576 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8577
8578 // ray casting algorithm for detecting if point is in polygon
8579 for (i = 0, len = this._parts.length; i < len; i++) {
8580 part = this._parts[i];
8581
8582 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8583 p1 = part[j];
8584 p2 = part[k];
8585
8586 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)) {
8587 inside = !inside;
8588 }
8589 }
8590 }
8591
8592 // also check if it's on polygon stroke
8593 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8594 }
8595
8596});
8597
8598
8599// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8600function polygon(latlngs, options) {
8601 return new Polygon(latlngs, options);
8602}
8603
8604/*
8605 * @class GeoJSON
8606 * @aka L.GeoJSON
8607 * @inherits FeatureGroup
8608 *
8609 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8610 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8611 *
8612 * @example
8613 *
8614 * ```js
8615 * L.geoJSON(data, {
8616 * style: function (feature) {
8617 * return {color: feature.properties.color};
8618 * }
8619 * }).bindPopup(function (layer) {
8620 * return layer.feature.properties.description;
8621 * }).addTo(map);
8622 * ```
8623 */
8624
8625var GeoJSON = FeatureGroup.extend({
8626
8627 /* @section
8628 * @aka GeoJSON options
8629 *
8630 * @option pointToLayer: Function = *
8631 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8632 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8633 * The default is to spawn a default `Marker`:
8634 * ```js
8635 * function(geoJsonPoint, latlng) {
8636 * return L.marker(latlng);
8637 * }
8638 * ```
8639 *
8640 * @option style: Function = *
8641 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8642 * called internally when data is added.
8643 * The default value is to not override any defaults:
8644 * ```js
8645 * function (geoJsonFeature) {
8646 * return {}
8647 * }
8648 * ```
8649 *
8650 * @option onEachFeature: Function = *
8651 * A `Function` that will be called once for each created `Feature`, after it has
8652 * been created and styled. Useful for attaching events and popups to features.
8653 * The default is to do nothing with the newly created layers:
8654 * ```js
8655 * function (feature, layer) {}
8656 * ```
8657 *
8658 * @option filter: Function = *
8659 * A `Function` that will be used to decide whether to include a feature or not.
8660 * The default is to include all features:
8661 * ```js
8662 * function (geoJsonFeature) {
8663 * return true;
8664 * }
8665 * ```
8666 * Note: dynamically changing the `filter` option will have effect only on newly
8667 * added data. It will _not_ re-evaluate already included features.
8668 *
8669 * @option coordsToLatLng: Function = *
8670 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8671 * The default is the `coordsToLatLng` static method.
8672 */
8673
8674 initialize: function (geojson, options) {
8675 setOptions(this, options);
8676
8677 this._layers = {};
8678
8679 if (geojson) {
8680 this.addData(geojson);
8681 }
8682 },
8683
8684 // @method addData( <GeoJSON> data ): this
8685 // Adds a GeoJSON object to the layer.
8686 addData: function (geojson) {
8687 var features = isArray(geojson) ? geojson : geojson.features,
8688 i, len, feature;
8689
8690 if (features) {
8691 for (i = 0, len = features.length; i < len; i++) {
8692 // only add this if geometry or geometries are set and not null
8693 feature = features[i];
8694 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8695 this.addData(feature);
8696 }
8697 }
8698 return this;
8699 }
8700
8701 var options = this.options;
8702
8703 if (options.filter && !options.filter(geojson)) { return this; }
8704
8705 var layer = geometryToLayer(geojson, options);
8706 if (!layer) {
8707 return this;
8708 }
8709 layer.feature = asFeature(geojson);
8710
8711 layer.defaultOptions = layer.options;
8712 this.resetStyle(layer);
8713
8714 if (options.onEachFeature) {
8715 options.onEachFeature(geojson, layer);
8716 }
8717
8718 return this.addLayer(layer);
8719 },
8720
8721 // @method resetStyle( <Path> layer ): this
8722 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8723 resetStyle: function (layer) {
8724 // reset any custom styles
8725 layer.options = extend({}, layer.defaultOptions);
8726 this._setLayerStyle(layer, this.options.style);
8727 return this;
8728 },
8729
8730 // @method setStyle( <Function> style ): this
8731 // Changes styles of GeoJSON vector layers with the given style function.
8732 setStyle: function (style) {
8733 return this.eachLayer(function (layer) {
8734 this._setLayerStyle(layer, style);
8735 }, this);
8736 },
8737
8738 _setLayerStyle: function (layer, style) {
8739 if (layer.setStyle) {
8740 if (typeof style === 'function') {
8741 style = style(layer.feature);
8742 }
8743 layer.setStyle(style);
8744 }
8745 }
8746});
8747
8748// @section
8749// There are several static functions which can be called without instantiating L.GeoJSON:
8750
8751// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8752// Creates a `Layer` from a given GeoJSON feature. Can use a custom
8753// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8754// functions if provided as options.
8755function geometryToLayer(geojson, options) {
8756
8757 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8758 coords = geometry ? geometry.coordinates : null,
8759 layers = [],
8760 pointToLayer = options && options.pointToLayer,
8761 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8762 latlng, latlngs, i, len;
8763
8764 if (!coords && !geometry) {
8765 return null;
8766 }
8767
8768 switch (geometry.type) {
8769 case 'Point':
8770 latlng = _coordsToLatLng(coords);
8771 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8772
8773 case 'MultiPoint':
8774 for (i = 0, len = coords.length; i < len; i++) {
8775 latlng = _coordsToLatLng(coords[i]);
8776 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8777 }
8778 return new FeatureGroup(layers);
8779
8780 case 'LineString':
8781 case 'MultiLineString':
8782 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8783 return new Polyline(latlngs, options);
8784
8785 case 'Polygon':
8786 case 'MultiPolygon':
8787 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8788 return new Polygon(latlngs, options);
8789
8790 case 'GeometryCollection':
8791 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8792 var layer = geometryToLayer({
8793 geometry: geometry.geometries[i],
8794 type: 'Feature',
8795 properties: geojson.properties
8796 }, options);
8797
8798 if (layer) {
8799 layers.push(layer);
8800 }
8801 }
8802 return new FeatureGroup(layers);
8803
8804 default:
8805 throw new Error('Invalid GeoJSON object.');
8806 }
8807}
8808
8809// @function coordsToLatLng(coords: Array): LatLng
8810// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8811// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8812function coordsToLatLng(coords) {
8813 return new LatLng(coords[1], coords[0], coords[2]);
8814}
8815
8816// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8817// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8818// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8819// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8820function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8821 var latlngs = [];
8822
8823 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8824 latlng = levelsDeep ?
8825 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8826 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8827
8828 latlngs.push(latlng);
8829 }
8830
8831 return latlngs;
8832}
8833
8834// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8835// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8836function latLngToCoords(latlng, precision) {
8837 precision = typeof precision === 'number' ? precision : 6;
8838 return latlng.alt !== undefined ?
8839 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8840 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8841}
8842
8843// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8844// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8845// `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.
8846function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8847 var coords = [];
8848
8849 for (var i = 0, len = latlngs.length; i < len; i++) {
8850 coords.push(levelsDeep ?
8851 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8852 latLngToCoords(latlngs[i], precision));
8853 }
8854
8855 if (!levelsDeep && closed) {
8856 coords.push(coords[0]);
8857 }
8858
8859 return coords;
8860}
8861
8862function getFeature(layer, newGeometry) {
8863 return layer.feature ?
8864 extend({}, layer.feature, {geometry: newGeometry}) :
8865 asFeature(newGeometry);
8866}
8867
8868// @function asFeature(geojson: Object): Object
8869// Normalize GeoJSON geometries/features into GeoJSON features.
8870function asFeature(geojson) {
8871 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8872 return geojson;
8873 }
8874
8875 return {
8876 type: 'Feature',
8877 properties: {},
8878 geometry: geojson
8879 };
8880}
8881
8882var PointToGeoJSON = {
8883 toGeoJSON: function (precision) {
8884 return getFeature(this, {
8885 type: 'Point',
8886 coordinates: latLngToCoords(this.getLatLng(), precision)
8887 });
8888 }
8889};
8890
8891// @namespace Marker
8892// @method toGeoJSON(precision?: Number): Object
8893// `precision` is the number of decimal places for coordinates.
8894// The default value is 6 places.
8895// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8896Marker.include(PointToGeoJSON);
8897
8898// @namespace CircleMarker
8899// @method toGeoJSON(precision?: Number): Object
8900// `precision` is the number of decimal places for coordinates.
8901// The default value is 6 places.
8902// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8903Circle.include(PointToGeoJSON);
8904CircleMarker.include(PointToGeoJSON);
8905
8906
8907// @namespace Polyline
8908// @method toGeoJSON(precision?: Number): Object
8909// `precision` is the number of decimal places for coordinates.
8910// The default value is 6 places.
8911// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8912Polyline.include({
8913 toGeoJSON: function (precision) {
8914 var multi = !isFlat(this._latlngs);
8915
8916 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8917
8918 return getFeature(this, {
8919 type: (multi ? 'Multi' : '') + 'LineString',
8920 coordinates: coords
8921 });
8922 }
8923});
8924
8925// @namespace Polygon
8926// @method toGeoJSON(precision?: Number): Object
8927// `precision` is the number of decimal places for coordinates.
8928// The default value is 6 places.
8929// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8930Polygon.include({
8931 toGeoJSON: function (precision) {
8932 var holes = !isFlat(this._latlngs),
8933 multi = holes && !isFlat(this._latlngs[0]);
8934
8935 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8936
8937 if (!holes) {
8938 coords = [coords];
8939 }
8940
8941 return getFeature(this, {
8942 type: (multi ? 'Multi' : '') + 'Polygon',
8943 coordinates: coords
8944 });
8945 }
8946});
8947
8948
8949// @namespace LayerGroup
8950LayerGroup.include({
8951 toMultiPoint: function (precision) {
8952 var coords = [];
8953
8954 this.eachLayer(function (layer) {
8955 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8956 });
8957
8958 return getFeature(this, {
8959 type: 'MultiPoint',
8960 coordinates: coords
8961 });
8962 },
8963
8964 // @method toGeoJSON(precision?: Number): Object
8965 // `precision` is the number of decimal places for coordinates.
8966 // The default value is 6 places.
8967 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8968 toGeoJSON: function (precision) {
8969
8970 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8971
8972 if (type === 'MultiPoint') {
8973 return this.toMultiPoint(precision);
8974 }
8975
8976 var isGeometryCollection = type === 'GeometryCollection',
8977 jsons = [];
8978
8979 this.eachLayer(function (layer) {
8980 if (layer.toGeoJSON) {
8981 var json = layer.toGeoJSON(precision);
8982 if (isGeometryCollection) {
8983 jsons.push(json.geometry);
8984 } else {
8985 var feature = asFeature(json);
8986 // Squash nested feature collections
8987 if (feature.type === 'FeatureCollection') {
8988 jsons.push.apply(jsons, feature.features);
8989 } else {
8990 jsons.push(feature);
8991 }
8992 }
8993 }
8994 });
8995
8996 if (isGeometryCollection) {
8997 return getFeature(this, {
8998 geometries: jsons,
8999 type: 'GeometryCollection'
9000 });
9001 }
9002
9003 return {
9004 type: 'FeatureCollection',
9005 features: jsons
9006 };
9007 }
9008});
9009
9010// @namespace GeoJSON
9011// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9012// Creates a GeoJSON layer. Optionally accepts an object in
9013// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9014// (you can alternatively add it later with `addData` method) and an `options` object.
9015function geoJSON(geojson, options) {
9016 return new GeoJSON(geojson, options);
9017}
9018
9019// Backward compatibility.
9020var geoJson = geoJSON;
9021
9022/*
9023 * @class ImageOverlay
9024 * @aka L.ImageOverlay
9025 * @inherits Interactive layer
9026 *
9027 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9028 *
9029 * @example
9030 *
9031 * ```js
9032 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9033 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9034 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9035 * ```
9036 */
9037
9038var ImageOverlay = Layer.extend({
9039
9040 // @section
9041 // @aka ImageOverlay options
9042 options: {
9043 // @option opacity: Number = 1.0
9044 // The opacity of the image overlay.
9045 opacity: 1,
9046
9047 // @option alt: String = ''
9048 // Text for the `alt` attribute of the image (useful for accessibility).
9049 alt: '',
9050
9051 // @option interactive: Boolean = false
9052 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9053 interactive: false,
9054
9055 // @option crossOrigin: Boolean|String = false
9056 // Whether the crossOrigin attribute will be added to the image.
9057 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
9058 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9059 crossOrigin: false,
9060
9061 // @option errorOverlayUrl: String = ''
9062 // URL to the overlay image to show in place of the overlay that failed to load.
9063 errorOverlayUrl: '',
9064
9065 // @option zIndex: Number = 1
9066 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9067 zIndex: 1,
9068
9069 // @option className: String = ''
9070 // A custom class name to assign to the image. Empty by default.
9071 className: ''
9072 },
9073
9074 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9075 this._url = url;
9076 this._bounds = toLatLngBounds(bounds);
9077
9078 setOptions(this, options);
9079 },
9080
9081 onAdd: function () {
9082 if (!this._image) {
9083 this._initImage();
9084
9085 if (this.options.opacity < 1) {
9086 this._updateOpacity();
9087 }
9088 }
9089
9090 if (this.options.interactive) {
9091 addClass(this._image, 'leaflet-interactive');
9092 this.addInteractiveTarget(this._image);
9093 }
9094
9095 this.getPane().appendChild(this._image);
9096 this._reset();
9097 },
9098
9099 onRemove: function () {
9100 remove(this._image);
9101 if (this.options.interactive) {
9102 this.removeInteractiveTarget(this._image);
9103 }
9104 },
9105
9106 // @method setOpacity(opacity: Number): this
9107 // Sets the opacity of the overlay.
9108 setOpacity: function (opacity) {
9109 this.options.opacity = opacity;
9110
9111 if (this._image) {
9112 this._updateOpacity();
9113 }
9114 return this;
9115 },
9116
9117 setStyle: function (styleOpts) {
9118 if (styleOpts.opacity) {
9119 this.setOpacity(styleOpts.opacity);
9120 }
9121 return this;
9122 },
9123
9124 // @method bringToFront(): this
9125 // Brings the layer to the top of all overlays.
9126 bringToFront: function () {
9127 if (this._map) {
9128 toFront(this._image);
9129 }
9130 return this;
9131 },
9132
9133 // @method bringToBack(): this
9134 // Brings the layer to the bottom of all overlays.
9135 bringToBack: function () {
9136 if (this._map) {
9137 toBack(this._image);
9138 }
9139 return this;
9140 },
9141
9142 // @method setUrl(url: String): this
9143 // Changes the URL of the image.
9144 setUrl: function (url) {
9145 this._url = url;
9146
9147 if (this._image) {
9148 this._image.src = url;
9149 }
9150 return this;
9151 },
9152
9153 // @method setBounds(bounds: LatLngBounds): this
9154 // Update the bounds that this ImageOverlay covers
9155 setBounds: function (bounds) {
9156 this._bounds = toLatLngBounds(bounds);
9157
9158 if (this._map) {
9159 this._reset();
9160 }
9161 return this;
9162 },
9163
9164 getEvents: function () {
9165 var events = {
9166 zoom: this._reset,
9167 viewreset: this._reset
9168 };
9169
9170 if (this._zoomAnimated) {
9171 events.zoomanim = this._animateZoom;
9172 }
9173
9174 return events;
9175 },
9176
9177 // @method setZIndex(value: Number): this
9178 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9179 setZIndex: function (value) {
9180 this.options.zIndex = value;
9181 this._updateZIndex();
9182 return this;
9183 },
9184
9185 // @method getBounds(): LatLngBounds
9186 // Get the bounds that this ImageOverlay covers
9187 getBounds: function () {
9188 return this._bounds;
9189 },
9190
9191 // @method getElement(): HTMLElement
9192 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9193 // used by this overlay.
9194 getElement: function () {
9195 return this._image;
9196 },
9197
9198 _initImage: function () {
9199 var wasElementSupplied = this._url.tagName === 'IMG';
9200 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9201
9202 addClass(img, 'leaflet-image-layer');
9203 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9204 if (this.options.className) { addClass(img, this.options.className); }
9205
9206 img.onselectstart = falseFn;
9207 img.onmousemove = falseFn;
9208
9209 // @event load: Event
9210 // Fired when the ImageOverlay layer has loaded its image
9211 img.onload = bind(this.fire, this, 'load');
9212 img.onerror = bind(this._overlayOnError, this, 'error');
9213
9214 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9215 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9216 }
9217
9218 if (this.options.zIndex) {
9219 this._updateZIndex();
9220 }
9221
9222 if (wasElementSupplied) {
9223 this._url = img.src;
9224 return;
9225 }
9226
9227 img.src = this._url;
9228 img.alt = this.options.alt;
9229 },
9230
9231 _animateZoom: function (e) {
9232 var scale = this._map.getZoomScale(e.zoom),
9233 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9234
9235 setTransform(this._image, offset, scale);
9236 },
9237
9238 _reset: function () {
9239 var image = this._image,
9240 bounds = new Bounds(
9241 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9242 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9243 size = bounds.getSize();
9244
9245 setPosition(image, bounds.min);
9246
9247 image.style.width = size.x + 'px';
9248 image.style.height = size.y + 'px';
9249 },
9250
9251 _updateOpacity: function () {
9252 setOpacity(this._image, this.options.opacity);
9253 },
9254
9255 _updateZIndex: function () {
9256 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9257 this._image.style.zIndex = this.options.zIndex;
9258 }
9259 },
9260
9261 _overlayOnError: function () {
9262 // @event error: Event
9263 // Fired when the ImageOverlay layer fails to load its image
9264 this.fire('error');
9265
9266 var errorUrl = this.options.errorOverlayUrl;
9267 if (errorUrl && this._url !== errorUrl) {
9268 this._url = errorUrl;
9269 this._image.src = errorUrl;
9270 }
9271 }
9272});
9273
9274// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9275// Instantiates an image overlay object given the URL of the image and the
9276// geographical bounds it is tied to.
9277var imageOverlay = function (url, bounds, options) {
9278 return new ImageOverlay(url, bounds, options);
9279};
9280
9281/*
9282 * @class VideoOverlay
9283 * @aka L.VideoOverlay
9284 * @inherits ImageOverlay
9285 *
9286 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9287 *
9288 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9289 * HTML5 element.
9290 *
9291 * @example
9292 *
9293 * ```js
9294 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9295 * videoBounds = [[ 32, -130], [ 13, -100]];
9296 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9297 * ```
9298 */
9299
9300var VideoOverlay = ImageOverlay.extend({
9301
9302 // @section
9303 // @aka VideoOverlay options
9304 options: {
9305 // @option autoplay: Boolean = true
9306 // Whether the video starts playing automatically when loaded.
9307 autoplay: true,
9308
9309 // @option loop: Boolean = true
9310 // Whether the video will loop back to the beginning when played.
9311 loop: true,
9312
9313 // @option keepAspectRatio: Boolean = true
9314 // Whether the video will save aspect ratio after the projection.
9315 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9316 keepAspectRatio: true
9317 },
9318
9319 _initImage: function () {
9320 var wasElementSupplied = this._url.tagName === 'VIDEO';
9321 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9322
9323 addClass(vid, 'leaflet-image-layer');
9324 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9325
9326 vid.onselectstart = falseFn;
9327 vid.onmousemove = falseFn;
9328
9329 // @event load: Event
9330 // Fired when the video has finished loading the first frame
9331 vid.onloadeddata = bind(this.fire, this, 'load');
9332
9333 if (wasElementSupplied) {
9334 var sourceElements = vid.getElementsByTagName('source');
9335 var sources = [];
9336 for (var j = 0; j < sourceElements.length; j++) {
9337 sources.push(sourceElements[j].src);
9338 }
9339
9340 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9341 return;
9342 }
9343
9344 if (!isArray(this._url)) { this._url = [this._url]; }
9345
9346 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
9347 vid.autoplay = !!this.options.autoplay;
9348 vid.loop = !!this.options.loop;
9349 for (var i = 0; i < this._url.length; i++) {
9350 var source = create$1('source');
9351 source.src = this._url[i];
9352 vid.appendChild(source);
9353 }
9354 }
9355
9356 // @method getElement(): HTMLVideoElement
9357 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9358 // used by this overlay.
9359});
9360
9361
9362// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9363// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9364// geographical bounds it is tied to.
9365
9366function videoOverlay(video, bounds, options) {
9367 return new VideoOverlay(video, bounds, options);
9368}
9369
9370/*
9371 * @class SVGOverlay
9372 * @aka L.SVGOverlay
9373 * @inherits ImageOverlay
9374 *
9375 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9376 *
9377 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9378 *
9379 * @example
9380 *
9381 * ```js
9382 * var element = '<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/></svg>',
9383 * elementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9384 * L.svgOverlay(element, elementBounds).addTo(map);
9385 * ```
9386 */
9387
9388var SVGOverlay = ImageOverlay.extend({
9389 _initImage: function () {
9390 var el = this._image = this._url;
9391
9392 addClass(el, 'leaflet-image-layer');
9393 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9394
9395 el.onselectstart = falseFn;
9396 el.onmousemove = falseFn;
9397 }
9398
9399 // @method getElement(): SVGElement
9400 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9401 // used by this overlay.
9402});
9403
9404
9405// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9406// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9407// A viewBox attribute is required on the SVG element to zoom in and out properly.
9408
9409function svgOverlay(el, bounds, options) {
9410 return new SVGOverlay(el, bounds, options);
9411}
9412
9413/*
9414 * @class DivOverlay
9415 * @inherits Layer
9416 * @aka L.DivOverlay
9417 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9418 */
9419
9420// @namespace DivOverlay
9421var DivOverlay = Layer.extend({
9422
9423 // @section
9424 // @aka DivOverlay options
9425 options: {
9426 // @option offset: Point = Point(0, 7)
9427 // The offset of the popup position. Useful to control the anchor
9428 // of the popup when opening it on some overlays.
9429 offset: [0, 7],
9430
9431 // @option className: String = ''
9432 // A custom CSS class name to assign to the popup.
9433 className: '',
9434
9435 // @option pane: String = 'popupPane'
9436 // `Map pane` where the popup will be added.
9437 pane: 'popupPane'
9438 },
9439
9440 initialize: function (options, source) {
9441 setOptions(this, options);
9442
9443 this._source = source;
9444 },
9445
9446 onAdd: function (map) {
9447 this._zoomAnimated = map._zoomAnimated;
9448
9449 if (!this._container) {
9450 this._initLayout();
9451 }
9452
9453 if (map._fadeAnimated) {
9454 setOpacity(this._container, 0);
9455 }
9456
9457 clearTimeout(this._removeTimeout);
9458 this.getPane().appendChild(this._container);
9459 this.update();
9460
9461 if (map._fadeAnimated) {
9462 setOpacity(this._container, 1);
9463 }
9464
9465 this.bringToFront();
9466 },
9467
9468 onRemove: function (map) {
9469 if (map._fadeAnimated) {
9470 setOpacity(this._container, 0);
9471 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9472 } else {
9473 remove(this._container);
9474 }
9475 },
9476
9477 // @namespace Popup
9478 // @method getLatLng: LatLng
9479 // Returns the geographical point of popup.
9480 getLatLng: function () {
9481 return this._latlng;
9482 },
9483
9484 // @method setLatLng(latlng: LatLng): this
9485 // Sets the geographical point where the popup will open.
9486 setLatLng: function (latlng) {
9487 this._latlng = toLatLng(latlng);
9488 if (this._map) {
9489 this._updatePosition();
9490 this._adjustPan();
9491 }
9492 return this;
9493 },
9494
9495 // @method getContent: String|HTMLElement
9496 // Returns the content of the popup.
9497 getContent: function () {
9498 return this._content;
9499 },
9500
9501 // @method setContent(htmlContent: String|HTMLElement|Function): this
9502 // 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.
9503 setContent: function (content) {
9504 this._content = content;
9505 this.update();
9506 return this;
9507 },
9508
9509 // @method getElement: String|HTMLElement
9510 // Alias for [getContent()](#popup-getcontent)
9511 getElement: function () {
9512 return this._container;
9513 },
9514
9515 // @method update: null
9516 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9517 update: function () {
9518 if (!this._map) { return; }
9519
9520 this._container.style.visibility = 'hidden';
9521
9522 this._updateContent();
9523 this._updateLayout();
9524 this._updatePosition();
9525
9526 this._container.style.visibility = '';
9527
9528 this._adjustPan();
9529 },
9530
9531 getEvents: function () {
9532 var events = {
9533 zoom: this._updatePosition,
9534 viewreset: this._updatePosition
9535 };
9536
9537 if (this._zoomAnimated) {
9538 events.zoomanim = this._animateZoom;
9539 }
9540 return events;
9541 },
9542
9543 // @method isOpen: Boolean
9544 // Returns `true` when the popup is visible on the map.
9545 isOpen: function () {
9546 return !!this._map && this._map.hasLayer(this);
9547 },
9548
9549 // @method bringToFront: this
9550 // Brings this popup in front of other popups (in the same map pane).
9551 bringToFront: function () {
9552 if (this._map) {
9553 toFront(this._container);
9554 }
9555 return this;
9556 },
9557
9558 // @method bringToBack: this
9559 // Brings this popup to the back of other popups (in the same map pane).
9560 bringToBack: function () {
9561 if (this._map) {
9562 toBack(this._container);
9563 }
9564 return this;
9565 },
9566
9567 _prepareOpen: function (parent, layer, latlng) {
9568 if (!(layer instanceof Layer)) {
9569 latlng = layer;
9570 layer = parent;
9571 }
9572
9573 if (layer instanceof FeatureGroup) {
9574 for (var id in parent._layers) {
9575 layer = parent._layers[id];
9576 break;
9577 }
9578 }
9579
9580 if (!latlng) {
9581 if (layer.getCenter) {
9582 latlng = layer.getCenter();
9583 } else if (layer.getLatLng) {
9584 latlng = layer.getLatLng();
9585 } else {
9586 throw new Error('Unable to get source layer LatLng.');
9587 }
9588 }
9589
9590 // set overlay source to this layer
9591 this._source = layer;
9592
9593 // update the overlay (content, layout, ect...)
9594 this.update();
9595
9596 return latlng;
9597 },
9598
9599 _updateContent: function () {
9600 if (!this._content) { return; }
9601
9602 var node = this._contentNode;
9603 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9604
9605 if (typeof content === 'string') {
9606 node.innerHTML = content;
9607 } else {
9608 while (node.hasChildNodes()) {
9609 node.removeChild(node.firstChild);
9610 }
9611 node.appendChild(content);
9612 }
9613 this.fire('contentupdate');
9614 },
9615
9616 _updatePosition: function () {
9617 if (!this._map) { return; }
9618
9619 var pos = this._map.latLngToLayerPoint(this._latlng),
9620 offset = toPoint(this.options.offset),
9621 anchor = this._getAnchor();
9622
9623 if (this._zoomAnimated) {
9624 setPosition(this._container, pos.add(anchor));
9625 } else {
9626 offset = offset.add(pos).add(anchor);
9627 }
9628
9629 var bottom = this._containerBottom = -offset.y,
9630 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9631
9632 // bottom position the popup in case the height of the popup changes (images loading etc)
9633 this._container.style.bottom = bottom + 'px';
9634 this._container.style.left = left + 'px';
9635 },
9636
9637 _getAnchor: function () {
9638 return [0, 0];
9639 }
9640
9641});
9642
9643/*
9644 * @class Popup
9645 * @inherits DivOverlay
9646 * @aka L.Popup
9647 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9648 * open popups while making sure that only one popup is open at one time
9649 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9650 *
9651 * @example
9652 *
9653 * If you want to just bind a popup to marker click and then open it, it's really easy:
9654 *
9655 * ```js
9656 * marker.bindPopup(popupContent).openPopup();
9657 * ```
9658 * Path overlays like polylines also have a `bindPopup` method.
9659 * Here's a more complicated way to open a popup on a map:
9660 *
9661 * ```js
9662 * var popup = L.popup()
9663 * .setLatLng(latlng)
9664 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9665 * .openOn(map);
9666 * ```
9667 */
9668
9669
9670// @namespace Popup
9671var Popup = DivOverlay.extend({
9672
9673 // @section
9674 // @aka Popup options
9675 options: {
9676 // @option maxWidth: Number = 300
9677 // Max width of the popup, in pixels.
9678 maxWidth: 300,
9679
9680 // @option minWidth: Number = 50
9681 // Min width of the popup, in pixels.
9682 minWidth: 50,
9683
9684 // @option maxHeight: Number = null
9685 // If set, creates a scrollable container of the given height
9686 // inside a popup if its content exceeds it.
9687 maxHeight: null,
9688
9689 // @option autoPan: Boolean = true
9690 // Set it to `false` if you don't want the map to do panning animation
9691 // to fit the opened popup.
9692 autoPan: true,
9693
9694 // @option autoPanPaddingTopLeft: Point = null
9695 // The margin between the popup and the top left corner of the map
9696 // view after autopanning was performed.
9697 autoPanPaddingTopLeft: null,
9698
9699 // @option autoPanPaddingBottomRight: Point = null
9700 // The margin between the popup and the bottom right corner of the map
9701 // view after autopanning was performed.
9702 autoPanPaddingBottomRight: null,
9703
9704 // @option autoPanPadding: Point = Point(5, 5)
9705 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9706 autoPanPadding: [5, 5],
9707
9708 // @option keepInView: Boolean = false
9709 // Set it to `true` if you want to prevent users from panning the popup
9710 // off of the screen while it is open.
9711 keepInView: false,
9712
9713 // @option closeButton: Boolean = true
9714 // Controls the presence of a close button in the popup.
9715 closeButton: true,
9716
9717 // @option autoClose: Boolean = true
9718 // Set it to `false` if you want to override the default behavior of
9719 // the popup closing when another popup is opened.
9720 autoClose: true,
9721
9722 // @option closeOnEscapeKey: Boolean = true
9723 // Set it to `false` if you want to override the default behavior of
9724 // the ESC key for closing of the popup.
9725 closeOnEscapeKey: true,
9726
9727 // @option closeOnClick: Boolean = *
9728 // Set it if you want to override the default behavior of the popup closing when user clicks
9729 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9730
9731 // @option className: String = ''
9732 // A custom CSS class name to assign to the popup.
9733 className: ''
9734 },
9735
9736 // @namespace Popup
9737 // @method openOn(map: Map): this
9738 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9739 openOn: function (map) {
9740 map.openPopup(this);
9741 return this;
9742 },
9743
9744 onAdd: function (map) {
9745 DivOverlay.prototype.onAdd.call(this, map);
9746
9747 // @namespace Map
9748 // @section Popup events
9749 // @event popupopen: PopupEvent
9750 // Fired when a popup is opened in the map
9751 map.fire('popupopen', {popup: this});
9752
9753 if (this._source) {
9754 // @namespace Layer
9755 // @section Popup events
9756 // @event popupopen: PopupEvent
9757 // Fired when a popup bound to this layer is opened
9758 this._source.fire('popupopen', {popup: this}, true);
9759 // For non-path layers, we toggle the popup when clicking
9760 // again the layer, so prevent the map to reopen it.
9761 if (!(this._source instanceof Path)) {
9762 this._source.on('preclick', stopPropagation);
9763 }
9764 }
9765 },
9766
9767 onRemove: function (map) {
9768 DivOverlay.prototype.onRemove.call(this, map);
9769
9770 // @namespace Map
9771 // @section Popup events
9772 // @event popupclose: PopupEvent
9773 // Fired when a popup in the map is closed
9774 map.fire('popupclose', {popup: this});
9775
9776 if (this._source) {
9777 // @namespace Layer
9778 // @section Popup events
9779 // @event popupclose: PopupEvent
9780 // Fired when a popup bound to this layer is closed
9781 this._source.fire('popupclose', {popup: this}, true);
9782 if (!(this._source instanceof Path)) {
9783 this._source.off('preclick', stopPropagation);
9784 }
9785 }
9786 },
9787
9788 getEvents: function () {
9789 var events = DivOverlay.prototype.getEvents.call(this);
9790
9791 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9792 events.preclick = this._close;
9793 }
9794
9795 if (this.options.keepInView) {
9796 events.moveend = this._adjustPan;
9797 }
9798
9799 return events;
9800 },
9801
9802 _close: function () {
9803 if (this._map) {
9804 this._map.closePopup(this);
9805 }
9806 },
9807
9808 _initLayout: function () {
9809 var prefix = 'leaflet-popup',
9810 container = this._container = create$1('div',
9811 prefix + ' ' + (this.options.className || '') +
9812 ' leaflet-zoom-animated');
9813
9814 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9815 this._contentNode = create$1('div', prefix + '-content', wrapper);
9816
9817 disableClickPropagation(wrapper);
9818 disableScrollPropagation(this._contentNode);
9819 on(wrapper, 'contextmenu', stopPropagation);
9820
9821 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9822 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9823
9824 if (this.options.closeButton) {
9825 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9826 closeButton.href = '#close';
9827 closeButton.innerHTML = '&#215;';
9828
9829 on(closeButton, 'click', this._onCloseButtonClick, this);
9830 }
9831 },
9832
9833 _updateLayout: function () {
9834 var container = this._contentNode,
9835 style = container.style;
9836
9837 style.width = '';
9838 style.whiteSpace = 'nowrap';
9839
9840 var width = container.offsetWidth;
9841 width = Math.min(width, this.options.maxWidth);
9842 width = Math.max(width, this.options.minWidth);
9843
9844 style.width = (width + 1) + 'px';
9845 style.whiteSpace = '';
9846
9847 style.height = '';
9848
9849 var height = container.offsetHeight,
9850 maxHeight = this.options.maxHeight,
9851 scrolledClass = 'leaflet-popup-scrolled';
9852
9853 if (maxHeight && height > maxHeight) {
9854 style.height = maxHeight + 'px';
9855 addClass(container, scrolledClass);
9856 } else {
9857 removeClass(container, scrolledClass);
9858 }
9859
9860 this._containerWidth = this._container.offsetWidth;
9861 },
9862
9863 _animateZoom: function (e) {
9864 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9865 anchor = this._getAnchor();
9866 setPosition(this._container, pos.add(anchor));
9867 },
9868
9869 _adjustPan: function () {
9870 if (!this.options.autoPan) { return; }
9871 if (this._map._panAnim) { this._map._panAnim.stop(); }
9872
9873 var map = this._map,
9874 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9875 containerHeight = this._container.offsetHeight + marginBottom,
9876 containerWidth = this._containerWidth,
9877 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9878
9879 layerPos._add(getPosition(this._container));
9880
9881 var containerPos = map.layerPointToContainerPoint(layerPos),
9882 padding = toPoint(this.options.autoPanPadding),
9883 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9884 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9885 size = map.getSize(),
9886 dx = 0,
9887 dy = 0;
9888
9889 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9890 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9891 }
9892 if (containerPos.x - dx - paddingTL.x < 0) { // left
9893 dx = containerPos.x - paddingTL.x;
9894 }
9895 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9896 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9897 }
9898 if (containerPos.y - dy - paddingTL.y < 0) { // top
9899 dy = containerPos.y - paddingTL.y;
9900 }
9901
9902 // @namespace Map
9903 // @section Popup events
9904 // @event autopanstart: Event
9905 // Fired when the map starts autopanning when opening a popup.
9906 if (dx || dy) {
9907 map
9908 .fire('autopanstart')
9909 .panBy([dx, dy]);
9910 }
9911 },
9912
9913 _onCloseButtonClick: function (e) {
9914 this._close();
9915 stop(e);
9916 },
9917
9918 _getAnchor: function () {
9919 // Where should we anchor the popup on the source layer?
9920 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9921 }
9922
9923});
9924
9925// @namespace Popup
9926// @factory L.popup(options?: Popup options, source?: Layer)
9927// 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.
9928var popup = function (options, source) {
9929 return new Popup(options, source);
9930};
9931
9932
9933/* @namespace Map
9934 * @section Interaction Options
9935 * @option closePopupOnClick: Boolean = true
9936 * Set it to `false` if you don't want popups to close when user clicks the map.
9937 */
9938Map.mergeOptions({
9939 closePopupOnClick: true
9940});
9941
9942
9943// @namespace Map
9944// @section Methods for Layers and Controls
9945Map.include({
9946 // @method openPopup(popup: Popup): this
9947 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9948 // @alternative
9949 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9950 // Creates a popup with the specified content and options and opens it in the given point on a map.
9951 openPopup: function (popup, latlng, options) {
9952 if (!(popup instanceof Popup)) {
9953 popup = new Popup(options).setContent(popup);
9954 }
9955
9956 if (latlng) {
9957 popup.setLatLng(latlng);
9958 }
9959
9960 if (this.hasLayer(popup)) {
9961 return this;
9962 }
9963
9964 if (this._popup && this._popup.options.autoClose) {
9965 this.closePopup();
9966 }
9967
9968 this._popup = popup;
9969 return this.addLayer(popup);
9970 },
9971
9972 // @method closePopup(popup?: Popup): this
9973 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9974 closePopup: function (popup) {
9975 if (!popup || popup === this._popup) {
9976 popup = this._popup;
9977 this._popup = null;
9978 }
9979 if (popup) {
9980 this.removeLayer(popup);
9981 }
9982 return this;
9983 }
9984});
9985
9986/*
9987 * @namespace Layer
9988 * @section Popup methods example
9989 *
9990 * All layers share a set of methods convenient for binding popups to it.
9991 *
9992 * ```js
9993 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9994 * layer.openPopup();
9995 * layer.closePopup();
9996 * ```
9997 *
9998 * 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.
9999 */
10000
10001// @section Popup methods
10002Layer.include({
10003
10004 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10005 // Binds a popup to the layer with the passed `content` and sets up the
10006 // necessary event listeners. If a `Function` is passed it will receive
10007 // the layer as the first argument and should return a `String` or `HTMLElement`.
10008 bindPopup: function (content, options) {
10009
10010 if (content instanceof Popup) {
10011 setOptions(content, options);
10012 this._popup = content;
10013 content._source = this;
10014 } else {
10015 if (!this._popup || options) {
10016 this._popup = new Popup(options, this);
10017 }
10018 this._popup.setContent(content);
10019 }
10020
10021 if (!this._popupHandlersAdded) {
10022 this.on({
10023 click: this._openPopup,
10024 keypress: this._onKeyPress,
10025 remove: this.closePopup,
10026 move: this._movePopup
10027 });
10028 this._popupHandlersAdded = true;
10029 }
10030
10031 return this;
10032 },
10033
10034 // @method unbindPopup(): this
10035 // Removes the popup previously bound with `bindPopup`.
10036 unbindPopup: function () {
10037 if (this._popup) {
10038 this.off({
10039 click: this._openPopup,
10040 keypress: this._onKeyPress,
10041 remove: this.closePopup,
10042 move: this._movePopup
10043 });
10044 this._popupHandlersAdded = false;
10045 this._popup = null;
10046 }
10047 return this;
10048 },
10049
10050 // @method openPopup(latlng?: LatLng): this
10051 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10052 openPopup: function (layer, latlng) {
10053 if (this._popup && this._map) {
10054 latlng = this._popup._prepareOpen(this, layer, latlng);
10055
10056 // open the popup on the map
10057 this._map.openPopup(this._popup, latlng);
10058 }
10059
10060 return this;
10061 },
10062
10063 // @method closePopup(): this
10064 // Closes the popup bound to this layer if it is open.
10065 closePopup: function () {
10066 if (this._popup) {
10067 this._popup._close();
10068 }
10069 return this;
10070 },
10071
10072 // @method togglePopup(): this
10073 // Opens or closes the popup bound to this layer depending on its current state.
10074 togglePopup: function (target) {
10075 if (this._popup) {
10076 if (this._popup._map) {
10077 this.closePopup();
10078 } else {
10079 this.openPopup(target);
10080 }
10081 }
10082 return this;
10083 },
10084
10085 // @method isPopupOpen(): boolean
10086 // Returns `true` if the popup bound to this layer is currently open.
10087 isPopupOpen: function () {
10088 return (this._popup ? this._popup.isOpen() : false);
10089 },
10090
10091 // @method setPopupContent(content: String|HTMLElement|Popup): this
10092 // Sets the content of the popup bound to this layer.
10093 setPopupContent: function (content) {
10094 if (this._popup) {
10095 this._popup.setContent(content);
10096 }
10097 return this;
10098 },
10099
10100 // @method getPopup(): Popup
10101 // Returns the popup bound to this layer.
10102 getPopup: function () {
10103 return this._popup;
10104 },
10105
10106 _openPopup: function (e) {
10107 var layer = e.layer || e.target;
10108
10109 if (!this._popup) {
10110 return;
10111 }
10112
10113 if (!this._map) {
10114 return;
10115 }
10116
10117 // prevent map click
10118 stop(e);
10119
10120 // if this inherits from Path its a vector and we can just
10121 // open the popup at the new location
10122 if (layer instanceof Path) {
10123 this.openPopup(e.layer || e.target, e.latlng);
10124 return;
10125 }
10126
10127 // otherwise treat it like a marker and figure out
10128 // if we should toggle it open/closed
10129 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10130 this.closePopup();
10131 } else {
10132 this.openPopup(layer, e.latlng);
10133 }
10134 },
10135
10136 _movePopup: function (e) {
10137 this._popup.setLatLng(e.latlng);
10138 },
10139
10140 _onKeyPress: function (e) {
10141 if (e.originalEvent.keyCode === 13) {
10142 this._openPopup(e);
10143 }
10144 }
10145});
10146
10147/*
10148 * @class Tooltip
10149 * @inherits DivOverlay
10150 * @aka L.Tooltip
10151 * Used to display small texts on top of map layers.
10152 *
10153 * @example
10154 *
10155 * ```js
10156 * marker.bindTooltip("my tooltip text").openTooltip();
10157 * ```
10158 * Note about tooltip offset. Leaflet takes two options in consideration
10159 * for computing tooltip offsetting:
10160 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10161 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10162 * move it to the bottom. Negatives will move to the left and top.
10163 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10164 * should adapt this value if you use a custom icon.
10165 */
10166
10167
10168// @namespace Tooltip
10169var Tooltip = DivOverlay.extend({
10170
10171 // @section
10172 // @aka Tooltip options
10173 options: {
10174 // @option pane: String = 'tooltipPane'
10175 // `Map pane` where the tooltip will be added.
10176 pane: 'tooltipPane',
10177
10178 // @option offset: Point = Point(0, 0)
10179 // Optional offset of the tooltip position.
10180 offset: [0, 0],
10181
10182 // @option direction: String = 'auto'
10183 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10184 // `top`, `bottom`, `center`, `auto`.
10185 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10186 // position on the map.
10187 direction: 'auto',
10188
10189 // @option permanent: Boolean = false
10190 // Whether to open the tooltip permanently or only on mouseover.
10191 permanent: false,
10192
10193 // @option sticky: Boolean = false
10194 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10195 sticky: false,
10196
10197 // @option interactive: Boolean = false
10198 // If true, the tooltip will listen to the feature events.
10199 interactive: false,
10200
10201 // @option opacity: Number = 0.9
10202 // Tooltip container opacity.
10203 opacity: 0.9
10204 },
10205
10206 onAdd: function (map) {
10207 DivOverlay.prototype.onAdd.call(this, map);
10208 this.setOpacity(this.options.opacity);
10209
10210 // @namespace Map
10211 // @section Tooltip events
10212 // @event tooltipopen: TooltipEvent
10213 // Fired when a tooltip is opened in the map.
10214 map.fire('tooltipopen', {tooltip: this});
10215
10216 if (this._source) {
10217 // @namespace Layer
10218 // @section Tooltip events
10219 // @event tooltipopen: TooltipEvent
10220 // Fired when a tooltip bound to this layer is opened.
10221 this._source.fire('tooltipopen', {tooltip: this}, true);
10222 }
10223 },
10224
10225 onRemove: function (map) {
10226 DivOverlay.prototype.onRemove.call(this, map);
10227
10228 // @namespace Map
10229 // @section Tooltip events
10230 // @event tooltipclose: TooltipEvent
10231 // Fired when a tooltip in the map is closed.
10232 map.fire('tooltipclose', {tooltip: this});
10233
10234 if (this._source) {
10235 // @namespace Layer
10236 // @section Tooltip events
10237 // @event tooltipclose: TooltipEvent
10238 // Fired when a tooltip bound to this layer is closed.
10239 this._source.fire('tooltipclose', {tooltip: this}, true);
10240 }
10241 },
10242
10243 getEvents: function () {
10244 var events = DivOverlay.prototype.getEvents.call(this);
10245
10246 if (touch && !this.options.permanent) {
10247 events.preclick = this._close;
10248 }
10249
10250 return events;
10251 },
10252
10253 _close: function () {
10254 if (this._map) {
10255 this._map.closeTooltip(this);
10256 }
10257 },
10258
10259 _initLayout: function () {
10260 var prefix = 'leaflet-tooltip',
10261 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10262
10263 this._contentNode = this._container = create$1('div', className);
10264 },
10265
10266 _updateLayout: function () {},
10267
10268 _adjustPan: function () {},
10269
10270 _setPosition: function (pos) {
10271 var map = this._map,
10272 container = this._container,
10273 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10274 tooltipPoint = map.layerPointToContainerPoint(pos),
10275 direction = this.options.direction,
10276 tooltipWidth = container.offsetWidth,
10277 tooltipHeight = container.offsetHeight,
10278 offset = toPoint(this.options.offset),
10279 anchor = this._getAnchor();
10280
10281 if (direction === 'top') {
10282 pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10283 } else if (direction === 'bottom') {
10284 pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10285 } else if (direction === 'center') {
10286 pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10287 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10288 direction = 'right';
10289 pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10290 } else {
10291 direction = 'left';
10292 pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10293 }
10294
10295 removeClass(container, 'leaflet-tooltip-right');
10296 removeClass(container, 'leaflet-tooltip-left');
10297 removeClass(container, 'leaflet-tooltip-top');
10298 removeClass(container, 'leaflet-tooltip-bottom');
10299 addClass(container, 'leaflet-tooltip-' + direction);
10300 setPosition(container, pos);
10301 },
10302
10303 _updatePosition: function () {
10304 var pos = this._map.latLngToLayerPoint(this._latlng);
10305 this._setPosition(pos);
10306 },
10307
10308 setOpacity: function (opacity) {
10309 this.options.opacity = opacity;
10310
10311 if (this._container) {
10312 setOpacity(this._container, opacity);
10313 }
10314 },
10315
10316 _animateZoom: function (e) {
10317 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10318 this._setPosition(pos);
10319 },
10320
10321 _getAnchor: function () {
10322 // Where should we anchor the tooltip on the source layer?
10323 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10324 }
10325
10326});
10327
10328// @namespace Tooltip
10329// @factory L.tooltip(options?: Tooltip options, source?: Layer)
10330// 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.
10331var tooltip = function (options, source) {
10332 return new Tooltip(options, source);
10333};
10334
10335// @namespace Map
10336// @section Methods for Layers and Controls
10337Map.include({
10338
10339 // @method openTooltip(tooltip: Tooltip): this
10340 // Opens the specified tooltip.
10341 // @alternative
10342 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10343 // Creates a tooltip with the specified content and options and open it.
10344 openTooltip: function (tooltip, latlng, options) {
10345 if (!(tooltip instanceof Tooltip)) {
10346 tooltip = new Tooltip(options).setContent(tooltip);
10347 }
10348
10349 if (latlng) {
10350 tooltip.setLatLng(latlng);
10351 }
10352
10353 if (this.hasLayer(tooltip)) {
10354 return this;
10355 }
10356
10357 return this.addLayer(tooltip);
10358 },
10359
10360 // @method closeTooltip(tooltip?: Tooltip): this
10361 // Closes the tooltip given as parameter.
10362 closeTooltip: function (tooltip) {
10363 if (tooltip) {
10364 this.removeLayer(tooltip);
10365 }
10366 return this;
10367 }
10368
10369});
10370
10371/*
10372 * @namespace Layer
10373 * @section Tooltip methods example
10374 *
10375 * All layers share a set of methods convenient for binding tooltips to it.
10376 *
10377 * ```js
10378 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10379 * layer.openTooltip();
10380 * layer.closeTooltip();
10381 * ```
10382 */
10383
10384// @section Tooltip methods
10385Layer.include({
10386
10387 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10388 // Binds a tooltip to the layer with the passed `content` and sets up the
10389 // necessary event listeners. If a `Function` is passed it will receive
10390 // the layer as the first argument and should return a `String` or `HTMLElement`.
10391 bindTooltip: function (content, options) {
10392
10393 if (content instanceof Tooltip) {
10394 setOptions(content, options);
10395 this._tooltip = content;
10396 content._source = this;
10397 } else {
10398 if (!this._tooltip || options) {
10399 this._tooltip = new Tooltip(options, this);
10400 }
10401 this._tooltip.setContent(content);
10402
10403 }
10404
10405 this._initTooltipInteractions();
10406
10407 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10408 this.openTooltip();
10409 }
10410
10411 return this;
10412 },
10413
10414 // @method unbindTooltip(): this
10415 // Removes the tooltip previously bound with `bindTooltip`.
10416 unbindTooltip: function () {
10417 if (this._tooltip) {
10418 this._initTooltipInteractions(true);
10419 this.closeTooltip();
10420 this._tooltip = null;
10421 }
10422 return this;
10423 },
10424
10425 _initTooltipInteractions: function (remove$$1) {
10426 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10427 var onOff = remove$$1 ? 'off' : 'on',
10428 events = {
10429 remove: this.closeTooltip,
10430 move: this._moveTooltip
10431 };
10432 if (!this._tooltip.options.permanent) {
10433 events.mouseover = this._openTooltip;
10434 events.mouseout = this.closeTooltip;
10435 if (this._tooltip.options.sticky) {
10436 events.mousemove = this._moveTooltip;
10437 }
10438 if (touch) {
10439 events.click = this._openTooltip;
10440 }
10441 } else {
10442 events.add = this._openTooltip;
10443 }
10444 this[onOff](events);
10445 this._tooltipHandlersAdded = !remove$$1;
10446 },
10447
10448 // @method openTooltip(latlng?: LatLng): this
10449 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10450 openTooltip: function (layer, latlng) {
10451 if (this._tooltip && this._map) {
10452 latlng = this._tooltip._prepareOpen(this, layer, latlng);
10453
10454 // open the tooltip on the map
10455 this._map.openTooltip(this._tooltip, latlng);
10456
10457 // Tooltip container may not be defined if not permanent and never
10458 // opened.
10459 if (this._tooltip.options.interactive && this._tooltip._container) {
10460 addClass(this._tooltip._container, 'leaflet-clickable');
10461 this.addInteractiveTarget(this._tooltip._container);
10462 }
10463 }
10464
10465 return this;
10466 },
10467
10468 // @method closeTooltip(): this
10469 // Closes the tooltip bound to this layer if it is open.
10470 closeTooltip: function () {
10471 if (this._tooltip) {
10472 this._tooltip._close();
10473 if (this._tooltip.options.interactive && this._tooltip._container) {
10474 removeClass(this._tooltip._container, 'leaflet-clickable');
10475 this.removeInteractiveTarget(this._tooltip._container);
10476 }
10477 }
10478 return this;
10479 },
10480
10481 // @method toggleTooltip(): this
10482 // Opens or closes the tooltip bound to this layer depending on its current state.
10483 toggleTooltip: function (target) {
10484 if (this._tooltip) {
10485 if (this._tooltip._map) {
10486 this.closeTooltip();
10487 } else {
10488 this.openTooltip(target);
10489 }
10490 }
10491 return this;
10492 },
10493
10494 // @method isTooltipOpen(): boolean
10495 // Returns `true` if the tooltip bound to this layer is currently open.
10496 isTooltipOpen: function () {
10497 return this._tooltip.isOpen();
10498 },
10499
10500 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10501 // Sets the content of the tooltip bound to this layer.
10502 setTooltipContent: function (content) {
10503 if (this._tooltip) {
10504 this._tooltip.setContent(content);
10505 }
10506 return this;
10507 },
10508
10509 // @method getTooltip(): Tooltip
10510 // Returns the tooltip bound to this layer.
10511 getTooltip: function () {
10512 return this._tooltip;
10513 },
10514
10515 _openTooltip: function (e) {
10516 var layer = e.layer || e.target;
10517
10518 if (!this._tooltip || !this._map) {
10519 return;
10520 }
10521 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10522 },
10523
10524 _moveTooltip: function (e) {
10525 var latlng = e.latlng, containerPoint, layerPoint;
10526 if (this._tooltip.options.sticky && e.originalEvent) {
10527 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10528 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10529 latlng = this._map.layerPointToLatLng(layerPoint);
10530 }
10531 this._tooltip.setLatLng(latlng);
10532 }
10533});
10534
10535/*
10536 * @class DivIcon
10537 * @aka L.DivIcon
10538 * @inherits Icon
10539 *
10540 * Represents a lightweight icon for markers that uses a simple `<div>`
10541 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10542 *
10543 * @example
10544 * ```js
10545 * var myIcon = L.divIcon({className: 'my-div-icon'});
10546 * // you can set .my-div-icon styles in CSS
10547 *
10548 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10549 * ```
10550 *
10551 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10552 */
10553
10554var DivIcon = Icon.extend({
10555 options: {
10556 // @section
10557 // @aka DivIcon options
10558 iconSize: [12, 12], // also can be set through CSS
10559
10560 // iconAnchor: (Point),
10561 // popupAnchor: (Point),
10562
10563 // @option html: String|HTMLElement = ''
10564 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10565 // an instance of `HTMLElement`.
10566 html: false,
10567
10568 // @option bgPos: Point = [0, 0]
10569 // Optional relative position of the background, in pixels
10570 bgPos: null,
10571
10572 className: 'leaflet-div-icon'
10573 },
10574
10575 createIcon: function (oldIcon) {
10576 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10577 options = this.options;
10578
10579 if (options.html instanceof Element) {
10580 empty(div);
10581 div.appendChild(options.html);
10582 } else {
10583 div.innerHTML = options.html !== false ? options.html : '';
10584 }
10585
10586 if (options.bgPos) {
10587 var bgPos = toPoint(options.bgPos);
10588 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10589 }
10590 this._setIconStyles(div, 'icon');
10591
10592 return div;
10593 },
10594
10595 createShadow: function () {
10596 return null;
10597 }
10598});
10599
10600// @factory L.divIcon(options: DivIcon options)
10601// Creates a `DivIcon` instance with the given options.
10602function divIcon(options) {
10603 return new DivIcon(options);
10604}
10605
10606Icon.Default = IconDefault;
10607
10608/*
10609 * @class GridLayer
10610 * @inherits Layer
10611 * @aka L.GridLayer
10612 *
10613 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10614 * 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.
10615 *
10616 *
10617 * @section Synchronous usage
10618 * @example
10619 *
10620 * 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.
10621 *
10622 * ```js
10623 * var CanvasLayer = L.GridLayer.extend({
10624 * createTile: function(coords){
10625 * // create a <canvas> element for drawing
10626 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10627 *
10628 * // setup tile width and height according to the options
10629 * var size = this.getTileSize();
10630 * tile.width = size.x;
10631 * tile.height = size.y;
10632 *
10633 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10634 * var ctx = tile.getContext('2d');
10635 *
10636 * // return the tile so it can be rendered on screen
10637 * return tile;
10638 * }
10639 * });
10640 * ```
10641 *
10642 * @section Asynchronous usage
10643 * @example
10644 *
10645 * 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.
10646 *
10647 * ```js
10648 * var CanvasLayer = L.GridLayer.extend({
10649 * createTile: function(coords, done){
10650 * var error;
10651 *
10652 * // create a <canvas> element for drawing
10653 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10654 *
10655 * // setup tile width and height according to the options
10656 * var size = this.getTileSize();
10657 * tile.width = size.x;
10658 * tile.height = size.y;
10659 *
10660 * // draw something asynchronously and pass the tile to the done() callback
10661 * setTimeout(function() {
10662 * done(error, tile);
10663 * }, 1000);
10664 *
10665 * return tile;
10666 * }
10667 * });
10668 * ```
10669 *
10670 * @section
10671 */
10672
10673
10674var GridLayer = Layer.extend({
10675
10676 // @section
10677 // @aka GridLayer options
10678 options: {
10679 // @option tileSize: Number|Point = 256
10680 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10681 tileSize: 256,
10682
10683 // @option opacity: Number = 1.0
10684 // Opacity of the tiles. Can be used in the `createTile()` function.
10685 opacity: 1,
10686
10687 // @option updateWhenIdle: Boolean = (depends)
10688 // Load new tiles only when panning ends.
10689 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10690 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10691 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10692 updateWhenIdle: mobile,
10693
10694 // @option updateWhenZooming: Boolean = true
10695 // 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.
10696 updateWhenZooming: true,
10697
10698 // @option updateInterval: Number = 200
10699 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10700 updateInterval: 200,
10701
10702 // @option zIndex: Number = 1
10703 // The explicit zIndex of the tile layer.
10704 zIndex: 1,
10705
10706 // @option bounds: LatLngBounds = undefined
10707 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10708 bounds: null,
10709
10710 // @option minZoom: Number = 0
10711 // The minimum zoom level down to which this layer will be displayed (inclusive).
10712 minZoom: 0,
10713
10714 // @option maxZoom: Number = undefined
10715 // The maximum zoom level up to which this layer will be displayed (inclusive).
10716 maxZoom: undefined,
10717
10718 // @option maxNativeZoom: Number = undefined
10719 // Maximum zoom number the tile source has available. If it is specified,
10720 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10721 // from `maxNativeZoom` level and auto-scaled.
10722 maxNativeZoom: undefined,
10723
10724 // @option minNativeZoom: Number = undefined
10725 // Minimum zoom number the tile source has available. If it is specified,
10726 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10727 // from `minNativeZoom` level and auto-scaled.
10728 minNativeZoom: undefined,
10729
10730 // @option noWrap: Boolean = false
10731 // Whether the layer is wrapped around the antimeridian. If `true`, the
10732 // GridLayer will only be displayed once at low zoom levels. Has no
10733 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10734 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10735 // tiles outside the CRS limits.
10736 noWrap: false,
10737
10738 // @option pane: String = 'tilePane'
10739 // `Map pane` where the grid layer will be added.
10740 pane: 'tilePane',
10741
10742 // @option className: String = ''
10743 // A custom class name to assign to the tile layer. Empty by default.
10744 className: '',
10745
10746 // @option keepBuffer: Number = 2
10747 // When panning the map, keep this many rows and columns of tiles before unloading them.
10748 keepBuffer: 2
10749 },
10750
10751 initialize: function (options) {
10752 setOptions(this, options);
10753 },
10754
10755 onAdd: function () {
10756 this._initContainer();
10757
10758 this._levels = {};
10759 this._tiles = {};
10760
10761 this._resetView();
10762 this._update();
10763 },
10764
10765 beforeAdd: function (map) {
10766 map._addZoomLimit(this);
10767 },
10768
10769 onRemove: function (map) {
10770 this._removeAllTiles();
10771 remove(this._container);
10772 map._removeZoomLimit(this);
10773 this._container = null;
10774 this._tileZoom = undefined;
10775 },
10776
10777 // @method bringToFront: this
10778 // Brings the tile layer to the top of all tile layers.
10779 bringToFront: function () {
10780 if (this._map) {
10781 toFront(this._container);
10782 this._setAutoZIndex(Math.max);
10783 }
10784 return this;
10785 },
10786
10787 // @method bringToBack: this
10788 // Brings the tile layer to the bottom of all tile layers.
10789 bringToBack: function () {
10790 if (this._map) {
10791 toBack(this._container);
10792 this._setAutoZIndex(Math.min);
10793 }
10794 return this;
10795 },
10796
10797 // @method getContainer: HTMLElement
10798 // Returns the HTML element that contains the tiles for this layer.
10799 getContainer: function () {
10800 return this._container;
10801 },
10802
10803 // @method setOpacity(opacity: Number): this
10804 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10805 setOpacity: function (opacity) {
10806 this.options.opacity = opacity;
10807 this._updateOpacity();
10808 return this;
10809 },
10810
10811 // @method setZIndex(zIndex: Number): this
10812 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10813 setZIndex: function (zIndex) {
10814 this.options.zIndex = zIndex;
10815 this._updateZIndex();
10816
10817 return this;
10818 },
10819
10820 // @method isLoading: Boolean
10821 // Returns `true` if any tile in the grid layer has not finished loading.
10822 isLoading: function () {
10823 return this._loading;
10824 },
10825
10826 // @method redraw: this
10827 // Causes the layer to clear all the tiles and request them again.
10828 redraw: function () {
10829 if (this._map) {
10830 this._removeAllTiles();
10831 this._update();
10832 }
10833 return this;
10834 },
10835
10836 getEvents: function () {
10837 var events = {
10838 viewprereset: this._invalidateAll,
10839 viewreset: this._resetView,
10840 zoom: this._resetView,
10841 moveend: this._onMoveEnd
10842 };
10843
10844 if (!this.options.updateWhenIdle) {
10845 // update tiles on move, but not more often than once per given interval
10846 if (!this._onMove) {
10847 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10848 }
10849
10850 events.move = this._onMove;
10851 }
10852
10853 if (this._zoomAnimated) {
10854 events.zoomanim = this._animateZoom;
10855 }
10856
10857 return events;
10858 },
10859
10860 // @section Extension methods
10861 // Layers extending `GridLayer` shall reimplement the following method.
10862 // @method createTile(coords: Object, done?: Function): HTMLElement
10863 // Called only internally, must be overridden by classes extending `GridLayer`.
10864 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10865 // is specified, it must be called when the tile has finished loading and drawing.
10866 createTile: function () {
10867 return document.createElement('div');
10868 },
10869
10870 // @section
10871 // @method getTileSize: Point
10872 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10873 getTileSize: function () {
10874 var s = this.options.tileSize;
10875 return s instanceof Point ? s : new Point(s, s);
10876 },
10877
10878 _updateZIndex: function () {
10879 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10880 this._container.style.zIndex = this.options.zIndex;
10881 }
10882 },
10883
10884 _setAutoZIndex: function (compare) {
10885 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10886
10887 var layers = this.getPane().children,
10888 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10889
10890 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10891
10892 zIndex = layers[i].style.zIndex;
10893
10894 if (layers[i] !== this._container && zIndex) {
10895 edgeZIndex = compare(edgeZIndex, +zIndex);
10896 }
10897 }
10898
10899 if (isFinite(edgeZIndex)) {
10900 this.options.zIndex = edgeZIndex + compare(-1, 1);
10901 this._updateZIndex();
10902 }
10903 },
10904
10905 _updateOpacity: function () {
10906 if (!this._map) { return; }
10907
10908 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10909 if (ielt9) { return; }
10910
10911 setOpacity(this._container, this.options.opacity);
10912
10913 var now = +new Date(),
10914 nextFrame = false,
10915 willPrune = false;
10916
10917 for (var key in this._tiles) {
10918 var tile = this._tiles[key];
10919 if (!tile.current || !tile.loaded) { continue; }
10920
10921 var fade = Math.min(1, (now - tile.loaded) / 200);
10922
10923 setOpacity(tile.el, fade);
10924 if (fade < 1) {
10925 nextFrame = true;
10926 } else {
10927 if (tile.active) {
10928 willPrune = true;
10929 } else {
10930 this._onOpaqueTile(tile);
10931 }
10932 tile.active = true;
10933 }
10934 }
10935
10936 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10937
10938 if (nextFrame) {
10939 cancelAnimFrame(this._fadeFrame);
10940 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10941 }
10942 },
10943
10944 _onOpaqueTile: falseFn,
10945
10946 _initContainer: function () {
10947 if (this._container) { return; }
10948
10949 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10950 this._updateZIndex();
10951
10952 if (this.options.opacity < 1) {
10953 this._updateOpacity();
10954 }
10955
10956 this.getPane().appendChild(this._container);
10957 },
10958
10959 _updateLevels: function () {
10960
10961 var zoom = this._tileZoom,
10962 maxZoom = this.options.maxZoom;
10963
10964 if (zoom === undefined) { return undefined; }
10965
10966 for (var z in this._levels) {
10967 if (this._levels[z].el.children.length || z === zoom) {
10968 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10969 this._onUpdateLevel(z);
10970 } else {
10971 remove(this._levels[z].el);
10972 this._removeTilesAtZoom(z);
10973 this._onRemoveLevel(z);
10974 delete this._levels[z];
10975 }
10976 }
10977
10978 var level = this._levels[zoom],
10979 map = this._map;
10980
10981 if (!level) {
10982 level = this._levels[zoom] = {};
10983
10984 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10985 level.el.style.zIndex = maxZoom;
10986
10987 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10988 level.zoom = zoom;
10989
10990 this._setZoomTransform(level, map.getCenter(), map.getZoom());
10991
10992 // force the browser to consider the newly added element for transition
10993 falseFn(level.el.offsetWidth);
10994
10995 this._onCreateLevel(level);
10996 }
10997
10998 this._level = level;
10999
11000 return level;
11001 },
11002
11003 _onUpdateLevel: falseFn,
11004
11005 _onRemoveLevel: falseFn,
11006
11007 _onCreateLevel: falseFn,
11008
11009 _pruneTiles: function () {
11010 if (!this._map) {
11011 return;
11012 }
11013
11014 var key, tile;
11015
11016 var zoom = this._map.getZoom();
11017 if (zoom > this.options.maxZoom ||
11018 zoom < this.options.minZoom) {
11019 this._removeAllTiles();
11020 return;
11021 }
11022
11023 for (key in this._tiles) {
11024 tile = this._tiles[key];
11025 tile.retain = tile.current;
11026 }
11027
11028 for (key in this._tiles) {
11029 tile = this._tiles[key];
11030 if (tile.current && !tile.active) {
11031 var coords = tile.coords;
11032 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11033 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11034 }
11035 }
11036 }
11037
11038 for (key in this._tiles) {
11039 if (!this._tiles[key].retain) {
11040 this._removeTile(key);
11041 }
11042 }
11043 },
11044
11045 _removeTilesAtZoom: function (zoom) {
11046 for (var key in this._tiles) {
11047 if (this._tiles[key].coords.z !== zoom) {
11048 continue;
11049 }
11050 this._removeTile(key);
11051 }
11052 },
11053
11054 _removeAllTiles: function () {
11055 for (var key in this._tiles) {
11056 this._removeTile(key);
11057 }
11058 },
11059
11060 _invalidateAll: function () {
11061 for (var z in this._levels) {
11062 remove(this._levels[z].el);
11063 this._onRemoveLevel(z);
11064 delete this._levels[z];
11065 }
11066 this._removeAllTiles();
11067
11068 this._tileZoom = undefined;
11069 },
11070
11071 _retainParent: function (x, y, z, minZoom) {
11072 var x2 = Math.floor(x / 2),
11073 y2 = Math.floor(y / 2),
11074 z2 = z - 1,
11075 coords2 = new Point(+x2, +y2);
11076 coords2.z = +z2;
11077
11078 var key = this._tileCoordsToKey(coords2),
11079 tile = this._tiles[key];
11080
11081 if (tile && tile.active) {
11082 tile.retain = true;
11083 return true;
11084
11085 } else if (tile && tile.loaded) {
11086 tile.retain = true;
11087 }
11088
11089 if (z2 > minZoom) {
11090 return this._retainParent(x2, y2, z2, minZoom);
11091 }
11092
11093 return false;
11094 },
11095
11096 _retainChildren: function (x, y, z, maxZoom) {
11097
11098 for (var i = 2 * x; i < 2 * x + 2; i++) {
11099 for (var j = 2 * y; j < 2 * y + 2; j++) {
11100
11101 var coords = new Point(i, j);
11102 coords.z = z + 1;
11103
11104 var key = this._tileCoordsToKey(coords),
11105 tile = this._tiles[key];
11106
11107 if (tile && tile.active) {
11108 tile.retain = true;
11109 continue;
11110
11111 } else if (tile && tile.loaded) {
11112 tile.retain = true;
11113 }
11114
11115 if (z + 1 < maxZoom) {
11116 this._retainChildren(i, j, z + 1, maxZoom);
11117 }
11118 }
11119 }
11120 },
11121
11122 _resetView: function (e) {
11123 var animating = e && (e.pinch || e.flyTo);
11124 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11125 },
11126
11127 _animateZoom: function (e) {
11128 this._setView(e.center, e.zoom, true, e.noUpdate);
11129 },
11130
11131 _clampZoom: function (zoom) {
11132 var options = this.options;
11133
11134 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11135 return options.minNativeZoom;
11136 }
11137
11138 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11139 return options.maxNativeZoom;
11140 }
11141
11142 return zoom;
11143 },
11144
11145 _setView: function (center, zoom, noPrune, noUpdate) {
11146 var tileZoom = this._clampZoom(Math.round(zoom));
11147 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11148 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11149 tileZoom = undefined;
11150 }
11151
11152 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11153
11154 if (!noUpdate || tileZoomChanged) {
11155
11156 this._tileZoom = tileZoom;
11157
11158 if (this._abortLoading) {
11159 this._abortLoading();
11160 }
11161
11162 this._updateLevels();
11163 this._resetGrid();
11164
11165 if (tileZoom !== undefined) {
11166 this._update(center);
11167 }
11168
11169 if (!noPrune) {
11170 this._pruneTiles();
11171 }
11172
11173 // Flag to prevent _updateOpacity from pruning tiles during
11174 // a zoom anim or a pinch gesture
11175 this._noPrune = !!noPrune;
11176 }
11177
11178 this._setZoomTransforms(center, zoom);
11179 },
11180
11181 _setZoomTransforms: function (center, zoom) {
11182 for (var i in this._levels) {
11183 this._setZoomTransform(this._levels[i], center, zoom);
11184 }
11185 },
11186
11187 _setZoomTransform: function (level, center, zoom) {
11188 var scale = this._map.getZoomScale(zoom, level.zoom),
11189 translate = level.origin.multiplyBy(scale)
11190 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11191
11192 if (any3d) {
11193 setTransform(level.el, translate, scale);
11194 } else {
11195 setPosition(level.el, translate);
11196 }
11197 },
11198
11199 _resetGrid: function () {
11200 var map = this._map,
11201 crs = map.options.crs,
11202 tileSize = this._tileSize = this.getTileSize(),
11203 tileZoom = this._tileZoom;
11204
11205 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11206 if (bounds) {
11207 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11208 }
11209
11210 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11211 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11212 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11213 ];
11214 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11215 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11216 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11217 ];
11218 },
11219
11220 _onMoveEnd: function () {
11221 if (!this._map || this._map._animatingZoom) { return; }
11222
11223 this._update();
11224 },
11225
11226 _getTiledPixelBounds: function (center) {
11227 var map = this._map,
11228 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11229 scale = map.getZoomScale(mapZoom, this._tileZoom),
11230 pixelCenter = map.project(center, this._tileZoom).floor(),
11231 halfSize = map.getSize().divideBy(scale * 2);
11232
11233 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11234 },
11235
11236 // Private method to load tiles in the grid's active zoom level according to map bounds
11237 _update: function (center) {
11238 var map = this._map;
11239 if (!map) { return; }
11240 var zoom = this._clampZoom(map.getZoom());
11241
11242 if (center === undefined) { center = map.getCenter(); }
11243 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11244
11245 var pixelBounds = this._getTiledPixelBounds(center),
11246 tileRange = this._pxBoundsToTileRange(pixelBounds),
11247 tileCenter = tileRange.getCenter(),
11248 queue = [],
11249 margin = this.options.keepBuffer,
11250 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11251 tileRange.getTopRight().add([margin, -margin]));
11252
11253 // Sanity check: panic if the tile range contains Infinity somewhere.
11254 if (!(isFinite(tileRange.min.x) &&
11255 isFinite(tileRange.min.y) &&
11256 isFinite(tileRange.max.x) &&
11257 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11258
11259 for (var key in this._tiles) {
11260 var c = this._tiles[key].coords;
11261 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11262 this._tiles[key].current = false;
11263 }
11264 }
11265
11266 // _update just loads more tiles. If the tile zoom level differs too much
11267 // from the map's, let _setView reset levels and prune old tiles.
11268 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11269
11270 // create a queue of coordinates to load tiles from
11271 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11272 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11273 var coords = new Point(i, j);
11274 coords.z = this._tileZoom;
11275
11276 if (!this._isValidTile(coords)) { continue; }
11277
11278 var tile = this._tiles[this._tileCoordsToKey(coords)];
11279 if (tile) {
11280 tile.current = true;
11281 } else {
11282 queue.push(coords);
11283 }
11284 }
11285 }
11286
11287 // sort tile queue to load tiles in order of their distance to center
11288 queue.sort(function (a, b) {
11289 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11290 });
11291
11292 if (queue.length !== 0) {
11293 // if it's the first batch of tiles to load
11294 if (!this._loading) {
11295 this._loading = true;
11296 // @event loading: Event
11297 // Fired when the grid layer starts loading tiles.
11298 this.fire('loading');
11299 }
11300
11301 // create DOM fragment to append tiles in one batch
11302 var fragment = document.createDocumentFragment();
11303
11304 for (i = 0; i < queue.length; i++) {
11305 this._addTile(queue[i], fragment);
11306 }
11307
11308 this._level.el.appendChild(fragment);
11309 }
11310 },
11311
11312 _isValidTile: function (coords) {
11313 var crs = this._map.options.crs;
11314
11315 if (!crs.infinite) {
11316 // don't load tile if it's out of bounds and not wrapped
11317 var bounds = this._globalTileRange;
11318 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11319 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11320 }
11321
11322 if (!this.options.bounds) { return true; }
11323
11324 // don't load tile if it doesn't intersect the bounds in options
11325 var tileBounds = this._tileCoordsToBounds(coords);
11326 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11327 },
11328
11329 _keyToBounds: function (key) {
11330 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11331 },
11332
11333 _tileCoordsToNwSe: function (coords) {
11334 var map = this._map,
11335 tileSize = this.getTileSize(),
11336 nwPoint = coords.scaleBy(tileSize),
11337 sePoint = nwPoint.add(tileSize),
11338 nw = map.unproject(nwPoint, coords.z),
11339 se = map.unproject(sePoint, coords.z);
11340 return [nw, se];
11341 },
11342
11343 // converts tile coordinates to its geographical bounds
11344 _tileCoordsToBounds: function (coords) {
11345 var bp = this._tileCoordsToNwSe(coords),
11346 bounds = new LatLngBounds(bp[0], bp[1]);
11347
11348 if (!this.options.noWrap) {
11349 bounds = this._map.wrapLatLngBounds(bounds);
11350 }
11351 return bounds;
11352 },
11353 // converts tile coordinates to key for the tile cache
11354 _tileCoordsToKey: function (coords) {
11355 return coords.x + ':' + coords.y + ':' + coords.z;
11356 },
11357
11358 // converts tile cache key to coordinates
11359 _keyToTileCoords: function (key) {
11360 var k = key.split(':'),
11361 coords = new Point(+k[0], +k[1]);
11362 coords.z = +k[2];
11363 return coords;
11364 },
11365
11366 _removeTile: function (key) {
11367 var tile = this._tiles[key];
11368 if (!tile) { return; }
11369
11370 remove(tile.el);
11371
11372 delete this._tiles[key];
11373
11374 // @event tileunload: TileEvent
11375 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11376 this.fire('tileunload', {
11377 tile: tile.el,
11378 coords: this._keyToTileCoords(key)
11379 });
11380 },
11381
11382 _initTile: function (tile) {
11383 addClass(tile, 'leaflet-tile');
11384
11385 var tileSize = this.getTileSize();
11386 tile.style.width = tileSize.x + 'px';
11387 tile.style.height = tileSize.y + 'px';
11388
11389 tile.onselectstart = falseFn;
11390 tile.onmousemove = falseFn;
11391
11392 // update opacity on tiles in IE7-8 because of filter inheritance problems
11393 if (ielt9 && this.options.opacity < 1) {
11394 setOpacity(tile, this.options.opacity);
11395 }
11396
11397 // without this hack, tiles disappear after zoom on Chrome for Android
11398 // https://github.com/Leaflet/Leaflet/issues/2078
11399 if (android && !android23) {
11400 tile.style.WebkitBackfaceVisibility = 'hidden';
11401 }
11402 },
11403
11404 _addTile: function (coords, container) {
11405 var tilePos = this._getTilePos(coords),
11406 key = this._tileCoordsToKey(coords);
11407
11408 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11409
11410 this._initTile(tile);
11411
11412 // if createTile is defined with a second argument ("done" callback),
11413 // we know that tile is async and will be ready later; otherwise
11414 if (this.createTile.length < 2) {
11415 // mark tile as ready, but delay one frame for opacity animation to happen
11416 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11417 }
11418
11419 setPosition(tile, tilePos);
11420
11421 // save tile in cache
11422 this._tiles[key] = {
11423 el: tile,
11424 coords: coords,
11425 current: true
11426 };
11427
11428 container.appendChild(tile);
11429 // @event tileloadstart: TileEvent
11430 // Fired when a tile is requested and starts loading.
11431 this.fire('tileloadstart', {
11432 tile: tile,
11433 coords: coords
11434 });
11435 },
11436
11437 _tileReady: function (coords, err, tile) {
11438 if (err) {
11439 // @event tileerror: TileErrorEvent
11440 // Fired when there is an error loading a tile.
11441 this.fire('tileerror', {
11442 error: err,
11443 tile: tile,
11444 coords: coords
11445 });
11446 }
11447
11448 var key = this._tileCoordsToKey(coords);
11449
11450 tile = this._tiles[key];
11451 if (!tile) { return; }
11452
11453 tile.loaded = +new Date();
11454 if (this._map._fadeAnimated) {
11455 setOpacity(tile.el, 0);
11456 cancelAnimFrame(this._fadeFrame);
11457 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11458 } else {
11459 tile.active = true;
11460 this._pruneTiles();
11461 }
11462
11463 if (!err) {
11464 addClass(tile.el, 'leaflet-tile-loaded');
11465
11466 // @event tileload: TileEvent
11467 // Fired when a tile loads.
11468 this.fire('tileload', {
11469 tile: tile.el,
11470 coords: coords
11471 });
11472 }
11473
11474 if (this._noTilesToLoad()) {
11475 this._loading = false;
11476 // @event load: Event
11477 // Fired when the grid layer loaded all visible tiles.
11478 this.fire('load');
11479
11480 if (ielt9 || !this._map._fadeAnimated) {
11481 requestAnimFrame(this._pruneTiles, this);
11482 } else {
11483 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11484 // to trigger a pruning.
11485 setTimeout(bind(this._pruneTiles, this), 250);
11486 }
11487 }
11488 },
11489
11490 _getTilePos: function (coords) {
11491 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11492 },
11493
11494 _wrapCoords: function (coords) {
11495 var newCoords = new Point(
11496 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11497 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11498 newCoords.z = coords.z;
11499 return newCoords;
11500 },
11501
11502 _pxBoundsToTileRange: function (bounds) {
11503 var tileSize = this.getTileSize();
11504 return new Bounds(
11505 bounds.min.unscaleBy(tileSize).floor(),
11506 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11507 },
11508
11509 _noTilesToLoad: function () {
11510 for (var key in this._tiles) {
11511 if (!this._tiles[key].loaded) { return false; }
11512 }
11513 return true;
11514 }
11515});
11516
11517// @factory L.gridLayer(options?: GridLayer options)
11518// Creates a new instance of GridLayer with the supplied options.
11519function gridLayer(options) {
11520 return new GridLayer(options);
11521}
11522
11523/*
11524 * @class TileLayer
11525 * @inherits GridLayer
11526 * @aka L.TileLayer
11527 * 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`.
11528 *
11529 * @example
11530 *
11531 * ```js
11532 * 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);
11533 * ```
11534 *
11535 * @section URL template
11536 * @example
11537 *
11538 * A string of the following form:
11539 *
11540 * ```
11541 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11542 * ```
11543 *
11544 * `{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.
11545 *
11546 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11547 *
11548 * ```
11549 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11550 * ```
11551 */
11552
11553
11554var TileLayer = GridLayer.extend({
11555
11556 // @section
11557 // @aka TileLayer options
11558 options: {
11559 // @option minZoom: Number = 0
11560 // The minimum zoom level down to which this layer will be displayed (inclusive).
11561 minZoom: 0,
11562
11563 // @option maxZoom: Number = 18
11564 // The maximum zoom level up to which this layer will be displayed (inclusive).
11565 maxZoom: 18,
11566
11567 // @option subdomains: String|String[] = 'abc'
11568 // 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.
11569 subdomains: 'abc',
11570
11571 // @option errorTileUrl: String = ''
11572 // URL to the tile image to show in place of the tile that failed to load.
11573 errorTileUrl: '',
11574
11575 // @option zoomOffset: Number = 0
11576 // The zoom number used in tile URLs will be offset with this value.
11577 zoomOffset: 0,
11578
11579 // @option tms: Boolean = false
11580 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11581 tms: false,
11582
11583 // @option zoomReverse: Boolean = false
11584 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11585 zoomReverse: false,
11586
11587 // @option detectRetina: Boolean = false
11588 // 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.
11589 detectRetina: false,
11590
11591 // @option crossOrigin: Boolean|String = false
11592 // Whether the crossOrigin attribute will be added to the tiles.
11593 // 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.
11594 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11595 crossOrigin: false
11596 },
11597
11598 initialize: function (url, options) {
11599
11600 this._url = url;
11601
11602 options = setOptions(this, options);
11603
11604 // detecting retina displays, adjusting tileSize and zoom levels
11605 if (options.detectRetina && retina && options.maxZoom > 0) {
11606
11607 options.tileSize = Math.floor(options.tileSize / 2);
11608
11609 if (!options.zoomReverse) {
11610 options.zoomOffset++;
11611 options.maxZoom--;
11612 } else {
11613 options.zoomOffset--;
11614 options.minZoom++;
11615 }
11616
11617 options.minZoom = Math.max(0, options.minZoom);
11618 }
11619
11620 if (typeof options.subdomains === 'string') {
11621 options.subdomains = options.subdomains.split('');
11622 }
11623
11624 // for https://github.com/Leaflet/Leaflet/issues/137
11625 if (!android) {
11626 this.on('tileunload', this._onTileRemove);
11627 }
11628 },
11629
11630 // @method setUrl(url: String, noRedraw?: Boolean): this
11631 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11632 // If the URL does not change, the layer will not be redrawn unless
11633 // the noRedraw parameter is set to false.
11634 setUrl: function (url, noRedraw) {
11635 if (this._url === url && noRedraw === undefined) {
11636 noRedraw = true;
11637 }
11638
11639 this._url = url;
11640
11641 if (!noRedraw) {
11642 this.redraw();
11643 }
11644 return this;
11645 },
11646
11647 // @method createTile(coords: Object, done?: Function): HTMLElement
11648 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11649 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11650 // callback is called when the tile has been loaded.
11651 createTile: function (coords, done) {
11652 var tile = document.createElement('img');
11653
11654 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11655 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11656
11657 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11658 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11659 }
11660
11661 /*
11662 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11663 http://www.w3.org/TR/WCAG20-TECHS/H67
11664 */
11665 tile.alt = '';
11666
11667 /*
11668 Set role="presentation" to force screen readers to ignore this
11669 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11670 */
11671 tile.setAttribute('role', 'presentation');
11672
11673 tile.src = this.getTileUrl(coords);
11674
11675 return tile;
11676 },
11677
11678 // @section Extension methods
11679 // @uninheritable
11680 // Layers extending `TileLayer` might reimplement the following method.
11681 // @method getTileUrl(coords: Object): String
11682 // Called only internally, returns the URL for a tile given its coordinates.
11683 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11684 getTileUrl: function (coords) {
11685 var data = {
11686 r: retina ? '@2x' : '',
11687 s: this._getSubdomain(coords),
11688 x: coords.x,
11689 y: coords.y,
11690 z: this._getZoomForUrl()
11691 };
11692 if (this._map && !this._map.options.crs.infinite) {
11693 var invertedY = this._globalTileRange.max.y - coords.y;
11694 if (this.options.tms) {
11695 data['y'] = invertedY;
11696 }
11697 data['-y'] = invertedY;
11698 }
11699
11700 return template(this._url, extend(data, this.options));
11701 },
11702
11703 _tileOnLoad: function (done, tile) {
11704 // For https://github.com/Leaflet/Leaflet/issues/3332
11705 if (ielt9) {
11706 setTimeout(bind(done, this, null, tile), 0);
11707 } else {
11708 done(null, tile);
11709 }
11710 },
11711
11712 _tileOnError: function (done, tile, e) {
11713 var errorUrl = this.options.errorTileUrl;
11714 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11715 tile.src = errorUrl;
11716 }
11717 done(e, tile);
11718 },
11719
11720 _onTileRemove: function (e) {
11721 e.tile.onload = null;
11722 },
11723
11724 _getZoomForUrl: function () {
11725 var zoom = this._tileZoom,
11726 maxZoom = this.options.maxZoom,
11727 zoomReverse = this.options.zoomReverse,
11728 zoomOffset = this.options.zoomOffset;
11729
11730 if (zoomReverse) {
11731 zoom = maxZoom - zoom;
11732 }
11733
11734 return zoom + zoomOffset;
11735 },
11736
11737 _getSubdomain: function (tilePoint) {
11738 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11739 return this.options.subdomains[index];
11740 },
11741
11742 // stops loading all tiles in the background layer
11743 _abortLoading: function () {
11744 var i, tile;
11745 for (i in this._tiles) {
11746 if (this._tiles[i].coords.z !== this._tileZoom) {
11747 tile = this._tiles[i].el;
11748
11749 tile.onload = falseFn;
11750 tile.onerror = falseFn;
11751
11752 if (!tile.complete) {
11753 tile.src = emptyImageUrl;
11754 remove(tile);
11755 delete this._tiles[i];
11756 }
11757 }
11758 }
11759 },
11760
11761 _removeTile: function (key) {
11762 var tile = this._tiles[key];
11763 if (!tile) { return; }
11764
11765 // Cancels any pending http requests associated with the tile
11766 // unless we're on Android's stock browser,
11767 // see https://github.com/Leaflet/Leaflet/issues/137
11768 if (!androidStock) {
11769 tile.el.setAttribute('src', emptyImageUrl);
11770 }
11771
11772 return GridLayer.prototype._removeTile.call(this, key);
11773 },
11774
11775 _tileReady: function (coords, err, tile) {
11776 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11777 return;
11778 }
11779
11780 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11781 }
11782});
11783
11784
11785// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11786// Instantiates a tile layer object given a `URL template` and optionally an options object.
11787
11788function tileLayer(url, options) {
11789 return new TileLayer(url, options);
11790}
11791
11792/*
11793 * @class TileLayer.WMS
11794 * @inherits TileLayer
11795 * @aka L.TileLayer.WMS
11796 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11797 *
11798 * @example
11799 *
11800 * ```js
11801 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11802 * layers: 'nexrad-n0r-900913',
11803 * format: 'image/png',
11804 * transparent: true,
11805 * attribution: "Weather data © 2012 IEM Nexrad"
11806 * });
11807 * ```
11808 */
11809
11810var TileLayerWMS = TileLayer.extend({
11811
11812 // @section
11813 // @aka TileLayer.WMS options
11814 // If any custom options not documented here are used, they will be sent to the
11815 // WMS server as extra parameters in each request URL. This can be useful for
11816 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11817 defaultWmsParams: {
11818 service: 'WMS',
11819 request: 'GetMap',
11820
11821 // @option layers: String = ''
11822 // **(required)** Comma-separated list of WMS layers to show.
11823 layers: '',
11824
11825 // @option styles: String = ''
11826 // Comma-separated list of WMS styles.
11827 styles: '',
11828
11829 // @option format: String = 'image/jpeg'
11830 // WMS image format (use `'image/png'` for layers with transparency).
11831 format: 'image/jpeg',
11832
11833 // @option transparent: Boolean = false
11834 // If `true`, the WMS service will return images with transparency.
11835 transparent: false,
11836
11837 // @option version: String = '1.1.1'
11838 // Version of the WMS service to use
11839 version: '1.1.1'
11840 },
11841
11842 options: {
11843 // @option crs: CRS = null
11844 // Coordinate Reference System to use for the WMS requests, defaults to
11845 // map CRS. Don't change this if you're not sure what it means.
11846 crs: null,
11847
11848 // @option uppercase: Boolean = false
11849 // If `true`, WMS request parameter keys will be uppercase.
11850 uppercase: false
11851 },
11852
11853 initialize: function (url, options) {
11854
11855 this._url = url;
11856
11857 var wmsParams = extend({}, this.defaultWmsParams);
11858
11859 // all keys that are not TileLayer options go to WMS params
11860 for (var i in options) {
11861 if (!(i in this.options)) {
11862 wmsParams[i] = options[i];
11863 }
11864 }
11865
11866 options = setOptions(this, options);
11867
11868 var realRetina = options.detectRetina && retina ? 2 : 1;
11869 var tileSize = this.getTileSize();
11870 wmsParams.width = tileSize.x * realRetina;
11871 wmsParams.height = tileSize.y * realRetina;
11872
11873 this.wmsParams = wmsParams;
11874 },
11875
11876 onAdd: function (map) {
11877
11878 this._crs = this.options.crs || map.options.crs;
11879 this._wmsVersion = parseFloat(this.wmsParams.version);
11880
11881 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11882 this.wmsParams[projectionKey] = this._crs.code;
11883
11884 TileLayer.prototype.onAdd.call(this, map);
11885 },
11886
11887 getTileUrl: function (coords) {
11888
11889 var tileBounds = this._tileCoordsToNwSe(coords),
11890 crs = this._crs,
11891 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11892 min = bounds.min,
11893 max = bounds.max,
11894 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11895 [min.y, min.x, max.y, max.x] :
11896 [min.x, min.y, max.x, max.y]).join(','),
11897 url = TileLayer.prototype.getTileUrl.call(this, coords);
11898 return url +
11899 getParamString(this.wmsParams, url, this.options.uppercase) +
11900 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11901 },
11902
11903 // @method setParams(params: Object, noRedraw?: Boolean): this
11904 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11905 setParams: function (params, noRedraw) {
11906
11907 extend(this.wmsParams, params);
11908
11909 if (!noRedraw) {
11910 this.redraw();
11911 }
11912
11913 return this;
11914 }
11915});
11916
11917
11918// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11919// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11920function tileLayerWMS(url, options) {
11921 return new TileLayerWMS(url, options);
11922}
11923
11924TileLayer.WMS = TileLayerWMS;
11925tileLayer.wms = tileLayerWMS;
11926
11927/*
11928 * @class Renderer
11929 * @inherits Layer
11930 * @aka L.Renderer
11931 *
11932 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11933 * DOM container of the renderer, its bounds, and its zoom animation.
11934 *
11935 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11936 * itself can be added or removed to the map. All paths use a renderer, which can
11937 * be implicit (the map will decide the type of renderer and use it automatically)
11938 * or explicit (using the [`renderer`](#path-renderer) option of the path).
11939 *
11940 * Do not use this class directly, use `SVG` and `Canvas` instead.
11941 *
11942 * @event update: Event
11943 * Fired when the renderer updates its bounds, center and zoom, for example when
11944 * its map has moved
11945 */
11946
11947var Renderer = Layer.extend({
11948
11949 // @section
11950 // @aka Renderer options
11951 options: {
11952 // @option padding: Number = 0.1
11953 // How much to extend the clip area around the map view (relative to its size)
11954 // e.g. 0.1 would be 10% of map view in each direction
11955 padding: 0.1,
11956
11957 // @option tolerance: Number = 0
11958 // How much to extend click tolerance round a path/object on the map
11959 tolerance : 0
11960 },
11961
11962 initialize: function (options) {
11963 setOptions(this, options);
11964 stamp(this);
11965 this._layers = this._layers || {};
11966 },
11967
11968 onAdd: function () {
11969 if (!this._container) {
11970 this._initContainer(); // defined by renderer implementations
11971
11972 if (this._zoomAnimated) {
11973 addClass(this._container, 'leaflet-zoom-animated');
11974 }
11975 }
11976
11977 this.getPane().appendChild(this._container);
11978 this._update();
11979 this.on('update', this._updatePaths, this);
11980 },
11981
11982 onRemove: function () {
11983 this.off('update', this._updatePaths, this);
11984 this._destroyContainer();
11985 },
11986
11987 getEvents: function () {
11988 var events = {
11989 viewreset: this._reset,
11990 zoom: this._onZoom,
11991 moveend: this._update,
11992 zoomend: this._onZoomEnd
11993 };
11994 if (this._zoomAnimated) {
11995 events.zoomanim = this._onAnimZoom;
11996 }
11997 return events;
11998 },
11999
12000 _onAnimZoom: function (ev) {
12001 this._updateTransform(ev.center, ev.zoom);
12002 },
12003
12004 _onZoom: function () {
12005 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12006 },
12007
12008 _updateTransform: function (center, zoom) {
12009 var scale = this._map.getZoomScale(zoom, this._zoom),
12010 position = getPosition(this._container),
12011 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12012 currentCenterPoint = this._map.project(this._center, zoom),
12013 destCenterPoint = this._map.project(center, zoom),
12014 centerOffset = destCenterPoint.subtract(currentCenterPoint),
12015
12016 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12017
12018 if (any3d) {
12019 setTransform(this._container, topLeftOffset, scale);
12020 } else {
12021 setPosition(this._container, topLeftOffset);
12022 }
12023 },
12024
12025 _reset: function () {
12026 this._update();
12027 this._updateTransform(this._center, this._zoom);
12028
12029 for (var id in this._layers) {
12030 this._layers[id]._reset();
12031 }
12032 },
12033
12034 _onZoomEnd: function () {
12035 for (var id in this._layers) {
12036 this._layers[id]._project();
12037 }
12038 },
12039
12040 _updatePaths: function () {
12041 for (var id in this._layers) {
12042 this._layers[id]._update();
12043 }
12044 },
12045
12046 _update: function () {
12047 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12048 // Subclasses are responsible of firing the 'update' event.
12049 var p = this.options.padding,
12050 size = this._map.getSize(),
12051 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12052
12053 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12054
12055 this._center = this._map.getCenter();
12056 this._zoom = this._map.getZoom();
12057 }
12058});
12059
12060/*
12061 * @class Canvas
12062 * @inherits Renderer
12063 * @aka L.Canvas
12064 *
12065 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12066 * Inherits `Renderer`.
12067 *
12068 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12069 * available in all web browsers, notably IE8, and overlapping geometries might
12070 * not display properly in some edge cases.
12071 *
12072 * @example
12073 *
12074 * Use Canvas by default for all paths in the map:
12075 *
12076 * ```js
12077 * var map = L.map('map', {
12078 * renderer: L.canvas()
12079 * });
12080 * ```
12081 *
12082 * Use a Canvas renderer with extra padding for specific vector geometries:
12083 *
12084 * ```js
12085 * var map = L.map('map');
12086 * var myRenderer = L.canvas({ padding: 0.5 });
12087 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12088 * var circle = L.circle( center, { renderer: myRenderer } );
12089 * ```
12090 */
12091
12092var Canvas = Renderer.extend({
12093 getEvents: function () {
12094 var events = Renderer.prototype.getEvents.call(this);
12095 events.viewprereset = this._onViewPreReset;
12096 return events;
12097 },
12098
12099 _onViewPreReset: function () {
12100 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12101 this._postponeUpdatePaths = true;
12102 },
12103
12104 onAdd: function () {
12105 Renderer.prototype.onAdd.call(this);
12106
12107 // Redraw vectors since canvas is cleared upon removal,
12108 // in case of removing the renderer itself from the map.
12109 this._draw();
12110 },
12111
12112 _initContainer: function () {
12113 var container = this._container = document.createElement('canvas');
12114
12115 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
12116 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12117 on(container, 'mouseout', this._handleMouseOut, this);
12118
12119 this._ctx = container.getContext('2d');
12120 },
12121
12122 _destroyContainer: function () {
12123 cancelAnimFrame(this._redrawRequest);
12124 delete this._ctx;
12125 remove(this._container);
12126 off(this._container);
12127 delete this._container;
12128 },
12129
12130 _updatePaths: function () {
12131 if (this._postponeUpdatePaths) { return; }
12132
12133 var layer;
12134 this._redrawBounds = null;
12135 for (var id in this._layers) {
12136 layer = this._layers[id];
12137 layer._update();
12138 }
12139 this._redraw();
12140 },
12141
12142 _update: function () {
12143 if (this._map._animatingZoom && this._bounds) { return; }
12144
12145 Renderer.prototype._update.call(this);
12146
12147 var b = this._bounds,
12148 container = this._container,
12149 size = b.getSize(),
12150 m = retina ? 2 : 1;
12151
12152 setPosition(container, b.min);
12153
12154 // set canvas size (also clearing it); use double size on retina
12155 container.width = m * size.x;
12156 container.height = m * size.y;
12157 container.style.width = size.x + 'px';
12158 container.style.height = size.y + 'px';
12159
12160 if (retina) {
12161 this._ctx.scale(2, 2);
12162 }
12163
12164 // translate so we use the same path coordinates after canvas element moves
12165 this._ctx.translate(-b.min.x, -b.min.y);
12166
12167 // Tell paths to redraw themselves
12168 this.fire('update');
12169 },
12170
12171 _reset: function () {
12172 Renderer.prototype._reset.call(this);
12173
12174 if (this._postponeUpdatePaths) {
12175 this._postponeUpdatePaths = false;
12176 this._updatePaths();
12177 }
12178 },
12179
12180 _initPath: function (layer) {
12181 this._updateDashArray(layer);
12182 this._layers[stamp(layer)] = layer;
12183
12184 var order = layer._order = {
12185 layer: layer,
12186 prev: this._drawLast,
12187 next: null
12188 };
12189 if (this._drawLast) { this._drawLast.next = order; }
12190 this._drawLast = order;
12191 this._drawFirst = this._drawFirst || this._drawLast;
12192 },
12193
12194 _addPath: function (layer) {
12195 this._requestRedraw(layer);
12196 },
12197
12198 _removePath: function (layer) {
12199 var order = layer._order;
12200 var next = order.next;
12201 var prev = order.prev;
12202
12203 if (next) {
12204 next.prev = prev;
12205 } else {
12206 this._drawLast = prev;
12207 }
12208 if (prev) {
12209 prev.next = next;
12210 } else {
12211 this._drawFirst = next;
12212 }
12213
12214 delete layer._order;
12215
12216 delete this._layers[stamp(layer)];
12217
12218 this._requestRedraw(layer);
12219 },
12220
12221 _updatePath: function (layer) {
12222 // Redraw the union of the layer's old pixel
12223 // bounds and the new pixel bounds.
12224 this._extendRedrawBounds(layer);
12225 layer._project();
12226 layer._update();
12227 // The redraw will extend the redraw bounds
12228 // with the new pixel bounds.
12229 this._requestRedraw(layer);
12230 },
12231
12232 _updateStyle: function (layer) {
12233 this._updateDashArray(layer);
12234 this._requestRedraw(layer);
12235 },
12236
12237 _updateDashArray: function (layer) {
12238 if (typeof layer.options.dashArray === 'string') {
12239 var parts = layer.options.dashArray.split(/[, ]+/),
12240 dashArray = [],
12241 dashValue,
12242 i;
12243 for (i = 0; i < parts.length; i++) {
12244 dashValue = Number(parts[i]);
12245 // Ignore dash array containing invalid lengths
12246 if (isNaN(dashValue)) { return; }
12247 dashArray.push(dashValue);
12248 }
12249 layer.options._dashArray = dashArray;
12250 } else {
12251 layer.options._dashArray = layer.options.dashArray;
12252 }
12253 },
12254
12255 _requestRedraw: function (layer) {
12256 if (!this._map) { return; }
12257
12258 this._extendRedrawBounds(layer);
12259 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12260 },
12261
12262 _extendRedrawBounds: function (layer) {
12263 if (layer._pxBounds) {
12264 var padding = (layer.options.weight || 0) + 1;
12265 this._redrawBounds = this._redrawBounds || new Bounds();
12266 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12267 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12268 }
12269 },
12270
12271 _redraw: function () {
12272 this._redrawRequest = null;
12273
12274 if (this._redrawBounds) {
12275 this._redrawBounds.min._floor();
12276 this._redrawBounds.max._ceil();
12277 }
12278
12279 this._clear(); // clear layers in redraw bounds
12280 this._draw(); // draw layers
12281
12282 this._redrawBounds = null;
12283 },
12284
12285 _clear: function () {
12286 var bounds = this._redrawBounds;
12287 if (bounds) {
12288 var size = bounds.getSize();
12289 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12290 } else {
12291 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12292 }
12293 },
12294
12295 _draw: function () {
12296 var layer, bounds = this._redrawBounds;
12297 this._ctx.save();
12298 if (bounds) {
12299 var size = bounds.getSize();
12300 this._ctx.beginPath();
12301 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12302 this._ctx.clip();
12303 }
12304
12305 this._drawing = true;
12306
12307 for (var order = this._drawFirst; order; order = order.next) {
12308 layer = order.layer;
12309 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12310 layer._updatePath();
12311 }
12312 }
12313
12314 this._drawing = false;
12315
12316 this._ctx.restore(); // Restore state before clipping.
12317 },
12318
12319 _updatePoly: function (layer, closed) {
12320 if (!this._drawing) { return; }
12321
12322 var i, j, len2, p,
12323 parts = layer._parts,
12324 len = parts.length,
12325 ctx = this._ctx;
12326
12327 if (!len) { return; }
12328
12329 ctx.beginPath();
12330
12331 for (i = 0; i < len; i++) {
12332 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12333 p = parts[i][j];
12334 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12335 }
12336 if (closed) {
12337 ctx.closePath();
12338 }
12339 }
12340
12341 this._fillStroke(ctx, layer);
12342
12343 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12344 },
12345
12346 _updateCircle: function (layer) {
12347
12348 if (!this._drawing || layer._empty()) { return; }
12349
12350 var p = layer._point,
12351 ctx = this._ctx,
12352 r = Math.max(Math.round(layer._radius), 1),
12353 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12354
12355 if (s !== 1) {
12356 ctx.save();
12357 ctx.scale(1, s);
12358 }
12359
12360 ctx.beginPath();
12361 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12362
12363 if (s !== 1) {
12364 ctx.restore();
12365 }
12366
12367 this._fillStroke(ctx, layer);
12368 },
12369
12370 _fillStroke: function (ctx, layer) {
12371 var options = layer.options;
12372
12373 if (options.fill) {
12374 ctx.globalAlpha = options.fillOpacity;
12375 ctx.fillStyle = options.fillColor || options.color;
12376 ctx.fill(options.fillRule || 'evenodd');
12377 }
12378
12379 if (options.stroke && options.weight !== 0) {
12380 if (ctx.setLineDash) {
12381 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12382 }
12383 ctx.globalAlpha = options.opacity;
12384 ctx.lineWidth = options.weight;
12385 ctx.strokeStyle = options.color;
12386 ctx.lineCap = options.lineCap;
12387 ctx.lineJoin = options.lineJoin;
12388 ctx.stroke();
12389 }
12390 },
12391
12392 // Canvas obviously doesn't have mouse events for individual drawn objects,
12393 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12394
12395 _onClick: function (e) {
12396 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12397
12398 for (var order = this._drawFirst; order; order = order.next) {
12399 layer = order.layer;
12400 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12401 clickedLayer = layer;
12402 }
12403 }
12404 if (clickedLayer) {
12405 fakeStop(e);
12406 this._fireEvent([clickedLayer], e);
12407 }
12408 },
12409
12410 _onMouseMove: function (e) {
12411 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12412
12413 var point = this._map.mouseEventToLayerPoint(e);
12414 this._handleMouseHover(e, point);
12415 },
12416
12417
12418 _handleMouseOut: function (e) {
12419 var layer = this._hoveredLayer;
12420 if (layer) {
12421 // if we're leaving the layer, fire mouseout
12422 removeClass(this._container, 'leaflet-interactive');
12423 this._fireEvent([layer], e, 'mouseout');
12424 this._hoveredLayer = null;
12425 }
12426 },
12427
12428 _handleMouseHover: function (e, point) {
12429 var layer, candidateHoveredLayer;
12430
12431 for (var order = this._drawFirst; order; order = order.next) {
12432 layer = order.layer;
12433 if (layer.options.interactive && layer._containsPoint(point)) {
12434 candidateHoveredLayer = layer;
12435 }
12436 }
12437
12438 if (candidateHoveredLayer !== this._hoveredLayer) {
12439 this._handleMouseOut(e);
12440
12441 if (candidateHoveredLayer) {
12442 addClass(this._container, 'leaflet-interactive'); // change cursor
12443 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12444 this._hoveredLayer = candidateHoveredLayer;
12445 }
12446 }
12447
12448 if (this._hoveredLayer) {
12449 this._fireEvent([this._hoveredLayer], e);
12450 }
12451 },
12452
12453 _fireEvent: function (layers, e, type) {
12454 this._map._fireDOMEvent(e, type || e.type, layers);
12455 },
12456
12457 _bringToFront: function (layer) {
12458 var order = layer._order;
12459
12460 if (!order) { return; }
12461
12462 var next = order.next;
12463 var prev = order.prev;
12464
12465 if (next) {
12466 next.prev = prev;
12467 } else {
12468 // Already last
12469 return;
12470 }
12471 if (prev) {
12472 prev.next = next;
12473 } else if (next) {
12474 // Update first entry unless this is the
12475 // single entry
12476 this._drawFirst = next;
12477 }
12478
12479 order.prev = this._drawLast;
12480 this._drawLast.next = order;
12481
12482 order.next = null;
12483 this._drawLast = order;
12484
12485 this._requestRedraw(layer);
12486 },
12487
12488 _bringToBack: function (layer) {
12489 var order = layer._order;
12490
12491 if (!order) { return; }
12492
12493 var next = order.next;
12494 var prev = order.prev;
12495
12496 if (prev) {
12497 prev.next = next;
12498 } else {
12499 // Already first
12500 return;
12501 }
12502 if (next) {
12503 next.prev = prev;
12504 } else if (prev) {
12505 // Update last entry unless this is the
12506 // single entry
12507 this._drawLast = prev;
12508 }
12509
12510 order.prev = null;
12511
12512 order.next = this._drawFirst;
12513 this._drawFirst.prev = order;
12514 this._drawFirst = order;
12515
12516 this._requestRedraw(layer);
12517 }
12518});
12519
12520// @factory L.canvas(options?: Renderer options)
12521// Creates a Canvas renderer with the given options.
12522function canvas$1(options) {
12523 return canvas ? new Canvas(options) : null;
12524}
12525
12526/*
12527 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12528 */
12529
12530
12531var vmlCreate = (function () {
12532 try {
12533 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12534 return function (name) {
12535 return document.createElement('<lvml:' + name + ' class="lvml">');
12536 };
12537 } catch (e) {
12538 return function (name) {
12539 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12540 };
12541 }
12542})();
12543
12544
12545/*
12546 * @class SVG
12547 *
12548 *
12549 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12550 * with old versions of Internet Explorer.
12551 */
12552
12553// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12554var vmlMixin = {
12555
12556 _initContainer: function () {
12557 this._container = create$1('div', 'leaflet-vml-container');
12558 },
12559
12560 _update: function () {
12561 if (this._map._animatingZoom) { return; }
12562 Renderer.prototype._update.call(this);
12563 this.fire('update');
12564 },
12565
12566 _initPath: function (layer) {
12567 var container = layer._container = vmlCreate('shape');
12568
12569 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12570
12571 container.coordsize = '1 1';
12572
12573 layer._path = vmlCreate('path');
12574 container.appendChild(layer._path);
12575
12576 this._updateStyle(layer);
12577 this._layers[stamp(layer)] = layer;
12578 },
12579
12580 _addPath: function (layer) {
12581 var container = layer._container;
12582 this._container.appendChild(container);
12583
12584 if (layer.options.interactive) {
12585 layer.addInteractiveTarget(container);
12586 }
12587 },
12588
12589 _removePath: function (layer) {
12590 var container = layer._container;
12591 remove(container);
12592 layer.removeInteractiveTarget(container);
12593 delete this._layers[stamp(layer)];
12594 },
12595
12596 _updateStyle: function (layer) {
12597 var stroke = layer._stroke,
12598 fill = layer._fill,
12599 options = layer.options,
12600 container = layer._container;
12601
12602 container.stroked = !!options.stroke;
12603 container.filled = !!options.fill;
12604
12605 if (options.stroke) {
12606 if (!stroke) {
12607 stroke = layer._stroke = vmlCreate('stroke');
12608 }
12609 container.appendChild(stroke);
12610 stroke.weight = options.weight + 'px';
12611 stroke.color = options.color;
12612 stroke.opacity = options.opacity;
12613
12614 if (options.dashArray) {
12615 stroke.dashStyle = isArray(options.dashArray) ?
12616 options.dashArray.join(' ') :
12617 options.dashArray.replace(/( *, *)/g, ' ');
12618 } else {
12619 stroke.dashStyle = '';
12620 }
12621 stroke.endcap = options.lineCap.replace('butt', 'flat');
12622 stroke.joinstyle = options.lineJoin;
12623
12624 } else if (stroke) {
12625 container.removeChild(stroke);
12626 layer._stroke = null;
12627 }
12628
12629 if (options.fill) {
12630 if (!fill) {
12631 fill = layer._fill = vmlCreate('fill');
12632 }
12633 container.appendChild(fill);
12634 fill.color = options.fillColor || options.color;
12635 fill.opacity = options.fillOpacity;
12636
12637 } else if (fill) {
12638 container.removeChild(fill);
12639 layer._fill = null;
12640 }
12641 },
12642
12643 _updateCircle: function (layer) {
12644 var p = layer._point.round(),
12645 r = Math.round(layer._radius),
12646 r2 = Math.round(layer._radiusY || r);
12647
12648 this._setPath(layer, layer._empty() ? 'M0 0' :
12649 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12650 },
12651
12652 _setPath: function (layer, path) {
12653 layer._path.v = path;
12654 },
12655
12656 _bringToFront: function (layer) {
12657 toFront(layer._container);
12658 },
12659
12660 _bringToBack: function (layer) {
12661 toBack(layer._container);
12662 }
12663};
12664
12665var create$2 = vml ? vmlCreate : svgCreate;
12666
12667/*
12668 * @class SVG
12669 * @inherits Renderer
12670 * @aka L.SVG
12671 *
12672 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12673 * Inherits `Renderer`.
12674 *
12675 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12676 * available in all web browsers, notably Android 2.x and 3.x.
12677 *
12678 * Although SVG is not available on IE7 and IE8, these browsers support
12679 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12680 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12681 * this case.
12682 *
12683 * @example
12684 *
12685 * Use SVG by default for all paths in the map:
12686 *
12687 * ```js
12688 * var map = L.map('map', {
12689 * renderer: L.svg()
12690 * });
12691 * ```
12692 *
12693 * Use a SVG renderer with extra padding for specific vector geometries:
12694 *
12695 * ```js
12696 * var map = L.map('map');
12697 * var myRenderer = L.svg({ padding: 0.5 });
12698 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12699 * var circle = L.circle( center, { renderer: myRenderer } );
12700 * ```
12701 */
12702
12703var SVG = Renderer.extend({
12704
12705 getEvents: function () {
12706 var events = Renderer.prototype.getEvents.call(this);
12707 events.zoomstart = this._onZoomStart;
12708 return events;
12709 },
12710
12711 _initContainer: function () {
12712 this._container = create$2('svg');
12713
12714 // makes it possible to click through svg root; we'll reset it back in individual paths
12715 this._container.setAttribute('pointer-events', 'none');
12716
12717 this._rootGroup = create$2('g');
12718 this._container.appendChild(this._rootGroup);
12719 },
12720
12721 _destroyContainer: function () {
12722 remove(this._container);
12723 off(this._container);
12724 delete this._container;
12725 delete this._rootGroup;
12726 delete this._svgSize;
12727 },
12728
12729 _onZoomStart: function () {
12730 // Drag-then-pinch interactions might mess up the center and zoom.
12731 // In this case, the easiest way to prevent this is re-do the renderer
12732 // bounds and padding when the zooming starts.
12733 this._update();
12734 },
12735
12736 _update: function () {
12737 if (this._map._animatingZoom && this._bounds) { return; }
12738
12739 Renderer.prototype._update.call(this);
12740
12741 var b = this._bounds,
12742 size = b.getSize(),
12743 container = this._container;
12744
12745 // set size of svg-container if changed
12746 if (!this._svgSize || !this._svgSize.equals(size)) {
12747 this._svgSize = size;
12748 container.setAttribute('width', size.x);
12749 container.setAttribute('height', size.y);
12750 }
12751
12752 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12753 setPosition(container, b.min);
12754 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12755
12756 this.fire('update');
12757 },
12758
12759 // methods below are called by vector layers implementations
12760
12761 _initPath: function (layer) {
12762 var path = layer._path = create$2('path');
12763
12764 // @namespace Path
12765 // @option className: String = null
12766 // Custom class name set on an element. Only for SVG renderer.
12767 if (layer.options.className) {
12768 addClass(path, layer.options.className);
12769 }
12770
12771 if (layer.options.interactive) {
12772 addClass(path, 'leaflet-interactive');
12773 }
12774
12775 this._updateStyle(layer);
12776 this._layers[stamp(layer)] = layer;
12777 },
12778
12779 _addPath: function (layer) {
12780 if (!this._rootGroup) { this._initContainer(); }
12781 this._rootGroup.appendChild(layer._path);
12782 layer.addInteractiveTarget(layer._path);
12783 },
12784
12785 _removePath: function (layer) {
12786 remove(layer._path);
12787 layer.removeInteractiveTarget(layer._path);
12788 delete this._layers[stamp(layer)];
12789 },
12790
12791 _updatePath: function (layer) {
12792 layer._project();
12793 layer._update();
12794 },
12795
12796 _updateStyle: function (layer) {
12797 var path = layer._path,
12798 options = layer.options;
12799
12800 if (!path) { return; }
12801
12802 if (options.stroke) {
12803 path.setAttribute('stroke', options.color);
12804 path.setAttribute('stroke-opacity', options.opacity);
12805 path.setAttribute('stroke-width', options.weight);
12806 path.setAttribute('stroke-linecap', options.lineCap);
12807 path.setAttribute('stroke-linejoin', options.lineJoin);
12808
12809 if (options.dashArray) {
12810 path.setAttribute('stroke-dasharray', options.dashArray);
12811 } else {
12812 path.removeAttribute('stroke-dasharray');
12813 }
12814
12815 if (options.dashOffset) {
12816 path.setAttribute('stroke-dashoffset', options.dashOffset);
12817 } else {
12818 path.removeAttribute('stroke-dashoffset');
12819 }
12820 } else {
12821 path.setAttribute('stroke', 'none');
12822 }
12823
12824 if (options.fill) {
12825 path.setAttribute('fill', options.fillColor || options.color);
12826 path.setAttribute('fill-opacity', options.fillOpacity);
12827 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12828 } else {
12829 path.setAttribute('fill', 'none');
12830 }
12831 },
12832
12833 _updatePoly: function (layer, closed) {
12834 this._setPath(layer, pointsToPath(layer._parts, closed));
12835 },
12836
12837 _updateCircle: function (layer) {
12838 var p = layer._point,
12839 r = Math.max(Math.round(layer._radius), 1),
12840 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12841 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12842
12843 // drawing a circle with two half-arcs
12844 var d = layer._empty() ? 'M0 0' :
12845 'M' + (p.x - r) + ',' + p.y +
12846 arc + (r * 2) + ',0 ' +
12847 arc + (-r * 2) + ',0 ';
12848
12849 this._setPath(layer, d);
12850 },
12851
12852 _setPath: function (layer, path) {
12853 layer._path.setAttribute('d', path);
12854 },
12855
12856 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12857 _bringToFront: function (layer) {
12858 toFront(layer._path);
12859 },
12860
12861 _bringToBack: function (layer) {
12862 toBack(layer._path);
12863 }
12864});
12865
12866if (vml) {
12867 SVG.include(vmlMixin);
12868}
12869
12870// @namespace SVG
12871// @factory L.svg(options?: Renderer options)
12872// Creates a SVG renderer with the given options.
12873function svg$1(options) {
12874 return svg || vml ? new SVG(options) : null;
12875}
12876
12877Map.include({
12878 // @namespace Map; @method getRenderer(layer: Path): Renderer
12879 // Returns the instance of `Renderer` that should be used to render the given
12880 // `Path`. It will ensure that the `renderer` options of the map and paths
12881 // are respected, and that the renderers do exist on the map.
12882 getRenderer: function (layer) {
12883 // @namespace Path; @option renderer: Renderer
12884 // Use this specific instance of `Renderer` for this path. Takes
12885 // precedence over the map's [default renderer](#map-renderer).
12886 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12887
12888 if (!renderer) {
12889 renderer = this._renderer = this._createRenderer();
12890 }
12891
12892 if (!this.hasLayer(renderer)) {
12893 this.addLayer(renderer);
12894 }
12895 return renderer;
12896 },
12897
12898 _getPaneRenderer: function (name) {
12899 if (name === 'overlayPane' || name === undefined) {
12900 return false;
12901 }
12902
12903 var renderer = this._paneRenderers[name];
12904 if (renderer === undefined) {
12905 renderer = this._createRenderer({pane: name});
12906 this._paneRenderers[name] = renderer;
12907 }
12908 return renderer;
12909 },
12910
12911 _createRenderer: function (options) {
12912 // @namespace Map; @option preferCanvas: Boolean = false
12913 // Whether `Path`s should be rendered on a `Canvas` renderer.
12914 // By default, all `Path`s are rendered in a `SVG` renderer.
12915 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12916 }
12917});
12918
12919/*
12920 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12921 */
12922
12923/*
12924 * @class Rectangle
12925 * @aka L.Rectangle
12926 * @inherits Polygon
12927 *
12928 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12929 *
12930 * @example
12931 *
12932 * ```js
12933 * // define rectangle geographical bounds
12934 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12935 *
12936 * // create an orange rectangle
12937 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12938 *
12939 * // zoom the map to the rectangle bounds
12940 * map.fitBounds(bounds);
12941 * ```
12942 *
12943 */
12944
12945
12946var Rectangle = Polygon.extend({
12947 initialize: function (latLngBounds, options) {
12948 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12949 },
12950
12951 // @method setBounds(latLngBounds: LatLngBounds): this
12952 // Redraws the rectangle with the passed bounds.
12953 setBounds: function (latLngBounds) {
12954 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12955 },
12956
12957 _boundsToLatLngs: function (latLngBounds) {
12958 latLngBounds = toLatLngBounds(latLngBounds);
12959 return [
12960 latLngBounds.getSouthWest(),
12961 latLngBounds.getNorthWest(),
12962 latLngBounds.getNorthEast(),
12963 latLngBounds.getSouthEast()
12964 ];
12965 }
12966});
12967
12968
12969// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12970function rectangle(latLngBounds, options) {
12971 return new Rectangle(latLngBounds, options);
12972}
12973
12974SVG.create = create$2;
12975SVG.pointsToPath = pointsToPath;
12976
12977GeoJSON.geometryToLayer = geometryToLayer;
12978GeoJSON.coordsToLatLng = coordsToLatLng;
12979GeoJSON.coordsToLatLngs = coordsToLatLngs;
12980GeoJSON.latLngToCoords = latLngToCoords;
12981GeoJSON.latLngsToCoords = latLngsToCoords;
12982GeoJSON.getFeature = getFeature;
12983GeoJSON.asFeature = asFeature;
12984
12985/*
12986 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12987 * (zoom to a selected bounding box), enabled by default.
12988 */
12989
12990// @namespace Map
12991// @section Interaction Options
12992Map.mergeOptions({
12993 // @option boxZoom: Boolean = true
12994 // Whether the map can be zoomed to a rectangular area specified by
12995 // dragging the mouse while pressing the shift key.
12996 boxZoom: true
12997});
12998
12999var BoxZoom = Handler.extend({
13000 initialize: function (map) {
13001 this._map = map;
13002 this._container = map._container;
13003 this._pane = map._panes.overlayPane;
13004 this._resetStateTimeout = 0;
13005 map.on('unload', this._destroy, this);
13006 },
13007
13008 addHooks: function () {
13009 on(this._container, 'mousedown', this._onMouseDown, this);
13010 },
13011
13012 removeHooks: function () {
13013 off(this._container, 'mousedown', this._onMouseDown, this);
13014 },
13015
13016 moved: function () {
13017 return this._moved;
13018 },
13019
13020 _destroy: function () {
13021 remove(this._pane);
13022 delete this._pane;
13023 },
13024
13025 _resetState: function () {
13026 this._resetStateTimeout = 0;
13027 this._moved = false;
13028 },
13029
13030 _clearDeferredResetState: function () {
13031 if (this._resetStateTimeout !== 0) {
13032 clearTimeout(this._resetStateTimeout);
13033 this._resetStateTimeout = 0;
13034 }
13035 },
13036
13037 _onMouseDown: function (e) {
13038 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13039
13040 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13041 // will interrupt the interaction and orphan a box element in the container.
13042 this._clearDeferredResetState();
13043 this._resetState();
13044
13045 disableTextSelection();
13046 disableImageDrag();
13047
13048 this._startPoint = this._map.mouseEventToContainerPoint(e);
13049
13050 on(document, {
13051 contextmenu: stop,
13052 mousemove: this._onMouseMove,
13053 mouseup: this._onMouseUp,
13054 keydown: this._onKeyDown
13055 }, this);
13056 },
13057
13058 _onMouseMove: function (e) {
13059 if (!this._moved) {
13060 this._moved = true;
13061
13062 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13063 addClass(this._container, 'leaflet-crosshair');
13064
13065 this._map.fire('boxzoomstart');
13066 }
13067
13068 this._point = this._map.mouseEventToContainerPoint(e);
13069
13070 var bounds = new Bounds(this._point, this._startPoint),
13071 size = bounds.getSize();
13072
13073 setPosition(this._box, bounds.min);
13074
13075 this._box.style.width = size.x + 'px';
13076 this._box.style.height = size.y + 'px';
13077 },
13078
13079 _finish: function () {
13080 if (this._moved) {
13081 remove(this._box);
13082 removeClass(this._container, 'leaflet-crosshair');
13083 }
13084
13085 enableTextSelection();
13086 enableImageDrag();
13087
13088 off(document, {
13089 contextmenu: stop,
13090 mousemove: this._onMouseMove,
13091 mouseup: this._onMouseUp,
13092 keydown: this._onKeyDown
13093 }, this);
13094 },
13095
13096 _onMouseUp: function (e) {
13097 if ((e.which !== 1) && (e.button !== 1)) { return; }
13098
13099 this._finish();
13100
13101 if (!this._moved) { return; }
13102 // Postpone to next JS tick so internal click event handling
13103 // still see it as "moved".
13104 this._clearDeferredResetState();
13105 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13106
13107 var bounds = new LatLngBounds(
13108 this._map.containerPointToLatLng(this._startPoint),
13109 this._map.containerPointToLatLng(this._point));
13110
13111 this._map
13112 .fitBounds(bounds)
13113 .fire('boxzoomend', {boxZoomBounds: bounds});
13114 },
13115
13116 _onKeyDown: function (e) {
13117 if (e.keyCode === 27) {
13118 this._finish();
13119 }
13120 }
13121});
13122
13123// @section Handlers
13124// @property boxZoom: Handler
13125// Box (shift-drag with mouse) zoom handler.
13126Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13127
13128/*
13129 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13130 */
13131
13132// @namespace Map
13133// @section Interaction Options
13134
13135Map.mergeOptions({
13136 // @option doubleClickZoom: Boolean|String = true
13137 // Whether the map can be zoomed in by double clicking on it and
13138 // zoomed out by double clicking while holding shift. If passed
13139 // `'center'`, double-click zoom will zoom to the center of the
13140 // view regardless of where the mouse was.
13141 doubleClickZoom: true
13142});
13143
13144var DoubleClickZoom = Handler.extend({
13145 addHooks: function () {
13146 this._map.on('dblclick', this._onDoubleClick, this);
13147 },
13148
13149 removeHooks: function () {
13150 this._map.off('dblclick', this._onDoubleClick, this);
13151 },
13152
13153 _onDoubleClick: function (e) {
13154 var map = this._map,
13155 oldZoom = map.getZoom(),
13156 delta = map.options.zoomDelta,
13157 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13158
13159 if (map.options.doubleClickZoom === 'center') {
13160 map.setZoom(zoom);
13161 } else {
13162 map.setZoomAround(e.containerPoint, zoom);
13163 }
13164 }
13165});
13166
13167// @section Handlers
13168//
13169// Map properties include interaction handlers that allow you to control
13170// interaction behavior in runtime, enabling or disabling certain features such
13171// as dragging or touch zoom (see `Handler` methods). For example:
13172//
13173// ```js
13174// map.doubleClickZoom.disable();
13175// ```
13176//
13177// @property doubleClickZoom: Handler
13178// Double click zoom handler.
13179Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13180
13181/*
13182 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13183 */
13184
13185// @namespace Map
13186// @section Interaction Options
13187Map.mergeOptions({
13188 // @option dragging: Boolean = true
13189 // Whether the map be draggable with mouse/touch or not.
13190 dragging: true,
13191
13192 // @section Panning Inertia Options
13193 // @option inertia: Boolean = *
13194 // If enabled, panning of the map will have an inertia effect where
13195 // the map builds momentum while dragging and continues moving in
13196 // the same direction for some time. Feels especially nice on touch
13197 // devices. Enabled by default unless running on old Android devices.
13198 inertia: !android23,
13199
13200 // @option inertiaDeceleration: Number = 3000
13201 // The rate with which the inertial movement slows down, in pixels/second².
13202 inertiaDeceleration: 3400, // px/s^2
13203
13204 // @option inertiaMaxSpeed: Number = Infinity
13205 // Max speed of the inertial movement, in pixels/second.
13206 inertiaMaxSpeed: Infinity, // px/s
13207
13208 // @option easeLinearity: Number = 0.2
13209 easeLinearity: 0.2,
13210
13211 // TODO refactor, move to CRS
13212 // @option worldCopyJump: Boolean = false
13213 // With this option enabled, the map tracks when you pan to another "copy"
13214 // of the world and seamlessly jumps to the original one so that all overlays
13215 // like markers and vector layers are still visible.
13216 worldCopyJump: false,
13217
13218 // @option maxBoundsViscosity: Number = 0.0
13219 // If `maxBounds` is set, this option will control how solid the bounds
13220 // are when dragging the map around. The default value of `0.0` allows the
13221 // user to drag outside the bounds at normal speed, higher values will
13222 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13223 // solid, preventing the user from dragging outside the bounds.
13224 maxBoundsViscosity: 0.0
13225});
13226
13227var Drag = Handler.extend({
13228 addHooks: function () {
13229 if (!this._draggable) {
13230 var map = this._map;
13231
13232 this._draggable = new Draggable(map._mapPane, map._container);
13233
13234 this._draggable.on({
13235 dragstart: this._onDragStart,
13236 drag: this._onDrag,
13237 dragend: this._onDragEnd
13238 }, this);
13239
13240 this._draggable.on('predrag', this._onPreDragLimit, this);
13241 if (map.options.worldCopyJump) {
13242 this._draggable.on('predrag', this._onPreDragWrap, this);
13243 map.on('zoomend', this._onZoomEnd, this);
13244
13245 map.whenReady(this._onZoomEnd, this);
13246 }
13247 }
13248 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13249 this._draggable.enable();
13250 this._positions = [];
13251 this._times = [];
13252 },
13253
13254 removeHooks: function () {
13255 removeClass(this._map._container, 'leaflet-grab');
13256 removeClass(this._map._container, 'leaflet-touch-drag');
13257 this._draggable.disable();
13258 },
13259
13260 moved: function () {
13261 return this._draggable && this._draggable._moved;
13262 },
13263
13264 moving: function () {
13265 return this._draggable && this._draggable._moving;
13266 },
13267
13268 _onDragStart: function () {
13269 var map = this._map;
13270
13271 map._stop();
13272 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13273 var bounds = toLatLngBounds(this._map.options.maxBounds);
13274
13275 this._offsetLimit = toBounds(
13276 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13277 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13278 .add(this._map.getSize()));
13279
13280 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13281 } else {
13282 this._offsetLimit = null;
13283 }
13284
13285 map
13286 .fire('movestart')
13287 .fire('dragstart');
13288
13289 if (map.options.inertia) {
13290 this._positions = [];
13291 this._times = [];
13292 }
13293 },
13294
13295 _onDrag: function (e) {
13296 if (this._map.options.inertia) {
13297 var time = this._lastTime = +new Date(),
13298 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13299
13300 this._positions.push(pos);
13301 this._times.push(time);
13302
13303 this._prunePositions(time);
13304 }
13305
13306 this._map
13307 .fire('move', e)
13308 .fire('drag', e);
13309 },
13310
13311 _prunePositions: function (time) {
13312 while (this._positions.length > 1 && time - this._times[0] > 50) {
13313 this._positions.shift();
13314 this._times.shift();
13315 }
13316 },
13317
13318 _onZoomEnd: function () {
13319 var pxCenter = this._map.getSize().divideBy(2),
13320 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13321
13322 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13323 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13324 },
13325
13326 _viscousLimit: function (value, threshold) {
13327 return value - (value - threshold) * this._viscosity;
13328 },
13329
13330 _onPreDragLimit: function () {
13331 if (!this._viscosity || !this._offsetLimit) { return; }
13332
13333 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13334
13335 var limit = this._offsetLimit;
13336 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13337 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13338 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13339 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13340
13341 this._draggable._newPos = this._draggable._startPos.add(offset);
13342 },
13343
13344 _onPreDragWrap: function () {
13345 // TODO refactor to be able to adjust map pane position after zoom
13346 var worldWidth = this._worldWidth,
13347 halfWidth = Math.round(worldWidth / 2),
13348 dx = this._initialWorldOffset,
13349 x = this._draggable._newPos.x,
13350 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13351 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13352 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13353
13354 this._draggable._absPos = this._draggable._newPos.clone();
13355 this._draggable._newPos.x = newX;
13356 },
13357
13358 _onDragEnd: function (e) {
13359 var map = this._map,
13360 options = map.options,
13361
13362 noInertia = !options.inertia || this._times.length < 2;
13363
13364 map.fire('dragend', e);
13365
13366 if (noInertia) {
13367 map.fire('moveend');
13368
13369 } else {
13370 this._prunePositions(+new Date());
13371
13372 var direction = this._lastPos.subtract(this._positions[0]),
13373 duration = (this._lastTime - this._times[0]) / 1000,
13374 ease = options.easeLinearity,
13375
13376 speedVector = direction.multiplyBy(ease / duration),
13377 speed = speedVector.distanceTo([0, 0]),
13378
13379 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13380 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13381
13382 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13383 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13384
13385 if (!offset.x && !offset.y) {
13386 map.fire('moveend');
13387
13388 } else {
13389 offset = map._limitOffset(offset, map.options.maxBounds);
13390
13391 requestAnimFrame(function () {
13392 map.panBy(offset, {
13393 duration: decelerationDuration,
13394 easeLinearity: ease,
13395 noMoveStart: true,
13396 animate: true
13397 });
13398 });
13399 }
13400 }
13401 }
13402});
13403
13404// @section Handlers
13405// @property dragging: Handler
13406// Map dragging handler (by both mouse and touch).
13407Map.addInitHook('addHandler', 'dragging', Drag);
13408
13409/*
13410 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13411 */
13412
13413// @namespace Map
13414// @section Keyboard Navigation Options
13415Map.mergeOptions({
13416 // @option keyboard: Boolean = true
13417 // Makes the map focusable and allows users to navigate the map with keyboard
13418 // arrows and `+`/`-` keys.
13419 keyboard: true,
13420
13421 // @option keyboardPanDelta: Number = 80
13422 // Amount of pixels to pan when pressing an arrow key.
13423 keyboardPanDelta: 80
13424});
13425
13426var Keyboard = Handler.extend({
13427
13428 keyCodes: {
13429 left: [37],
13430 right: [39],
13431 down: [40],
13432 up: [38],
13433 zoomIn: [187, 107, 61, 171],
13434 zoomOut: [189, 109, 54, 173]
13435 },
13436
13437 initialize: function (map) {
13438 this._map = map;
13439
13440 this._setPanDelta(map.options.keyboardPanDelta);
13441 this._setZoomDelta(map.options.zoomDelta);
13442 },
13443
13444 addHooks: function () {
13445 var container = this._map._container;
13446
13447 // make the container focusable by tabbing
13448 if (container.tabIndex <= 0) {
13449 container.tabIndex = '0';
13450 }
13451
13452 on(container, {
13453 focus: this._onFocus,
13454 blur: this._onBlur,
13455 mousedown: this._onMouseDown
13456 }, this);
13457
13458 this._map.on({
13459 focus: this._addHooks,
13460 blur: this._removeHooks
13461 }, this);
13462 },
13463
13464 removeHooks: function () {
13465 this._removeHooks();
13466
13467 off(this._map._container, {
13468 focus: this._onFocus,
13469 blur: this._onBlur,
13470 mousedown: this._onMouseDown
13471 }, this);
13472
13473 this._map.off({
13474 focus: this._addHooks,
13475 blur: this._removeHooks
13476 }, this);
13477 },
13478
13479 _onMouseDown: function () {
13480 if (this._focused) { return; }
13481
13482 var body = document.body,
13483 docEl = document.documentElement,
13484 top = body.scrollTop || docEl.scrollTop,
13485 left = body.scrollLeft || docEl.scrollLeft;
13486
13487 this._map._container.focus();
13488
13489 window.scrollTo(left, top);
13490 },
13491
13492 _onFocus: function () {
13493 this._focused = true;
13494 this._map.fire('focus');
13495 },
13496
13497 _onBlur: function () {
13498 this._focused = false;
13499 this._map.fire('blur');
13500 },
13501
13502 _setPanDelta: function (panDelta) {
13503 var keys = this._panKeys = {},
13504 codes = this.keyCodes,
13505 i, len;
13506
13507 for (i = 0, len = codes.left.length; i < len; i++) {
13508 keys[codes.left[i]] = [-1 * panDelta, 0];
13509 }
13510 for (i = 0, len = codes.right.length; i < len; i++) {
13511 keys[codes.right[i]] = [panDelta, 0];
13512 }
13513 for (i = 0, len = codes.down.length; i < len; i++) {
13514 keys[codes.down[i]] = [0, panDelta];
13515 }
13516 for (i = 0, len = codes.up.length; i < len; i++) {
13517 keys[codes.up[i]] = [0, -1 * panDelta];
13518 }
13519 },
13520
13521 _setZoomDelta: function (zoomDelta) {
13522 var keys = this._zoomKeys = {},
13523 codes = this.keyCodes,
13524 i, len;
13525
13526 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13527 keys[codes.zoomIn[i]] = zoomDelta;
13528 }
13529 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13530 keys[codes.zoomOut[i]] = -zoomDelta;
13531 }
13532 },
13533
13534 _addHooks: function () {
13535 on(document, 'keydown', this._onKeyDown, this);
13536 },
13537
13538 _removeHooks: function () {
13539 off(document, 'keydown', this._onKeyDown, this);
13540 },
13541
13542 _onKeyDown: function (e) {
13543 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13544
13545 var key = e.keyCode,
13546 map = this._map,
13547 offset;
13548
13549 if (key in this._panKeys) {
13550 if (!map._panAnim || !map._panAnim._inProgress) {
13551 offset = this._panKeys[key];
13552 if (e.shiftKey) {
13553 offset = toPoint(offset).multiplyBy(3);
13554 }
13555
13556 map.panBy(offset);
13557
13558 if (map.options.maxBounds) {
13559 map.panInsideBounds(map.options.maxBounds);
13560 }
13561 }
13562 } else if (key in this._zoomKeys) {
13563 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13564
13565 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13566 map.closePopup();
13567
13568 } else {
13569 return;
13570 }
13571
13572 stop(e);
13573 }
13574});
13575
13576// @section Handlers
13577// @section Handlers
13578// @property keyboard: Handler
13579// Keyboard navigation handler.
13580Map.addInitHook('addHandler', 'keyboard', Keyboard);
13581
13582/*
13583 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13584 */
13585
13586// @namespace Map
13587// @section Interaction Options
13588Map.mergeOptions({
13589 // @section Mousewheel options
13590 // @option scrollWheelZoom: Boolean|String = true
13591 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13592 // it will zoom to the center of the view regardless of where the mouse was.
13593 scrollWheelZoom: true,
13594
13595 // @option wheelDebounceTime: Number = 40
13596 // Limits the rate at which a wheel can fire (in milliseconds). By default
13597 // user can't zoom via wheel more often than once per 40 ms.
13598 wheelDebounceTime: 40,
13599
13600 // @option wheelPxPerZoomLevel: Number = 60
13601 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13602 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13603 // faster (and vice versa).
13604 wheelPxPerZoomLevel: 60
13605});
13606
13607var ScrollWheelZoom = Handler.extend({
13608 addHooks: function () {
13609 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13610
13611 this._delta = 0;
13612 },
13613
13614 removeHooks: function () {
13615 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13616 },
13617
13618 _onWheelScroll: function (e) {
13619 var delta = getWheelDelta(e);
13620
13621 var debounce = this._map.options.wheelDebounceTime;
13622
13623 this._delta += delta;
13624 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13625
13626 if (!this._startTime) {
13627 this._startTime = +new Date();
13628 }
13629
13630 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13631
13632 clearTimeout(this._timer);
13633 this._timer = setTimeout(bind(this._performZoom, this), left);
13634
13635 stop(e);
13636 },
13637
13638 _performZoom: function () {
13639 var map = this._map,
13640 zoom = map.getZoom(),
13641 snap = this._map.options.zoomSnap || 0;
13642
13643 map._stop(); // stop panning and fly animations if any
13644
13645 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13646 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13647 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13648 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13649 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13650
13651 this._delta = 0;
13652 this._startTime = null;
13653
13654 if (!delta) { return; }
13655
13656 if (map.options.scrollWheelZoom === 'center') {
13657 map.setZoom(zoom + delta);
13658 } else {
13659 map.setZoomAround(this._lastMousePos, zoom + delta);
13660 }
13661 }
13662});
13663
13664// @section Handlers
13665// @property scrollWheelZoom: Handler
13666// Scroll wheel zoom handler.
13667Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13668
13669/*
13670 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13671 */
13672
13673// @namespace Map
13674// @section Interaction Options
13675Map.mergeOptions({
13676 // @section Touch interaction options
13677 // @option tap: Boolean = true
13678 // Enables mobile hacks for supporting instant taps (fixing 200ms click
13679 // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13680 tap: true,
13681
13682 // @option tapTolerance: Number = 15
13683 // The max number of pixels a user can shift his finger during touch
13684 // for it to be considered a valid tap.
13685 tapTolerance: 15
13686});
13687
13688var Tap = Handler.extend({
13689 addHooks: function () {
13690 on(this._map._container, 'touchstart', this._onDown, this);
13691 },
13692
13693 removeHooks: function () {
13694 off(this._map._container, 'touchstart', this._onDown, this);
13695 },
13696
13697 _onDown: function (e) {
13698 if (!e.touches) { return; }
13699
13700 preventDefault(e);
13701
13702 this._fireClick = true;
13703
13704 // don't simulate click or track longpress if more than 1 touch
13705 if (e.touches.length > 1) {
13706 this._fireClick = false;
13707 clearTimeout(this._holdTimeout);
13708 return;
13709 }
13710
13711 var first = e.touches[0],
13712 el = first.target;
13713
13714 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13715
13716 // if touching a link, highlight it
13717 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13718 addClass(el, 'leaflet-active');
13719 }
13720
13721 // simulate long hold but setting a timeout
13722 this._holdTimeout = setTimeout(bind(function () {
13723 if (this._isTapValid()) {
13724 this._fireClick = false;
13725 this._onUp();
13726 this._simulateEvent('contextmenu', first);
13727 }
13728 }, this), 1000);
13729
13730 this._simulateEvent('mousedown', first);
13731
13732 on(document, {
13733 touchmove: this._onMove,
13734 touchend: this._onUp
13735 }, this);
13736 },
13737
13738 _onUp: function (e) {
13739 clearTimeout(this._holdTimeout);
13740
13741 off(document, {
13742 touchmove: this._onMove,
13743 touchend: this._onUp
13744 }, this);
13745
13746 if (this._fireClick && e && e.changedTouches) {
13747
13748 var first = e.changedTouches[0],
13749 el = first.target;
13750
13751 if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13752 removeClass(el, 'leaflet-active');
13753 }
13754
13755 this._simulateEvent('mouseup', first);
13756
13757 // simulate click if the touch didn't move too much
13758 if (this._isTapValid()) {
13759 this._simulateEvent('click', first);
13760 }
13761 }
13762 },
13763
13764 _isTapValid: function () {
13765 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13766 },
13767
13768 _onMove: function (e) {
13769 var first = e.touches[0];
13770 this._newPos = new Point(first.clientX, first.clientY);
13771 this._simulateEvent('mousemove', first);
13772 },
13773
13774 _simulateEvent: function (type, e) {
13775 var simulatedEvent = document.createEvent('MouseEvents');
13776
13777 simulatedEvent._simulated = true;
13778 e.target._simulatedClick = true;
13779
13780 simulatedEvent.initMouseEvent(
13781 type, true, true, window, 1,
13782 e.screenX, e.screenY,
13783 e.clientX, e.clientY,
13784 false, false, false, false, 0, null);
13785
13786 e.target.dispatchEvent(simulatedEvent);
13787 }
13788});
13789
13790// @section Handlers
13791// @property tap: Handler
13792// Mobile touch hacks (quick tap and touch hold) handler.
13793if (touch && !pointer) {
13794 Map.addInitHook('addHandler', 'tap', Tap);
13795}
13796
13797/*
13798 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13799 */
13800
13801// @namespace Map
13802// @section Interaction Options
13803Map.mergeOptions({
13804 // @section Touch interaction options
13805 // @option touchZoom: Boolean|String = *
13806 // Whether the map can be zoomed by touch-dragging with two fingers. If
13807 // passed `'center'`, it will zoom to the center of the view regardless of
13808 // where the touch events (fingers) were. Enabled for touch-capable web
13809 // browsers except for old Androids.
13810 touchZoom: touch && !android23,
13811
13812 // @option bounceAtZoomLimits: Boolean = true
13813 // Set it to false if you don't want the map to zoom beyond min/max zoom
13814 // and then bounce back when pinch-zooming.
13815 bounceAtZoomLimits: true
13816});
13817
13818var TouchZoom = Handler.extend({
13819 addHooks: function () {
13820 addClass(this._map._container, 'leaflet-touch-zoom');
13821 on(this._map._container, 'touchstart', this._onTouchStart, this);
13822 },
13823
13824 removeHooks: function () {
13825 removeClass(this._map._container, 'leaflet-touch-zoom');
13826 off(this._map._container, 'touchstart', this._onTouchStart, this);
13827 },
13828
13829 _onTouchStart: function (e) {
13830 var map = this._map;
13831 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13832
13833 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13834 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13835
13836 this._centerPoint = map.getSize()._divideBy(2);
13837 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13838 if (map.options.touchZoom !== 'center') {
13839 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13840 }
13841
13842 this._startDist = p1.distanceTo(p2);
13843 this._startZoom = map.getZoom();
13844
13845 this._moved = false;
13846 this._zooming = true;
13847
13848 map._stop();
13849
13850 on(document, 'touchmove', this._onTouchMove, this);
13851 on(document, 'touchend', this._onTouchEnd, this);
13852
13853 preventDefault(e);
13854 },
13855
13856 _onTouchMove: function (e) {
13857 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13858
13859 var map = this._map,
13860 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13861 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13862 scale = p1.distanceTo(p2) / this._startDist;
13863
13864 this._zoom = map.getScaleZoom(scale, this._startZoom);
13865
13866 if (!map.options.bounceAtZoomLimits && (
13867 (this._zoom < map.getMinZoom() && scale < 1) ||
13868 (this._zoom > map.getMaxZoom() && scale > 1))) {
13869 this._zoom = map._limitZoom(this._zoom);
13870 }
13871
13872 if (map.options.touchZoom === 'center') {
13873 this._center = this._startLatLng;
13874 if (scale === 1) { return; }
13875 } else {
13876 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13877 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13878 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13879 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13880 }
13881
13882 if (!this._moved) {
13883 map._moveStart(true, false);
13884 this._moved = true;
13885 }
13886
13887 cancelAnimFrame(this._animRequest);
13888
13889 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13890 this._animRequest = requestAnimFrame(moveFn, this, true);
13891
13892 preventDefault(e);
13893 },
13894
13895 _onTouchEnd: function () {
13896 if (!this._moved || !this._zooming) {
13897 this._zooming = false;
13898 return;
13899 }
13900
13901 this._zooming = false;
13902 cancelAnimFrame(this._animRequest);
13903
13904 off(document, 'touchmove', this._onTouchMove);
13905 off(document, 'touchend', this._onTouchEnd);
13906
13907 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13908 if (this._map.options.zoomAnimation) {
13909 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13910 } else {
13911 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13912 }
13913 }
13914});
13915
13916// @section Handlers
13917// @property touchZoom: Handler
13918// Touch zoom handler.
13919Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13920
13921Map.BoxZoom = BoxZoom;
13922Map.DoubleClickZoom = DoubleClickZoom;
13923Map.Drag = Drag;
13924Map.Keyboard = Keyboard;
13925Map.ScrollWheelZoom = ScrollWheelZoom;
13926Map.Tap = Tap;
13927Map.TouchZoom = TouchZoom;
13928
13929Object.freeze = freeze;
13930
13931exports.version = version;
13932exports.Control = Control;
13933exports.control = control;
13934exports.Browser = Browser;
13935exports.Evented = Evented;
13936exports.Mixin = Mixin;
13937exports.Util = Util;
13938exports.Class = Class;
13939exports.Handler = Handler;
13940exports.extend = extend;
13941exports.bind = bind;
13942exports.stamp = stamp;
13943exports.setOptions = setOptions;
13944exports.DomEvent = DomEvent;
13945exports.DomUtil = DomUtil;
13946exports.PosAnimation = PosAnimation;
13947exports.Draggable = Draggable;
13948exports.LineUtil = LineUtil;
13949exports.PolyUtil = PolyUtil;
13950exports.Point = Point;
13951exports.point = toPoint;
13952exports.Bounds = Bounds;
13953exports.bounds = toBounds;
13954exports.Transformation = Transformation;
13955exports.transformation = toTransformation;
13956exports.Projection = index;
13957exports.LatLng = LatLng;
13958exports.latLng = toLatLng;
13959exports.LatLngBounds = LatLngBounds;
13960exports.latLngBounds = toLatLngBounds;
13961exports.CRS = CRS;
13962exports.GeoJSON = GeoJSON;
13963exports.geoJSON = geoJSON;
13964exports.geoJson = geoJson;
13965exports.Layer = Layer;
13966exports.LayerGroup = LayerGroup;
13967exports.layerGroup = layerGroup;
13968exports.FeatureGroup = FeatureGroup;
13969exports.featureGroup = featureGroup;
13970exports.ImageOverlay = ImageOverlay;
13971exports.imageOverlay = imageOverlay;
13972exports.VideoOverlay = VideoOverlay;
13973exports.videoOverlay = videoOverlay;
13974exports.SVGOverlay = SVGOverlay;
13975exports.svgOverlay = svgOverlay;
13976exports.DivOverlay = DivOverlay;
13977exports.Popup = Popup;
13978exports.popup = popup;
13979exports.Tooltip = Tooltip;
13980exports.tooltip = tooltip;
13981exports.Icon = Icon;
13982exports.icon = icon;
13983exports.DivIcon = DivIcon;
13984exports.divIcon = divIcon;
13985exports.Marker = Marker;
13986exports.marker = marker;
13987exports.TileLayer = TileLayer;
13988exports.tileLayer = tileLayer;
13989exports.GridLayer = GridLayer;
13990exports.gridLayer = gridLayer;
13991exports.SVG = SVG;
13992exports.svg = svg$1;
13993exports.Renderer = Renderer;
13994exports.Canvas = Canvas;
13995exports.canvas = canvas$1;
13996exports.Path = Path;
13997exports.CircleMarker = CircleMarker;
13998exports.circleMarker = circleMarker;
13999exports.Circle = Circle;
14000exports.circle = circle;
14001exports.Polyline = Polyline;
14002exports.polyline = polyline;
14003exports.Polygon = Polygon;
14004exports.polygon = polygon;
14005exports.Rectangle = Rectangle;
14006exports.rectangle = rectangle;
14007exports.Map = Map;
14008exports.map = createMap;
14009
14010var oldL = window.L;
14011exports.noConflict = function() {
14012 window.L = oldL;
14013 return this;
14014}
14015
14016// Always export us to window global (see #2364)
14017window.L = exports;
14018
14019})));
14020//# sourceMappingURL=leaflet-src.js.map