2 * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
3 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
11 * Various utility functions, used by Leaflet internally.
\r
14 // @function extend(dest: Object, src?: Object): Object
\r
15 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
\r
16 function extend(dest) {
\r
19 for (j = 1, len = arguments.length; j < len; j++) {
\r
28 // @function create(proto: Object, properties?: Object): Object
\r
29 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
\r
30 var create$2 = Object.create || (function () {
\r
32 return function (proto) {
\r
33 F.prototype = proto;
\r
38 // @function bind(fn: Function, …): Function
\r
39 // 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).
\r
40 // Has a `L.bind()` shortcut.
\r
41 function bind(fn, obj) {
\r
42 var slice = Array.prototype.slice;
\r
45 return fn.bind.apply(fn, slice.call(arguments, 1));
\r
48 var args = slice.call(arguments, 2);
\r
50 return function () {
\r
51 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
\r
55 // @property lastId: Number
\r
56 // Last unique ID used by [`stamp()`](#util-stamp)
\r
59 // @function stamp(obj: Object): Number
\r
60 // Returns the unique ID of an object, assigning it one if it doesn't have it.
\r
61 function stamp(obj) {
\r
62 if (!('_leaflet_id' in obj)) {
\r
63 obj['_leaflet_id'] = ++lastId;
\r
65 return obj._leaflet_id;
\r
68 // @function throttle(fn: Function, time: Number, context: Object): Function
\r
69 // Returns a function which executes function `fn` with the given scope `context`
\r
70 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
\r
71 // `fn` will be called no more than one time per given amount of `time`. The arguments
\r
72 // received by the bound function will be any arguments passed when binding the
\r
73 // function, followed by any arguments passed when invoking the bound function.
\r
74 // Has an `L.throttle` shortcut.
\r
75 function throttle(fn, time, context) {
\r
76 var lock, args, wrapperFn, later;
\r
78 later = function () {
\r
79 // reset lock and call if queued
\r
82 wrapperFn.apply(context, args);
\r
87 wrapperFn = function () {
\r
89 // called too soon, queue to call later
\r
93 // call and lock until later
\r
94 fn.apply(context, arguments);
\r
95 setTimeout(later, time);
\r
103 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
\r
104 // Returns the number `num` modulo `range` in such a way so it lies within
\r
105 // `range[0]` and `range[1]`. The returned value will be always smaller than
\r
106 // `range[1]` unless `includeMax` is set to `true`.
\r
107 function wrapNum(x, range, includeMax) {
\r
108 var max = range[1],
\r
111 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
\r
114 // @function falseFn(): Function
\r
115 // Returns a function which always returns `false`.
\r
116 function falseFn() { return false; }
\r
118 // @function formatNum(num: Number, precision?: Number|false): Number
\r
119 // Returns the number `num` rounded with specified `precision`.
\r
120 // The default `precision` value is 6 decimal places.
\r
121 // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
\r
122 function formatNum(num, precision) {
\r
123 if (precision === false) { return num; }
\r
124 var pow = Math.pow(10, precision === undefined ? 6 : precision);
\r
125 return Math.round(num * pow) / pow;
\r
128 // @function trim(str: String): String
\r
129 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
\r
130 function trim(str) {
\r
131 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
\r
134 // @function splitWords(str: String): String[]
\r
135 // Trims and splits the string on whitespace and returns the array of parts.
\r
136 function splitWords(str) {
\r
137 return trim(str).split(/\s+/);
\r
140 // @function setOptions(obj: Object, options: Object): Object
\r
141 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
\r
142 function setOptions(obj, options) {
\r
143 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
\r
144 obj.options = obj.options ? create$2(obj.options) : {};
\r
146 for (var i in options) {
\r
147 obj.options[i] = options[i];
\r
149 return obj.options;
\r
152 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
\r
153 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
\r
154 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
\r
155 // be appended at the end. If `uppercase` is `true`, the parameter names will
\r
156 // be uppercased (e.g. `'?A=foo&B=bar'`)
\r
157 function getParamString(obj, existingUrl, uppercase) {
\r
159 for (var i in obj) {
\r
160 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
\r
162 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
\r
165 var templateRe = /\{ *([\w_ -]+) *\}/g;
\r
167 // @function template(str: String, data: Object): String
\r
168 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
\r
169 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
\r
170 // `('Hello foo, bar')`. You can also specify functions instead of strings for
\r
171 // data values — they will be evaluated passing `data` as an argument.
\r
172 function template(str, data) {
\r
173 return str.replace(templateRe, function (str, key) {
\r
174 var value = data[key];
\r
176 if (value === undefined) {
\r
177 throw new Error('No value provided for variable ' + str);
\r
179 } else if (typeof value === 'function') {
\r
180 value = value(data);
\r
186 // @function isArray(obj): Boolean
\r
187 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
\r
188 var isArray = Array.isArray || function (obj) {
\r
189 return (Object.prototype.toString.call(obj) === '[object Array]');
\r
192 // @function indexOf(array: Array, el: Object): Number
\r
193 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
\r
194 function indexOf(array, el) {
\r
195 for (var i = 0; i < array.length; i++) {
\r
196 if (array[i] === el) { return i; }
\r
201 // @property emptyImageUrl: String
\r
202 // Data URI string containing a base64-encoded empty GIF image.
\r
203 // Used as a hack to free memory from unused images on WebKit-powered
\r
204 // mobile devices (by setting image `src` to this string).
\r
205 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
\r
207 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
\r
209 function getPrefixed(name) {
\r
210 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
\r
215 // fallback for IE 7-8
\r
216 function timeoutDefer(fn) {
\r
217 var time = +new Date(),
\r
218 timeToCall = Math.max(0, 16 - (time - lastTime));
\r
220 lastTime = time + timeToCall;
\r
221 return window.setTimeout(fn, timeToCall);
\r
224 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
\r
225 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
\r
226 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
\r
228 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
\r
229 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
\r
230 // `context` if given. When `immediate` is set, `fn` is called immediately if
\r
231 // the browser doesn't have native support for
\r
232 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
\r
233 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
\r
234 function requestAnimFrame(fn, context, immediate) {
\r
235 if (immediate && requestFn === timeoutDefer) {
\r
238 return requestFn.call(window, bind(fn, context));
\r
242 // @function cancelAnimFrame(id: Number): undefined
\r
243 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
\r
244 function cancelAnimFrame(id) {
\r
246 cancelFn.call(window, id);
\r
255 get lastId () { return lastId; },
260 formatNum: formatNum,
262 splitWords: splitWords,
263 setOptions: setOptions,
264 getParamString: getParamString,
268 emptyImageUrl: emptyImageUrl,
269 requestFn: requestFn,
271 requestAnimFrame: requestAnimFrame,
272 cancelAnimFrame: cancelAnimFrame
281 // Thanks to John Resig and Dean Edwards for inspiration!
\r
283 function Class() {}
\r
285 Class.extend = function (props) {
\r
287 // @function extend(props: Object): Function
\r
288 // [Extends the current class](#class-inheritance) given the properties to be included.
\r
289 // Returns a Javascript function that is a class constructor (to be called with `new`).
\r
290 var NewClass = function () {
\r
294 // call the constructor
\r
295 if (this.initialize) {
\r
296 this.initialize.apply(this, arguments);
\r
299 // call all constructor hooks
\r
300 this.callInitHooks();
\r
303 var parentProto = NewClass.__super__ = this.prototype;
\r
305 var proto = create$2(parentProto);
\r
306 proto.constructor = NewClass;
\r
308 NewClass.prototype = proto;
\r
310 // inherit parent's statics
\r
311 for (var i in this) {
\r
312 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
\r
313 NewClass[i] = this[i];
\r
317 // mix static properties into the class
\r
318 if (props.statics) {
\r
319 extend(NewClass, props.statics);
\r
322 // mix includes into the prototype
\r
323 if (props.includes) {
\r
324 checkDeprecatedMixinEvents(props.includes);
\r
325 extend.apply(null, [proto].concat(props.includes));
\r
328 // mix given properties into the prototype
\r
329 extend(proto, props);
\r
330 delete proto.statics;
\r
331 delete proto.includes;
\r
334 if (proto.options) {
\r
335 proto.options = parentProto.options ? create$2(parentProto.options) : {};
\r
336 extend(proto.options, props.options);
\r
339 proto._initHooks = [];
\r
341 // add method for calling all hooks
\r
342 proto.callInitHooks = function () {
\r
344 if (this._initHooksCalled) { return; }
\r
346 if (parentProto.callInitHooks) {
\r
347 parentProto.callInitHooks.call(this);
\r
350 this._initHooksCalled = true;
\r
352 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
\r
353 proto._initHooks[i].call(this);
\r
361 // @function include(properties: Object): this
\r
362 // [Includes a mixin](#class-includes) into the current class.
\r
363 Class.include = function (props) {
\r
364 var parentOptions = this.prototype.options;
\r
365 extend(this.prototype, props);
\r
366 if (props.options) {
\r
367 this.prototype.options = parentOptions;
\r
368 this.mergeOptions(props.options);
\r
373 // @function mergeOptions(options: Object): this
\r
374 // [Merges `options`](#class-options) into the defaults of the class.
\r
375 Class.mergeOptions = function (options) {
\r
376 extend(this.prototype.options, options);
\r
380 // @function addInitHook(fn: Function): this
\r
381 // Adds a [constructor hook](#class-constructor-hooks) to the class.
\r
382 Class.addInitHook = function (fn) { // (Function) || (String, args...)
\r
383 var args = Array.prototype.slice.call(arguments, 1);
\r
385 var init = typeof fn === 'function' ? fn : function () {
\r
386 this[fn].apply(this, args);
\r
389 this.prototype._initHooks = this.prototype._initHooks || [];
\r
390 this.prototype._initHooks.push(init);
\r
394 function checkDeprecatedMixinEvents(includes) {
\r
395 /* global L: true */
\r
396 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
\r
398 includes = isArray(includes) ? includes : [includes];
\r
400 for (var i = 0; i < includes.length; i++) {
\r
401 if (includes[i] === L.Mixin.Events) {
\r
402 console.warn('Deprecated include of L.Mixin.Events: ' +
\r
403 'this property will be removed in future releases, ' +
\r
404 'please inherit from L.Evented instead.', new Error().stack);
\r
414 * 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).
\r
419 * map.on('click', function(e) {
\r
424 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
\r
427 * function onClick(e) { ... }
\r
429 * map.on('click', onClick);
\r
430 * map.off('click', onClick);
\r
435 /* @method on(type: String, fn: Function, context?: Object): this
\r
436 * 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'`).
\r
439 * @method on(eventMap: Object): this
\r
440 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
\r
442 on: function (types, fn, context) {
\r
444 // types can be a map of types/handlers
\r
445 if (typeof types === 'object') {
\r
446 for (var type in types) {
\r
447 // we don't process space-separated events here for performance;
\r
448 // it's a hot path since Layer uses the on(obj) syntax
\r
449 this._on(type, types[type], fn);
\r
453 // types can be a string of space-separated words
\r
454 types = splitWords(types);
\r
456 for (var i = 0, len = types.length; i < len; i++) {
\r
457 this._on(types[i], fn, context);
\r
464 /* @method off(type: String, fn?: Function, context?: Object): this
\r
465 * 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.
\r
468 * @method off(eventMap: Object): this
\r
469 * Removes a set of type/listener pairs.
\r
472 * @method off: this
\r
473 * Removes all listeners to all events on the object. This includes implicitly attached events.
\r
475 off: function (types, fn, context) {
\r
477 if (!arguments.length) {
\r
478 // clear all listeners if called without arguments
\r
479 delete this._events;
\r
481 } else if (typeof types === 'object') {
\r
482 for (var type in types) {
\r
483 this._off(type, types[type], fn);
\r
487 types = splitWords(types);
\r
489 var removeAll = arguments.length === 1;
\r
490 for (var i = 0, len = types.length; i < len; i++) {
\r
492 this._off(types[i]);
\r
494 this._off(types[i], fn, context);
\r
502 // attach listener (without syntactic sugar now)
\r
503 _on: function (type, fn, context, _once) {
\r
504 if (typeof fn !== 'function') {
\r
505 console.warn('wrong listener type: ' + typeof fn);
\r
509 // check if fn already there
\r
510 if (this._listens(type, fn, context) !== false) {
\r
514 if (context === this) {
\r
515 // Less memory footprint.
\r
516 context = undefined;
\r
519 var newListener = {fn: fn, ctx: context};
\r
521 newListener.once = true;
\r
524 this._events = this._events || {};
\r
525 this._events[type] = this._events[type] || [];
\r
526 this._events[type].push(newListener);
\r
529 _off: function (type, fn, context) {
\r
534 if (!this._events) {
\r
538 listeners = this._events[type];
\r
543 if (arguments.length === 1) { // remove all
\r
544 if (this._firingCount) {
\r
545 // Set all removed listeners to noop
\r
546 // so they are not called if remove happens in fire
\r
547 for (i = 0, len = listeners.length; i < len; i++) {
\r
548 listeners[i].fn = falseFn;
\r
551 // clear all listeners for a type if function isn't specified
\r
552 delete this._events[type];
\r
556 if (typeof fn !== 'function') {
\r
557 console.warn('wrong listener type: ' + typeof fn);
\r
561 // find fn and remove it
\r
562 var index = this._listens(type, fn, context);
\r
563 if (index !== false) {
\r
564 var listener = listeners[index];
\r
565 if (this._firingCount) {
\r
566 // set the removed listener to noop so that's not called if remove happens in fire
\r
567 listener.fn = falseFn;
\r
569 /* copy array in case events are being fired */
\r
570 this._events[type] = listeners = listeners.slice();
\r
572 listeners.splice(index, 1);
\r
576 // @method fire(type: String, data?: Object, propagate?: Boolean): this
\r
577 // Fires an event of the specified type. You can optionally provide a data
\r
578 // object — the first argument of the listener function will contain its
\r
579 // properties. The event can optionally be propagated to event parents.
\r
580 fire: function (type, data, propagate) {
\r
581 if (!this.listens(type, propagate)) { return this; }
\r
583 var event = extend({}, data, {
\r
586 sourceTarget: data && data.sourceTarget || this
\r
589 if (this._events) {
\r
590 var listeners = this._events[type];
\r
592 this._firingCount = (this._firingCount + 1) || 1;
\r
593 for (var i = 0, len = listeners.length; i < len; i++) {
\r
594 var l = listeners[i];
\r
595 // off overwrites l.fn, so we need to copy fn to a var
\r
598 this.off(type, fn, l.ctx);
\r
600 fn.call(l.ctx || this, event);
\r
603 this._firingCount--;
\r
608 // propagate the event to parents (set with addEventParent)
\r
609 this._propagateEvent(event);
\r
615 // @method listens(type: String, propagate?: Boolean): Boolean
\r
616 // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
\r
617 // Returns `true` if a particular event type has any listeners attached to it.
\r
618 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
\r
619 listens: function (type, fn, context, propagate) {
\r
620 if (typeof type !== 'string') {
\r
621 console.warn('"string" type argument expected');
\r
624 // we don't overwrite the input `fn` value, because we need to use it for propagation
\r
626 if (typeof fn !== 'function') {
\r
629 context = undefined;
\r
632 var listeners = this._events && this._events[type];
\r
633 if (listeners && listeners.length) {
\r
634 if (this._listens(type, _fn, context) !== false) {
\r
640 // also check parents for listeners if event propagates
\r
641 for (var id in this._eventParents) {
\r
642 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
\r
648 // returns the index (number) or false
\r
649 _listens: function (type, fn, context) {
\r
650 if (!this._events) {
\r
654 var listeners = this._events[type] || [];
\r
656 return !!listeners.length;
\r
659 if (context === this) {
\r
660 // Less memory footprint.
\r
661 context = undefined;
\r
664 for (var i = 0, len = listeners.length; i < len; i++) {
\r
665 if (listeners[i].fn === fn && listeners[i].ctx === context) {
\r
673 // @method once(…): this
\r
674 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
\r
675 once: function (types, fn, context) {
\r
677 // types can be a map of types/handlers
\r
678 if (typeof types === 'object') {
\r
679 for (var type in types) {
\r
680 // we don't process space-separated events here for performance;
\r
681 // it's a hot path since Layer uses the on(obj) syntax
\r
682 this._on(type, types[type], fn, true);
\r
686 // types can be a string of space-separated words
\r
687 types = splitWords(types);
\r
689 for (var i = 0, len = types.length; i < len; i++) {
\r
690 this._on(types[i], fn, context, true);
\r
697 // @method addEventParent(obj: Evented): this
\r
698 // Adds an event parent - an `Evented` that will receive propagated events
\r
699 addEventParent: function (obj) {
\r
700 this._eventParents = this._eventParents || {};
\r
701 this._eventParents[stamp(obj)] = obj;
\r
705 // @method removeEventParent(obj: Evented): this
\r
706 // Removes an event parent, so it will stop receiving propagated events
\r
707 removeEventParent: function (obj) {
\r
708 if (this._eventParents) {
\r
709 delete this._eventParents[stamp(obj)];
\r
714 _propagateEvent: function (e) {
\r
715 for (var id in this._eventParents) {
\r
716 this._eventParents[id].fire(e.type, extend({
\r
718 propagatedFrom: e.target
\r
724 // aliases; we should ditch those eventually
\r
726 // @method addEventListener(…): this
\r
727 // Alias to [`on(…)`](#evented-on)
\r
728 Events.addEventListener = Events.on;
\r
730 // @method removeEventListener(…): this
\r
731 // Alias to [`off(…)`](#evented-off)
\r
733 // @method clearAllEventListeners(…): this
\r
734 // Alias to [`off()`](#evented-off)
\r
735 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
\r
737 // @method addOneTimeEventListener(…): this
\r
738 // Alias to [`once(…)`](#evented-once)
\r
739 Events.addOneTimeEventListener = Events.once;
\r
741 // @method fireEvent(…): this
\r
742 // Alias to [`fire(…)`](#evented-fire)
\r
743 Events.fireEvent = Events.fire;
\r
745 // @method hasEventListeners(…): Boolean
\r
746 // Alias to [`listens(…)`](#evented-listens)
\r
747 Events.hasEventListeners = Events.listens;
\r
749 var Evented = Class.extend(Events);
755 * Represents a point with `x` and `y` coordinates in pixels.
\r
760 * var point = L.point(200, 300);
\r
763 * 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:
\r
766 * map.panBy([200, 300]);
\r
767 * map.panBy(L.point(200, 300));
\r
770 * Note that `Point` does not inherit from Leaflet's `Class` object,
\r
771 * which means new classes can't inherit from it, and new methods
\r
772 * can't be added to it with the `include` function.
\r
775 function Point(x, y, round) {
\r
776 // @property x: Number; The `x` coordinate of the point
\r
777 this.x = (round ? Math.round(x) : x);
\r
778 // @property y: Number; The `y` coordinate of the point
\r
779 this.y = (round ? Math.round(y) : y);
\r
782 var trunc = Math.trunc || function (v) {
\r
783 return v > 0 ? Math.floor(v) : Math.ceil(v);
\r
786 Point.prototype = {
\r
788 // @method clone(): Point
\r
789 // Returns a copy of the current point.
\r
790 clone: function () {
\r
791 return new Point(this.x, this.y);
\r
794 // @method add(otherPoint: Point): Point
\r
795 // Returns the result of addition of the current and the given points.
\r
796 add: function (point) {
\r
797 // non-destructive, returns a new point
\r
798 return this.clone()._add(toPoint(point));
\r
801 _add: function (point) {
\r
802 // destructive, used directly for performance in situations where it's safe to modify existing point
\r
808 // @method subtract(otherPoint: Point): Point
\r
809 // Returns the result of subtraction of the given point from the current.
\r
810 subtract: function (point) {
\r
811 return this.clone()._subtract(toPoint(point));
\r
814 _subtract: function (point) {
\r
820 // @method divideBy(num: Number): Point
\r
821 // Returns the result of division of the current point by the given number.
\r
822 divideBy: function (num) {
\r
823 return this.clone()._divideBy(num);
\r
826 _divideBy: function (num) {
\r
832 // @method multiplyBy(num: Number): Point
\r
833 // Returns the result of multiplication of the current point by the given number.
\r
834 multiplyBy: function (num) {
\r
835 return this.clone()._multiplyBy(num);
\r
838 _multiplyBy: function (num) {
\r
844 // @method scaleBy(scale: Point): Point
\r
845 // Multiply each coordinate of the current point by each coordinate of
\r
846 // `scale`. In linear algebra terms, multiply the point by the
\r
847 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
\r
848 // defined by `scale`.
\r
849 scaleBy: function (point) {
\r
850 return new Point(this.x * point.x, this.y * point.y);
\r
853 // @method unscaleBy(scale: Point): Point
\r
854 // Inverse of `scaleBy`. Divide each coordinate of the current point by
\r
855 // each coordinate of `scale`.
\r
856 unscaleBy: function (point) {
\r
857 return new Point(this.x / point.x, this.y / point.y);
\r
860 // @method round(): Point
\r
861 // Returns a copy of the current point with rounded coordinates.
\r
862 round: function () {
\r
863 return this.clone()._round();
\r
866 _round: function () {
\r
867 this.x = Math.round(this.x);
\r
868 this.y = Math.round(this.y);
\r
872 // @method floor(): Point
\r
873 // Returns a copy of the current point with floored coordinates (rounded down).
\r
874 floor: function () {
\r
875 return this.clone()._floor();
\r
878 _floor: function () {
\r
879 this.x = Math.floor(this.x);
\r
880 this.y = Math.floor(this.y);
\r
884 // @method ceil(): Point
\r
885 // Returns a copy of the current point with ceiled coordinates (rounded up).
\r
886 ceil: function () {
\r
887 return this.clone()._ceil();
\r
890 _ceil: function () {
\r
891 this.x = Math.ceil(this.x);
\r
892 this.y = Math.ceil(this.y);
\r
896 // @method trunc(): Point
\r
897 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
\r
898 trunc: function () {
\r
899 return this.clone()._trunc();
\r
902 _trunc: function () {
\r
903 this.x = trunc(this.x);
\r
904 this.y = trunc(this.y);
\r
908 // @method distanceTo(otherPoint: Point): Number
\r
909 // Returns the cartesian distance between the current and the given points.
\r
910 distanceTo: function (point) {
\r
911 point = toPoint(point);
\r
913 var x = point.x - this.x,
\r
914 y = point.y - this.y;
\r
916 return Math.sqrt(x * x + y * y);
\r
919 // @method equals(otherPoint: Point): Boolean
\r
920 // Returns `true` if the given point has the same coordinates.
\r
921 equals: function (point) {
\r
922 point = toPoint(point);
\r
924 return point.x === this.x &&
\r
925 point.y === this.y;
\r
928 // @method contains(otherPoint: Point): Boolean
\r
929 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
\r
930 contains: function (point) {
\r
931 point = toPoint(point);
\r
933 return Math.abs(point.x) <= Math.abs(this.x) &&
\r
934 Math.abs(point.y) <= Math.abs(this.y);
\r
937 // @method toString(): String
\r
938 // Returns a string representation of the point for debugging purposes.
\r
939 toString: function () {
\r
941 formatNum(this.x) + ', ' +
\r
942 formatNum(this.y) + ')';
\r
946 // @factory L.point(x: Number, y: Number, round?: Boolean)
\r
947 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
\r
950 // @factory L.point(coords: Number[])
\r
951 // Expects an array of the form `[x, y]` instead.
\r
954 // @factory L.point(coords: Object)
\r
955 // Expects a plain object of the form `{x: Number, y: Number}` instead.
\r
956 function toPoint(x, y, round) {
\r
957 if (x instanceof Point) {
\r
961 return new Point(x[0], x[1]);
\r
963 if (x === undefined || x === null) {
\r
966 if (typeof x === 'object' && 'x' in x && 'y' in x) {
\r
967 return new Point(x.x, x.y);
\r
969 return new Point(x, y, round);
\r
976 * Represents a rectangular area in pixel coordinates.
\r
981 * var p1 = L.point(10, 10),
\r
982 * p2 = L.point(40, 60),
\r
983 * bounds = L.bounds(p1, p2);
\r
986 * 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:
\r
989 * otherBounds.intersects([[10, 10], [40, 60]]);
\r
992 * Note that `Bounds` does not inherit from Leaflet's `Class` object,
\r
993 * which means new classes can't inherit from it, and new methods
\r
994 * can't be added to it with the `include` function.
\r
997 function Bounds(a, b) {
\r
998 if (!a) { return; }
\r
1000 var points = b ? [a, b] : a;
\r
1002 for (var i = 0, len = points.length; i < len; i++) {
\r
1003 this.extend(points[i]);
\r
1007 Bounds.prototype = {
\r
1008 // @method extend(point: Point): this
\r
1009 // Extends the bounds to contain the given point.
\r
1012 // @method extend(otherBounds: Bounds): this
\r
1013 // Extend the bounds to contain the given bounds
\r
1014 extend: function (obj) {
\r
1016 if (!obj) { return this; }
\r
1018 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
\r
1019 min2 = max2 = toPoint(obj);
\r
1021 obj = toBounds(obj);
\r
1025 if (!min2 || !max2) { return this; }
\r
1028 // @property min: Point
\r
1029 // The top left corner of the rectangle.
\r
1030 // @property max: Point
\r
1031 // The bottom right corner of the rectangle.
\r
1032 if (!this.min && !this.max) {
\r
1033 this.min = min2.clone();
\r
1034 this.max = max2.clone();
\r
1036 this.min.x = Math.min(min2.x, this.min.x);
\r
1037 this.max.x = Math.max(max2.x, this.max.x);
\r
1038 this.min.y = Math.min(min2.y, this.min.y);
\r
1039 this.max.y = Math.max(max2.y, this.max.y);
\r
1044 // @method getCenter(round?: Boolean): Point
\r
1045 // Returns the center point of the bounds.
\r
1046 getCenter: function (round) {
\r
1048 (this.min.x + this.max.x) / 2,
\r
1049 (this.min.y + this.max.y) / 2, round);
\r
1052 // @method getBottomLeft(): Point
\r
1053 // Returns the bottom-left point of the bounds.
\r
1054 getBottomLeft: function () {
\r
1055 return toPoint(this.min.x, this.max.y);
\r
1058 // @method getTopRight(): Point
\r
1059 // Returns the top-right point of the bounds.
\r
1060 getTopRight: function () { // -> Point
\r
1061 return toPoint(this.max.x, this.min.y);
\r
1064 // @method getTopLeft(): Point
\r
1065 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
\r
1066 getTopLeft: function () {
\r
1067 return this.min; // left, top
\r
1070 // @method getBottomRight(): Point
\r
1071 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
\r
1072 getBottomRight: function () {
\r
1073 return this.max; // right, bottom
\r
1076 // @method getSize(): Point
\r
1077 // Returns the size of the given bounds
\r
1078 getSize: function () {
\r
1079 return this.max.subtract(this.min);
\r
1082 // @method contains(otherBounds: Bounds): Boolean
\r
1083 // Returns `true` if the rectangle contains the given one.
\r
1085 // @method contains(point: Point): Boolean
\r
1086 // Returns `true` if the rectangle contains the given point.
\r
1087 contains: function (obj) {
\r
1090 if (typeof obj[0] === 'number' || obj instanceof Point) {
\r
1091 obj = toPoint(obj);
\r
1093 obj = toBounds(obj);
\r
1096 if (obj instanceof Bounds) {
\r
1103 return (min.x >= this.min.x) &&
\r
1104 (max.x <= this.max.x) &&
\r
1105 (min.y >= this.min.y) &&
\r
1106 (max.y <= this.max.y);
\r
1109 // @method intersects(otherBounds: Bounds): Boolean
\r
1110 // Returns `true` if the rectangle intersects the given bounds. Two bounds
\r
1111 // intersect if they have at least one point in common.
\r
1112 intersects: function (bounds) { // (Bounds) -> Boolean
\r
1113 bounds = toBounds(bounds);
\r
1115 var min = this.min,
\r
1117 min2 = bounds.min,
\r
1118 max2 = bounds.max,
\r
1119 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
\r
1120 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
\r
1122 return xIntersects && yIntersects;
\r
1125 // @method overlaps(otherBounds: Bounds): Boolean
\r
1126 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
\r
1127 // overlap if their intersection is an area.
\r
1128 overlaps: function (bounds) { // (Bounds) -> Boolean
\r
1129 bounds = toBounds(bounds);
\r
1131 var min = this.min,
\r
1133 min2 = bounds.min,
\r
1134 max2 = bounds.max,
\r
1135 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
\r
1136 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
\r
1138 return xOverlaps && yOverlaps;
\r
1141 // @method isValid(): Boolean
\r
1142 // Returns `true` if the bounds are properly initialized.
\r
1143 isValid: function () {
\r
1144 return !!(this.min && this.max);
\r
1148 // @method pad(bufferRatio: Number): Bounds
\r
1149 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
\r
1150 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
\r
1151 // Negative values will retract the bounds.
\r
1152 pad: function (bufferRatio) {
\r
1153 var min = this.min,
\r
1155 heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
\r
1156 widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
\r
1160 toPoint(min.x - heightBuffer, min.y - widthBuffer),
\r
1161 toPoint(max.x + heightBuffer, max.y + widthBuffer));
\r
1165 // @method equals(otherBounds: Bounds): Boolean
\r
1166 // Returns `true` if the rectangle is equivalent to the given bounds.
\r
1167 equals: function (bounds) {
\r
1168 if (!bounds) { return false; }
\r
1170 bounds = toBounds(bounds);
\r
1172 return this.min.equals(bounds.getTopLeft()) &&
\r
1173 this.max.equals(bounds.getBottomRight());
\r
1178 // @factory L.bounds(corner1: Point, corner2: Point)
\r
1179 // Creates a Bounds object from two corners coordinate pairs.
\r
1181 // @factory L.bounds(points: Point[])
\r
1182 // Creates a Bounds object from the given array of points.
\r
1183 function toBounds(a, b) {
\r
1184 if (!a || a instanceof Bounds) {
\r
1187 return new Bounds(a, b);
\r
1191 * @class LatLngBounds
\r
1192 * @aka L.LatLngBounds
\r
1194 * Represents a rectangular geographical area on a map.
\r
1199 * var corner1 = L.latLng(40.712, -74.227),
\r
1200 * corner2 = L.latLng(40.774, -74.125),
\r
1201 * bounds = L.latLngBounds(corner1, corner2);
\r
1204 * 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:
\r
1208 * [40.712, -74.227],
\r
1209 * [40.774, -74.125]
\r
1213 * 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.
\r
1215 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
\r
1216 * which means new classes can't inherit from it, and new methods
\r
1217 * can't be added to it with the `include` function.
\r
1220 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
\r
1221 if (!corner1) { return; }
\r
1223 var latlngs = corner2 ? [corner1, corner2] : corner1;
\r
1225 for (var i = 0, len = latlngs.length; i < len; i++) {
\r
1226 this.extend(latlngs[i]);
\r
1230 LatLngBounds.prototype = {
\r
1232 // @method extend(latlng: LatLng): this
\r
1233 // Extend the bounds to contain the given point
\r
1236 // @method extend(otherBounds: LatLngBounds): this
\r
1237 // Extend the bounds to contain the given bounds
\r
1238 extend: function (obj) {
\r
1239 var sw = this._southWest,
\r
1240 ne = this._northEast,
\r
1243 if (obj instanceof LatLng) {
\r
1247 } else if (obj instanceof LatLngBounds) {
\r
1248 sw2 = obj._southWest;
\r
1249 ne2 = obj._northEast;
\r
1251 if (!sw2 || !ne2) { return this; }
\r
1254 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
\r
1258 this._southWest = new LatLng(sw2.lat, sw2.lng);
\r
1259 this._northEast = new LatLng(ne2.lat, ne2.lng);
\r
1261 sw.lat = Math.min(sw2.lat, sw.lat);
\r
1262 sw.lng = Math.min(sw2.lng, sw.lng);
\r
1263 ne.lat = Math.max(ne2.lat, ne.lat);
\r
1264 ne.lng = Math.max(ne2.lng, ne.lng);
\r
1270 // @method pad(bufferRatio: Number): LatLngBounds
\r
1271 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
\r
1272 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
\r
1273 // Negative values will retract the bounds.
\r
1274 pad: function (bufferRatio) {
\r
1275 var sw = this._southWest,
\r
1276 ne = this._northEast,
\r
1277 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
\r
1278 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
\r
1280 return new LatLngBounds(
\r
1281 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
\r
1282 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
\r
1285 // @method getCenter(): LatLng
\r
1286 // Returns the center point of the bounds.
\r
1287 getCenter: function () {
\r
1288 return new LatLng(
\r
1289 (this._southWest.lat + this._northEast.lat) / 2,
\r
1290 (this._southWest.lng + this._northEast.lng) / 2);
\r
1293 // @method getSouthWest(): LatLng
\r
1294 // Returns the south-west point of the bounds.
\r
1295 getSouthWest: function () {
\r
1296 return this._southWest;
\r
1299 // @method getNorthEast(): LatLng
\r
1300 // Returns the north-east point of the bounds.
\r
1301 getNorthEast: function () {
\r
1302 return this._northEast;
\r
1305 // @method getNorthWest(): LatLng
\r
1306 // Returns the north-west point of the bounds.
\r
1307 getNorthWest: function () {
\r
1308 return new LatLng(this.getNorth(), this.getWest());
\r
1311 // @method getSouthEast(): LatLng
\r
1312 // Returns the south-east point of the bounds.
\r
1313 getSouthEast: function () {
\r
1314 return new LatLng(this.getSouth(), this.getEast());
\r
1317 // @method getWest(): Number
\r
1318 // Returns the west longitude of the bounds
\r
1319 getWest: function () {
\r
1320 return this._southWest.lng;
\r
1323 // @method getSouth(): Number
\r
1324 // Returns the south latitude of the bounds
\r
1325 getSouth: function () {
\r
1326 return this._southWest.lat;
\r
1329 // @method getEast(): Number
\r
1330 // Returns the east longitude of the bounds
\r
1331 getEast: function () {
\r
1332 return this._northEast.lng;
\r
1335 // @method getNorth(): Number
\r
1336 // Returns the north latitude of the bounds
\r
1337 getNorth: function () {
\r
1338 return this._northEast.lat;
\r
1341 // @method contains(otherBounds: LatLngBounds): Boolean
\r
1342 // Returns `true` if the rectangle contains the given one.
\r
1345 // @method contains (latlng: LatLng): Boolean
\r
1346 // Returns `true` if the rectangle contains the given point.
\r
1347 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
\r
1348 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
\r
1349 obj = toLatLng(obj);
\r
1351 obj = toLatLngBounds(obj);
\r
1354 var sw = this._southWest,
\r
1355 ne = this._northEast,
\r
1358 if (obj instanceof LatLngBounds) {
\r
1359 sw2 = obj.getSouthWest();
\r
1360 ne2 = obj.getNorthEast();
\r
1365 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
\r
1366 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
\r
1369 // @method intersects(otherBounds: LatLngBounds): Boolean
\r
1370 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
\r
1371 intersects: function (bounds) {
\r
1372 bounds = toLatLngBounds(bounds);
\r
1374 var sw = this._southWest,
\r
1375 ne = this._northEast,
\r
1376 sw2 = bounds.getSouthWest(),
\r
1377 ne2 = bounds.getNorthEast(),
\r
1379 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
\r
1380 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
\r
1382 return latIntersects && lngIntersects;
\r
1385 // @method overlaps(otherBounds: LatLngBounds): Boolean
\r
1386 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
\r
1387 overlaps: function (bounds) {
\r
1388 bounds = toLatLngBounds(bounds);
\r
1390 var sw = this._southWest,
\r
1391 ne = this._northEast,
\r
1392 sw2 = bounds.getSouthWest(),
\r
1393 ne2 = bounds.getNorthEast(),
\r
1395 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
\r
1396 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
\r
1398 return latOverlaps && lngOverlaps;
\r
1401 // @method toBBoxString(): String
\r
1402 // 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.
\r
1403 toBBoxString: function () {
\r
1404 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
\r
1407 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
\r
1408 // 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.
\r
1409 equals: function (bounds, maxMargin) {
\r
1410 if (!bounds) { return false; }
\r
1412 bounds = toLatLngBounds(bounds);
\r
1414 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
\r
1415 this._northEast.equals(bounds.getNorthEast(), maxMargin);
\r
1418 // @method isValid(): Boolean
\r
1419 // Returns `true` if the bounds are properly initialized.
\r
1420 isValid: function () {
\r
1421 return !!(this._southWest && this._northEast);
\r
1425 // TODO International date line?
\r
1427 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
\r
1428 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
\r
1431 // @factory L.latLngBounds(latlngs: LatLng[])
\r
1432 // 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).
\r
1433 function toLatLngBounds(a, b) {
\r
1434 if (a instanceof LatLngBounds) {
\r
1437 return new LatLngBounds(a, b);
\r
1443 * Represents a geographical point with a certain latitude and longitude.
\r
1448 * var latlng = L.latLng(50.5, 30.5);
\r
1451 * 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:
\r
1454 * map.panTo([50, 30]);
\r
1455 * map.panTo({lon: 30, lat: 50});
\r
1456 * map.panTo({lat: 50, lng: 30});
\r
1457 * map.panTo(L.latLng(50, 30));
\r
1460 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
\r
1461 * which means new classes can't inherit from it, and new methods
\r
1462 * can't be added to it with the `include` function.
\r
1465 function LatLng(lat, lng, alt) {
\r
1466 if (isNaN(lat) || isNaN(lng)) {
\r
1467 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
\r
1470 // @property lat: Number
\r
1471 // Latitude in degrees
\r
1474 // @property lng: Number
\r
1475 // Longitude in degrees
\r
1478 // @property alt: Number
\r
1479 // Altitude in meters (optional)
\r
1480 if (alt !== undefined) {
\r
1485 LatLng.prototype = {
\r
1486 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
\r
1487 // 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.
\r
1488 equals: function (obj, maxMargin) {
\r
1489 if (!obj) { return false; }
\r
1491 obj = toLatLng(obj);
\r
1493 var margin = Math.max(
\r
1494 Math.abs(this.lat - obj.lat),
\r
1495 Math.abs(this.lng - obj.lng));
\r
1497 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
\r
1500 // @method toString(): String
\r
1501 // Returns a string representation of the point (for debugging purposes).
\r
1502 toString: function (precision) {
\r
1503 return 'LatLng(' +
\r
1504 formatNum(this.lat, precision) + ', ' +
\r
1505 formatNum(this.lng, precision) + ')';
\r
1508 // @method distanceTo(otherLatLng: LatLng): Number
\r
1509 // 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).
\r
1510 distanceTo: function (other) {
\r
1511 return Earth.distance(this, toLatLng(other));
\r
1514 // @method wrap(): LatLng
\r
1515 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
\r
1516 wrap: function () {
\r
1517 return Earth.wrapLatLng(this);
\r
1520 // @method toBounds(sizeInMeters: Number): LatLngBounds
\r
1521 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
\r
1522 toBounds: function (sizeInMeters) {
\r
1523 var latAccuracy = 180 * sizeInMeters / 40075017,
\r
1524 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
\r
1526 return toLatLngBounds(
\r
1527 [this.lat - latAccuracy, this.lng - lngAccuracy],
\r
1528 [this.lat + latAccuracy, this.lng + lngAccuracy]);
\r
1531 clone: function () {
\r
1532 return new LatLng(this.lat, this.lng, this.alt);
\r
1538 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
\r
1539 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
\r
1542 // @factory L.latLng(coords: Array): LatLng
\r
1543 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
\r
1546 // @factory L.latLng(coords: Object): LatLng
\r
1547 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
\r
1549 function toLatLng(a, b, c) {
\r
1550 if (a instanceof LatLng) {
\r
1553 if (isArray(a) && typeof a[0] !== 'object') {
\r
1554 if (a.length === 3) {
\r
1555 return new LatLng(a[0], a[1], a[2]);
\r
1557 if (a.length === 2) {
\r
1558 return new LatLng(a[0], a[1]);
\r
1562 if (a === undefined || a === null) {
\r
1565 if (typeof a === 'object' && 'lat' in a) {
\r
1566 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
\r
1568 if (b === undefined) {
\r
1571 return new LatLng(a, b, c);
\r
1577 * Object that defines coordinate reference systems for projecting
\r
1578 * geographical points into pixel (screen) coordinates and back (and to
\r
1579 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
\r
1580 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
\r
1582 * Leaflet defines the most usual CRSs by default. If you want to use a
\r
1583 * CRS not defined by default, take a look at the
\r
1584 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
\r
1586 * Note that the CRS instances do not inherit from Leaflet's `Class` object,
\r
1587 * and can't be instantiated. Also, new classes can't inherit from them,
\r
1588 * and methods can't be added to them with the `include` function.
\r
1592 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
\r
1593 // Projects geographical coordinates into pixel coordinates for a given zoom.
\r
1594 latLngToPoint: function (latlng, zoom) {
\r
1595 var projectedPoint = this.projection.project(latlng),
\r
1596 scale = this.scale(zoom);
\r
1598 return this.transformation._transform(projectedPoint, scale);
\r
1601 // @method pointToLatLng(point: Point, zoom: Number): LatLng
\r
1602 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
\r
1603 // zoom into geographical coordinates.
\r
1604 pointToLatLng: function (point, zoom) {
\r
1605 var scale = this.scale(zoom),
\r
1606 untransformedPoint = this.transformation.untransform(point, scale);
\r
1608 return this.projection.unproject(untransformedPoint);
\r
1611 // @method project(latlng: LatLng): Point
\r
1612 // Projects geographical coordinates into coordinates in units accepted for
\r
1613 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
\r
1614 project: function (latlng) {
\r
1615 return this.projection.project(latlng);
\r
1618 // @method unproject(point: Point): LatLng
\r
1619 // Given a projected coordinate returns the corresponding LatLng.
\r
1620 // The inverse of `project`.
\r
1621 unproject: function (point) {
\r
1622 return this.projection.unproject(point);
\r
1625 // @method scale(zoom: Number): Number
\r
1626 // Returns the scale used when transforming projected coordinates into
\r
1627 // pixel coordinates for a particular zoom. For example, it returns
\r
1628 // `256 * 2^zoom` for Mercator-based CRS.
\r
1629 scale: function (zoom) {
\r
1630 return 256 * Math.pow(2, zoom);
\r
1633 // @method zoom(scale: Number): Number
\r
1634 // Inverse of `scale()`, returns the zoom level corresponding to a scale
\r
1635 // factor of `scale`.
\r
1636 zoom: function (scale) {
\r
1637 return Math.log(scale / 256) / Math.LN2;
\r
1640 // @method getProjectedBounds(zoom: Number): Bounds
\r
1641 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
\r
1642 getProjectedBounds: function (zoom) {
\r
1643 if (this.infinite) { return null; }
\r
1645 var b = this.projection.bounds,
\r
1646 s = this.scale(zoom),
\r
1647 min = this.transformation.transform(b.min, s),
\r
1648 max = this.transformation.transform(b.max, s);
\r
1650 return new Bounds(min, max);
\r
1653 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
\r
1654 // Returns the distance between two geographical coordinates.
\r
1656 // @property code: String
\r
1657 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
\r
1659 // @property wrapLng: Number[]
\r
1660 // An array of two numbers defining whether the longitude (horizontal) coordinate
\r
1661 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
\r
1662 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
\r
1664 // @property wrapLat: Number[]
\r
1665 // Like `wrapLng`, but for the latitude (vertical) axis.
\r
1667 // wrapLng: [min, max],
\r
1668 // wrapLat: [min, max],
\r
1670 // @property infinite: Boolean
\r
1671 // If true, the coordinate space will be unbounded (infinite in both axes)
\r
1674 // @method wrapLatLng(latlng: LatLng): LatLng
\r
1675 // Returns a `LatLng` where lat and lng has been wrapped according to the
\r
1676 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
\r
1677 wrapLatLng: function (latlng) {
\r
1678 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
\r
1679 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
\r
1682 return new LatLng(lat, lng, alt);
\r
1685 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
\r
1686 // Returns a `LatLngBounds` with the same size as the given one, ensuring
\r
1687 // that its center is within the CRS's bounds.
\r
1688 // Only accepts actual `L.LatLngBounds` instances, not arrays.
\r
1689 wrapLatLngBounds: function (bounds) {
\r
1690 var center = bounds.getCenter(),
\r
1691 newCenter = this.wrapLatLng(center),
\r
1692 latShift = center.lat - newCenter.lat,
\r
1693 lngShift = center.lng - newCenter.lng;
\r
1695 if (latShift === 0 && lngShift === 0) {
\r
1699 var sw = bounds.getSouthWest(),
\r
1700 ne = bounds.getNorthEast(),
\r
1701 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
\r
1702 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
\r
1704 return new LatLngBounds(newSw, newNe);
\r
1712 * Serves as the base for CRS that are global such that they cover the earth.
1713 * Can only be used as the base for other CRS and cannot be used directly,
1714 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1718 var Earth = extend({}, CRS, {
1719 wrapLng: [-180, 180],
1721 // Mean Earth Radius, as recommended for use by
1722 // the International Union of Geodesy and Geophysics,
1723 // see https://rosettacode.org/wiki/Haversine_formula
1726 // distance between two geographical points using spherical law of cosines approximation
1727 distance: function (latlng1, latlng2) {
1728 var rad = Math.PI / 180,
1729 lat1 = latlng1.lat * rad,
1730 lat2 = latlng2.lat * rad,
1731 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1732 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1733 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1734 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1740 * @namespace Projection
\r
1741 * @projection L.Projection.SphericalMercator
\r
1743 * Spherical Mercator projection — the most common projection for online maps,
\r
1744 * used by almost all free and commercial tile providers. Assumes that Earth is
\r
1745 * a sphere. Used by the `EPSG:3857` CRS.
\r
1748 var earthRadius = 6378137;
\r
1750 var SphericalMercator = {
\r
1753 MAX_LATITUDE: 85.0511287798,
\r
1755 project: function (latlng) {
\r
1756 var d = Math.PI / 180,
\r
1757 max = this.MAX_LATITUDE,
\r
1758 lat = Math.max(Math.min(max, latlng.lat), -max),
\r
1759 sin = Math.sin(lat * d);
\r
1762 this.R * latlng.lng * d,
\r
1763 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
\r
1766 unproject: function (point) {
\r
1767 var d = 180 / Math.PI;
\r
1769 return new LatLng(
\r
1770 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
\r
1771 point.x * d / this.R);
\r
1774 bounds: (function () {
\r
1775 var d = earthRadius * Math.PI;
\r
1776 return new Bounds([-d, -d], [d, d]);
\r
1781 * @class Transformation
\r
1782 * @aka L.Transformation
\r
1784 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
\r
1785 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
\r
1786 * the reverse. Used by Leaflet in its projections code.
\r
1791 * var transformation = L.transformation(2, 5, -1, 10),
\r
1792 * p = L.point(1, 2),
\r
1793 * p2 = transformation.transform(p), // L.point(7, 8)
\r
1794 * p3 = transformation.untransform(p2); // L.point(1, 2)
\r
1799 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
\r
1800 // Creates a `Transformation` object with the given coefficients.
\r
1801 function Transformation(a, b, c, d) {
\r
1803 // use array properties
\r
1816 Transformation.prototype = {
\r
1817 // @method transform(point: Point, scale?: Number): Point
\r
1818 // Returns a transformed point, optionally multiplied by the given scale.
\r
1819 // Only accepts actual `L.Point` instances, not arrays.
\r
1820 transform: function (point, scale) { // (Point, Number) -> Point
\r
1821 return this._transform(point.clone(), scale);
\r
1824 // destructive transform (faster)
\r
1825 _transform: function (point, scale) {
\r
1826 scale = scale || 1;
\r
1827 point.x = scale * (this._a * point.x + this._b);
\r
1828 point.y = scale * (this._c * point.y + this._d);
\r
1832 // @method untransform(point: Point, scale?: Number): Point
\r
1833 // Returns the reverse transformation of the given point, optionally divided
\r
1834 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
\r
1835 untransform: function (point, scale) {
\r
1836 scale = scale || 1;
\r
1838 (point.x / scale - this._b) / this._a,
\r
1839 (point.y / scale - this._d) / this._c);
\r
1843 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
\r
1845 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
\r
1846 // Instantiates a Transformation object with the given coefficients.
\r
1849 // @factory L.transformation(coefficients: Array): Transformation
\r
1850 // Expects an coefficients array of the form
\r
1851 // `[a: Number, b: Number, c: Number, d: Number]`.
\r
1853 function toTransformation(a, b, c, d) {
\r
1854 return new Transformation(a, b, c, d);
\r
1859 * @crs L.CRS.EPSG3857
\r
1861 * The most common CRS for online maps, used by almost all free and commercial
\r
1862 * tile providers. Uses Spherical Mercator projection. Set in by default in
\r
1863 * Map's `crs` option.
\r
1866 var EPSG3857 = extend({}, Earth, {
\r
1867 code: 'EPSG:3857',
\r
1868 projection: SphericalMercator,
\r
1870 transformation: (function () {
\r
1871 var scale = 0.5 / (Math.PI * SphericalMercator.R);
\r
1872 return toTransformation(scale, 0.5, -scale, 0.5);
\r
1876 var EPSG900913 = extend({}, EPSG3857, {
\r
1877 code: 'EPSG:900913'
\r
1880 // @namespace SVG; @section
1881 // There are several static functions which can be called without instantiating L.SVG:
1883 // @function create(name: String): SVGElement
1884 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1885 // corresponding to the class name passed. For example, using 'line' will return
1886 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1887 function svgCreate(name) {
1888 return document.createElementNS('http://www.w3.org/2000/svg', name);
1891 // @function pointsToPath(rings: Point[], closed: Boolean): String
1892 // Generates a SVG path string for multiple rings, with each ring turning
1893 // into "M..L..L.." instructions
1894 function pointsToPath(rings, closed) {
1896 i, j, len, len2, points, p;
1898 for (i = 0, len = rings.length; i < len; i++) {
1901 for (j = 0, len2 = points.length; j < len2; j++) {
1903 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1906 // closes the ring for polygons; "x" is VML syntax
1907 str += closed ? (Browser.svg ? 'z' : 'x') : '';
1910 // SVG complains about empty path strings
1911 return str || 'M0 0';
1915 * @namespace Browser
\r
1918 * A namespace with static properties for browser/feature detection used by Leaflet internally.
\r
1923 * if (L.Browser.ielt9) {
\r
1924 * alert('Upgrade your browser, dude!');
\r
1929 var style = document.documentElement.style;
\r
1931 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
\r
1932 var ie = 'ActiveXObject' in window;
\r
1934 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
\r
1935 var ielt9 = ie && !document.addEventListener;
\r
1937 // @property edge: Boolean; `true` for the Edge web browser.
\r
1938 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
\r
1940 // @property webkit: Boolean;
\r
1941 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
\r
1942 var webkit = userAgentContains('webkit');
\r
1944 // @property android: Boolean
\r
1945 // **Deprecated.** `true` for any browser running on an Android platform.
\r
1946 var android = userAgentContains('android');
\r
1948 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
\r
1949 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
\r
1951 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
\r
1952 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
\r
1953 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
\r
1954 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
\r
1956 // @property opera: Boolean; `true` for the Opera browser
\r
1957 var opera = !!window.opera;
\r
1959 // @property chrome: Boolean; `true` for the Chrome browser.
\r
1960 var chrome = !edge && userAgentContains('chrome');
\r
1962 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
\r
1963 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
\r
1965 // @property safari: Boolean; `true` for the Safari browser.
\r
1966 var safari = !chrome && userAgentContains('safari');
\r
1968 var phantom = userAgentContains('phantom');
\r
1970 // @property opera12: Boolean
\r
1971 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
\r
1972 var opera12 = 'OTransition' in style;
\r
1974 // @property win: Boolean; `true` when the browser is running in a Windows platform
\r
1975 var win = navigator.platform.indexOf('Win') === 0;
\r
1977 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
\r
1978 var ie3d = ie && ('transition' in style);
\r
1980 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
\r
1981 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
\r
1983 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
\r
1984 var gecko3d = 'MozPerspective' in style;
\r
1986 // @property any3d: Boolean
\r
1987 // `true` for all browsers supporting CSS transforms.
\r
1988 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
\r
1990 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
\r
1991 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
\r
1993 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
\r
1994 var mobileWebkit = mobile && webkit;
\r
1996 // @property mobileWebkit3d: Boolean
\r
1997 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
\r
1998 var mobileWebkit3d = mobile && webkit3d;
\r
2000 // @property msPointer: Boolean
\r
2001 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
\r
2002 var msPointer = !window.PointerEvent && window.MSPointerEvent;
\r
2004 // @property pointer: Boolean
\r
2005 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
\r
2006 var pointer = !!(window.PointerEvent || msPointer);
\r
2008 // @property touchNative: Boolean
\r
2009 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
\r
2010 // **This does not necessarily mean** that the browser is running in a computer with
\r
2011 // a touchscreen, it only means that the browser is capable of understanding
\r
2013 var touchNative = 'ontouchstart' in window || !!window.TouchEvent;
\r
2015 // @property touch: Boolean
\r
2016 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
\r
2017 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
\r
2018 var touch = !window.L_NO_TOUCH && (touchNative || pointer);
\r
2020 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
\r
2021 var mobileOpera = mobile && opera;
\r
2023 // @property mobileGecko: Boolean
\r
2024 // `true` for gecko-based browsers running in a mobile device.
\r
2025 var mobileGecko = mobile && gecko;
\r
2027 // @property retina: Boolean
\r
2028 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
\r
2029 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
\r
2031 // @property passiveEvents: Boolean
\r
2032 // `true` for browsers that support passive events.
\r
2033 var passiveEvents = (function () {
\r
2034 var supportsPassiveOption = false;
\r
2036 var opts = Object.defineProperty({}, 'passive', {
\r
2037 get: function () { // eslint-disable-line getter-return
\r
2038 supportsPassiveOption = true;
\r
2041 window.addEventListener('testPassiveEventSupport', falseFn, opts);
\r
2042 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
\r
2044 // Errors can safely be ignored since this is only a browser support test.
\r
2046 return supportsPassiveOption;
\r
2049 // @property canvas: Boolean
\r
2050 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
\r
2051 var canvas$1 = (function () {
\r
2052 return !!document.createElement('canvas').getContext;
\r
2055 // @property svg: Boolean
\r
2056 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
\r
2057 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);
\r
2059 var inlineSvg = !!svg$1 && (function () {
\r
2060 var div = document.createElement('div');
\r
2061 div.innerHTML = '<svg/>';
\r
2062 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
\r
2065 // @property vml: Boolean
\r
2066 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
\r
2067 var vml = !svg$1 && (function () {
\r
2069 var div = document.createElement('div');
\r
2070 div.innerHTML = '<v:shape adj="1"/>';
\r
2072 var shape = div.firstChild;
\r
2073 shape.style.behavior = 'url(#default#VML)';
\r
2075 return shape && (typeof shape.adj === 'object');
\r
2083 // @property mac: Boolean; `true` when the browser is running in a Mac platform
\r
2084 var mac = navigator.platform.indexOf('Mac') === 0;
\r
2086 // @property mac: Boolean; `true` when the browser is running in a Linux platform
\r
2087 var linux = navigator.platform.indexOf('Linux') === 0;
\r
2089 function userAgentContains(str) {
\r
2090 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
\r
2100 android23: android23,
\r
2101 androidStock: androidStock,
\r
2110 webkit3d: webkit3d,
\r
2114 mobileWebkit: mobileWebkit,
\r
2115 mobileWebkit3d: mobileWebkit3d,
\r
2116 msPointer: msPointer,
\r
2119 touchNative: touchNative,
\r
2120 mobileOpera: mobileOpera,
\r
2121 mobileGecko: mobileGecko,
\r
2123 passiveEvents: passiveEvents,
\r
2127 inlineSvg: inlineSvg,
\r
2133 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2136 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown';
2137 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove';
2138 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup';
2139 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2141 touchstart : POINTER_DOWN,
2142 touchmove : POINTER_MOVE,
2143 touchend : POINTER_UP,
2144 touchcancel : POINTER_CANCEL
2147 touchstart : _onPointerStart,
2148 touchmove : _handlePointer,
2149 touchend : _handlePointer,
2150 touchcancel : _handlePointer
2153 var _pointerDocListener = false;
2155 // Provides a touch events wrapper for (ms)pointer events.
2156 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2158 function addPointerListener(obj, type, handler) {
2159 if (type === 'touchstart') {
2160 _addPointerDocListener();
2162 if (!handle[type]) {
2163 console.warn('wrong event specified:', type);
2166 handler = handle[type].bind(this, handler);
2167 obj.addEventListener(pEvent[type], handler, false);
2171 function removePointerListener(obj, type, handler) {
2172 if (!pEvent[type]) {
2173 console.warn('wrong event specified:', type);
2176 obj.removeEventListener(pEvent[type], handler, false);
2179 function _globalPointerDown(e) {
2180 _pointers[e.pointerId] = e;
2183 function _globalPointerMove(e) {
2184 if (_pointers[e.pointerId]) {
2185 _pointers[e.pointerId] = e;
2189 function _globalPointerUp(e) {
2190 delete _pointers[e.pointerId];
2193 function _addPointerDocListener() {
2194 // need to keep track of what pointers and how many are active to provide e.touches emulation
2195 if (!_pointerDocListener) {
2196 // we listen document as any drags that end by moving the touch off the screen get fired there
2197 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2198 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2199 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2200 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2202 _pointerDocListener = true;
2206 function _handlePointer(handler, e) {
2207 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
2210 for (var i in _pointers) {
2211 e.touches.push(_pointers[i]);
2213 e.changedTouches = [e];
2218 function _onPointerStart(handler, e) {
2219 // IE10 specific: MsTouch needs preventDefault. See #2000
2220 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2223 _handlePointer(handler, e);
2227 * Extends the event handling code with double tap support for mobile browsers.
\r
2229 * Note: currently most browsers fire native dblclick, with only a few exceptions
\r
2230 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
\r
2233 function makeDblclick(event) {
\r
2234 // in modern browsers `type` cannot be just overridden:
\r
2235 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
\r
2236 var newEvent = {},
\r
2238 for (i in event) {
\r
2240 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
\r
2243 newEvent.type = 'dblclick';
\r
2244 newEvent.detail = 2;
\r
2245 newEvent.isTrusted = false;
\r
2246 newEvent._simulated = true; // for debug purposes
\r
2251 function addDoubleTapListener(obj, handler) {
\r
2252 // Most browsers handle double tap natively
\r
2253 obj.addEventListener('dblclick', handler);
\r
2255 // On some platforms the browser doesn't fire native dblclicks for touch events.
\r
2256 // It seems that in all such cases `detail` property of `click` event is always `1`.
\r
2257 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
\r
2260 function simDblclick(e) {
\r
2261 if (e.detail !== 1) {
\r
2262 detail = e.detail; // keep in sync to avoid false dblclick in some cases
\r
2266 if (e.pointerType === 'mouse' ||
\r
2267 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {
\r
2272 // When clicking on an <input>, the browser generates a click on its
\r
2273 // <label> (and vice versa) triggering two clicks in quick succession.
\r
2274 // This ignores clicks on elements which are a label with a 'for'
\r
2275 // attribute (or children of such a label), but not children of
\r
2277 var path = getPropagationPath(e);
\r
2278 if (path.some(function (el) {
\r
2279 return el instanceof HTMLLabelElement && el.attributes.for;
\r
2281 !path.some(function (el) {
\r
2283 el instanceof HTMLInputElement ||
\r
2284 el instanceof HTMLSelectElement
\r
2291 var now = Date.now();
\r
2292 if (now - last <= delay) {
\r
2294 if (detail === 2) {
\r
2295 handler(makeDblclick(e));
\r
2303 obj.addEventListener('click', simDblclick);
\r
2306 dblclick: handler,
\r
2307 simDblclick: simDblclick
\r
2311 function removeDoubleTapListener(obj, handlers) {
\r
2312 obj.removeEventListener('dblclick', handlers.dblclick);
\r
2313 obj.removeEventListener('click', handlers.simDblclick);
\r
2317 * @namespace DomUtil
\r
2319 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
\r
2320 * tree, used by Leaflet internally.
\r
2322 * Most functions expecting or returning a `HTMLElement` also work for
\r
2323 * SVG elements. The only difference is that classes refer to CSS classes
\r
2324 * in HTML and SVG classes in SVG.
\r
2328 // @property TRANSFORM: String
\r
2329 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
\r
2330 var TRANSFORM = testProp(
\r
2331 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
\r
2333 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
\r
2334 // the same for the transitionend event, in particular the Android 4.1 stock browser
\r
2336 // @property TRANSITION: String
\r
2337 // Vendor-prefixed transition style name.
\r
2338 var TRANSITION = testProp(
\r
2339 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
\r
2341 // @property TRANSITION_END: String
\r
2342 // Vendor-prefixed transitionend event name.
\r
2343 var TRANSITION_END =
\r
2344 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
\r
2347 // @function get(id: String|HTMLElement): HTMLElement
\r
2348 // Returns an element given its DOM id, or returns the element itself
\r
2349 // if it was passed directly.
\r
2350 function get(id) {
\r
2351 return typeof id === 'string' ? document.getElementById(id) : id;
\r
2354 // @function getStyle(el: HTMLElement, styleAttrib: String): String
\r
2355 // Returns the value for a certain style attribute on an element,
\r
2356 // including computed values or values set through CSS.
\r
2357 function getStyle(el, style) {
\r
2358 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
\r
2360 if ((!value || value === 'auto') && document.defaultView) {
\r
2361 var css = document.defaultView.getComputedStyle(el, null);
\r
2362 value = css ? css[style] : null;
\r
2364 return value === 'auto' ? null : value;
\r
2367 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
\r
2368 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
\r
2369 function create$1(tagName, className, container) {
\r
2370 var el = document.createElement(tagName);
\r
2371 el.className = className || '';
\r
2374 container.appendChild(el);
\r
2379 // @function remove(el: HTMLElement)
\r
2380 // Removes `el` from its parent element
\r
2381 function remove(el) {
\r
2382 var parent = el.parentNode;
\r
2384 parent.removeChild(el);
\r
2388 // @function empty(el: HTMLElement)
\r
2389 // Removes all of `el`'s children elements from `el`
\r
2390 function empty(el) {
\r
2391 while (el.firstChild) {
\r
2392 el.removeChild(el.firstChild);
\r
2396 // @function toFront(el: HTMLElement)
\r
2397 // Makes `el` the last child of its parent, so it renders in front of the other children.
\r
2398 function toFront(el) {
\r
2399 var parent = el.parentNode;
\r
2400 if (parent && parent.lastChild !== el) {
\r
2401 parent.appendChild(el);
\r
2405 // @function toBack(el: HTMLElement)
\r
2406 // Makes `el` the first child of its parent, so it renders behind the other children.
\r
2407 function toBack(el) {
\r
2408 var parent = el.parentNode;
\r
2409 if (parent && parent.firstChild !== el) {
\r
2410 parent.insertBefore(el, parent.firstChild);
\r
2414 // @function hasClass(el: HTMLElement, name: String): Boolean
\r
2415 // Returns `true` if the element's class attribute contains `name`.
\r
2416 function hasClass(el, name) {
\r
2417 if (el.classList !== undefined) {
\r
2418 return el.classList.contains(name);
\r
2420 var className = getClass(el);
\r
2421 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
\r
2424 // @function addClass(el: HTMLElement, name: String)
\r
2425 // Adds `name` to the element's class attribute.
\r
2426 function addClass(el, name) {
\r
2427 if (el.classList !== undefined) {
\r
2428 var classes = splitWords(name);
\r
2429 for (var i = 0, len = classes.length; i < len; i++) {
\r
2430 el.classList.add(classes[i]);
\r
2432 } else if (!hasClass(el, name)) {
\r
2433 var className = getClass(el);
\r
2434 setClass(el, (className ? className + ' ' : '') + name);
\r
2438 // @function removeClass(el: HTMLElement, name: String)
\r
2439 // Removes `name` from the element's class attribute.
\r
2440 function removeClass(el, name) {
\r
2441 if (el.classList !== undefined) {
\r
2442 el.classList.remove(name);
\r
2444 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
\r
2448 // @function setClass(el: HTMLElement, name: String)
\r
2449 // Sets the element's class.
\r
2450 function setClass(el, name) {
\r
2451 if (el.className.baseVal === undefined) {
\r
2452 el.className = name;
\r
2454 // in case of SVG element
\r
2455 el.className.baseVal = name;
\r
2459 // @function getClass(el: HTMLElement): String
\r
2460 // Returns the element's class.
\r
2461 function getClass(el) {
\r
2462 // Check if the element is an SVGElementInstance and use the correspondingElement instead
\r
2463 // (Required for linked SVG elements in IE11.)
\r
2464 if (el.correspondingElement) {
\r
2465 el = el.correspondingElement;
\r
2467 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
\r
2470 // @function setOpacity(el: HTMLElement, opacity: Number)
\r
2471 // Set the opacity of an element (including old IE support).
\r
2472 // `opacity` must be a number from `0` to `1`.
\r
2473 function setOpacity(el, value) {
\r
2474 if ('opacity' in el.style) {
\r
2475 el.style.opacity = value;
\r
2476 } else if ('filter' in el.style) {
\r
2477 _setOpacityIE(el, value);
\r
2481 function _setOpacityIE(el, value) {
\r
2482 var filter = false,
\r
2483 filterName = 'DXImageTransform.Microsoft.Alpha';
\r
2485 // filters collection throws an error if we try to retrieve a filter that doesn't exist
\r
2487 filter = el.filters.item(filterName);
\r
2489 // don't set opacity to 1 if we haven't already set an opacity,
\r
2490 // it isn't needed and breaks transparent pngs.
\r
2491 if (value === 1) { return; }
\r
2494 value = Math.round(value * 100);
\r
2497 filter.Enabled = (value !== 100);
\r
2498 filter.Opacity = value;
\r
2500 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
\r
2504 // @function testProp(props: String[]): String|false
\r
2505 // Goes through the array of style names and returns the first name
\r
2506 // that is a valid style name for an element. If no such name is found,
\r
2507 // it returns false. Useful for vendor-prefixed styles like `transform`.
\r
2508 function testProp(props) {
\r
2509 var style = document.documentElement.style;
\r
2511 for (var i = 0; i < props.length; i++) {
\r
2512 if (props[i] in style) {
\r
2519 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
\r
2520 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
\r
2521 // and optionally scaled by `scale`. Does not have an effect if the
\r
2522 // browser doesn't support 3D CSS transforms.
\r
2523 function setTransform(el, offset, scale) {
\r
2524 var pos = offset || new Point(0, 0);
\r
2526 el.style[TRANSFORM] =
\r
2528 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
\r
2529 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
\r
2530 (scale ? ' scale(' + scale + ')' : '');
\r
2533 // @function setPosition(el: HTMLElement, position: Point)
\r
2534 // Sets the position of `el` to coordinates specified by `position`,
\r
2535 // using CSS translate or top/left positioning depending on the browser
\r
2536 // (used by Leaflet internally to position its layers).
\r
2537 function setPosition(el, point) {
\r
2539 /*eslint-disable */
\r
2540 el._leaflet_pos = point;
\r
2541 /* eslint-enable */
\r
2543 if (Browser.any3d) {
\r
2544 setTransform(el, point);
\r
2546 el.style.left = point.x + 'px';
\r
2547 el.style.top = point.y + 'px';
\r
2551 // @function getPosition(el: HTMLElement): Point
\r
2552 // Returns the coordinates of an element previously positioned with setPosition.
\r
2553 function getPosition(el) {
\r
2554 // this method is only used for elements previously positioned using setPosition,
\r
2555 // so it's safe to cache the position for performance
\r
2557 return el._leaflet_pos || new Point(0, 0);
\r
2560 // @function disableTextSelection()
\r
2561 // Prevents the user from generating `selectstart` DOM events, usually generated
\r
2562 // when the user drags the mouse through a page with text. Used internally
\r
2563 // by Leaflet to override the behaviour of any click-and-drag interaction on
\r
2564 // the map. Affects drag interactions on the whole document.
\r
2566 // @function enableTextSelection()
\r
2567 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
\r
2568 var disableTextSelection;
\r
2569 var enableTextSelection;
\r
2571 if ('onselectstart' in document) {
\r
2572 disableTextSelection = function () {
\r
2573 on(window, 'selectstart', preventDefault);
\r
2575 enableTextSelection = function () {
\r
2576 off(window, 'selectstart', preventDefault);
\r
2579 var userSelectProperty = testProp(
\r
2580 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
\r
2582 disableTextSelection = function () {
\r
2583 if (userSelectProperty) {
\r
2584 var style = document.documentElement.style;
\r
2585 _userSelect = style[userSelectProperty];
\r
2586 style[userSelectProperty] = 'none';
\r
2589 enableTextSelection = function () {
\r
2590 if (userSelectProperty) {
\r
2591 document.documentElement.style[userSelectProperty] = _userSelect;
\r
2592 _userSelect = undefined;
\r
2597 // @function disableImageDrag()
\r
2598 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
\r
2599 // for `dragstart` DOM events, usually generated when the user drags an image.
\r
2600 function disableImageDrag() {
\r
2601 on(window, 'dragstart', preventDefault);
\r
2604 // @function enableImageDrag()
\r
2605 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
\r
2606 function enableImageDrag() {
\r
2607 off(window, 'dragstart', preventDefault);
\r
2610 var _outlineElement, _outlineStyle;
\r
2611 // @function preventOutline(el: HTMLElement)
\r
2612 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
\r
2613 // of the element `el` invisible. Used internally by Leaflet to prevent
\r
2614 // focusable elements from displaying an outline when the user performs a
\r
2615 // drag interaction on them.
\r
2616 function preventOutline(element) {
\r
2617 while (element.tabIndex === -1) {
\r
2618 element = element.parentNode;
\r
2620 if (!element.style) { return; }
\r
2622 _outlineElement = element;
\r
2623 _outlineStyle = element.style.outlineStyle;
\r
2624 element.style.outlineStyle = 'none';
\r
2625 on(window, 'keydown', restoreOutline);
\r
2628 // @function restoreOutline()
\r
2629 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
\r
2630 function restoreOutline() {
\r
2631 if (!_outlineElement) { return; }
\r
2632 _outlineElement.style.outlineStyle = _outlineStyle;
\r
2633 _outlineElement = undefined;
\r
2634 _outlineStyle = undefined;
\r
2635 off(window, 'keydown', restoreOutline);
\r
2638 // @function getSizedParentNode(el: HTMLElement): HTMLElement
\r
2639 // Finds the closest parent node which size (width and height) is not null.
\r
2640 function getSizedParentNode(element) {
\r
2642 element = element.parentNode;
\r
2643 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
\r
2647 // @function getScale(el: HTMLElement): Object
\r
2648 // Computes the CSS scale currently applied on the element.
\r
2649 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
\r
2650 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
\r
2651 function getScale(element) {
\r
2652 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
\r
2655 x: rect.width / element.offsetWidth || 1,
\r
2656 y: rect.height / element.offsetHeight || 1,
\r
2657 boundingClientRect: rect
\r
2663 TRANSFORM: TRANSFORM,
2664 TRANSITION: TRANSITION,
2665 TRANSITION_END: TRANSITION_END,
2675 removeClass: removeClass,
2678 setOpacity: setOpacity,
2680 setTransform: setTransform,
2681 setPosition: setPosition,
2682 getPosition: getPosition,
2683 get disableTextSelection () { return disableTextSelection; },
2684 get enableTextSelection () { return enableTextSelection; },
2685 disableImageDrag: disableImageDrag,
2686 enableImageDrag: enableImageDrag,
2687 preventOutline: preventOutline,
2688 restoreOutline: restoreOutline,
2689 getSizedParentNode: getSizedParentNode,
2694 * @namespace DomEvent
\r
2695 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
\r
2698 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
\r
2700 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
\r
2701 // Adds a listener function (`fn`) to a particular DOM event type of the
\r
2702 // element `el`. You can optionally specify the context of the listener
\r
2703 // (object the `this` keyword will point to). You can also pass several
\r
2704 // space-separated types (e.g. `'click dblclick'`).
\r
2707 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
\r
2708 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
\r
2709 function on(obj, types, fn, context) {
\r
2711 if (types && typeof types === 'object') {
\r
2712 for (var type in types) {
\r
2713 addOne(obj, type, types[type], fn);
\r
2716 types = splitWords(types);
\r
2718 for (var i = 0, len = types.length; i < len; i++) {
\r
2719 addOne(obj, types[i], fn, context);
\r
2726 var eventsKey = '_leaflet_events';
\r
2728 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
\r
2729 // Removes a previously added listener function.
\r
2730 // Note that if you passed a custom context to on, you must pass the same
\r
2731 // context to `off` in order to remove the listener.
\r
2734 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
\r
2735 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
\r
2738 // @function off(el: HTMLElement, types: String): this
\r
2739 // Removes all previously added listeners of given types.
\r
2742 // @function off(el: HTMLElement): this
\r
2743 // Removes all previously added listeners from given HTMLElement
\r
2744 function off(obj, types, fn, context) {
\r
2746 if (arguments.length === 1) {
\r
2748 delete obj[eventsKey];
\r
2750 } else if (types && typeof types === 'object') {
\r
2751 for (var type in types) {
\r
2752 removeOne(obj, type, types[type], fn);
\r
2756 types = splitWords(types);
\r
2758 if (arguments.length === 2) {
\r
2759 batchRemove(obj, function (type) {
\r
2760 return indexOf(types, type) !== -1;
\r
2763 for (var i = 0, len = types.length; i < len; i++) {
\r
2764 removeOne(obj, types[i], fn, context);
\r
2772 function batchRemove(obj, filterFn) {
\r
2773 for (var id in obj[eventsKey]) {
\r
2774 var type = id.split(/\d/)[0];
\r
2775 if (!filterFn || filterFn(type)) {
\r
2776 removeOne(obj, type, null, null, id);
\r
2781 var mouseSubst = {
\r
2782 mouseenter: 'mouseover',
\r
2783 mouseleave: 'mouseout',
\r
2784 wheel: !('onwheel' in window) && 'mousewheel'
\r
2787 function addOne(obj, type, fn, context) {
\r
2788 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
\r
2790 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
\r
2792 var handler = function (e) {
\r
2793 return fn.call(context || obj, e || window.event);
\r
2796 var originalHandler = handler;
\r
2798 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
\r
2799 // Needs DomEvent.Pointer.js
\r
2800 handler = addPointerListener(obj, type, handler);
\r
2802 } else if (Browser.touch && (type === 'dblclick')) {
\r
2803 handler = addDoubleTapListener(obj, handler);
\r
2805 } else if ('addEventListener' in obj) {
\r
2807 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
\r
2808 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);
\r
2810 } else if (type === 'mouseenter' || type === 'mouseleave') {
\r
2811 handler = function (e) {
\r
2812 e = e || window.event;
\r
2813 if (isExternalTarget(obj, e)) {
\r
2814 originalHandler(e);
\r
2817 obj.addEventListener(mouseSubst[type], handler, false);
\r
2820 obj.addEventListener(type, originalHandler, false);
\r
2824 obj.attachEvent('on' + type, handler);
\r
2827 obj[eventsKey] = obj[eventsKey] || {};
\r
2828 obj[eventsKey][id] = handler;
\r
2831 function removeOne(obj, type, fn, context, id) {
\r
2832 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
\r
2833 var handler = obj[eventsKey] && obj[eventsKey][id];
\r
2835 if (!handler) { return this; }
\r
2837 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
\r
2838 removePointerListener(obj, type, handler);
\r
2840 } else if (Browser.touch && (type === 'dblclick')) {
\r
2841 removeDoubleTapListener(obj, handler);
\r
2843 } else if ('removeEventListener' in obj) {
\r
2845 obj.removeEventListener(mouseSubst[type] || type, handler, false);
\r
2848 obj.detachEvent('on' + type, handler);
\r
2851 obj[eventsKey][id] = null;
\r
2854 // @function stopPropagation(ev: DOMEvent): this
\r
2855 // Stop the given event from propagation to parent elements. Used inside the listener functions:
\r
2857 // L.DomEvent.on(div, 'click', function (ev) {
\r
2858 // L.DomEvent.stopPropagation(ev);
\r
2861 function stopPropagation(e) {
\r
2863 if (e.stopPropagation) {
\r
2864 e.stopPropagation();
\r
2865 } else if (e.originalEvent) { // In case of Leaflet event.
\r
2866 e.originalEvent._stopped = true;
\r
2868 e.cancelBubble = true;
\r
2874 // @function disableScrollPropagation(el: HTMLElement): this
\r
2875 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
\r
2876 function disableScrollPropagation(el) {
\r
2877 addOne(el, 'wheel', stopPropagation);
\r
2881 // @function disableClickPropagation(el: HTMLElement): this
\r
2882 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
\r
2883 // `'mousedown'` and `'touchstart'` events (plus browser variants).
\r
2884 function disableClickPropagation(el) {
\r
2885 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
\r
2886 el['_leaflet_disable_click'] = true;
\r
2890 // @function preventDefault(ev: DOMEvent): this
\r
2891 // Prevents the default action of the DOM Event `ev` from happening (such as
\r
2892 // following a link in the href of the a element, or doing a POST request
\r
2893 // with page reload when a `<form>` is submitted).
\r
2894 // Use it inside listener functions.
\r
2895 function preventDefault(e) {
\r
2896 if (e.preventDefault) {
\r
2897 e.preventDefault();
\r
2899 e.returnValue = false;
\r
2904 // @function stop(ev: DOMEvent): this
\r
2905 // Does `stopPropagation` and `preventDefault` at the same time.
\r
2906 function stop(e) {
\r
2907 preventDefault(e);
\r
2908 stopPropagation(e);
\r
2912 // @function getPropagationPath(ev: DOMEvent): Array
\r
2913 // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).
\r
2914 // Returns an array containing the `HTMLElement`s that the given DOM event
\r
2915 // should propagate to (if not stopped).
\r
2916 function getPropagationPath(ev) {
\r
2917 if (ev.composedPath) {
\r
2918 return ev.composedPath();
\r
2922 var el = ev.target;
\r
2926 el = el.parentNode;
\r
2932 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
\r
2933 // Gets normalized mouse position from a DOM event relative to the
\r
2934 // `container` (border excluded) or to the whole page if not specified.
\r
2935 function getMousePosition(e, container) {
\r
2937 return new Point(e.clientX, e.clientY);
\r
2940 var scale = getScale(container),
\r
2941 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
\r
2944 // offset.left/top values are in page scale (like clientX/Y),
\r
2945 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
\r
2946 (e.clientX - offset.left) / scale.x - container.clientLeft,
\r
2947 (e.clientY - offset.top) / scale.y - container.clientTop
\r
2952 // except , Safari and
\r
2953 // We need double the scroll pixels (see #7403 and #4538) for all Browsers
\r
2954 // except OSX (Mac) -> 3x, Chrome running on Linux 1x
\r
2956 var wheelPxFactor =
\r
2957 (Browser.linux && Browser.chrome) ? window.devicePixelRatio :
\r
2958 Browser.mac ? window.devicePixelRatio * 3 :
\r
2959 window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;
\r
2960 // @function getWheelDelta(ev: DOMEvent): Number
\r
2961 // Gets normalized wheel delta from a wheel DOM event, in vertical
\r
2962 // pixels scrolled (negative if scrolling down).
\r
2963 // Events from pointing devices without precise scrolling are mapped to
\r
2964 // a best guess of 60 pixels.
\r
2965 function getWheelDelta(e) {
\r
2966 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
\r
2967 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
\r
2968 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
\r
2969 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
\r
2970 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
\r
2971 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
\r
2972 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
\r
2973 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
\r
2977 // check if element really left/entered the event target (for mouseenter/mouseleave)
\r
2978 function isExternalTarget(el, e) {
\r
2980 var related = e.relatedTarget;
\r
2982 if (!related) { return true; }
\r
2985 while (related && (related !== el)) {
\r
2986 related = related.parentNode;
\r
2991 return (related !== el);
\r
2998 stopPropagation: stopPropagation,
2999 disableScrollPropagation: disableScrollPropagation,
3000 disableClickPropagation: disableClickPropagation,
3001 preventDefault: preventDefault,
3003 getPropagationPath: getPropagationPath,
3004 getMousePosition: getMousePosition,
3005 getWheelDelta: getWheelDelta,
3006 isExternalTarget: isExternalTarget,
3012 * @class PosAnimation
3013 * @aka L.PosAnimation
3015 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
3019 * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
3021 * myPositionMarker.on("click", function() {
3022 * var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
3024 * var fx = new L.PosAnimation();
3026 * fx.once('end',function() {
3028 * fx.run(myPositionMarker._icon, pos, 0.8);
3031 * fx.run(myPositionMarker._icon, pos, 0.3);
3036 * @constructor L.PosAnimation()
3037 * Creates a `PosAnimation` object.
3041 var PosAnimation = Evented.extend({
3043 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
3044 // Run an animation of a given element to a new position, optionally setting
3045 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
3046 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
3047 // `0.5` by default).
3048 run: function (el, newPos, duration, easeLinearity) {
3052 this._inProgress = true;
3053 this._duration = duration || 0.25;
3054 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
3056 this._startPos = getPosition(el);
3057 this._offset = newPos.subtract(this._startPos);
3058 this._startTime = +new Date();
3060 // @event start: Event
3061 // Fired when the animation starts
3068 // Stops the animation (if currently running).
3070 if (!this._inProgress) { return; }
3076 _animate: function () {
3078 this._animId = requestAnimFrame(this._animate, this);
3082 _step: function (round) {
3083 var elapsed = (+new Date()) - this._startTime,
3084 duration = this._duration * 1000;
3086 if (elapsed < duration) {
3087 this._runFrame(this._easeOut(elapsed / duration), round);
3094 _runFrame: function (progress, round) {
3095 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3099 setPosition(this._el, pos);
3101 // @event step: Event
3102 // Fired continuously during the animation.
3106 _complete: function () {
3107 cancelAnimFrame(this._animId);
3109 this._inProgress = false;
3110 // @event end: Event
3111 // Fired when the animation ends.
3115 _easeOut: function (t) {
3116 return 1 - Math.pow(1 - t, this._easeOutPower);
3123 * @inherits Evented
\r
3125 * The central class of the API — it is used to create a map on a page and manipulate it.
\r
3130 * // initialize the map on the "map" div with a given center and zoom
\r
3131 * var map = L.map('map', {
\r
3132 * center: [51.505, -0.09],
\r
3139 var Map = Evented.extend({
\r
3142 // @section Map State Options
\r
3143 // @option crs: CRS = L.CRS.EPSG3857
\r
3144 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
\r
3145 // sure what it means.
\r
3148 // @option center: LatLng = undefined
\r
3149 // Initial geographic center of the map
\r
3150 center: undefined,
\r
3152 // @option zoom: Number = undefined
\r
3153 // Initial map zoom level
\r
3156 // @option minZoom: Number = *
\r
3157 // Minimum zoom level of the map.
\r
3158 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
\r
3159 // the lowest of their `minZoom` options will be used instead.
\r
3160 minZoom: undefined,
\r
3162 // @option maxZoom: Number = *
\r
3163 // Maximum zoom level of the map.
\r
3164 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
\r
3165 // the highest of their `maxZoom` options will be used instead.
\r
3166 maxZoom: undefined,
\r
3168 // @option layers: Layer[] = []
\r
3169 // Array of layers that will be added to the map initially
\r
3172 // @option maxBounds: LatLngBounds = null
\r
3173 // When this option is set, the map restricts the view to the given
\r
3174 // geographical bounds, bouncing the user back if the user tries to pan
\r
3175 // outside the view. To set the restriction dynamically, use
\r
3176 // [`setMaxBounds`](#map-setmaxbounds) method.
\r
3177 maxBounds: undefined,
\r
3179 // @option renderer: Renderer = *
\r
3180 // The default method for drawing vector layers on the map. `L.SVG`
\r
3181 // or `L.Canvas` by default depending on browser support.
\r
3182 renderer: undefined,
\r
3185 // @section Animation Options
\r
3186 // @option zoomAnimation: Boolean = true
\r
3187 // Whether the map zoom animation is enabled. By default it's enabled
\r
3188 // in all browsers that support CSS3 Transitions except Android.
\r
3189 zoomAnimation: true,
\r
3191 // @option zoomAnimationThreshold: Number = 4
\r
3192 // Won't animate zoom if the zoom difference exceeds this value.
\r
3193 zoomAnimationThreshold: 4,
\r
3195 // @option fadeAnimation: Boolean = true
\r
3196 // Whether the tile fade animation is enabled. By default it's enabled
\r
3197 // in all browsers that support CSS3 Transitions except Android.
\r
3198 fadeAnimation: true,
\r
3200 // @option markerZoomAnimation: Boolean = true
\r
3201 // Whether markers animate their zoom with the zoom animation, if disabled
\r
3202 // they will disappear for the length of the animation. By default it's
\r
3203 // enabled in all browsers that support CSS3 Transitions except Android.
\r
3204 markerZoomAnimation: true,
\r
3206 // @option transform3DLimit: Number = 2^23
\r
3207 // Defines the maximum size of a CSS translation transform. The default
\r
3208 // value should not be changed unless a web browser positions layers in
\r
3209 // the wrong place after doing a large `panBy`.
\r
3210 transform3DLimit: 8388608, // Precision limit of a 32-bit float
\r
3212 // @section Interaction Options
\r
3213 // @option zoomSnap: Number = 1
\r
3214 // Forces the map's zoom level to always be a multiple of this, particularly
\r
3215 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
\r
3216 // By default, the zoom level snaps to the nearest integer; lower values
\r
3217 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
\r
3218 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
\r
3221 // @option zoomDelta: Number = 1
\r
3222 // Controls how much the map's zoom level will change after a
\r
3223 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
\r
3224 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
\r
3225 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
\r
3228 // @option trackResize: Boolean = true
\r
3229 // Whether the map automatically handles browser window resize to update itself.
\r
3233 initialize: function (id, options) { // (HTMLElement or String, Object)
\r
3234 options = setOptions(this, options);
\r
3236 // Make sure to assign internal flags at the beginning,
\r
3237 // to avoid inconsistent state in some edge cases.
\r
3238 this._handlers = [];
\r
3239 this._layers = {};
\r
3240 this._zoomBoundLayers = {};
\r
3241 this._sizeChanged = true;
\r
3243 this._initContainer(id);
\r
3244 this._initLayout();
\r
3246 // hack for https://github.com/Leaflet/Leaflet/issues/1980
\r
3247 this._onResize = bind(this._onResize, this);
\r
3249 this._initEvents();
\r
3251 if (options.maxBounds) {
\r
3252 this.setMaxBounds(options.maxBounds);
\r
3255 if (options.zoom !== undefined) {
\r
3256 this._zoom = this._limitZoom(options.zoom);
\r
3259 if (options.center && options.zoom !== undefined) {
\r
3260 this.setView(toLatLng(options.center), options.zoom, {reset: true});
\r
3263 this.callInitHooks();
\r
3265 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
\r
3266 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
\r
3267 this.options.zoomAnimation;
\r
3269 // zoom transitions run with the same duration for all layers, so if one of transitionend events
\r
3270 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
\r
3271 if (this._zoomAnimated) {
\r
3272 this._createAnimProxy();
\r
3273 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
\r
3276 this._addLayers(this.options.layers);
\r
3280 // @section Methods for modifying map state
\r
3282 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
\r
3283 // Sets the view of the map (geographical center and zoom) with the given
\r
3284 // animation options.
\r
3285 setView: function (center, zoom, options) {
\r
3287 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
\r
3288 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
\r
3289 options = options || {};
\r
3293 if (this._loaded && !options.reset && options !== true) {
\r
3295 if (options.animate !== undefined) {
\r
3296 options.zoom = extend({animate: options.animate}, options.zoom);
\r
3297 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
\r
3300 // try animating pan or zoom
\r
3301 var moved = (this._zoom !== zoom) ?
\r
3302 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
\r
3303 this._tryAnimatedPan(center, options.pan);
\r
3306 // prevent resize handler call, the view will refresh after animation anyway
\r
3307 clearTimeout(this._sizeTimer);
\r
3312 // animation didn't start, just reset the map view
\r
3313 this._resetView(center, zoom, options.pan && options.pan.noMoveStart);
\r
3318 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
\r
3319 // Sets the zoom of the map.
\r
3320 setZoom: function (zoom, options) {
\r
3321 if (!this._loaded) {
\r
3322 this._zoom = zoom;
\r
3325 return this.setView(this.getCenter(), zoom, {zoom: options});
\r
3328 // @method zoomIn(delta?: Number, options?: Zoom options): this
\r
3329 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
\r
3330 zoomIn: function (delta, options) {
\r
3331 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
\r
3332 return this.setZoom(this._zoom + delta, options);
\r
3335 // @method zoomOut(delta?: Number, options?: Zoom options): this
\r
3336 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
\r
3337 zoomOut: function (delta, options) {
\r
3338 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
\r
3339 return this.setZoom(this._zoom - delta, options);
\r
3342 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
\r
3343 // Zooms the map while keeping a specified geographical point on the map
\r
3344 // stationary (e.g. used internally for scroll zoom and double-click zoom).
\r
3346 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
\r
3347 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
\r
3348 setZoomAround: function (latlng, zoom, options) {
\r
3349 var scale = this.getZoomScale(zoom),
\r
3350 viewHalf = this.getSize().divideBy(2),
\r
3351 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
\r
3353 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
\r
3354 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
\r
3356 return this.setView(newCenter, zoom, {zoom: options});
\r
3359 _getBoundsCenterZoom: function (bounds, options) {
\r
3361 options = options || {};
\r
3362 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
\r
3364 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
\r
3365 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
\r
3367 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
\r
3369 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
\r
3371 if (zoom === Infinity) {
\r
3373 center: bounds.getCenter(),
\r
3378 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
\r
3380 swPoint = this.project(bounds.getSouthWest(), zoom),
\r
3381 nePoint = this.project(bounds.getNorthEast(), zoom),
\r
3382 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
\r
3390 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
\r
3391 // Sets a map view that contains the given geographical bounds with the
\r
3392 // maximum zoom level possible.
\r
3393 fitBounds: function (bounds, options) {
\r
3395 bounds = toLatLngBounds(bounds);
\r
3397 if (!bounds.isValid()) {
\r
3398 throw new Error('Bounds are not valid.');
\r
3401 var target = this._getBoundsCenterZoom(bounds, options);
\r
3402 return this.setView(target.center, target.zoom, options);
\r
3405 // @method fitWorld(options?: fitBounds options): this
\r
3406 // Sets a map view that mostly contains the whole world with the maximum
\r
3407 // zoom level possible.
\r
3408 fitWorld: function (options) {
\r
3409 return this.fitBounds([[-90, -180], [90, 180]], options);
\r
3412 // @method panTo(latlng: LatLng, options?: Pan options): this
\r
3413 // Pans the map to a given center.
\r
3414 panTo: function (center, options) { // (LatLng)
\r
3415 return this.setView(center, this._zoom, {pan: options});
\r
3418 // @method panBy(offset: Point, options?: Pan options): this
\r
3419 // Pans the map by a given number of pixels (animated).
\r
3420 panBy: function (offset, options) {
\r
3421 offset = toPoint(offset).round();
\r
3422 options = options || {};
\r
3424 if (!offset.x && !offset.y) {
\r
3425 return this.fire('moveend');
\r
3427 // If we pan too far, Chrome gets issues with tiles
\r
3428 // and makes them disappear or appear in the wrong place (slightly offset) #2602
\r
3429 if (options.animate !== true && !this.getSize().contains(offset)) {
\r
3430 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
\r
3434 if (!this._panAnim) {
\r
3435 this._panAnim = new PosAnimation();
\r
3437 this._panAnim.on({
\r
3438 'step': this._onPanTransitionStep,
\r
3439 'end': this._onPanTransitionEnd
\r
3443 // don't fire movestart if animating inertia
\r
3444 if (!options.noMoveStart) {
\r
3445 this.fire('movestart');
\r
3448 // animate pan unless animate: false specified
\r
3449 if (options.animate !== false) {
\r
3450 addClass(this._mapPane, 'leaflet-pan-anim');
\r
3452 var newPos = this._getMapPanePos().subtract(offset).round();
\r
3453 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
\r
3455 this._rawPanBy(offset);
\r
3456 this.fire('move').fire('moveend');
\r
3462 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
\r
3463 // Sets the view of the map (geographical center and zoom) performing a smooth
\r
3464 // pan-zoom animation.
\r
3465 flyTo: function (targetCenter, targetZoom, options) {
\r
3467 options = options || {};
\r
3468 if (options.animate === false || !Browser.any3d) {
\r
3469 return this.setView(targetCenter, targetZoom, options);
\r
3474 var from = this.project(this.getCenter()),
\r
3475 to = this.project(targetCenter),
\r
3476 size = this.getSize(),
\r
3477 startZoom = this._zoom;
\r
3479 targetCenter = toLatLng(targetCenter);
\r
3480 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
\r
3482 var w0 = Math.max(size.x, size.y),
\r
3483 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
\r
3484 u1 = (to.distanceTo(from)) || 1,
\r
3489 var s1 = i ? -1 : 1,
\r
3491 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
\r
3492 b1 = 2 * s2 * rho2 * u1,
\r
3494 sq = Math.sqrt(b * b + 1) - b;
\r
3496 // workaround for floating point precision bug when sq = 0, log = -Infinite,
\r
3497 // thus triggering an infinite loop in flyTo
\r
3498 var log = sq < 0.000000001 ? -18 : Math.log(sq);
\r
3503 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
\r
3504 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
\r
3505 function tanh(n) { return sinh(n) / cosh(n); }
\r
3509 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
\r
3510 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
\r
3512 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
\r
3514 var start = Date.now(),
\r
3515 S = (r(1) - r0) / rho,
\r
3516 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
\r
3518 function frame() {
\r
3519 var t = (Date.now() - start) / duration,
\r
3520 s = easeOut(t) * S;
\r
3523 this._flyToFrame = requestAnimFrame(frame, this);
\r
3526 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
\r
3527 this.getScaleZoom(w0 / w(s), startZoom),
\r
3532 ._move(targetCenter, targetZoom)
\r
3537 this._moveStart(true, options.noMoveStart);
\r
3543 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
\r
3544 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
\r
3545 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
\r
3546 flyToBounds: function (bounds, options) {
\r
3547 var target = this._getBoundsCenterZoom(bounds, options);
\r
3548 return this.flyTo(target.center, target.zoom, options);
\r
3551 // @method setMaxBounds(bounds: LatLngBounds): this
\r
3552 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
\r
3553 setMaxBounds: function (bounds) {
\r
3554 bounds = toLatLngBounds(bounds);
\r
3556 if (this.listens('moveend', this._panInsideMaxBounds)) {
\r
3557 this.off('moveend', this._panInsideMaxBounds);
\r
3560 if (!bounds.isValid()) {
\r
3561 this.options.maxBounds = null;
\r
3565 this.options.maxBounds = bounds;
\r
3567 if (this._loaded) {
\r
3568 this._panInsideMaxBounds();
\r
3571 return this.on('moveend', this._panInsideMaxBounds);
\r
3574 // @method setMinZoom(zoom: Number): this
\r
3575 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
\r
3576 setMinZoom: function (zoom) {
\r
3577 var oldZoom = this.options.minZoom;
\r
3578 this.options.minZoom = zoom;
\r
3580 if (this._loaded && oldZoom !== zoom) {
\r
3581 this.fire('zoomlevelschange');
\r
3583 if (this.getZoom() < this.options.minZoom) {
\r
3584 return this.setZoom(zoom);
\r
3591 // @method setMaxZoom(zoom: Number): this
\r
3592 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
\r
3593 setMaxZoom: function (zoom) {
\r
3594 var oldZoom = this.options.maxZoom;
\r
3595 this.options.maxZoom = zoom;
\r
3597 if (this._loaded && oldZoom !== zoom) {
\r
3598 this.fire('zoomlevelschange');
\r
3600 if (this.getZoom() > this.options.maxZoom) {
\r
3601 return this.setZoom(zoom);
\r
3608 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
\r
3609 // 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.
\r
3610 panInsideBounds: function (bounds, options) {
\r
3611 this._enforcingBounds = true;
\r
3612 var center = this.getCenter(),
\r
3613 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
\r
3615 if (!center.equals(newCenter)) {
\r
3616 this.panTo(newCenter, options);
\r
3619 this._enforcingBounds = false;
\r
3623 // @method panInside(latlng: LatLng, options?: padding options): this
\r
3624 // Pans the map the minimum amount to make the `latlng` visible. Use
\r
3625 // padding options to fit the display to more restricted bounds.
\r
3626 // If `latlng` is already within the (optionally padded) display bounds,
\r
3627 // the map will not be panned.
\r
3628 panInside: function (latlng, options) {
\r
3629 options = options || {};
\r
3631 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
\r
3632 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
\r
3633 pixelCenter = this.project(this.getCenter()),
\r
3634 pixelPoint = this.project(latlng),
\r
3635 pixelBounds = this.getPixelBounds(),
\r
3636 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
\r
3637 paddedSize = paddedBounds.getSize();
\r
3639 if (!paddedBounds.contains(pixelPoint)) {
\r
3640 this._enforcingBounds = true;
\r
3641 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
\r
3642 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
\r
3643 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
\r
3644 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
\r
3645 this.panTo(this.unproject(pixelCenter), options);
\r
3646 this._enforcingBounds = false;
\r
3651 // @method invalidateSize(options: Zoom/pan options): this
\r
3652 // Checks if the map container size changed and updates the map if so —
\r
3653 // call it after you've changed the map size dynamically, also animating
\r
3654 // pan by default. If `options.pan` is `false`, panning will not occur.
\r
3655 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
\r
3656 // that it doesn't happen often even if the method is called many
\r
3657 // times in a row.
\r
3660 // @method invalidateSize(animate: Boolean): this
\r
3661 // Checks if the map container size changed and updates the map if so —
\r
3662 // call it after you've changed the map size dynamically, also animating
\r
3663 // pan by default.
\r
3664 invalidateSize: function (options) {
\r
3665 if (!this._loaded) { return this; }
\r
3667 options = extend({
\r
3670 }, options === true ? {animate: true} : options);
\r
3672 var oldSize = this.getSize();
\r
3673 this._sizeChanged = true;
\r
3674 this._lastCenter = null;
\r
3676 var newSize = this.getSize(),
\r
3677 oldCenter = oldSize.divideBy(2).round(),
\r
3678 newCenter = newSize.divideBy(2).round(),
\r
3679 offset = oldCenter.subtract(newCenter);
\r
3681 if (!offset.x && !offset.y) { return this; }
\r
3683 if (options.animate && options.pan) {
\r
3684 this.panBy(offset);
\r
3687 if (options.pan) {
\r
3688 this._rawPanBy(offset);
\r
3691 this.fire('move');
\r
3693 if (options.debounceMoveend) {
\r
3694 clearTimeout(this._sizeTimer);
\r
3695 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
\r
3697 this.fire('moveend');
\r
3701 // @section Map state change events
\r
3702 // @event resize: ResizeEvent
\r
3703 // Fired when the map is resized.
\r
3704 return this.fire('resize', {
\r
3710 // @section Methods for modifying map state
\r
3711 // @method stop(): this
\r
3712 // Stops the currently running `panTo` or `flyTo` animation, if any.
\r
3713 stop: function () {
\r
3714 this.setZoom(this._limitZoom(this._zoom));
\r
3715 if (!this.options.zoomSnap) {
\r
3716 this.fire('viewreset');
\r
3718 return this._stop();
\r
3721 // @section Geolocation methods
\r
3722 // @method locate(options?: Locate options): this
\r
3723 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
\r
3724 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
\r
3725 // and optionally sets the map view to the user's location with respect to
\r
3726 // detection accuracy (or to the world view if geolocation failed).
\r
3727 // Note that, if your page doesn't use HTTPS, this method will fail in
\r
3728 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
\r
3729 // See `Locate options` for more details.
\r
3730 locate: function (options) {
\r
3732 options = this._locateOptions = extend({
\r
3736 // maxZoom: <Number>
\r
3738 // enableHighAccuracy: false
\r
3741 if (!('geolocation' in navigator)) {
\r
3742 this._handleGeolocationError({
\r
3744 message: 'Geolocation not supported.'
\r
3749 var onResponse = bind(this._handleGeolocationResponse, this),
\r
3750 onError = bind(this._handleGeolocationError, this);
\r
3752 if (options.watch) {
\r
3753 this._locationWatchId =
\r
3754 navigator.geolocation.watchPosition(onResponse, onError, options);
\r
3756 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
\r
3761 // @method stopLocate(): this
\r
3762 // Stops watching location previously initiated by `map.locate({watch: true})`
\r
3763 // and aborts resetting the map view if map.locate was called with
\r
3764 // `{setView: true}`.
\r
3765 stopLocate: function () {
\r
3766 if (navigator.geolocation && navigator.geolocation.clearWatch) {
\r
3767 navigator.geolocation.clearWatch(this._locationWatchId);
\r
3769 if (this._locateOptions) {
\r
3770 this._locateOptions.setView = false;
\r
3775 _handleGeolocationError: function (error) {
\r
3776 if (!this._container._leaflet_id) { return; }
\r
3778 var c = error.code,
\r
3779 message = error.message ||
\r
3780 (c === 1 ? 'permission denied' :
\r
3781 (c === 2 ? 'position unavailable' : 'timeout'));
\r
3783 if (this._locateOptions.setView && !this._loaded) {
\r
3787 // @section Location events
\r
3788 // @event locationerror: ErrorEvent
\r
3789 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
\r
3790 this.fire('locationerror', {
\r
3792 message: 'Geolocation error: ' + message + '.'
\r
3796 _handleGeolocationResponse: function (pos) {
\r
3797 if (!this._container._leaflet_id) { return; }
\r
3799 var lat = pos.coords.latitude,
\r
3800 lng = pos.coords.longitude,
\r
3801 latlng = new LatLng(lat, lng),
\r
3802 bounds = latlng.toBounds(pos.coords.accuracy * 2),
\r
3803 options = this._locateOptions;
\r
3805 if (options.setView) {
\r
3806 var zoom = this.getBoundsZoom(bounds);
\r
3807 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
\r
3813 timestamp: pos.timestamp
\r
3816 for (var i in pos.coords) {
\r
3817 if (typeof pos.coords[i] === 'number') {
\r
3818 data[i] = pos.coords[i];
\r
3822 // @event locationfound: LocationEvent
\r
3823 // Fired when geolocation (using the [`locate`](#map-locate) method)
\r
3824 // went successfully.
\r
3825 this.fire('locationfound', data);
\r
3828 // TODO Appropriate docs section?
\r
3829 // @section Other Methods
\r
3830 // @method addHandler(name: String, HandlerClass: Function): this
\r
3831 // Adds a new `Handler` to the map, given its name and constructor function.
\r
3832 addHandler: function (name, HandlerClass) {
\r
3833 if (!HandlerClass) { return this; }
\r
3835 var handler = this[name] = new HandlerClass(this);
\r
3837 this._handlers.push(handler);
\r
3839 if (this.options[name]) {
\r
3846 // @method remove(): this
\r
3847 // Destroys the map and clears all related event listeners.
\r
3848 remove: function () {
\r
3850 this._initEvents(true);
\r
3851 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
\r
3853 if (this._containerId !== this._container._leaflet_id) {
\r
3854 throw new Error('Map container is being reused by another instance');
\r
3858 // throws error in IE6-8
\r
3859 delete this._container._leaflet_id;
\r
3860 delete this._containerId;
\r
3862 /*eslint-disable */
\r
3863 this._container._leaflet_id = undefined;
\r
3864 /* eslint-enable */
\r
3865 this._containerId = undefined;
\r
3868 if (this._locationWatchId !== undefined) {
\r
3869 this.stopLocate();
\r
3874 remove(this._mapPane);
\r
3876 if (this._clearControlPos) {
\r
3877 this._clearControlPos();
\r
3879 if (this._resizeRequest) {
\r
3880 cancelAnimFrame(this._resizeRequest);
\r
3881 this._resizeRequest = null;
\r
3884 this._clearHandlers();
\r
3886 if (this._loaded) {
\r
3887 // @section Map state change events
\r
3888 // @event unload: Event
\r
3889 // Fired when the map is destroyed with [remove](#map-remove) method.
\r
3890 this.fire('unload');
\r
3894 for (i in this._layers) {
\r
3895 this._layers[i].remove();
\r
3897 for (i in this._panes) {
\r
3898 remove(this._panes[i]);
\r
3901 this._layers = [];
\r
3903 delete this._mapPane;
\r
3904 delete this._renderer;
\r
3909 // @section Other Methods
\r
3910 // @method createPane(name: String, container?: HTMLElement): HTMLElement
\r
3911 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
\r
3912 // then returns it. The pane is created as a child of `container`, or
\r
3913 // as a child of the main map pane if not set.
\r
3914 createPane: function (name, container) {
\r
3915 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
\r
3916 pane = create$1('div', className, container || this._mapPane);
\r
3919 this._panes[name] = pane;
\r
3924 // @section Methods for Getting Map State
\r
3926 // @method getCenter(): LatLng
\r
3927 // Returns the geographical center of the map view
\r
3928 getCenter: function () {
\r
3929 this._checkIfLoaded();
\r
3931 if (this._lastCenter && !this._moved()) {
\r
3932 return this._lastCenter.clone();
\r
3934 return this.layerPointToLatLng(this._getCenterLayerPoint());
\r
3937 // @method getZoom(): Number
\r
3938 // Returns the current zoom level of the map view
\r
3939 getZoom: function () {
\r
3940 return this._zoom;
\r
3943 // @method getBounds(): LatLngBounds
\r
3944 // Returns the geographical bounds visible in the current map view
\r
3945 getBounds: function () {
\r
3946 var bounds = this.getPixelBounds(),
\r
3947 sw = this.unproject(bounds.getBottomLeft()),
\r
3948 ne = this.unproject(bounds.getTopRight());
\r
3950 return new LatLngBounds(sw, ne);
\r
3953 // @method getMinZoom(): Number
\r
3954 // 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.
\r
3955 getMinZoom: function () {
\r
3956 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
\r
3959 // @method getMaxZoom(): Number
\r
3960 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
\r
3961 getMaxZoom: function () {
\r
3962 return this.options.maxZoom === undefined ?
\r
3963 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
\r
3964 this.options.maxZoom;
\r
3967 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
\r
3968 // Returns the maximum zoom level on which the given bounds fit to the map
\r
3969 // view in its entirety. If `inside` (optional) is set to `true`, the method
\r
3970 // instead returns the minimum zoom level on which the map view fits into
\r
3971 // the given bounds in its entirety.
\r
3972 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
\r
3973 bounds = toLatLngBounds(bounds);
\r
3974 padding = toPoint(padding || [0, 0]);
\r
3976 var zoom = this.getZoom() || 0,
\r
3977 min = this.getMinZoom(),
\r
3978 max = this.getMaxZoom(),
\r
3979 nw = bounds.getNorthWest(),
\r
3980 se = bounds.getSouthEast(),
\r
3981 size = this.getSize().subtract(padding),
\r
3982 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
\r
3983 snap = Browser.any3d ? this.options.zoomSnap : 1,
\r
3984 scalex = size.x / boundsSize.x,
\r
3985 scaley = size.y / boundsSize.y,
\r
3986 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
\r
3988 zoom = this.getScaleZoom(scale, zoom);
\r
3991 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
\r
3992 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
\r
3995 return Math.max(min, Math.min(max, zoom));
\r
3998 // @method getSize(): Point
\r
3999 // Returns the current size of the map container (in pixels).
\r
4000 getSize: function () {
\r
4001 if (!this._size || this._sizeChanged) {
\r
4002 this._size = new Point(
\r
4003 this._container.clientWidth || 0,
\r
4004 this._container.clientHeight || 0);
\r
4006 this._sizeChanged = false;
\r
4008 return this._size.clone();
\r
4011 // @method getPixelBounds(): Bounds
\r
4012 // Returns the bounds of the current map view in projected pixel
\r
4013 // coordinates (sometimes useful in layer and overlay implementations).
\r
4014 getPixelBounds: function (center, zoom) {
\r
4015 var topLeftPoint = this._getTopLeftPoint(center, zoom);
\r
4016 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
\r
4019 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
\r
4020 // the map pane? "left point of the map layer" can be confusing, specially
\r
4021 // since there can be negative offsets.
\r
4022 // @method getPixelOrigin(): Point
\r
4023 // Returns the projected pixel coordinates of the top left point of
\r
4024 // the map layer (useful in custom layer and overlay implementations).
\r
4025 getPixelOrigin: function () {
\r
4026 this._checkIfLoaded();
\r
4027 return this._pixelOrigin;
\r
4030 // @method getPixelWorldBounds(zoom?: Number): Bounds
\r
4031 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
\r
4032 // If `zoom` is omitted, the map's current zoom level is used.
\r
4033 getPixelWorldBounds: function (zoom) {
\r
4034 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
\r
4037 // @section Other Methods
\r
4039 // @method getPane(pane: String|HTMLElement): HTMLElement
\r
4040 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
\r
4041 getPane: function (pane) {
\r
4042 return typeof pane === 'string' ? this._panes[pane] : pane;
\r
4045 // @method getPanes(): Object
\r
4046 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
\r
4047 // the panes as values.
\r
4048 getPanes: function () {
\r
4049 return this._panes;
\r
4052 // @method getContainer: HTMLElement
\r
4053 // Returns the HTML element that contains the map.
\r
4054 getContainer: function () {
\r
4055 return this._container;
\r
4059 // @section Conversion Methods
\r
4061 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
\r
4062 // Returns the scale factor to be applied to a map transition from zoom level
\r
4063 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
\r
4064 getZoomScale: function (toZoom, fromZoom) {
\r
4065 // TODO replace with universal implementation after refactoring projections
\r
4066 var crs = this.options.crs;
\r
4067 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
\r
4068 return crs.scale(toZoom) / crs.scale(fromZoom);
\r
4071 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
\r
4072 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
\r
4073 // level and everything is scaled by a factor of `scale`. Inverse of
\r
4074 // [`getZoomScale`](#map-getZoomScale).
\r
4075 getScaleZoom: function (scale, fromZoom) {
\r
4076 var crs = this.options.crs;
\r
4077 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
\r
4078 var zoom = crs.zoom(scale * crs.scale(fromZoom));
\r
4079 return isNaN(zoom) ? Infinity : zoom;
\r
4082 // @method project(latlng: LatLng, zoom: Number): Point
\r
4083 // Projects a geographical coordinate `LatLng` according to the projection
\r
4084 // of the map's CRS, then scales it according to `zoom` and the CRS's
\r
4085 // `Transformation`. The result is pixel coordinate relative to
\r
4086 // the CRS origin.
\r
4087 project: function (latlng, zoom) {
\r
4088 zoom = zoom === undefined ? this._zoom : zoom;
\r
4089 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
\r
4092 // @method unproject(point: Point, zoom: Number): LatLng
\r
4093 // Inverse of [`project`](#map-project).
\r
4094 unproject: function (point, zoom) {
\r
4095 zoom = zoom === undefined ? this._zoom : zoom;
\r
4096 return this.options.crs.pointToLatLng(toPoint(point), zoom);
\r
4099 // @method layerPointToLatLng(point: Point): LatLng
\r
4100 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
\r
4101 // returns the corresponding geographical coordinate (for the current zoom level).
\r
4102 layerPointToLatLng: function (point) {
\r
4103 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
\r
4104 return this.unproject(projectedPoint);
\r
4107 // @method latLngToLayerPoint(latlng: LatLng): Point
\r
4108 // Given a geographical coordinate, returns the corresponding pixel coordinate
\r
4109 // relative to the [origin pixel](#map-getpixelorigin).
\r
4110 latLngToLayerPoint: function (latlng) {
\r
4111 var projectedPoint = this.project(toLatLng(latlng))._round();
\r
4112 return projectedPoint._subtract(this.getPixelOrigin());
\r
4115 // @method wrapLatLng(latlng: LatLng): LatLng
\r
4116 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
\r
4117 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
\r
4119 // By default this means longitude is wrapped around the dateline so its
\r
4120 // value is between -180 and +180 degrees.
\r
4121 wrapLatLng: function (latlng) {
\r
4122 return this.options.crs.wrapLatLng(toLatLng(latlng));
\r
4125 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
\r
4126 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
\r
4127 // its center is within the CRS's bounds.
\r
4128 // By default this means the center longitude is wrapped around the dateline so its
\r
4129 // value is between -180 and +180 degrees, and the majority of the bounds
\r
4130 // overlaps the CRS's bounds.
\r
4131 wrapLatLngBounds: function (latlng) {
\r
4132 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
\r
4135 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
\r
4136 // Returns the distance between two geographical coordinates according to
\r
4137 // the map's CRS. By default this measures distance in meters.
\r
4138 distance: function (latlng1, latlng2) {
\r
4139 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
\r
4142 // @method containerPointToLayerPoint(point: Point): Point
\r
4143 // Given a pixel coordinate relative to the map container, returns the corresponding
\r
4144 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
\r
4145 containerPointToLayerPoint: function (point) { // (Point)
\r
4146 return toPoint(point).subtract(this._getMapPanePos());
\r
4149 // @method layerPointToContainerPoint(point: Point): Point
\r
4150 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
\r
4151 // returns the corresponding pixel coordinate relative to the map container.
\r
4152 layerPointToContainerPoint: function (point) { // (Point)
\r
4153 return toPoint(point).add(this._getMapPanePos());
\r
4156 // @method containerPointToLatLng(point: Point): LatLng
\r
4157 // Given a pixel coordinate relative to the map container, returns
\r
4158 // the corresponding geographical coordinate (for the current zoom level).
\r
4159 containerPointToLatLng: function (point) {
\r
4160 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
\r
4161 return this.layerPointToLatLng(layerPoint);
\r
4164 // @method latLngToContainerPoint(latlng: LatLng): Point
\r
4165 // Given a geographical coordinate, returns the corresponding pixel coordinate
\r
4166 // relative to the map container.
\r
4167 latLngToContainerPoint: function (latlng) {
\r
4168 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
\r
4171 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
\r
4172 // Given a MouseEvent object, returns the pixel coordinate relative to the
\r
4173 // map container where the event took place.
\r
4174 mouseEventToContainerPoint: function (e) {
\r
4175 return getMousePosition(e, this._container);
\r
4178 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
\r
4179 // Given a MouseEvent object, returns the pixel coordinate relative to
\r
4180 // the [origin pixel](#map-getpixelorigin) where the event took place.
\r
4181 mouseEventToLayerPoint: function (e) {
\r
4182 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
\r
4185 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
\r
4186 // Given a MouseEvent object, returns geographical coordinate where the
\r
4187 // event took place.
\r
4188 mouseEventToLatLng: function (e) { // (MouseEvent)
\r
4189 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
\r
4193 // map initialization methods
\r
4195 _initContainer: function (id) {
\r
4196 var container = this._container = get(id);
\r
4199 throw new Error('Map container not found.');
\r
4200 } else if (container._leaflet_id) {
\r
4201 throw new Error('Map container is already initialized.');
\r
4204 on(container, 'scroll', this._onScroll, this);
\r
4205 this._containerId = stamp(container);
\r
4208 _initLayout: function () {
\r
4209 var container = this._container;
\r
4211 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
\r
4213 addClass(container, 'leaflet-container' +
\r
4214 (Browser.touch ? ' leaflet-touch' : '') +
\r
4215 (Browser.retina ? ' leaflet-retina' : '') +
\r
4216 (Browser.ielt9 ? ' leaflet-oldie' : '') +
\r
4217 (Browser.safari ? ' leaflet-safari' : '') +
\r
4218 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
\r
4220 var position = getStyle(container, 'position');
\r
4222 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
\r
4223 container.style.position = 'relative';
\r
4226 this._initPanes();
\r
4228 if (this._initControlPos) {
\r
4229 this._initControlPos();
\r
4233 _initPanes: function () {
\r
4234 var panes = this._panes = {};
\r
4235 this._paneRenderers = {};
\r
4239 // Panes are DOM elements used to control the ordering of layers on the map. You
\r
4240 // can access panes with [`map.getPane`](#map-getpane) or
\r
4241 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
\r
4242 // [`map.createPane`](#map-createpane) method.
\r
4244 // Every map has the following default panes that differ only in zIndex.
\r
4246 // @pane mapPane: HTMLElement = 'auto'
\r
4247 // Pane that contains all other map panes
\r
4249 this._mapPane = this.createPane('mapPane', this._container);
\r
4250 setPosition(this._mapPane, new Point(0, 0));
\r
4252 // @pane tilePane: HTMLElement = 200
\r
4253 // Pane for `GridLayer`s and `TileLayer`s
\r
4254 this.createPane('tilePane');
\r
4255 // @pane overlayPane: HTMLElement = 400
\r
4256 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
\r
4257 this.createPane('overlayPane');
\r
4258 // @pane shadowPane: HTMLElement = 500
\r
4259 // Pane for overlay shadows (e.g. `Marker` shadows)
\r
4260 this.createPane('shadowPane');
\r
4261 // @pane markerPane: HTMLElement = 600
\r
4262 // Pane for `Icon`s of `Marker`s
\r
4263 this.createPane('markerPane');
\r
4264 // @pane tooltipPane: HTMLElement = 650
\r
4265 // Pane for `Tooltip`s.
\r
4266 this.createPane('tooltipPane');
\r
4267 // @pane popupPane: HTMLElement = 700
\r
4268 // Pane for `Popup`s.
\r
4269 this.createPane('popupPane');
\r
4271 if (!this.options.markerZoomAnimation) {
\r
4272 addClass(panes.markerPane, 'leaflet-zoom-hide');
\r
4273 addClass(panes.shadowPane, 'leaflet-zoom-hide');
\r
4278 // private methods that modify map state
\r
4280 // @section Map state change events
\r
4281 _resetView: function (center, zoom, noMoveStart) {
\r
4282 setPosition(this._mapPane, new Point(0, 0));
\r
4284 var loading = !this._loaded;
\r
4285 this._loaded = true;
\r
4286 zoom = this._limitZoom(zoom);
\r
4288 this.fire('viewprereset');
\r
4290 var zoomChanged = this._zoom !== zoom;
\r
4292 ._moveStart(zoomChanged, noMoveStart)
\r
4293 ._move(center, zoom)
\r
4294 ._moveEnd(zoomChanged);
\r
4296 // @event viewreset: Event
\r
4297 // Fired when the map needs to redraw its content (this usually happens
\r
4298 // on map zoom or load). Very useful for creating custom overlays.
\r
4299 this.fire('viewreset');
\r
4301 // @event load: Event
\r
4302 // Fired when the map is initialized (when its center and zoom are set
\r
4303 // for the first time).
\r
4305 this.fire('load');
\r
4309 _moveStart: function (zoomChanged, noMoveStart) {
\r
4310 // @event zoomstart: Event
\r
4311 // Fired when the map zoom is about to change (e.g. before zoom animation).
\r
4312 // @event movestart: Event
\r
4313 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
\r
4314 if (zoomChanged) {
\r
4315 this.fire('zoomstart');
\r
4317 if (!noMoveStart) {
\r
4318 this.fire('movestart');
\r
4323 _move: function (center, zoom, data, supressEvent) {
\r
4324 if (zoom === undefined) {
\r
4325 zoom = this._zoom;
\r
4327 var zoomChanged = this._zoom !== zoom;
\r
4329 this._zoom = zoom;
\r
4330 this._lastCenter = center;
\r
4331 this._pixelOrigin = this._getNewPixelOrigin(center);
\r
4333 if (!supressEvent) {
\r
4334 // @event zoom: Event
\r
4335 // Fired repeatedly during any change in zoom level,
\r
4336 // including zoom and fly animations.
\r
4337 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
\r
4338 this.fire('zoom', data);
\r
4341 // @event move: Event
\r
4342 // Fired repeatedly during any movement of the map,
\r
4343 // including pan and fly animations.
\r
4344 this.fire('move', data);
\r
4345 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530
\r
4346 this.fire('zoom', data);
\r
4351 _moveEnd: function (zoomChanged) {
\r
4352 // @event zoomend: Event
\r
4353 // Fired when the map zoom changed, after any animations.
\r
4354 if (zoomChanged) {
\r
4355 this.fire('zoomend');
\r
4358 // @event moveend: Event
\r
4359 // Fired when the center of the map stops changing
\r
4360 // (e.g. user stopped dragging the map or after non-centered zoom).
\r
4361 return this.fire('moveend');
\r
4364 _stop: function () {
\r
4365 cancelAnimFrame(this._flyToFrame);
\r
4366 if (this._panAnim) {
\r
4367 this._panAnim.stop();
\r
4372 _rawPanBy: function (offset) {
\r
4373 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
\r
4376 _getZoomSpan: function () {
\r
4377 return this.getMaxZoom() - this.getMinZoom();
\r
4380 _panInsideMaxBounds: function () {
\r
4381 if (!this._enforcingBounds) {
\r
4382 this.panInsideBounds(this.options.maxBounds);
\r
4386 _checkIfLoaded: function () {
\r
4387 if (!this._loaded) {
\r
4388 throw new Error('Set map center and zoom first.');
\r
4392 // DOM event handling
\r
4394 // @section Interaction events
\r
4395 _initEvents: function (remove) {
\r
4396 this._targets = {};
\r
4397 this._targets[stamp(this._container)] = this;
\r
4399 var onOff = remove ? off : on;
\r
4401 // @event click: MouseEvent
\r
4402 // Fired when the user clicks (or taps) the map.
\r
4403 // @event dblclick: MouseEvent
\r
4404 // Fired when the user double-clicks (or double-taps) the map.
\r
4405 // @event mousedown: MouseEvent
\r
4406 // Fired when the user pushes the mouse button on the map.
\r
4407 // @event mouseup: MouseEvent
\r
4408 // Fired when the user releases the mouse button on the map.
\r
4409 // @event mouseover: MouseEvent
\r
4410 // Fired when the mouse enters the map.
\r
4411 // @event mouseout: MouseEvent
\r
4412 // Fired when the mouse leaves the map.
\r
4413 // @event mousemove: MouseEvent
\r
4414 // Fired while the mouse moves over the map.
\r
4415 // @event contextmenu: MouseEvent
\r
4416 // Fired when the user pushes the right mouse button on the map, prevents
\r
4417 // default browser context menu from showing if there are listeners on
\r
4418 // this event. Also fired on mobile when the user holds a single touch
\r
4419 // for a second (also called long press).
\r
4420 // @event keypress: KeyboardEvent
\r
4421 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
\r
4422 // @event keydown: KeyboardEvent
\r
4423 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
\r
4424 // the `keydown` event is fired for keys that produce a character value and for keys
\r
4425 // that do not produce a character value.
\r
4426 // @event keyup: KeyboardEvent
\r
4427 // Fired when the user releases a key from the keyboard while the map is focused.
\r
4428 onOff(this._container, 'click dblclick mousedown mouseup ' +
\r
4429 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
\r
4431 if (this.options.trackResize) {
\r
4432 onOff(window, 'resize', this._onResize, this);
\r
4435 if (Browser.any3d && this.options.transform3DLimit) {
\r
4436 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
\r
4440 _onResize: function () {
\r
4441 cancelAnimFrame(this._resizeRequest);
\r
4442 this._resizeRequest = requestAnimFrame(
\r
4443 function () { this.invalidateSize({debounceMoveend: true}); }, this);
\r
4446 _onScroll: function () {
\r
4447 this._container.scrollTop = 0;
\r
4448 this._container.scrollLeft = 0;
\r
4451 _onMoveEnd: function () {
\r
4452 var pos = this._getMapPanePos();
\r
4453 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
\r
4454 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
\r
4455 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
\r
4456 this._resetView(this.getCenter(), this.getZoom());
\r
4460 _findEventTargets: function (e, type) {
\r
4463 isHover = type === 'mouseout' || type === 'mouseover',
\r
4464 src = e.target || e.srcElement,
\r
4468 target = this._targets[stamp(src)];
\r
4469 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
\r
4470 // Prevent firing click after you just dragged an object.
\r
4474 if (target && target.listens(type, true)) {
\r
4475 if (isHover && !isExternalTarget(src, e)) { break; }
\r
4476 targets.push(target);
\r
4477 if (isHover) { break; }
\r
4479 if (src === this._container) { break; }
\r
4480 src = src.parentNode;
\r
4482 if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
\r
4488 _isClickDisabled: function (el) {
\r
4489 while (el && el !== this._container) {
\r
4490 if (el['_leaflet_disable_click']) { return true; }
\r
4491 el = el.parentNode;
\r
4495 _handleDOMEvent: function (e) {
\r
4496 var el = (e.target || e.srcElement);
\r
4497 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
\r
4501 var type = e.type;
\r
4503 if (type === 'mousedown') {
\r
4504 // prevents outline when clicking on keyboard-focusable element
\r
4505 preventOutline(el);
\r
4508 this._fireDOMEvent(e, type);
\r
4511 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
\r
4513 _fireDOMEvent: function (e, type, canvasTargets) {
\r
4515 if (e.type === 'click') {
\r
4516 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
\r
4517 // @event preclick: MouseEvent
\r
4518 // Fired before mouse click on the map (sometimes useful when you
\r
4519 // want something to happen on click before any existing click
\r
4520 // handlers start running).
\r
4521 var synth = extend({}, e);
\r
4522 synth.type = 'preclick';
\r
4523 this._fireDOMEvent(synth, synth.type, canvasTargets);
\r
4526 // Find the layer the event is propagating from and its parents.
\r
4527 var targets = this._findEventTargets(e, type);
\r
4529 if (canvasTargets) {
\r
4530 var filtered = []; // pick only targets with listeners
\r
4531 for (var i = 0; i < canvasTargets.length; i++) {
\r
4532 if (canvasTargets[i].listens(type, true)) {
\r
4533 filtered.push(canvasTargets[i]);
\r
4536 targets = filtered.concat(targets);
\r
4539 if (!targets.length) { return; }
\r
4541 if (type === 'contextmenu') {
\r
4542 preventDefault(e);
\r
4545 var target = targets[0];
\r
4550 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
\r
4551 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
\r
4552 data.containerPoint = isMarker ?
\r
4553 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
\r
4554 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
\r
4555 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
\r
4558 for (i = 0; i < targets.length; i++) {
\r
4559 targets[i].fire(type, data, true);
\r
4560 if (data.originalEvent._stopped ||
\r
4561 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
\r
4565 _draggableMoved: function (obj) {
\r
4566 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
\r
4567 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
\r
4570 _clearHandlers: function () {
\r
4571 for (var i = 0, len = this._handlers.length; i < len; i++) {
\r
4572 this._handlers[i].disable();
\r
4576 // @section Other Methods
\r
4578 // @method whenReady(fn: Function, context?: Object): this
\r
4579 // Runs the given function `fn` when the map gets initialized with
\r
4580 // a view (center and zoom) and at least one layer, or immediately
\r
4581 // if it's already initialized, optionally passing a function context.
\r
4582 whenReady: function (callback, context) {
\r
4583 if (this._loaded) {
\r
4584 callback.call(context || this, {target: this});
\r
4586 this.on('load', callback, context);
\r
4592 // private methods for getting map state
\r
4594 _getMapPanePos: function () {
\r
4595 return getPosition(this._mapPane) || new Point(0, 0);
\r
4598 _moved: function () {
\r
4599 var pos = this._getMapPanePos();
\r
4600 return pos && !pos.equals([0, 0]);
\r
4603 _getTopLeftPoint: function (center, zoom) {
\r
4604 var pixelOrigin = center && zoom !== undefined ?
\r
4605 this._getNewPixelOrigin(center, zoom) :
\r
4606 this.getPixelOrigin();
\r
4607 return pixelOrigin.subtract(this._getMapPanePos());
\r
4610 _getNewPixelOrigin: function (center, zoom) {
\r
4611 var viewHalf = this.getSize()._divideBy(2);
\r
4612 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
\r
4615 _latLngToNewLayerPoint: function (latlng, zoom, center) {
\r
4616 var topLeft = this._getNewPixelOrigin(center, zoom);
\r
4617 return this.project(latlng, zoom)._subtract(topLeft);
\r
4620 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
\r
4621 var topLeft = this._getNewPixelOrigin(center, zoom);
\r
4623 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
\r
4624 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
\r
4625 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
\r
4626 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
\r
4630 // layer point of the current center
\r
4631 _getCenterLayerPoint: function () {
\r
4632 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
\r
4635 // offset of the specified place to the current center in pixels
\r
4636 _getCenterOffset: function (latlng) {
\r
4637 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
\r
4640 // adjust center for view to get inside bounds
\r
4641 _limitCenter: function (center, zoom, bounds) {
\r
4643 if (!bounds) { return center; }
\r
4645 var centerPoint = this.project(center, zoom),
\r
4646 viewHalf = this.getSize().divideBy(2),
\r
4647 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
\r
4648 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
\r
4650 // If offset is less than a pixel, ignore.
\r
4651 // This prevents unstable projections from getting into
\r
4652 // an infinite loop of tiny offsets.
\r
4653 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
\r
4657 return this.unproject(centerPoint.add(offset), zoom);
\r
4660 // adjust offset for view to get inside bounds
\r
4661 _limitOffset: function (offset, bounds) {
\r
4662 if (!bounds) { return offset; }
\r
4664 var viewBounds = this.getPixelBounds(),
\r
4665 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
\r
4667 return offset.add(this._getBoundsOffset(newBounds, bounds));
\r
4670 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
\r
4671 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
\r
4672 var projectedMaxBounds = toBounds(
\r
4673 this.project(maxBounds.getNorthEast(), zoom),
\r
4674 this.project(maxBounds.getSouthWest(), zoom)
\r
4676 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
\r
4677 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
\r
4679 dx = this._rebound(minOffset.x, -maxOffset.x),
\r
4680 dy = this._rebound(minOffset.y, -maxOffset.y);
\r
4682 return new Point(dx, dy);
\r
4685 _rebound: function (left, right) {
\r
4686 return left + right > 0 ?
\r
4687 Math.round(left - right) / 2 :
\r
4688 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
\r
4691 _limitZoom: function (zoom) {
\r
4692 var min = this.getMinZoom(),
\r
4693 max = this.getMaxZoom(),
\r
4694 snap = Browser.any3d ? this.options.zoomSnap : 1;
\r
4696 zoom = Math.round(zoom / snap) * snap;
\r
4698 return Math.max(min, Math.min(max, zoom));
\r
4701 _onPanTransitionStep: function () {
\r
4702 this.fire('move');
\r
4705 _onPanTransitionEnd: function () {
\r
4706 removeClass(this._mapPane, 'leaflet-pan-anim');
\r
4707 this.fire('moveend');
\r
4710 _tryAnimatedPan: function (center, options) {
\r
4711 // difference between the new and current centers in pixels
\r
4712 var offset = this._getCenterOffset(center)._trunc();
\r
4714 // don't animate too far unless animate: true specified in options
\r
4715 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
\r
4717 this.panBy(offset, options);
\r
4722 _createAnimProxy: function () {
\r
4724 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
\r
4725 this._panes.mapPane.appendChild(proxy);
\r
4727 this.on('zoomanim', function (e) {
\r
4728 var prop = TRANSFORM,
\r
4729 transform = this._proxy.style[prop];
\r
4731 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
\r
4733 // workaround for case when transform is the same and so transitionend event is not fired
\r
4734 if (transform === this._proxy.style[prop] && this._animatingZoom) {
\r
4735 this._onZoomTransitionEnd();
\r
4739 this.on('load moveend', this._animMoveEnd, this);
\r
4741 this._on('unload', this._destroyAnimProxy, this);
\r
4744 _destroyAnimProxy: function () {
\r
4745 remove(this._proxy);
\r
4746 this.off('load moveend', this._animMoveEnd, this);
\r
4747 delete this._proxy;
\r
4750 _animMoveEnd: function () {
\r
4751 var c = this.getCenter(),
\r
4752 z = this.getZoom();
\r
4753 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
\r
4756 _catchTransitionEnd: function (e) {
\r
4757 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
\r
4758 this._onZoomTransitionEnd();
\r
4762 _nothingToAnimate: function () {
\r
4763 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
\r
4766 _tryAnimatedZoom: function (center, zoom, options) {
\r
4768 if (this._animatingZoom) { return true; }
\r
4770 options = options || {};
\r
4772 // don't animate if disabled, not supported or zoom difference is too large
\r
4773 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
\r
4774 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
\r
4776 // offset is the pixel coords of the zoom origin relative to the current center
\r
4777 var scale = this.getZoomScale(zoom),
\r
4778 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
\r
4780 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
\r
4781 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
\r
4783 requestAnimFrame(function () {
\r
4785 ._moveStart(true, options.noMoveStart || false)
\r
4786 ._animateZoom(center, zoom, true);
\r
4792 _animateZoom: function (center, zoom, startAnim, noUpdate) {
\r
4793 if (!this._mapPane) { return; }
\r
4796 this._animatingZoom = true;
\r
4798 // remember what center/zoom to set after animation
\r
4799 this._animateToCenter = center;
\r
4800 this._animateToZoom = zoom;
\r
4802 addClass(this._mapPane, 'leaflet-zoom-anim');
\r
4805 // @section Other Events
\r
4806 // @event zoomanim: ZoomAnimEvent
\r
4807 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
\r
4808 this.fire('zoomanim', {
\r
4811 noUpdate: noUpdate
\r
4814 if (!this._tempFireZoomEvent) {
\r
4815 this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
\r
4818 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
\r
4820 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
\r
4821 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
\r
4824 _onZoomTransitionEnd: function () {
\r
4825 if (!this._animatingZoom) { return; }
\r
4827 if (this._mapPane) {
\r
4828 removeClass(this._mapPane, 'leaflet-zoom-anim');
\r
4831 this._animatingZoom = false;
\r
4833 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
\r
4835 if (this._tempFireZoomEvent) {
\r
4836 this.fire('zoom');
\r
4838 delete this._tempFireZoomEvent;
\r
4840 this.fire('move');
\r
4842 this._moveEnd(true);
\r
4848 // @factory L.map(id: String, options?: Map options)
\r
4849 // Instantiates a map object given the DOM ID of a `<div>` element
\r
4850 // and optionally an object literal with `Map options`.
\r
4853 // @factory L.map(el: HTMLElement, options?: Map options)
\r
4854 // Instantiates a map object given an instance of a `<div>` HTML element
\r
4855 // and optionally an object literal with `Map options`.
\r
4856 function createMap(id, options) {
\r
4857 return new Map(id, options);
\r
4865 * L.Control is a base class for implementing map controls. Handles positioning.
\r
4866 * All other controls extend from this class.
\r
4869 var Control = Class.extend({
\r
4871 // @aka Control Options
\r
4873 // @option position: String = 'topright'
\r
4874 // The position of the control (one of the map corners). Possible values are `'topleft'`,
\r
4875 // `'topright'`, `'bottomleft'` or `'bottomright'`
\r
4876 position: 'topright'
\r
4879 initialize: function (options) {
\r
4880 setOptions(this, options);
\r
4884 * Classes extending L.Control will inherit the following methods:
\r
4886 * @method getPosition: string
\r
4887 * Returns the position of the control.
\r
4889 getPosition: function () {
\r
4890 return this.options.position;
\r
4893 // @method setPosition(position: string): this
\r
4894 // Sets the position of the control.
\r
4895 setPosition: function (position) {
\r
4896 var map = this._map;
\r
4899 map.removeControl(this);
\r
4902 this.options.position = position;
\r
4905 map.addControl(this);
\r
4911 // @method getContainer: HTMLElement
\r
4912 // Returns the HTMLElement that contains the control.
\r
4913 getContainer: function () {
\r
4914 return this._container;
\r
4917 // @method addTo(map: Map): this
\r
4918 // Adds the control to the given map.
\r
4919 addTo: function (map) {
\r
4923 var container = this._container = this.onAdd(map),
\r
4924 pos = this.getPosition(),
\r
4925 corner = map._controlCorners[pos];
\r
4927 addClass(container, 'leaflet-control');
\r
4929 if (pos.indexOf('bottom') !== -1) {
\r
4930 corner.insertBefore(container, corner.firstChild);
\r
4932 corner.appendChild(container);
\r
4935 this._map.on('unload', this.remove, this);
\r
4940 // @method remove: this
\r
4941 // Removes the control from the map it is currently active on.
\r
4942 remove: function () {
\r
4947 remove(this._container);
\r
4949 if (this.onRemove) {
\r
4950 this.onRemove(this._map);
\r
4953 this._map.off('unload', this.remove, this);
\r
4959 _refocusOnMap: function (e) {
\r
4960 // if map exists and event is not a keyboard event
\r
4961 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
\r
4962 this._map.getContainer().focus();
\r
4967 var control = function (options) {
\r
4968 return new Control(options);
\r
4971 /* @section Extension methods
\r
4974 * Every control should extend from `L.Control` and (re-)implement the following methods.
\r
4976 * @method onAdd(map: Map): HTMLElement
\r
4977 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
\r
4979 * @method onRemove(map: Map)
\r
4980 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
\r
4984 * @section Methods for Layers and Controls
\r
4987 // @method addControl(control: Control): this
\r
4988 // Adds the given control to the map
\r
4989 addControl: function (control) {
\r
4990 control.addTo(this);
\r
4994 // @method removeControl(control: Control): this
\r
4995 // Removes the given control from the map
\r
4996 removeControl: function (control) {
\r
5001 _initControlPos: function () {
\r
5002 var corners = this._controlCorners = {},
\r
5004 container = this._controlContainer =
\r
5005 create$1('div', l + 'control-container', this._container);
\r
5007 function createCorner(vSide, hSide) {
\r
5008 var className = l + vSide + ' ' + l + hSide;
\r
5010 corners[vSide + hSide] = create$1('div', className, container);
\r
5013 createCorner('top', 'left');
\r
5014 createCorner('top', 'right');
\r
5015 createCorner('bottom', 'left');
\r
5016 createCorner('bottom', 'right');
\r
5019 _clearControlPos: function () {
\r
5020 for (var i in this._controlCorners) {
\r
5021 remove(this._controlCorners[i]);
\r
5023 remove(this._controlContainer);
\r
5024 delete this._controlCorners;
\r
5025 delete this._controlContainer;
\r
5030 * @class Control.Layers
\r
5031 * @aka L.Control.Layers
\r
5032 * @inherits Control
\r
5034 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.
\r
5039 * var baseLayers = {
\r
5040 * "Mapbox": mapbox,
\r
5041 * "OpenStreetMap": osm
\r
5044 * var overlays = {
\r
5045 * "Marker": marker,
\r
5046 * "Roads": roadsLayer
\r
5049 * L.control.layers(baseLayers, overlays).addTo(map);
\r
5052 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
\r
5056 * "<someName1>": layer1,
\r
5057 * "<someName2>": layer2
\r
5061 * The layer names can contain HTML, which allows you to add additional styling to the items:
\r
5064 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
\r
5068 var Layers = Control.extend({
\r
5070 // @aka Control.Layers options
\r
5072 // @option collapsed: Boolean = true
\r
5073 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
\r
5075 position: 'topright',
\r
5077 // @option autoZIndex: Boolean = true
\r
5078 // 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.
\r
5081 // @option hideSingleBase: Boolean = false
\r
5082 // If `true`, the base layers in the control will be hidden when there is only one.
\r
5083 hideSingleBase: false,
\r
5085 // @option sortLayers: Boolean = false
\r
5086 // Whether to sort the layers. When `false`, layers will keep the order
\r
5087 // in which they were added to the control.
\r
5088 sortLayers: false,
\r
5090 // @option sortFunction: Function = *
\r
5091 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
\r
5092 // that will be used for sorting the layers, when `sortLayers` is `true`.
\r
5093 // The function receives both the `L.Layer` instances and their names, as in
\r
5094 // `sortFunction(layerA, layerB, nameA, nameB)`.
\r
5095 // By default, it sorts layers alphabetically by their name.
\r
5096 sortFunction: function (layerA, layerB, nameA, nameB) {
\r
5097 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
\r
5101 initialize: function (baseLayers, overlays, options) {
\r
5102 setOptions(this, options);
\r
5104 this._layerControlInputs = [];
\r
5105 this._layers = [];
\r
5106 this._lastZIndex = 0;
\r
5107 this._handlingClick = false;
\r
5108 this._preventClick = false;
\r
5110 for (var i in baseLayers) {
\r
5111 this._addLayer(baseLayers[i], i);
\r
5114 for (i in overlays) {
\r
5115 this._addLayer(overlays[i], i, true);
\r
5119 onAdd: function (map) {
\r
5120 this._initLayout();
\r
5124 map.on('zoomend', this._checkDisabledLayers, this);
\r
5126 for (var i = 0; i < this._layers.length; i++) {
\r
5127 this._layers[i].layer.on('add remove', this._onLayerChange, this);
\r
5130 return this._container;
\r
5133 addTo: function (map) {
\r
5134 Control.prototype.addTo.call(this, map);
\r
5135 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
\r
5136 return this._expandIfNotCollapsed();
\r
5139 onRemove: function () {
\r
5140 this._map.off('zoomend', this._checkDisabledLayers, this);
\r
5142 for (var i = 0; i < this._layers.length; i++) {
\r
5143 this._layers[i].layer.off('add remove', this._onLayerChange, this);
\r
5147 // @method addBaseLayer(layer: Layer, name: String): this
\r
5148 // Adds a base layer (radio button entry) with the given name to the control.
\r
5149 addBaseLayer: function (layer, name) {
\r
5150 this._addLayer(layer, name);
\r
5151 return (this._map) ? this._update() : this;
\r
5154 // @method addOverlay(layer: Layer, name: String): this
\r
5155 // Adds an overlay (checkbox entry) with the given name to the control.
\r
5156 addOverlay: function (layer, name) {
\r
5157 this._addLayer(layer, name, true);
\r
5158 return (this._map) ? this._update() : this;
\r
5161 // @method removeLayer(layer: Layer): this
\r
5162 // Remove the given layer from the control.
\r
5163 removeLayer: function (layer) {
\r
5164 layer.off('add remove', this._onLayerChange, this);
\r
5166 var obj = this._getLayer(stamp(layer));
\r
5168 this._layers.splice(this._layers.indexOf(obj), 1);
\r
5170 return (this._map) ? this._update() : this;
\r
5173 // @method expand(): this
\r
5174 // Expand the control container if collapsed.
\r
5175 expand: function () {
\r
5176 addClass(this._container, 'leaflet-control-layers-expanded');
\r
5177 this._section.style.height = null;
\r
5178 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
\r
5179 if (acceptableHeight < this._section.clientHeight) {
\r
5180 addClass(this._section, 'leaflet-control-layers-scrollbar');
\r
5181 this._section.style.height = acceptableHeight + 'px';
\r
5183 removeClass(this._section, 'leaflet-control-layers-scrollbar');
\r
5185 this._checkDisabledLayers();
\r
5189 // @method collapse(): this
\r
5190 // Collapse the control container if expanded.
\r
5191 collapse: function () {
\r
5192 removeClass(this._container, 'leaflet-control-layers-expanded');
\r
5196 _initLayout: function () {
\r
5197 var className = 'leaflet-control-layers',
\r
5198 container = this._container = create$1('div', className),
\r
5199 collapsed = this.options.collapsed;
\r
5201 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
\r
5202 container.setAttribute('aria-haspopup', true);
\r
5204 disableClickPropagation(container);
\r
5205 disableScrollPropagation(container);
\r
5207 var section = this._section = create$1('section', className + '-list');
\r
5210 this._map.on('click', this.collapse, this);
\r
5213 mouseenter: this._expandSafely,
\r
5214 mouseleave: this.collapse
\r
5218 var link = this._layersLink = create$1('a', className + '-toggle', container);
\r
5220 link.title = 'Layers';
\r
5221 link.setAttribute('role', 'button');
\r
5224 keydown: function (e) {
\r
5225 if (e.keyCode === 13) {
\r
5226 this._expandSafely();
\r
5229 // Certain screen readers intercept the key event and instead send a click event
\r
5230 click: function (e) {
\r
5231 preventDefault(e);
\r
5232 this._expandSafely();
\r
5240 this._baseLayersList = create$1('div', className + '-base', section);
\r
5241 this._separator = create$1('div', className + '-separator', section);
\r
5242 this._overlaysList = create$1('div', className + '-overlays', section);
\r
5244 container.appendChild(section);
\r
5247 _getLayer: function (id) {
\r
5248 for (var i = 0; i < this._layers.length; i++) {
\r
5250 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
\r
5251 return this._layers[i];
\r
5256 _addLayer: function (layer, name, overlay) {
\r
5258 layer.on('add remove', this._onLayerChange, this);
\r
5261 this._layers.push({
\r
5267 if (this.options.sortLayers) {
\r
5268 this._layers.sort(bind(function (a, b) {
\r
5269 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
\r
5273 if (this.options.autoZIndex && layer.setZIndex) {
\r
5274 this._lastZIndex++;
\r
5275 layer.setZIndex(this._lastZIndex);
\r
5278 this._expandIfNotCollapsed();
\r
5281 _update: function () {
\r
5282 if (!this._container) { return this; }
\r
5284 empty(this._baseLayersList);
\r
5285 empty(this._overlaysList);
\r
5287 this._layerControlInputs = [];
\r
5288 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
\r
5290 for (i = 0; i < this._layers.length; i++) {
\r
5291 obj = this._layers[i];
\r
5292 this._addItem(obj);
\r
5293 overlaysPresent = overlaysPresent || obj.overlay;
\r
5294 baseLayersPresent = baseLayersPresent || !obj.overlay;
\r
5295 baseLayersCount += !obj.overlay ? 1 : 0;
\r
5298 // Hide base layers section if there's only one layer.
\r
5299 if (this.options.hideSingleBase) {
\r
5300 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
\r
5301 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
\r
5304 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
\r
5309 _onLayerChange: function (e) {
\r
5310 if (!this._handlingClick) {
\r
5314 var obj = this._getLayer(stamp(e.target));
\r
5317 // @section Layer events
\r
5318 // @event baselayerchange: LayersControlEvent
\r
5319 // Fired when the base layer is changed through the [layers control](#control-layers).
\r
5320 // @event overlayadd: LayersControlEvent
\r
5321 // Fired when an overlay is selected through the [layers control](#control-layers).
\r
5322 // @event overlayremove: LayersControlEvent
\r
5323 // Fired when an overlay is deselected through the [layers control](#control-layers).
\r
5324 // @namespace Control.Layers
\r
5325 var type = obj.overlay ?
\r
5326 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
\r
5327 (e.type === 'add' ? 'baselayerchange' : null);
\r
5330 this._map.fire(type, obj);
\r
5334 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
\r
5335 _createRadioElement: function (name, checked) {
\r
5337 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
\r
5338 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
\r
5340 var radioFragment = document.createElement('div');
\r
5341 radioFragment.innerHTML = radioHtml;
\r
5343 return radioFragment.firstChild;
\r
5346 _addItem: function (obj) {
\r
5347 var label = document.createElement('label'),
\r
5348 checked = this._map.hasLayer(obj.layer),
\r
5351 if (obj.overlay) {
\r
5352 input = document.createElement('input');
\r
5353 input.type = 'checkbox';
\r
5354 input.className = 'leaflet-control-layers-selector';
\r
5355 input.defaultChecked = checked;
\r
5357 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
\r
5360 this._layerControlInputs.push(input);
\r
5361 input.layerId = stamp(obj.layer);
\r
5363 on(input, 'click', this._onInputClick, this);
\r
5365 var name = document.createElement('span');
\r
5366 name.innerHTML = ' ' + obj.name;
\r
5368 // Helps from preventing layer control flicker when checkboxes are disabled
\r
5369 // https://github.com/Leaflet/Leaflet/issues/2771
\r
5370 var holder = document.createElement('span');
\r
5372 label.appendChild(holder);
\r
5373 holder.appendChild(input);
\r
5374 holder.appendChild(name);
\r
5376 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
\r
5377 container.appendChild(label);
\r
5379 this._checkDisabledLayers();
\r
5383 _onInputClick: function () {
\r
5384 // expanding the control on mobile with a click can cause adding a layer - we don't want this
\r
5385 if (this._preventClick) {
\r
5389 var inputs = this._layerControlInputs,
\r
5391 var addedLayers = [],
\r
5392 removedLayers = [];
\r
5394 this._handlingClick = true;
\r
5396 for (var i = inputs.length - 1; i >= 0; i--) {
\r
5397 input = inputs[i];
\r
5398 layer = this._getLayer(input.layerId).layer;
\r
5400 if (input.checked) {
\r
5401 addedLayers.push(layer);
\r
5402 } else if (!input.checked) {
\r
5403 removedLayers.push(layer);
\r
5407 // Bugfix issue 2318: Should remove all old layers before readding new ones
\r
5408 for (i = 0; i < removedLayers.length; i++) {
\r
5409 if (this._map.hasLayer(removedLayers[i])) {
\r
5410 this._map.removeLayer(removedLayers[i]);
\r
5413 for (i = 0; i < addedLayers.length; i++) {
\r
5414 if (!this._map.hasLayer(addedLayers[i])) {
\r
5415 this._map.addLayer(addedLayers[i]);
\r
5419 this._handlingClick = false;
\r
5421 this._refocusOnMap();
\r
5424 _checkDisabledLayers: function () {
\r
5425 var inputs = this._layerControlInputs,
\r
5428 zoom = this._map.getZoom();
\r
5430 for (var i = inputs.length - 1; i >= 0; i--) {
\r
5431 input = inputs[i];
\r
5432 layer = this._getLayer(input.layerId).layer;
\r
5433 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
\r
5434 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
\r
5439 _expandIfNotCollapsed: function () {
\r
5440 if (this._map && !this.options.collapsed) {
\r
5446 _expandSafely: function () {
\r
5447 var section = this._section;
\r
5448 this._preventClick = true;
\r
5449 on(section, 'click', preventDefault);
\r
5452 setTimeout(function () {
\r
5453 off(section, 'click', preventDefault);
\r
5454 that._preventClick = false;
\r
5461 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
\r
5462 // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
\r
5463 var layers = function (baseLayers, overlays, options) {
\r
5464 return new Layers(baseLayers, overlays, options);
\r
5468 * @class Control.Zoom
\r
5469 * @aka L.Control.Zoom
\r
5470 * @inherits Control
\r
5472 * 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`.
\r
5475 var Zoom = Control.extend({
\r
5477 // @aka Control.Zoom options
\r
5479 position: 'topleft',
\r
5481 // @option zoomInText: String = '<span aria-hidden="true">+</span>'
\r
5482 // The text set on the 'zoom in' button.
\r
5483 zoomInText: '<span aria-hidden="true">+</span>',
\r
5485 // @option zoomInTitle: String = 'Zoom in'
\r
5486 // The title set on the 'zoom in' button.
\r
5487 zoomInTitle: 'Zoom in',
\r
5489 // @option zoomOutText: String = '<span aria-hidden="true">−</span>'
\r
5490 // The text set on the 'zoom out' button.
\r
5491 zoomOutText: '<span aria-hidden="true">−</span>',
\r
5493 // @option zoomOutTitle: String = 'Zoom out'
\r
5494 // The title set on the 'zoom out' button.
\r
5495 zoomOutTitle: 'Zoom out'
\r
5498 onAdd: function (map) {
\r
5499 var zoomName = 'leaflet-control-zoom',
\r
5500 container = create$1('div', zoomName + ' leaflet-bar'),
\r
5501 options = this.options;
\r
5503 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
\r
5504 zoomName + '-in', container, this._zoomIn);
\r
5505 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
\r
5506 zoomName + '-out', container, this._zoomOut);
\r
5508 this._updateDisabled();
\r
5509 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
\r
5514 onRemove: function (map) {
\r
5515 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
\r
5518 disable: function () {
\r
5519 this._disabled = true;
\r
5520 this._updateDisabled();
\r
5524 enable: function () {
\r
5525 this._disabled = false;
\r
5526 this._updateDisabled();
\r
5530 _zoomIn: function (e) {
\r
5531 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
\r
5532 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
\r
5536 _zoomOut: function (e) {
\r
5537 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
\r
5538 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
\r
5542 _createButton: function (html, title, className, container, fn) {
\r
5543 var link = create$1('a', className, container);
\r
5544 link.innerHTML = html;
\r
5546 link.title = title;
\r
5549 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
\r
5551 link.setAttribute('role', 'button');
\r
5552 link.setAttribute('aria-label', title);
\r
5554 disableClickPropagation(link);
\r
5555 on(link, 'click', stop);
\r
5556 on(link, 'click', fn, this);
\r
5557 on(link, 'click', this._refocusOnMap, this);
\r
5562 _updateDisabled: function () {
\r
5563 var map = this._map,
\r
5564 className = 'leaflet-disabled';
\r
5566 removeClass(this._zoomInButton, className);
\r
5567 removeClass(this._zoomOutButton, className);
\r
5568 this._zoomInButton.setAttribute('aria-disabled', 'false');
\r
5569 this._zoomOutButton.setAttribute('aria-disabled', 'false');
\r
5571 if (this._disabled || map._zoom === map.getMinZoom()) {
\r
5572 addClass(this._zoomOutButton, className);
\r
5573 this._zoomOutButton.setAttribute('aria-disabled', 'true');
\r
5575 if (this._disabled || map._zoom === map.getMaxZoom()) {
\r
5576 addClass(this._zoomInButton, className);
\r
5577 this._zoomInButton.setAttribute('aria-disabled', 'true');
\r
5583 // @section Control options
\r
5584 // @option zoomControl: Boolean = true
\r
5585 // Whether a [zoom control](#control-zoom) is added to the map by default.
\r
5586 Map.mergeOptions({
\r
5590 Map.addInitHook(function () {
\r
5591 if (this.options.zoomControl) {
\r
5592 // @section Controls
\r
5593 // @property zoomControl: Control.Zoom
\r
5594 // The default zoom control (only available if the
\r
5595 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
\r
5596 this.zoomControl = new Zoom();
\r
5597 this.addControl(this.zoomControl);
\r
5601 // @namespace Control.Zoom
\r
5602 // @factory L.control.zoom(options: Control.Zoom options)
\r
5603 // Creates a zoom control
\r
5604 var zoom = function (options) {
\r
5605 return new Zoom(options);
\r
5609 * @class Control.Scale
5610 * @aka L.Control.Scale
5613 * 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`.
5618 * L.control.scale().addTo(map);
5622 var Scale = Control.extend({
5624 // @aka Control.Scale options
5626 position: 'bottomleft',
5628 // @option maxWidth: Number = 100
5629 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5632 // @option metric: Boolean = True
5633 // Whether to show the metric scale line (m/km).
5636 // @option imperial: Boolean = True
5637 // Whether to show the imperial scale line (mi/ft).
5640 // @option updateWhenIdle: Boolean = false
5641 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5644 onAdd: function (map) {
5645 var className = 'leaflet-control-scale',
5646 container = create$1('div', className),
5647 options = this.options;
5649 this._addScales(options, className + '-line', container);
5651 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5652 map.whenReady(this._update, this);
5657 onRemove: function (map) {
5658 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5661 _addScales: function (options, className, container) {
5662 if (options.metric) {
5663 this._mScale = create$1('div', className, container);
5665 if (options.imperial) {
5666 this._iScale = create$1('div', className, container);
5670 _update: function () {
5671 var map = this._map,
5672 y = map.getSize().y / 2;
5674 var maxMeters = map.distance(
5675 map.containerPointToLatLng([0, y]),
5676 map.containerPointToLatLng([this.options.maxWidth, y]));
5678 this._updateScales(maxMeters);
5681 _updateScales: function (maxMeters) {
5682 if (this.options.metric && maxMeters) {
5683 this._updateMetric(maxMeters);
5685 if (this.options.imperial && maxMeters) {
5686 this._updateImperial(maxMeters);
5690 _updateMetric: function (maxMeters) {
5691 var meters = this._getRoundNum(maxMeters),
5692 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5694 this._updateScale(this._mScale, label, meters / maxMeters);
5697 _updateImperial: function (maxMeters) {
5698 var maxFeet = maxMeters * 3.2808399,
5699 maxMiles, miles, feet;
5701 if (maxFeet > 5280) {
5702 maxMiles = maxFeet / 5280;
5703 miles = this._getRoundNum(maxMiles);
5704 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5707 feet = this._getRoundNum(maxFeet);
5708 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5712 _updateScale: function (scale, text, ratio) {
5713 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5714 scale.innerHTML = text;
5717 _getRoundNum: function (num) {
5718 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5731 // @factory L.control.scale(options?: Control.Scale options)
5732 // Creates an scale control with the given options.
5733 var scale = function (options) {
5734 return new Scale(options);
5737 var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';
\r
5741 * @class Control.Attribution
\r
5742 * @aka L.Control.Attribution
\r
5743 * @inherits Control
\r
5745 * 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.
\r
5748 var Attribution = Control.extend({
\r
5750 // @aka Control.Attribution options
\r
5752 position: 'bottomright',
\r
5754 // @option prefix: String|false = 'Leaflet'
\r
5755 // The HTML text shown before the attributions. Pass `false` to disable.
\r
5756 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
\r
5759 initialize: function (options) {
\r
5760 setOptions(this, options);
\r
5762 this._attributions = {};
\r
5765 onAdd: function (map) {
\r
5766 map.attributionControl = this;
\r
5767 this._container = create$1('div', 'leaflet-control-attribution');
\r
5768 disableClickPropagation(this._container);
\r
5770 // TODO ugly, refactor
\r
5771 for (var i in map._layers) {
\r
5772 if (map._layers[i].getAttribution) {
\r
5773 this.addAttribution(map._layers[i].getAttribution());
\r
5779 map.on('layeradd', this._addAttribution, this);
\r
5781 return this._container;
\r
5784 onRemove: function (map) {
\r
5785 map.off('layeradd', this._addAttribution, this);
\r
5788 _addAttribution: function (ev) {
\r
5789 if (ev.layer.getAttribution) {
\r
5790 this.addAttribution(ev.layer.getAttribution());
\r
5791 ev.layer.once('remove', function () {
\r
5792 this.removeAttribution(ev.layer.getAttribution());
\r
5797 // @method setPrefix(prefix: String|false): this
\r
5798 // The HTML text shown before the attributions. Pass `false` to disable.
\r
5799 setPrefix: function (prefix) {
\r
5800 this.options.prefix = prefix;
\r
5805 // @method addAttribution(text: String): this
\r
5806 // Adds an attribution text (e.g. `'© OpenStreetMap contributors'`).
\r
5807 addAttribution: function (text) {
\r
5808 if (!text) { return this; }
\r
5810 if (!this._attributions[text]) {
\r
5811 this._attributions[text] = 0;
\r
5813 this._attributions[text]++;
\r
5820 // @method removeAttribution(text: String): this
\r
5821 // Removes an attribution text.
\r
5822 removeAttribution: function (text) {
\r
5823 if (!text) { return this; }
\r
5825 if (this._attributions[text]) {
\r
5826 this._attributions[text]--;
\r
5833 _update: function () {
\r
5834 if (!this._map) { return; }
\r
5838 for (var i in this._attributions) {
\r
5839 if (this._attributions[i]) {
\r
5844 var prefixAndAttribs = [];
\r
5846 if (this.options.prefix) {
\r
5847 prefixAndAttribs.push(this.options.prefix);
\r
5849 if (attribs.length) {
\r
5850 prefixAndAttribs.push(attribs.join(', '));
\r
5853 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
\r
5858 // @section Control options
\r
5859 // @option attributionControl: Boolean = true
\r
5860 // Whether a [attribution control](#control-attribution) is added to the map by default.
\r
5861 Map.mergeOptions({
\r
5862 attributionControl: true
\r
5865 Map.addInitHook(function () {
\r
5866 if (this.options.attributionControl) {
\r
5867 new Attribution().addTo(this);
\r
5871 // @namespace Control.Attribution
\r
5872 // @factory L.control.attribution(options: Control.Attribution options)
\r
5873 // Creates an attribution control.
\r
5874 var attribution = function (options) {
\r
5875 return new Attribution(options);
\r
5878 Control.Layers = Layers;
5879 Control.Zoom = Zoom;
5880 Control.Scale = Scale;
5881 Control.Attribution = Attribution;
5883 control.layers = layers;
5884 control.zoom = zoom;
5885 control.scale = scale;
5886 control.attribution = attribution;
5889 L.Handler is a base class for handler classes that are used internally to inject
5890 interaction features like dragging to classes like Map and Marker.
5895 // Abstract class for map interaction handlers
5897 var Handler = Class.extend({
5898 initialize: function (map) {
5902 // @method enable(): this
5903 // Enables the handler
5904 enable: function () {
5905 if (this._enabled) { return this; }
5907 this._enabled = true;
5912 // @method disable(): this
5913 // Disables the handler
5914 disable: function () {
5915 if (!this._enabled) { return this; }
5917 this._enabled = false;
5922 // @method enabled(): Boolean
5923 // Returns `true` if the handler is enabled
5924 enabled: function () {
5925 return !!this._enabled;
5928 // @section Extension methods
5929 // Classes inheriting from `Handler` must implement the two following methods:
5930 // @method addHooks()
5931 // Called when the handler is enabled, should add event hooks.
5932 // @method removeHooks()
5933 // Called when the handler is disabled, should remove the event hooks added previously.
5936 // @section There is static function which can be called without instantiating L.Handler:
5937 // @function addTo(map: Map, name: String): this
5938 // Adds a new Handler to the given map with the given name.
5939 Handler.addTo = function (map, name) {
5940 map.addHandler(name, this);
5944 var Mixin = {Events: Events};
5947 * @class Draggable
\r
5948 * @aka L.Draggable
\r
5949 * @inherits Evented
\r
5951 * A class for making DOM elements draggable (including touch support).
\r
5952 * Used internally for map and marker dragging. Only works for elements
\r
5953 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
\r
5957 * var draggable = new L.Draggable(elementToDrag);
\r
5958 * draggable.enable();
\r
5962 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
\r
5964 var Draggable = Evented.extend({
\r
5968 // @aka Draggable options
\r
5969 // @option clickTolerance: Number = 3
\r
5970 // The max number of pixels a user can shift the mouse pointer during a click
\r
5971 // for it to be considered a valid click (as opposed to a mouse drag).
\r
5975 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
\r
5976 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
\r
5977 initialize: function (element, dragStartTarget, preventOutline, options) {
\r
5978 setOptions(this, options);
\r
5980 this._element = element;
\r
5981 this._dragStartTarget = dragStartTarget || element;
\r
5982 this._preventOutline = preventOutline;
\r
5985 // @method enable()
\r
5986 // Enables the dragging ability
\r
5987 enable: function () {
\r
5988 if (this._enabled) { return; }
\r
5990 on(this._dragStartTarget, START, this._onDown, this);
\r
5992 this._enabled = true;
\r
5995 // @method disable()
\r
5996 // Disables the dragging ability
\r
5997 disable: function () {
\r
5998 if (!this._enabled) { return; }
\r
6000 // If we're currently dragging this draggable,
\r
6001 // disabling it counts as first ending the drag.
\r
6002 if (Draggable._dragging === this) {
\r
6003 this.finishDrag(true);
\r
6006 off(this._dragStartTarget, START, this._onDown, this);
\r
6008 this._enabled = false;
\r
6009 this._moved = false;
\r
6012 _onDown: function (e) {
\r
6013 // Ignore the event if disabled; this happens in IE11
\r
6014 // under some circumstances, see #3666.
\r
6015 if (!this._enabled) { return; }
\r
6017 this._moved = false;
\r
6019 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
\r
6021 if (e.touches && e.touches.length !== 1) {
\r
6022 // Finish dragging to avoid conflict with touchZoom
\r
6023 if (Draggable._dragging === this) {
\r
6024 this.finishDrag();
\r
6029 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
\r
6030 Draggable._dragging = this; // Prevent dragging multiple objects at once.
\r
6032 if (this._preventOutline) {
\r
6033 preventOutline(this._element);
\r
6036 disableImageDrag();
\r
6037 disableTextSelection();
\r
6039 if (this._moving) { return; }
\r
6041 // @event down: Event
\r
6042 // Fired when a drag is about to start.
\r
6043 this.fire('down');
\r
6045 var first = e.touches ? e.touches[0] : e,
\r
6046 sizedParent = getSizedParentNode(this._element);
\r
6048 this._startPoint = new Point(first.clientX, first.clientY);
\r
6049 this._startPos = getPosition(this._element);
\r
6051 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
\r
6052 this._parentScale = getScale(sizedParent);
\r
6054 var mouseevent = e.type === 'mousedown';
\r
6055 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
\r
6056 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
\r
6059 _onMove: function (e) {
\r
6060 // Ignore the event if disabled; this happens in IE11
\r
6061 // under some circumstances, see #3666.
\r
6062 if (!this._enabled) { return; }
\r
6064 if (e.touches && e.touches.length > 1) {
\r
6065 this._moved = true;
\r
6069 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
\r
6070 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
\r
6072 if (!offset.x && !offset.y) { return; }
\r
6073 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
\r
6075 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
\r
6076 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
\r
6077 // and we can use the cached value for the scale.
\r
6078 offset.x /= this._parentScale.x;
\r
6079 offset.y /= this._parentScale.y;
\r
6081 preventDefault(e);
\r
6083 if (!this._moved) {
\r
6084 // @event dragstart: Event
\r
6085 // Fired when a drag starts
\r
6086 this.fire('dragstart');
\r
6088 this._moved = true;
\r
6090 addClass(document.body, 'leaflet-dragging');
\r
6092 this._lastTarget = e.target || e.srcElement;
\r
6093 // IE and Edge do not give the <use> element, so fetch it
\r
6095 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
\r
6096 this._lastTarget = this._lastTarget.correspondingUseElement;
\r
6098 addClass(this._lastTarget, 'leaflet-drag-target');
\r
6101 this._newPos = this._startPos.add(offset);
\r
6102 this._moving = true;
\r
6104 this._lastEvent = e;
\r
6105 this._updatePosition();
\r
6108 _updatePosition: function () {
\r
6109 var e = {originalEvent: this._lastEvent};
\r
6111 // @event predrag: Event
\r
6112 // Fired continuously during dragging *before* each corresponding
\r
6113 // update of the element's position.
\r
6114 this.fire('predrag', e);
\r
6115 setPosition(this._element, this._newPos);
\r
6117 // @event drag: Event
\r
6118 // Fired continuously during dragging.
\r
6119 this.fire('drag', e);
\r
6122 _onUp: function () {
\r
6123 // Ignore the event if disabled; this happens in IE11
\r
6124 // under some circumstances, see #3666.
\r
6125 if (!this._enabled) { return; }
\r
6126 this.finishDrag();
\r
6129 finishDrag: function (noInertia) {
\r
6130 removeClass(document.body, 'leaflet-dragging');
\r
6132 if (this._lastTarget) {
\r
6133 removeClass(this._lastTarget, 'leaflet-drag-target');
\r
6134 this._lastTarget = null;
\r
6137 off(document, 'mousemove touchmove', this._onMove, this);
\r
6138 off(document, 'mouseup touchend touchcancel', this._onUp, this);
\r
6140 enableImageDrag();
\r
6141 enableTextSelection();
\r
6143 var fireDragend = this._moved && this._moving;
\r
6145 this._moving = false;
\r
6146 Draggable._dragging = false;
\r
6148 if (fireDragend) {
\r
6149 // @event dragend: DragEndEvent
\r
6150 // Fired when the drag ends.
\r
6151 this.fire('dragend', {
\r
6152 noInertia: noInertia,
\r
6153 distance: this._newPos.distanceTo(this._startPos)
\r
6161 * @namespace PolyUtil
\r
6162 * Various utility functions for polygon geometries.
\r
6165 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
\r
6166 * 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)).
\r
6167 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
\r
6168 * performance. Note that polygon points needs different algorithm for clipping
\r
6169 * than polyline, so there's a separate method for it.
\r
6171 function clipPolygon(points, bounds, round) {
\r
6172 var clippedPoints,
\r
6173 edges = [1, 4, 2, 8],
\r
6178 for (i = 0, len = points.length; i < len; i++) {
\r
6179 points[i]._code = _getBitCode(points[i], bounds);
\r
6182 // for each edge (left, bottom, right, top)
\r
6183 for (k = 0; k < 4; k++) {
\r
6185 clippedPoints = [];
\r
6187 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
\r
6191 // if a is inside the clip window
\r
6192 if (!(a._code & edge)) {
\r
6193 // if b is outside the clip window (a->b goes out of screen)
\r
6194 if (b._code & edge) {
\r
6195 p = _getEdgeIntersection(b, a, edge, bounds, round);
\r
6196 p._code = _getBitCode(p, bounds);
\r
6197 clippedPoints.push(p);
\r
6199 clippedPoints.push(a);
\r
6201 // else if b is inside the clip window (a->b enters the screen)
\r
6202 } else if (!(b._code & edge)) {
\r
6203 p = _getEdgeIntersection(b, a, edge, bounds, round);
\r
6204 p._code = _getBitCode(p, bounds);
\r
6205 clippedPoints.push(p);
\r
6208 points = clippedPoints;
\r
6214 /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng
\r
6215 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
\r
6217 function polygonCenter(latlngs, crs) {
\r
6218 var i, j, p1, p2, f, area, x, y, center;
\r
6220 if (!latlngs || latlngs.length === 0) {
\r
6221 throw new Error('latlngs not passed');
\r
6224 if (!isFlat(latlngs)) {
\r
6225 console.warn('latlngs are not flat! Only the first ring will be used');
\r
6226 latlngs = latlngs[0];
\r
6229 var centroidLatLng = toLatLng([0, 0]);
\r
6231 var bounds = toLatLngBounds(latlngs);
\r
6232 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
\r
6233 // tests showed that below 1700 rounding errors are happening
\r
6234 if (areaBounds < 1700) {
\r
6235 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
\r
6236 centroidLatLng = centroid(latlngs);
\r
6239 var len = latlngs.length;
\r
6241 for (i = 0; i < len; i++) {
\r
6242 var latlng = toLatLng(latlngs[i]);
\r
6243 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
\r
6248 // polygon centroid algorithm;
\r
6249 for (i = 0, j = len - 1; i < len; j = i++) {
\r
6253 f = p1.y * p2.x - p2.y * p1.x;
\r
6254 x += (p1.x + p2.x) * f;
\r
6255 y += (p1.y + p2.y) * f;
\r
6260 // Polygon is so small that all points are on same pixel.
\r
6261 center = points[0];
\r
6263 center = [x / area, y / area];
\r
6266 var latlngCenter = crs.unproject(toPoint(center));
\r
6267 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
\r
6270 /* @function centroid(latlngs: LatLng[]): LatLng
\r
6271 * Returns the 'center of mass' of the passed LatLngs.
\r
6273 function centroid(coords) {
\r
6277 for (var i = 0; i < coords.length; i++) {
\r
6278 var latlng = toLatLng(coords[i]);
\r
6279 latSum += latlng.lat;
\r
6280 lngSum += latlng.lng;
\r
6283 return toLatLng([latSum / len, lngSum / len]);
\r
6288 clipPolygon: clipPolygon,
6289 polygonCenter: polygonCenter,
6294 * @namespace LineUtil
\r
6296 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
\r
6299 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
\r
6300 // Improves rendering performance dramatically by lessening the number of points to draw.
\r
6302 // @function simplify(points: Point[], tolerance: Number): Point[]
\r
6303 // Dramatically reduces the number of points in a polyline while retaining
\r
6304 // its shape and returns a new array of simplified points, using the
\r
6305 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
\r
6306 // Used for a huge performance boost when processing/displaying Leaflet polylines for
\r
6307 // each zoom level and also reducing visual noise. tolerance affects the amount of
\r
6308 // simplification (lesser value means higher quality but slower and with more points).
\r
6309 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
\r
6310 function simplify(points, tolerance) {
\r
6311 if (!tolerance || !points.length) {
\r
6312 return points.slice();
\r
6315 var sqTolerance = tolerance * tolerance;
\r
6317 // stage 1: vertex reduction
\r
6318 points = _reducePoints(points, sqTolerance);
\r
6320 // stage 2: Douglas-Peucker simplification
\r
6321 points = _simplifyDP(points, sqTolerance);
\r
6326 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
\r
6327 // Returns the distance between point `p` and segment `p1` to `p2`.
\r
6328 function pointToSegmentDistance(p, p1, p2) {
\r
6329 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
\r
6332 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
\r
6333 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
\r
6334 function closestPointOnSegment(p, p1, p2) {
\r
6335 return _sqClosestPointOnSegment(p, p1, p2);
\r
6338 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
\r
6339 function _simplifyDP(points, sqTolerance) {
\r
6341 var len = points.length,
\r
6342 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
\r
6343 markers = new ArrayConstructor(len);
\r
6345 markers[0] = markers[len - 1] = 1;
\r
6347 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
\r
6352 for (i = 0; i < len; i++) {
\r
6354 newPoints.push(points[i]);
\r
6361 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
\r
6363 var maxSqDist = 0,
\r
6366 for (i = first + 1; i <= last - 1; i++) {
\r
6367 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
\r
6369 if (sqDist > maxSqDist) {
\r
6371 maxSqDist = sqDist;
\r
6375 if (maxSqDist > sqTolerance) {
\r
6376 markers[index] = 1;
\r
6378 _simplifyDPStep(points, markers, sqTolerance, first, index);
\r
6379 _simplifyDPStep(points, markers, sqTolerance, index, last);
\r
6383 // reduce points that are too close to each other to a single point
\r
6384 function _reducePoints(points, sqTolerance) {
\r
6385 var reducedPoints = [points[0]];
\r
6387 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
\r
6388 if (_sqDist(points[i], points[prev]) > sqTolerance) {
\r
6389 reducedPoints.push(points[i]);
\r
6393 if (prev < len - 1) {
\r
6394 reducedPoints.push(points[len - 1]);
\r
6396 return reducedPoints;
\r
6401 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
\r
6402 // Clips the segment a to b by rectangular bounds with the
\r
6403 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
\r
6404 // (modifying the segment points directly!). Used by Leaflet to only show polyline
\r
6405 // points that are on the screen or near, increasing performance.
\r
6406 function clipSegment(a, b, bounds, useLastCode, round) {
\r
6407 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
\r
6408 codeB = _getBitCode(b, bounds),
\r
6410 codeOut, p, newCode;
\r
6412 // save 2nd code to avoid calculating it on the next segment
\r
6413 _lastCode = codeB;
\r
6416 // if a,b is inside the clip window (trivial accept)
\r
6417 if (!(codeA | codeB)) {
\r
6421 // if a,b is outside the clip window (trivial reject)
\r
6422 if (codeA & codeB) {
\r
6427 codeOut = codeA || codeB;
\r
6428 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
\r
6429 newCode = _getBitCode(p, bounds);
\r
6431 if (codeOut === codeA) {
\r
6441 function _getEdgeIntersection(a, b, code, bounds, round) {
\r
6442 var dx = b.x - a.x,
\r
6448 if (code & 8) { // top
\r
6449 x = a.x + dx * (max.y - a.y) / dy;
\r
6452 } else if (code & 4) { // bottom
\r
6453 x = a.x + dx * (min.y - a.y) / dy;
\r
6456 } else if (code & 2) { // right
\r
6458 y = a.y + dy * (max.x - a.x) / dx;
\r
6460 } else if (code & 1) { // left
\r
6462 y = a.y + dy * (min.x - a.x) / dx;
\r
6465 return new Point(x, y, round);
\r
6468 function _getBitCode(p, bounds) {
\r
6471 if (p.x < bounds.min.x) { // left
\r
6473 } else if (p.x > bounds.max.x) { // right
\r
6477 if (p.y < bounds.min.y) { // bottom
\r
6479 } else if (p.y > bounds.max.y) { // top
\r
6486 // square distance (to avoid unnecessary Math.sqrt calls)
\r
6487 function _sqDist(p1, p2) {
\r
6488 var dx = p2.x - p1.x,
\r
6490 return dx * dx + dy * dy;
\r
6493 // return closest point on segment or distance to that point
\r
6494 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
\r
6499 dot = dx * dx + dy * dy,
\r
6503 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
\r
6508 } else if (t > 0) {
\r
6517 return sqDist ? dx * dx + dy * dy : new Point(x, y);
\r
6521 // @function isFlat(latlngs: LatLng[]): Boolean
\r
6522 // Returns true if `latlngs` is a flat array, false is nested.
\r
6523 function isFlat(latlngs) {
\r
6524 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
\r
6527 function _flat(latlngs) {
\r
6528 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
\r
6529 return isFlat(latlngs);
\r
6532 /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
\r
6533 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
\r
6535 function polylineCenter(latlngs, crs) {
\r
6536 var i, halfDist, segDist, dist, p1, p2, ratio, center;
\r
6538 if (!latlngs || latlngs.length === 0) {
\r
6539 throw new Error('latlngs not passed');
\r
6542 if (!isFlat(latlngs)) {
\r
6543 console.warn('latlngs are not flat! Only the first ring will be used');
\r
6544 latlngs = latlngs[0];
\r
6547 var centroidLatLng = toLatLng([0, 0]);
\r
6549 var bounds = toLatLngBounds(latlngs);
\r
6550 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
\r
6551 // tests showed that below 1700 rounding errors are happening
\r
6552 if (areaBounds < 1700) {
\r
6553 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
\r
6554 centroidLatLng = centroid(latlngs);
\r
6557 var len = latlngs.length;
\r
6559 for (i = 0; i < len; i++) {
\r
6560 var latlng = toLatLng(latlngs[i]);
\r
6561 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
\r
6564 for (i = 0, halfDist = 0; i < len - 1; i++) {
\r
6565 halfDist += points[i].distanceTo(points[i + 1]) / 2;
\r
6568 // The line is so small in the current view that all points are on the same pixel.
\r
6569 if (halfDist === 0) {
\r
6570 center = points[0];
\r
6572 for (i = 0, dist = 0; i < len - 1; i++) {
\r
6574 p2 = points[i + 1];
\r
6575 segDist = p1.distanceTo(p2);
\r
6578 if (dist > halfDist) {
\r
6579 ratio = (dist - halfDist) / segDist;
\r
6581 p2.x - ratio * (p2.x - p1.x),
\r
6582 p2.y - ratio * (p2.y - p1.y)
\r
6589 var latlngCenter = crs.unproject(toPoint(center));
\r
6590 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
\r
6596 pointToSegmentDistance: pointToSegmentDistance,
6597 closestPointOnSegment: closestPointOnSegment,
6598 clipSegment: clipSegment,
6599 _getEdgeIntersection: _getEdgeIntersection,
6600 _getBitCode: _getBitCode,
6601 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6604 polylineCenter: polylineCenter
6608 * @namespace Projection
\r
6610 * Leaflet comes with a set of already defined Projections out of the box:
\r
6612 * @projection L.Projection.LonLat
\r
6614 * Equirectangular, or Plate Carree projection — the most simple projection,
\r
6615 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
\r
6616 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
\r
6617 * `EPSG:4326` and `Simple` CRS.
\r
6621 project: function (latlng) {
\r
6622 return new Point(latlng.lng, latlng.lat);
\r
6625 unproject: function (point) {
\r
6626 return new LatLng(point.y, point.x);
\r
6629 bounds: new Bounds([-180, -90], [180, 90])
\r
6633 * @namespace Projection
\r
6634 * @projection L.Projection.Mercator
\r
6636 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
\r
6641 R_MINOR: 6356752.314245179,
\r
6643 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
\r
6645 project: function (latlng) {
\r
6646 var d = Math.PI / 180,
\r
6648 y = latlng.lat * d,
\r
6649 tmp = this.R_MINOR / r,
\r
6650 e = Math.sqrt(1 - tmp * tmp),
\r
6651 con = e * Math.sin(y);
\r
6653 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
\r
6654 y = -r * Math.log(Math.max(ts, 1E-10));
\r
6656 return new Point(latlng.lng * d * r, y);
\r
6659 unproject: function (point) {
\r
6660 var d = 180 / Math.PI,
\r
6662 tmp = this.R_MINOR / r,
\r
6663 e = Math.sqrt(1 - tmp * tmp),
\r
6664 ts = Math.exp(-point.y / r),
\r
6665 phi = Math.PI / 2 - 2 * Math.atan(ts);
\r
6667 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
\r
6668 con = e * Math.sin(phi);
\r
6669 con = Math.pow((1 - con) / (1 + con), e / 2);
\r
6670 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
\r
6674 return new LatLng(phi * d, point.x * d / r);
\r
6681 * An object with methods for projecting geographical coordinates of the world onto
6682 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6684 * @property bounds: Bounds
6685 * The bounds (specified in CRS units) where the projection is valid
6687 * @method project(latlng: LatLng): Point
6688 * Projects geographical coordinates into a 2D point.
6689 * Only accepts actual `L.LatLng` instances, not arrays.
6691 * @method unproject(point: Point): LatLng
6692 * The inverse of `project`. Projects a 2D point into a geographical location.
6693 * Only accepts actual `L.Point` instances, not arrays.
6695 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6696 * and can't be instantiated. Also, new classes can't inherit from them,
6697 * and methods can't be added to them with the `include` function.
6705 SphericalMercator: SphericalMercator
6710 * @crs L.CRS.EPSG3395
\r
6712 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
\r
6714 var EPSG3395 = extend({}, Earth, {
\r
6715 code: 'EPSG:3395',
\r
6716 projection: Mercator,
\r
6718 transformation: (function () {
\r
6719 var scale = 0.5 / (Math.PI * Mercator.R);
\r
6720 return toTransformation(scale, 0.5, -scale, 0.5);
\r
6726 * @crs L.CRS.EPSG4326
\r
6728 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
\r
6730 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
\r
6731 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
\r
6732 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
\r
6733 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
\r
6734 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
\r
6737 var EPSG4326 = extend({}, Earth, {
\r
6738 code: 'EPSG:4326',
\r
6739 projection: LonLat,
\r
6740 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
\r
6747 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6748 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6749 * axis should still be inverted (going from bottom to top). `distance()` returns
6750 * simple euclidean distance.
6753 var Simple = extend({}, CRS, {
6755 transformation: toTransformation(1, 0, -1, 0),
6757 scale: function (zoom) {
6758 return Math.pow(2, zoom);
6761 zoom: function (scale) {
6762 return Math.log(scale) / Math.LN2;
6765 distance: function (latlng1, latlng2) {
6766 var dx = latlng2.lng - latlng1.lng,
6767 dy = latlng2.lat - latlng1.lat;
6769 return Math.sqrt(dx * dx + dy * dy);
6776 CRS.EPSG3395 = EPSG3395;
6777 CRS.EPSG3857 = EPSG3857;
6778 CRS.EPSG900913 = EPSG900913;
6779 CRS.EPSG4326 = EPSG4326;
6780 CRS.Simple = Simple;
6788 * A set of methods from the Layer base class that all Leaflet layers use.
6789 * Inherits all methods, options and events from `L.Evented`.
6794 * var layer = L.marker(latlng).addTo(map);
6800 * Fired after the layer is added to a map
6802 * @event remove: Event
6803 * Fired after the layer is removed from a map
6807 var Layer = Evented.extend({
6809 // Classes extending `L.Layer` will inherit the following options:
6811 // @option pane: String = 'overlayPane'
6812 // 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.
6813 pane: 'overlayPane',
6815 // @option attribution: String = null
6816 // 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.
6819 bubblingMouseEvents: true
6823 * Classes extending `L.Layer` will inherit the following methods:
6825 * @method addTo(map: Map|LayerGroup): this
6826 * Adds the layer to the given map or layer group.
6828 addTo: function (map) {
6833 // @method remove: this
6834 // Removes the layer from the map it is currently active on.
6835 remove: function () {
6836 return this.removeFrom(this._map || this._mapToAdd);
6839 // @method removeFrom(map: Map): this
6840 // Removes the layer from the given map
6843 // @method removeFrom(group: LayerGroup): this
6844 // Removes the layer from the given `LayerGroup`
6845 removeFrom: function (obj) {
6847 obj.removeLayer(this);
6852 // @method getPane(name? : String): HTMLElement
6853 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6854 getPane: function (name) {
6855 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6858 addInteractiveTarget: function (targetEl) {
6859 this._map._targets[stamp(targetEl)] = this;
6863 removeInteractiveTarget: function (targetEl) {
6864 delete this._map._targets[stamp(targetEl)];
6868 // @method getAttribution: String
6869 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6870 getAttribution: function () {
6871 return this.options.attribution;
6874 _layerAdd: function (e) {
6877 // check in case layer gets added and then removed before the map is ready
6878 if (!map.hasLayer(this)) { return; }
6881 this._zoomAnimated = map._zoomAnimated;
6883 if (this.getEvents) {
6884 var events = this.getEvents();
6885 map.on(events, this);
6886 this.once('remove', function () {
6887 map.off(events, this);
6894 map.fire('layeradd', {layer: this});
6898 /* @section Extension methods
6901 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6903 * @method onAdd(map: Map): this
6904 * 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).
6906 * @method onRemove(map: Map): this
6907 * 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).
6909 * @method getEvents(): Object
6910 * 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.
6912 * @method getAttribution(): String
6913 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6915 * @method beforeAdd(map: Map): this
6916 * 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.
6921 * @section Layer events
6923 * @event layeradd: LayerEvent
6924 * Fired when a new layer is added to the map.
6926 * @event layerremove: LayerEvent
6927 * Fired when some layer is removed from the map
6929 * @section Methods for Layers and Controls
6932 // @method addLayer(layer: Layer): this
6933 // Adds the given layer to the map
6934 addLayer: function (layer) {
6935 if (!layer._layerAdd) {
6936 throw new Error('The provided object is not a Layer.');
6939 var id = stamp(layer);
6940 if (this._layers[id]) { return this; }
6941 this._layers[id] = layer;
6943 layer._mapToAdd = this;
6945 if (layer.beforeAdd) {
6946 layer.beforeAdd(this);
6949 this.whenReady(layer._layerAdd, layer);
6954 // @method removeLayer(layer: Layer): this
6955 // Removes the given layer from the map.
6956 removeLayer: function (layer) {
6957 var id = stamp(layer);
6959 if (!this._layers[id]) { return this; }
6962 layer.onRemove(this);
6965 delete this._layers[id];
6968 this.fire('layerremove', {layer: layer});
6969 layer.fire('remove');
6972 layer._map = layer._mapToAdd = null;
6977 // @method hasLayer(layer: Layer): Boolean
6978 // Returns `true` if the given layer is currently added to the map
6979 hasLayer: function (layer) {
6980 return stamp(layer) in this._layers;
6983 /* @method eachLayer(fn: Function, context?: Object): this
6984 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6986 * map.eachLayer(function(layer){
6987 * layer.bindPopup('Hello');
6991 eachLayer: function (method, context) {
6992 for (var i in this._layers) {
6993 method.call(context, this._layers[i]);
6998 _addLayers: function (layers) {
6999 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
7001 for (var i = 0, len = layers.length; i < len; i++) {
7002 this.addLayer(layers[i]);
7006 _addZoomLimit: function (layer) {
7007 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
7008 this._zoomBoundLayers[stamp(layer)] = layer;
7009 this._updateZoomLevels();
7013 _removeZoomLimit: function (layer) {
7014 var id = stamp(layer);
7016 if (this._zoomBoundLayers[id]) {
7017 delete this._zoomBoundLayers[id];
7018 this._updateZoomLevels();
7022 _updateZoomLevels: function () {
7023 var minZoom = Infinity,
7024 maxZoom = -Infinity,
7025 oldZoomSpan = this._getZoomSpan();
7027 for (var i in this._zoomBoundLayers) {
7028 var options = this._zoomBoundLayers[i].options;
7030 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
7031 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
7034 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
7035 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
7037 // @section Map state change events
7038 // @event zoomlevelschange: Event
7039 // Fired when the number of zoomlevels on the map is changed due
7040 // to adding or removing a layer.
7041 if (oldZoomSpan !== this._getZoomSpan()) {
7042 this.fire('zoomlevelschange');
7045 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
7046 this.setZoom(this._layersMaxZoom);
7048 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
7049 this.setZoom(this._layersMinZoom);
7055 * @class LayerGroup
\r
7056 * @aka L.LayerGroup
\r
7057 * @inherits Interactive layer
\r
7059 * Used to group several layers and handle them as one. If you add it to the map,
\r
7060 * any layers added or removed from the group will be added/removed on the map as
\r
7061 * well. Extends `Layer`.
\r
7066 * L.layerGroup([marker1, marker2])
\r
7067 * .addLayer(polyline)
\r
7072 var LayerGroup = Layer.extend({
\r
7074 initialize: function (layers, options) {
\r
7075 setOptions(this, options);
\r
7077 this._layers = {};
\r
7082 for (i = 0, len = layers.length; i < len; i++) {
\r
7083 this.addLayer(layers[i]);
\r
7088 // @method addLayer(layer: Layer): this
\r
7089 // Adds the given layer to the group.
\r
7090 addLayer: function (layer) {
\r
7091 var id = this.getLayerId(layer);
\r
7093 this._layers[id] = layer;
\r
7096 this._map.addLayer(layer);
\r
7102 // @method removeLayer(layer: Layer): this
\r
7103 // Removes the given layer from the group.
\r
7105 // @method removeLayer(id: Number): this
\r
7106 // Removes the layer with the given internal ID from the group.
\r
7107 removeLayer: function (layer) {
\r
7108 var id = layer in this._layers ? layer : this.getLayerId(layer);
\r
7110 if (this._map && this._layers[id]) {
\r
7111 this._map.removeLayer(this._layers[id]);
\r
7114 delete this._layers[id];
\r
7119 // @method hasLayer(layer: Layer): Boolean
\r
7120 // Returns `true` if the given layer is currently added to the group.
\r
7122 // @method hasLayer(id: Number): Boolean
\r
7123 // Returns `true` if the given internal ID is currently added to the group.
\r
7124 hasLayer: function (layer) {
\r
7125 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
\r
7126 return layerId in this._layers;
\r
7129 // @method clearLayers(): this
\r
7130 // Removes all the layers from the group.
\r
7131 clearLayers: function () {
\r
7132 return this.eachLayer(this.removeLayer, this);
\r
7135 // @method invoke(methodName: String, …): this
\r
7136 // Calls `methodName` on every layer contained in this group, passing any
\r
7137 // additional parameters. Has no effect if the layers contained do not
\r
7138 // implement `methodName`.
\r
7139 invoke: function (methodName) {
\r
7140 var args = Array.prototype.slice.call(arguments, 1),
\r
7143 for (i in this._layers) {
\r
7144 layer = this._layers[i];
\r
7146 if (layer[methodName]) {
\r
7147 layer[methodName].apply(layer, args);
\r
7154 onAdd: function (map) {
\r
7155 this.eachLayer(map.addLayer, map);
\r
7158 onRemove: function (map) {
\r
7159 this.eachLayer(map.removeLayer, map);
\r
7162 // @method eachLayer(fn: Function, context?: Object): this
\r
7163 // Iterates over the layers of the group, optionally specifying context of the iterator function.
\r
7165 // group.eachLayer(function (layer) {
\r
7166 // layer.bindPopup('Hello');
\r
7169 eachLayer: function (method, context) {
\r
7170 for (var i in this._layers) {
\r
7171 method.call(context, this._layers[i]);
\r
7176 // @method getLayer(id: Number): Layer
\r
7177 // Returns the layer with the given internal ID.
\r
7178 getLayer: function (id) {
\r
7179 return this._layers[id];
\r
7182 // @method getLayers(): Layer[]
\r
7183 // Returns an array of all the layers added to the group.
\r
7184 getLayers: function () {
\r
7186 this.eachLayer(layers.push, layers);
\r
7190 // @method setZIndex(zIndex: Number): this
\r
7191 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
\r
7192 setZIndex: function (zIndex) {
\r
7193 return this.invoke('setZIndex', zIndex);
\r
7196 // @method getLayerId(layer: Layer): Number
\r
7197 // Returns the internal ID for a layer
\r
7198 getLayerId: function (layer) {
\r
7199 return stamp(layer);
\r
7204 // @factory L.layerGroup(layers?: Layer[], options?: Object)
\r
7205 // Create a layer group, optionally given an initial set of layers and an `options` object.
\r
7206 var layerGroup = function (layers, options) {
\r
7207 return new LayerGroup(layers, options);
\r
7211 * @class FeatureGroup
\r
7212 * @aka L.FeatureGroup
\r
7213 * @inherits LayerGroup
\r
7215 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
\r
7216 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
\r
7217 * * Events are propagated to the `FeatureGroup`, so if the group has an event
\r
7218 * handler, it will handle events from any of the layers. This includes mouse events
\r
7219 * and custom events.
\r
7220 * * Has `layeradd` and `layerremove` events
\r
7225 * L.featureGroup([marker1, marker2, polyline])
\r
7226 * .bindPopup('Hello world!')
\r
7227 * .on('click', function() { alert('Clicked on a member of the group!'); })
\r
7232 var FeatureGroup = LayerGroup.extend({
\r
7234 addLayer: function (layer) {
\r
7235 if (this.hasLayer(layer)) {
\r
7239 layer.addEventParent(this);
\r
7241 LayerGroup.prototype.addLayer.call(this, layer);
\r
7243 // @event layeradd: LayerEvent
\r
7244 // Fired when a layer is added to this `FeatureGroup`
\r
7245 return this.fire('layeradd', {layer: layer});
\r
7248 removeLayer: function (layer) {
\r
7249 if (!this.hasLayer(layer)) {
\r
7252 if (layer in this._layers) {
\r
7253 layer = this._layers[layer];
\r
7256 layer.removeEventParent(this);
\r
7258 LayerGroup.prototype.removeLayer.call(this, layer);
\r
7260 // @event layerremove: LayerEvent
\r
7261 // Fired when a layer is removed from this `FeatureGroup`
\r
7262 return this.fire('layerremove', {layer: layer});
\r
7265 // @method setStyle(style: Path options): this
\r
7266 // Sets the given path options to each layer of the group that has a `setStyle` method.
\r
7267 setStyle: function (style) {
\r
7268 return this.invoke('setStyle', style);
\r
7271 // @method bringToFront(): this
\r
7272 // Brings the layer group to the top of all other layers
\r
7273 bringToFront: function () {
\r
7274 return this.invoke('bringToFront');
\r
7277 // @method bringToBack(): this
\r
7278 // Brings the layer group to the back of all other layers
\r
7279 bringToBack: function () {
\r
7280 return this.invoke('bringToBack');
\r
7283 // @method getBounds(): LatLngBounds
\r
7284 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
\r
7285 getBounds: function () {
\r
7286 var bounds = new LatLngBounds();
\r
7288 for (var id in this._layers) {
\r
7289 var layer = this._layers[id];
\r
7290 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
\r
7296 // @factory L.featureGroup(layers?: Layer[], options?: Object)
\r
7297 // Create a feature group, optionally given an initial set of layers and an `options` object.
\r
7298 var featureGroup = function (layers, options) {
\r
7299 return new FeatureGroup(layers, options);
\r
7306 * Represents an icon to provide when creating a marker.
\r
7311 * var myIcon = L.icon({
\r
7312 * iconUrl: 'my-icon.png',
\r
7313 * iconRetinaUrl: 'my-icon@2x.png',
\r
7314 * iconSize: [38, 95],
\r
7315 * iconAnchor: [22, 94],
\r
7316 * popupAnchor: [-3, -76],
\r
7317 * shadowUrl: 'my-icon-shadow.png',
\r
7318 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
\r
7319 * shadowSize: [68, 95],
\r
7320 * shadowAnchor: [22, 94]
\r
7323 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
\r
7326 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
\r
7330 var Icon = Class.extend({
\r
7333 * @aka Icon options
\r
7335 * @option iconUrl: String = null
\r
7336 * **(required)** The URL to the icon image (absolute or relative to your script path).
\r
7338 * @option iconRetinaUrl: String = null
\r
7339 * The URL to a retina sized version of the icon image (absolute or relative to your
\r
7340 * script path). Used for Retina screen devices.
\r
7342 * @option iconSize: Point = null
\r
7343 * Size of the icon image in pixels.
\r
7345 * @option iconAnchor: Point = null
\r
7346 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
\r
7347 * will be aligned so that this point is at the marker's geographical location. Centered
\r
7348 * by default if size is specified, also can be set in CSS with negative margins.
\r
7350 * @option popupAnchor: Point = [0, 0]
\r
7351 * The coordinates of the point from which popups will "open", relative to the icon anchor.
\r
7353 * @option tooltipAnchor: Point = [0, 0]
\r
7354 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
\r
7356 * @option shadowUrl: String = null
\r
7357 * The URL to the icon shadow image. If not specified, no shadow image will be created.
\r
7359 * @option shadowRetinaUrl: String = null
\r
7361 * @option shadowSize: Point = null
\r
7362 * Size of the shadow image in pixels.
\r
7364 * @option shadowAnchor: Point = null
\r
7365 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
\r
7366 * as iconAnchor if not specified).
\r
7368 * @option className: String = ''
\r
7369 * A custom class name to assign to both icon and shadow images. Empty by default.
\r
7373 popupAnchor: [0, 0],
\r
7374 tooltipAnchor: [0, 0],
\r
7376 // @option crossOrigin: Boolean|String = false
\r
7377 // Whether the crossOrigin attribute will be added to the tiles.
\r
7378 // 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.
\r
7379 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
\r
7380 crossOrigin: false
\r
7383 initialize: function (options) {
\r
7384 setOptions(this, options);
\r
7387 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
\r
7388 // Called internally when the icon has to be shown, returns a `<img>` HTML element
\r
7389 // styled according to the options.
\r
7390 createIcon: function (oldIcon) {
\r
7391 return this._createIcon('icon', oldIcon);
\r
7394 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
\r
7395 // As `createIcon`, but for the shadow beneath it.
\r
7396 createShadow: function (oldIcon) {
\r
7397 return this._createIcon('shadow', oldIcon);
\r
7400 _createIcon: function (name, oldIcon) {
\r
7401 var src = this._getIconUrl(name);
\r
7404 if (name === 'icon') {
\r
7405 throw new Error('iconUrl not set in Icon options (see the docs).');
\r
7410 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
\r
7411 this._setIconStyles(img, name);
\r
7413 if (this.options.crossOrigin || this.options.crossOrigin === '') {
\r
7414 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
\r
7420 _setIconStyles: function (img, name) {
\r
7421 var options = this.options;
\r
7422 var sizeOption = options[name + 'Size'];
\r
7424 if (typeof sizeOption === 'number') {
\r
7425 sizeOption = [sizeOption, sizeOption];
\r
7428 var size = toPoint(sizeOption),
\r
7429 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
\r
7430 size && size.divideBy(2, true));
\r
7432 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
\r
7435 img.style.marginLeft = (-anchor.x) + 'px';
\r
7436 img.style.marginTop = (-anchor.y) + 'px';
\r
7440 img.style.width = size.x + 'px';
\r
7441 img.style.height = size.y + 'px';
\r
7445 _createImg: function (src, el) {
\r
7446 el = el || document.createElement('img');
\r
7451 _getIconUrl: function (name) {
\r
7452 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
\r
7457 // @factory L.icon(options: Icon options)
\r
7458 // Creates an icon instance with the given options.
\r
7459 function icon(options) {
\r
7460 return new Icon(options);
\r
7464 * @miniclass Icon.Default (Icon)
7465 * @aka L.Icon.Default
7468 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7469 * no icon is specified. Points to the blue marker image distributed with Leaflet
7472 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7473 * (which is a set of `Icon options`).
7475 * If you want to _completely_ replace the default icon, override the
7476 * `L.Marker.prototype.options.icon` with your own icon instead.
7479 var IconDefault = Icon.extend({
7482 iconUrl: 'marker-icon.png',
7483 iconRetinaUrl: 'marker-icon-2x.png',
7484 shadowUrl: 'marker-shadow.png',
7486 iconAnchor: [12, 41],
7487 popupAnchor: [1, -34],
7488 tooltipAnchor: [16, -28],
7489 shadowSize: [41, 41]
7492 _getIconUrl: function (name) {
7493 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only
7494 IconDefault.imagePath = this._detectIconPath();
7497 // @option imagePath: String
7498 // `Icon.Default` will try to auto-detect the location of the
7499 // blue icon images. If you are placing these images in a non-standard
7500 // way, set this option to point to the right path.
7501 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7504 _stripUrl: function (path) { // separate function to use in tests
7505 var strip = function (str, re, idx) {
7506 var match = re.exec(str);
7507 return match && match[idx];
7509 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7510 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7513 _detectIconPath: function () {
7514 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7515 var path = getStyle(el, 'background-image') ||
7516 getStyle(el, 'backgroundImage'); // IE8
7518 document.body.removeChild(el);
7519 path = this._stripUrl(path);
7520 if (path) { return path; }
7521 var link = document.querySelector('link[href$="leaflet.css"]');
7522 if (!link) { return ''; }
7523 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7528 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7532 /* @namespace Marker
7533 * @section Interaction handlers
7535 * 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:
7538 * marker.dragging.disable();
7541 * @property dragging: Handler
7542 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7545 var MarkerDrag = Handler.extend({
7546 initialize: function (marker) {
7547 this._marker = marker;
7550 addHooks: function () {
7551 var icon = this._marker._icon;
7553 if (!this._draggable) {
7554 this._draggable = new Draggable(icon, icon, true);
7557 this._draggable.on({
7558 dragstart: this._onDragStart,
7559 predrag: this._onPreDrag,
7561 dragend: this._onDragEnd
7564 addClass(icon, 'leaflet-marker-draggable');
7567 removeHooks: function () {
7568 this._draggable.off({
7569 dragstart: this._onDragStart,
7570 predrag: this._onPreDrag,
7572 dragend: this._onDragEnd
7575 if (this._marker._icon) {
7576 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7580 moved: function () {
7581 return this._draggable && this._draggable._moved;
7584 _adjustPan: function (e) {
7585 var marker = this._marker,
7587 speed = this._marker.options.autoPanSpeed,
7588 padding = this._marker.options.autoPanPadding,
7589 iconPos = getPosition(marker._icon),
7590 bounds = map.getPixelBounds(),
7591 origin = map.getPixelOrigin();
7593 var panBounds = toBounds(
7594 bounds.min._subtract(origin).add(padding),
7595 bounds.max._subtract(origin).subtract(padding)
7598 if (!panBounds.contains(iconPos)) {
7599 // Compute incremental movement
7600 var movement = toPoint(
7601 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7602 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7604 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7605 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7606 ).multiplyBy(speed);
7608 map.panBy(movement, {animate: false});
7610 this._draggable._newPos._add(movement);
7611 this._draggable._startPos._add(movement);
7613 setPosition(marker._icon, this._draggable._newPos);
7616 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7620 _onDragStart: function () {
7621 // @section Dragging events
7622 // @event dragstart: Event
7623 // Fired when the user starts dragging the marker.
7625 // @event movestart: Event
7626 // Fired when the marker starts moving (because of dragging).
7628 this._oldLatLng = this._marker.getLatLng();
7630 // When using ES6 imports it could not be set when `Popup` was not imported as well
7631 this._marker.closePopup && this._marker.closePopup();
7638 _onPreDrag: function (e) {
7639 if (this._marker.options.autoPan) {
7640 cancelAnimFrame(this._panRequest);
7641 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7645 _onDrag: function (e) {
7646 var marker = this._marker,
7647 shadow = marker._shadow,
7648 iconPos = getPosition(marker._icon),
7649 latlng = marker._map.layerPointToLatLng(iconPos);
7651 // update shadow position
7653 setPosition(shadow, iconPos);
7656 marker._latlng = latlng;
7658 e.oldLatLng = this._oldLatLng;
7660 // @event drag: Event
7661 // Fired repeatedly while the user drags the marker.
7667 _onDragEnd: function (e) {
7668 // @event dragend: DragEndEvent
7669 // Fired when the user stops dragging the marker.
7671 cancelAnimFrame(this._panRequest);
7673 // @event moveend: Event
7674 // Fired when the marker stops moving (because of dragging).
7675 delete this._oldLatLng;
7678 .fire('dragend', e);
7684 * @inherits Interactive layer
\r
7686 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
\r
7691 * L.marker([50.5, 30.5]).addTo(map);
\r
7695 var Marker = Layer.extend({
\r
7698 // @aka Marker options
\r
7700 // @option icon: Icon = *
\r
7701 // Icon instance to use for rendering the marker.
\r
7702 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
\r
7703 // If not specified, a common instance of `L.Icon.Default` is used.
\r
7704 icon: new IconDefault(),
\r
7706 // Option inherited from "Interactive layer" abstract class
\r
7707 interactive: true,
\r
7709 // @option keyboard: Boolean = true
\r
7710 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
\r
7713 // @option title: String = ''
\r
7714 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
\r
7715 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
\r
7718 // @option alt: String = 'Marker'
\r
7719 // Text for the `alt` attribute of the icon image.
\r
7720 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
\r
7723 // @option zIndexOffset: Number = 0
\r
7724 // 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).
\r
7727 // @option opacity: Number = 1.0
\r
7728 // The opacity of the marker.
\r
7731 // @option riseOnHover: Boolean = false
\r
7732 // If `true`, the marker will get on top of others when you hover the mouse over it.
\r
7733 riseOnHover: false,
\r
7735 // @option riseOffset: Number = 250
\r
7736 // The z-index offset used for the `riseOnHover` feature.
\r
7739 // @option pane: String = 'markerPane'
\r
7740 // `Map pane` where the markers icon will be added.
\r
7741 pane: 'markerPane',
\r
7743 // @option shadowPane: String = 'shadowPane'
\r
7744 // `Map pane` where the markers shadow will be added.
\r
7745 shadowPane: 'shadowPane',
\r
7747 // @option bubblingMouseEvents: Boolean = false
\r
7748 // When `true`, a mouse event on this marker will trigger the same event on the map
\r
7749 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
\r
7750 bubblingMouseEvents: false,
\r
7752 // @option autoPanOnFocus: Boolean = true
\r
7753 // When `true`, the map will pan whenever the marker is focused (via
\r
7754 // e.g. pressing `tab` on the keyboard) to ensure the marker is
\r
7755 // visible within the map's bounds
\r
7756 autoPanOnFocus: true,
\r
7758 // @section Draggable marker options
\r
7759 // @option draggable: Boolean = false
\r
7760 // Whether the marker is draggable with mouse/touch or not.
\r
7763 // @option autoPan: Boolean = false
\r
7764 // Whether to pan the map when dragging this marker near its edge or not.
\r
7767 // @option autoPanPadding: Point = Point(50, 50)
\r
7768 // Distance (in pixels to the left/right and to the top/bottom) of the
\r
7769 // map edge to start panning the map.
\r
7770 autoPanPadding: [50, 50],
\r
7772 // @option autoPanSpeed: Number = 10
\r
7773 // Number of pixels the map should pan by.
\r
7779 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
\r
7782 initialize: function (latlng, options) {
\r
7783 setOptions(this, options);
\r
7784 this._latlng = toLatLng(latlng);
\r
7787 onAdd: function (map) {
\r
7788 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
\r
7790 if (this._zoomAnimated) {
\r
7791 map.on('zoomanim', this._animateZoom, this);
\r
7798 onRemove: function (map) {
\r
7799 if (this.dragging && this.dragging.enabled()) {
\r
7800 this.options.draggable = true;
\r
7801 this.dragging.removeHooks();
\r
7803 delete this.dragging;
\r
7805 if (this._zoomAnimated) {
\r
7806 map.off('zoomanim', this._animateZoom, this);
\r
7809 this._removeIcon();
\r
7810 this._removeShadow();
\r
7813 getEvents: function () {
\r
7815 zoom: this.update,
\r
7816 viewreset: this.update
\r
7820 // @method getLatLng: LatLng
\r
7821 // Returns the current geographical position of the marker.
\r
7822 getLatLng: function () {
\r
7823 return this._latlng;
\r
7826 // @method setLatLng(latlng: LatLng): this
\r
7827 // Changes the marker position to the given point.
\r
7828 setLatLng: function (latlng) {
\r
7829 var oldLatLng = this._latlng;
\r
7830 this._latlng = toLatLng(latlng);
\r
7833 // @event move: Event
\r
7834 // 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`.
\r
7835 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
\r
7838 // @method setZIndexOffset(offset: Number): this
\r
7839 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
\r
7840 setZIndexOffset: function (offset) {
\r
7841 this.options.zIndexOffset = offset;
\r
7842 return this.update();
\r
7845 // @method getIcon: Icon
\r
7846 // Returns the current icon used by the marker
\r
7847 getIcon: function () {
\r
7848 return this.options.icon;
\r
7851 // @method setIcon(icon: Icon): this
\r
7852 // Changes the marker icon.
\r
7853 setIcon: function (icon) {
\r
7855 this.options.icon = icon;
\r
7862 if (this._popup) {
\r
7863 this.bindPopup(this._popup, this._popup.options);
\r
7869 getElement: function () {
\r
7870 return this._icon;
\r
7873 update: function () {
\r
7875 if (this._icon && this._map) {
\r
7876 var pos = this._map.latLngToLayerPoint(this._latlng).round();
\r
7877 this._setPos(pos);
\r
7883 _initIcon: function () {
\r
7884 var options = this.options,
\r
7885 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
\r
7887 var icon = options.icon.createIcon(this._icon),
\r
7890 // if we're not reusing the icon, remove the old one and init new one
\r
7891 if (icon !== this._icon) {
\r
7893 this._removeIcon();
\r
7897 if (options.title) {
\r
7898 icon.title = options.title;
\r
7901 if (icon.tagName === 'IMG') {
\r
7902 icon.alt = options.alt || '';
\r
7906 addClass(icon, classToAdd);
\r
7908 if (options.keyboard) {
\r
7909 icon.tabIndex = '0';
\r
7910 icon.setAttribute('role', 'button');
\r
7913 this._icon = icon;
\r
7915 if (options.riseOnHover) {
\r
7917 mouseover: this._bringToFront,
\r
7918 mouseout: this._resetZIndex
\r
7922 if (this.options.autoPanOnFocus) {
\r
7923 on(icon, 'focus', this._panOnFocus, this);
\r
7926 var newShadow = options.icon.createShadow(this._shadow),
\r
7927 addShadow = false;
\r
7929 if (newShadow !== this._shadow) {
\r
7930 this._removeShadow();
\r
7935 addClass(newShadow, classToAdd);
\r
7936 newShadow.alt = '';
\r
7938 this._shadow = newShadow;
\r
7941 if (options.opacity < 1) {
\r
7942 this._updateOpacity();
\r
7947 this.getPane().appendChild(this._icon);
\r
7949 this._initInteraction();
\r
7950 if (newShadow && addShadow) {
\r
7951 this.getPane(options.shadowPane).appendChild(this._shadow);
\r
7955 _removeIcon: function () {
\r
7956 if (this.options.riseOnHover) {
\r
7958 mouseover: this._bringToFront,
\r
7959 mouseout: this._resetZIndex
\r
7963 if (this.options.autoPanOnFocus) {
\r
7964 off(this._icon, 'focus', this._panOnFocus, this);
\r
7967 remove(this._icon);
\r
7968 this.removeInteractiveTarget(this._icon);
\r
7970 this._icon = null;
\r
7973 _removeShadow: function () {
\r
7974 if (this._shadow) {
\r
7975 remove(this._shadow);
\r
7977 this._shadow = null;
\r
7980 _setPos: function (pos) {
\r
7983 setPosition(this._icon, pos);
\r
7986 if (this._shadow) {
\r
7987 setPosition(this._shadow, pos);
\r
7990 this._zIndex = pos.y + this.options.zIndexOffset;
\r
7992 this._resetZIndex();
\r
7995 _updateZIndex: function (offset) {
\r
7997 this._icon.style.zIndex = this._zIndex + offset;
\r
8001 _animateZoom: function (opt) {
\r
8002 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
\r
8004 this._setPos(pos);
\r
8007 _initInteraction: function () {
\r
8009 if (!this.options.interactive) { return; }
\r
8011 addClass(this._icon, 'leaflet-interactive');
\r
8013 this.addInteractiveTarget(this._icon);
\r
8016 var draggable = this.options.draggable;
\r
8017 if (this.dragging) {
\r
8018 draggable = this.dragging.enabled();
\r
8019 this.dragging.disable();
\r
8022 this.dragging = new MarkerDrag(this);
\r
8025 this.dragging.enable();
\r
8030 // @method setOpacity(opacity: Number): this
\r
8031 // Changes the opacity of the marker.
\r
8032 setOpacity: function (opacity) {
\r
8033 this.options.opacity = opacity;
\r
8035 this._updateOpacity();
\r
8041 _updateOpacity: function () {
\r
8042 var opacity = this.options.opacity;
\r
8045 setOpacity(this._icon, opacity);
\r
8048 if (this._shadow) {
\r
8049 setOpacity(this._shadow, opacity);
\r
8053 _bringToFront: function () {
\r
8054 this._updateZIndex(this.options.riseOffset);
\r
8057 _resetZIndex: function () {
\r
8058 this._updateZIndex(0);
\r
8061 _panOnFocus: function () {
\r
8062 var map = this._map;
\r
8063 if (!map) { return; }
\r
8065 var iconOpts = this.options.icon.options;
\r
8066 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
\r
8067 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);
\r
8069 map.panInside(this._latlng, {
\r
8070 paddingTopLeft: anchor,
\r
8071 paddingBottomRight: size.subtract(anchor)
\r
8075 _getPopupAnchor: function () {
\r
8076 return this.options.icon.options.popupAnchor;
\r
8079 _getTooltipAnchor: function () {
\r
8080 return this.options.icon.options.tooltipAnchor;
\r
8085 // factory L.marker(latlng: LatLng, options? : Marker options)
\r
8087 // @factory L.marker(latlng: LatLng, options? : Marker options)
\r
8088 // Instantiates a Marker object given a geographical point and optionally an options object.
\r
8089 function marker(latlng, options) {
\r
8090 return new Marker(latlng, options);
\r
8096 * @inherits Interactive layer
8098 * An abstract class that contains options and constants shared between vector
8099 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8102 var Path = Layer.extend({
8105 // @aka Path options
8107 // @option stroke: Boolean = true
8108 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8111 // @option color: String = '#3388ff'
8115 // @option weight: Number = 3
8116 // Stroke width in pixels
8119 // @option opacity: Number = 1.0
8123 // @option lineCap: String= 'round'
8124 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8127 // @option lineJoin: String = 'round'
8128 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8131 // @option dashArray: String = null
8132 // 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).
8135 // @option dashOffset: String = null
8136 // 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).
8139 // @option fill: Boolean = depends
8140 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8143 // @option fillColor: String = *
8144 // Fill color. Defaults to the value of the [`color`](#path-color) option
8147 // @option fillOpacity: Number = 0.2
8151 // @option fillRule: String = 'evenodd'
8152 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8153 fillRule: 'evenodd',
8157 // Option inherited from "Interactive layer" abstract class
8160 // @option bubblingMouseEvents: Boolean = true
8161 // When `true`, a mouse event on this path will trigger the same event on the map
8162 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
8163 bubblingMouseEvents: true
8166 beforeAdd: function (map) {
8167 // Renderer is set here because we need to call renderer.getEvents
8168 // before this.getEvents.
8169 this._renderer = map.getRenderer(this);
8172 onAdd: function () {
8173 this._renderer._initPath(this);
8175 this._renderer._addPath(this);
8178 onRemove: function () {
8179 this._renderer._removePath(this);
8182 // @method redraw(): this
8183 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8184 redraw: function () {
8186 this._renderer._updatePath(this);
8191 // @method setStyle(style: Path options): this
8192 // Changes the appearance of a Path based on the options in the `Path options` object.
8193 setStyle: function (style) {
8194 setOptions(this, style);
8195 if (this._renderer) {
8196 this._renderer._updateStyle(this);
8197 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
8198 this._updateBounds();
8204 // @method bringToFront(): this
8205 // Brings the layer to the top of all path layers.
8206 bringToFront: function () {
8207 if (this._renderer) {
8208 this._renderer._bringToFront(this);
8213 // @method bringToBack(): this
8214 // Brings the layer to the bottom of all path layers.
8215 bringToBack: function () {
8216 if (this._renderer) {
8217 this._renderer._bringToBack(this);
8222 getElement: function () {
8226 _reset: function () {
8227 // defined in child classes
8232 _clickTolerance: function () {
8233 // used when doing hit detection for Canvas layers
8234 return (this.options.stroke ? this.options.weight / 2 : 0) +
8235 (this._renderer.options.tolerance || 0);
8240 * @class CircleMarker
8241 * @aka L.CircleMarker
8244 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8247 var CircleMarker = Path.extend({
8250 // @aka CircleMarker options
8254 // @option radius: Number = 10
8255 // Radius of the circle marker, in pixels
8259 initialize: function (latlng, options) {
8260 setOptions(this, options);
8261 this._latlng = toLatLng(latlng);
8262 this._radius = this.options.radius;
8265 // @method setLatLng(latLng: LatLng): this
8266 // Sets the position of a circle marker to a new location.
8267 setLatLng: function (latlng) {
8268 var oldLatLng = this._latlng;
8269 this._latlng = toLatLng(latlng);
8272 // @event move: Event
8273 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
8274 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
8277 // @method getLatLng(): LatLng
8278 // Returns the current geographical position of the circle marker
8279 getLatLng: function () {
8280 return this._latlng;
8283 // @method setRadius(radius: Number): this
8284 // Sets the radius of a circle marker. Units are in pixels.
8285 setRadius: function (radius) {
8286 this.options.radius = this._radius = radius;
8287 return this.redraw();
8290 // @method getRadius(): Number
8291 // Returns the current radius of the circle
8292 getRadius: function () {
8293 return this._radius;
8296 setStyle : function (options) {
8297 var radius = options && options.radius || this._radius;
8298 Path.prototype.setStyle.call(this, options);
8299 this.setRadius(radius);
8303 _project: function () {
8304 this._point = this._map.latLngToLayerPoint(this._latlng);
8305 this._updateBounds();
8308 _updateBounds: function () {
8309 var r = this._radius,
8310 r2 = this._radiusY || r,
8311 w = this._clickTolerance(),
8312 p = [r + w, r2 + w];
8313 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8316 _update: function () {
8322 _updatePath: function () {
8323 this._renderer._updateCircle(this);
8326 _empty: function () {
8327 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8330 // Needed by the `Canvas` renderer for interactivity
8331 _containsPoint: function (p) {
8332 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8337 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8338 // Instantiates a circle marker object given a geographical point, and an optional options object.
8339 function circleMarker(latlng, options) {
8340 return new CircleMarker(latlng, options);
8346 * @inherits CircleMarker
8348 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8350 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8355 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8359 var Circle = CircleMarker.extend({
8361 initialize: function (latlng, options, legacyOptions) {
8362 if (typeof options === 'number') {
8363 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8364 options = extend({}, legacyOptions, {radius: options});
8366 setOptions(this, options);
8367 this._latlng = toLatLng(latlng);
8369 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8372 // @aka Circle options
8373 // @option radius: Number; Radius of the circle, in meters.
8374 this._mRadius = this.options.radius;
8377 // @method setRadius(radius: Number): this
8378 // Sets the radius of a circle. Units are in meters.
8379 setRadius: function (radius) {
8380 this._mRadius = radius;
8381 return this.redraw();
8384 // @method getRadius(): Number
8385 // Returns the current radius of a circle. Units are in meters.
8386 getRadius: function () {
8387 return this._mRadius;
8390 // @method getBounds(): LatLngBounds
8391 // Returns the `LatLngBounds` of the path.
8392 getBounds: function () {
8393 var half = [this._radius, this._radiusY || this._radius];
8395 return new LatLngBounds(
8396 this._map.layerPointToLatLng(this._point.subtract(half)),
8397 this._map.layerPointToLatLng(this._point.add(half)));
8400 setStyle: Path.prototype.setStyle,
8402 _project: function () {
8404 var lng = this._latlng.lng,
8405 lat = this._latlng.lat,
8407 crs = map.options.crs;
8409 if (crs.distance === Earth.distance) {
8410 var d = Math.PI / 180,
8411 latR = (this._mRadius / Earth.R) / d,
8412 top = map.project([lat + latR, lng]),
8413 bottom = map.project([lat - latR, lng]),
8414 p = top.add(bottom).divideBy(2),
8415 lat2 = map.unproject(p).lat,
8416 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8417 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8419 if (isNaN(lngR) || lngR === 0) {
8420 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8423 this._point = p.subtract(map.getPixelOrigin());
8424 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8425 this._radiusY = p.y - top.y;
8428 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8430 this._point = map.latLngToLayerPoint(this._latlng);
8431 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8434 this._updateBounds();
8438 // @factory L.circle(latlng: LatLng, options?: Circle options)
8439 // Instantiates a circle object given a geographical point, and an options object
8440 // which contains the circle radius.
8442 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8443 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8444 // Do not use in new applications or plugins.
8445 function circle(latlng, options, legacyOptions) {
8446 return new Circle(latlng, options, legacyOptions);
8454 * A class for drawing polyline overlays on a map. Extends `Path`.
8459 * // create a red polyline from an array of LatLng points
8466 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8468 * // zoom the map to the polyline
8469 * map.fitBounds(polyline.getBounds());
8472 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8475 * // create a red polyline from an array of arrays of LatLng points
8477 * [[45.51, -122.68],
8488 var Polyline = Path.extend({
8491 // @aka Polyline options
8493 // @option smoothFactor: Number = 1.0
8494 // How much to simplify the polyline on each zoom level. More means
8495 // better performance and smoother look, and less means more accurate representation.
8498 // @option noClip: Boolean = false
8499 // Disable polyline clipping.
8503 initialize: function (latlngs, options) {
8504 setOptions(this, options);
8505 this._setLatLngs(latlngs);
8508 // @method getLatLngs(): LatLng[]
8509 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8510 getLatLngs: function () {
8511 return this._latlngs;
8514 // @method setLatLngs(latlngs: LatLng[]): this
8515 // Replaces all the points in the polyline with the given array of geographical points.
8516 setLatLngs: function (latlngs) {
8517 this._setLatLngs(latlngs);
8518 return this.redraw();
8521 // @method isEmpty(): Boolean
8522 // Returns `true` if the Polyline has no LatLngs.
8523 isEmpty: function () {
8524 return !this._latlngs.length;
8527 // @method closestLayerPoint(p: Point): Point
8528 // Returns the point closest to `p` on the Polyline.
8529 closestLayerPoint: function (p) {
8530 var minDistance = Infinity,
8532 closest = _sqClosestPointOnSegment,
8535 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8536 var points = this._parts[j];
8538 for (var i = 1, len = points.length; i < len; i++) {
8542 var sqDist = closest(p, p1, p2, true);
8544 if (sqDist < minDistance) {
8545 minDistance = sqDist;
8546 minPoint = closest(p, p1, p2);
8551 minPoint.distance = Math.sqrt(minDistance);
8556 // @method getCenter(): LatLng
8557 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8558 getCenter: function () {
8559 // throws error when not yet added to map as this center calculation requires projected coordinates
8561 throw new Error('Must add layer to map before using getCenter()');
8563 return polylineCenter(this._defaultShape(), this._map.options.crs);
8566 // @method getBounds(): LatLngBounds
8567 // Returns the `LatLngBounds` of the path.
8568 getBounds: function () {
8569 return this._bounds;
8572 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8573 // Adds a given point to the polyline. By default, adds to the first ring of
8574 // the polyline in case of a multi-polyline, but can be overridden by passing
8575 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8576 addLatLng: function (latlng, latlngs) {
8577 latlngs = latlngs || this._defaultShape();
8578 latlng = toLatLng(latlng);
8579 latlngs.push(latlng);
8580 this._bounds.extend(latlng);
8581 return this.redraw();
8584 _setLatLngs: function (latlngs) {
8585 this._bounds = new LatLngBounds();
8586 this._latlngs = this._convertLatLngs(latlngs);
8589 _defaultShape: function () {
8590 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8593 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8594 _convertLatLngs: function (latlngs) {
8596 flat = isFlat(latlngs);
8598 for (var i = 0, len = latlngs.length; i < len; i++) {
8600 result[i] = toLatLng(latlngs[i]);
8601 this._bounds.extend(result[i]);
8603 result[i] = this._convertLatLngs(latlngs[i]);
8610 _project: function () {
8611 var pxBounds = new Bounds();
8613 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8615 if (this._bounds.isValid() && pxBounds.isValid()) {
8616 this._rawPxBounds = pxBounds;
8617 this._updateBounds();
8621 _updateBounds: function () {
8622 var w = this._clickTolerance(),
8623 p = new Point(w, w);
8625 if (!this._rawPxBounds) {
8629 this._pxBounds = new Bounds([
8630 this._rawPxBounds.min.subtract(p),
8631 this._rawPxBounds.max.add(p)
8635 // recursively turns latlngs into a set of rings with projected coordinates
8636 _projectLatlngs: function (latlngs, result, projectedBounds) {
8637 var flat = latlngs[0] instanceof LatLng,
8638 len = latlngs.length,
8643 for (i = 0; i < len; i++) {
8644 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8645 projectedBounds.extend(ring[i]);
8649 for (i = 0; i < len; i++) {
8650 this._projectLatlngs(latlngs[i], result, projectedBounds);
8655 // clip polyline by renderer bounds so that we have less to render for performance
8656 _clipPoints: function () {
8657 var bounds = this._renderer._bounds;
8660 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8664 if (this.options.noClip) {
8665 this._parts = this._rings;
8669 var parts = this._parts,
8670 i, j, k, len, len2, segment, points;
8672 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8673 points = this._rings[i];
8675 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8676 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8678 if (!segment) { continue; }
8680 parts[k] = parts[k] || [];
8681 parts[k].push(segment[0]);
8683 // if segment goes out of screen, or it's the last one, it's the end of the line part
8684 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8685 parts[k].push(segment[1]);
8692 // simplify each clipped part of the polyline for performance
8693 _simplifyPoints: function () {
8694 var parts = this._parts,
8695 tolerance = this.options.smoothFactor;
8697 for (var i = 0, len = parts.length; i < len; i++) {
8698 parts[i] = simplify(parts[i], tolerance);
8702 _update: function () {
8703 if (!this._map) { return; }
8706 this._simplifyPoints();
8710 _updatePath: function () {
8711 this._renderer._updatePoly(this);
8714 // Needed by the `Canvas` renderer for interactivity
8715 _containsPoint: function (p, closed) {
8716 var i, j, k, len, len2, part,
8717 w = this._clickTolerance();
8719 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8721 // hit detection for polylines
8722 for (i = 0, len = this._parts.length; i < len; i++) {
8723 part = this._parts[i];
8725 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8726 if (!closed && (j === 0)) { continue; }
8728 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8737 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8738 // Instantiates a polyline object given an array of geographical points and
8739 // optionally an options object. You can create a `Polyline` object with
8740 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8741 // of geographic points.
8742 function polyline(latlngs, options) {
8743 return new Polyline(latlngs, options);
8746 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8747 Polyline._flat = _flat;
8752 * @inherits Polyline
8754 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8756 * 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.
8762 * // create a red polygon from an array of LatLng points
8763 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8765 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8767 * // zoom the map to the polygon
8768 * map.fitBounds(polygon.getBounds());
8771 * 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:
8775 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8776 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8780 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8784 * [ // first polygon
8785 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8786 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8788 * [ // second polygon
8789 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8795 var Polygon = Polyline.extend({
8801 isEmpty: function () {
8802 return !this._latlngs.length || !this._latlngs[0].length;
8805 // @method getCenter(): LatLng
8806 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
8807 getCenter: function () {
8808 // throws error when not yet added to map as this center calculation requires projected coordinates
8810 throw new Error('Must add layer to map before using getCenter()');
8812 return polygonCenter(this._defaultShape(), this._map.options.crs);
8815 _convertLatLngs: function (latlngs) {
8816 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8817 len = result.length;
8819 // remove last point if it equals first one
8820 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8826 _setLatLngs: function (latlngs) {
8827 Polyline.prototype._setLatLngs.call(this, latlngs);
8828 if (isFlat(this._latlngs)) {
8829 this._latlngs = [this._latlngs];
8833 _defaultShape: function () {
8834 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8837 _clipPoints: function () {
8838 // polygons need a different clipping algorithm so we redefine that
8840 var bounds = this._renderer._bounds,
8841 w = this.options.weight,
8842 p = new Point(w, w);
8844 // increase clip padding by stroke width to avoid stroke on clip edges
8845 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8848 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8852 if (this.options.noClip) {
8853 this._parts = this._rings;
8857 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8858 clipped = clipPolygon(this._rings[i], bounds, true);
8859 if (clipped.length) {
8860 this._parts.push(clipped);
8865 _updatePath: function () {
8866 this._renderer._updatePoly(this, true);
8869 // Needed by the `Canvas` renderer for interactivity
8870 _containsPoint: function (p) {
8872 part, p1, p2, i, j, k, len, len2;
8874 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8876 // ray casting algorithm for detecting if point is in polygon
8877 for (i = 0, len = this._parts.length; i < len; i++) {
8878 part = this._parts[i];
8880 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8884 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)) {
8890 // also check if it's on polygon stroke
8891 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8897 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8898 function polygon(latlngs, options) {
8899 return new Polygon(latlngs, options);
8905 * @inherits FeatureGroup
\r
8907 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
\r
8908 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
\r
8913 * L.geoJSON(data, {
\r
8914 * style: function (feature) {
\r
8915 * return {color: feature.properties.color};
\r
8917 * }).bindPopup(function (layer) {
\r
8918 * return layer.feature.properties.description;
\r
8923 var GeoJSON = FeatureGroup.extend({
\r
8926 * @aka GeoJSON options
\r
8928 * @option pointToLayer: Function = *
\r
8929 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
\r
8930 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
\r
8931 * The default is to spawn a default `Marker`:
\r
8933 * function(geoJsonPoint, latlng) {
\r
8934 * return L.marker(latlng);
\r
8938 * @option style: Function = *
\r
8939 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
\r
8940 * called internally when data is added.
\r
8941 * The default value is to not override any defaults:
\r
8943 * function (geoJsonFeature) {
\r
8948 * @option onEachFeature: Function = *
\r
8949 * A `Function` that will be called once for each created `Feature`, after it has
\r
8950 * been created and styled. Useful for attaching events and popups to features.
\r
8951 * The default is to do nothing with the newly created layers:
\r
8953 * function (feature, layer) {}
\r
8956 * @option filter: Function = *
\r
8957 * A `Function` that will be used to decide whether to include a feature or not.
\r
8958 * The default is to include all features:
\r
8960 * function (geoJsonFeature) {
\r
8964 * Note: dynamically changing the `filter` option will have effect only on newly
\r
8965 * added data. It will _not_ re-evaluate already included features.
\r
8967 * @option coordsToLatLng: Function = *
\r
8968 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
\r
8969 * The default is the `coordsToLatLng` static method.
\r
8971 * @option markersInheritOptions: Boolean = false
\r
8972 * Whether default Markers for "Point" type Features inherit from group options.
\r
8975 initialize: function (geojson, options) {
\r
8976 setOptions(this, options);
\r
8978 this._layers = {};
\r
8981 this.addData(geojson);
\r
8985 // @method addData( <GeoJSON> data ): this
\r
8986 // Adds a GeoJSON object to the layer.
\r
8987 addData: function (geojson) {
\r
8988 var features = isArray(geojson) ? geojson : geojson.features,
\r
8992 for (i = 0, len = features.length; i < len; i++) {
\r
8993 // only add this if geometry or geometries are set and not null
\r
8994 feature = features[i];
\r
8995 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
\r
8996 this.addData(feature);
\r
9002 var options = this.options;
\r
9004 if (options.filter && !options.filter(geojson)) { return this; }
\r
9006 var layer = geometryToLayer(geojson, options);
\r
9010 layer.feature = asFeature(geojson);
\r
9012 layer.defaultOptions = layer.options;
\r
9013 this.resetStyle(layer);
\r
9015 if (options.onEachFeature) {
\r
9016 options.onEachFeature(geojson, layer);
\r
9019 return this.addLayer(layer);
\r
9022 // @method resetStyle( <Path> layer? ): this
\r
9023 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
\r
9024 // If `layer` is omitted, the style of all features in the current layer is reset.
\r
9025 resetStyle: function (layer) {
\r
9026 if (layer === undefined) {
\r
9027 return this.eachLayer(this.resetStyle, this);
\r
9029 // reset any custom styles
\r
9030 layer.options = extend({}, layer.defaultOptions);
\r
9031 this._setLayerStyle(layer, this.options.style);
\r
9035 // @method setStyle( <Function> style ): this
\r
9036 // Changes styles of GeoJSON vector layers with the given style function.
\r
9037 setStyle: function (style) {
\r
9038 return this.eachLayer(function (layer) {
\r
9039 this._setLayerStyle(layer, style);
\r
9043 _setLayerStyle: function (layer, style) {
\r
9044 if (layer.setStyle) {
\r
9045 if (typeof style === 'function') {
\r
9046 style = style(layer.feature);
\r
9048 layer.setStyle(style);
\r
9054 // There are several static functions which can be called without instantiating L.GeoJSON:
\r
9056 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
\r
9057 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
\r
9058 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
\r
9059 // functions if provided as options.
\r
9060 function geometryToLayer(geojson, options) {
\r
9062 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
\r
9063 coords = geometry ? geometry.coordinates : null,
\r
9065 pointToLayer = options && options.pointToLayer,
\r
9066 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
\r
9067 latlng, latlngs, i, len;
\r
9069 if (!coords && !geometry) {
\r
9073 switch (geometry.type) {
\r
9075 latlng = _coordsToLatLng(coords);
\r
9076 return _pointToLayer(pointToLayer, geojson, latlng, options);
\r
9078 case 'MultiPoint':
\r
9079 for (i = 0, len = coords.length; i < len; i++) {
\r
9080 latlng = _coordsToLatLng(coords[i]);
\r
9081 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
\r
9083 return new FeatureGroup(layers);
\r
9085 case 'LineString':
\r
9086 case 'MultiLineString':
\r
9087 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
\r
9088 return new Polyline(latlngs, options);
\r
9091 case 'MultiPolygon':
\r
9092 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
\r
9093 return new Polygon(latlngs, options);
\r
9095 case 'GeometryCollection':
\r
9096 for (i = 0, len = geometry.geometries.length; i < len; i++) {
\r
9097 var geoLayer = geometryToLayer({
\r
9098 geometry: geometry.geometries[i],
\r
9100 properties: geojson.properties
\r
9104 layers.push(geoLayer);
\r
9107 return new FeatureGroup(layers);
\r
9109 case 'FeatureCollection':
\r
9110 for (i = 0, len = geometry.features.length; i < len; i++) {
\r
9111 var featureLayer = geometryToLayer(geometry.features[i], options);
\r
9113 if (featureLayer) {
\r
9114 layers.push(featureLayer);
\r
9117 return new FeatureGroup(layers);
\r
9120 throw new Error('Invalid GeoJSON object.');
\r
9124 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
\r
9125 return pointToLayerFn ?
\r
9126 pointToLayerFn(geojson, latlng) :
\r
9127 new Marker(latlng, options && options.markersInheritOptions && options);
\r
9130 // @function coordsToLatLng(coords: Array): LatLng
\r
9131 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
\r
9132 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
\r
9133 function coordsToLatLng(coords) {
\r
9134 return new LatLng(coords[1], coords[0], coords[2]);
\r
9137 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
\r
9138 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
\r
9139 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
\r
9140 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
\r
9141 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
\r
9144 for (var i = 0, len = coords.length, latlng; i < len; i++) {
\r
9145 latlng = levelsDeep ?
\r
9146 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
\r
9147 (_coordsToLatLng || coordsToLatLng)(coords[i]);
\r
9149 latlngs.push(latlng);
\r
9155 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
\r
9156 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
\r
9157 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
\r
9158 function latLngToCoords(latlng, precision) {
\r
9159 latlng = toLatLng(latlng);
\r
9160 return latlng.alt !== undefined ?
\r
9161 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
\r
9162 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
\r
9165 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
\r
9166 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
\r
9167 // `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.
\r
9168 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
\r
9169 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
\r
9172 for (var i = 0, len = latlngs.length; i < len; i++) {
\r
9173 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
\r
9174 coords.push(levelsDeep ?
\r
9175 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
\r
9176 latLngToCoords(latlngs[i], precision));
\r
9179 if (!levelsDeep && closed && coords.length > 0) {
\r
9180 coords.push(coords[0].slice());
\r
9186 function getFeature(layer, newGeometry) {
\r
9187 return layer.feature ?
\r
9188 extend({}, layer.feature, {geometry: newGeometry}) :
\r
9189 asFeature(newGeometry);
\r
9192 // @function asFeature(geojson: Object): Object
\r
9193 // Normalize GeoJSON geometries/features into GeoJSON features.
\r
9194 function asFeature(geojson) {
\r
9195 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
\r
9206 var PointToGeoJSON = {
\r
9207 toGeoJSON: function (precision) {
\r
9208 return getFeature(this, {
\r
9210 coordinates: latLngToCoords(this.getLatLng(), precision)
\r
9215 // @namespace Marker
\r
9216 // @section Other methods
\r
9217 // @method toGeoJSON(precision?: Number|false): Object
\r
9218 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9219 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
\r
9220 Marker.include(PointToGeoJSON);
\r
9222 // @namespace CircleMarker
\r
9223 // @method toGeoJSON(precision?: Number|false): Object
\r
9224 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9225 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
\r
9226 Circle.include(PointToGeoJSON);
\r
9227 CircleMarker.include(PointToGeoJSON);
\r
9230 // @namespace Polyline
\r
9231 // @method toGeoJSON(precision?: Number|false): Object
\r
9232 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9233 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
\r
9234 Polyline.include({
\r
9235 toGeoJSON: function (precision) {
\r
9236 var multi = !isFlat(this._latlngs);
\r
9238 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
\r
9240 return getFeature(this, {
\r
9241 type: (multi ? 'Multi' : '') + 'LineString',
\r
9242 coordinates: coords
\r
9247 // @namespace Polygon
\r
9248 // @method toGeoJSON(precision?: Number|false): Object
\r
9249 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9250 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
\r
9252 toGeoJSON: function (precision) {
\r
9253 var holes = !isFlat(this._latlngs),
\r
9254 multi = holes && !isFlat(this._latlngs[0]);
\r
9256 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
\r
9259 coords = [coords];
\r
9262 return getFeature(this, {
\r
9263 type: (multi ? 'Multi' : '') + 'Polygon',
\r
9264 coordinates: coords
\r
9270 // @namespace LayerGroup
\r
9271 LayerGroup.include({
\r
9272 toMultiPoint: function (precision) {
\r
9275 this.eachLayer(function (layer) {
\r
9276 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
\r
9279 return getFeature(this, {
\r
9280 type: 'MultiPoint',
\r
9281 coordinates: coords
\r
9285 // @method toGeoJSON(precision?: Number|false): Object
\r
9286 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9287 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
\r
9288 toGeoJSON: function (precision) {
\r
9290 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
\r
9292 if (type === 'MultiPoint') {
\r
9293 return this.toMultiPoint(precision);
\r
9296 var isGeometryCollection = type === 'GeometryCollection',
\r
9299 this.eachLayer(function (layer) {
\r
9300 if (layer.toGeoJSON) {
\r
9301 var json = layer.toGeoJSON(precision);
\r
9302 if (isGeometryCollection) {
\r
9303 jsons.push(json.geometry);
\r
9305 var feature = asFeature(json);
\r
9306 // Squash nested feature collections
\r
9307 if (feature.type === 'FeatureCollection') {
\r
9308 jsons.push.apply(jsons, feature.features);
\r
9310 jsons.push(feature);
\r
9316 if (isGeometryCollection) {
\r
9317 return getFeature(this, {
\r
9318 geometries: jsons,
\r
9319 type: 'GeometryCollection'
\r
9324 type: 'FeatureCollection',
\r
9330 // @namespace GeoJSON
\r
9331 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
\r
9332 // Creates a GeoJSON layer. Optionally accepts an object in
\r
9333 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
\r
9334 // (you can alternatively add it later with `addData` method) and an `options` object.
\r
9335 function geoJSON(geojson, options) {
\r
9336 return new GeoJSON(geojson, options);
\r
9339 // Backward compatibility.
\r
9340 var geoJson = geoJSON;
9343 * @class ImageOverlay
\r
9344 * @aka L.ImageOverlay
\r
9345 * @inherits Interactive layer
\r
9347 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
\r
9352 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
\r
9353 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
\r
9354 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
\r
9358 var ImageOverlay = Layer.extend({
\r
9361 // @aka ImageOverlay options
\r
9363 // @option opacity: Number = 1.0
\r
9364 // The opacity of the image overlay.
\r
9367 // @option alt: String = ''
\r
9368 // Text for the `alt` attribute of the image (useful for accessibility).
\r
9371 // @option interactive: Boolean = false
\r
9372 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
\r
9373 interactive: false,
\r
9375 // @option crossOrigin: Boolean|String = false
\r
9376 // Whether the crossOrigin attribute will be added to the image.
\r
9377 // 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.
\r
9378 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
\r
9379 crossOrigin: false,
\r
9381 // @option errorOverlayUrl: String = ''
\r
9382 // URL to the overlay image to show in place of the overlay that failed to load.
\r
9383 errorOverlayUrl: '',
\r
9385 // @option zIndex: Number = 1
\r
9386 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
\r
9389 // @option className: String = ''
\r
9390 // A custom class name to assign to the image. Empty by default.
\r
9394 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
\r
9396 this._bounds = toLatLngBounds(bounds);
\r
9398 setOptions(this, options);
\r
9401 onAdd: function () {
\r
9402 if (!this._image) {
\r
9403 this._initImage();
\r
9405 if (this.options.opacity < 1) {
\r
9406 this._updateOpacity();
\r
9410 if (this.options.interactive) {
\r
9411 addClass(this._image, 'leaflet-interactive');
\r
9412 this.addInteractiveTarget(this._image);
\r
9415 this.getPane().appendChild(this._image);
\r
9419 onRemove: function () {
\r
9420 remove(this._image);
\r
9421 if (this.options.interactive) {
\r
9422 this.removeInteractiveTarget(this._image);
\r
9426 // @method setOpacity(opacity: Number): this
\r
9427 // Sets the opacity of the overlay.
\r
9428 setOpacity: function (opacity) {
\r
9429 this.options.opacity = opacity;
\r
9431 if (this._image) {
\r
9432 this._updateOpacity();
\r
9437 setStyle: function (styleOpts) {
\r
9438 if (styleOpts.opacity) {
\r
9439 this.setOpacity(styleOpts.opacity);
\r
9444 // @method bringToFront(): this
\r
9445 // Brings the layer to the top of all overlays.
\r
9446 bringToFront: function () {
\r
9448 toFront(this._image);
\r
9453 // @method bringToBack(): this
\r
9454 // Brings the layer to the bottom of all overlays.
\r
9455 bringToBack: function () {
\r
9457 toBack(this._image);
\r
9462 // @method setUrl(url: String): this
\r
9463 // Changes the URL of the image.
\r
9464 setUrl: function (url) {
\r
9467 if (this._image) {
\r
9468 this._image.src = url;
\r
9473 // @method setBounds(bounds: LatLngBounds): this
\r
9474 // Update the bounds that this ImageOverlay covers
\r
9475 setBounds: function (bounds) {
\r
9476 this._bounds = toLatLngBounds(bounds);
\r
9484 getEvents: function () {
\r
9486 zoom: this._reset,
\r
9487 viewreset: this._reset
\r
9490 if (this._zoomAnimated) {
\r
9491 events.zoomanim = this._animateZoom;
\r
9497 // @method setZIndex(value: Number): this
\r
9498 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
\r
9499 setZIndex: function (value) {
\r
9500 this.options.zIndex = value;
\r
9501 this._updateZIndex();
\r
9505 // @method getBounds(): LatLngBounds
\r
9506 // Get the bounds that this ImageOverlay covers
\r
9507 getBounds: function () {
\r
9508 return this._bounds;
\r
9511 // @method getElement(): HTMLElement
\r
9512 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
\r
9513 // used by this overlay.
\r
9514 getElement: function () {
\r
9515 return this._image;
\r
9518 _initImage: function () {
\r
9519 var wasElementSupplied = this._url.tagName === 'IMG';
\r
9520 var img = this._image = wasElementSupplied ? this._url : create$1('img');
\r
9522 addClass(img, 'leaflet-image-layer');
\r
9523 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
\r
9524 if (this.options.className) { addClass(img, this.options.className); }
\r
9526 img.onselectstart = falseFn;
\r
9527 img.onmousemove = falseFn;
\r
9529 // @event load: Event
\r
9530 // Fired when the ImageOverlay layer has loaded its image
\r
9531 img.onload = bind(this.fire, this, 'load');
\r
9532 img.onerror = bind(this._overlayOnError, this, 'error');
\r
9534 if (this.options.crossOrigin || this.options.crossOrigin === '') {
\r
9535 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
\r
9538 if (this.options.zIndex) {
\r
9539 this._updateZIndex();
\r
9542 if (wasElementSupplied) {
\r
9543 this._url = img.src;
\r
9547 img.src = this._url;
\r
9548 img.alt = this.options.alt;
\r
9551 _animateZoom: function (e) {
\r
9552 var scale = this._map.getZoomScale(e.zoom),
\r
9553 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
\r
9555 setTransform(this._image, offset, scale);
\r
9558 _reset: function () {
\r
9559 var image = this._image,
\r
9560 bounds = new Bounds(
\r
9561 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
\r
9562 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
\r
9563 size = bounds.getSize();
\r
9565 setPosition(image, bounds.min);
\r
9567 image.style.width = size.x + 'px';
\r
9568 image.style.height = size.y + 'px';
\r
9571 _updateOpacity: function () {
\r
9572 setOpacity(this._image, this.options.opacity);
\r
9575 _updateZIndex: function () {
\r
9576 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
\r
9577 this._image.style.zIndex = this.options.zIndex;
\r
9581 _overlayOnError: function () {
\r
9582 // @event error: Event
\r
9583 // Fired when the ImageOverlay layer fails to load its image
\r
9584 this.fire('error');
\r
9586 var errorUrl = this.options.errorOverlayUrl;
\r
9587 if (errorUrl && this._url !== errorUrl) {
\r
9588 this._url = errorUrl;
\r
9589 this._image.src = errorUrl;
\r
9593 // @method getCenter(): LatLng
\r
9594 // Returns the center of the ImageOverlay.
\r
9595 getCenter: function () {
\r
9596 return this._bounds.getCenter();
\r
9600 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
\r
9601 // Instantiates an image overlay object given the URL of the image and the
\r
9602 // geographical bounds it is tied to.
\r
9603 var imageOverlay = function (url, bounds, options) {
\r
9604 return new ImageOverlay(url, bounds, options);
\r
9608 * @class VideoOverlay
\r
9609 * @aka L.VideoOverlay
\r
9610 * @inherits ImageOverlay
\r
9612 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
\r
9614 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
\r
9620 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
\r
9621 * videoBounds = [[ 32, -130], [ 13, -100]];
\r
9622 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
\r
9626 var VideoOverlay = ImageOverlay.extend({
\r
9629 // @aka VideoOverlay options
\r
9631 // @option autoplay: Boolean = true
\r
9632 // Whether the video starts playing automatically when loaded.
\r
9633 // On some browsers autoplay will only work with `muted: true`
\r
9636 // @option loop: Boolean = true
\r
9637 // Whether the video will loop back to the beginning when played.
\r
9640 // @option keepAspectRatio: Boolean = true
\r
9641 // Whether the video will save aspect ratio after the projection.
\r
9642 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
\r
9643 keepAspectRatio: true,
\r
9645 // @option muted: Boolean = false
\r
9646 // Whether the video starts on mute when loaded.
\r
9649 // @option playsInline: Boolean = true
\r
9650 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
\r
9654 _initImage: function () {
\r
9655 var wasElementSupplied = this._url.tagName === 'VIDEO';
\r
9656 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
\r
9658 addClass(vid, 'leaflet-image-layer');
\r
9659 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
\r
9660 if (this.options.className) { addClass(vid, this.options.className); }
\r
9662 vid.onselectstart = falseFn;
\r
9663 vid.onmousemove = falseFn;
\r
9665 // @event load: Event
\r
9666 // Fired when the video has finished loading the first frame
\r
9667 vid.onloadeddata = bind(this.fire, this, 'load');
\r
9669 if (wasElementSupplied) {
\r
9670 var sourceElements = vid.getElementsByTagName('source');
\r
9672 for (var j = 0; j < sourceElements.length; j++) {
\r
9673 sources.push(sourceElements[j].src);
\r
9676 this._url = (sourceElements.length > 0) ? sources : [vid.src];
\r
9680 if (!isArray(this._url)) { this._url = [this._url]; }
\r
9682 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
\r
9683 vid.style['objectFit'] = 'fill';
\r
9685 vid.autoplay = !!this.options.autoplay;
\r
9686 vid.loop = !!this.options.loop;
\r
9687 vid.muted = !!this.options.muted;
\r
9688 vid.playsInline = !!this.options.playsInline;
\r
9689 for (var i = 0; i < this._url.length; i++) {
\r
9690 var source = create$1('source');
\r
9691 source.src = this._url[i];
\r
9692 vid.appendChild(source);
\r
9696 // @method getElement(): HTMLVideoElement
\r
9697 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
\r
9698 // used by this overlay.
\r
9702 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
\r
9703 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
\r
9704 // geographical bounds it is tied to.
\r
9706 function videoOverlay(video, bounds, options) {
\r
9707 return new VideoOverlay(video, bounds, options);
\r
9713 * @inherits ImageOverlay
9715 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9717 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9722 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9723 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9724 * svgElement.setAttribute('viewBox', "0 0 200 200");
9725 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9726 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9727 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9731 var SVGOverlay = ImageOverlay.extend({
9732 _initImage: function () {
9733 var el = this._image = this._url;
9735 addClass(el, 'leaflet-image-layer');
9736 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9737 if (this.options.className) { addClass(el, this.options.className); }
9739 el.onselectstart = falseFn;
9740 el.onmousemove = falseFn;
9743 // @method getElement(): SVGElement
9744 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9745 // used by this overlay.
9749 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9750 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9751 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9753 function svgOverlay(el, bounds, options) {
9754 return new SVGOverlay(el, bounds, options);
9758 * @class DivOverlay
\r
9759 * @inherits Interactive layer
\r
9760 * @aka L.DivOverlay
\r
9761 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
\r
9764 // @namespace DivOverlay
\r
9765 var DivOverlay = Layer.extend({
\r
9768 // @aka DivOverlay options
\r
9770 // @option interactive: Boolean = false
\r
9771 // If true, the popup/tooltip will listen to the mouse events.
\r
9772 interactive: false,
\r
9774 // @option offset: Point = Point(0, 0)
\r
9775 // The offset of the overlay position.
\r
9778 // @option className: String = ''
\r
9779 // A custom CSS class name to assign to the overlay.
\r
9782 // @option pane: String = undefined
\r
9783 // `Map pane` where the overlay will be added.
\r
9786 // @option content: String|HTMLElement|Function = ''
\r
9787 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
\r
9788 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
\r
9792 initialize: function (options, source) {
\r
9793 if (options && (options instanceof LatLng || isArray(options))) {
\r
9794 this._latlng = toLatLng(options);
\r
9795 setOptions(this, source);
\r
9797 setOptions(this, options);
\r
9798 this._source = source;
\r
9800 if (this.options.content) {
\r
9801 this._content = this.options.content;
\r
9805 // @method openOn(map: Map): this
\r
9806 // Adds the overlay to the map.
\r
9807 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
\r
9808 openOn: function (map) {
\r
9809 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
\r
9810 if (!map.hasLayer(this)) {
\r
9811 map.addLayer(this);
\r
9816 // @method close(): this
\r
9817 // Closes the overlay.
\r
9818 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
\r
9819 // and `layer.closePopup()`/`.closeTooltip()`.
\r
9820 close: function () {
\r
9822 this._map.removeLayer(this);
\r
9827 // @method toggle(layer?: Layer): this
\r
9828 // Opens or closes the overlay bound to layer depending on its current state.
\r
9829 // Argument may be omitted only for overlay bound to layer.
\r
9830 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
\r
9831 toggle: function (layer) {
\r
9835 if (arguments.length) {
\r
9836 this._source = layer;
\r
9838 layer = this._source;
\r
9840 this._prepareOpen();
\r
9842 // open the overlay on the map
\r
9843 this.openOn(layer._map);
\r
9848 onAdd: function (map) {
\r
9849 this._zoomAnimated = map._zoomAnimated;
\r
9851 if (!this._container) {
\r
9852 this._initLayout();
\r
9855 if (map._fadeAnimated) {
\r
9856 setOpacity(this._container, 0);
\r
9859 clearTimeout(this._removeTimeout);
\r
9860 this.getPane().appendChild(this._container);
\r
9863 if (map._fadeAnimated) {
\r
9864 setOpacity(this._container, 1);
\r
9867 this.bringToFront();
\r
9869 if (this.options.interactive) {
\r
9870 addClass(this._container, 'leaflet-interactive');
\r
9871 this.addInteractiveTarget(this._container);
\r
9875 onRemove: function (map) {
\r
9876 if (map._fadeAnimated) {
\r
9877 setOpacity(this._container, 0);
\r
9878 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
\r
9880 remove(this._container);
\r
9883 if (this.options.interactive) {
\r
9884 removeClass(this._container, 'leaflet-interactive');
\r
9885 this.removeInteractiveTarget(this._container);
\r
9889 // @namespace DivOverlay
\r
9890 // @method getLatLng: LatLng
\r
9891 // Returns the geographical point of the overlay.
\r
9892 getLatLng: function () {
\r
9893 return this._latlng;
\r
9896 // @method setLatLng(latlng: LatLng): this
\r
9897 // Sets the geographical point where the overlay will open.
\r
9898 setLatLng: function (latlng) {
\r
9899 this._latlng = toLatLng(latlng);
\r
9901 this._updatePosition();
\r
9902 this._adjustPan();
\r
9907 // @method getContent: String|HTMLElement
\r
9908 // Returns the content of the overlay.
\r
9909 getContent: function () {
\r
9910 return this._content;
\r
9913 // @method setContent(htmlContent: String|HTMLElement|Function): this
\r
9914 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
\r
9915 // The function should return a `String` or `HTMLElement` to be used in the overlay.
\r
9916 setContent: function (content) {
\r
9917 this._content = content;
\r
9922 // @method getElement: String|HTMLElement
\r
9923 // Returns the HTML container of the overlay.
\r
9924 getElement: function () {
\r
9925 return this._container;
\r
9928 // @method update: null
\r
9929 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
\r
9930 update: function () {
\r
9931 if (!this._map) { return; }
\r
9933 this._container.style.visibility = 'hidden';
\r
9935 this._updateContent();
\r
9936 this._updateLayout();
\r
9937 this._updatePosition();
\r
9939 this._container.style.visibility = '';
\r
9941 this._adjustPan();
\r
9944 getEvents: function () {
\r
9946 zoom: this._updatePosition,
\r
9947 viewreset: this._updatePosition
\r
9950 if (this._zoomAnimated) {
\r
9951 events.zoomanim = this._animateZoom;
\r
9956 // @method isOpen: Boolean
\r
9957 // Returns `true` when the overlay is visible on the map.
\r
9958 isOpen: function () {
\r
9959 return !!this._map && this._map.hasLayer(this);
\r
9962 // @method bringToFront: this
\r
9963 // Brings this overlay in front of other overlays (in the same map pane).
\r
9964 bringToFront: function () {
\r
9966 toFront(this._container);
\r
9971 // @method bringToBack: this
\r
9972 // Brings this overlay to the back of other overlays (in the same map pane).
\r
9973 bringToBack: function () {
\r
9975 toBack(this._container);
\r
9980 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
\r
9981 _prepareOpen: function (latlng) {
\r
9982 var source = this._source;
\r
9983 if (!source._map) { return false; }
\r
9985 if (source instanceof FeatureGroup) {
\r
9987 var layers = this._source._layers;
\r
9988 for (var id in layers) {
\r
9989 if (layers[id]._map) {
\r
9990 source = layers[id];
\r
9994 if (!source) { return false; } // Unable to get source layer.
\r
9996 // set overlay source to this layer
\r
9997 this._source = source;
\r
10001 if (source.getCenter) {
\r
10002 latlng = source.getCenter();
\r
10003 } else if (source.getLatLng) {
\r
10004 latlng = source.getLatLng();
\r
10005 } else if (source.getBounds) {
\r
10006 latlng = source.getBounds().getCenter();
\r
10008 throw new Error('Unable to get source layer LatLng.');
\r
10011 this.setLatLng(latlng);
\r
10014 // update the overlay (content, layout, etc...)
\r
10021 _updateContent: function () {
\r
10022 if (!this._content) { return; }
\r
10024 var node = this._contentNode;
\r
10025 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
\r
10027 if (typeof content === 'string') {
\r
10028 node.innerHTML = content;
\r
10030 while (node.hasChildNodes()) {
\r
10031 node.removeChild(node.firstChild);
\r
10033 node.appendChild(content);
\r
10036 // @namespace DivOverlay
\r
10037 // @section DivOverlay events
\r
10038 // @event contentupdate: Event
\r
10039 // Fired when the content of the overlay is updated
\r
10040 this.fire('contentupdate');
\r
10043 _updatePosition: function () {
\r
10044 if (!this._map) { return; }
\r
10046 var pos = this._map.latLngToLayerPoint(this._latlng),
\r
10047 offset = toPoint(this.options.offset),
\r
10048 anchor = this._getAnchor();
\r
10050 if (this._zoomAnimated) {
\r
10051 setPosition(this._container, pos.add(anchor));
\r
10053 offset = offset.add(pos).add(anchor);
\r
10056 var bottom = this._containerBottom = -offset.y,
\r
10057 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
\r
10059 // bottom position the overlay in case the height of the overlay changes (images loading etc)
\r
10060 this._container.style.bottom = bottom + 'px';
\r
10061 this._container.style.left = left + 'px';
\r
10064 _getAnchor: function () {
\r
10071 _initOverlay: function (OverlayClass, content, latlng, options) {
\r
10072 var overlay = content;
\r
10073 if (!(overlay instanceof OverlayClass)) {
\r
10074 overlay = new OverlayClass(options).setContent(content);
\r
10077 overlay.setLatLng(latlng);
\r
10085 _initOverlay: function (OverlayClass, old, content, options) {
\r
10086 var overlay = content;
\r
10087 if (overlay instanceof OverlayClass) {
\r
10088 setOptions(overlay, options);
\r
10089 overlay._source = this;
\r
10091 overlay = (old && !options) ? old : new OverlayClass(options, this);
\r
10092 overlay.setContent(content);
\r
10100 * @inherits DivOverlay
\r
10102 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
\r
10103 * open popups while making sure that only one popup is open at one time
\r
10104 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
\r
10108 * If you want to just bind a popup to marker click and then open it, it's really easy:
\r
10111 * marker.bindPopup(popupContent).openPopup();
\r
10113 * Path overlays like polylines also have a `bindPopup` method.
\r
10115 * A popup can be also standalone:
\r
10118 * var popup = L.popup()
\r
10119 * .setLatLng(latlng)
\r
10120 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
\r
10125 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
\r
10131 // @namespace Popup
\r
10132 var Popup = DivOverlay.extend({
\r
10135 // @aka Popup options
\r
10137 // @option pane: String = 'popupPane'
\r
10138 // `Map pane` where the popup will be added.
\r
10139 pane: 'popupPane',
\r
10141 // @option offset: Point = Point(0, 7)
\r
10142 // The offset of the popup position.
\r
10145 // @option maxWidth: Number = 300
\r
10146 // Max width of the popup, in pixels.
\r
10149 // @option minWidth: Number = 50
\r
10150 // Min width of the popup, in pixels.
\r
10153 // @option maxHeight: Number = null
\r
10154 // If set, creates a scrollable container of the given height
\r
10155 // inside a popup if its content exceeds it.
\r
10156 // The scrollable container can be styled using the
\r
10157 // `leaflet-popup-scrolled` CSS class selector.
\r
10160 // @option autoPan: Boolean = true
\r
10161 // Set it to `false` if you don't want the map to do panning animation
\r
10162 // to fit the opened popup.
\r
10165 // @option autoPanPaddingTopLeft: Point = null
\r
10166 // The margin between the popup and the top left corner of the map
\r
10167 // view after autopanning was performed.
\r
10168 autoPanPaddingTopLeft: null,
\r
10170 // @option autoPanPaddingBottomRight: Point = null
\r
10171 // The margin between the popup and the bottom right corner of the map
\r
10172 // view after autopanning was performed.
\r
10173 autoPanPaddingBottomRight: null,
\r
10175 // @option autoPanPadding: Point = Point(5, 5)
\r
10176 // Equivalent of setting both top left and bottom right autopan padding to the same value.
\r
10177 autoPanPadding: [5, 5],
\r
10179 // @option keepInView: Boolean = false
\r
10180 // Set it to `true` if you want to prevent users from panning the popup
\r
10181 // off of the screen while it is open.
\r
10182 keepInView: false,
\r
10184 // @option closeButton: Boolean = true
\r
10185 // Controls the presence of a close button in the popup.
\r
10186 closeButton: true,
\r
10188 // @option autoClose: Boolean = true
\r
10189 // Set it to `false` if you want to override the default behavior of
\r
10190 // the popup closing when another popup is opened.
\r
10193 // @option closeOnEscapeKey: Boolean = true
\r
10194 // Set it to `false` if you want to override the default behavior of
\r
10195 // the ESC key for closing of the popup.
\r
10196 closeOnEscapeKey: true,
\r
10198 // @option closeOnClick: Boolean = *
\r
10199 // Set it if you want to override the default behavior of the popup closing when user clicks
\r
10200 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
\r
10202 // @option className: String = ''
\r
10203 // A custom CSS class name to assign to the popup.
\r
10207 // @namespace Popup
\r
10208 // @method openOn(map: Map): this
\r
10209 // Alternative to `map.openPopup(popup)`.
\r
10210 // Adds the popup to the map and closes the previous one.
\r
10211 openOn: function (map) {
\r
10212 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
\r
10214 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
\r
10215 map.removeLayer(map._popup);
\r
10217 map._popup = this;
\r
10219 return DivOverlay.prototype.openOn.call(this, map);
\r
10222 onAdd: function (map) {
\r
10223 DivOverlay.prototype.onAdd.call(this, map);
\r
10225 // @namespace Map
\r
10226 // @section Popup events
\r
10227 // @event popupopen: PopupEvent
\r
10228 // Fired when a popup is opened in the map
\r
10229 map.fire('popupopen', {popup: this});
\r
10231 if (this._source) {
\r
10232 // @namespace Layer
\r
10233 // @section Popup events
\r
10234 // @event popupopen: PopupEvent
\r
10235 // Fired when a popup bound to this layer is opened
\r
10236 this._source.fire('popupopen', {popup: this}, true);
\r
10237 // For non-path layers, we toggle the popup when clicking
\r
10238 // again the layer, so prevent the map to reopen it.
\r
10239 if (!(this._source instanceof Path)) {
\r
10240 this._source.on('preclick', stopPropagation);
\r
10245 onRemove: function (map) {
\r
10246 DivOverlay.prototype.onRemove.call(this, map);
\r
10248 // @namespace Map
\r
10249 // @section Popup events
\r
10250 // @event popupclose: PopupEvent
\r
10251 // Fired when a popup in the map is closed
\r
10252 map.fire('popupclose', {popup: this});
\r
10254 if (this._source) {
\r
10255 // @namespace Layer
\r
10256 // @section Popup events
\r
10257 // @event popupclose: PopupEvent
\r
10258 // Fired when a popup bound to this layer is closed
\r
10259 this._source.fire('popupclose', {popup: this}, true);
\r
10260 if (!(this._source instanceof Path)) {
\r
10261 this._source.off('preclick', stopPropagation);
\r
10266 getEvents: function () {
\r
10267 var events = DivOverlay.prototype.getEvents.call(this);
\r
10269 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
\r
10270 events.preclick = this.close;
\r
10273 if (this.options.keepInView) {
\r
10274 events.moveend = this._adjustPan;
\r
10280 _initLayout: function () {
\r
10281 var prefix = 'leaflet-popup',
\r
10282 container = this._container = create$1('div',
\r
10283 prefix + ' ' + (this.options.className || '') +
\r
10284 ' leaflet-zoom-animated');
\r
10286 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
\r
10287 this._contentNode = create$1('div', prefix + '-content', wrapper);
\r
10289 disableClickPropagation(container);
\r
10290 disableScrollPropagation(this._contentNode);
\r
10291 on(container, 'contextmenu', stopPropagation);
\r
10293 this._tipContainer = create$1('div', prefix + '-tip-container', container);
\r
10294 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
\r
10296 if (this.options.closeButton) {
\r
10297 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
\r
10298 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
\r
10299 closeButton.setAttribute('aria-label', 'Close popup');
\r
10300 closeButton.href = '#close';
\r
10301 closeButton.innerHTML = '<span aria-hidden="true">×</span>';
\r
10303 on(closeButton, 'click', function (ev) {
\r
10304 preventDefault(ev);
\r
10310 _updateLayout: function () {
\r
10311 var container = this._contentNode,
\r
10312 style = container.style;
\r
10314 style.width = '';
\r
10315 style.whiteSpace = 'nowrap';
\r
10317 var width = container.offsetWidth;
\r
10318 width = Math.min(width, this.options.maxWidth);
\r
10319 width = Math.max(width, this.options.minWidth);
\r
10321 style.width = (width + 1) + 'px';
\r
10322 style.whiteSpace = '';
\r
10324 style.height = '';
\r
10326 var height = container.offsetHeight,
\r
10327 maxHeight = this.options.maxHeight,
\r
10328 scrolledClass = 'leaflet-popup-scrolled';
\r
10330 if (maxHeight && height > maxHeight) {
\r
10331 style.height = maxHeight + 'px';
\r
10332 addClass(container, scrolledClass);
\r
10334 removeClass(container, scrolledClass);
\r
10337 this._containerWidth = this._container.offsetWidth;
\r
10340 _animateZoom: function (e) {
\r
10341 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
\r
10342 anchor = this._getAnchor();
\r
10343 setPosition(this._container, pos.add(anchor));
\r
10346 _adjustPan: function () {
\r
10347 if (!this.options.autoPan) { return; }
\r
10348 if (this._map._panAnim) { this._map._panAnim.stop(); }
\r
10350 // We can endlessly recurse if keepInView is set and the view resets.
\r
10351 // Let's guard against that by exiting early if we're responding to our own autopan.
\r
10352 if (this._autopanning) {
\r
10353 this._autopanning = false;
\r
10357 var map = this._map,
\r
10358 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
\r
10359 containerHeight = this._container.offsetHeight + marginBottom,
\r
10360 containerWidth = this._containerWidth,
\r
10361 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
\r
10363 layerPos._add(getPosition(this._container));
\r
10365 var containerPos = map.layerPointToContainerPoint(layerPos),
\r
10366 padding = toPoint(this.options.autoPanPadding),
\r
10367 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
\r
10368 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
\r
10369 size = map.getSize(),
\r
10373 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
\r
10374 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
\r
10376 if (containerPos.x - dx - paddingTL.x < 0) { // left
\r
10377 dx = containerPos.x - paddingTL.x;
\r
10379 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
\r
10380 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
\r
10382 if (containerPos.y - dy - paddingTL.y < 0) { // top
\r
10383 dy = containerPos.y - paddingTL.y;
\r
10386 // @namespace Map
\r
10387 // @section Popup events
\r
10388 // @event autopanstart: Event
\r
10389 // Fired when the map starts autopanning when opening a popup.
\r
10391 // Track that we're autopanning, as this function will be re-ran on moveend
\r
10392 if (this.options.keepInView) {
\r
10393 this._autopanning = true;
\r
10397 .fire('autopanstart')
\r
10398 .panBy([dx, dy]);
\r
10402 _getAnchor: function () {
\r
10403 // Where should we anchor the popup on the source layer?
\r
10404 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
\r
10409 // @namespace Popup
\r
10410 // @factory L.popup(options?: Popup options, source?: Layer)
\r
10411 // 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.
\r
10413 // @factory L.popup(latlng: LatLng, options?: Popup options)
\r
10414 // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
\r
10415 var popup = function (options, source) {
\r
10416 return new Popup(options, source);
\r
10420 /* @namespace Map
\r
10421 * @section Interaction Options
\r
10422 * @option closePopupOnClick: Boolean = true
\r
10423 * Set it to `false` if you don't want popups to close when user clicks the map.
\r
10425 Map.mergeOptions({
\r
10426 closePopupOnClick: true
\r
10430 // @namespace Map
\r
10431 // @section Methods for Layers and Controls
\r
10433 // @method openPopup(popup: Popup): this
\r
10434 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
\r
10436 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
\r
10437 // Creates a popup with the specified content and options and opens it in the given point on a map.
\r
10438 openPopup: function (popup, latlng, options) {
\r
10439 this._initOverlay(Popup, popup, latlng, options)
\r
10445 // @method closePopup(popup?: Popup): this
\r
10446 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
\r
10447 closePopup: function (popup) {
\r
10448 popup = arguments.length ? popup : this._popup;
\r
10457 * @namespace Layer
\r
10458 * @section Popup methods example
\r
10460 * All layers share a set of methods convenient for binding popups to it.
\r
10463 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
\r
10464 * layer.openPopup();
\r
10465 * layer.closePopup();
\r
10468 * 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.
\r
10471 // @section Popup methods
\r
10474 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
\r
10475 // Binds a popup to the layer with the passed `content` and sets up the
\r
10476 // necessary event listeners. If a `Function` is passed it will receive
\r
10477 // the layer as the first argument and should return a `String` or `HTMLElement`.
\r
10478 bindPopup: function (content, options) {
\r
10479 this._popup = this._initOverlay(Popup, this._popup, content, options);
\r
10480 if (!this._popupHandlersAdded) {
\r
10482 click: this._openPopup,
\r
10483 keypress: this._onKeyPress,
\r
10484 remove: this.closePopup,
\r
10485 move: this._movePopup
\r
10487 this._popupHandlersAdded = true;
\r
10493 // @method unbindPopup(): this
\r
10494 // Removes the popup previously bound with `bindPopup`.
\r
10495 unbindPopup: function () {
\r
10496 if (this._popup) {
\r
10498 click: this._openPopup,
\r
10499 keypress: this._onKeyPress,
\r
10500 remove: this.closePopup,
\r
10501 move: this._movePopup
\r
10503 this._popupHandlersAdded = false;
\r
10504 this._popup = null;
\r
10509 // @method openPopup(latlng?: LatLng): this
\r
10510 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
\r
10511 openPopup: function (latlng) {
\r
10512 if (this._popup) {
\r
10513 if (!(this instanceof FeatureGroup)) {
\r
10514 this._popup._source = this;
\r
10516 if (this._popup._prepareOpen(latlng || this._latlng)) {
\r
10517 // open the popup on the map
\r
10518 this._popup.openOn(this._map);
\r
10524 // @method closePopup(): this
\r
10525 // Closes the popup bound to this layer if it is open.
\r
10526 closePopup: function () {
\r
10527 if (this._popup) {
\r
10528 this._popup.close();
\r
10533 // @method togglePopup(): this
\r
10534 // Opens or closes the popup bound to this layer depending on its current state.
\r
10535 togglePopup: function () {
\r
10536 if (this._popup) {
\r
10537 this._popup.toggle(this);
\r
10542 // @method isPopupOpen(): boolean
\r
10543 // Returns `true` if the popup bound to this layer is currently open.
\r
10544 isPopupOpen: function () {
\r
10545 return (this._popup ? this._popup.isOpen() : false);
\r
10548 // @method setPopupContent(content: String|HTMLElement|Popup): this
\r
10549 // Sets the content of the popup bound to this layer.
\r
10550 setPopupContent: function (content) {
\r
10551 if (this._popup) {
\r
10552 this._popup.setContent(content);
\r
10557 // @method getPopup(): Popup
\r
10558 // Returns the popup bound to this layer.
\r
10559 getPopup: function () {
\r
10560 return this._popup;
\r
10563 _openPopup: function (e) {
\r
10564 if (!this._popup || !this._map) {
\r
10567 // prevent map click
\r
10570 var target = e.layer || e.target;
\r
10571 if (this._popup._source === target && !(target instanceof Path)) {
\r
10572 // treat it like a marker and figure out
\r
10573 // if we should toggle it open/closed
\r
10574 if (this._map.hasLayer(this._popup)) {
\r
10575 this.closePopup();
\r
10577 this.openPopup(e.latlng);
\r
10581 this._popup._source = target;
\r
10582 this.openPopup(e.latlng);
\r
10585 _movePopup: function (e) {
\r
10586 this._popup.setLatLng(e.latlng);
\r
10589 _onKeyPress: function (e) {
\r
10590 if (e.originalEvent.keyCode === 13) {
\r
10591 this._openPopup(e);
\r
10598 * @inherits DivOverlay
10600 * Used to display small texts on top of map layers.
10603 * If you want to just bind a tooltip to marker:
10606 * marker.bindTooltip("my tooltip text").openTooltip();
10608 * Path overlays like polylines also have a `bindTooltip` method.
10610 * A tooltip can be also standalone:
10613 * var tooltip = L.tooltip()
10614 * .setLatLng(latlng)
10615 * .setContent('Hello world!<br />This is a nice tooltip.')
10620 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
10625 * Note about tooltip offset. Leaflet takes two options in consideration
10626 * for computing tooltip offsetting:
10627 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10628 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10629 * move it to the bottom. Negatives will move to the left and top.
10630 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10631 * should adapt this value if you use a custom icon.
10635 // @namespace Tooltip
10636 var Tooltip = DivOverlay.extend({
10639 // @aka Tooltip options
10641 // @option pane: String = 'tooltipPane'
10642 // `Map pane` where the tooltip will be added.
10643 pane: 'tooltipPane',
10645 // @option offset: Point = Point(0, 0)
10646 // Optional offset of the tooltip position.
10649 // @option direction: String = 'auto'
10650 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10651 // `top`, `bottom`, `center`, `auto`.
10652 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10653 // position on the map.
10656 // @option permanent: Boolean = false
10657 // Whether to open the tooltip permanently or only on mouseover.
10660 // @option sticky: Boolean = false
10661 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10664 // @option opacity: Number = 0.9
10665 // Tooltip container opacity.
10669 onAdd: function (map) {
10670 DivOverlay.prototype.onAdd.call(this, map);
10671 this.setOpacity(this.options.opacity);
10674 // @section Tooltip events
10675 // @event tooltipopen: TooltipEvent
10676 // Fired when a tooltip is opened in the map.
10677 map.fire('tooltipopen', {tooltip: this});
10679 if (this._source) {
10680 this.addEventParent(this._source);
10682 // @namespace Layer
10683 // @section Tooltip events
10684 // @event tooltipopen: TooltipEvent
10685 // Fired when a tooltip bound to this layer is opened.
10686 this._source.fire('tooltipopen', {tooltip: this}, true);
10690 onRemove: function (map) {
10691 DivOverlay.prototype.onRemove.call(this, map);
10694 // @section Tooltip events
10695 // @event tooltipclose: TooltipEvent
10696 // Fired when a tooltip in the map is closed.
10697 map.fire('tooltipclose', {tooltip: this});
10699 if (this._source) {
10700 this.removeEventParent(this._source);
10702 // @namespace Layer
10703 // @section Tooltip events
10704 // @event tooltipclose: TooltipEvent
10705 // Fired when a tooltip bound to this layer is closed.
10706 this._source.fire('tooltipclose', {tooltip: this}, true);
10710 getEvents: function () {
10711 var events = DivOverlay.prototype.getEvents.call(this);
10713 if (!this.options.permanent) {
10714 events.preclick = this.close;
10720 _initLayout: function () {
10721 var prefix = 'leaflet-tooltip',
10722 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10724 this._contentNode = this._container = create$1('div', className);
10726 this._container.setAttribute('role', 'tooltip');
10727 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
10730 _updateLayout: function () {},
10732 _adjustPan: function () {},
10734 _setPosition: function (pos) {
10737 container = this._container,
10738 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10739 tooltipPoint = map.layerPointToContainerPoint(pos),
10740 direction = this.options.direction,
10741 tooltipWidth = container.offsetWidth,
10742 tooltipHeight = container.offsetHeight,
10743 offset = toPoint(this.options.offset),
10744 anchor = this._getAnchor();
10746 if (direction === 'top') {
10747 subX = tooltipWidth / 2;
10748 subY = tooltipHeight;
10749 } else if (direction === 'bottom') {
10750 subX = tooltipWidth / 2;
10752 } else if (direction === 'center') {
10753 subX = tooltipWidth / 2;
10754 subY = tooltipHeight / 2;
10755 } else if (direction === 'right') {
10757 subY = tooltipHeight / 2;
10758 } else if (direction === 'left') {
10759 subX = tooltipWidth;
10760 subY = tooltipHeight / 2;
10761 } else if (tooltipPoint.x < centerPoint.x) {
10762 direction = 'right';
10764 subY = tooltipHeight / 2;
10766 direction = 'left';
10767 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10768 subY = tooltipHeight / 2;
10771 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10773 removeClass(container, 'leaflet-tooltip-right');
10774 removeClass(container, 'leaflet-tooltip-left');
10775 removeClass(container, 'leaflet-tooltip-top');
10776 removeClass(container, 'leaflet-tooltip-bottom');
10777 addClass(container, 'leaflet-tooltip-' + direction);
10778 setPosition(container, pos);
10781 _updatePosition: function () {
10782 var pos = this._map.latLngToLayerPoint(this._latlng);
10783 this._setPosition(pos);
10786 setOpacity: function (opacity) {
10787 this.options.opacity = opacity;
10789 if (this._container) {
10790 setOpacity(this._container, opacity);
10794 _animateZoom: function (e) {
10795 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10796 this._setPosition(pos);
10799 _getAnchor: function () {
10800 // Where should we anchor the tooltip on the source layer?
10801 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10806 // @namespace Tooltip
10807 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10808 // 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.
10810 // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
10811 // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
10812 var tooltip = function (options, source) {
10813 return new Tooltip(options, source);
10817 // @section Methods for Layers and Controls
10820 // @method openTooltip(tooltip: Tooltip): this
10821 // Opens the specified tooltip.
10823 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10824 // Creates a tooltip with the specified content and options and open it.
10825 openTooltip: function (tooltip, latlng, options) {
10826 this._initOverlay(Tooltip, tooltip, latlng, options)
10832 // @method closeTooltip(tooltip: Tooltip): this
10833 // Closes the tooltip given as parameter.
10834 closeTooltip: function (tooltip) {
10843 * @section Tooltip methods example
10845 * All layers share a set of methods convenient for binding tooltips to it.
10848 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10849 * layer.openTooltip();
10850 * layer.closeTooltip();
10854 // @section Tooltip methods
10857 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10858 // Binds a tooltip to the layer with the passed `content` and sets up the
10859 // necessary event listeners. If a `Function` is passed it will receive
10860 // the layer as the first argument and should return a `String` or `HTMLElement`.
10861 bindTooltip: function (content, options) {
10863 if (this._tooltip && this.isTooltipOpen()) {
10864 this.unbindTooltip();
10867 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10868 this._initTooltipInteractions();
10870 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10871 this.openTooltip();
10877 // @method unbindTooltip(): this
10878 // Removes the tooltip previously bound with `bindTooltip`.
10879 unbindTooltip: function () {
10880 if (this._tooltip) {
10881 this._initTooltipInteractions(true);
10882 this.closeTooltip();
10883 this._tooltip = null;
10888 _initTooltipInteractions: function (remove) {
10889 if (!remove && this._tooltipHandlersAdded) { return; }
10890 var onOff = remove ? 'off' : 'on',
10892 remove: this.closeTooltip,
10893 move: this._moveTooltip
10895 if (!this._tooltip.options.permanent) {
10896 events.mouseover = this._openTooltip;
10897 events.mouseout = this.closeTooltip;
10898 events.click = this._openTooltip;
10900 this._addFocusListeners();
10902 events.add = this._addFocusListeners;
10905 events.add = this._openTooltip;
10907 if (this._tooltip.options.sticky) {
10908 events.mousemove = this._moveTooltip;
10910 this[onOff](events);
10911 this._tooltipHandlersAdded = !remove;
10914 // @method openTooltip(latlng?: LatLng): this
10915 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10916 openTooltip: function (latlng) {
10917 if (this._tooltip) {
10918 if (!(this instanceof FeatureGroup)) {
10919 this._tooltip._source = this;
10921 if (this._tooltip._prepareOpen(latlng)) {
10922 // open the tooltip on the map
10923 this._tooltip.openOn(this._map);
10925 if (this.getElement) {
10926 this._setAriaDescribedByOnLayer(this);
10927 } else if (this.eachLayer) {
10928 this.eachLayer(this._setAriaDescribedByOnLayer, this);
10935 // @method closeTooltip(): this
10936 // Closes the tooltip bound to this layer if it is open.
10937 closeTooltip: function () {
10938 if (this._tooltip) {
10939 return this._tooltip.close();
10943 // @method toggleTooltip(): this
10944 // Opens or closes the tooltip bound to this layer depending on its current state.
10945 toggleTooltip: function () {
10946 if (this._tooltip) {
10947 this._tooltip.toggle(this);
10952 // @method isTooltipOpen(): boolean
10953 // Returns `true` if the tooltip bound to this layer is currently open.
10954 isTooltipOpen: function () {
10955 return this._tooltip.isOpen();
10958 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10959 // Sets the content of the tooltip bound to this layer.
10960 setTooltipContent: function (content) {
10961 if (this._tooltip) {
10962 this._tooltip.setContent(content);
10967 // @method getTooltip(): Tooltip
10968 // Returns the tooltip bound to this layer.
10969 getTooltip: function () {
10970 return this._tooltip;
10973 _addFocusListeners: function () {
10974 if (this.getElement) {
10975 this._addFocusListenersOnLayer(this);
10976 } else if (this.eachLayer) {
10977 this.eachLayer(this._addFocusListenersOnLayer, this);
10981 _addFocusListenersOnLayer: function (layer) {
10982 var el = typeof layer.getElement === 'function' && layer.getElement();
10984 on(el, 'focus', function () {
10985 this._tooltip._source = layer;
10986 this.openTooltip();
10988 on(el, 'blur', this.closeTooltip, this);
10992 _setAriaDescribedByOnLayer: function (layer) {
10993 var el = typeof layer.getElement === 'function' && layer.getElement();
10995 el.setAttribute('aria-describedby', this._tooltip._container.id);
11000 _openTooltip: function (e) {
11001 if (!this._tooltip || !this._map) {
11005 // If the map is moving, we will show the tooltip after it's done.
11006 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
11007 this._openOnceFlag = true;
11009 this._map.once('moveend', function () {
11010 that._openOnceFlag = false;
11011 that._openTooltip(e);
11016 this._tooltip._source = e.layer || e.target;
11018 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
11021 _moveTooltip: function (e) {
11022 var latlng = e.latlng, containerPoint, layerPoint;
11023 if (this._tooltip.options.sticky && e.originalEvent) {
11024 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
11025 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
11026 latlng = this._map.layerPointToLatLng(layerPoint);
11028 this._tooltip.setLatLng(latlng);
11037 * Represents a lightweight icon for markers that uses a simple `<div>`
11038 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
11042 * var myIcon = L.divIcon({className: 'my-div-icon'});
11043 * // you can set .my-div-icon styles in CSS
11045 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
11048 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
11051 var DivIcon = Icon.extend({
11054 // @aka DivIcon options
11055 iconSize: [12, 12], // also can be set through CSS
11057 // iconAnchor: (Point),
11058 // popupAnchor: (Point),
11060 // @option html: String|HTMLElement = ''
11061 // Custom HTML code to put inside the div element, empty by default. Alternatively,
11062 // an instance of `HTMLElement`.
11065 // @option bgPos: Point = [0, 0]
11066 // Optional relative position of the background, in pixels
11069 className: 'leaflet-div-icon'
11072 createIcon: function (oldIcon) {
11073 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
11074 options = this.options;
11076 if (options.html instanceof Element) {
11078 div.appendChild(options.html);
11080 div.innerHTML = options.html !== false ? options.html : '';
11083 if (options.bgPos) {
11084 var bgPos = toPoint(options.bgPos);
11085 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
11087 this._setIconStyles(div, 'icon');
11092 createShadow: function () {
11097 // @factory L.divIcon(options: DivIcon options)
11098 // Creates a `DivIcon` instance with the given options.
11099 function divIcon(options) {
11100 return new DivIcon(options);
11103 Icon.Default = IconDefault;
11110 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
11111 * 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.
11114 * @section Synchronous usage
11117 * 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.
11120 * var CanvasLayer = L.GridLayer.extend({
11121 * createTile: function(coords){
11122 * // create a <canvas> element for drawing
11123 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11125 * // setup tile width and height according to the options
11126 * var size = this.getTileSize();
11127 * tile.width = size.x;
11128 * tile.height = size.y;
11130 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
11131 * var ctx = tile.getContext('2d');
11133 * // return the tile so it can be rendered on screen
11139 * @section Asynchronous usage
11142 * 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.
11145 * var CanvasLayer = L.GridLayer.extend({
11146 * createTile: function(coords, done){
11149 * // create a <canvas> element for drawing
11150 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11152 * // setup tile width and height according to the options
11153 * var size = this.getTileSize();
11154 * tile.width = size.x;
11155 * tile.height = size.y;
11157 * // draw something asynchronously and pass the tile to the done() callback
11158 * setTimeout(function() {
11159 * done(error, tile);
11171 var GridLayer = Layer.extend({
11174 // @aka GridLayer options
11176 // @option tileSize: Number|Point = 256
11177 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
11180 // @option opacity: Number = 1.0
11181 // Opacity of the tiles. Can be used in the `createTile()` function.
11184 // @option updateWhenIdle: Boolean = (depends)
11185 // Load new tiles only when panning ends.
11186 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
11187 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
11188 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
11189 updateWhenIdle: Browser.mobile,
11191 // @option updateWhenZooming: Boolean = true
11192 // 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.
11193 updateWhenZooming: true,
11195 // @option updateInterval: Number = 200
11196 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
11197 updateInterval: 200,
11199 // @option zIndex: Number = 1
11200 // The explicit zIndex of the tile layer.
11203 // @option bounds: LatLngBounds = undefined
11204 // If set, tiles will only be loaded inside the set `LatLngBounds`.
11207 // @option minZoom: Number = 0
11208 // The minimum zoom level down to which this layer will be displayed (inclusive).
11211 // @option maxZoom: Number = undefined
11212 // The maximum zoom level up to which this layer will be displayed (inclusive).
11213 maxZoom: undefined,
11215 // @option maxNativeZoom: Number = undefined
11216 // Maximum zoom number the tile source has available. If it is specified,
11217 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
11218 // from `maxNativeZoom` level and auto-scaled.
11219 maxNativeZoom: undefined,
11221 // @option minNativeZoom: Number = undefined
11222 // Minimum zoom number the tile source has available. If it is specified,
11223 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
11224 // from `minNativeZoom` level and auto-scaled.
11225 minNativeZoom: undefined,
11227 // @option noWrap: Boolean = false
11228 // Whether the layer is wrapped around the antimeridian. If `true`, the
11229 // GridLayer will only be displayed once at low zoom levels. Has no
11230 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
11231 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
11232 // tiles outside the CRS limits.
11235 // @option pane: String = 'tilePane'
11236 // `Map pane` where the grid layer will be added.
11239 // @option className: String = ''
11240 // A custom class name to assign to the tile layer. Empty by default.
11243 // @option keepBuffer: Number = 2
11244 // When panning the map, keep this many rows and columns of tiles before unloading them.
11248 initialize: function (options) {
11249 setOptions(this, options);
11252 onAdd: function () {
11253 this._initContainer();
11258 this._resetView(); // implicit _update() call
11261 beforeAdd: function (map) {
11262 map._addZoomLimit(this);
11265 onRemove: function (map) {
11266 this._removeAllTiles();
11267 remove(this._container);
11268 map._removeZoomLimit(this);
11269 this._container = null;
11270 this._tileZoom = undefined;
11273 // @method bringToFront: this
11274 // Brings the tile layer to the top of all tile layers.
11275 bringToFront: function () {
11277 toFront(this._container);
11278 this._setAutoZIndex(Math.max);
11283 // @method bringToBack: this
11284 // Brings the tile layer to the bottom of all tile layers.
11285 bringToBack: function () {
11287 toBack(this._container);
11288 this._setAutoZIndex(Math.min);
11293 // @method getContainer: HTMLElement
11294 // Returns the HTML element that contains the tiles for this layer.
11295 getContainer: function () {
11296 return this._container;
11299 // @method setOpacity(opacity: Number): this
11300 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
11301 setOpacity: function (opacity) {
11302 this.options.opacity = opacity;
11303 this._updateOpacity();
11307 // @method setZIndex(zIndex: Number): this
11308 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
11309 setZIndex: function (zIndex) {
11310 this.options.zIndex = zIndex;
11311 this._updateZIndex();
11316 // @method isLoading: Boolean
11317 // Returns `true` if any tile in the grid layer has not finished loading.
11318 isLoading: function () {
11319 return this._loading;
11322 // @method redraw: this
11323 // Causes the layer to clear all the tiles and request them again.
11324 redraw: function () {
11326 this._removeAllTiles();
11327 var tileZoom = this._clampZoom(this._map.getZoom());
11328 if (tileZoom !== this._tileZoom) {
11329 this._tileZoom = tileZoom;
11330 this._updateLevels();
11337 getEvents: function () {
11339 viewprereset: this._invalidateAll,
11340 viewreset: this._resetView,
11341 zoom: this._resetView,
11342 moveend: this._onMoveEnd
11345 if (!this.options.updateWhenIdle) {
11346 // update tiles on move, but not more often than once per given interval
11347 if (!this._onMove) {
11348 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
11351 events.move = this._onMove;
11354 if (this._zoomAnimated) {
11355 events.zoomanim = this._animateZoom;
11361 // @section Extension methods
11362 // Layers extending `GridLayer` shall reimplement the following method.
11363 // @method createTile(coords: Object, done?: Function): HTMLElement
11364 // Called only internally, must be overridden by classes extending `GridLayer`.
11365 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
11366 // is specified, it must be called when the tile has finished loading and drawing.
11367 createTile: function () {
11368 return document.createElement('div');
11372 // @method getTileSize: Point
11373 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
11374 getTileSize: function () {
11375 var s = this.options.tileSize;
11376 return s instanceof Point ? s : new Point(s, s);
11379 _updateZIndex: function () {
11380 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11381 this._container.style.zIndex = this.options.zIndex;
11385 _setAutoZIndex: function (compare) {
11386 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11388 var layers = this.getPane().children,
11389 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11391 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11393 zIndex = layers[i].style.zIndex;
11395 if (layers[i] !== this._container && zIndex) {
11396 edgeZIndex = compare(edgeZIndex, +zIndex);
11400 if (isFinite(edgeZIndex)) {
11401 this.options.zIndex = edgeZIndex + compare(-1, 1);
11402 this._updateZIndex();
11406 _updateOpacity: function () {
11407 if (!this._map) { return; }
11409 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11410 if (Browser.ielt9) { return; }
11412 setOpacity(this._container, this.options.opacity);
11414 var now = +new Date(),
11418 for (var key in this._tiles) {
11419 var tile = this._tiles[key];
11420 if (!tile.current || !tile.loaded) { continue; }
11422 var fade = Math.min(1, (now - tile.loaded) / 200);
11424 setOpacity(tile.el, fade);
11431 this._onOpaqueTile(tile);
11433 tile.active = true;
11437 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11440 cancelAnimFrame(this._fadeFrame);
11441 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11445 _onOpaqueTile: falseFn,
11447 _initContainer: function () {
11448 if (this._container) { return; }
11450 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11451 this._updateZIndex();
11453 if (this.options.opacity < 1) {
11454 this._updateOpacity();
11457 this.getPane().appendChild(this._container);
11460 _updateLevels: function () {
11462 var zoom = this._tileZoom,
11463 maxZoom = this.options.maxZoom;
11465 if (zoom === undefined) { return undefined; }
11467 for (var z in this._levels) {
11469 if (this._levels[z].el.children.length || z === zoom) {
11470 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11471 this._onUpdateLevel(z);
11473 remove(this._levels[z].el);
11474 this._removeTilesAtZoom(z);
11475 this._onRemoveLevel(z);
11476 delete this._levels[z];
11480 var level = this._levels[zoom],
11484 level = this._levels[zoom] = {};
11486 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11487 level.el.style.zIndex = maxZoom;
11489 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11492 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11494 // force the browser to consider the newly added element for transition
11495 falseFn(level.el.offsetWidth);
11497 this._onCreateLevel(level);
11500 this._level = level;
11505 _onUpdateLevel: falseFn,
11507 _onRemoveLevel: falseFn,
11509 _onCreateLevel: falseFn,
11511 _pruneTiles: function () {
11518 var zoom = this._map.getZoom();
11519 if (zoom > this.options.maxZoom ||
11520 zoom < this.options.minZoom) {
11521 this._removeAllTiles();
11525 for (key in this._tiles) {
11526 tile = this._tiles[key];
11527 tile.retain = tile.current;
11530 for (key in this._tiles) {
11531 tile = this._tiles[key];
11532 if (tile.current && !tile.active) {
11533 var coords = tile.coords;
11534 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11535 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11540 for (key in this._tiles) {
11541 if (!this._tiles[key].retain) {
11542 this._removeTile(key);
11547 _removeTilesAtZoom: function (zoom) {
11548 for (var key in this._tiles) {
11549 if (this._tiles[key].coords.z !== zoom) {
11552 this._removeTile(key);
11556 _removeAllTiles: function () {
11557 for (var key in this._tiles) {
11558 this._removeTile(key);
11562 _invalidateAll: function () {
11563 for (var z in this._levels) {
11564 remove(this._levels[z].el);
11565 this._onRemoveLevel(Number(z));
11566 delete this._levels[z];
11568 this._removeAllTiles();
11570 this._tileZoom = undefined;
11573 _retainParent: function (x, y, z, minZoom) {
11574 var x2 = Math.floor(x / 2),
11575 y2 = Math.floor(y / 2),
11577 coords2 = new Point(+x2, +y2);
11580 var key = this._tileCoordsToKey(coords2),
11581 tile = this._tiles[key];
11583 if (tile && tile.active) {
11584 tile.retain = true;
11587 } else if (tile && tile.loaded) {
11588 tile.retain = true;
11591 if (z2 > minZoom) {
11592 return this._retainParent(x2, y2, z2, minZoom);
11598 _retainChildren: function (x, y, z, maxZoom) {
11600 for (var i = 2 * x; i < 2 * x + 2; i++) {
11601 for (var j = 2 * y; j < 2 * y + 2; j++) {
11603 var coords = new Point(i, j);
11606 var key = this._tileCoordsToKey(coords),
11607 tile = this._tiles[key];
11609 if (tile && tile.active) {
11610 tile.retain = true;
11613 } else if (tile && tile.loaded) {
11614 tile.retain = true;
11617 if (z + 1 < maxZoom) {
11618 this._retainChildren(i, j, z + 1, maxZoom);
11624 _resetView: function (e) {
11625 var animating = e && (e.pinch || e.flyTo);
11626 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11629 _animateZoom: function (e) {
11630 this._setView(e.center, e.zoom, true, e.noUpdate);
11633 _clampZoom: function (zoom) {
11634 var options = this.options;
11636 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11637 return options.minNativeZoom;
11640 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11641 return options.maxNativeZoom;
11647 _setView: function (center, zoom, noPrune, noUpdate) {
11648 var tileZoom = Math.round(zoom);
11649 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11650 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11651 tileZoom = undefined;
11653 tileZoom = this._clampZoom(tileZoom);
11656 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11658 if (!noUpdate || tileZoomChanged) {
11660 this._tileZoom = tileZoom;
11662 if (this._abortLoading) {
11663 this._abortLoading();
11666 this._updateLevels();
11669 if (tileZoom !== undefined) {
11670 this._update(center);
11674 this._pruneTiles();
11677 // Flag to prevent _updateOpacity from pruning tiles during
11678 // a zoom anim or a pinch gesture
11679 this._noPrune = !!noPrune;
11682 this._setZoomTransforms(center, zoom);
11685 _setZoomTransforms: function (center, zoom) {
11686 for (var i in this._levels) {
11687 this._setZoomTransform(this._levels[i], center, zoom);
11691 _setZoomTransform: function (level, center, zoom) {
11692 var scale = this._map.getZoomScale(zoom, level.zoom),
11693 translate = level.origin.multiplyBy(scale)
11694 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11696 if (Browser.any3d) {
11697 setTransform(level.el, translate, scale);
11699 setPosition(level.el, translate);
11703 _resetGrid: function () {
11704 var map = this._map,
11705 crs = map.options.crs,
11706 tileSize = this._tileSize = this.getTileSize(),
11707 tileZoom = this._tileZoom;
11709 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11711 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11714 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11715 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11716 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11718 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11719 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11720 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11724 _onMoveEnd: function () {
11725 if (!this._map || this._map._animatingZoom) { return; }
11730 _getTiledPixelBounds: function (center) {
11731 var map = this._map,
11732 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11733 scale = map.getZoomScale(mapZoom, this._tileZoom),
11734 pixelCenter = map.project(center, this._tileZoom).floor(),
11735 halfSize = map.getSize().divideBy(scale * 2);
11737 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11740 // Private method to load tiles in the grid's active zoom level according to map bounds
11741 _update: function (center) {
11742 var map = this._map;
11743 if (!map) { return; }
11744 var zoom = this._clampZoom(map.getZoom());
11746 if (center === undefined) { center = map.getCenter(); }
11747 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11749 var pixelBounds = this._getTiledPixelBounds(center),
11750 tileRange = this._pxBoundsToTileRange(pixelBounds),
11751 tileCenter = tileRange.getCenter(),
11753 margin = this.options.keepBuffer,
11754 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11755 tileRange.getTopRight().add([margin, -margin]));
11757 // Sanity check: panic if the tile range contains Infinity somewhere.
11758 if (!(isFinite(tileRange.min.x) &&
11759 isFinite(tileRange.min.y) &&
11760 isFinite(tileRange.max.x) &&
11761 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11763 for (var key in this._tiles) {
11764 var c = this._tiles[key].coords;
11765 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11766 this._tiles[key].current = false;
11770 // _update just loads more tiles. If the tile zoom level differs too much
11771 // from the map's, let _setView reset levels and prune old tiles.
11772 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11774 // create a queue of coordinates to load tiles from
11775 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11776 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11777 var coords = new Point(i, j);
11778 coords.z = this._tileZoom;
11780 if (!this._isValidTile(coords)) { continue; }
11782 var tile = this._tiles[this._tileCoordsToKey(coords)];
11784 tile.current = true;
11786 queue.push(coords);
11791 // sort tile queue to load tiles in order of their distance to center
11792 queue.sort(function (a, b) {
11793 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11796 if (queue.length !== 0) {
11797 // if it's the first batch of tiles to load
11798 if (!this._loading) {
11799 this._loading = true;
11800 // @event loading: Event
11801 // Fired when the grid layer starts loading tiles.
11802 this.fire('loading');
11805 // create DOM fragment to append tiles in one batch
11806 var fragment = document.createDocumentFragment();
11808 for (i = 0; i < queue.length; i++) {
11809 this._addTile(queue[i], fragment);
11812 this._level.el.appendChild(fragment);
11816 _isValidTile: function (coords) {
11817 var crs = this._map.options.crs;
11819 if (!crs.infinite) {
11820 // don't load tile if it's out of bounds and not wrapped
11821 var bounds = this._globalTileRange;
11822 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11823 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11826 if (!this.options.bounds) { return true; }
11828 // don't load tile if it doesn't intersect the bounds in options
11829 var tileBounds = this._tileCoordsToBounds(coords);
11830 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11833 _keyToBounds: function (key) {
11834 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11837 _tileCoordsToNwSe: function (coords) {
11838 var map = this._map,
11839 tileSize = this.getTileSize(),
11840 nwPoint = coords.scaleBy(tileSize),
11841 sePoint = nwPoint.add(tileSize),
11842 nw = map.unproject(nwPoint, coords.z),
11843 se = map.unproject(sePoint, coords.z);
11847 // converts tile coordinates to its geographical bounds
11848 _tileCoordsToBounds: function (coords) {
11849 var bp = this._tileCoordsToNwSe(coords),
11850 bounds = new LatLngBounds(bp[0], bp[1]);
11852 if (!this.options.noWrap) {
11853 bounds = this._map.wrapLatLngBounds(bounds);
11857 // converts tile coordinates to key for the tile cache
11858 _tileCoordsToKey: function (coords) {
11859 return coords.x + ':' + coords.y + ':' + coords.z;
11862 // converts tile cache key to coordinates
11863 _keyToTileCoords: function (key) {
11864 var k = key.split(':'),
11865 coords = new Point(+k[0], +k[1]);
11870 _removeTile: function (key) {
11871 var tile = this._tiles[key];
11872 if (!tile) { return; }
11876 delete this._tiles[key];
11878 // @event tileunload: TileEvent
11879 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11880 this.fire('tileunload', {
11882 coords: this._keyToTileCoords(key)
11886 _initTile: function (tile) {
11887 addClass(tile, 'leaflet-tile');
11889 var tileSize = this.getTileSize();
11890 tile.style.width = tileSize.x + 'px';
11891 tile.style.height = tileSize.y + 'px';
11893 tile.onselectstart = falseFn;
11894 tile.onmousemove = falseFn;
11896 // update opacity on tiles in IE7-8 because of filter inheritance problems
11897 if (Browser.ielt9 && this.options.opacity < 1) {
11898 setOpacity(tile, this.options.opacity);
11902 _addTile: function (coords, container) {
11903 var tilePos = this._getTilePos(coords),
11904 key = this._tileCoordsToKey(coords);
11906 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11908 this._initTile(tile);
11910 // if createTile is defined with a second argument ("done" callback),
11911 // we know that tile is async and will be ready later; otherwise
11912 if (this.createTile.length < 2) {
11913 // mark tile as ready, but delay one frame for opacity animation to happen
11914 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11917 setPosition(tile, tilePos);
11919 // save tile in cache
11920 this._tiles[key] = {
11926 container.appendChild(tile);
11927 // @event tileloadstart: TileEvent
11928 // Fired when a tile is requested and starts loading.
11929 this.fire('tileloadstart', {
11935 _tileReady: function (coords, err, tile) {
11937 // @event tileerror: TileErrorEvent
11938 // Fired when there is an error loading a tile.
11939 this.fire('tileerror', {
11946 var key = this._tileCoordsToKey(coords);
11948 tile = this._tiles[key];
11949 if (!tile) { return; }
11951 tile.loaded = +new Date();
11952 if (this._map._fadeAnimated) {
11953 setOpacity(tile.el, 0);
11954 cancelAnimFrame(this._fadeFrame);
11955 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11957 tile.active = true;
11958 this._pruneTiles();
11962 addClass(tile.el, 'leaflet-tile-loaded');
11964 // @event tileload: TileEvent
11965 // Fired when a tile loads.
11966 this.fire('tileload', {
11972 if (this._noTilesToLoad()) {
11973 this._loading = false;
11974 // @event load: Event
11975 // Fired when the grid layer loaded all visible tiles.
11978 if (Browser.ielt9 || !this._map._fadeAnimated) {
11979 requestAnimFrame(this._pruneTiles, this);
11981 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11982 // to trigger a pruning.
11983 setTimeout(bind(this._pruneTiles, this), 250);
11988 _getTilePos: function (coords) {
11989 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11992 _wrapCoords: function (coords) {
11993 var newCoords = new Point(
11994 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11995 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11996 newCoords.z = coords.z;
12000 _pxBoundsToTileRange: function (bounds) {
12001 var tileSize = this.getTileSize();
12003 bounds.min.unscaleBy(tileSize).floor(),
12004 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
12007 _noTilesToLoad: function () {
12008 for (var key in this._tiles) {
12009 if (!this._tiles[key].loaded) { return false; }
12015 // @factory L.gridLayer(options?: GridLayer options)
12016 // Creates a new instance of GridLayer with the supplied options.
12017 function gridLayer(options) {
12018 return new GridLayer(options);
12022 * @class TileLayer
\r
12023 * @inherits GridLayer
\r
12024 * @aka L.TileLayer
\r
12025 * 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`.
\r
12030 * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
12033 * @section URL template
\r
12036 * A string of the following form:
\r
12039 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
\r
12042 * `{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 "@2x" to the URL to load retina tiles.
\r
12044 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
\r
12047 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
\r
12052 var TileLayer = GridLayer.extend({
\r
12055 // @aka TileLayer options
\r
12057 // @option minZoom: Number = 0
\r
12058 // The minimum zoom level down to which this layer will be displayed (inclusive).
\r
12061 // @option maxZoom: Number = 18
\r
12062 // The maximum zoom level up to which this layer will be displayed (inclusive).
\r
12065 // @option subdomains: String|String[] = 'abc'
\r
12066 // 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.
\r
12067 subdomains: 'abc',
\r
12069 // @option errorTileUrl: String = ''
\r
12070 // URL to the tile image to show in place of the tile that failed to load.
\r
12071 errorTileUrl: '',
\r
12073 // @option zoomOffset: Number = 0
\r
12074 // The zoom number used in tile URLs will be offset with this value.
\r
12077 // @option tms: Boolean = false
\r
12078 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
\r
12081 // @option zoomReverse: Boolean = false
\r
12082 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
\r
12083 zoomReverse: false,
\r
12085 // @option detectRetina: Boolean = false
\r
12086 // 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.
\r
12087 detectRetina: false,
\r
12089 // @option crossOrigin: Boolean|String = false
\r
12090 // Whether the crossOrigin attribute will be added to the tiles.
\r
12091 // 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.
\r
12092 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
\r
12093 crossOrigin: false,
\r
12095 // @option referrerPolicy: Boolean|String = false
\r
12096 // Whether the referrerPolicy attribute will be added to the tiles.
\r
12097 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
\r
12098 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
\r
12099 // (e.g. to validate an API token).
\r
12100 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
\r
12101 referrerPolicy: false
\r
12104 initialize: function (url, options) {
\r
12108 options = setOptions(this, options);
\r
12110 // detecting retina displays, adjusting tileSize and zoom levels
\r
12111 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
\r
12113 options.tileSize = Math.floor(options.tileSize / 2);
\r
12115 if (!options.zoomReverse) {
\r
12116 options.zoomOffset++;
\r
12117 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
\r
12119 options.zoomOffset--;
\r
12120 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
\r
12123 options.minZoom = Math.max(0, options.minZoom);
\r
12124 } else if (!options.zoomReverse) {
\r
12125 // make sure maxZoom is gte minZoom
\r
12126 options.maxZoom = Math.max(options.minZoom, options.maxZoom);
\r
12128 // make sure minZoom is lte maxZoom
\r
12129 options.minZoom = Math.min(options.maxZoom, options.minZoom);
\r
12132 if (typeof options.subdomains === 'string') {
\r
12133 options.subdomains = options.subdomains.split('');
\r
12136 this.on('tileunload', this._onTileRemove);
\r
12139 // @method setUrl(url: String, noRedraw?: Boolean): this
\r
12140 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
\r
12141 // If the URL does not change, the layer will not be redrawn unless
\r
12142 // the noRedraw parameter is set to false.
\r
12143 setUrl: function (url, noRedraw) {
\r
12144 if (this._url === url && noRedraw === undefined) {
\r
12156 // @method createTile(coords: Object, done?: Function): HTMLElement
\r
12157 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
\r
12158 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
\r
12159 // callback is called when the tile has been loaded.
\r
12160 createTile: function (coords, done) {
\r
12161 var tile = document.createElement('img');
\r
12163 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
\r
12164 on(tile, 'error', bind(this._tileOnError, this, done, tile));
\r
12166 if (this.options.crossOrigin || this.options.crossOrigin === '') {
\r
12167 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
\r
12170 // for this new option we follow the documented behavior
\r
12171 // more closely by only setting the property when string
\r
12172 if (typeof this.options.referrerPolicy === 'string') {
\r
12173 tile.referrerPolicy = this.options.referrerPolicy;
\r
12176 // The alt attribute is set to the empty string,
\r
12177 // allowing screen readers to ignore the decorative image tiles.
\r
12178 // https://www.w3.org/WAI/tutorials/images/decorative/
\r
12179 // https://www.w3.org/TR/html-aria/#el-img-empty-alt
\r
12182 tile.src = this.getTileUrl(coords);
\r
12187 // @section Extension methods
\r
12188 // @uninheritable
\r
12189 // Layers extending `TileLayer` might reimplement the following method.
\r
12190 // @method getTileUrl(coords: Object): String
\r
12191 // Called only internally, returns the URL for a tile given its coordinates.
\r
12192 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
\r
12193 getTileUrl: function (coords) {
\r
12195 r: Browser.retina ? '@2x' : '',
\r
12196 s: this._getSubdomain(coords),
\r
12199 z: this._getZoomForUrl()
\r
12201 if (this._map && !this._map.options.crs.infinite) {
\r
12202 var invertedY = this._globalTileRange.max.y - coords.y;
\r
12203 if (this.options.tms) {
\r
12204 data['y'] = invertedY;
\r
12206 data['-y'] = invertedY;
\r
12209 return template(this._url, extend(data, this.options));
\r
12212 _tileOnLoad: function (done, tile) {
\r
12213 // For https://github.com/Leaflet/Leaflet/issues/3332
\r
12214 if (Browser.ielt9) {
\r
12215 setTimeout(bind(done, this, null, tile), 0);
\r
12217 done(null, tile);
\r
12221 _tileOnError: function (done, tile, e) {
\r
12222 var errorUrl = this.options.errorTileUrl;
\r
12223 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
\r
12224 tile.src = errorUrl;
\r
12229 _onTileRemove: function (e) {
\r
12230 e.tile.onload = null;
\r
12233 _getZoomForUrl: function () {
\r
12234 var zoom = this._tileZoom,
\r
12235 maxZoom = this.options.maxZoom,
\r
12236 zoomReverse = this.options.zoomReverse,
\r
12237 zoomOffset = this.options.zoomOffset;
\r
12239 if (zoomReverse) {
\r
12240 zoom = maxZoom - zoom;
\r
12243 return zoom + zoomOffset;
\r
12246 _getSubdomain: function (tilePoint) {
\r
12247 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
\r
12248 return this.options.subdomains[index];
\r
12251 // stops loading all tiles in the background layer
\r
12252 _abortLoading: function () {
\r
12254 for (i in this._tiles) {
\r
12255 if (this._tiles[i].coords.z !== this._tileZoom) {
\r
12256 tile = this._tiles[i].el;
\r
12258 tile.onload = falseFn;
\r
12259 tile.onerror = falseFn;
\r
12261 if (!tile.complete) {
\r
12262 tile.src = emptyImageUrl;
\r
12263 var coords = this._tiles[i].coords;
\r
12265 delete this._tiles[i];
\r
12266 // @event tileabort: TileEvent
\r
12267 // Fired when a tile was loading but is now not wanted.
\r
12268 this.fire('tileabort', {
\r
12277 _removeTile: function (key) {
\r
12278 var tile = this._tiles[key];
\r
12279 if (!tile) { return; }
\r
12281 // Cancels any pending http requests associated with the tile
\r
12282 tile.el.setAttribute('src', emptyImageUrl);
\r
12284 return GridLayer.prototype._removeTile.call(this, key);
\r
12287 _tileReady: function (coords, err, tile) {
\r
12288 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
\r
12292 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
\r
12297 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
\r
12298 // Instantiates a tile layer object given a `URL template` and optionally an options object.
\r
12300 function tileLayer(url, options) {
\r
12301 return new TileLayer(url, options);
\r
12305 * @class TileLayer.WMS
\r
12306 * @inherits TileLayer
\r
12307 * @aka L.TileLayer.WMS
\r
12308 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
\r
12313 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
\r
12314 * layers: 'nexrad-n0r-900913',
\r
12315 * format: 'image/png',
\r
12316 * transparent: true,
\r
12317 * attribution: "Weather data © 2012 IEM Nexrad"
\r
12322 var TileLayerWMS = TileLayer.extend({
\r
12325 // @aka TileLayer.WMS options
\r
12326 // If any custom options not documented here are used, they will be sent to the
\r
12327 // WMS server as extra parameters in each request URL. This can be useful for
\r
12328 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
\r
12329 defaultWmsParams: {
\r
12331 request: 'GetMap',
\r
12333 // @option layers: String = ''
\r
12334 // **(required)** Comma-separated list of WMS layers to show.
\r
12337 // @option styles: String = ''
\r
12338 // Comma-separated list of WMS styles.
\r
12341 // @option format: String = 'image/jpeg'
\r
12342 // WMS image format (use `'image/png'` for layers with transparency).
\r
12343 format: 'image/jpeg',
\r
12345 // @option transparent: Boolean = false
\r
12346 // If `true`, the WMS service will return images with transparency.
\r
12347 transparent: false,
\r
12349 // @option version: String = '1.1.1'
\r
12350 // Version of the WMS service to use
\r
12355 // @option crs: CRS = null
\r
12356 // Coordinate Reference System to use for the WMS requests, defaults to
\r
12357 // map CRS. Don't change this if you're not sure what it means.
\r
12360 // @option uppercase: Boolean = false
\r
12361 // If `true`, WMS request parameter keys will be uppercase.
\r
12365 initialize: function (url, options) {
\r
12369 var wmsParams = extend({}, this.defaultWmsParams);
\r
12371 // all keys that are not TileLayer options go to WMS params
\r
12372 for (var i in options) {
\r
12373 if (!(i in this.options)) {
\r
12374 wmsParams[i] = options[i];
\r
12378 options = setOptions(this, options);
\r
12380 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
\r
12381 var tileSize = this.getTileSize();
\r
12382 wmsParams.width = tileSize.x * realRetina;
\r
12383 wmsParams.height = tileSize.y * realRetina;
\r
12385 this.wmsParams = wmsParams;
\r
12388 onAdd: function (map) {
\r
12390 this._crs = this.options.crs || map.options.crs;
\r
12391 this._wmsVersion = parseFloat(this.wmsParams.version);
\r
12393 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
\r
12394 this.wmsParams[projectionKey] = this._crs.code;
\r
12396 TileLayer.prototype.onAdd.call(this, map);
\r
12399 getTileUrl: function (coords) {
\r
12401 var tileBounds = this._tileCoordsToNwSe(coords),
\r
12403 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
\r
12404 min = bounds.min,
\r
12405 max = bounds.max,
\r
12406 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
\r
12407 [min.y, min.x, max.y, max.x] :
\r
12408 [min.x, min.y, max.x, max.y]).join(','),
\r
12409 url = TileLayer.prototype.getTileUrl.call(this, coords);
\r
12411 getParamString(this.wmsParams, url, this.options.uppercase) +
\r
12412 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
\r
12415 // @method setParams(params: Object, noRedraw?: Boolean): this
\r
12416 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
\r
12417 setParams: function (params, noRedraw) {
\r
12419 extend(this.wmsParams, params);
\r
12430 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
\r
12431 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
\r
12432 function tileLayerWMS(url, options) {
\r
12433 return new TileLayerWMS(url, options);
\r
12436 TileLayer.WMS = TileLayerWMS;
12437 tileLayer.wms = tileLayerWMS;
12444 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12445 * DOM container of the renderer, its bounds, and its zoom animation.
12447 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12448 * itself can be added or removed to the map. All paths use a renderer, which can
12449 * be implicit (the map will decide the type of renderer and use it automatically)
12450 * or explicit (using the [`renderer`](#path-renderer) option of the path).
12452 * Do not use this class directly, use `SVG` and `Canvas` instead.
12454 * @event update: Event
12455 * Fired when the renderer updates its bounds, center and zoom, for example when
12456 * its map has moved
12459 var Renderer = Layer.extend({
12462 // @aka Renderer options
12464 // @option padding: Number = 0.1
12465 // How much to extend the clip area around the map view (relative to its size)
12466 // e.g. 0.1 would be 10% of map view in each direction
12470 initialize: function (options) {
12471 setOptions(this, options);
12473 this._layers = this._layers || {};
12476 onAdd: function () {
12477 if (!this._container) {
12478 this._initContainer(); // defined by renderer implementations
12480 // always keep transform-origin as 0 0
12481 addClass(this._container, 'leaflet-zoom-animated');
12484 this.getPane().appendChild(this._container);
12486 this.on('update', this._updatePaths, this);
12489 onRemove: function () {
12490 this.off('update', this._updatePaths, this);
12491 this._destroyContainer();
12494 getEvents: function () {
12496 viewreset: this._reset,
12497 zoom: this._onZoom,
12498 moveend: this._update,
12499 zoomend: this._onZoomEnd
12501 if (this._zoomAnimated) {
12502 events.zoomanim = this._onAnimZoom;
12507 _onAnimZoom: function (ev) {
12508 this._updateTransform(ev.center, ev.zoom);
12511 _onZoom: function () {
12512 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12515 _updateTransform: function (center, zoom) {
12516 var scale = this._map.getZoomScale(zoom, this._zoom),
12517 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12518 currentCenterPoint = this._map.project(this._center, zoom),
12520 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12521 .subtract(this._map._getNewPixelOrigin(center, zoom));
12523 if (Browser.any3d) {
12524 setTransform(this._container, topLeftOffset, scale);
12526 setPosition(this._container, topLeftOffset);
12530 _reset: function () {
12532 this._updateTransform(this._center, this._zoom);
12534 for (var id in this._layers) {
12535 this._layers[id]._reset();
12539 _onZoomEnd: function () {
12540 for (var id in this._layers) {
12541 this._layers[id]._project();
12545 _updatePaths: function () {
12546 for (var id in this._layers) {
12547 this._layers[id]._update();
12551 _update: function () {
12552 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12553 // Subclasses are responsible of firing the 'update' event.
12554 var p = this.options.padding,
12555 size = this._map.getSize(),
12556 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12558 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12560 this._center = this._map.getCenter();
12561 this._zoom = this._map.getZoom();
12567 * @inherits Renderer
12570 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12571 * Inherits `Renderer`.
12573 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12574 * available in all web browsers, notably IE8, and overlapping geometries might
12575 * not display properly in some edge cases.
12579 * Use Canvas by default for all paths in the map:
12582 * var map = L.map('map', {
12583 * renderer: L.canvas()
12587 * Use a Canvas renderer with extra padding for specific vector geometries:
12590 * var map = L.map('map');
12591 * var myRenderer = L.canvas({ padding: 0.5 });
12592 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12593 * var circle = L.circle( center, { renderer: myRenderer } );
12597 var Canvas = Renderer.extend({
12600 // @aka Canvas options
12602 // @option tolerance: Number = 0
12603 // How much to extend the click tolerance around a path/object on the map.
12607 getEvents: function () {
12608 var events = Renderer.prototype.getEvents.call(this);
12609 events.viewprereset = this._onViewPreReset;
12613 _onViewPreReset: function () {
12614 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12615 this._postponeUpdatePaths = true;
12618 onAdd: function () {
12619 Renderer.prototype.onAdd.call(this);
12621 // Redraw vectors since canvas is cleared upon removal,
12622 // in case of removing the renderer itself from the map.
12626 _initContainer: function () {
12627 var container = this._container = document.createElement('canvas');
12629 on(container, 'mousemove', this._onMouseMove, this);
12630 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12631 on(container, 'mouseout', this._handleMouseOut, this);
12632 container['_leaflet_disable_events'] = true;
12634 this._ctx = container.getContext('2d');
12637 _destroyContainer: function () {
12638 cancelAnimFrame(this._redrawRequest);
12640 remove(this._container);
12641 off(this._container);
12642 delete this._container;
12645 _updatePaths: function () {
12646 if (this._postponeUpdatePaths) { return; }
12649 this._redrawBounds = null;
12650 for (var id in this._layers) {
12651 layer = this._layers[id];
12657 _update: function () {
12658 if (this._map._animatingZoom && this._bounds) { return; }
12660 Renderer.prototype._update.call(this);
12662 var b = this._bounds,
12663 container = this._container,
12664 size = b.getSize(),
12665 m = Browser.retina ? 2 : 1;
12667 setPosition(container, b.min);
12669 // set canvas size (also clearing it); use double size on retina
12670 container.width = m * size.x;
12671 container.height = m * size.y;
12672 container.style.width = size.x + 'px';
12673 container.style.height = size.y + 'px';
12675 if (Browser.retina) {
12676 this._ctx.scale(2, 2);
12679 // translate so we use the same path coordinates after canvas element moves
12680 this._ctx.translate(-b.min.x, -b.min.y);
12682 // Tell paths to redraw themselves
12683 this.fire('update');
12686 _reset: function () {
12687 Renderer.prototype._reset.call(this);
12689 if (this._postponeUpdatePaths) {
12690 this._postponeUpdatePaths = false;
12691 this._updatePaths();
12695 _initPath: function (layer) {
12696 this._updateDashArray(layer);
12697 this._layers[stamp(layer)] = layer;
12699 var order = layer._order = {
12701 prev: this._drawLast,
12704 if (this._drawLast) { this._drawLast.next = order; }
12705 this._drawLast = order;
12706 this._drawFirst = this._drawFirst || this._drawLast;
12709 _addPath: function (layer) {
12710 this._requestRedraw(layer);
12713 _removePath: function (layer) {
12714 var order = layer._order;
12715 var next = order.next;
12716 var prev = order.prev;
12721 this._drawLast = prev;
12726 this._drawFirst = next;
12729 delete layer._order;
12731 delete this._layers[stamp(layer)];
12733 this._requestRedraw(layer);
12736 _updatePath: function (layer) {
12737 // Redraw the union of the layer's old pixel
12738 // bounds and the new pixel bounds.
12739 this._extendRedrawBounds(layer);
12742 // The redraw will extend the redraw bounds
12743 // with the new pixel bounds.
12744 this._requestRedraw(layer);
12747 _updateStyle: function (layer) {
12748 this._updateDashArray(layer);
12749 this._requestRedraw(layer);
12752 _updateDashArray: function (layer) {
12753 if (typeof layer.options.dashArray === 'string') {
12754 var parts = layer.options.dashArray.split(/[, ]+/),
12758 for (i = 0; i < parts.length; i++) {
12759 dashValue = Number(parts[i]);
12760 // Ignore dash array containing invalid lengths
12761 if (isNaN(dashValue)) { return; }
12762 dashArray.push(dashValue);
12764 layer.options._dashArray = dashArray;
12766 layer.options._dashArray = layer.options.dashArray;
12770 _requestRedraw: function (layer) {
12771 if (!this._map) { return; }
12773 this._extendRedrawBounds(layer);
12774 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12777 _extendRedrawBounds: function (layer) {
12778 if (layer._pxBounds) {
12779 var padding = (layer.options.weight || 0) + 1;
12780 this._redrawBounds = this._redrawBounds || new Bounds();
12781 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12782 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12786 _redraw: function () {
12787 this._redrawRequest = null;
12789 if (this._redrawBounds) {
12790 this._redrawBounds.min._floor();
12791 this._redrawBounds.max._ceil();
12794 this._clear(); // clear layers in redraw bounds
12795 this._draw(); // draw layers
12797 this._redrawBounds = null;
12800 _clear: function () {
12801 var bounds = this._redrawBounds;
12803 var size = bounds.getSize();
12804 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12807 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12808 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12809 this._ctx.restore();
12813 _draw: function () {
12814 var layer, bounds = this._redrawBounds;
12817 var size = bounds.getSize();
12818 this._ctx.beginPath();
12819 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12823 this._drawing = true;
12825 for (var order = this._drawFirst; order; order = order.next) {
12826 layer = order.layer;
12827 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12828 layer._updatePath();
12832 this._drawing = false;
12834 this._ctx.restore(); // Restore state before clipping.
12837 _updatePoly: function (layer, closed) {
12838 if (!this._drawing) { return; }
12841 parts = layer._parts,
12842 len = parts.length,
12845 if (!len) { return; }
12849 for (i = 0; i < len; i++) {
12850 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12852 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12859 this._fillStroke(ctx, layer);
12861 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12864 _updateCircle: function (layer) {
12866 if (!this._drawing || layer._empty()) { return; }
12868 var p = layer._point,
12870 r = Math.max(Math.round(layer._radius), 1),
12871 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12879 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12885 this._fillStroke(ctx, layer);
12888 _fillStroke: function (ctx, layer) {
12889 var options = layer.options;
12891 if (options.fill) {
12892 ctx.globalAlpha = options.fillOpacity;
12893 ctx.fillStyle = options.fillColor || options.color;
12894 ctx.fill(options.fillRule || 'evenodd');
12897 if (options.stroke && options.weight !== 0) {
12898 if (ctx.setLineDash) {
12899 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12901 ctx.globalAlpha = options.opacity;
12902 ctx.lineWidth = options.weight;
12903 ctx.strokeStyle = options.color;
12904 ctx.lineCap = options.lineCap;
12905 ctx.lineJoin = options.lineJoin;
12910 // Canvas obviously doesn't have mouse events for individual drawn objects,
12911 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12913 _onClick: function (e) {
12914 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12916 for (var order = this._drawFirst; order; order = order.next) {
12917 layer = order.layer;
12918 if (layer.options.interactive && layer._containsPoint(point)) {
12919 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12920 clickedLayer = layer;
12924 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12927 _onMouseMove: function (e) {
12928 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12930 var point = this._map.mouseEventToLayerPoint(e);
12931 this._handleMouseHover(e, point);
12935 _handleMouseOut: function (e) {
12936 var layer = this._hoveredLayer;
12938 // if we're leaving the layer, fire mouseout
12939 removeClass(this._container, 'leaflet-interactive');
12940 this._fireEvent([layer], e, 'mouseout');
12941 this._hoveredLayer = null;
12942 this._mouseHoverThrottled = false;
12946 _handleMouseHover: function (e, point) {
12947 if (this._mouseHoverThrottled) {
12951 var layer, candidateHoveredLayer;
12953 for (var order = this._drawFirst; order; order = order.next) {
12954 layer = order.layer;
12955 if (layer.options.interactive && layer._containsPoint(point)) {
12956 candidateHoveredLayer = layer;
12960 if (candidateHoveredLayer !== this._hoveredLayer) {
12961 this._handleMouseOut(e);
12963 if (candidateHoveredLayer) {
12964 addClass(this._container, 'leaflet-interactive'); // change cursor
12965 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12966 this._hoveredLayer = candidateHoveredLayer;
12970 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12972 this._mouseHoverThrottled = true;
12973 setTimeout(bind(function () {
12974 this._mouseHoverThrottled = false;
12978 _fireEvent: function (layers, e, type) {
12979 this._map._fireDOMEvent(e, type || e.type, layers);
12982 _bringToFront: function (layer) {
12983 var order = layer._order;
12985 if (!order) { return; }
12987 var next = order.next;
12988 var prev = order.prev;
12999 // Update first entry unless this is the
13001 this._drawFirst = next;
13004 order.prev = this._drawLast;
13005 this._drawLast.next = order;
13008 this._drawLast = order;
13010 this._requestRedraw(layer);
13013 _bringToBack: function (layer) {
13014 var order = layer._order;
13016 if (!order) { return; }
13018 var next = order.next;
13019 var prev = order.prev;
13030 // Update last entry unless this is the
13032 this._drawLast = prev;
13037 order.next = this._drawFirst;
13038 this._drawFirst.prev = order;
13039 this._drawFirst = order;
13041 this._requestRedraw(layer);
13045 // @factory L.canvas(options?: Renderer options)
13046 // Creates a Canvas renderer with the given options.
13047 function canvas(options) {
13048 return Browser.canvas ? new Canvas(options) : null;
13052 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
13056 var vmlCreate = (function () {
13058 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
13059 return function (name) {
13060 return document.createElement('<lvml:' + name + ' class="lvml">');
13063 // Do not return fn from catch block so `e` can be garbage collected
13064 // See https://github.com/Leaflet/Leaflet/pull/7279
13066 return function (name) {
13067 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
13076 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
13077 * with old versions of Internet Explorer.
13080 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
13083 _initContainer: function () {
13084 this._container = create$1('div', 'leaflet-vml-container');
13087 _update: function () {
13088 if (this._map._animatingZoom) { return; }
13089 Renderer.prototype._update.call(this);
13090 this.fire('update');
13093 _initPath: function (layer) {
13094 var container = layer._container = vmlCreate('shape');
13096 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
13098 container.coordsize = '1 1';
13100 layer._path = vmlCreate('path');
13101 container.appendChild(layer._path);
13103 this._updateStyle(layer);
13104 this._layers[stamp(layer)] = layer;
13107 _addPath: function (layer) {
13108 var container = layer._container;
13109 this._container.appendChild(container);
13111 if (layer.options.interactive) {
13112 layer.addInteractiveTarget(container);
13116 _removePath: function (layer) {
13117 var container = layer._container;
13119 layer.removeInteractiveTarget(container);
13120 delete this._layers[stamp(layer)];
13123 _updateStyle: function (layer) {
13124 var stroke = layer._stroke,
13125 fill = layer._fill,
13126 options = layer.options,
13127 container = layer._container;
13129 container.stroked = !!options.stroke;
13130 container.filled = !!options.fill;
13132 if (options.stroke) {
13134 stroke = layer._stroke = vmlCreate('stroke');
13136 container.appendChild(stroke);
13137 stroke.weight = options.weight + 'px';
13138 stroke.color = options.color;
13139 stroke.opacity = options.opacity;
13141 if (options.dashArray) {
13142 stroke.dashStyle = isArray(options.dashArray) ?
13143 options.dashArray.join(' ') :
13144 options.dashArray.replace(/( *, *)/g, ' ');
13146 stroke.dashStyle = '';
13148 stroke.endcap = options.lineCap.replace('butt', 'flat');
13149 stroke.joinstyle = options.lineJoin;
13151 } else if (stroke) {
13152 container.removeChild(stroke);
13153 layer._stroke = null;
13156 if (options.fill) {
13158 fill = layer._fill = vmlCreate('fill');
13160 container.appendChild(fill);
13161 fill.color = options.fillColor || options.color;
13162 fill.opacity = options.fillOpacity;
13165 container.removeChild(fill);
13166 layer._fill = null;
13170 _updateCircle: function (layer) {
13171 var p = layer._point.round(),
13172 r = Math.round(layer._radius),
13173 r2 = Math.round(layer._radiusY || r);
13175 this._setPath(layer, layer._empty() ? 'M0 0' :
13176 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
13179 _setPath: function (layer, path) {
13180 layer._path.v = path;
13183 _bringToFront: function (layer) {
13184 toFront(layer._container);
13187 _bringToBack: function (layer) {
13188 toBack(layer._container);
13192 var create = Browser.vml ? vmlCreate : svgCreate;
13196 * @inherits Renderer
13199 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
13200 * Inherits `Renderer`.
13202 * Due to [technical limitations](https://caniuse.com/svg), SVG is not
13203 * available in all web browsers, notably Android 2.x and 3.x.
13205 * Although SVG is not available on IE7 and IE8, these browsers support
13206 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
13207 * (a now deprecated technology), and the SVG renderer will fall back to VML in
13212 * Use SVG by default for all paths in the map:
13215 * var map = L.map('map', {
13216 * renderer: L.svg()
13220 * Use a SVG renderer with extra padding for specific vector geometries:
13223 * var map = L.map('map');
13224 * var myRenderer = L.svg({ padding: 0.5 });
13225 * var line = L.polyline( coordinates, { renderer: myRenderer } );
13226 * var circle = L.circle( center, { renderer: myRenderer } );
13230 var SVG = Renderer.extend({
13232 _initContainer: function () {
13233 this._container = create('svg');
13235 // makes it possible to click through svg root; we'll reset it back in individual paths
13236 this._container.setAttribute('pointer-events', 'none');
13238 this._rootGroup = create('g');
13239 this._container.appendChild(this._rootGroup);
13242 _destroyContainer: function () {
13243 remove(this._container);
13244 off(this._container);
13245 delete this._container;
13246 delete this._rootGroup;
13247 delete this._svgSize;
13250 _update: function () {
13251 if (this._map._animatingZoom && this._bounds) { return; }
13253 Renderer.prototype._update.call(this);
13255 var b = this._bounds,
13256 size = b.getSize(),
13257 container = this._container;
13259 // set size of svg-container if changed
13260 if (!this._svgSize || !this._svgSize.equals(size)) {
13261 this._svgSize = size;
13262 container.setAttribute('width', size.x);
13263 container.setAttribute('height', size.y);
13266 // movement: update container viewBox so that we don't have to change coordinates of individual layers
13267 setPosition(container, b.min);
13268 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
13270 this.fire('update');
13273 // methods below are called by vector layers implementations
13275 _initPath: function (layer) {
13276 var path = layer._path = create('path');
13279 // @option className: String = null
13280 // Custom class name set on an element. Only for SVG renderer.
13281 if (layer.options.className) {
13282 addClass(path, layer.options.className);
13285 if (layer.options.interactive) {
13286 addClass(path, 'leaflet-interactive');
13289 this._updateStyle(layer);
13290 this._layers[stamp(layer)] = layer;
13293 _addPath: function (layer) {
13294 if (!this._rootGroup) { this._initContainer(); }
13295 this._rootGroup.appendChild(layer._path);
13296 layer.addInteractiveTarget(layer._path);
13299 _removePath: function (layer) {
13300 remove(layer._path);
13301 layer.removeInteractiveTarget(layer._path);
13302 delete this._layers[stamp(layer)];
13305 _updatePath: function (layer) {
13310 _updateStyle: function (layer) {
13311 var path = layer._path,
13312 options = layer.options;
13314 if (!path) { return; }
13316 if (options.stroke) {
13317 path.setAttribute('stroke', options.color);
13318 path.setAttribute('stroke-opacity', options.opacity);
13319 path.setAttribute('stroke-width', options.weight);
13320 path.setAttribute('stroke-linecap', options.lineCap);
13321 path.setAttribute('stroke-linejoin', options.lineJoin);
13323 if (options.dashArray) {
13324 path.setAttribute('stroke-dasharray', options.dashArray);
13326 path.removeAttribute('stroke-dasharray');
13329 if (options.dashOffset) {
13330 path.setAttribute('stroke-dashoffset', options.dashOffset);
13332 path.removeAttribute('stroke-dashoffset');
13335 path.setAttribute('stroke', 'none');
13338 if (options.fill) {
13339 path.setAttribute('fill', options.fillColor || options.color);
13340 path.setAttribute('fill-opacity', options.fillOpacity);
13341 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
13343 path.setAttribute('fill', 'none');
13347 _updatePoly: function (layer, closed) {
13348 this._setPath(layer, pointsToPath(layer._parts, closed));
13351 _updateCircle: function (layer) {
13352 var p = layer._point,
13353 r = Math.max(Math.round(layer._radius), 1),
13354 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
13355 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
13357 // drawing a circle with two half-arcs
13358 var d = layer._empty() ? 'M0 0' :
13359 'M' + (p.x - r) + ',' + p.y +
13360 arc + (r * 2) + ',0 ' +
13361 arc + (-r * 2) + ',0 ';
13363 this._setPath(layer, d);
13366 _setPath: function (layer, path) {
13367 layer._path.setAttribute('d', path);
13370 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
13371 _bringToFront: function (layer) {
13372 toFront(layer._path);
13375 _bringToBack: function (layer) {
13376 toBack(layer._path);
13381 SVG.include(vmlMixin);
13385 // @factory L.svg(options?: Renderer options)
13386 // Creates a SVG renderer with the given options.
13387 function svg(options) {
13388 return Browser.svg || Browser.vml ? new SVG(options) : null;
13392 // @namespace Map; @method getRenderer(layer: Path): Renderer
13393 // Returns the instance of `Renderer` that should be used to render the given
13394 // `Path`. It will ensure that the `renderer` options of the map and paths
13395 // are respected, and that the renderers do exist on the map.
13396 getRenderer: function (layer) {
13397 // @namespace Path; @option renderer: Renderer
13398 // Use this specific instance of `Renderer` for this path. Takes
13399 // precedence over the map's [default renderer](#map-renderer).
13400 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13403 renderer = this._renderer = this._createRenderer();
13406 if (!this.hasLayer(renderer)) {
13407 this.addLayer(renderer);
13412 _getPaneRenderer: function (name) {
13413 if (name === 'overlayPane' || name === undefined) {
13417 var renderer = this._paneRenderers[name];
13418 if (renderer === undefined) {
13419 renderer = this._createRenderer({pane: name});
13420 this._paneRenderers[name] = renderer;
13425 _createRenderer: function (options) {
13426 // @namespace Map; @option preferCanvas: Boolean = false
13427 // Whether `Path`s should be rendered on a `Canvas` renderer.
13428 // By default, all `Path`s are rendered in a `SVG` renderer.
13429 return (this.options.preferCanvas && canvas(options)) || svg(options);
13434 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13440 * @inherits Polygon
13442 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13447 * // define rectangle geographical bounds
13448 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13450 * // create an orange rectangle
13451 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13453 * // zoom the map to the rectangle bounds
13454 * map.fitBounds(bounds);
13460 var Rectangle = Polygon.extend({
13461 initialize: function (latLngBounds, options) {
13462 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13465 // @method setBounds(latLngBounds: LatLngBounds): this
13466 // Redraws the rectangle with the passed bounds.
13467 setBounds: function (latLngBounds) {
13468 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13471 _boundsToLatLngs: function (latLngBounds) {
13472 latLngBounds = toLatLngBounds(latLngBounds);
13474 latLngBounds.getSouthWest(),
13475 latLngBounds.getNorthWest(),
13476 latLngBounds.getNorthEast(),
13477 latLngBounds.getSouthEast()
13483 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13484 function rectangle(latLngBounds, options) {
13485 return new Rectangle(latLngBounds, options);
13488 SVG.create = create;
13489 SVG.pointsToPath = pointsToPath;
13491 GeoJSON.geometryToLayer = geometryToLayer;
13492 GeoJSON.coordsToLatLng = coordsToLatLng;
13493 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13494 GeoJSON.latLngToCoords = latLngToCoords;
13495 GeoJSON.latLngsToCoords = latLngsToCoords;
13496 GeoJSON.getFeature = getFeature;
13497 GeoJSON.asFeature = asFeature;
13500 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13501 * (zoom to a selected bounding box), enabled by default.
13505 // @section Interaction Options
13507 // @option boxZoom: Boolean = true
13508 // Whether the map can be zoomed to a rectangular area specified by
13509 // dragging the mouse while pressing the shift key.
13513 var BoxZoom = Handler.extend({
13514 initialize: function (map) {
13516 this._container = map._container;
13517 this._pane = map._panes.overlayPane;
13518 this._resetStateTimeout = 0;
13519 map.on('unload', this._destroy, this);
13522 addHooks: function () {
13523 on(this._container, 'mousedown', this._onMouseDown, this);
13526 removeHooks: function () {
13527 off(this._container, 'mousedown', this._onMouseDown, this);
13530 moved: function () {
13531 return this._moved;
13534 _destroy: function () {
13535 remove(this._pane);
13539 _resetState: function () {
13540 this._resetStateTimeout = 0;
13541 this._moved = false;
13544 _clearDeferredResetState: function () {
13545 if (this._resetStateTimeout !== 0) {
13546 clearTimeout(this._resetStateTimeout);
13547 this._resetStateTimeout = 0;
13551 _onMouseDown: function (e) {
13552 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13554 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13555 // will interrupt the interaction and orphan a box element in the container.
13556 this._clearDeferredResetState();
13557 this._resetState();
13559 disableTextSelection();
13560 disableImageDrag();
13562 this._startPoint = this._map.mouseEventToContainerPoint(e);
13566 mousemove: this._onMouseMove,
13567 mouseup: this._onMouseUp,
13568 keydown: this._onKeyDown
13572 _onMouseMove: function (e) {
13573 if (!this._moved) {
13574 this._moved = true;
13576 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13577 addClass(this._container, 'leaflet-crosshair');
13579 this._map.fire('boxzoomstart');
13582 this._point = this._map.mouseEventToContainerPoint(e);
13584 var bounds = new Bounds(this._point, this._startPoint),
13585 size = bounds.getSize();
13587 setPosition(this._box, bounds.min);
13589 this._box.style.width = size.x + 'px';
13590 this._box.style.height = size.y + 'px';
13593 _finish: function () {
13596 removeClass(this._container, 'leaflet-crosshair');
13599 enableTextSelection();
13604 mousemove: this._onMouseMove,
13605 mouseup: this._onMouseUp,
13606 keydown: this._onKeyDown
13610 _onMouseUp: function (e) {
13611 if ((e.which !== 1) && (e.button !== 1)) { return; }
13615 if (!this._moved) { return; }
13616 // Postpone to next JS tick so internal click event handling
13617 // still see it as "moved".
13618 this._clearDeferredResetState();
13619 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13621 var bounds = new LatLngBounds(
13622 this._map.containerPointToLatLng(this._startPoint),
13623 this._map.containerPointToLatLng(this._point));
13627 .fire('boxzoomend', {boxZoomBounds: bounds});
13630 _onKeyDown: function (e) {
13631 if (e.keyCode === 27) {
13633 this._clearDeferredResetState();
13634 this._resetState();
13639 // @section Handlers
13640 // @property boxZoom: Handler
13641 // Box (shift-drag with mouse) zoom handler.
13642 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13645 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13649 // @section Interaction Options
13652 // @option doubleClickZoom: Boolean|String = true
13653 // Whether the map can be zoomed in by double clicking on it and
13654 // zoomed out by double clicking while holding shift. If passed
13655 // `'center'`, double-click zoom will zoom to the center of the
13656 // view regardless of where the mouse was.
13657 doubleClickZoom: true
13660 var DoubleClickZoom = Handler.extend({
13661 addHooks: function () {
13662 this._map.on('dblclick', this._onDoubleClick, this);
13665 removeHooks: function () {
13666 this._map.off('dblclick', this._onDoubleClick, this);
13669 _onDoubleClick: function (e) {
13670 var map = this._map,
13671 oldZoom = map.getZoom(),
13672 delta = map.options.zoomDelta,
13673 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13675 if (map.options.doubleClickZoom === 'center') {
13678 map.setZoomAround(e.containerPoint, zoom);
13683 // @section Handlers
13685 // Map properties include interaction handlers that allow you to control
13686 // interaction behavior in runtime, enabling or disabling certain features such
13687 // as dragging or touch zoom (see `Handler` methods). For example:
13690 // map.doubleClickZoom.disable();
13693 // @property doubleClickZoom: Handler
13694 // Double click zoom handler.
13695 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13698 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13702 // @section Interaction Options
13704 // @option dragging: Boolean = true
13705 // Whether the map is draggable with mouse/touch or not.
13708 // @section Panning Inertia Options
13709 // @option inertia: Boolean = *
13710 // If enabled, panning of the map will have an inertia effect where
13711 // the map builds momentum while dragging and continues moving in
13712 // the same direction for some time. Feels especially nice on touch
13713 // devices. Enabled by default.
13716 // @option inertiaDeceleration: Number = 3000
13717 // The rate with which the inertial movement slows down, in pixels/second².
13718 inertiaDeceleration: 3400, // px/s^2
13720 // @option inertiaMaxSpeed: Number = Infinity
13721 // Max speed of the inertial movement, in pixels/second.
13722 inertiaMaxSpeed: Infinity, // px/s
13724 // @option easeLinearity: Number = 0.2
13725 easeLinearity: 0.2,
13727 // TODO refactor, move to CRS
13728 // @option worldCopyJump: Boolean = false
13729 // With this option enabled, the map tracks when you pan to another "copy"
13730 // of the world and seamlessly jumps to the original one so that all overlays
13731 // like markers and vector layers are still visible.
13732 worldCopyJump: false,
13734 // @option maxBoundsViscosity: Number = 0.0
13735 // If `maxBounds` is set, this option will control how solid the bounds
13736 // are when dragging the map around. The default value of `0.0` allows the
13737 // user to drag outside the bounds at normal speed, higher values will
13738 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13739 // solid, preventing the user from dragging outside the bounds.
13740 maxBoundsViscosity: 0.0
13743 var Drag = Handler.extend({
13744 addHooks: function () {
13745 if (!this._draggable) {
13746 var map = this._map;
13748 this._draggable = new Draggable(map._mapPane, map._container);
13750 this._draggable.on({
13751 dragstart: this._onDragStart,
13752 drag: this._onDrag,
13753 dragend: this._onDragEnd
13756 this._draggable.on('predrag', this._onPreDragLimit, this);
13757 if (map.options.worldCopyJump) {
13758 this._draggable.on('predrag', this._onPreDragWrap, this);
13759 map.on('zoomend', this._onZoomEnd, this);
13761 map.whenReady(this._onZoomEnd, this);
13764 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13765 this._draggable.enable();
13766 this._positions = [];
13770 removeHooks: function () {
13771 removeClass(this._map._container, 'leaflet-grab');
13772 removeClass(this._map._container, 'leaflet-touch-drag');
13773 this._draggable.disable();
13776 moved: function () {
13777 return this._draggable && this._draggable._moved;
13780 moving: function () {
13781 return this._draggable && this._draggable._moving;
13784 _onDragStart: function () {
13785 var map = this._map;
13788 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13789 var bounds = toLatLngBounds(this._map.options.maxBounds);
13791 this._offsetLimit = toBounds(
13792 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13793 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13794 .add(this._map.getSize()));
13796 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13798 this._offsetLimit = null;
13803 .fire('dragstart');
13805 if (map.options.inertia) {
13806 this._positions = [];
13811 _onDrag: function (e) {
13812 if (this._map.options.inertia) {
13813 var time = this._lastTime = +new Date(),
13814 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13816 this._positions.push(pos);
13817 this._times.push(time);
13819 this._prunePositions(time);
13827 _prunePositions: function (time) {
13828 while (this._positions.length > 1 && time - this._times[0] > 50) {
13829 this._positions.shift();
13830 this._times.shift();
13834 _onZoomEnd: function () {
13835 var pxCenter = this._map.getSize().divideBy(2),
13836 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13838 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13839 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13842 _viscousLimit: function (value, threshold) {
13843 return value - (value - threshold) * this._viscosity;
13846 _onPreDragLimit: function () {
13847 if (!this._viscosity || !this._offsetLimit) { return; }
13849 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13851 var limit = this._offsetLimit;
13852 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13853 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13854 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13855 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13857 this._draggable._newPos = this._draggable._startPos.add(offset);
13860 _onPreDragWrap: function () {
13861 // TODO refactor to be able to adjust map pane position after zoom
13862 var worldWidth = this._worldWidth,
13863 halfWidth = Math.round(worldWidth / 2),
13864 dx = this._initialWorldOffset,
13865 x = this._draggable._newPos.x,
13866 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13867 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13868 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13870 this._draggable._absPos = this._draggable._newPos.clone();
13871 this._draggable._newPos.x = newX;
13874 _onDragEnd: function (e) {
13875 var map = this._map,
13876 options = map.options,
13878 noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13880 map.fire('dragend', e);
13883 map.fire('moveend');
13886 this._prunePositions(+new Date());
13888 var direction = this._lastPos.subtract(this._positions[0]),
13889 duration = (this._lastTime - this._times[0]) / 1000,
13890 ease = options.easeLinearity,
13892 speedVector = direction.multiplyBy(ease / duration),
13893 speed = speedVector.distanceTo([0, 0]),
13895 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13896 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13898 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13899 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13901 if (!offset.x && !offset.y) {
13902 map.fire('moveend');
13905 offset = map._limitOffset(offset, map.options.maxBounds);
13907 requestAnimFrame(function () {
13908 map.panBy(offset, {
13909 duration: decelerationDuration,
13910 easeLinearity: ease,
13920 // @section Handlers
13921 // @property dragging: Handler
13922 // Map dragging handler (by both mouse and touch).
13923 Map.addInitHook('addHandler', 'dragging', Drag);
13926 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13930 // @section Keyboard Navigation Options
13932 // @option keyboard: Boolean = true
13933 // Makes the map focusable and allows users to navigate the map with keyboard
13934 // arrows and `+`/`-` keys.
13937 // @option keyboardPanDelta: Number = 80
13938 // Amount of pixels to pan when pressing an arrow key.
13939 keyboardPanDelta: 80
13942 var Keyboard = Handler.extend({
13949 zoomIn: [187, 107, 61, 171],
13950 zoomOut: [189, 109, 54, 173]
13953 initialize: function (map) {
13956 this._setPanDelta(map.options.keyboardPanDelta);
13957 this._setZoomDelta(map.options.zoomDelta);
13960 addHooks: function () {
13961 var container = this._map._container;
13963 // make the container focusable by tabbing
13964 if (container.tabIndex <= 0) {
13965 container.tabIndex = '0';
13969 focus: this._onFocus,
13970 blur: this._onBlur,
13971 mousedown: this._onMouseDown
13975 focus: this._addHooks,
13976 blur: this._removeHooks
13980 removeHooks: function () {
13981 this._removeHooks();
13983 off(this._map._container, {
13984 focus: this._onFocus,
13985 blur: this._onBlur,
13986 mousedown: this._onMouseDown
13990 focus: this._addHooks,
13991 blur: this._removeHooks
13995 _onMouseDown: function () {
13996 if (this._focused) { return; }
13998 var body = document.body,
13999 docEl = document.documentElement,
14000 top = body.scrollTop || docEl.scrollTop,
14001 left = body.scrollLeft || docEl.scrollLeft;
14003 this._map._container.focus();
14005 window.scrollTo(left, top);
14008 _onFocus: function () {
14009 this._focused = true;
14010 this._map.fire('focus');
14013 _onBlur: function () {
14014 this._focused = false;
14015 this._map.fire('blur');
14018 _setPanDelta: function (panDelta) {
14019 var keys = this._panKeys = {},
14020 codes = this.keyCodes,
14023 for (i = 0, len = codes.left.length; i < len; i++) {
14024 keys[codes.left[i]] = [-1 * panDelta, 0];
14026 for (i = 0, len = codes.right.length; i < len; i++) {
14027 keys[codes.right[i]] = [panDelta, 0];
14029 for (i = 0, len = codes.down.length; i < len; i++) {
14030 keys[codes.down[i]] = [0, panDelta];
14032 for (i = 0, len = codes.up.length; i < len; i++) {
14033 keys[codes.up[i]] = [0, -1 * panDelta];
14037 _setZoomDelta: function (zoomDelta) {
14038 var keys = this._zoomKeys = {},
14039 codes = this.keyCodes,
14042 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
14043 keys[codes.zoomIn[i]] = zoomDelta;
14045 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
14046 keys[codes.zoomOut[i]] = -zoomDelta;
14050 _addHooks: function () {
14051 on(document, 'keydown', this._onKeyDown, this);
14054 _removeHooks: function () {
14055 off(document, 'keydown', this._onKeyDown, this);
14058 _onKeyDown: function (e) {
14059 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
14061 var key = e.keyCode,
14065 if (key in this._panKeys) {
14066 if (!map._panAnim || !map._panAnim._inProgress) {
14067 offset = this._panKeys[key];
14069 offset = toPoint(offset).multiplyBy(3);
14072 if (map.options.maxBounds) {
14073 offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
14076 if (map.options.worldCopyJump) {
14077 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
14078 map.panTo(newLatLng);
14083 } else if (key in this._zoomKeys) {
14084 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
14086 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
14097 // @section Handlers
14098 // @section Handlers
14099 // @property keyboard: Handler
14100 // Keyboard navigation handler.
14101 Map.addInitHook('addHandler', 'keyboard', Keyboard);
14104 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
14108 // @section Interaction Options
14110 // @section Mouse wheel options
14111 // @option scrollWheelZoom: Boolean|String = true
14112 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
14113 // it will zoom to the center of the view regardless of where the mouse was.
14114 scrollWheelZoom: true,
14116 // @option wheelDebounceTime: Number = 40
14117 // Limits the rate at which a wheel can fire (in milliseconds). By default
14118 // user can't zoom via wheel more often than once per 40 ms.
14119 wheelDebounceTime: 40,
14121 // @option wheelPxPerZoomLevel: Number = 60
14122 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
14123 // mean a change of one full zoom level. Smaller values will make wheel-zooming
14124 // faster (and vice versa).
14125 wheelPxPerZoomLevel: 60
14128 var ScrollWheelZoom = Handler.extend({
14129 addHooks: function () {
14130 on(this._map._container, 'wheel', this._onWheelScroll, this);
14135 removeHooks: function () {
14136 off(this._map._container, 'wheel', this._onWheelScroll, this);
14139 _onWheelScroll: function (e) {
14140 var delta = getWheelDelta(e);
14142 var debounce = this._map.options.wheelDebounceTime;
14144 this._delta += delta;
14145 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
14147 if (!this._startTime) {
14148 this._startTime = +new Date();
14151 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
14153 clearTimeout(this._timer);
14154 this._timer = setTimeout(bind(this._performZoom, this), left);
14159 _performZoom: function () {
14160 var map = this._map,
14161 zoom = map.getZoom(),
14162 snap = this._map.options.zoomSnap || 0;
14164 map._stop(); // stop panning and fly animations if any
14166 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
14167 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
14168 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
14169 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
14170 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
14173 this._startTime = null;
14175 if (!delta) { return; }
14177 if (map.options.scrollWheelZoom === 'center') {
14178 map.setZoom(zoom + delta);
14180 map.setZoomAround(this._lastMousePos, zoom + delta);
14185 // @section Handlers
14186 // @property scrollWheelZoom: Handler
14187 // Scroll wheel zoom handler.
14188 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
14191 * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
14192 * which otherwise is not fired by mobile Safari.
14195 var tapHoldDelay = 600;
14198 // @section Interaction Options
14200 // @section Touch interaction options
14201 // @option tapHold: Boolean
14202 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
14203 tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
14205 // @option tapTolerance: Number = 15
14206 // The max number of pixels a user can shift his finger during touch
14207 // for it to be considered a valid tap.
14211 var TapHold = Handler.extend({
14212 addHooks: function () {
14213 on(this._map._container, 'touchstart', this._onDown, this);
14216 removeHooks: function () {
14217 off(this._map._container, 'touchstart', this._onDown, this);
14220 _onDown: function (e) {
14221 clearTimeout(this._holdTimeout);
14222 if (e.touches.length !== 1) { return; }
14224 var first = e.touches[0];
14225 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
14227 this._holdTimeout = setTimeout(bind(function () {
14229 if (!this._isTapValid()) { return; }
14231 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
14232 on(document, 'touchend', preventDefault);
14233 on(document, 'touchend touchcancel', this._cancelClickPrevent);
14234 this._simulateEvent('contextmenu', first);
14235 }, this), tapHoldDelay);
14237 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
14238 on(document, 'touchmove', this._onMove, this);
14241 _cancelClickPrevent: function cancelClickPrevent() {
14242 off(document, 'touchend', preventDefault);
14243 off(document, 'touchend touchcancel', cancelClickPrevent);
14246 _cancel: function () {
14247 clearTimeout(this._holdTimeout);
14248 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
14249 off(document, 'touchmove', this._onMove, this);
14252 _onMove: function (e) {
14253 var first = e.touches[0];
14254 this._newPos = new Point(first.clientX, first.clientY);
14257 _isTapValid: function () {
14258 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
14261 _simulateEvent: function (type, e) {
14262 var simulatedEvent = new MouseEvent(type, {
14267 screenX: e.screenX,
14268 screenY: e.screenY,
14269 clientX: e.clientX,
14270 clientY: e.clientY,
14275 simulatedEvent._simulated = true;
14277 e.target.dispatchEvent(simulatedEvent);
14281 // @section Handlers
14282 // @property tapHold: Handler
14283 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
14284 Map.addInitHook('addHandler', 'tapHold', TapHold);
14287 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
14291 // @section Interaction Options
14293 // @section Touch interaction options
14294 // @option touchZoom: Boolean|String = *
14295 // Whether the map can be zoomed by touch-dragging with two fingers. If
14296 // passed `'center'`, it will zoom to the center of the view regardless of
14297 // where the touch events (fingers) were. Enabled for touch-capable web
14299 touchZoom: Browser.touch,
14301 // @option bounceAtZoomLimits: Boolean = true
14302 // Set it to false if you don't want the map to zoom beyond min/max zoom
14303 // and then bounce back when pinch-zooming.
14304 bounceAtZoomLimits: true
14307 var TouchZoom = Handler.extend({
14308 addHooks: function () {
14309 addClass(this._map._container, 'leaflet-touch-zoom');
14310 on(this._map._container, 'touchstart', this._onTouchStart, this);
14313 removeHooks: function () {
14314 removeClass(this._map._container, 'leaflet-touch-zoom');
14315 off(this._map._container, 'touchstart', this._onTouchStart, this);
14318 _onTouchStart: function (e) {
14319 var map = this._map;
14320 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
14322 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
14323 p2 = map.mouseEventToContainerPoint(e.touches[1]);
14325 this._centerPoint = map.getSize()._divideBy(2);
14326 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
14327 if (map.options.touchZoom !== 'center') {
14328 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
14331 this._startDist = p1.distanceTo(p2);
14332 this._startZoom = map.getZoom();
14334 this._moved = false;
14335 this._zooming = true;
14339 on(document, 'touchmove', this._onTouchMove, this);
14340 on(document, 'touchend touchcancel', this._onTouchEnd, this);
14345 _onTouchMove: function (e) {
14346 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
14348 var map = this._map,
14349 p1 = map.mouseEventToContainerPoint(e.touches[0]),
14350 p2 = map.mouseEventToContainerPoint(e.touches[1]),
14351 scale = p1.distanceTo(p2) / this._startDist;
14353 this._zoom = map.getScaleZoom(scale, this._startZoom);
14355 if (!map.options.bounceAtZoomLimits && (
14356 (this._zoom < map.getMinZoom() && scale < 1) ||
14357 (this._zoom > map.getMaxZoom() && scale > 1))) {
14358 this._zoom = map._limitZoom(this._zoom);
14361 if (map.options.touchZoom === 'center') {
14362 this._center = this._startLatLng;
14363 if (scale === 1) { return; }
14365 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
14366 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
14367 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
14368 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
14371 if (!this._moved) {
14372 map._moveStart(true, false);
14373 this._moved = true;
14376 cancelAnimFrame(this._animRequest);
14378 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
14379 this._animRequest = requestAnimFrame(moveFn, this, true);
14384 _onTouchEnd: function () {
14385 if (!this._moved || !this._zooming) {
14386 this._zooming = false;
14390 this._zooming = false;
14391 cancelAnimFrame(this._animRequest);
14393 off(document, 'touchmove', this._onTouchMove, this);
14394 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14396 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14397 if (this._map.options.zoomAnimation) {
14398 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14400 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14405 // @section Handlers
14406 // @property touchZoom: Handler
14407 // Touch zoom handler.
14408 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14410 Map.BoxZoom = BoxZoom;
14411 Map.DoubleClickZoom = DoubleClickZoom;
14413 Map.Keyboard = Keyboard;
14414 Map.ScrollWheelZoom = ScrollWheelZoom;
14415 Map.TapHold = TapHold;
14416 Map.TouchZoom = TouchZoom;
14418 export { Bounds, Browser, CRS, Canvas, Circle, CircleMarker, Class, Control, DivIcon, DivOverlay, DomEvent, DomUtil, Draggable, Evented, FeatureGroup, GeoJSON, GridLayer, Handler, Icon, ImageOverlay, LatLng, LatLngBounds, Layer, LayerGroup, LineUtil, Map, Marker, Mixin, Path, Point, PolyUtil, Polygon, Polyline, Popup, PosAnimation, index as Projection, Rectangle, Renderer, SVG, SVGOverlay, TileLayer, Tooltip, Transformation, Util, VideoOverlay, bind, toBounds as bounds, canvas, circle, circleMarker, control, divIcon, extend, featureGroup, geoJSON, geoJson, gridLayer, icon, imageOverlay, toLatLng as latLng, toLatLngBounds as latLngBounds, layerGroup, createMap as map, marker, toPoint as point, polygon, polyline, popup, rectangle, setOptions, stamp, svg, svgOverlay, tileLayer, tooltip, toTransformation as transformation, version, videoOverlay };
14419 //# sourceMappingURL=leaflet-src.esm.js.map