vendor: 450,229 bytes, lines 1-14512
1/* @preserve 2 * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com 3 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade 4 */ 5 6(function (global, factory) { 7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 8 typeof define === 'function' && define.amd ? define(['exports'], factory) : 9 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {})); 10})(this, (function (exports) { 'use strict'; 11 12 var version = "1.9.4"; 13 14 /* 15 * @namespace Util 16 * 17 * Various utility functions, used by Leaflet internally. 18 */ 19 20 // @function extend(dest: Object, src?: Object): Object 21 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 22 function extend(dest) { 23 var i, j, len, src; 24 25 for (j = 1, len = arguments.length; j < len; j++) { 26 src = arguments[j]; 27 for (i in src) { 28 dest[i] = src[i]; 29 } 30 } 31 return dest; 32 } 33 34 // @function create(proto: Object, properties?: Object): Object 35 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 36 var create$2 = Object.create || (function () { 37 function F() {} 38 return function (proto) { 39 F.prototype = proto; 40 return new F(); 41 }; 42 })(); 43 44 // @function bind(fn: Function, â¦): Function 45 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). 46 // Has a `L.bind()` shortcut. 47 function bind(fn, obj) { 48 var slice = Array.prototype.slice; 49 50 if (fn.bind) { 51 return fn.bind.apply(fn, slice.call(arguments, 1)); 52 } 53 54 var args = slice.call(arguments, 2); 55 56 return function () { 57 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 58 }; 59 } 60 61 // @property lastId: Number 62 // Last unique ID used by [`stamp()`](#util-stamp) 63 var lastId = 0; 64 65 // @function stamp(obj: Object): Number 66 // Returns the unique ID of an object, assigning it one if it doesn't have it. 67 function stamp(obj) { 68 if (!('_leaflet_id' in obj)) { 69 obj['_leaflet_id'] = ++lastId; 70 } 71 return obj._leaflet_id; 72 } 73 74 // @function throttle(fn: Function, time: Number, context: Object): Function 75 // Returns a function which executes function `fn` with the given scope `context` 76 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function 77 // `fn` will be called no more than one time per given amount of `time`. The arguments 78 // received by the bound function will be any arguments passed when binding the 79 // function, followed by any arguments passed when invoking the bound function. 80 // Has an `L.throttle` shortcut. 81 function throttle(fn, time, context) { 82 var lock, args, wrapperFn, later; 83 84 later = function () { 85 // reset lock and call if queued 86 lock = false; 87 if (args) { 88 wrapperFn.apply(context, args); 89 args = false; 90 } 91 }; 92 93 wrapperFn = function () { 94 if (lock) { 95 // called too soon, queue to call later 96 args = arguments; 97 98 } else { 99 // call and lock until later 100 fn.apply(context, arguments); 101 setTimeout(later, time); 102 lock = true; 103 } 104 }; 105 106 return wrapperFn; 107 } 108 109 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number 110 // Returns the number `num` modulo `range` in such a way so it lies within 111 // `range[0]` and `range[1]`. The returned value will be always smaller than 112 // `range[1]` unless `includeMax` is set to `true`. 113 function wrapNum(x, range, includeMax) { 114 var max = range[1], 115 min = range[0], 116 d = max - min; 117 return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 118 } 119 120 // @function falseFn(): Function 121 // Returns a function which always returns `false`. 122 function falseFn() { return false; } 123 124 // @function formatNum(num: Number, precision?: Number|false): Number 125 // Returns the number `num` rounded with specified `precision`. 126 // The default `precision` value is 6 decimal places. 127 // `false` can be passed to skip any processing (can be useful to avoid round-off errors). 128 function formatNum(num, precision) { 129 if (precision === false) { return num; } 130 var pow = Math.pow(10, precision === undefined ? 6 : precision); 131 return Math.round(num * pow) / pow; 132 } 133 134 // @function trim(str: String): String 135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) 136 function trim(str) { 137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); 138 } 139 140 // @function splitWords(str: String): String[] 141 // Trims and splits the string on whitespace and returns the array of parts. 142 function splitWords(str) { 143 return trim(str).split(/\s+/); 144 } 145 146 // @function setOptions(obj: Object, options: Object): Object 147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. 148 function setOptions(obj, options) { 149 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) { 150 obj.options = obj.options ? create$2(obj.options) : {}; 151 } 152 for (var i in options) { 153 obj.options[i] = options[i]; 154 } 155 return obj.options; 156 } 157 158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String 159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` 160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will 161 // be appended at the end. If `uppercase` is `true`, the parameter names will 162 // be uppercased (e.g. `'?A=foo&B=bar'`) 163 function getParamString(obj, existingUrl, uppercase) { 164 var params = []; 165 for (var i in obj) { 166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); 167 } 168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); 169 } 170 171 var templateRe = /\{ *([\w_ -]+) *\}/g; 172 173 // @function template(str: String, data: Object): String 174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` 175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string 176 // `('Hello foo, bar')`. You can also specify functions instead of strings for 177 // data values â they will be evaluated passing `data` as an argument. 178 function template(str, data) { 179 return str.replace(templateRe, function (str, key) { 180 var value = data[key]; 181 182 if (value === undefined) { 183 throw new Error('No value provided for variable ' + str); 184 185 } else if (typeof value === 'function') { 186 value = value(data); 187 } 188 return value; 189 }); 190 } 191 192 // @function isArray(obj): Boolean 193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) 194 var isArray = Array.isArray || function (obj) { 195 return (Object.prototype.toString.call(obj) === '[object Array]'); 196 }; 197 198 // @function indexOf(array: Array, el: Object): Number 199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) 200 function indexOf(array, el) { 201 for (var i = 0; i < array.length; i++) { 202 if (array[i] === el) { return i; } 203 } 204 return -1; 205 } 206 207 // @property emptyImageUrl: String 208 // Data URI string containing a base64-encoded empty GIF image. 209 // Used as a hack to free memory from unused images on WebKit-powered 210 // mobile devices (by setting image `src` to this string). 211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; 212 213 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/ 214 215 function getPrefixed(name) { 216 return window['webkit' + name] || window['moz' + name] || window['ms' + name]; 217 } 218 219 var lastTime = 0; 220 221 // fallback for IE 7-8 222 function timeoutDefer(fn) { 223 var time = +new Date(), 224 timeToCall = Math.max(0, 16 - (time - lastTime)); 225 226 lastTime = time + timeToCall; 227 return window.setTimeout(fn, timeToCall); 228 } 229 230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; 231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || 232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; 233 234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number 235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to 236 // `context` if given. When `immediate` is set, `fn` is called immediately if 237 // the browser doesn't have native support for 238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), 239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request. 240 function requestAnimFrame(fn, context, immediate) { 241 if (immediate && requestFn === timeoutDefer) { 242 fn.call(context); 243 } else { 244 return requestFn.call(window, bind(fn, context)); 245 } 246 } 247 248 // @function cancelAnimFrame(id: Number): undefined 249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). 250 function cancelAnimFrame(id) { 251 if (id) { 252 cancelFn.call(window, id); 253 } 254 } 255 256 var Util = { 257 __proto__: null, 258 extend: extend, 259 create: create$2, 260 bind: bind, 261 get lastId () { return lastId; }, 262 stamp: stamp, 263 throttle: throttle, 264 wrapNum: wrapNum, 265 falseFn: falseFn, 266 formatNum: formatNum, 267 trim: trim, 268 splitWords: splitWords, 269 setOptions: setOptions, 270 getParamString: getParamString, 271 template: template, 272 isArray: isArray, 273 indexOf: indexOf, 274 emptyImageUrl: emptyImageUrl, 275 requestFn: requestFn, 276 cancelFn: cancelFn, 277 requestAnimFrame: requestAnimFrame, 278 cancelAnimFrame: cancelAnimFrame 279 }; 280 281 // @class Class 282 // @aka L.Class 283 284 // @section 285 // @uninheritable 286 287 // Thanks to John Resig and Dean Edwards for inspiration! 288 289 function Class() {} 290 291 Class.extend = function (props) { 292 293 // @function extend(props: Object): Function 294 // [Extends the current class](#class-inheritance) given the properties to be included. 295 // Returns a Javascript function that is a class constructor (to be called with `new`). 296 var NewClass = function () { 297 298 setOptions(this); 299 300 // call the constructor 301 if (this.initialize) { 302 this.initialize.apply(this, arguments); 303 } 304 305 // call all constructor hooks 306 this.callInitHooks(); 307 }; 308 309 var parentProto = NewClass.__super__ = this.prototype; 310 311 var proto = create$2(parentProto); 312 proto.constructor = NewClass; 313 314 NewClass.prototype = proto; 315 316 // inherit parent's statics 317 for (var i in this) { 318 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') { 319 NewClass[i] = this[i]; 320 } 321 } 322 323 // mix static properties into the class 324 if (props.statics) { 325 extend(NewClass, props.statics); 326 } 327 328 // mix includes into the prototype 329 if (props.includes) { 330 checkDeprecatedMixinEvents(props.includes); 331 extend.apply(null, [proto].concat(props.includes)); 332 } 333 334 // mix given properties into the prototype 335 extend(proto, props); 336 delete proto.statics; 337 delete proto.includes; 338 339 // merge options 340 if (proto.options) { 341 proto.options = parentProto.options ? create$2(parentProto.options) : {}; 342 extend(proto.options, props.options); 343 } 344 345 proto._initHooks = []; 346 347 // add method for calling all hooks 348 proto.callInitHooks = function () { 349 350 if (this._initHooksCalled) { return; } 351 352 if (parentProto.callInitHooks) { 353 parentProto.callInitHooks.call(this); 354 } 355 356 this._initHooksCalled = true; 357 358 for (var i = 0, len = proto._initHooks.length; i < len; i++) { 359 proto._initHooks[i].call(this); 360 } 361 }; 362 363 return NewClass; 364 }; 365 366 367 // @function include(properties: Object): this 368 // [Includes a mixin](#class-includes) into the current class. 369 Class.include = function (props) { 370 var parentOptions = this.prototype.options; 371 extend(this.prototype, props); 372 if (props.options) { 373 this.prototype.options = parentOptions; 374 this.mergeOptions(props.options); 375 } 376 return this; 377 }; 378 379 // @function mergeOptions(options: Object): this 380 // [Merges `options`](#class-options) into the defaults of the class. 381 Class.mergeOptions = function (options) { 382 extend(this.prototype.options, options); 383 return this; 384 }; 385 386 // @function addInitHook(fn: Function): this 387 // Adds a [constructor hook](#class-constructor-hooks) to the class. 388 Class.addInitHook = function (fn) { // (Function) || (String, args...) 389 var args = Array.prototype.slice.call(arguments, 1); 390 391 var init = typeof fn === 'function' ? fn : function () { 392 this[fn].apply(this, args); 393 }; 394 395 this.prototype._initHooks = this.prototype._initHooks || []; 396 this.prototype._initHooks.push(init); 397 return this; 398 }; 399 400 function checkDeprecatedMixinEvents(includes) { 401 /* global L: true */ 402 if (typeof L === 'undefined' || !L || !L.Mixin) { return; } 403 404 includes = isArray(includes) ? includes : [includes]; 405 406 for (var i = 0; i < includes.length; i++) { 407 if (includes[i] === L.Mixin.Events) { 408 console.warn('Deprecated include of L.Mixin.Events: ' + 409 'this property will be removed in future releases, ' + 410 'please inherit from L.Evented instead.', new Error().stack); 411 } 412 } 413 } 414 415 /* 416 * @class Evented 417 * @aka L.Evented 418 * @inherits Class 419 * 420 * 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). 421 * 422 * @example 423 * 424 * ```js 425 * map.on('click', function(e) { 426 * alert(e.latlng); 427 * } ); 428 * ``` 429 * 430 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 431 * 432 * ```js 433 * function onClick(e) { ... } 434 * 435 * map.on('click', onClick); 436 * map.off('click', onClick); 437 * ``` 438 */ 439 440 var Events = { 441 /* @method on(type: String, fn: Function, context?: Object): this 442 * 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'`). 443 * 444 * @alternative 445 * @method on(eventMap: Object): this 446 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 447 */ 448 on: function (types, fn, context) { 449 450 // types can be a map of types/handlers 451 if (typeof types === 'object') { 452 for (var type in types) { 453 // we don't process space-separated events here for performance; 454 // it's a hot path since Layer uses the on(obj) syntax 455 this._on(type, types[type], fn); 456 } 457 458 } else { 459 // types can be a string of space-separated words 460 types = splitWords(types); 461 462 for (var i = 0, len = types.length; i < len; i++) { 463 this._on(types[i], fn, context); 464 } 465 } 466 467 return this; 468 }, 469 470 /* @method off(type: String, fn?: Function, context?: Object): this 471 * 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. 472 * 473 * @alternative 474 * @method off(eventMap: Object): this 475 * Removes a set of type/listener pairs. 476 * 477 * @alternative 478 * @method off: this 479 * Removes all listeners to all events on the object. This includes implicitly attached events. 480 */ 481 off: function (types, fn, context) { 482 483 if (!arguments.length) { 484 // clear all listeners if called without arguments 485 delete this._events; 486 487 } else if (typeof types === 'object') { 488 for (var type in types) { 489 this._off(type, types[type], fn); 490 } 491 492 } else { 493 types = splitWords(types); 494 495 var removeAll = arguments.length === 1; 496 for (var i = 0, len = types.length; i < len; i++) { 497 if (removeAll) { 498 this._off(types[i]); 499 } else { 500 this._off(types[i], fn, context); 501 } 502 } 503 } 504 505 return this; 506 }, 507 508 // attach listener (without syntactic sugar now) 509 _on: function (type, fn, context, _once) { 510 if (typeof fn !== 'function') { 511 console.warn('wrong listener type: ' + typeof fn); 512 return; 513 } 514 515 // check if fn already there 516 if (this._listens(type, fn, context) !== false) { 517 return; 518 } 519 520 if (context === this) { 521 // Less memory footprint. 522 context = undefined; 523 } 524 525 var newListener = {fn: fn, ctx: context}; 526 if (_once) { 527 newListener.once = true; 528 } 529 530 this._events = this._events || {}; 531 this._events[type] = this._events[type] || []; 532 this._events[type].push(newListener); 533 }, 534 535 _off: function (type, fn, context) { 536 var listeners, 537 i, 538 len; 539 540 if (!this._events) { 541 return; 542 } 543 544 listeners = this._events[type]; 545 if (!listeners) { 546 return; 547 } 548 549 if (arguments.length === 1) { // remove all 550 if (this._firingCount) { 551 // Set all removed listeners to noop 552 // so they are not called if remove happens in fire 553 for (i = 0, len = listeners.length; i < len; i++) { 554 listeners[i].fn = falseFn; 555 } 556 } 557 // clear all listeners for a type if function isn't specified 558 delete this._events[type]; 559 return; 560 } 561 562 if (typeof fn !== 'function') { 563 console.warn('wrong listener type: ' + typeof fn); 564 return; 565 } 566 567 // find fn and remove it 568 var index = this._listens(type, fn, context); 569 if (index !== false) { 570 var listener = listeners[index]; 571 if (this._firingCount) { 572 // set the removed listener to noop so that's not called if remove happens in fire 573 listener.fn = falseFn; 574 575 /* copy array in case events are being fired */ 576 this._events[type] = listeners = listeners.slice(); 577 } 578 listeners.splice(index, 1); 579 } 580 }, 581 582 // @method fire(type: String, data?: Object, propagate?: Boolean): this 583 // Fires an event of the specified type. You can optionally provide a data 584 // object â the first argument of the listener function will contain its 585 // properties. The event can optionally be propagated to event parents. 586 fire: function (type, data, propagate) { 587 if (!this.listens(type, propagate)) { return this; } 588 589 var event = extend({}, data, { 590 type: type, 591 target: this, 592 sourceTarget: data && data.sourceTarget || this 593 }); 594 595 if (this._events) { 596 var listeners = this._events[type]; 597 if (listeners) { 598 this._firingCount = (this._firingCount + 1) || 1; 599 for (var i = 0, len = listeners.length; i < len; i++) { 600 var l = listeners[i]; 601 // off overwrites l.fn, so we need to copy fn to a var 602 var fn = l.fn; 603 if (l.once) { 604 this.off(type, fn, l.ctx); 605 } 606 fn.call(l.ctx || this, event); 607 } 608 609 this._firingCount--; 610 } 611 } 612 613 if (propagate) { 614 // propagate the event to parents (set with addEventParent) 615 this._propagateEvent(event); 616 } 617 618 return this; 619 }, 620 621 // @method listens(type: String, propagate?: Boolean): Boolean 622 // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean 623 // Returns `true` if a particular event type has any listeners attached to it. 624 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it. 625 listens: function (type, fn, context, propagate) { 626 if (typeof type !== 'string') { 627 console.warn('"string" type argument expected'); 628 } 629 630 // we don't overwrite the input `fn` value, because we need to use it for propagation 631 var _fn = fn; 632 if (typeof fn !== 'function') { 633 propagate = !!fn; 634 _fn = undefined; 635 context = undefined; 636 } 637 638 var listeners = this._events && this._events[type]; 639 if (listeners && listeners.length) { 640 if (this._listens(type, _fn, context) !== false) { 641 return true; 642 } 643 } 644 645 if (propagate) { 646 // also check parents for listeners if event propagates 647 for (var id in this._eventParents) { 648 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; } 649 } 650 } 651 return false; 652 }, 653 654 // returns the index (number) or false 655 _listens: function (type, fn, context) { 656 if (!this._events) { 657 return false; 658 } 659 660 var listeners = this._events[type] || []; 661 if (!fn) { 662 return !!listeners.length; 663 } 664 665 if (context === this) { 666 // Less memory footprint. 667 context = undefined; 668 } 669 670 for (var i = 0, len = listeners.length; i < len; i++) { 671 if (listeners[i].fn === fn && listeners[i].ctx === context) { 672 return i; 673 } 674 } 675 return false; 676 677 }, 678 679 // @method once(â¦): this 680 // Behaves as [`on(â¦)`](#evented-on), except the listener will only get fired once and then removed. 681 once: function (types, fn, context) { 682 683 // types can be a map of types/handlers 684 if (typeof types === 'object') { 685 for (var type in types) { 686 // we don't process space-separated events here for performance; 687 // it's a hot path since Layer uses the on(obj) syntax 688 this._on(type, types[type], fn, true); 689 } 690 691 } else { 692 // types can be a string of space-separated words 693 types = splitWords(types); 694 695 for (var i = 0, len = types.length; i < len; i++) { 696 this._on(types[i], fn, context, true); 697 } 698 } 699 700 return this; 701 }, 702 703 // @method addEventParent(obj: Evented): this 704 // Adds an event parent - an `Evented` that will receive propagated events 705 addEventParent: function (obj) { 706 this._eventParents = this._eventParents || {}; 707 this._eventParents[stamp(obj)] = obj; 708 return this; 709 }, 710 711 // @method removeEventParent(obj: Evented): this 712 // Removes an event parent, so it will stop receiving propagated events 713 removeEventParent: function (obj) { 714 if (this._eventParents) { 715 delete this._eventParents[stamp(obj)]; 716 } 717 return this; 718 }, 719 720 _propagateEvent: function (e) { 721 for (var id in this._eventParents) { 722 this._eventParents[id].fire(e.type, extend({ 723 layer: e.target, 724 propagatedFrom: e.target 725 }, e), true); 726 } 727 } 728 }; 729 730 // aliases; we should ditch those eventually 731 732 // @method addEventListener(â¦): this 733 // Alias to [`on(â¦)`](#evented-on) 734 Events.addEventListener = Events.on; 735 736 // @method removeEventListener(â¦): this 737 // Alias to [`off(â¦)`](#evented-off) 738 739 // @method clearAllEventListeners(â¦): this 740 // Alias to [`off()`](#evented-off) 741 Events.removeEventListener = Events.clearAllEventListeners = Events.off; 742 743 // @method addOneTimeEventListener(â¦): this 744 // Alias to [`once(â¦)`](#evented-once) 745 Events.addOneTimeEventListener = Events.once; 746 747 // @method fireEvent(â¦): this 748 // Alias to [`fire(â¦)`](#evented-fire) 749 Events.fireEvent = Events.fire; 750 751 // @method hasEventListeners(â¦): Boolean 752 // Alias to [`listens(â¦)`](#evented-listens) 753 Events.hasEventListeners = Events.listens; 754 755 var Evented = Class.extend(Events); 756 757 /* 758 * @class Point 759 * @aka L.Point 760 * 761 * Represents a point with `x` and `y` coordinates in pixels. 762 * 763 * @example 764 * 765 * ```js 766 * var point = L.point(200, 300); 767 * ``` 768 * 769 * 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: 770 * 771 * ```js 772 * map.panBy([200, 300]); 773 * map.panBy(L.point(200, 300)); 774 * ``` 775 * 776 * Note that `Point` does not inherit from Leaflet's `Class` object, 777 * which means new classes can't inherit from it, and new methods 778 * can't be added to it with the `include` function. 779 */ 780 781 function Point(x, y, round) { 782 // @property x: Number; The `x` coordinate of the point 783 this.x = (round ? Math.round(x) : x); 784 // @property y: Number; The `y` coordinate of the point 785 this.y = (round ? Math.round(y) : y); 786 } 787 788 var trunc = Math.trunc || function (v) { 789 return v > 0 ? Math.floor(v) : Math.ceil(v); 790 }; 791 792 Point.prototype = { 793 794 // @method clone(): Point 795 // Returns a copy of the current point. 796 clone: function () { 797 return new Point(this.x, this.y); 798 }, 799 800 // @method add(otherPoint: Point): Point 801 // Returns the result of addition of the current and the given points. 802 add: function (point) { 803 // non-destructive, returns a new point 804 return this.clone()._add(toPoint(point)); 805 }, 806 807 _add: function (point) { 808 // destructive, used directly for performance in situations where it's safe to modify existing point 809 this.x += point.x; 810 this.y += point.y; 811 return this; 812 }, 813 814 // @method subtract(otherPoint: Point): Point 815 // Returns the result of subtraction of the given point from the current. 816 subtract: function (point) { 817 return this.clone()._subtract(toPoint(point)); 818 }, 819 820 _subtract: function (point) { 821 this.x -= point.x; 822 this.y -= point.y; 823 return this; 824 }, 825 826 // @method divideBy(num: Number): Point 827 // Returns the result of division of the current point by the given number. 828 divideBy: function (num) { 829 return this.clone()._divideBy(num); 830 }, 831 832 _divideBy: function (num) { 833 this.x /= num; 834 this.y /= num; 835 return this; 836 }, 837 838 // @method multiplyBy(num: Number): Point 839 // Returns the result of multiplication of the current point by the given number. 840 multiplyBy: function (num) { 841 return this.clone()._multiplyBy(num); 842 }, 843 844 _multiplyBy: function (num) { 845 this.x *= num; 846 this.y *= num; 847 return this; 848 }, 849 850 // @method scaleBy(scale: Point): Point 851 // Multiply each coordinate of the current point by each coordinate of 852 // `scale`. In linear algebra terms, multiply the point by the 853 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 854 // defined by `scale`. 855 scaleBy: function (point) { 856 return new Point(this.x * point.x, this.y * point.y); 857 }, 858 859 // @method unscaleBy(scale: Point): Point 860 // Inverse of `scaleBy`. Divide each coordinate of the current point by 861 // each coordinate of `scale`. 862 unscaleBy: function (point) { 863 return new Point(this.x / point.x, this.y / point.y); 864 }, 865 866 // @method round(): Point 867 // Returns a copy of the current point with rounded coordinates. 868 round: function () { 869 return this.clone()._round(); 870 }, 871 872 _round: function () { 873 this.x = Math.round(this.x); 874 this.y = Math.round(this.y); 875 return this; 876 }, 877 878 // @method floor(): Point 879 // Returns a copy of the current point with floored coordinates (rounded down). 880 floor: function () { 881 return this.clone()._floor(); 882 }, 883 884 _floor: function () { 885 this.x = Math.floor(this.x); 886 this.y = Math.floor(this.y); 887 return this; 888 }, 889 890 // @method ceil(): Point 891 // Returns a copy of the current point with ceiled coordinates (rounded up). 892 ceil: function () { 893 return this.clone()._ceil(); 894 }, 895 896 _ceil: function () { 897 this.x = Math.ceil(this.x); 898 this.y = Math.ceil(this.y); 899 return this; 900 }, 901 902 // @method trunc(): Point 903 // Returns a copy of the current point with truncated coordinates (rounded towards zero). 904 trunc: function () { 905 return this.clone()._trunc(); 906 }, 907 908 _trunc: function () { 909 this.x = trunc(this.x); 910 this.y = trunc(this.y); 911 return this; 912 }, 913 914 // @method distanceTo(otherPoint: Point): Number 915 // Returns the cartesian distance between the current and the given points. 916 distanceTo: function (point) { 917 point = toPoint(point); 918 919 var x = point.x - this.x, 920 y = point.y - this.y; 921 922 return Math.sqrt(x * x + y * y); 923 }, 924 925 // @method equals(otherPoint: Point): Boolean 926 // Returns `true` if the given point has the same coordinates. 927 equals: function (point) { 928 point = toPoint(point); 929 930 return point.x === this.x && 931 point.y === this.y; 932 }, 933 934 // @method contains(otherPoint: Point): Boolean 935 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 936 contains: function (point) { 937 point = toPoint(point); 938 939 return Math.abs(point.x) <= Math.abs(this.x) && 940 Math.abs(point.y) <= Math.abs(this.y); 941 }, 942 943 // @method toString(): String 944 // Returns a string representation of the point for debugging purposes. 945 toString: function () { 946 return 'Point(' + 947 formatNum(this.x) + ', ' + 948 formatNum(this.y) + ')'; 949 } 950 }; 951 952 // @factory L.point(x: Number, y: Number, round?: Boolean) 953 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 954 955 // @alternative 956 // @factory L.point(coords: Number[]) 957 // Expects an array of the form `[x, y]` instead. 958 959 // @alternative 960 // @factory L.point(coords: Object) 961 // Expects a plain object of the form `{x: Number, y: Number}` instead. 962 function toPoint(x, y, round) { 963 if (x instanceof Point) { 964 return x; 965 } 966 if (isArray(x)) { 967 return new Point(x[0], x[1]); 968 } 969 if (x === undefined || x === null) { 970 return x; 971 } 972 if (typeof x === 'object' && 'x' in x && 'y' in x) { 973 return new Point(x.x, x.y); 974 } 975 return new Point(x, y, round); 976 } 977 978 /* 979 * @class Bounds 980 * @aka L.Bounds 981 * 982 * Represents a rectangular area in pixel coordinates. 983 * 984 * @example 985 * 986 * ```js 987 * var p1 = L.point(10, 10), 988 * p2 = L.point(40, 60), 989 * bounds = L.bounds(p1, p2); 990 * ``` 991 * 992 * 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: 993 * 994 * ```js 995 * otherBounds.intersects([[10, 10], [40, 60]]); 996 * ``` 997 * 998 * Note that `Bounds` does not inherit from Leaflet's `Class` object, 999 * which means new classes can't inherit from it, and new methods 1000 * can't be added to it with the `include` function. 1001 */ 1002 1003 function Bounds(a, b) { 1004 if (!a) { return; } 1005 1006 var points = b ? [a, b] : a; 1007 1008 for (var i = 0, len = points.length; i < len; i++) { 1009 this.extend(points[i]); 1010 } 1011 } 1012 1013 Bounds.prototype = { 1014 // @method extend(point: Point): this 1015 // Extends the bounds to contain the given point. 1016 1017 // @alternative 1018 // @method extend(otherBounds: Bounds): this 1019 // Extend the bounds to contain the given bounds 1020 extend: function (obj) { 1021 var min2, max2; 1022 if (!obj) { return this; } 1023 1024 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) { 1025 min2 = max2 = toPoint(obj); 1026 } else { 1027 obj = toBounds(obj); 1028 min2 = obj.min; 1029 max2 = obj.max; 1030 1031 if (!min2 || !max2) { return this; } 1032 } 1033 1034 // @property min: Point 1035 // The top left corner of the rectangle. 1036 // @property max: Point 1037 // The bottom right corner of the rectangle. 1038 if (!this.min && !this.max) { 1039 this.min = min2.clone(); 1040 this.max = max2.clone(); 1041 } else { 1042 this.min.x = Math.min(min2.x, this.min.x); 1043 this.max.x = Math.max(max2.x, this.max.x); 1044 this.min.y = Math.min(min2.y, this.min.y); 1045 this.max.y = Math.max(max2.y, this.max.y); 1046 } 1047 return this; 1048 }, 1049 1050 // @method getCenter(round?: Boolean): Point 1051 // Returns the center point of the bounds. 1052 getCenter: function (round) { 1053 return toPoint( 1054 (this.min.x + this.max.x) / 2, 1055 (this.min.y + this.max.y) / 2, round); 1056 }, 1057 1058 // @method getBottomLeft(): Point 1059 // Returns the bottom-left point of the bounds. 1060 getBottomLeft: function () { 1061 return toPoint(this.min.x, this.max.y); 1062 }, 1063 1064 // @method getTopRight(): Point 1065 // Returns the top-right point of the bounds. 1066 getTopRight: function () { // -> Point 1067 return toPoint(this.max.x, this.min.y); 1068 }, 1069 1070 // @method getTopLeft(): Point 1071 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 1072 getTopLeft: function () { 1073 return this.min; // left, top 1074 }, 1075 1076 // @method getBottomRight(): Point 1077 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 1078 getBottomRight: function () { 1079 return this.max; // right, bottom 1080 }, 1081 1082 // @method getSize(): Point 1083 // Returns the size of the given bounds 1084 getSize: function () { 1085 return this.max.subtract(this.min); 1086 }, 1087 1088 // @method contains(otherBounds: Bounds): Boolean 1089 // Returns `true` if the rectangle contains the given one. 1090 // @alternative 1091 // @method contains(point: Point): Boolean 1092 // Returns `true` if the rectangle contains the given point. 1093 contains: function (obj) { 1094 var min, max; 1095 1096 if (typeof obj[0] === 'number' || obj instanceof Point) { 1097 obj = toPoint(obj); 1098 } else { 1099 obj = toBounds(obj); 1100 } 1101 1102 if (obj instanceof Bounds) { 1103 min = obj.min; 1104 max = obj.max; 1105 } else { 1106 min = max = obj; 1107 } 1108 1109 return (min.x >= this.min.x) && 1110 (max.x <= this.max.x) && 1111 (min.y >= this.min.y) && 1112 (max.y <= this.max.y); 1113 }, 1114 1115 // @method intersects(otherBounds: Bounds): Boolean 1116 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1117 // intersect if they have at least one point in common. 1118 intersects: function (bounds) { // (Bounds) -> Boolean 1119 bounds = toBounds(bounds); 1120 1121 var min = this.min, 1122 max = this.max, 1123 min2 = bounds.min, 1124 max2 = bounds.max, 1125 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1126 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1127 1128 return xIntersects && yIntersects; 1129 }, 1130 1131 // @method overlaps(otherBounds: Bounds): Boolean 1132 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1133 // overlap if their intersection is an area. 1134 overlaps: function (bounds) { // (Bounds) -> Boolean 1135 bounds = toBounds(bounds); 1136 1137 var min = this.min, 1138 max = this.max, 1139 min2 = bounds.min, 1140 max2 = bounds.max, 1141 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1142 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1143 1144 return xOverlaps && yOverlaps; 1145 }, 1146 1147 // @method isValid(): Boolean 1148 // Returns `true` if the bounds are properly initialized. 1149 isValid: function () { 1150 return !!(this.min && this.max); 1151 }, 1152 1153 1154 // @method pad(bufferRatio: Number): Bounds 1155 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1156 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1157 // Negative values will retract the bounds. 1158 pad: function (bufferRatio) { 1159 var min = this.min, 1160 max = this.max, 1161 heightBuffer = Math.abs(min.x - max.x) * bufferRatio, 1162 widthBuffer = Math.abs(min.y - max.y) * bufferRatio; 1163 1164 1165 return toBounds( 1166 toPoint(min.x - heightBuffer, min.y - widthBuffer), 1167 toPoint(max.x + heightBuffer, max.y + widthBuffer)); 1168 }, 1169 1170 1171 // @method equals(otherBounds: Bounds): Boolean 1172 // Returns `true` if the rectangle is equivalent to the given bounds. 1173 equals: function (bounds) { 1174 if (!bounds) { return false; } 1175 1176 bounds = toBounds(bounds); 1177 1178 return this.min.equals(bounds.getTopLeft()) && 1179 this.max.equals(bounds.getBottomRight()); 1180 }, 1181 }; 1182 1183 1184 // @factory L.bounds(corner1: Point, corner2: Point) 1185 // Creates a Bounds object from two corners coordinate pairs. 1186 // @alternative 1187 // @factory L.bounds(points: Point[]) 1188 // Creates a Bounds object from the given array of points. 1189 function toBounds(a, b) { 1190 if (!a || a instanceof Bounds) { 1191 return a; 1192 } 1193 return new Bounds(a, b); 1194 } 1195 1196 /* 1197 * @class LatLngBounds 1198 * @aka L.LatLngBounds 1199 * 1200 * Represents a rectangular geographical area on a map. 1201 * 1202 * @example 1203 * 1204 * ```js 1205 * var corner1 = L.latLng(40.712, -74.227), 1206 * corner2 = L.latLng(40.774, -74.125), 1207 * bounds = L.latLngBounds(corner1, corner2); 1208 * ``` 1209 * 1210 * 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: 1211 * 1212 * ```js 1213 * map.fitBounds([ 1214 * [40.712, -74.227], 1215 * [40.774, -74.125] 1216 * ]); 1217 * ``` 1218 * 1219 * 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. 1220 * 1221 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object, 1222 * which means new classes can't inherit from it, and new methods 1223 * can't be added to it with the `include` function. 1224 */ 1225 1226 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1227 if (!corner1) { return; } 1228 1229 var latlngs = corner2 ? [corner1, corner2] : corner1; 1230 1231 for (var i = 0, len = latlngs.length; i < len; i++) { 1232 this.extend(latlngs[i]); 1233 } 1234 } 1235 1236 LatLngBounds.prototype = { 1237 1238 // @method extend(latlng: LatLng): this 1239 // Extend the bounds to contain the given point 1240 1241 // @alternative 1242 // @method extend(otherBounds: LatLngBounds): this 1243 // Extend the bounds to contain the given bounds 1244 extend: function (obj) { 1245 var sw = this._southWest, 1246 ne = this._northEast, 1247 sw2, ne2; 1248 1249 if (obj instanceof LatLng) { 1250 sw2 = obj; 1251 ne2 = obj; 1252 1253 } else if (obj instanceof LatLngBounds) { 1254 sw2 = obj._southWest; 1255 ne2 = obj._northEast; 1256 1257 if (!sw2 || !ne2) { return this; } 1258 1259 } else { 1260 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 1261 } 1262 1263 if (!sw && !ne) { 1264 this._southWest = new LatLng(sw2.lat, sw2.lng); 1265 this._northEast = new LatLng(ne2.lat, ne2.lng); 1266 } else { 1267 sw.lat = Math.min(sw2.lat, sw.lat); 1268 sw.lng = Math.min(sw2.lng, sw.lng); 1269 ne.lat = Math.max(ne2.lat, ne.lat); 1270 ne.lng = Math.max(ne2.lng, ne.lng); 1271 } 1272 1273 return this; 1274 }, 1275 1276 // @method pad(bufferRatio: Number): LatLngBounds 1277 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1278 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1279 // Negative values will retract the bounds. 1280 pad: function (bufferRatio) { 1281 var sw = this._southWest, 1282 ne = this._northEast, 1283 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1284 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1285 1286 return new LatLngBounds( 1287 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1288 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1289 }, 1290 1291 // @method getCenter(): LatLng 1292 // Returns the center point of the bounds. 1293 getCenter: function () { 1294 return new LatLng( 1295 (this._southWest.lat + this._northEast.lat) / 2, 1296 (this._southWest.lng + this._northEast.lng) / 2); 1297 }, 1298 1299 // @method getSouthWest(): LatLng 1300 // Returns the south-west point of the bounds. 1301 getSouthWest: function () { 1302 return this._southWest; 1303 }, 1304 1305 // @method getNorthEast(): LatLng 1306 // Returns the north-east point of the bounds. 1307 getNorthEast: function () { 1308 return this._northEast; 1309 }, 1310 1311 // @method getNorthWest(): LatLng 1312 // Returns the north-west point of the bounds. 1313 getNorthWest: function () { 1314 return new LatLng(this.getNorth(), this.getWest()); 1315 }, 1316 1317 // @method getSouthEast(): LatLng 1318 // Returns the south-east point of the bounds. 1319 getSouthEast: function () { 1320 return new LatLng(this.getSouth(), this.getEast()); 1321 }, 1322 1323 // @method getWest(): Number 1324 // Returns the west longitude of the bounds 1325 getWest: function () { 1326 return this._southWest.lng; 1327 }, 1328 1329 // @method getSouth(): Number 1330 // Returns the south latitude of the bounds 1331 getSouth: function () { 1332 return this._southWest.lat; 1333 }, 1334 1335 // @method getEast(): Number 1336 // Returns the east longitude of the bounds 1337 getEast: function () { 1338 return this._northEast.lng; 1339 }, 1340 1341 // @method getNorth(): Number 1342 // Returns the north latitude of the bounds 1343 getNorth: function () { 1344 return this._northEast.lat; 1345 }, 1346 1347 // @method contains(otherBounds: LatLngBounds): Boolean 1348 // Returns `true` if the rectangle contains the given one. 1349 1350 // @alternative 1351 // @method contains (latlng: LatLng): Boolean 1352 // Returns `true` if the rectangle contains the given point. 1353 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1354 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { 1355 obj = toLatLng(obj); 1356 } else { 1357 obj = toLatLngBounds(obj); 1358 } 1359 1360 var sw = this._southWest, 1361 ne = this._northEast, 1362 sw2, ne2; 1363 1364 if (obj instanceof LatLngBounds) { 1365 sw2 = obj.getSouthWest(); 1366 ne2 = obj.getNorthEast(); 1367 } else { 1368 sw2 = ne2 = obj; 1369 } 1370 1371 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1372 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1373 }, 1374 1375 // @method intersects(otherBounds: LatLngBounds): Boolean 1376 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1377 intersects: function (bounds) { 1378 bounds = toLatLngBounds(bounds); 1379 1380 var sw = this._southWest, 1381 ne = this._northEast, 1382 sw2 = bounds.getSouthWest(), 1383 ne2 = bounds.getNorthEast(), 1384 1385 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1386 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1387 1388 return latIntersects && lngIntersects; 1389 }, 1390 1391 // @method overlaps(otherBounds: LatLngBounds): Boolean 1392 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1393 overlaps: function (bounds) { 1394 bounds = toLatLngBounds(bounds); 1395 1396 var sw = this._southWest, 1397 ne = this._northEast, 1398 sw2 = bounds.getSouthWest(), 1399 ne2 = bounds.getNorthEast(), 1400 1401 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1402 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1403 1404 return latOverlaps && lngOverlaps; 1405 }, 1406 1407 // @method toBBoxString(): String 1408 // 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. 1409 toBBoxString: function () { 1410 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1411 }, 1412 1413 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 1414 // 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. 1415 equals: function (bounds, maxMargin) { 1416 if (!bounds) { return false; } 1417 1418 bounds = toLatLngBounds(bounds); 1419 1420 return this._southWest.equals(bounds.getSouthWest(), maxMargin) && 1421 this._northEast.equals(bounds.getNorthEast(), maxMargin); 1422 }, 1423 1424 // @method isValid(): Boolean 1425 // Returns `true` if the bounds are properly initialized. 1426 isValid: function () { 1427 return !!(this._southWest && this._northEast); 1428 } 1429 }; 1430 1431 // TODO International date line? 1432 1433 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1434 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1435 1436 // @alternative 1437 // @factory L.latLngBounds(latlngs: LatLng[]) 1438 // 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). 1439 function toLatLngBounds(a, b) { 1440 if (a instanceof LatLngBounds) { 1441 return a; 1442 } 1443 return new LatLngBounds(a, b); 1444 } 1445 1446 /* @class LatLng 1447 * @aka L.LatLng 1448 * 1449 * Represents a geographical point with a certain latitude and longitude. 1450 * 1451 * @example 1452 * 1453 * ``` 1454 * var latlng = L.latLng(50.5, 30.5); 1455 * ``` 1456 * 1457 * 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: 1458 * 1459 * ``` 1460 * map.panTo([50, 30]); 1461 * map.panTo({lon: 30, lat: 50}); 1462 * map.panTo({lat: 50, lng: 30}); 1463 * map.panTo(L.latLng(50, 30)); 1464 * ``` 1465 * 1466 * Note that `LatLng` does not inherit from Leaflet's `Class` object, 1467 * which means new classes can't inherit from it, and new methods 1468 * can't be added to it with the `include` function. 1469 */ 1470 1471 function LatLng(lat, lng, alt) { 1472 if (isNaN(lat) || isNaN(lng)) { 1473 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1474 } 1475 1476 // @property lat: Number 1477 // Latitude in degrees 1478 this.lat = +lat; 1479 1480 // @property lng: Number 1481 // Longitude in degrees 1482 this.lng = +lng; 1483 1484 // @property alt: Number 1485 // Altitude in meters (optional) 1486 if (alt !== undefined) { 1487 this.alt = +alt; 1488 } 1489 } 1490 1491 LatLng.prototype = { 1492 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1493 // 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. 1494 equals: function (obj, maxMargin) { 1495 if (!obj) { return false; } 1496 1497 obj = toLatLng(obj); 1498 1499 var margin = Math.max( 1500 Math.abs(this.lat - obj.lat), 1501 Math.abs(this.lng - obj.lng)); 1502 1503 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1504 }, 1505 1506 // @method toString(): String 1507 // Returns a string representation of the point (for debugging purposes). 1508 toString: function (precision) { 1509 return 'LatLng(' + 1510 formatNum(this.lat, precision) + ', ' + 1511 formatNum(this.lng, precision) + ')'; 1512 }, 1513 1514 // @method distanceTo(otherLatLng: LatLng): Number 1515 // 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). 1516 distanceTo: function (other) { 1517 return Earth.distance(this, toLatLng(other)); 1518 }, 1519 1520 // @method wrap(): LatLng 1521 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1522 wrap: function () { 1523 return Earth.wrapLatLng(this); 1524 }, 1525 1526 // @method toBounds(sizeInMeters: Number): LatLngBounds 1527 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1528 toBounds: function (sizeInMeters) { 1529 var latAccuracy = 180 * sizeInMeters / 40075017, 1530 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1531 1532 return toLatLngBounds( 1533 [this.lat - latAccuracy, this.lng - lngAccuracy], 1534 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1535 }, 1536 1537 clone: function () { 1538 return new LatLng(this.lat, this.lng, this.alt); 1539 } 1540 }; 1541 1542 1543 1544 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1545 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1546 1547 // @alternative 1548 // @factory L.latLng(coords: Array): LatLng 1549 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1550 1551 // @alternative 1552 // @factory L.latLng(coords: Object): LatLng 1553 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1554 1555 function toLatLng(a, b, c) { 1556 if (a instanceof LatLng) { 1557 return a; 1558 } 1559 if (isArray(a) && typeof a[0] !== 'object') { 1560 if (a.length === 3) { 1561 return new LatLng(a[0], a[1], a[2]); 1562 } 1563 if (a.length === 2) { 1564 return new LatLng(a[0], a[1]); 1565 } 1566 return null; 1567 } 1568 if (a === undefined || a === null) { 1569 return a; 1570 } 1571 if (typeof a === 'object' && 'lat' in a) { 1572 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1573 } 1574 if (b === undefined) { 1575 return null; 1576 } 1577 return new LatLng(a, b, c); 1578 } 1579 1580 /* 1581 * @namespace CRS 1582 * @crs L.CRS.Base 1583 * Object that defines coordinate reference systems for projecting 1584 * geographical points into pixel (screen) coordinates and back (and to 1585 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 1586 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system). 1587 * 1588 * Leaflet defines the most usual CRSs by default. If you want to use a 1589 * CRS not defined by default, take a look at the 1590 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 1591 * 1592 * Note that the CRS instances do not inherit from Leaflet's `Class` object, 1593 * and can't be instantiated. Also, new classes can't inherit from them, 1594 * and methods can't be added to them with the `include` function. 1595 */ 1596 1597 var CRS = { 1598 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1599 // Projects geographical coordinates into pixel coordinates for a given zoom. 1600 latLngToPoint: function (latlng, zoom) { 1601 var projectedPoint = this.projection.project(latlng), 1602 scale = this.scale(zoom); 1603 1604 return this.transformation._transform(projectedPoint, scale); 1605 }, 1606 1607 // @method pointToLatLng(point: Point, zoom: Number): LatLng 1608 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1609 // zoom into geographical coordinates. 1610 pointToLatLng: function (point, zoom) { 1611 var scale = this.scale(zoom), 1612 untransformedPoint = this.transformation.untransform(point, scale); 1613 1614 return this.projection.unproject(untransformedPoint); 1615 }, 1616 1617 // @method project(latlng: LatLng): Point 1618 // Projects geographical coordinates into coordinates in units accepted for 1619 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1620 project: function (latlng) { 1621 return this.projection.project(latlng); 1622 }, 1623 1624 // @method unproject(point: Point): LatLng 1625 // Given a projected coordinate returns the corresponding LatLng. 1626 // The inverse of `project`. 1627 unproject: function (point) { 1628 return this.projection.unproject(point); 1629 }, 1630 1631 // @method scale(zoom: Number): Number 1632 // Returns the scale used when transforming projected coordinates into 1633 // pixel coordinates for a particular zoom. For example, it returns 1634 // `256 * 2^zoom` for Mercator-based CRS. 1635 scale: function (zoom) { 1636 return 256 * Math.pow(2, zoom); 1637 }, 1638 1639 // @method zoom(scale: Number): Number 1640 // Inverse of `scale()`, returns the zoom level corresponding to a scale 1641 // factor of `scale`. 1642 zoom: function (scale) { 1643 return Math.log(scale / 256) / Math.LN2; 1644 }, 1645 1646 // @method getProjectedBounds(zoom: Number): Bounds 1647 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1648 getProjectedBounds: function (zoom) { 1649 if (this.infinite) { return null; } 1650 1651 var b = this.projection.bounds, 1652 s = this.scale(zoom), 1653 min = this.transformation.transform(b.min, s), 1654 max = this.transformation.transform(b.max, s); 1655 1656 return new Bounds(min, max); 1657 }, 1658 1659 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1660 // Returns the distance between two geographical coordinates. 1661 1662 // @property code: String 1663 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1664 // 1665 // @property wrapLng: Number[] 1666 // An array of two numbers defining whether the longitude (horizontal) coordinate 1667 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1668 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1669 // 1670 // @property wrapLat: Number[] 1671 // Like `wrapLng`, but for the latitude (vertical) axis. 1672 1673 // wrapLng: [min, max], 1674 // wrapLat: [min, max], 1675 1676 // @property infinite: Boolean 1677 // If true, the coordinate space will be unbounded (infinite in both axes) 1678 infinite: false, 1679 1680 // @method wrapLatLng(latlng: LatLng): LatLng 1681 // Returns a `LatLng` where lat and lng has been wrapped according to the 1682 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1683 wrapLatLng: function (latlng) { 1684 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 1685 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 1686 alt = latlng.alt; 1687 1688 return new LatLng(lat, lng, alt); 1689 }, 1690 1691 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1692 // Returns a `LatLngBounds` with the same size as the given one, ensuring 1693 // that its center is within the CRS's bounds. 1694 // Only accepts actual `L.LatLngBounds` instances, not arrays. 1695 wrapLatLngBounds: function (bounds) { 1696 var center = bounds.getCenter(), 1697 newCenter = this.wrapLatLng(center), 1698 latShift = center.lat - newCenter.lat, 1699 lngShift = center.lng - newCenter.lng; 1700 1701 if (latShift === 0 && lngShift === 0) { 1702 return bounds; 1703 } 1704 1705 var sw = bounds.getSouthWest(), 1706 ne = bounds.getNorthEast(), 1707 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), 1708 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1709 1710 return new LatLngBounds(newSw, newNe); 1711 } 1712 }; 1713 1714 /* 1715 * @namespace CRS 1716 * @crs L.CRS.Earth 1717 * 1718 * Serves as the base for CRS that are global such that they cover the earth. 1719 * Can only be used as the base for other CRS and cannot be used directly, 1720 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 1721 * meters. 1722 */ 1723 1724 var Earth = extend({}, CRS, { 1725 wrapLng: [-180, 180], 1726 1727 // Mean Earth Radius, as recommended for use by 1728 // the International Union of Geodesy and Geophysics, 1729 // see https://rosettacode.org/wiki/Haversine_formula 1730 R: 6371000, 1731 1732 // distance between two geographical points using spherical law of cosines approximation 1733 distance: function (latlng1, latlng2) { 1734 var rad = Math.PI / 180, 1735 lat1 = latlng1.lat * rad, 1736 lat2 = latlng2.lat * rad, 1737 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), 1738 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), 1739 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, 1740 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1741 return this.R * c; 1742 } 1743 }); 1744 1745 /* 1746 * @namespace Projection 1747 * @projection L.Projection.SphericalMercator 1748 * 1749 * Spherical Mercator projection â the most common projection for online maps, 1750 * used by almost all free and commercial tile providers. Assumes that Earth is 1751 * a sphere. Used by the `EPSG:3857` CRS. 1752 */ 1753 1754 var earthRadius = 6378137; 1755 1756 var SphericalMercator = { 1757 1758 R: earthRadius, 1759 MAX_LATITUDE: 85.0511287798, 1760 1761 project: function (latlng) { 1762 var d = Math.PI / 180, 1763 max = this.MAX_LATITUDE, 1764 lat = Math.max(Math.min(max, latlng.lat), -max), 1765 sin = Math.sin(lat * d); 1766 1767 return new Point( 1768 this.R * latlng.lng * d, 1769 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 1770 }, 1771 1772 unproject: function (point) { 1773 var d = 180 / Math.PI; 1774 1775 return new LatLng( 1776 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 1777 point.x * d / this.R); 1778 }, 1779 1780 bounds: (function () { 1781 var d = earthRadius * Math.PI; 1782 return new Bounds([-d, -d], [d, d]); 1783 })() 1784 }; 1785 1786 /* 1787 * @class Transformation 1788 * @aka L.Transformation 1789 * 1790 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1791 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1792 * the reverse. Used by Leaflet in its projections code. 1793 * 1794 * @example 1795 * 1796 * ```js 1797 * var transformation = L.transformation(2, 5, -1, 10), 1798 * p = L.point(1, 2), 1799 * p2 = transformation.transform(p), // L.point(7, 8) 1800 * p3 = transformation.untransform(p2); // L.point(1, 2) 1801 * ``` 1802 */ 1803 1804 1805 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1806 // Creates a `Transformation` object with the given coefficients. 1807 function Transformation(a, b, c, d) { 1808 if (isArray(a)) { 1809 // use array properties 1810 this._a = a[0]; 1811 this._b = a[1]; 1812 this._c = a[2]; 1813 this._d = a[3]; 1814 return; 1815 } 1816 this._a = a; 1817 this._b = b; 1818 this._c = c; 1819 this._d = d; 1820 } 1821 1822 Transformation.prototype = { 1823 // @method transform(point: Point, scale?: Number): Point 1824 // Returns a transformed point, optionally multiplied by the given scale. 1825 // Only accepts actual `L.Point` instances, not arrays. 1826 transform: function (point, scale) { // (Point, Number) -> Point 1827 return this._transform(point.clone(), scale); 1828 }, 1829 1830 // destructive transform (faster) 1831 _transform: function (point, scale) { 1832 scale = scale || 1; 1833 point.x = scale * (this._a * point.x + this._b); 1834 point.y = scale * (this._c * point.y + this._d); 1835 return point; 1836 }, 1837 1838 // @method untransform(point: Point, scale?: Number): Point 1839 // Returns the reverse transformation of the given point, optionally divided 1840 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1841 untransform: function (point, scale) { 1842 scale = scale || 1; 1843 return new Point( 1844 (point.x / scale - this._b) / this._a, 1845 (point.y / scale - this._d) / this._c); 1846 } 1847 }; 1848 1849 // factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1850 1851 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1852 // Instantiates a Transformation object with the given coefficients. 1853 1854 // @alternative 1855 // @factory L.transformation(coefficients: Array): Transformation 1856 // Expects an coefficients array of the form 1857 // `[a: Number, b: Number, c: Number, d: Number]`. 1858 1859 function toTransformation(a, b, c, d) { 1860 return new Transformation(a, b, c, d); 1861 } 1862 1863 /* 1864 * @namespace CRS 1865 * @crs L.CRS.EPSG3857 1866 * 1867 * The most common CRS for online maps, used by almost all free and commercial 1868 * tile providers. Uses Spherical Mercator projection. Set in by default in 1869 * Map's `crs` option. 1870 */ 1871 1872 var EPSG3857 = extend({}, Earth, { 1873 code: 'EPSG:3857', 1874 projection: SphericalMercator, 1875 1876 transformation: (function () { 1877 var scale = 0.5 / (Math.PI * SphericalMercator.R); 1878 return toTransformation(scale, 0.5, -scale, 0.5); 1879 }()) 1880 }); 1881 1882 var EPSG900913 = extend({}, EPSG3857, { 1883 code: 'EPSG:900913' 1884 }); 1885 1886 // @namespace SVG; @section 1887 // There are several static functions which can be called without instantiating L.SVG: 1888 1889 // @function create(name: String): SVGElement 1890 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 1891 // corresponding to the class name passed. For example, using 'line' will return 1892 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 1893 function svgCreate(name) { 1894 return document.createElementNS('http://www.w3.org/2000/svg', name); 1895 } 1896 1897 // @function pointsToPath(rings: Point[], closed: Boolean): String 1898 // Generates a SVG path string for multiple rings, with each ring turning 1899 // into "M..L..L.." instructions 1900 function pointsToPath(rings, closed) { 1901 var str = '', 1902 i, j, len, len2, points, p; 1903 1904 for (i = 0, len = rings.length; i < len; i++) { 1905 points = rings[i]; 1906 1907 for (j = 0, len2 = points.length; j < len2; j++) { 1908 p = points[j]; 1909 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 1910 } 1911 1912 // closes the ring for polygons; "x" is VML syntax 1913 str += closed ? (Browser.svg ? 'z' : 'x') : ''; 1914 } 1915 1916 // SVG complains about empty path strings 1917 return str || 'M0 0'; 1918 } 1919 1920 /* 1921 * @namespace Browser 1922 * @aka L.Browser 1923 * 1924 * A namespace with static properties for browser/feature detection used by Leaflet internally. 1925 * 1926 * @example 1927 * 1928 * ```js 1929 * if (L.Browser.ielt9) { 1930 * alert('Upgrade your browser, dude!'); 1931 * } 1932 * ``` 1933 */ 1934 1935 var style = document.documentElement.style; 1936 1937 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 1938 var ie = 'ActiveXObject' in window; 1939 1940 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 1941 var ielt9 = ie && !document.addEventListener; 1942 1943 // @property edge: Boolean; `true` for the Edge web browser. 1944 var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 1945 1946 // @property webkit: Boolean; 1947 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 1948 var webkit = userAgentContains('webkit'); 1949 1950 // @property android: Boolean 1951 // **Deprecated.** `true` for any browser running on an Android platform. 1952 var android = userAgentContains('android'); 1953 1954 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3. 1955 var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 1956 1957 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 1958 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 1959 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome) 1960 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 1961 1962 // @property opera: Boolean; `true` for the Opera browser 1963 var opera = !!window.opera; 1964 1965 // @property chrome: Boolean; `true` for the Chrome browser. 1966 var chrome = !edge && userAgentContains('chrome'); 1967 1968 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 1969 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 1970 1971 // @property safari: Boolean; `true` for the Safari browser. 1972 var safari = !chrome && userAgentContains('safari'); 1973 1974 var phantom = userAgentContains('phantom'); 1975 1976 // @property opera12: Boolean 1977 // `true` for the Opera browser supporting CSS transforms (version 12 or later). 1978 var opera12 = 'OTransition' in style; 1979 1980 // @property win: Boolean; `true` when the browser is running in a Windows platform 1981 var win = navigator.platform.indexOf('Win') === 0; 1982 1983 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. 1984 var ie3d = ie && ('transition' in style); 1985 1986 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 1987 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 1988 1989 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 1990 var gecko3d = 'MozPerspective' in style; 1991 1992 // @property any3d: Boolean 1993 // `true` for all browsers supporting CSS transforms. 1994 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1995 1996 // @property mobile: Boolean; `true` for all browsers running in a mobile device. 1997 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 1998 1999 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 2000 var mobileWebkit = mobile && webkit; 2001 2002 // @property mobileWebkit3d: Boolean 2003 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 2004 var mobileWebkit3d = mobile && webkit3d; 2005 2006 // @property msPointer: Boolean 2007 // `true` for browsers implementing the Microsoft touch events model (notably IE10). 2008 var msPointer = !window.PointerEvent && window.MSPointerEvent; 2009 2010 // @property pointer: Boolean 2011 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 2012 var pointer = !!(window.PointerEvent || msPointer); 2013 2014 // @property touchNative: Boolean 2015 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 2016 // **This does not necessarily mean** that the browser is running in a computer with 2017 // a touchscreen, it only means that the browser is capable of understanding 2018 // touch events. 2019 var touchNative = 'ontouchstart' in window || !!window.TouchEvent; 2020 2021 // @property touch: Boolean 2022 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events. 2023 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners. 2024 var touch = !window.L_NO_TOUCH && (touchNative || pointer); 2025 2026 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 2027 var mobileOpera = mobile && opera; 2028 2029 // @property mobileGecko: Boolean 2030 // `true` for gecko-based browsers running in a mobile device. 2031 var mobileGecko = mobile && gecko; 2032 2033 // @property retina: Boolean 2034 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 2035 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 2036 2037 // @property passiveEvents: Boolean 2038 // `true` for browsers that support passive events. 2039 var passiveEvents = (function () { 2040 var supportsPassiveOption = false; 2041 try { 2042 var opts = Object.defineProperty({}, 'passive', { 2043 get: function () { // eslint-disable-line getter-return 2044 supportsPassiveOption = true; 2045 } 2046 }); 2047 window.addEventListener('testPassiveEventSupport', falseFn, opts); 2048 window.removeEventListener('testPassiveEventSupport', falseFn, opts); 2049 } catch (e) { 2050 // Errors can safely be ignored since this is only a browser support test. 2051 } 2052 return supportsPassiveOption; 2053 }()); 2054 2055 // @property canvas: Boolean 2056 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 2057 var canvas$1 = (function () { 2058 return !!document.createElement('canvas').getContext; 2059 }()); 2060 2061 // @property svg: Boolean 2062 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 2063 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect); 2064 2065 var inlineSvg = !!svg$1 && (function () { 2066 var div = document.createElement('div'); 2067 div.innerHTML = '<svg/>'; 2068 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg'; 2069 })(); 2070 2071 // @property vml: Boolean 2072 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 2073 var vml = !svg$1 && (function () { 2074 try { 2075 var div = document.createElement('div'); 2076 div.innerHTML = '<v:shape adj="1"/>'; 2077 2078 var shape = div.firstChild; 2079 shape.style.behavior = 'url(#default#VML)'; 2080 2081 return shape && (typeof shape.adj === 'object'); 2082 2083 } catch (e) { 2084 return false; 2085 } 2086 }()); 2087 2088 2089 // @property mac: Boolean; `true` when the browser is running in a Mac platform 2090 var mac = navigator.platform.indexOf('Mac') === 0; 2091 2092 // @property mac: Boolean; `true` when the browser is running in a Linux platform 2093 var linux = navigator.platform.indexOf('Linux') === 0; 2094 2095 function userAgentContains(str) { 2096 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 2097 } 2098 2099 2100 var Browser = { 2101 ie: ie, 2102 ielt9: ielt9, 2103 edge: edge, 2104 webkit: webkit, 2105 android: android, 2106 android23: android23, 2107 androidStock: androidStock, 2108 opera: opera, 2109 chrome: chrome, 2110 gecko: gecko, 2111 safari: safari, 2112 phantom: phantom, 2113 opera12: opera12, 2114 win: win, 2115 ie3d: ie3d, 2116 webkit3d: webkit3d, 2117 gecko3d: gecko3d, 2118 any3d: any3d, 2119 mobile: mobile, 2120 mobileWebkit: mobileWebkit, 2121 mobileWebkit3d: mobileWebkit3d, 2122 msPointer: msPointer, 2123 pointer: pointer, 2124 touch: touch, 2125 touchNative: touchNative, 2126 mobileOpera: mobileOpera, 2127 mobileGecko: mobileGecko, 2128 retina: retina, 2129 passiveEvents: passiveEvents, 2130 canvas: canvas$1, 2131 svg: svg$1, 2132 vml: vml, 2133 inlineSvg: inlineSvg, 2134 mac: mac, 2135 linux: linux 2136 }; 2137 2138 /* 2139 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 2140 */ 2141 2142 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown'; 2143 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove'; 2144 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup'; 2145 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel'; 2146 var pEvent = { 2147 touchstart : POINTER_DOWN, 2148 touchmove : POINTER_MOVE, 2149 touchend : POINTER_UP, 2150 touchcancel : POINTER_CANCEL 2151 }; 2152 var handle = { 2153 touchstart : _onPointerStart, 2154 touchmove : _handlePointer, 2155 touchend : _handlePointer, 2156 touchcancel : _handlePointer 2157 }; 2158 var _pointers = {}; 2159 var _pointerDocListener = false; 2160 2161 // Provides a touch events wrapper for (ms)pointer events. 2162 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2163 2164 function addPointerListener(obj, type, handler) { 2165 if (type === 'touchstart') { 2166 _addPointerDocListener(); 2167 } 2168 if (!handle[type]) { 2169 console.warn('wrong event specified:', type); 2170 return falseFn; 2171 } 2172 handler = handle[type].bind(this, handler); 2173 obj.addEventListener(pEvent[type], handler, false); 2174 return handler; 2175 } 2176 2177 function removePointerListener(obj, type, handler) { 2178 if (!pEvent[type]) { 2179 console.warn('wrong event specified:', type); 2180 return; 2181 } 2182 obj.removeEventListener(pEvent[type], handler, false); 2183 } 2184 2185 function _globalPointerDown(e) { 2186 _pointers[e.pointerId] = e; 2187 } 2188 2189 function _globalPointerMove(e) { 2190 if (_pointers[e.pointerId]) { 2191 _pointers[e.pointerId] = e; 2192 } 2193 } 2194 2195 function _globalPointerUp(e) { 2196 delete _pointers[e.pointerId]; 2197 } 2198 2199 function _addPointerDocListener() { 2200 // need to keep track of what pointers and how many are active to provide e.touches emulation 2201 if (!_pointerDocListener) { 2202 // we listen document as any drags that end by moving the touch off the screen get fired there 2203 document.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2204 document.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2205 document.addEventListener(POINTER_UP, _globalPointerUp, true); 2206 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2207 2208 _pointerDocListener = true; 2209 } 2210 } 2211 2212 function _handlePointer(handler, e) { 2213 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; } 2214 2215 e.touches = []; 2216 for (var i in _pointers) { 2217 e.touches.push(_pointers[i]); 2218 } 2219 e.changedTouches = [e]; 2220 2221 handler(e); 2222 } 2223 2224 function _onPointerStart(handler, e) { 2225 // IE10 specific: MsTouch needs preventDefault. See #2000 2226 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) { 2227 preventDefault(e); 2228 } 2229 _handlePointer(handler, e); 2230 } 2231 2232 /* 2233 * Extends the event handling code with double tap support for mobile browsers. 2234 * 2235 * Note: currently most browsers fire native dblclick, with only a few exceptions 2236 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386) 2237 */ 2238 2239 function makeDblclick(event) { 2240 // in modern browsers `type` cannot be just overridden: 2241 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only 2242 var newEvent = {}, 2243 prop, i; 2244 for (i in event) { 2245 prop = event[i]; 2246 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop; 2247 } 2248 event = newEvent; 2249 newEvent.type = 'dblclick'; 2250 newEvent.detail = 2; 2251 newEvent.isTrusted = false; 2252 newEvent._simulated = true; // for debug purposes 2253 return newEvent; 2254 } 2255 2256 var delay = 200; 2257 function addDoubleTapListener(obj, handler) { 2258 // Most browsers handle double tap natively 2259 obj.addEventListener('dblclick', handler); 2260 2261 // On some platforms the browser doesn't fire native dblclicks for touch events. 2262 // It seems that in all such cases `detail` property of `click` event is always `1`. 2263 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed. 2264 var last = 0, 2265 detail; 2266 function simDblclick(e) { 2267 if (e.detail !== 1) { 2268 detail = e.detail; // keep in sync to avoid false dblclick in some cases 2269 return; 2270 } 2271 2272 if (e.pointerType === 'mouse' || 2273 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) { 2274 2275 return; 2276 } 2277 2278 // When clicking on an <input>, the browser generates a click on its 2279 // <label> (and vice versa) triggering two clicks in quick succession. 2280 // This ignores clicks on elements which are a label with a 'for' 2281 // attribute (or children of such a label), but not children of 2282 // a <input>. 2283 var path = getPropagationPath(e); 2284 if (path.some(function (el) { 2285 return el instanceof HTMLLabelElement && el.attributes.for; 2286 }) && 2287 !path.some(function (el) { 2288 return ( 2289 el instanceof HTMLInputElement || 2290 el instanceof HTMLSelectElement 2291 ); 2292 }) 2293 ) { 2294 return; 2295 } 2296 2297 var now = Date.now(); 2298 if (now - last <= delay) { 2299 detail++; 2300 if (detail === 2) { 2301 handler(makeDblclick(e)); 2302 } 2303 } else { 2304 detail = 1; 2305 } 2306 last = now; 2307 } 2308 2309 obj.addEventListener('click', simDblclick); 2310 2311 return { 2312 dblclick: handler, 2313 simDblclick: simDblclick 2314 }; 2315 } 2316 2317 function removeDoubleTapListener(obj, handlers) { 2318 obj.removeEventListener('dblclick', handlers.dblclick); 2319 obj.removeEventListener('click', handlers.simDblclick); 2320 } 2321 2322 /* 2323 * @namespace DomUtil 2324 * 2325 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2326 * tree, used by Leaflet internally. 2327 * 2328 * Most functions expecting or returning a `HTMLElement` also work for 2329 * SVG elements. The only difference is that classes refer to CSS classes 2330 * in HTML and SVG classes in SVG. 2331 */ 2332 2333 2334 // @property TRANSFORM: String 2335 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2336 var TRANSFORM = testProp( 2337 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2338 2339 // webkitTransition comes first because some browser versions that drop vendor prefix don't do 2340 // the same for the transitionend event, in particular the Android 4.1 stock browser 2341 2342 // @property TRANSITION: String 2343 // Vendor-prefixed transition style name. 2344 var TRANSITION = testProp( 2345 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2346 2347 // @property TRANSITION_END: String 2348 // Vendor-prefixed transitionend event name. 2349 var TRANSITION_END = 2350 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2351 2352 2353 // @function get(id: String|HTMLElement): HTMLElement 2354 // Returns an element given its DOM id, or returns the element itself 2355 // if it was passed directly. 2356 function get(id) { 2357 return typeof id === 'string' ? document.getElementById(id) : id; 2358 } 2359 2360 // @function getStyle(el: HTMLElement, styleAttrib: String): String 2361 // Returns the value for a certain style attribute on an element, 2362 // including computed values or values set through CSS. 2363 function getStyle(el, style) { 2364 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2365 2366 if ((!value || value === 'auto') && document.defaultView) { 2367 var css = document.defaultView.getComputedStyle(el, null); 2368 value = css ? css[style] : null; 2369 } 2370 return value === 'auto' ? null : value; 2371 } 2372 2373 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2374 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2375 function create$1(tagName, className, container) { 2376 var el = document.createElement(tagName); 2377 el.className = className || ''; 2378 2379 if (container) { 2380 container.appendChild(el); 2381 } 2382 return el; 2383 } 2384 2385 // @function remove(el: HTMLElement) 2386 // Removes `el` from its parent element 2387 function remove(el) { 2388 var parent = el.parentNode; 2389 if (parent) { 2390 parent.removeChild(el); 2391 } 2392 } 2393 2394 // @function empty(el: HTMLElement) 2395 // Removes all of `el`'s children elements from `el` 2396 function empty(el) { 2397 while (el.firstChild) { 2398 el.removeChild(el.firstChild); 2399 } 2400 } 2401 2402 // @function toFront(el: HTMLElement) 2403 // Makes `el` the last child of its parent, so it renders in front of the other children. 2404 function toFront(el) { 2405 var parent = el.parentNode; 2406 if (parent && parent.lastChild !== el) { 2407 parent.appendChild(el); 2408 } 2409 } 2410 2411 // @function toBack(el: HTMLElement) 2412 // Makes `el` the first child of its parent, so it renders behind the other children. 2413 function toBack(el) { 2414 var parent = el.parentNode; 2415 if (parent && parent.firstChild !== el) { 2416 parent.insertBefore(el, parent.firstChild); 2417 } 2418 } 2419 2420 // @function hasClass(el: HTMLElement, name: String): Boolean 2421 // Returns `true` if the element's class attribute contains `name`. 2422 function hasClass(el, name) { 2423 if (el.classList !== undefined) { 2424 return el.classList.contains(name); 2425 } 2426 var className = getClass(el); 2427 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2428 } 2429 2430 // @function addClass(el: HTMLElement, name: String) 2431 // Adds `name` to the element's class attribute. 2432 function addClass(el, name) { 2433 if (el.classList !== undefined) { 2434 var classes = splitWords(name); 2435 for (var i = 0, len = classes.length; i < len; i++) { 2436 el.classList.add(classes[i]); 2437 } 2438 } else if (!hasClass(el, name)) { 2439 var className = getClass(el); 2440 setClass(el, (className ? className + ' ' : '') + name); 2441 } 2442 } 2443 2444 // @function removeClass(el: HTMLElement, name: String) 2445 // Removes `name` from the element's class attribute. 2446 function removeClass(el, name) { 2447 if (el.classList !== undefined) { 2448 el.classList.remove(name); 2449 } else { 2450 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2451 } 2452 } 2453 2454 // @function setClass(el: HTMLElement, name: String) 2455 // Sets the element's class. 2456 function setClass(el, name) { 2457 if (el.className.baseVal === undefined) { 2458 el.className = name; 2459 } else { 2460 // in case of SVG element 2461 el.className.baseVal = name; 2462 } 2463 } 2464 2465 // @function getClass(el: HTMLElement): String 2466 // Returns the element's class. 2467 function getClass(el) { 2468 // Check if the element is an SVGElementInstance and use the correspondingElement instead 2469 // (Required for linked SVG elements in IE11.) 2470 if (el.correspondingElement) { 2471 el = el.correspondingElement; 2472 } 2473 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2474 } 2475 2476 // @function setOpacity(el: HTMLElement, opacity: Number) 2477 // Set the opacity of an element (including old IE support). 2478 // `opacity` must be a number from `0` to `1`. 2479 function setOpacity(el, value) { 2480 if ('opacity' in el.style) { 2481 el.style.opacity = value; 2482 } else if ('filter' in el.style) { 2483 _setOpacityIE(el, value); 2484 } 2485 } 2486 2487 function _setOpacityIE(el, value) { 2488 var filter = false, 2489 filterName = 'DXImageTransform.Microsoft.Alpha'; 2490 2491 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2492 try { 2493 filter = el.filters.item(filterName); 2494 } catch (e) { 2495 // don't set opacity to 1 if we haven't already set an opacity, 2496 // it isn't needed and breaks transparent pngs. 2497 if (value === 1) { return; } 2498 } 2499 2500 value = Math.round(value * 100); 2501 2502 if (filter) { 2503 filter.Enabled = (value !== 100); 2504 filter.Opacity = value; 2505 } else { 2506 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2507 } 2508 } 2509 2510 // @function testProp(props: String[]): String|false 2511 // Goes through the array of style names and returns the first name 2512 // that is a valid style name for an element. If no such name is found, 2513 // it returns false. Useful for vendor-prefixed styles like `transform`. 2514 function testProp(props) { 2515 var style = document.documentElement.style; 2516 2517 for (var i = 0; i < props.length; i++) { 2518 if (props[i] in style) { 2519 return props[i]; 2520 } 2521 } 2522 return false; 2523 } 2524 2525 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2526 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2527 // and optionally scaled by `scale`. Does not have an effect if the 2528 // browser doesn't support 3D CSS transforms. 2529 function setTransform(el, offset, scale) { 2530 var pos = offset || new Point(0, 0); 2531 2532 el.style[TRANSFORM] = 2533 (Browser.ie3d ? 2534 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2535 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2536 (scale ? ' scale(' + scale + ')' : ''); 2537 } 2538 2539 // @function setPosition(el: HTMLElement, position: Point) 2540 // Sets the position of `el` to coordinates specified by `position`, 2541 // using CSS translate or top/left positioning depending on the browser 2542 // (used by Leaflet internally to position its layers). 2543 function setPosition(el, point) { 2544 2545 /*eslint-disable */ 2546 el._leaflet_pos = point; 2547 /* eslint-enable */ 2548 2549 if (Browser.any3d) { 2550 setTransform(el, point); 2551 } else { 2552 el.style.left = point.x + 'px'; 2553 el.style.top = point.y + 'px'; 2554 } 2555 } 2556 2557 // @function getPosition(el: HTMLElement): Point 2558 // Returns the coordinates of an element previously positioned with setPosition. 2559 function getPosition(el) { 2560 // this method is only used for elements previously positioned using setPosition, 2561 // so it's safe to cache the position for performance 2562 2563 return el._leaflet_pos || new Point(0, 0); 2564 } 2565 2566 // @function disableTextSelection() 2567 // Prevents the user from generating `selectstart` DOM events, usually generated 2568 // when the user drags the mouse through a page with text. Used internally 2569 // by Leaflet to override the behaviour of any click-and-drag interaction on 2570 // the map. Affects drag interactions on the whole document. 2571 2572 // @function enableTextSelection() 2573 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2574 var disableTextSelection; 2575 var enableTextSelection; 2576 var _userSelect; 2577 if ('onselectstart' in document) { 2578 disableTextSelection = function () { 2579 on(window, 'selectstart', preventDefault); 2580 }; 2581 enableTextSelection = function () { 2582 off(window, 'selectstart', preventDefault); 2583 }; 2584 } else { 2585 var userSelectProperty = testProp( 2586 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2587 2588 disableTextSelection = function () { 2589 if (userSelectProperty) { 2590 var style = document.documentElement.style; 2591 _userSelect = style[userSelectProperty]; 2592 style[userSelectProperty] = 'none'; 2593 } 2594 }; 2595 enableTextSelection = function () { 2596 if (userSelectProperty) { 2597 document.documentElement.style[userSelectProperty] = _userSelect; 2598 _userSelect = undefined; 2599 } 2600 }; 2601 } 2602 2603 // @function disableImageDrag() 2604 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2605 // for `dragstart` DOM events, usually generated when the user drags an image. 2606 function disableImageDrag() { 2607 on(window, 'dragstart', preventDefault); 2608 } 2609 2610 // @function enableImageDrag() 2611 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2612 function enableImageDrag() { 2613 off(window, 'dragstart', preventDefault); 2614 } 2615 2616 var _outlineElement, _outlineStyle; 2617 // @function preventOutline(el: HTMLElement) 2618 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2619 // of the element `el` invisible. Used internally by Leaflet to prevent 2620 // focusable elements from displaying an outline when the user performs a 2621 // drag interaction on them. 2622 function preventOutline(element) { 2623 while (element.tabIndex === -1) { 2624 element = element.parentNode; 2625 } 2626 if (!element.style) { return; } 2627 restoreOutline(); 2628 _outlineElement = element; 2629 _outlineStyle = element.style.outlineStyle; 2630 element.style.outlineStyle = 'none'; 2631 on(window, 'keydown', restoreOutline); 2632 } 2633 2634 // @function restoreOutline() 2635 // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2636 function restoreOutline() { 2637 if (!_outlineElement) { return; } 2638 _outlineElement.style.outlineStyle = _outlineStyle; 2639 _outlineElement = undefined; 2640 _outlineStyle = undefined; 2641 off(window, 'keydown', restoreOutline); 2642 } 2643 2644 // @function getSizedParentNode(el: HTMLElement): HTMLElement 2645 // Finds the closest parent node which size (width and height) is not null. 2646 function getSizedParentNode(element) { 2647 do { 2648 element = element.parentNode; 2649 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2650 return element; 2651 } 2652 2653 // @function getScale(el: HTMLElement): Object 2654 // Computes the CSS scale currently applied on the element. 2655 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2656 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2657 function getScale(element) { 2658 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2659 2660 return { 2661 x: rect.width / element.offsetWidth || 1, 2662 y: rect.height / element.offsetHeight || 1, 2663 boundingClientRect: rect 2664 }; 2665 } 2666 2667 var DomUtil = { 2668 __proto__: null, 2669 TRANSFORM: TRANSFORM, 2670 TRANSITION: TRANSITION, 2671 TRANSITION_END: TRANSITION_END, 2672 get: get, 2673 getStyle: getStyle, 2674 create: create$1, 2675 remove: remove, 2676 empty: empty, 2677 toFront: toFront, 2678 toBack: toBack, 2679 hasClass: hasClass, 2680 addClass: addClass, 2681 removeClass: removeClass, 2682 setClass: setClass, 2683 getClass: getClass, 2684 setOpacity: setOpacity, 2685 testProp: testProp, 2686 setTransform: setTransform, 2687 setPosition: setPosition, 2688 getPosition: getPosition, 2689 get disableTextSelection () { return disableTextSelection; }, 2690 get enableTextSelection () { return enableTextSelection; }, 2691 disableImageDrag: disableImageDrag, 2692 enableImageDrag: enableImageDrag, 2693 preventOutline: preventOutline, 2694 restoreOutline: restoreOutline, 2695 getSizedParentNode: getSizedParentNode, 2696 getScale: getScale 2697 }; 2698 2699 /* 2700 * @namespace DomEvent 2701 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2702 */ 2703 2704 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2705 2706 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2707 // Adds a listener function (`fn`) to a particular DOM event type of the 2708 // element `el`. You can optionally specify the context of the listener 2709 // (object the `this` keyword will point to). You can also pass several 2710 // space-separated types (e.g. `'click dblclick'`). 2711 2712 // @alternative 2713 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2714 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2715 function on(obj, types, fn, context) { 2716 2717 if (types && typeof types === 'object') { 2718 for (var type in types) { 2719 addOne(obj, type, types[type], fn); 2720 } 2721 } else { 2722 types = splitWords(types); 2723 2724 for (var i = 0, len = types.length; i < len; i++) { 2725 addOne(obj, types[i], fn, context); 2726 } 2727 } 2728 2729 return this; 2730 } 2731 2732 var eventsKey = '_leaflet_events'; 2733 2734 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2735 // Removes a previously added listener function. 2736 // Note that if you passed a custom context to on, you must pass the same 2737 // context to `off` in order to remove the listener. 2738 2739 // @alternative 2740 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2741 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2742 2743 // @alternative 2744 // @function off(el: HTMLElement, types: String): this 2745 // Removes all previously added listeners of given types. 2746 2747 // @alternative 2748 // @function off(el: HTMLElement): this 2749 // Removes all previously added listeners from given HTMLElement 2750 function off(obj, types, fn, context) { 2751 2752 if (arguments.length === 1) { 2753 batchRemove(obj); 2754 delete obj[eventsKey]; 2755 2756 } else if (types && typeof types === 'object') { 2757 for (var type in types) { 2758 removeOne(obj, type, types[type], fn); 2759 } 2760 2761 } else { 2762 types = splitWords(types); 2763 2764 if (arguments.length === 2) { 2765 batchRemove(obj, function (type) { 2766 return indexOf(types, type) !== -1; 2767 }); 2768 } else { 2769 for (var i = 0, len = types.length; i < len; i++) { 2770 removeOne(obj, types[i], fn, context); 2771 } 2772 } 2773 } 2774 2775 return this; 2776 } 2777 2778 function batchRemove(obj, filterFn) { 2779 for (var id in obj[eventsKey]) { 2780 var type = id.split(/\d/)[0]; 2781 if (!filterFn || filterFn(type)) { 2782 removeOne(obj, type, null, null, id); 2783 } 2784 } 2785 } 2786 2787 var mouseSubst = { 2788 mouseenter: 'mouseover', 2789 mouseleave: 'mouseout', 2790 wheel: !('onwheel' in window) && 'mousewheel' 2791 }; 2792 2793 function addOne(obj, type, fn, context) { 2794 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2795 2796 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 2797 2798 var handler = function (e) { 2799 return fn.call(context || obj, e || window.event); 2800 }; 2801 2802 var originalHandler = handler; 2803 2804 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 2805 // Needs DomEvent.Pointer.js 2806 handler = addPointerListener(obj, type, handler); 2807 2808 } else if (Browser.touch && (type === 'dblclick')) { 2809 handler = addDoubleTapListener(obj, handler); 2810 2811 } else if ('addEventListener' in obj) { 2812 2813 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') { 2814 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false); 2815 2816 } else if (type === 'mouseenter' || type === 'mouseleave') { 2817 handler = function (e) { 2818 e = e || window.event; 2819 if (isExternalTarget(obj, e)) { 2820 originalHandler(e); 2821 } 2822 }; 2823 obj.addEventListener(mouseSubst[type], handler, false); 2824 2825 } else { 2826 obj.addEventListener(type, originalHandler, false); 2827 } 2828 2829 } else { 2830 obj.attachEvent('on' + type, handler); 2831 } 2832 2833 obj[eventsKey] = obj[eventsKey] || {}; 2834 obj[eventsKey][id] = handler; 2835 } 2836 2837 function removeOne(obj, type, fn, context, id) { 2838 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2839 var handler = obj[eventsKey] && obj[eventsKey][id]; 2840 2841 if (!handler) { return this; } 2842 2843 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 2844 removePointerListener(obj, type, handler); 2845 2846 } else if (Browser.touch && (type === 'dblclick')) { 2847 removeDoubleTapListener(obj, handler); 2848 2849 } else if ('removeEventListener' in obj) { 2850 2851 obj.removeEventListener(mouseSubst[type] || type, handler, false); 2852 2853 } else { 2854 obj.detachEvent('on' + type, handler); 2855 } 2856 2857 obj[eventsKey][id] = null; 2858 } 2859 2860 // @function stopPropagation(ev: DOMEvent): this 2861 // Stop the given event from propagation to parent elements. Used inside the listener functions: 2862 // ```js 2863 // L.DomEvent.on(div, 'click', function (ev) { 2864 // L.DomEvent.stopPropagation(ev); 2865 // }); 2866 // ``` 2867 function stopPropagation(e) { 2868 2869 if (e.stopPropagation) { 2870 e.stopPropagation(); 2871 } else if (e.originalEvent) { // In case of Leaflet event. 2872 e.originalEvent._stopped = true; 2873 } else { 2874 e.cancelBubble = true; 2875 } 2876 2877 return this; 2878 } 2879 2880 // @function disableScrollPropagation(el: HTMLElement): this 2881 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants). 2882 function disableScrollPropagation(el) { 2883 addOne(el, 'wheel', stopPropagation); 2884 return this; 2885 } 2886 2887 // @function disableClickPropagation(el: HTMLElement): this 2888 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`, 2889 // `'mousedown'` and `'touchstart'` events (plus browser variants). 2890 function disableClickPropagation(el) { 2891 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation); 2892 el['_leaflet_disable_click'] = true; 2893 return this; 2894 } 2895 2896 // @function preventDefault(ev: DOMEvent): this 2897 // Prevents the default action of the DOM Event `ev` from happening (such as 2898 // following a link in the href of the a element, or doing a POST request 2899 // with page reload when a `<form>` is submitted). 2900 // Use it inside listener functions. 2901 function preventDefault(e) { 2902 if (e.preventDefault) { 2903 e.preventDefault(); 2904 } else { 2905 e.returnValue = false; 2906 } 2907 return this; 2908 } 2909 2910 // @function stop(ev: DOMEvent): this 2911 // Does `stopPropagation` and `preventDefault` at the same time. 2912 function stop(e) { 2913 preventDefault(e); 2914 stopPropagation(e); 2915 return this; 2916 } 2917 2918 // @function getPropagationPath(ev: DOMEvent): Array 2919 // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath). 2920 // Returns an array containing the `HTMLElement`s that the given DOM event 2921 // should propagate to (if not stopped). 2922 function getPropagationPath(ev) { 2923 if (ev.composedPath) { 2924 return ev.composedPath(); 2925 } 2926 2927 var path = []; 2928 var el = ev.target; 2929 2930 while (el) { 2931 path.push(el); 2932 el = el.parentNode; 2933 } 2934 return path; 2935 } 2936 2937 2938 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 2939 // Gets normalized mouse position from a DOM event relative to the 2940 // `container` (border excluded) or to the whole page if not specified. 2941 function getMousePosition(e, container) { 2942 if (!container) { 2943 return new Point(e.clientX, e.clientY); 2944 } 2945 2946 var scale = getScale(container), 2947 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 2948 2949 return new Point( 2950 // offset.left/top values are in page scale (like clientX/Y), 2951 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 2952 (e.clientX - offset.left) / scale.x - container.clientLeft, 2953 (e.clientY - offset.top) / scale.y - container.clientTop 2954 ); 2955 } 2956 2957 2958 // except , Safari and 2959 // We need double the scroll pixels (see #7403 and #4538) for all Browsers 2960 // except OSX (Mac) -> 3x, Chrome running on Linux 1x 2961 2962 var wheelPxFactor = 2963 (Browser.linux && Browser.chrome) ? window.devicePixelRatio : 2964 Browser.mac ? window.devicePixelRatio * 3 : 2965 window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1; 2966 // @function getWheelDelta(ev: DOMEvent): Number 2967 // Gets normalized wheel delta from a wheel DOM event, in vertical 2968 // pixels scrolled (negative if scrolling down). 2969 // Events from pointing devices without precise scrolling are mapped to 2970 // a best guess of 60 pixels. 2971 function getWheelDelta(e) { 2972 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 2973 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 2974 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 2975 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 2976 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 2977 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 2978 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 2979 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 2980 0; 2981 } 2982 2983 // check if element really left/entered the event target (for mouseenter/mouseleave) 2984 function isExternalTarget(el, e) { 2985 2986 var related = e.relatedTarget; 2987 2988 if (!related) { return true; } 2989 2990 try { 2991 while (related && (related !== el)) { 2992 related = related.parentNode; 2993 } 2994 } catch (err) { 2995 return false; 2996 } 2997 return (related !== el); 2998 } 2999 3000 var DomEvent = { 3001 __proto__: null, 3002 on: on, 3003 off: off, 3004 stopPropagation: stopPropagation, 3005 disableScrollPropagation: disableScrollPropagation, 3006 disableClickPropagation: disableClickPropagation, 3007 preventDefault: preventDefault, 3008 stop: stop, 3009 getPropagationPath: getPropagationPath, 3010 getMousePosition: getMousePosition, 3011 getWheelDelta: getWheelDelta, 3012 isExternalTarget: isExternalTarget, 3013 addListener: on, 3014 removeListener: off 3015 }; 3016 3017 /* 3018 * @class PosAnimation 3019 * @aka L.PosAnimation 3020 * @inherits Evented 3021 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 3022 * 3023 * @example 3024 * ```js 3025 * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map); 3026 * 3027 * myPositionMarker.on("click", function() { 3028 * var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng()); 3029 * pos.y -= 25; 3030 * var fx = new L.PosAnimation(); 3031 * 3032 * fx.once('end',function() { 3033 * pos.y += 25; 3034 * fx.run(myPositionMarker._icon, pos, 0.8); 3035 * }); 3036 * 3037 * fx.run(myPositionMarker._icon, pos, 0.3); 3038 * }); 3039 * 3040 * ``` 3041 * 3042 * @constructor L.PosAnimation() 3043 * Creates a `PosAnimation` object. 3044 * 3045 */ 3046 3047 var PosAnimation = Evented.extend({ 3048 3049 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 3050 // Run an animation of a given element to a new position, optionally setting 3051 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 3052 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1), 3053 // `0.5` by default). 3054 run: function (el, newPos, duration, easeLinearity) { 3055 this.stop(); 3056 3057 this._el = el; 3058 this._inProgress = true; 3059 this._duration = duration || 0.25; 3060 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 3061 3062 this._startPos = getPosition(el); 3063 this._offset = newPos.subtract(this._startPos); 3064 this._startTime = +new Date(); 3065 3066 // @event start: Event 3067 // Fired when the animation starts 3068 this.fire('start'); 3069 3070 this._animate(); 3071 }, 3072 3073 // @method stop() 3074 // Stops the animation (if currently running). 3075 stop: function () { 3076 if (!this._inProgress) { return; } 3077 3078 this._step(true); 3079 this._complete(); 3080 }, 3081 3082 _animate: function () { 3083 // animation loop 3084 this._animId = requestAnimFrame(this._animate, this); 3085 this._step(); 3086 }, 3087 3088 _step: function (round) { 3089 var elapsed = (+new Date()) - this._startTime, 3090 duration = this._duration * 1000; 3091 3092 if (elapsed < duration) { 3093 this._runFrame(this._easeOut(elapsed / duration), round); 3094 } else { 3095 this._runFrame(1); 3096 this._complete(); 3097 } 3098 }, 3099 3100 _runFrame: function (progress, round) { 3101 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 3102 if (round) { 3103 pos._round(); 3104 } 3105 setPosition(this._el, pos); 3106 3107 // @event step: Event 3108 // Fired continuously during the animation. 3109 this.fire('step'); 3110 }, 3111 3112 _complete: function () { 3113 cancelAnimFrame(this._animId); 3114 3115 this._inProgress = false; 3116 // @event end: Event 3117 // Fired when the animation ends. 3118 this.fire('end'); 3119 }, 3120 3121 _easeOut: function (t) { 3122 return 1 - Math.pow(1 - t, this._easeOutPower); 3123 } 3124 }); 3125 3126 /* 3127 * @class Map 3128 * @aka L.Map 3129 * @inherits Evented 3130 * 3131 * The central class of the API â it is used to create a map on a page and manipulate it. 3132 * 3133 * @example 3134 * 3135 * ```js 3136 * // initialize the map on the "map" div with a given center and zoom 3137 * var map = L.map('map', { 3138 * center: [51.505, -0.09], 3139 * zoom: 13 3140 * }); 3141 * ``` 3142 * 3143 */ 3144 3145 var Map = Evented.extend({ 3146 3147 options: { 3148 // @section Map State Options 3149 // @option crs: CRS = L.CRS.EPSG3857 3150 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 3151 // sure what it means. 3152 crs: EPSG3857, 3153 3154 // @option center: LatLng = undefined 3155 // Initial geographic center of the map 3156 center: undefined, 3157 3158 // @option zoom: Number = undefined 3159 // Initial map zoom level 3160 zoom: undefined, 3161 3162 // @option minZoom: Number = * 3163 // Minimum zoom level of the map. 3164 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3165 // the lowest of their `minZoom` options will be used instead. 3166 minZoom: undefined, 3167 3168 // @option maxZoom: Number = * 3169 // Maximum zoom level of the map. 3170 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3171 // the highest of their `maxZoom` options will be used instead. 3172 maxZoom: undefined, 3173 3174 // @option layers: Layer[] = [] 3175 // Array of layers that will be added to the map initially 3176 layers: [], 3177 3178 // @option maxBounds: LatLngBounds = null 3179 // When this option is set, the map restricts the view to the given 3180 // geographical bounds, bouncing the user back if the user tries to pan 3181 // outside the view. To set the restriction dynamically, use 3182 // [`setMaxBounds`](#map-setmaxbounds) method. 3183 maxBounds: undefined, 3184 3185 // @option renderer: Renderer = * 3186 // The default method for drawing vector layers on the map. `L.SVG` 3187 // or `L.Canvas` by default depending on browser support. 3188 renderer: undefined, 3189 3190 3191 // @section Animation Options 3192 // @option zoomAnimation: Boolean = true 3193 // Whether the map zoom animation is enabled. By default it's enabled 3194 // in all browsers that support CSS3 Transitions except Android. 3195 zoomAnimation: true, 3196 3197 // @option zoomAnimationThreshold: Number = 4 3198 // Won't animate zoom if the zoom difference exceeds this value. 3199 zoomAnimationThreshold: 4, 3200 3201 // @option fadeAnimation: Boolean = true 3202 // Whether the tile fade animation is enabled. By default it's enabled 3203 // in all browsers that support CSS3 Transitions except Android. 3204 fadeAnimation: true, 3205 3206 // @option markerZoomAnimation: Boolean = true 3207 // Whether markers animate their zoom with the zoom animation, if disabled 3208 // they will disappear for the length of the animation. By default it's 3209 // enabled in all browsers that support CSS3 Transitions except Android. 3210 markerZoomAnimation: true, 3211 3212 // @option transform3DLimit: Number = 2^23 3213 // Defines the maximum size of a CSS translation transform. The default 3214 // value should not be changed unless a web browser positions layers in 3215 // the wrong place after doing a large `panBy`. 3216 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3217 3218 // @section Interaction Options 3219 // @option zoomSnap: Number = 1 3220 // Forces the map's zoom level to always be a multiple of this, particularly 3221 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3222 // By default, the zoom level snaps to the nearest integer; lower values 3223 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3224 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3225 zoomSnap: 1, 3226 3227 // @option zoomDelta: Number = 1 3228 // Controls how much the map's zoom level will change after a 3229 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3230 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3231 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3232 zoomDelta: 1, 3233 3234 // @option trackResize: Boolean = true 3235 // Whether the map automatically handles browser window resize to update itself. 3236 trackResize: true 3237 }, 3238 3239 initialize: function (id, options) { // (HTMLElement or String, Object) 3240 options = setOptions(this, options); 3241 3242 // Make sure to assign internal flags at the beginning, 3243 // to avoid inconsistent state in some edge cases. 3244 this._handlers = []; 3245 this._layers = {}; 3246 this._zoomBoundLayers = {}; 3247 this._sizeChanged = true; 3248 3249 this._initContainer(id); 3250 this._initLayout(); 3251 3252 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3253 this._onResize = bind(this._onResize, this); 3254 3255 this._initEvents(); 3256 3257 if (options.maxBounds) { 3258 this.setMaxBounds(options.maxBounds); 3259 } 3260 3261 if (options.zoom !== undefined) { 3262 this._zoom = this._limitZoom(options.zoom); 3263 } 3264 3265 if (options.center && options.zoom !== undefined) { 3266 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3267 } 3268 3269 this.callInitHooks(); 3270 3271 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3272 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera && 3273 this.options.zoomAnimation; 3274 3275 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3276 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3277 if (this._zoomAnimated) { 3278 this._createAnimProxy(); 3279 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3280 } 3281 3282 this._addLayers(this.options.layers); 3283 }, 3284 3285 3286 // @section Methods for modifying map state 3287 3288 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3289 // Sets the view of the map (geographical center and zoom) with the given 3290 // animation options. 3291 setView: function (center, zoom, options) { 3292 3293 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3294 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3295 options = options || {}; 3296 3297 this._stop(); 3298 3299 if (this._loaded && !options.reset && options !== true) { 3300 3301 if (options.animate !== undefined) { 3302 options.zoom = extend({animate: options.animate}, options.zoom); 3303 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3304 } 3305 3306 // try animating pan or zoom 3307 var moved = (this._zoom !== zoom) ? 3308 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3309 this._tryAnimatedPan(center, options.pan); 3310 3311 if (moved) { 3312 // prevent resize handler call, the view will refresh after animation anyway 3313 clearTimeout(this._sizeTimer); 3314 return this; 3315 } 3316 } 3317 3318 // animation didn't start, just reset the map view 3319 this._resetView(center, zoom, options.pan && options.pan.noMoveStart); 3320 3321 return this; 3322 }, 3323 3324 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3325 // Sets the zoom of the map. 3326 setZoom: function (zoom, options) { 3327 if (!this._loaded) { 3328 this._zoom = zoom; 3329 return this; 3330 } 3331 return this.setView(this.getCenter(), zoom, {zoom: options}); 3332 }, 3333 3334 // @method zoomIn(delta?: Number, options?: Zoom options): this 3335 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3336 zoomIn: function (delta, options) { 3337 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3338 return this.setZoom(this._zoom + delta, options); 3339 }, 3340 3341 // @method zoomOut(delta?: Number, options?: Zoom options): this 3342 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3343 zoomOut: function (delta, options) { 3344 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3345 return this.setZoom(this._zoom - delta, options); 3346 }, 3347 3348 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3349 // Zooms the map while keeping a specified geographical point on the map 3350 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3351 // @alternative 3352 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3353 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3354 setZoomAround: function (latlng, zoom, options) { 3355 var scale = this.getZoomScale(zoom), 3356 viewHalf = this.getSize().divideBy(2), 3357 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3358 3359 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3360 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3361 3362 return this.setView(newCenter, zoom, {zoom: options}); 3363 }, 3364 3365 _getBoundsCenterZoom: function (bounds, options) { 3366 3367 options = options || {}; 3368 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3369 3370 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3371 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3372 3373 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3374 3375 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3376 3377 if (zoom === Infinity) { 3378 return { 3379 center: bounds.getCenter(), 3380 zoom: zoom 3381 }; 3382 } 3383 3384 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3385 3386 swPoint = this.project(bounds.getSouthWest(), zoom), 3387 nePoint = this.project(bounds.getNorthEast(), zoom), 3388 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3389 3390 return { 3391 center: center, 3392 zoom: zoom 3393 }; 3394 }, 3395 3396 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3397 // Sets a map view that contains the given geographical bounds with the 3398 // maximum zoom level possible. 3399 fitBounds: function (bounds, options) { 3400 3401 bounds = toLatLngBounds(bounds); 3402 3403 if (!bounds.isValid()) { 3404 throw new Error('Bounds are not valid.'); 3405 } 3406 3407 var target = this._getBoundsCenterZoom(bounds, options); 3408 return this.setView(target.center, target.zoom, options); 3409 }, 3410 3411 // @method fitWorld(options?: fitBounds options): this 3412 // Sets a map view that mostly contains the whole world with the maximum 3413 // zoom level possible. 3414 fitWorld: function (options) { 3415 return this.fitBounds([[-90, -180], [90, 180]], options); 3416 }, 3417 3418 // @method panTo(latlng: LatLng, options?: Pan options): this 3419 // Pans the map to a given center. 3420 panTo: function (center, options) { // (LatLng) 3421 return this.setView(center, this._zoom, {pan: options}); 3422 }, 3423 3424 // @method panBy(offset: Point, options?: Pan options): this 3425 // Pans the map by a given number of pixels (animated). 3426 panBy: function (offset, options) { 3427 offset = toPoint(offset).round(); 3428 options = options || {}; 3429 3430 if (!offset.x && !offset.y) { 3431 return this.fire('moveend'); 3432 } 3433 // If we pan too far, Chrome gets issues with tiles 3434 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3435 if (options.animate !== true && !this.getSize().contains(offset)) { 3436 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3437 return this; 3438 } 3439 3440 if (!this._panAnim) { 3441 this._panAnim = new PosAnimation(); 3442 3443 this._panAnim.on({ 3444 'step': this._onPanTransitionStep, 3445 'end': this._onPanTransitionEnd 3446 }, this); 3447 } 3448 3449 // don't fire movestart if animating inertia 3450 if (!options.noMoveStart) { 3451 this.fire('movestart'); 3452 } 3453 3454 // animate pan unless animate: false specified 3455 if (options.animate !== false) { 3456 addClass(this._mapPane, 'leaflet-pan-anim'); 3457 3458 var newPos = this._getMapPanePos().subtract(offset).round(); 3459 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3460 } else { 3461 this._rawPanBy(offset); 3462 this.fire('move').fire('moveend'); 3463 } 3464 3465 return this; 3466 }, 3467 3468 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3469 // Sets the view of the map (geographical center and zoom) performing a smooth 3470 // pan-zoom animation. 3471 flyTo: function (targetCenter, targetZoom, options) { 3472 3473 options = options || {}; 3474 if (options.animate === false || !Browser.any3d) { 3475 return this.setView(targetCenter, targetZoom, options); 3476 } 3477 3478 this._stop(); 3479 3480 var from = this.project(this.getCenter()), 3481 to = this.project(targetCenter), 3482 size = this.getSize(), 3483 startZoom = this._zoom; 3484 3485 targetCenter = toLatLng(targetCenter); 3486 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3487 3488 var w0 = Math.max(size.x, size.y), 3489 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3490 u1 = (to.distanceTo(from)) || 1, 3491 rho = 1.42, 3492 rho2 = rho * rho; 3493 3494 function r(i) { 3495 var s1 = i ? -1 : 1, 3496 s2 = i ? w1 : w0, 3497 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3498 b1 = 2 * s2 * rho2 * u1, 3499 b = t1 / b1, 3500 sq = Math.sqrt(b * b + 1) - b; 3501 3502 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3503 // thus triggering an infinite loop in flyTo 3504 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3505 3506 return log; 3507 } 3508 3509 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3510 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3511 function tanh(n) { return sinh(n) / cosh(n); } 3512 3513 var r0 = r(0); 3514 3515 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3516 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3517 3518 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3519 3520 var start = Date.now(), 3521 S = (r(1) - r0) / rho, 3522 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3523 3524 function frame() { 3525 var t = (Date.now() - start) / duration, 3526 s = easeOut(t) * S; 3527 3528 if (t <= 1) { 3529 this._flyToFrame = requestAnimFrame(frame, this); 3530 3531 this._move( 3532 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3533 this.getScaleZoom(w0 / w(s), startZoom), 3534 {flyTo: true}); 3535 3536 } else { 3537 this 3538 ._move(targetCenter, targetZoom) 3539 ._moveEnd(true); 3540 } 3541 } 3542 3543 this._moveStart(true, options.noMoveStart); 3544 3545 frame.call(this); 3546 return this; 3547 }, 3548 3549 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3550 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3551 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3552 flyToBounds: function (bounds, options) { 3553 var target = this._getBoundsCenterZoom(bounds, options); 3554 return this.flyTo(target.center, target.zoom, options); 3555 }, 3556 3557 // @method setMaxBounds(bounds: LatLngBounds): this 3558 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3559 setMaxBounds: function (bounds) { 3560 bounds = toLatLngBounds(bounds); 3561 3562 if (this.listens('moveend', this._panInsideMaxBounds)) { 3563 this.off('moveend', this._panInsideMaxBounds); 3564 } 3565 3566 if (!bounds.isValid()) { 3567 this.options.maxBounds = null; 3568 return this; 3569 } 3570 3571 this.options.maxBounds = bounds; 3572 3573 if (this._loaded) { 3574 this._panInsideMaxBounds(); 3575 } 3576 3577 return this.on('moveend', this._panInsideMaxBounds); 3578 }, 3579 3580 // @method setMinZoom(zoom: Number): this 3581 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3582 setMinZoom: function (zoom) { 3583 var oldZoom = this.options.minZoom; 3584 this.options.minZoom = zoom; 3585 3586 if (this._loaded && oldZoom !== zoom) { 3587 this.fire('zoomlevelschange'); 3588 3589 if (this.getZoom() < this.options.minZoom) { 3590 return this.setZoom(zoom); 3591 } 3592 } 3593 3594 return this; 3595 }, 3596 3597 // @method setMaxZoom(zoom: Number): this 3598 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3599 setMaxZoom: function (zoom) { 3600 var oldZoom = this.options.maxZoom; 3601 this.options.maxZoom = zoom; 3602 3603 if (this._loaded && oldZoom !== zoom) { 3604 this.fire('zoomlevelschange'); 3605 3606 if (this.getZoom() > this.options.maxZoom) { 3607 return this.setZoom(zoom); 3608 } 3609 } 3610 3611 return this; 3612 }, 3613 3614 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3615 // 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. 3616 panInsideBounds: function (bounds, options) { 3617 this._enforcingBounds = true; 3618 var center = this.getCenter(), 3619 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3620 3621 if (!center.equals(newCenter)) { 3622 this.panTo(newCenter, options); 3623 } 3624 3625 this._enforcingBounds = false; 3626 return this; 3627 }, 3628 3629 // @method panInside(latlng: LatLng, options?: padding options): this 3630 // Pans the map the minimum amount to make the `latlng` visible. Use 3631 // padding options to fit the display to more restricted bounds. 3632 // If `latlng` is already within the (optionally padded) display bounds, 3633 // the map will not be panned. 3634 panInside: function (latlng, options) { 3635 options = options || {}; 3636 3637 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3638 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3639 pixelCenter = this.project(this.getCenter()), 3640 pixelPoint = this.project(latlng), 3641 pixelBounds = this.getPixelBounds(), 3642 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]), 3643 paddedSize = paddedBounds.getSize(); 3644 3645 if (!paddedBounds.contains(pixelPoint)) { 3646 this._enforcingBounds = true; 3647 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter()); 3648 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize); 3649 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x; 3650 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y; 3651 this.panTo(this.unproject(pixelCenter), options); 3652 this._enforcingBounds = false; 3653 } 3654 return this; 3655 }, 3656 3657 // @method invalidateSize(options: Zoom/pan options): this 3658 // Checks if the map container size changed and updates the map if so â 3659 // call it after you've changed the map size dynamically, also animating 3660 // pan by default. If `options.pan` is `false`, panning will not occur. 3661 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3662 // that it doesn't happen often even if the method is called many 3663 // times in a row. 3664 3665 // @alternative 3666 // @method invalidateSize(animate: Boolean): this 3667 // Checks if the map container size changed and updates the map if so â 3668 // call it after you've changed the map size dynamically, also animating 3669 // pan by default. 3670 invalidateSize: function (options) { 3671 if (!this._loaded) { return this; } 3672 3673 options = extend({ 3674 animate: false, 3675 pan: true 3676 }, options === true ? {animate: true} : options); 3677 3678 var oldSize = this.getSize(); 3679 this._sizeChanged = true; 3680 this._lastCenter = null; 3681 3682 var newSize = this.getSize(), 3683 oldCenter = oldSize.divideBy(2).round(), 3684 newCenter = newSize.divideBy(2).round(), 3685 offset = oldCenter.subtract(newCenter); 3686 3687 if (!offset.x && !offset.y) { return this; } 3688 3689 if (options.animate && options.pan) { 3690 this.panBy(offset); 3691 3692 } else { 3693 if (options.pan) { 3694 this._rawPanBy(offset); 3695 } 3696 3697 this.fire('move'); 3698 3699 if (options.debounceMoveend) { 3700 clearTimeout(this._sizeTimer); 3701 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3702 } else { 3703 this.fire('moveend'); 3704 } 3705 } 3706 3707 // @section Map state change events 3708 // @event resize: ResizeEvent 3709 // Fired when the map is resized. 3710 return this.fire('resize', { 3711 oldSize: oldSize, 3712 newSize: newSize 3713 }); 3714 }, 3715 3716 // @section Methods for modifying map state 3717 // @method stop(): this 3718 // Stops the currently running `panTo` or `flyTo` animation, if any. 3719 stop: function () { 3720 this.setZoom(this._limitZoom(this._zoom)); 3721 if (!this.options.zoomSnap) { 3722 this.fire('viewreset'); 3723 } 3724 return this._stop(); 3725 }, 3726 3727 // @section Geolocation methods 3728 // @method locate(options?: Locate options): this 3729 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3730 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3731 // and optionally sets the map view to the user's location with respect to 3732 // detection accuracy (or to the world view if geolocation failed). 3733 // Note that, if your page doesn't use HTTPS, this method will fail in 3734 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3735 // See `Locate options` for more details. 3736 locate: function (options) { 3737 3738 options = this._locateOptions = extend({ 3739 timeout: 10000, 3740 watch: false 3741 // setView: false 3742 // maxZoom: <Number> 3743 // maximumAge: 0 3744 // enableHighAccuracy: false 3745 }, options); 3746 3747 if (!('geolocation' in navigator)) { 3748 this._handleGeolocationError({ 3749 code: 0, 3750 message: 'Geolocation not supported.' 3751 }); 3752 return this; 3753 } 3754 3755 var onResponse = bind(this._handleGeolocationResponse, this), 3756 onError = bind(this._handleGeolocationError, this); 3757 3758 if (options.watch) { 3759 this._locationWatchId = 3760 navigator.geolocation.watchPosition(onResponse, onError, options); 3761 } else { 3762 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 3763 } 3764 return this; 3765 }, 3766 3767 // @method stopLocate(): this 3768 // Stops watching location previously initiated by `map.locate({watch: true})` 3769 // and aborts resetting the map view if map.locate was called with 3770 // `{setView: true}`. 3771 stopLocate: function () { 3772 if (navigator.geolocation && navigator.geolocation.clearWatch) { 3773 navigator.geolocation.clearWatch(this._locationWatchId); 3774 } 3775 if (this._locateOptions) { 3776 this._locateOptions.setView = false; 3777 } 3778 return this; 3779 }, 3780 3781 _handleGeolocationError: function (error) { 3782 if (!this._container._leaflet_id) { return; } 3783 3784 var c = error.code, 3785 message = error.message || 3786 (c === 1 ? 'permission denied' : 3787 (c === 2 ? 'position unavailable' : 'timeout')); 3788 3789 if (this._locateOptions.setView && !this._loaded) { 3790 this.fitWorld(); 3791 } 3792 3793 // @section Location events 3794 // @event locationerror: ErrorEvent 3795 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 3796 this.fire('locationerror', { 3797 code: c, 3798 message: 'Geolocation error: ' + message + '.' 3799 }); 3800 }, 3801 3802 _handleGeolocationResponse: function (pos) { 3803 if (!this._container._leaflet_id) { return; } 3804 3805 var lat = pos.coords.latitude, 3806 lng = pos.coords.longitude, 3807 latlng = new LatLng(lat, lng), 3808 bounds = latlng.toBounds(pos.coords.accuracy * 2), 3809 options = this._locateOptions; 3810 3811 if (options.setView) { 3812 var zoom = this.getBoundsZoom(bounds); 3813 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 3814 } 3815 3816 var data = { 3817 latlng: latlng, 3818 bounds: bounds, 3819 timestamp: pos.timestamp 3820 }; 3821 3822 for (var i in pos.coords) { 3823 if (typeof pos.coords[i] === 'number') { 3824 data[i] = pos.coords[i]; 3825 } 3826 } 3827 3828 // @event locationfound: LocationEvent 3829 // Fired when geolocation (using the [`locate`](#map-locate) method) 3830 // went successfully. 3831 this.fire('locationfound', data); 3832 }, 3833 3834 // TODO Appropriate docs section? 3835 // @section Other Methods 3836 // @method addHandler(name: String, HandlerClass: Function): this 3837 // Adds a new `Handler` to the map, given its name and constructor function. 3838 addHandler: function (name, HandlerClass) { 3839 if (!HandlerClass) { return this; } 3840 3841 var handler = this[name] = new HandlerClass(this); 3842 3843 this._handlers.push(handler); 3844 3845 if (this.options[name]) { 3846 handler.enable(); 3847 } 3848 3849 return this; 3850 }, 3851 3852 // @method remove(): this 3853 // Destroys the map and clears all related event listeners. 3854 remove: function () { 3855 3856 this._initEvents(true); 3857 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); } 3858 3859 if (this._containerId !== this._container._leaflet_id) { 3860 throw new Error('Map container is being reused by another instance'); 3861 } 3862 3863 try { 3864 // throws error in IE6-8 3865 delete this._container._leaflet_id; 3866 delete this._containerId; 3867 } catch (e) { 3868 /*eslint-disable */ 3869 this._container._leaflet_id = undefined; 3870 /* eslint-enable */ 3871 this._containerId = undefined; 3872 } 3873 3874 if (this._locationWatchId !== undefined) { 3875 this.stopLocate(); 3876 } 3877 3878 this._stop(); 3879 3880 remove(this._mapPane); 3881 3882 if (this._clearControlPos) { 3883 this._clearControlPos(); 3884 } 3885 if (this._resizeRequest) { 3886 cancelAnimFrame(this._resizeRequest); 3887 this._resizeRequest = null; 3888 } 3889 3890 this._clearHandlers(); 3891 3892 if (this._loaded) { 3893 // @section Map state change events 3894 // @event unload: Event 3895 // Fired when the map is destroyed with [remove](#map-remove) method. 3896 this.fire('unload'); 3897 } 3898 3899 var i; 3900 for (i in this._layers) { 3901 this._layers[i].remove(); 3902 } 3903 for (i in this._panes) { 3904 remove(this._panes[i]); 3905 } 3906 3907 this._layers = []; 3908 this._panes = []; 3909 delete this._mapPane; 3910 delete this._renderer; 3911 3912 return this; 3913 }, 3914 3915 // @section Other Methods 3916 // @method createPane(name: String, container?: HTMLElement): HTMLElement 3917 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 3918 // then returns it. The pane is created as a child of `container`, or 3919 // as a child of the main map pane if not set. 3920 createPane: function (name, container) { 3921 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3922 pane = create$1('div', className, container || this._mapPane); 3923 3924 if (name) { 3925 this._panes[name] = pane; 3926 } 3927 return pane; 3928 }, 3929 3930 // @section Methods for Getting Map State 3931 3932 // @method getCenter(): LatLng 3933 // Returns the geographical center of the map view 3934 getCenter: function () { 3935 this._checkIfLoaded(); 3936 3937 if (this._lastCenter && !this._moved()) { 3938 return this._lastCenter.clone(); 3939 } 3940 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3941 }, 3942 3943 // @method getZoom(): Number 3944 // Returns the current zoom level of the map view 3945 getZoom: function () { 3946 return this._zoom; 3947 }, 3948 3949 // @method getBounds(): LatLngBounds 3950 // Returns the geographical bounds visible in the current map view 3951 getBounds: function () { 3952 var bounds = this.getPixelBounds(), 3953 sw = this.unproject(bounds.getBottomLeft()), 3954 ne = this.unproject(bounds.getTopRight()); 3955 3956 return new LatLngBounds(sw, ne); 3957 }, 3958 3959 // @method getMinZoom(): Number 3960 // 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. 3961 getMinZoom: function () { 3962 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3963 }, 3964 3965 // @method getMaxZoom(): Number 3966 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3967 getMaxZoom: function () { 3968 return this.options.maxZoom === undefined ? 3969 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3970 this.options.maxZoom; 3971 }, 3972 3973 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 3974 // Returns the maximum zoom level on which the given bounds fit to the map 3975 // view in its entirety. If `inside` (optional) is set to `true`, the method 3976 // instead returns the minimum zoom level on which the map view fits into 3977 // the given bounds in its entirety. 3978 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3979 bounds = toLatLngBounds(bounds); 3980 padding = toPoint(padding || [0, 0]); 3981 3982 var zoom = this.getZoom() || 0, 3983 min = this.getMinZoom(), 3984 max = this.getMaxZoom(), 3985 nw = bounds.getNorthWest(), 3986 se = bounds.getSouthEast(), 3987 size = this.getSize().subtract(padding), 3988 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3989 snap = Browser.any3d ? this.options.zoomSnap : 1, 3990 scalex = size.x / boundsSize.x, 3991 scaley = size.y / boundsSize.y, 3992 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 3993 3994 zoom = this.getScaleZoom(scale, zoom); 3995 3996 if (snap) { 3997 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3998 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3999 } 4000 4001 return Math.max(min, Math.min(max, zoom)); 4002 }, 4003 4004 // @method getSize(): Point 4005 // Returns the current size of the map container (in pixels). 4006 getSize: function () { 4007 if (!this._size || this._sizeChanged) { 4008 this._size = new Point( 4009 this._container.clientWidth || 0, 4010 this._container.clientHeight || 0); 4011 4012 this._sizeChanged = false; 4013 } 4014 return this._size.clone(); 4015 }, 4016 4017 // @method getPixelBounds(): Bounds 4018 // Returns the bounds of the current map view in projected pixel 4019 // coordinates (sometimes useful in layer and overlay implementations). 4020 getPixelBounds: function (center, zoom) { 4021 var topLeftPoint = this._getTopLeftPoint(center, zoom); 4022 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 4023 }, 4024 4025 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 4026 // the map pane? "left point of the map layer" can be confusing, specially 4027 // since there can be negative offsets. 4028 // @method getPixelOrigin(): Point 4029 // Returns the projected pixel coordinates of the top left point of 4030 // the map layer (useful in custom layer and overlay implementations). 4031 getPixelOrigin: function () { 4032 this._checkIfLoaded(); 4033 return this._pixelOrigin; 4034 }, 4035 4036 // @method getPixelWorldBounds(zoom?: Number): Bounds 4037 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 4038 // If `zoom` is omitted, the map's current zoom level is used. 4039 getPixelWorldBounds: function (zoom) { 4040 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 4041 }, 4042 4043 // @section Other Methods 4044 4045 // @method getPane(pane: String|HTMLElement): HTMLElement 4046 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 4047 getPane: function (pane) { 4048 return typeof pane === 'string' ? this._panes[pane] : pane; 4049 }, 4050 4051 // @method getPanes(): Object 4052 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 4053 // the panes as values. 4054 getPanes: function () { 4055 return this._panes; 4056 }, 4057 4058 // @method getContainer: HTMLElement 4059 // Returns the HTML element that contains the map. 4060 getContainer: function () { 4061 return this._container; 4062 }, 4063 4064 4065 // @section Conversion Methods 4066 4067 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 4068 // Returns the scale factor to be applied to a map transition from zoom level 4069 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 4070 getZoomScale: function (toZoom, fromZoom) { 4071 // TODO replace with universal implementation after refactoring projections 4072 var crs = this.options.crs; 4073 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 4074 return crs.scale(toZoom) / crs.scale(fromZoom); 4075 }, 4076 4077 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 4078 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 4079 // level and everything is scaled by a factor of `scale`. Inverse of 4080 // [`getZoomScale`](#map-getZoomScale). 4081 getScaleZoom: function (scale, fromZoom) { 4082 var crs = this.options.crs; 4083 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 4084 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 4085 return isNaN(zoom) ? Infinity : zoom; 4086 }, 4087 4088 // @method project(latlng: LatLng, zoom: Number): Point 4089 // Projects a geographical coordinate `LatLng` according to the projection 4090 // of the map's CRS, then scales it according to `zoom` and the CRS's 4091 // `Transformation`. The result is pixel coordinate relative to 4092 // the CRS origin. 4093 project: function (latlng, zoom) { 4094 zoom = zoom === undefined ? this._zoom : zoom; 4095 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 4096 }, 4097 4098 // @method unproject(point: Point, zoom: Number): LatLng 4099 // Inverse of [`project`](#map-project). 4100 unproject: function (point, zoom) { 4101 zoom = zoom === undefined ? this._zoom : zoom; 4102 return this.options.crs.pointToLatLng(toPoint(point), zoom); 4103 }, 4104 4105 // @method layerPointToLatLng(point: Point): LatLng 4106 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4107 // returns the corresponding geographical coordinate (for the current zoom level). 4108 layerPointToLatLng: function (point) { 4109 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 4110 return this.unproject(projectedPoint); 4111 }, 4112 4113 // @method latLngToLayerPoint(latlng: LatLng): Point 4114 // Given a geographical coordinate, returns the corresponding pixel coordinate 4115 // relative to the [origin pixel](#map-getpixelorigin). 4116 latLngToLayerPoint: function (latlng) { 4117 var projectedPoint = this.project(toLatLng(latlng))._round(); 4118 return projectedPoint._subtract(this.getPixelOrigin()); 4119 }, 4120 4121 // @method wrapLatLng(latlng: LatLng): LatLng 4122 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 4123 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 4124 // CRS's bounds. 4125 // By default this means longitude is wrapped around the dateline so its 4126 // value is between -180 and +180 degrees. 4127 wrapLatLng: function (latlng) { 4128 return this.options.crs.wrapLatLng(toLatLng(latlng)); 4129 }, 4130 4131 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 4132 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 4133 // its center is within the CRS's bounds. 4134 // By default this means the center longitude is wrapped around the dateline so its 4135 // value is between -180 and +180 degrees, and the majority of the bounds 4136 // overlaps the CRS's bounds. 4137 wrapLatLngBounds: function (latlng) { 4138 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 4139 }, 4140 4141 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 4142 // Returns the distance between two geographical coordinates according to 4143 // the map's CRS. By default this measures distance in meters. 4144 distance: function (latlng1, latlng2) { 4145 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 4146 }, 4147 4148 // @method containerPointToLayerPoint(point: Point): Point 4149 // Given a pixel coordinate relative to the map container, returns the corresponding 4150 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 4151 containerPointToLayerPoint: function (point) { // (Point) 4152 return toPoint(point).subtract(this._getMapPanePos()); 4153 }, 4154 4155 // @method layerPointToContainerPoint(point: Point): Point 4156 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4157 // returns the corresponding pixel coordinate relative to the map container. 4158 layerPointToContainerPoint: function (point) { // (Point) 4159 return toPoint(point).add(this._getMapPanePos()); 4160 }, 4161 4162 // @method containerPointToLatLng(point: Point): LatLng 4163 // Given a pixel coordinate relative to the map container, returns 4164 // the corresponding geographical coordinate (for the current zoom level). 4165 containerPointToLatLng: function (point) { 4166 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4167 return this.layerPointToLatLng(layerPoint); 4168 }, 4169 4170 // @method latLngToContainerPoint(latlng: LatLng): Point 4171 // Given a geographical coordinate, returns the corresponding pixel coordinate 4172 // relative to the map container. 4173 latLngToContainerPoint: function (latlng) { 4174 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4175 }, 4176 4177 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4178 // Given a MouseEvent object, returns the pixel coordinate relative to the 4179 // map container where the event took place. 4180 mouseEventToContainerPoint: function (e) { 4181 return getMousePosition(e, this._container); 4182 }, 4183 4184 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4185 // Given a MouseEvent object, returns the pixel coordinate relative to 4186 // the [origin pixel](#map-getpixelorigin) where the event took place. 4187 mouseEventToLayerPoint: function (e) { 4188 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4189 }, 4190 4191 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4192 // Given a MouseEvent object, returns geographical coordinate where the 4193 // event took place. 4194 mouseEventToLatLng: function (e) { // (MouseEvent) 4195 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4196 }, 4197 4198 4199 // map initialization methods 4200 4201 _initContainer: function (id) { 4202 var container = this._container = get(id); 4203 4204 if (!container) { 4205 throw new Error('Map container not found.'); 4206 } else if (container._leaflet_id) { 4207 throw new Error('Map container is already initialized.'); 4208 } 4209 4210 on(container, 'scroll', this._onScroll, this); 4211 this._containerId = stamp(container); 4212 }, 4213 4214 _initLayout: function () { 4215 var container = this._container; 4216 4217 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d; 4218 4219 addClass(container, 'leaflet-container' + 4220 (Browser.touch ? ' leaflet-touch' : '') + 4221 (Browser.retina ? ' leaflet-retina' : '') + 4222 (Browser.ielt9 ? ' leaflet-oldie' : '') + 4223 (Browser.safari ? ' leaflet-safari' : '') + 4224 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4225 4226 var position = getStyle(container, 'position'); 4227 4228 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') { 4229 container.style.position = 'relative'; 4230 } 4231 4232 this._initPanes(); 4233 4234 if (this._initControlPos) { 4235 this._initControlPos(); 4236 } 4237 }, 4238 4239 _initPanes: function () { 4240 var panes = this._panes = {}; 4241 this._paneRenderers = {}; 4242 4243 // @section 4244 // 4245 // Panes are DOM elements used to control the ordering of layers on the map. You 4246 // can access panes with [`map.getPane`](#map-getpane) or 4247 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4248 // [`map.createPane`](#map-createpane) method. 4249 // 4250 // Every map has the following default panes that differ only in zIndex. 4251 // 4252 // @pane mapPane: HTMLElement = 'auto' 4253 // Pane that contains all other map panes 4254 4255 this._mapPane = this.createPane('mapPane', this._container); 4256 setPosition(this._mapPane, new Point(0, 0)); 4257 4258 // @pane tilePane: HTMLElement = 200 4259 // Pane for `GridLayer`s and `TileLayer`s 4260 this.createPane('tilePane'); 4261 // @pane overlayPane: HTMLElement = 400 4262 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4263 this.createPane('overlayPane'); 4264 // @pane shadowPane: HTMLElement = 500 4265 // Pane for overlay shadows (e.g. `Marker` shadows) 4266 this.createPane('shadowPane'); 4267 // @pane markerPane: HTMLElement = 600 4268 // Pane for `Icon`s of `Marker`s 4269 this.createPane('markerPane'); 4270 // @pane tooltipPane: HTMLElement = 650 4271 // Pane for `Tooltip`s. 4272 this.createPane('tooltipPane'); 4273 // @pane popupPane: HTMLElement = 700 4274 // Pane for `Popup`s. 4275 this.createPane('popupPane'); 4276 4277 if (!this.options.markerZoomAnimation) { 4278 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4279 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4280 } 4281 }, 4282 4283 4284 // private methods that modify map state 4285 4286 // @section Map state change events 4287 _resetView: function (center, zoom, noMoveStart) { 4288 setPosition(this._mapPane, new Point(0, 0)); 4289 4290 var loading = !this._loaded; 4291 this._loaded = true; 4292 zoom = this._limitZoom(zoom); 4293 4294 this.fire('viewprereset'); 4295 4296 var zoomChanged = this._zoom !== zoom; 4297 this 4298 ._moveStart(zoomChanged, noMoveStart) 4299 ._move(center, zoom) 4300 ._moveEnd(zoomChanged); 4301 4302 // @event viewreset: Event 4303 // Fired when the map needs to redraw its content (this usually happens 4304 // on map zoom or load). Very useful for creating custom overlays. 4305 this.fire('viewreset'); 4306 4307 // @event load: Event 4308 // Fired when the map is initialized (when its center and zoom are set 4309 // for the first time). 4310 if (loading) { 4311 this.fire('load'); 4312 } 4313 }, 4314 4315 _moveStart: function (zoomChanged, noMoveStart) { 4316 // @event zoomstart: Event 4317 // Fired when the map zoom is about to change (e.g. before zoom animation). 4318 // @event movestart: Event 4319 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4320 if (zoomChanged) { 4321 this.fire('zoomstart'); 4322 } 4323 if (!noMoveStart) { 4324 this.fire('movestart'); 4325 } 4326 return this; 4327 }, 4328 4329 _move: function (center, zoom, data, supressEvent) { 4330 if (zoom === undefined) { 4331 zoom = this._zoom; 4332 } 4333 var zoomChanged = this._zoom !== zoom; 4334 4335 this._zoom = zoom; 4336 this._lastCenter = center; 4337 this._pixelOrigin = this._getNewPixelOrigin(center); 4338 4339 if (!supressEvent) { 4340 // @event zoom: Event 4341 // Fired repeatedly during any change in zoom level, 4342 // including zoom and fly animations. 4343 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4344 this.fire('zoom', data); 4345 } 4346 4347 // @event move: Event 4348 // Fired repeatedly during any movement of the map, 4349 // including pan and fly animations. 4350 this.fire('move', data); 4351 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530 4352 this.fire('zoom', data); 4353 } 4354 return this; 4355 }, 4356 4357 _moveEnd: function (zoomChanged) { 4358 // @event zoomend: Event 4359 // Fired when the map zoom changed, after any animations. 4360 if (zoomChanged) { 4361 this.fire('zoomend'); 4362 } 4363 4364 // @event moveend: Event 4365 // Fired when the center of the map stops changing 4366 // (e.g. user stopped dragging the map or after non-centered zoom). 4367 return this.fire('moveend'); 4368 }, 4369 4370 _stop: function () { 4371 cancelAnimFrame(this._flyToFrame); 4372 if (this._panAnim) { 4373 this._panAnim.stop(); 4374 } 4375 return this; 4376 }, 4377 4378 _rawPanBy: function (offset) { 4379 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4380 }, 4381 4382 _getZoomSpan: function () { 4383 return this.getMaxZoom() - this.getMinZoom(); 4384 }, 4385 4386 _panInsideMaxBounds: function () { 4387 if (!this._enforcingBounds) { 4388 this.panInsideBounds(this.options.maxBounds); 4389 } 4390 }, 4391 4392 _checkIfLoaded: function () { 4393 if (!this._loaded) { 4394 throw new Error('Set map center and zoom first.'); 4395 } 4396 }, 4397 4398 // DOM event handling 4399 4400 // @section Interaction events 4401 _initEvents: function (remove) { 4402 this._targets = {}; 4403 this._targets[stamp(this._container)] = this; 4404 4405 var onOff = remove ? off : on; 4406 4407 // @event click: MouseEvent 4408 // Fired when the user clicks (or taps) the map. 4409 // @event dblclick: MouseEvent 4410 // Fired when the user double-clicks (or double-taps) the map. 4411 // @event mousedown: MouseEvent 4412 // Fired when the user pushes the mouse button on the map. 4413 // @event mouseup: MouseEvent 4414 // Fired when the user releases the mouse button on the map. 4415 // @event mouseover: MouseEvent 4416 // Fired when the mouse enters the map. 4417 // @event mouseout: MouseEvent 4418 // Fired when the mouse leaves the map. 4419 // @event mousemove: MouseEvent 4420 // Fired while the mouse moves over the map. 4421 // @event contextmenu: MouseEvent 4422 // Fired when the user pushes the right mouse button on the map, prevents 4423 // default browser context menu from showing if there are listeners on 4424 // this event. Also fired on mobile when the user holds a single touch 4425 // for a second (also called long press). 4426 // @event keypress: KeyboardEvent 4427 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused. 4428 // @event keydown: KeyboardEvent 4429 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event, 4430 // the `keydown` event is fired for keys that produce a character value and for keys 4431 // that do not produce a character value. 4432 // @event keyup: KeyboardEvent 4433 // Fired when the user releases a key from the keyboard while the map is focused. 4434 onOff(this._container, 'click dblclick mousedown mouseup ' + 4435 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this); 4436 4437 if (this.options.trackResize) { 4438 onOff(window, 'resize', this._onResize, this); 4439 } 4440 4441 if (Browser.any3d && this.options.transform3DLimit) { 4442 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4443 } 4444 }, 4445 4446 _onResize: function () { 4447 cancelAnimFrame(this._resizeRequest); 4448 this._resizeRequest = requestAnimFrame( 4449 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4450 }, 4451 4452 _onScroll: function () { 4453 this._container.scrollTop = 0; 4454 this._container.scrollLeft = 0; 4455 }, 4456 4457 _onMoveEnd: function () { 4458 var pos = this._getMapPanePos(); 4459 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4460 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4461 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/ 4462 this._resetView(this.getCenter(), this.getZoom()); 4463 } 4464 }, 4465 4466 _findEventTargets: function (e, type) { 4467 var targets = [], 4468 target, 4469 isHover = type === 'mouseout' || type === 'mouseover', 4470 src = e.target || e.srcElement, 4471 dragging = false; 4472 4473 while (src) { 4474 target = this._targets[stamp(src)]; 4475 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) { 4476 // Prevent firing click after you just dragged an object. 4477 dragging = true; 4478 break; 4479 } 4480 if (target && target.listens(type, true)) { 4481 if (isHover && !isExternalTarget(src, e)) { break; } 4482 targets.push(target); 4483 if (isHover) { break; } 4484 } 4485 if (src === this._container) { break; } 4486 src = src.parentNode; 4487 } 4488 if (!targets.length && !dragging && !isHover && this.listens(type, true)) { 4489 targets = [this]; 4490 } 4491 return targets; 4492 }, 4493 4494 _isClickDisabled: function (el) { 4495 while (el && el !== this._container) { 4496 if (el['_leaflet_disable_click']) { return true; } 4497 el = el.parentNode; 4498 } 4499 }, 4500 4501 _handleDOMEvent: function (e) { 4502 var el = (e.target || e.srcElement); 4503 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) { 4504 return; 4505 } 4506 4507 var type = e.type; 4508 4509 if (type === 'mousedown') { 4510 // prevents outline when clicking on keyboard-focusable element 4511 preventOutline(el); 4512 } 4513 4514 this._fireDOMEvent(e, type); 4515 }, 4516 4517 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4518 4519 _fireDOMEvent: function (e, type, canvasTargets) { 4520 4521 if (e.type === 'click') { 4522 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4523 // @event preclick: MouseEvent 4524 // Fired before mouse click on the map (sometimes useful when you 4525 // want something to happen on click before any existing click 4526 // handlers start running). 4527 var synth = extend({}, e); 4528 synth.type = 'preclick'; 4529 this._fireDOMEvent(synth, synth.type, canvasTargets); 4530 } 4531 4532 // Find the layer the event is propagating from and its parents. 4533 var targets = this._findEventTargets(e, type); 4534 4535 if (canvasTargets) { 4536 var filtered = []; // pick only targets with listeners 4537 for (var i = 0; i < canvasTargets.length; i++) { 4538 if (canvasTargets[i].listens(type, true)) { 4539 filtered.push(canvasTargets[i]); 4540 } 4541 } 4542 targets = filtered.concat(targets); 4543 } 4544 4545 if (!targets.length) { return; } 4546 4547 if (type === 'contextmenu') { 4548 preventDefault(e); 4549 } 4550 4551 var target = targets[0]; 4552 var data = { 4553 originalEvent: e 4554 }; 4555 4556 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') { 4557 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4558 data.containerPoint = isMarker ? 4559 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4560 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4561 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4562 } 4563 4564 for (i = 0; i < targets.length; i++) { 4565 targets[i].fire(type, data, true); 4566 if (data.originalEvent._stopped || 4567 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4568 } 4569 }, 4570 4571 _draggableMoved: function (obj) { 4572 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4573 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4574 }, 4575 4576 _clearHandlers: function () { 4577 for (var i = 0, len = this._handlers.length; i < len; i++) { 4578 this._handlers[i].disable(); 4579 } 4580 }, 4581 4582 // @section Other Methods 4583 4584 // @method whenReady(fn: Function, context?: Object): this 4585 // Runs the given function `fn` when the map gets initialized with 4586 // a view (center and zoom) and at least one layer, or immediately 4587 // if it's already initialized, optionally passing a function context. 4588 whenReady: function (callback, context) { 4589 if (this._loaded) { 4590 callback.call(context || this, {target: this}); 4591 } else { 4592 this.on('load', callback, context); 4593 } 4594 return this; 4595 }, 4596 4597 4598 // private methods for getting map state 4599 4600 _getMapPanePos: function () { 4601 return getPosition(this._mapPane) || new Point(0, 0); 4602 }, 4603 4604 _moved: function () { 4605 var pos = this._getMapPanePos(); 4606 return pos && !pos.equals([0, 0]); 4607 }, 4608 4609 _getTopLeftPoint: function (center, zoom) { 4610 var pixelOrigin = center && zoom !== undefined ? 4611 this._getNewPixelOrigin(center, zoom) : 4612 this.getPixelOrigin(); 4613 return pixelOrigin.subtract(this._getMapPanePos()); 4614 }, 4615 4616 _getNewPixelOrigin: function (center, zoom) { 4617 var viewHalf = this.getSize()._divideBy(2); 4618 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4619 }, 4620 4621 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4622 var topLeft = this._getNewPixelOrigin(center, zoom); 4623 return this.project(latlng, zoom)._subtract(topLeft); 4624 }, 4625 4626 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4627 var topLeft = this._getNewPixelOrigin(center, zoom); 4628 return toBounds([ 4629 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4630 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4631 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4632 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4633 ]); 4634 }, 4635 4636 // layer point of the current center 4637 _getCenterLayerPoint: function () { 4638 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4639 }, 4640 4641 // offset of the specified place to the current center in pixels 4642 _getCenterOffset: function (latlng) { 4643 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4644 }, 4645 4646 // adjust center for view to get inside bounds 4647 _limitCenter: function (center, zoom, bounds) { 4648 4649 if (!bounds) { return center; } 4650 4651 var centerPoint = this.project(center, zoom), 4652 viewHalf = this.getSize().divideBy(2), 4653 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4654 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4655 4656 // If offset is less than a pixel, ignore. 4657 // This prevents unstable projections from getting into 4658 // an infinite loop of tiny offsets. 4659 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) { 4660 return center; 4661 } 4662 4663 return this.unproject(centerPoint.add(offset), zoom); 4664 }, 4665 4666 // adjust offset for view to get inside bounds 4667 _limitOffset: function (offset, bounds) { 4668 if (!bounds) { return offset; } 4669 4670 var viewBounds = this.getPixelBounds(), 4671 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4672 4673 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4674 }, 4675 4676 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4677 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4678 var projectedMaxBounds = toBounds( 4679 this.project(maxBounds.getNorthEast(), zoom), 4680 this.project(maxBounds.getSouthWest(), zoom) 4681 ), 4682 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4683 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4684 4685 dx = this._rebound(minOffset.x, -maxOffset.x), 4686 dy = this._rebound(minOffset.y, -maxOffset.y); 4687 4688 return new Point(dx, dy); 4689 }, 4690 4691 _rebound: function (left, right) { 4692 return left + right > 0 ? 4693 Math.round(left - right) / 2 : 4694 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4695 }, 4696 4697 _limitZoom: function (zoom) { 4698 var min = this.getMinZoom(), 4699 max = this.getMaxZoom(), 4700 snap = Browser.any3d ? this.options.zoomSnap : 1; 4701 if (snap) { 4702 zoom = Math.round(zoom / snap) * snap; 4703 } 4704 return Math.max(min, Math.min(max, zoom)); 4705 }, 4706 4707 _onPanTransitionStep: function () { 4708 this.fire('move'); 4709 }, 4710 4711 _onPanTransitionEnd: function () { 4712 removeClass(this._mapPane, 'leaflet-pan-anim'); 4713 this.fire('moveend'); 4714 }, 4715 4716 _tryAnimatedPan: function (center, options) { 4717 // difference between the new and current centers in pixels 4718 var offset = this._getCenterOffset(center)._trunc(); 4719 4720 // don't animate too far unless animate: true specified in options 4721 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4722 4723 this.panBy(offset, options); 4724 4725 return true; 4726 }, 4727 4728 _createAnimProxy: function () { 4729 4730 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4731 this._panes.mapPane.appendChild(proxy); 4732 4733 this.on('zoomanim', function (e) { 4734 var prop = TRANSFORM, 4735 transform = this._proxy.style[prop]; 4736 4737 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4738 4739 // workaround for case when transform is the same and so transitionend event is not fired 4740 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4741 this._onZoomTransitionEnd(); 4742 } 4743 }, this); 4744 4745 this.on('load moveend', this._animMoveEnd, this); 4746 4747 this._on('unload', this._destroyAnimProxy, this); 4748 }, 4749 4750 _destroyAnimProxy: function () { 4751 remove(this._proxy); 4752 this.off('load moveend', this._animMoveEnd, this); 4753 delete this._proxy; 4754 }, 4755 4756 _animMoveEnd: function () { 4757 var c = this.getCenter(), 4758 z = this.getZoom(); 4759 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 4760 }, 4761 4762 _catchTransitionEnd: function (e) { 4763 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 4764 this._onZoomTransitionEnd(); 4765 } 4766 }, 4767 4768 _nothingToAnimate: function () { 4769 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 4770 }, 4771 4772 _tryAnimatedZoom: function (center, zoom, options) { 4773 4774 if (this._animatingZoom) { return true; } 4775 4776 options = options || {}; 4777 4778 // don't animate if disabled, not supported or zoom difference is too large 4779 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 4780 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 4781 4782 // offset is the pixel coords of the zoom origin relative to the current center 4783 var scale = this.getZoomScale(zoom), 4784 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 4785 4786 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 4787 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 4788 4789 requestAnimFrame(function () { 4790 this 4791 ._moveStart(true, options.noMoveStart || false) 4792 ._animateZoom(center, zoom, true); 4793 }, this); 4794 4795 return true; 4796 }, 4797 4798 _animateZoom: function (center, zoom, startAnim, noUpdate) { 4799 if (!this._mapPane) { return; } 4800 4801 if (startAnim) { 4802 this._animatingZoom = true; 4803 4804 // remember what center/zoom to set after animation 4805 this._animateToCenter = center; 4806 this._animateToZoom = zoom; 4807 4808 addClass(this._mapPane, 'leaflet-zoom-anim'); 4809 } 4810 4811 // @section Other Events 4812 // @event zoomanim: ZoomAnimEvent 4813 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom. 4814 this.fire('zoomanim', { 4815 center: center, 4816 zoom: zoom, 4817 noUpdate: noUpdate 4818 }); 4819 4820 if (!this._tempFireZoomEvent) { 4821 this._tempFireZoomEvent = this._zoom !== this._animateToZoom; 4822 } 4823 4824 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 4825 4826 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 4827 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 4828 }, 4829 4830 _onZoomTransitionEnd: function () { 4831 if (!this._animatingZoom) { return; } 4832 4833 if (this._mapPane) { 4834 removeClass(this._mapPane, 'leaflet-zoom-anim'); 4835 } 4836 4837 this._animatingZoom = false; 4838 4839 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 4840 4841 if (this._tempFireZoomEvent) { 4842 this.fire('zoom'); 4843 } 4844 delete this._tempFireZoomEvent; 4845 4846 this.fire('move'); 4847 4848 this._moveEnd(true); 4849 } 4850 }); 4851 4852 // @section 4853 4854 // @factory L.map(id: String, options?: Map options) 4855 // Instantiates a map object given the DOM ID of a `<div>` element 4856 // and optionally an object literal with `Map options`. 4857 // 4858 // @alternative 4859 // @factory L.map(el: HTMLElement, options?: Map options) 4860 // Instantiates a map object given an instance of a `<div>` HTML element 4861 // and optionally an object literal with `Map options`. 4862 function createMap(id, options) { 4863 return new Map(id, options); 4864 } 4865 4866 /* 4867 * @class Control 4868 * @aka L.Control 4869 * @inherits Class 4870 * 4871 * L.Control is a base class for implementing map controls. Handles positioning. 4872 * All other controls extend from this class. 4873 */ 4874 4875 var Control = Class.extend({ 4876 // @section 4877 // @aka Control Options 4878 options: { 4879 // @option position: String = 'topright' 4880 // The position of the control (one of the map corners). Possible values are `'topleft'`, 4881 // `'topright'`, `'bottomleft'` or `'bottomright'` 4882 position: 'topright' 4883 }, 4884 4885 initialize: function (options) { 4886 setOptions(this, options); 4887 }, 4888 4889 /* @section 4890 * Classes extending L.Control will inherit the following methods: 4891 * 4892 * @method getPosition: string 4893 * Returns the position of the control. 4894 */ 4895 getPosition: function () { 4896 return this.options.position; 4897 }, 4898 4899 // @method setPosition(position: string): this 4900 // Sets the position of the control. 4901 setPosition: function (position) { 4902 var map = this._map; 4903 4904 if (map) { 4905 map.removeControl(this); 4906 } 4907 4908 this.options.position = position; 4909 4910 if (map) { 4911 map.addControl(this); 4912 } 4913 4914 return this; 4915 }, 4916 4917 // @method getContainer: HTMLElement 4918 // Returns the HTMLElement that contains the control. 4919 getContainer: function () { 4920 return this._container; 4921 }, 4922 4923 // @method addTo(map: Map): this 4924 // Adds the control to the given map. 4925 addTo: function (map) { 4926 this.remove(); 4927 this._map = map; 4928 4929 var container = this._container = this.onAdd(map), 4930 pos = this.getPosition(), 4931 corner = map._controlCorners[pos]; 4932 4933 addClass(container, 'leaflet-control'); 4934 4935 if (pos.indexOf('bottom') !== -1) { 4936 corner.insertBefore(container, corner.firstChild); 4937 } else { 4938 corner.appendChild(container); 4939 } 4940 4941 this._map.on('unload', this.remove, this); 4942 4943 return this; 4944 }, 4945 4946 // @method remove: this 4947 // Removes the control from the map it is currently active on. 4948 remove: function () { 4949 if (!this._map) { 4950 return this; 4951 } 4952 4953 remove(this._container); 4954 4955 if (this.onRemove) { 4956 this.onRemove(this._map); 4957 } 4958 4959 this._map.off('unload', this.remove, this); 4960 this._map = null; 4961 4962 return this; 4963 }, 4964 4965 _refocusOnMap: function (e) { 4966 // if map exists and event is not a keyboard event 4967 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 4968 this._map.getContainer().focus(); 4969 } 4970 } 4971 }); 4972 4973 var control = function (options) { 4974 return new Control(options); 4975 }; 4976 4977 /* @section Extension methods 4978 * @uninheritable 4979 * 4980 * Every control should extend from `L.Control` and (re-)implement the following methods. 4981 * 4982 * @method onAdd(map: Map): HTMLElement 4983 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 4984 * 4985 * @method onRemove(map: Map) 4986 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 4987 */ 4988 4989 /* @namespace Map 4990 * @section Methods for Layers and Controls 4991 */ 4992 Map.include({ 4993 // @method addControl(control: Control): this 4994 // Adds the given control to the map 4995 addControl: function (control) { 4996 control.addTo(this); 4997 return this; 4998 }, 4999 5000 // @method removeControl(control: Control): this 5001 // Removes the given control from the map 5002 removeControl: function (control) { 5003 control.remove(); 5004 return this; 5005 }, 5006 5007 _initControlPos: function () { 5008 var corners = this._controlCorners = {}, 5009 l = 'leaflet-', 5010 container = this._controlContainer = 5011 create$1('div', l + 'control-container', this._container); 5012 5013 function createCorner(vSide, hSide) { 5014 var className = l + vSide + ' ' + l + hSide; 5015 5016 corners[vSide + hSide] = create$1('div', className, container); 5017 } 5018 5019 createCorner('top', 'left'); 5020 createCorner('top', 'right'); 5021 createCorner('bottom', 'left'); 5022 createCorner('bottom', 'right'); 5023 }, 5024 5025 _clearControlPos: function () { 5026 for (var i in this._controlCorners) { 5027 remove(this._controlCorners[i]); 5028 } 5029 remove(this._controlContainer); 5030 delete this._controlCorners; 5031 delete this._controlContainer; 5032 } 5033 }); 5034 5035 /* 5036 * @class Control.Layers 5037 * @aka L.Control.Layers 5038 * @inherits Control 5039 * 5040 * 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`. 5041 * 5042 * @example 5043 * 5044 * ```js 5045 * var baseLayers = { 5046 * "Mapbox": mapbox, 5047 * "OpenStreetMap": osm 5048 * }; 5049 * 5050 * var overlays = { 5051 * "Marker": marker, 5052 * "Roads": roadsLayer 5053 * }; 5054 * 5055 * L.control.layers(baseLayers, overlays).addTo(map); 5056 * ``` 5057 * 5058 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 5059 * 5060 * ```js 5061 * { 5062 * "<someName1>": layer1, 5063 * "<someName2>": layer2 5064 * } 5065 * ``` 5066 * 5067 * The layer names can contain HTML, which allows you to add additional styling to the items: 5068 * 5069 * ```js 5070 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 5071 * ``` 5072 */ 5073 5074 var Layers = Control.extend({ 5075 // @section 5076 // @aka Control.Layers options 5077 options: { 5078 // @option collapsed: Boolean = true 5079 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation. 5080 collapsed: true, 5081 position: 'topright', 5082 5083 // @option autoZIndex: Boolean = true 5084 // 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. 5085 autoZIndex: true, 5086 5087 // @option hideSingleBase: Boolean = false 5088 // If `true`, the base layers in the control will be hidden when there is only one. 5089 hideSingleBase: false, 5090 5091 // @option sortLayers: Boolean = false 5092 // Whether to sort the layers. When `false`, layers will keep the order 5093 // in which they were added to the control. 5094 sortLayers: false, 5095 5096 // @option sortFunction: Function = * 5097 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 5098 // that will be used for sorting the layers, when `sortLayers` is `true`. 5099 // The function receives both the `L.Layer` instances and their names, as in 5100 // `sortFunction(layerA, layerB, nameA, nameB)`. 5101 // By default, it sorts layers alphabetically by their name. 5102 sortFunction: function (layerA, layerB, nameA, nameB) { 5103 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 5104 } 5105 }, 5106 5107 initialize: function (baseLayers, overlays, options) { 5108 setOptions(this, options); 5109 5110 this._layerControlInputs = []; 5111 this._layers = []; 5112 this._lastZIndex = 0; 5113 this._handlingClick = false; 5114 this._preventClick = false; 5115 5116 for (var i in baseLayers) { 5117 this._addLayer(baseLayers[i], i); 5118 } 5119 5120 for (i in overlays) { 5121 this._addLayer(overlays[i], i, true); 5122 } 5123 }, 5124 5125 onAdd: function (map) { 5126 this._initLayout(); 5127 this._update(); 5128 5129 this._map = map; 5130 map.on('zoomend', this._checkDisabledLayers, this); 5131 5132 for (var i = 0; i < this._layers.length; i++) { 5133 this._layers[i].layer.on('add remove', this._onLayerChange, this); 5134 } 5135 5136 return this._container; 5137 }, 5138 5139 addTo: function (map) { 5140 Control.prototype.addTo.call(this, map); 5141 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 5142 return this._expandIfNotCollapsed(); 5143 }, 5144 5145 onRemove: function () { 5146 this._map.off('zoomend', this._checkDisabledLayers, this); 5147 5148 for (var i = 0; i < this._layers.length; i++) { 5149 this._layers[i].layer.off('add remove', this._onLayerChange, this); 5150 } 5151 }, 5152 5153 // @method addBaseLayer(layer: Layer, name: String): this 5154 // Adds a base layer (radio button entry) with the given name to the control. 5155 addBaseLayer: function (layer, name) { 5156 this._addLayer(layer, name); 5157 return (this._map) ? this._update() : this; 5158 }, 5159 5160 // @method addOverlay(layer: Layer, name: String): this 5161 // Adds an overlay (checkbox entry) with the given name to the control. 5162 addOverlay: function (layer, name) { 5163 this._addLayer(layer, name, true); 5164 return (this._map) ? this._update() : this; 5165 }, 5166 5167 // @method removeLayer(layer: Layer): this 5168 // Remove the given layer from the control. 5169 removeLayer: function (layer) { 5170 layer.off('add remove', this._onLayerChange, this); 5171 5172 var obj = this._getLayer(stamp(layer)); 5173 if (obj) { 5174 this._layers.splice(this._layers.indexOf(obj), 1); 5175 } 5176 return (this._map) ? this._update() : this; 5177 }, 5178 5179 // @method expand(): this 5180 // Expand the control container if collapsed. 5181 expand: function () { 5182 addClass(this._container, 'leaflet-control-layers-expanded'); 5183 this._section.style.height = null; 5184 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 5185 if (acceptableHeight < this._section.clientHeight) { 5186 addClass(this._section, 'leaflet-control-layers-scrollbar'); 5187 this._section.style.height = acceptableHeight + 'px'; 5188 } else { 5189 removeClass(this._section, 'leaflet-control-layers-scrollbar'); 5190 } 5191 this._checkDisabledLayers(); 5192 return this; 5193 }, 5194 5195 // @method collapse(): this 5196 // Collapse the control container if expanded. 5197 collapse: function () { 5198 removeClass(this._container, 'leaflet-control-layers-expanded'); 5199 return this; 5200 }, 5201 5202 _initLayout: function () { 5203 var className = 'leaflet-control-layers', 5204 container = this._container = create$1('div', className), 5205 collapsed = this.options.collapsed; 5206 5207 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 5208 container.setAttribute('aria-haspopup', true); 5209 5210 disableClickPropagation(container); 5211 disableScrollPropagation(container); 5212 5213 var section = this._section = create$1('section', className + '-list'); 5214 5215 if (collapsed) { 5216 this._map.on('click', this.collapse, this); 5217 5218 on(container, { 5219 mouseenter: this._expandSafely, 5220 mouseleave: this.collapse 5221 }, this); 5222 } 5223 5224 var link = this._layersLink = create$1('a', className + '-toggle', container); 5225 link.href = '#'; 5226 link.title = 'Layers'; 5227 link.setAttribute('role', 'button'); 5228 5229 on(link, { 5230 keydown: function (e) { 5231 if (e.keyCode === 13) { 5232 this._expandSafely(); 5233 } 5234 }, 5235 // Certain screen readers intercept the key event and instead send a click event 5236 click: function (e) { 5237 preventDefault(e); 5238 this._expandSafely(); 5239 } 5240 }, this); 5241 5242 if (!collapsed) { 5243 this.expand(); 5244 } 5245 5246 this._baseLayersList = create$1('div', className + '-base', section); 5247 this._separator = create$1('div', className + '-separator', section); 5248 this._overlaysList = create$1('div', className + '-overlays', section); 5249 5250 container.appendChild(section); 5251 }, 5252 5253 _getLayer: function (id) { 5254 for (var i = 0; i < this._layers.length; i++) { 5255 5256 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5257 return this._layers[i]; 5258 } 5259 } 5260 }, 5261 5262 _addLayer: function (layer, name, overlay) { 5263 if (this._map) { 5264 layer.on('add remove', this._onLayerChange, this); 5265 } 5266 5267 this._layers.push({ 5268 layer: layer, 5269 name: name, 5270 overlay: overlay 5271 }); 5272 5273 if (this.options.sortLayers) { 5274 this._layers.sort(bind(function (a, b) { 5275 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5276 }, this)); 5277 } 5278 5279 if (this.options.autoZIndex && layer.setZIndex) { 5280 this._lastZIndex++; 5281 layer.setZIndex(this._lastZIndex); 5282 } 5283 5284 this._expandIfNotCollapsed(); 5285 }, 5286 5287 _update: function () { 5288 if (!this._container) { return this; } 5289 5290 empty(this._baseLayersList); 5291 empty(this._overlaysList); 5292 5293 this._layerControlInputs = []; 5294 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5295 5296 for (i = 0; i < this._layers.length; i++) { 5297 obj = this._layers[i]; 5298 this._addItem(obj); 5299 overlaysPresent = overlaysPresent || obj.overlay; 5300 baseLayersPresent = baseLayersPresent || !obj.overlay; 5301 baseLayersCount += !obj.overlay ? 1 : 0; 5302 } 5303 5304 // Hide base layers section if there's only one layer. 5305 if (this.options.hideSingleBase) { 5306 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5307 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5308 } 5309 5310 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5311 5312 return this; 5313 }, 5314 5315 _onLayerChange: function (e) { 5316 if (!this._handlingClick) { 5317 this._update(); 5318 } 5319 5320 var obj = this._getLayer(stamp(e.target)); 5321 5322 // @namespace Map 5323 // @section Layer events 5324 // @event baselayerchange: LayersControlEvent 5325 // Fired when the base layer is changed through the [layers control](#control-layers). 5326 // @event overlayadd: LayersControlEvent 5327 // Fired when an overlay is selected through the [layers control](#control-layers). 5328 // @event overlayremove: LayersControlEvent 5329 // Fired when an overlay is deselected through the [layers control](#control-layers). 5330 // @namespace Control.Layers 5331 var type = obj.overlay ? 5332 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5333 (e.type === 'add' ? 'baselayerchange' : null); 5334 5335 if (type) { 5336 this._map.fire(type, obj); 5337 } 5338 }, 5339 5340 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079) 5341 _createRadioElement: function (name, checked) { 5342 5343 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5344 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5345 5346 var radioFragment = document.createElement('div'); 5347 radioFragment.innerHTML = radioHtml; 5348 5349 return radioFragment.firstChild; 5350 }, 5351 5352 _addItem: function (obj) { 5353 var label = document.createElement('label'), 5354 checked = this._map.hasLayer(obj.layer), 5355 input; 5356 5357 if (obj.overlay) { 5358 input = document.createElement('input'); 5359 input.type = 'checkbox'; 5360 input.className = 'leaflet-control-layers-selector'; 5361 input.defaultChecked = checked; 5362 } else { 5363 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked); 5364 } 5365 5366 this._layerControlInputs.push(input); 5367 input.layerId = stamp(obj.layer); 5368 5369 on(input, 'click', this._onInputClick, this); 5370 5371 var name = document.createElement('span'); 5372 name.innerHTML = ' ' + obj.name; 5373 5374 // Helps from preventing layer control flicker when checkboxes are disabled 5375 // https://github.com/Leaflet/Leaflet/issues/2771 5376 var holder = document.createElement('span'); 5377 5378 label.appendChild(holder); 5379 holder.appendChild(input); 5380 holder.appendChild(name); 5381 5382 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5383 container.appendChild(label); 5384 5385 this._checkDisabledLayers(); 5386 return label; 5387 }, 5388 5389 _onInputClick: function () { 5390 // expanding the control on mobile with a click can cause adding a layer - we don't want this 5391 if (this._preventClick) { 5392 return; 5393 } 5394 5395 var inputs = this._layerControlInputs, 5396 input, layer; 5397 var addedLayers = [], 5398 removedLayers = []; 5399 5400 this._handlingClick = true; 5401 5402 for (var i = inputs.length - 1; i >= 0; i--) { 5403 input = inputs[i]; 5404 layer = this._getLayer(input.layerId).layer; 5405 5406 if (input.checked) { 5407 addedLayers.push(layer); 5408 } else if (!input.checked) { 5409 removedLayers.push(layer); 5410 } 5411 } 5412 5413 // Bugfix issue 2318: Should remove all old layers before readding new ones 5414 for (i = 0; i < removedLayers.length; i++) { 5415 if (this._map.hasLayer(removedLayers[i])) { 5416 this._map.removeLayer(removedLayers[i]); 5417 } 5418 } 5419 for (i = 0; i < addedLayers.length; i++) { 5420 if (!this._map.hasLayer(addedLayers[i])) { 5421 this._map.addLayer(addedLayers[i]); 5422 } 5423 } 5424 5425 this._handlingClick = false; 5426 5427 this._refocusOnMap(); 5428 }, 5429 5430 _checkDisabledLayers: function () { 5431 var inputs = this._layerControlInputs, 5432 input, 5433 layer, 5434 zoom = this._map.getZoom(); 5435 5436 for (var i = inputs.length - 1; i >= 0; i--) { 5437 input = inputs[i]; 5438 layer = this._getLayer(input.layerId).layer; 5439 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5440 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5441 5442 } 5443 }, 5444 5445 _expandIfNotCollapsed: function () { 5446 if (this._map && !this.options.collapsed) { 5447 this.expand(); 5448 } 5449 return this; 5450 }, 5451 5452 _expandSafely: function () { 5453 var section = this._section; 5454 this._preventClick = true; 5455 on(section, 'click', preventDefault); 5456 this.expand(); 5457 var that = this; 5458 setTimeout(function () { 5459 off(section, 'click', preventDefault); 5460 that._preventClick = false; 5461 }); 5462 } 5463 5464 }); 5465 5466 5467 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5468 // 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. 5469 var layers = function (baseLayers, overlays, options) { 5470 return new Layers(baseLayers, overlays, options); 5471 }; 5472 5473 /* 5474 * @class Control.Zoom 5475 * @aka L.Control.Zoom 5476 * @inherits Control 5477 * 5478 * 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`. 5479 */ 5480 5481 var Zoom = Control.extend({ 5482 // @section 5483 // @aka Control.Zoom options 5484 options: { 5485 position: 'topleft', 5486 5487 // @option zoomInText: String = '<span aria-hidden="true">+</span>' 5488 // The text set on the 'zoom in' button. 5489 zoomInText: '<span aria-hidden="true">+</span>', 5490 5491 // @option zoomInTitle: String = 'Zoom in' 5492 // The title set on the 'zoom in' button. 5493 zoomInTitle: 'Zoom in', 5494 5495 // @option zoomOutText: String = '<span aria-hidden="true">−</span>' 5496 // The text set on the 'zoom out' button. 5497 zoomOutText: '<span aria-hidden="true">−</span>', 5498 5499 // @option zoomOutTitle: String = 'Zoom out' 5500 // The title set on the 'zoom out' button. 5501 zoomOutTitle: 'Zoom out' 5502 }, 5503 5504 onAdd: function (map) { 5505 var zoomName = 'leaflet-control-zoom', 5506 container = create$1('div', zoomName + ' leaflet-bar'), 5507 options = this.options; 5508 5509 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5510 zoomName + '-in', container, this._zoomIn); 5511 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5512 zoomName + '-out', container, this._zoomOut); 5513 5514 this._updateDisabled(); 5515 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5516 5517 return container; 5518 }, 5519 5520 onRemove: function (map) { 5521 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5522 }, 5523 5524 disable: function () { 5525 this._disabled = true; 5526 this._updateDisabled(); 5527 return this; 5528 }, 5529 5530 enable: function () { 5531 this._disabled = false; 5532 this._updateDisabled(); 5533 return this; 5534 }, 5535 5536 _zoomIn: function (e) { 5537 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5538 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5539 } 5540 }, 5541 5542 _zoomOut: function (e) { 5543 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5544 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5545 } 5546 }, 5547 5548 _createButton: function (html, title, className, container, fn) { 5549 var link = create$1('a', className, container); 5550 link.innerHTML = html; 5551 link.href = '#'; 5552 link.title = title; 5553 5554 /* 5555 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5556 */ 5557 link.setAttribute('role', 'button'); 5558 link.setAttribute('aria-label', title); 5559 5560 disableClickPropagation(link); 5561 on(link, 'click', stop); 5562 on(link, 'click', fn, this); 5563 on(link, 'click', this._refocusOnMap, this); 5564 5565 return link; 5566 }, 5567 5568 _updateDisabled: function () { 5569 var map = this._map, 5570 className = 'leaflet-disabled'; 5571 5572 removeClass(this._zoomInButton, className); 5573 removeClass(this._zoomOutButton, className); 5574 this._zoomInButton.setAttribute('aria-disabled', 'false'); 5575 this._zoomOutButton.setAttribute('aria-disabled', 'false'); 5576 5577 if (this._disabled || map._zoom === map.getMinZoom()) { 5578 addClass(this._zoomOutButton, className); 5579 this._zoomOutButton.setAttribute('aria-disabled', 'true'); 5580 } 5581 if (this._disabled || map._zoom === map.getMaxZoom()) { 5582 addClass(this._zoomInButton, className); 5583 this._zoomInButton.setAttribute('aria-disabled', 'true'); 5584 } 5585 } 5586 }); 5587 5588 // @namespace Map 5589 // @section Control options 5590 // @option zoomControl: Boolean = true 5591 // Whether a [zoom control](#control-zoom) is added to the map by default. 5592 Map.mergeOptions({ 5593 zoomControl: true 5594 }); 5595 5596 Map.addInitHook(function () { 5597 if (this.options.zoomControl) { 5598 // @section Controls 5599 // @property zoomControl: Control.Zoom 5600 // The default zoom control (only available if the 5601 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5602 this.zoomControl = new Zoom(); 5603 this.addControl(this.zoomControl); 5604 } 5605 }); 5606 5607 // @namespace Control.Zoom 5608 // @factory L.control.zoom(options: Control.Zoom options) 5609 // Creates a zoom control 5610 var zoom = function (options) { 5611 return new Zoom(options); 5612 }; 5613 5614 /* 5615 * @class Control.Scale 5616 * @aka L.Control.Scale 5617 * @inherits Control 5618 * 5619 * 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`. 5620 * 5621 * @example 5622 * 5623 * ```js 5624 * L.control.scale().addTo(map); 5625 * ``` 5626 */ 5627 5628 var Scale = Control.extend({ 5629 // @section 5630 // @aka Control.Scale options 5631 options: { 5632 position: 'bottomleft', 5633 5634 // @option maxWidth: Number = 100 5635 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5636 maxWidth: 100, 5637 5638 // @option metric: Boolean = True 5639 // Whether to show the metric scale line (m/km). 5640 metric: true, 5641 5642 // @option imperial: Boolean = True 5643 // Whether to show the imperial scale line (mi/ft). 5644 imperial: true 5645 5646 // @option updateWhenIdle: Boolean = false 5647 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5648 }, 5649 5650 onAdd: function (map) { 5651 var className = 'leaflet-control-scale', 5652 container = create$1('div', className), 5653 options = this.options; 5654 5655 this._addScales(options, className + '-line', container); 5656 5657 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5658 map.whenReady(this._update, this); 5659 5660 return container; 5661 }, 5662 5663 onRemove: function (map) { 5664 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5665 }, 5666 5667 _addScales: function (options, className, container) { 5668 if (options.metric) { 5669 this._mScale = create$1('div', className, container); 5670 } 5671 if (options.imperial) { 5672 this._iScale = create$1('div', className, container); 5673 } 5674 }, 5675 5676 _update: function () { 5677 var map = this._map, 5678 y = map.getSize().y / 2; 5679 5680 var maxMeters = map.distance( 5681 map.containerPointToLatLng([0, y]), 5682 map.containerPointToLatLng([this.options.maxWidth, y])); 5683 5684 this._updateScales(maxMeters); 5685 }, 5686 5687 _updateScales: function (maxMeters) { 5688 if (this.options.metric && maxMeters) { 5689 this._updateMetric(maxMeters); 5690 } 5691 if (this.options.imperial && maxMeters) { 5692 this._updateImperial(maxMeters); 5693 } 5694 }, 5695 5696 _updateMetric: function (maxMeters) { 5697 var meters = this._getRoundNum(maxMeters), 5698 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5699 5700 this._updateScale(this._mScale, label, meters / maxMeters); 5701 }, 5702 5703 _updateImperial: function (maxMeters) { 5704 var maxFeet = maxMeters * 3.2808399, 5705 maxMiles, miles, feet; 5706 5707 if (maxFeet > 5280) { 5708 maxMiles = maxFeet / 5280; 5709 miles = this._getRoundNum(maxMiles); 5710 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5711 5712 } else { 5713 feet = this._getRoundNum(maxFeet); 5714 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5715 } 5716 }, 5717 5718 _updateScale: function (scale, text, ratio) { 5719 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5720 scale.innerHTML = text; 5721 }, 5722 5723 _getRoundNum: function (num) { 5724 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5725 d = num / pow10; 5726 5727 d = d >= 10 ? 10 : 5728 d >= 5 ? 5 : 5729 d >= 3 ? 3 : 5730 d >= 2 ? 2 : 1; 5731 5732 return pow10 * d; 5733 } 5734 }); 5735 5736 5737 // @factory L.control.scale(options?: Control.Scale options) 5738 // Creates an scale control with the given options. 5739 var scale = function (options) { 5740 return new Scale(options); 5741 }; 5742 5743 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>'; 5744 5745 5746 /* 5747 * @class Control.Attribution 5748 * @aka L.Control.Attribution 5749 * @inherits Control 5750 * 5751 * 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. 5752 */ 5753 5754 var Attribution = Control.extend({ 5755 // @section 5756 // @aka Control.Attribution options 5757 options: { 5758 position: 'bottomright', 5759 5760 // @option prefix: String|false = 'Leaflet' 5761 // The HTML text shown before the attributions. Pass `false` to disable. 5762 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>' 5763 }, 5764 5765 initialize: function (options) { 5766 setOptions(this, options); 5767 5768 this._attributions = {}; 5769 }, 5770 5771 onAdd: function (map) { 5772 map.attributionControl = this; 5773 this._container = create$1('div', 'leaflet-control-attribution'); 5774 disableClickPropagation(this._container); 5775 5776 // TODO ugly, refactor 5777 for (var i in map._layers) { 5778 if (map._layers[i].getAttribution) { 5779 this.addAttribution(map._layers[i].getAttribution()); 5780 } 5781 } 5782 5783 this._update(); 5784 5785 map.on('layeradd', this._addAttribution, this); 5786 5787 return this._container; 5788 }, 5789 5790 onRemove: function (map) { 5791 map.off('layeradd', this._addAttribution, this); 5792 }, 5793 5794 _addAttribution: function (ev) { 5795 if (ev.layer.getAttribution) { 5796 this.addAttribution(ev.layer.getAttribution()); 5797 ev.layer.once('remove', function () { 5798 this.removeAttribution(ev.layer.getAttribution()); 5799 }, this); 5800 } 5801 }, 5802 5803 // @method setPrefix(prefix: String|false): this 5804 // The HTML text shown before the attributions. Pass `false` to disable. 5805 setPrefix: function (prefix) { 5806 this.options.prefix = prefix; 5807 this._update(); 5808 return this; 5809 }, 5810 5811 // @method addAttribution(text: String): this 5812 // Adds an attribution text (e.g. `'© OpenStreetMap contributors'`). 5813 addAttribution: function (text) { 5814 if (!text) { return this; } 5815 5816 if (!this._attributions[text]) { 5817 this._attributions[text] = 0; 5818 } 5819 this._attributions[text]++; 5820 5821 this._update(); 5822 5823 return this; 5824 }, 5825 5826 // @method removeAttribution(text: String): this 5827 // Removes an attribution text. 5828 removeAttribution: function (text) { 5829 if (!text) { return this; } 5830 5831 if (this._attributions[text]) { 5832 this._attributions[text]--; 5833 this._update(); 5834 } 5835 5836 return this; 5837 }, 5838 5839 _update: function () { 5840 if (!this._map) { return; } 5841 5842 var attribs = []; 5843 5844 for (var i in this._attributions) { 5845 if (this._attributions[i]) { 5846 attribs.push(i); 5847 } 5848 } 5849 5850 var prefixAndAttribs = []; 5851 5852 if (this.options.prefix) { 5853 prefixAndAttribs.push(this.options.prefix); 5854 } 5855 if (attribs.length) { 5856 prefixAndAttribs.push(attribs.join(', ')); 5857 } 5858 5859 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> '); 5860 } 5861 }); 5862 5863 // @namespace Map 5864 // @section Control options 5865 // @option attributionControl: Boolean = true 5866 // Whether a [attribution control](#control-attribution) is added to the map by default. 5867 Map.mergeOptions({ 5868 attributionControl: true 5869 }); 5870 5871 Map.addInitHook(function () { 5872 if (this.options.attributionControl) { 5873 new Attribution().addTo(this); 5874 } 5875 }); 5876 5877 // @namespace Control.Attribution 5878 // @factory L.control.attribution(options: Control.Attribution options) 5879 // Creates an attribution control. 5880 var attribution = function (options) { 5881 return new Attribution(options); 5882 }; 5883 5884 Control.Layers = Layers; 5885 Control.Zoom = Zoom; 5886 Control.Scale = Scale; 5887 Control.Attribution = Attribution; 5888 5889 control.layers = layers; 5890 control.zoom = zoom; 5891 control.scale = scale; 5892 control.attribution = attribution; 5893 5894 /* 5895 L.Handler is a base class for handler classes that are used internally to inject 5896 interaction features like dragging to classes like Map and Marker. 5897 */ 5898 5899 // @class Handler 5900 // @aka L.Handler 5901 // Abstract class for map interaction handlers 5902 5903 var Handler = Class.extend({ 5904 initialize: function (map) { 5905 this._map = map; 5906 }, 5907 5908 // @method enable(): this 5909 // Enables the handler 5910 enable: function () { 5911 if (this._enabled) { return this; } 5912 5913 this._enabled = true; 5914 this.addHooks(); 5915 return this; 5916 }, 5917 5918 // @method disable(): this 5919 // Disables the handler 5920 disable: function () { 5921 if (!this._enabled) { return this; } 5922 5923 this._enabled = false; 5924 this.removeHooks(); 5925 return this; 5926 }, 5927 5928 // @method enabled(): Boolean 5929 // Returns `true` if the handler is enabled 5930 enabled: function () { 5931 return !!this._enabled; 5932 } 5933 5934 // @section Extension methods 5935 // Classes inheriting from `Handler` must implement the two following methods: 5936 // @method addHooks() 5937 // Called when the handler is enabled, should add event hooks. 5938 // @method removeHooks() 5939 // Called when the handler is disabled, should remove the event hooks added previously. 5940 }); 5941 5942 // @section There is static function which can be called without instantiating L.Handler: 5943 // @function addTo(map: Map, name: String): this 5944 // Adds a new Handler to the given map with the given name. 5945 Handler.addTo = function (map, name) { 5946 map.addHandler(name, this); 5947 return this; 5948 }; 5949 5950 var Mixin = {Events: Events}; 5951 5952 /* 5953 * @class Draggable 5954 * @aka L.Draggable 5955 * @inherits Evented 5956 * 5957 * A class for making DOM elements draggable (including touch support). 5958 * Used internally for map and marker dragging. Only works for elements 5959 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 5960 * 5961 * @example 5962 * ```js 5963 * var draggable = new L.Draggable(elementToDrag); 5964 * draggable.enable(); 5965 * ``` 5966 */ 5967 5968 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown'; 5969 5970 var Draggable = Evented.extend({ 5971 5972 options: { 5973 // @section 5974 // @aka Draggable options 5975 // @option clickTolerance: Number = 3 5976 // The max number of pixels a user can shift the mouse pointer during a click 5977 // for it to be considered a valid click (as opposed to a mouse drag). 5978 clickTolerance: 3 5979 }, 5980 5981 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 5982 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 5983 initialize: function (element, dragStartTarget, preventOutline, options) { 5984 setOptions(this, options); 5985 5986 this._element = element; 5987 this._dragStartTarget = dragStartTarget || element; 5988 this._preventOutline = preventOutline; 5989 }, 5990 5991 // @method enable() 5992 // Enables the dragging ability 5993 enable: function () { 5994 if (this._enabled) { return; } 5995 5996 on(this._dragStartTarget, START, this._onDown, this); 5997 5998 this._enabled = true; 5999 }, 6000 6001 // @method disable() 6002 // Disables the dragging ability 6003 disable: function () { 6004 if (!this._enabled) { return; } 6005 6006 // If we're currently dragging this draggable, 6007 // disabling it counts as first ending the drag. 6008 if (Draggable._dragging === this) { 6009 this.finishDrag(true); 6010 } 6011 6012 off(this._dragStartTarget, START, this._onDown, this); 6013 6014 this._enabled = false; 6015 this._moved = false; 6016 }, 6017 6018 _onDown: function (e) { 6019 // Ignore the event if disabled; this happens in IE11 6020 // under some circumstances, see #3666. 6021 if (!this._enabled) { return; } 6022 6023 this._moved = false; 6024 6025 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 6026 6027 if (e.touches && e.touches.length !== 1) { 6028 // Finish dragging to avoid conflict with touchZoom 6029 if (Draggable._dragging === this) { 6030 this.finishDrag(); 6031 } 6032 return; 6033 } 6034 6035 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 6036 Draggable._dragging = this; // Prevent dragging multiple objects at once. 6037 6038 if (this._preventOutline) { 6039 preventOutline(this._element); 6040 } 6041 6042 disableImageDrag(); 6043 disableTextSelection(); 6044 6045 if (this._moving) { return; } 6046 6047 // @event down: Event 6048 // Fired when a drag is about to start. 6049 this.fire('down'); 6050 6051 var first = e.touches ? e.touches[0] : e, 6052 sizedParent = getSizedParentNode(this._element); 6053 6054 this._startPoint = new Point(first.clientX, first.clientY); 6055 this._startPos = getPosition(this._element); 6056 6057 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 6058 this._parentScale = getScale(sizedParent); 6059 6060 var mouseevent = e.type === 'mousedown'; 6061 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this); 6062 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this); 6063 }, 6064 6065 _onMove: function (e) { 6066 // Ignore the event if disabled; this happens in IE11 6067 // under some circumstances, see #3666. 6068 if (!this._enabled) { return; } 6069 6070 if (e.touches && e.touches.length > 1) { 6071 this._moved = true; 6072 return; 6073 } 6074 6075 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 6076 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 6077 6078 if (!offset.x && !offset.y) { return; } 6079 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 6080 6081 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 6082 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 6083 // and we can use the cached value for the scale. 6084 offset.x /= this._parentScale.x; 6085 offset.y /= this._parentScale.y; 6086 6087 preventDefault(e); 6088 6089 if (!this._moved) { 6090 // @event dragstart: Event 6091 // Fired when a drag starts 6092 this.fire('dragstart'); 6093 6094 this._moved = true; 6095 6096 addClass(document.body, 'leaflet-dragging'); 6097 6098 this._lastTarget = e.target || e.srcElement; 6099 // IE and Edge do not give the <use> element, so fetch it 6100 // if necessary 6101 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) { 6102 this._lastTarget = this._lastTarget.correspondingUseElement; 6103 } 6104 addClass(this._lastTarget, 'leaflet-drag-target'); 6105 } 6106 6107 this._newPos = this._startPos.add(offset); 6108 this._moving = true; 6109 6110 this._lastEvent = e; 6111 this._updatePosition(); 6112 }, 6113 6114 _updatePosition: function () { 6115 var e = {originalEvent: this._lastEvent}; 6116 6117 // @event predrag: Event 6118 // Fired continuously during dragging *before* each corresponding 6119 // update of the element's position. 6120 this.fire('predrag', e); 6121 setPosition(this._element, this._newPos); 6122 6123 // @event drag: Event 6124 // Fired continuously during dragging. 6125 this.fire('drag', e); 6126 }, 6127 6128 _onUp: function () { 6129 // Ignore the event if disabled; this happens in IE11 6130 // under some circumstances, see #3666. 6131 if (!this._enabled) { return; } 6132 this.finishDrag(); 6133 }, 6134 6135 finishDrag: function (noInertia) { 6136 removeClass(document.body, 'leaflet-dragging'); 6137 6138 if (this._lastTarget) { 6139 removeClass(this._lastTarget, 'leaflet-drag-target'); 6140 this._lastTarget = null; 6141 } 6142 6143 off(document, 'mousemove touchmove', this._onMove, this); 6144 off(document, 'mouseup touchend touchcancel', this._onUp, this); 6145 6146 enableImageDrag(); 6147 enableTextSelection(); 6148 6149 var fireDragend = this._moved && this._moving; 6150 6151 this._moving = false; 6152 Draggable._dragging = false; 6153 6154 if (fireDragend) { 6155 // @event dragend: DragEndEvent 6156 // Fired when the drag ends. 6157 this.fire('dragend', { 6158 noInertia: noInertia, 6159 distance: this._newPos.distanceTo(this._startPos) 6160 }); 6161 } 6162 } 6163 6164 }); 6165 6166 /* 6167 * @namespace PolyUtil 6168 * Various utility functions for polygon geometries. 6169 */ 6170 6171 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6172 * 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)). 6173 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6174 * performance. Note that polygon points needs different algorithm for clipping 6175 * than polyline, so there's a separate method for it. 6176 */ 6177 function clipPolygon(points, bounds, round) { 6178 var clippedPoints, 6179 edges = [1, 4, 2, 8], 6180 i, j, k, 6181 a, b, 6182 len, edge, p; 6183 6184 for (i = 0, len = points.length; i < len; i++) { 6185 points[i]._code = _getBitCode(points[i], bounds); 6186 } 6187 6188 // for each edge (left, bottom, right, top) 6189 for (k = 0; k < 4; k++) { 6190 edge = edges[k]; 6191 clippedPoints = []; 6192 6193 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6194 a = points[i]; 6195 b = points[j]; 6196 6197 // if a is inside the clip window 6198 if (!(a._code & edge)) { 6199 // if b is outside the clip window (a->b goes out of screen) 6200 if (b._code & edge) { 6201 p = _getEdgeIntersection(b, a, edge, bounds, round); 6202 p._code = _getBitCode(p, bounds); 6203 clippedPoints.push(p); 6204 } 6205 clippedPoints.push(a); 6206 6207 // else if b is inside the clip window (a->b enters the screen) 6208 } else if (!(b._code & edge)) { 6209 p = _getEdgeIntersection(b, a, edge, bounds, round); 6210 p._code = _getBitCode(p, bounds); 6211 clippedPoints.push(p); 6212 } 6213 } 6214 points = clippedPoints; 6215 } 6216 6217 return points; 6218 } 6219 6220 /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng 6221 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon. 6222 */ 6223 function polygonCenter(latlngs, crs) { 6224 var i, j, p1, p2, f, area, x, y, center; 6225 6226 if (!latlngs || latlngs.length === 0) { 6227 throw new Error('latlngs not passed'); 6228 } 6229 6230 if (!isFlat(latlngs)) { 6231 console.warn('latlngs are not flat! Only the first ring will be used'); 6232 latlngs = latlngs[0]; 6233 } 6234 6235 var centroidLatLng = toLatLng([0, 0]); 6236 6237 var bounds = toLatLngBounds(latlngs); 6238 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 6239 // tests showed that below 1700 rounding errors are happening 6240 if (areaBounds < 1700) { 6241 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors 6242 centroidLatLng = centroid(latlngs); 6243 } 6244 6245 var len = latlngs.length; 6246 var points = []; 6247 for (i = 0; i < len; i++) { 6248 var latlng = toLatLng(latlngs[i]); 6249 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 6250 } 6251 6252 area = x = y = 0; 6253 6254 // polygon centroid algorithm; 6255 for (i = 0, j = len - 1; i < len; j = i++) { 6256 p1 = points[i]; 6257 p2 = points[j]; 6258 6259 f = p1.y * p2.x - p2.y * p1.x; 6260 x += (p1.x + p2.x) * f; 6261 y += (p1.y + p2.y) * f; 6262 area += f * 3; 6263 } 6264 6265 if (area === 0) { 6266 // Polygon is so small that all points are on same pixel. 6267 center = points[0]; 6268 } else { 6269 center = [x / area, y / area]; 6270 } 6271 6272 var latlngCenter = crs.unproject(toPoint(center)); 6273 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 6274 } 6275 6276 /* @function centroid(latlngs: LatLng[]): LatLng 6277 * Returns the 'center of mass' of the passed LatLngs. 6278 */ 6279 function centroid(coords) { 6280 var latSum = 0; 6281 var lngSum = 0; 6282 var len = 0; 6283 for (var i = 0; i < coords.length; i++) { 6284 var latlng = toLatLng(coords[i]); 6285 latSum += latlng.lat; 6286 lngSum += latlng.lng; 6287 len++; 6288 } 6289 return toLatLng([latSum / len, lngSum / len]); 6290 } 6291 6292 var PolyUtil = { 6293 __proto__: null, 6294 clipPolygon: clipPolygon, 6295 polygonCenter: polygonCenter, 6296 centroid: centroid 6297 }; 6298 6299 /* 6300 * @namespace LineUtil 6301 * 6302 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 6303 */ 6304 6305 // Simplify polyline with vertex reduction and Douglas-Peucker simplification. 6306 // Improves rendering performance dramatically by lessening the number of points to draw. 6307 6308 // @function simplify(points: Point[], tolerance: Number): Point[] 6309 // Dramatically reduces the number of points in a polyline while retaining 6310 // its shape and returns a new array of simplified points, using the 6311 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm). 6312 // Used for a huge performance boost when processing/displaying Leaflet polylines for 6313 // each zoom level and also reducing visual noise. tolerance affects the amount of 6314 // simplification (lesser value means higher quality but slower and with more points). 6315 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/). 6316 function simplify(points, tolerance) { 6317 if (!tolerance || !points.length) { 6318 return points.slice(); 6319 } 6320 6321 var sqTolerance = tolerance * tolerance; 6322 6323 // stage 1: vertex reduction 6324 points = _reducePoints(points, sqTolerance); 6325 6326 // stage 2: Douglas-Peucker simplification 6327 points = _simplifyDP(points, sqTolerance); 6328 6329 return points; 6330 } 6331 6332 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 6333 // Returns the distance between point `p` and segment `p1` to `p2`. 6334 function pointToSegmentDistance(p, p1, p2) { 6335 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 6336 } 6337 6338 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 6339 // Returns the closest point from a point `p` on a segment `p1` to `p2`. 6340 function closestPointOnSegment(p, p1, p2) { 6341 return _sqClosestPointOnSegment(p, p1, p2); 6342 } 6343 6344 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm 6345 function _simplifyDP(points, sqTolerance) { 6346 6347 var len = points.length, 6348 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 6349 markers = new ArrayConstructor(len); 6350 6351 markers[0] = markers[len - 1] = 1; 6352 6353 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 6354 6355 var i, 6356 newPoints = []; 6357 6358 for (i = 0; i < len; i++) { 6359 if (markers[i]) { 6360 newPoints.push(points[i]); 6361 } 6362 } 6363 6364 return newPoints; 6365 } 6366 6367 function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6368 6369 var maxSqDist = 0, 6370 index, i, sqDist; 6371 6372 for (i = first + 1; i <= last - 1; i++) { 6373 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6374 6375 if (sqDist > maxSqDist) { 6376 index = i; 6377 maxSqDist = sqDist; 6378 } 6379 } 6380 6381 if (maxSqDist > sqTolerance) { 6382 markers[index] = 1; 6383 6384 _simplifyDPStep(points, markers, sqTolerance, first, index); 6385 _simplifyDPStep(points, markers, sqTolerance, index, last); 6386 } 6387 } 6388 6389 // reduce points that are too close to each other to a single point 6390 function _reducePoints(points, sqTolerance) { 6391 var reducedPoints = [points[0]]; 6392 6393 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6394 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6395 reducedPoints.push(points[i]); 6396 prev = i; 6397 } 6398 } 6399 if (prev < len - 1) { 6400 reducedPoints.push(points[len - 1]); 6401 } 6402 return reducedPoints; 6403 } 6404 6405 var _lastCode; 6406 6407 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean 6408 // Clips the segment a to b by rectangular bounds with the 6409 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6410 // (modifying the segment points directly!). Used by Leaflet to only show polyline 6411 // points that are on the screen or near, increasing performance. 6412 function clipSegment(a, b, bounds, useLastCode, round) { 6413 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6414 codeB = _getBitCode(b, bounds), 6415 6416 codeOut, p, newCode; 6417 6418 // save 2nd code to avoid calculating it on the next segment 6419 _lastCode = codeB; 6420 6421 while (true) { 6422 // if a,b is inside the clip window (trivial accept) 6423 if (!(codeA | codeB)) { 6424 return [a, b]; 6425 } 6426 6427 // if a,b is outside the clip window (trivial reject) 6428 if (codeA & codeB) { 6429 return false; 6430 } 6431 6432 // other cases 6433 codeOut = codeA || codeB; 6434 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6435 newCode = _getBitCode(p, bounds); 6436 6437 if (codeOut === codeA) { 6438 a = p; 6439 codeA = newCode; 6440 } else { 6441 b = p; 6442 codeB = newCode; 6443 } 6444 } 6445 } 6446 6447 function _getEdgeIntersection(a, b, code, bounds, round) { 6448 var dx = b.x - a.x, 6449 dy = b.y - a.y, 6450 min = bounds.min, 6451 max = bounds.max, 6452 x, y; 6453 6454 if (code & 8) { // top 6455 x = a.x + dx * (max.y - a.y) / dy; 6456 y = max.y; 6457 6458 } else if (code & 4) { // bottom 6459 x = a.x + dx * (min.y - a.y) / dy; 6460 y = min.y; 6461 6462 } else if (code & 2) { // right 6463 x = max.x; 6464 y = a.y + dy * (max.x - a.x) / dx; 6465 6466 } else if (code & 1) { // left 6467 x = min.x; 6468 y = a.y + dy * (min.x - a.x) / dx; 6469 } 6470 6471 return new Point(x, y, round); 6472 } 6473 6474 function _getBitCode(p, bounds) { 6475 var code = 0; 6476 6477 if (p.x < bounds.min.x) { // left 6478 code |= 1; 6479 } else if (p.x > bounds.max.x) { // right 6480 code |= 2; 6481 } 6482 6483 if (p.y < bounds.min.y) { // bottom 6484 code |= 4; 6485 } else if (p.y > bounds.max.y) { // top 6486 code |= 8; 6487 } 6488 6489 return code; 6490 } 6491 6492 // square distance (to avoid unnecessary Math.sqrt calls) 6493 function _sqDist(p1, p2) { 6494 var dx = p2.x - p1.x, 6495 dy = p2.y - p1.y; 6496 return dx * dx + dy * dy; 6497 } 6498 6499 // return closest point on segment or distance to that point 6500 function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6501 var x = p1.x, 6502 y = p1.y, 6503 dx = p2.x - x, 6504 dy = p2.y - y, 6505 dot = dx * dx + dy * dy, 6506 t; 6507 6508 if (dot > 0) { 6509 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6510 6511 if (t > 1) { 6512 x = p2.x; 6513 y = p2.y; 6514 } else if (t > 0) { 6515 x += dx * t; 6516 y += dy * t; 6517 } 6518 } 6519 6520 dx = p.x - x; 6521 dy = p.y - y; 6522 6523 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6524 } 6525 6526 6527 // @function isFlat(latlngs: LatLng[]): Boolean 6528 // Returns true if `latlngs` is a flat array, false is nested. 6529 function isFlat(latlngs) { 6530 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6531 } 6532 6533 function _flat(latlngs) { 6534 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6535 return isFlat(latlngs); 6536 } 6537 6538 /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng 6539 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline. 6540 */ 6541 function polylineCenter(latlngs, crs) { 6542 var i, halfDist, segDist, dist, p1, p2, ratio, center; 6543 6544 if (!latlngs || latlngs.length === 0) { 6545 throw new Error('latlngs not passed'); 6546 } 6547 6548 if (!isFlat(latlngs)) { 6549 console.warn('latlngs are not flat! Only the first ring will be used'); 6550 latlngs = latlngs[0]; 6551 } 6552 6553 var centroidLatLng = toLatLng([0, 0]); 6554 6555 var bounds = toLatLngBounds(latlngs); 6556 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 6557 // tests showed that below 1700 rounding errors are happening 6558 if (areaBounds < 1700) { 6559 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors 6560 centroidLatLng = centroid(latlngs); 6561 } 6562 6563 var len = latlngs.length; 6564 var points = []; 6565 for (i = 0; i < len; i++) { 6566 var latlng = toLatLng(latlngs[i]); 6567 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 6568 } 6569 6570 for (i = 0, halfDist = 0; i < len - 1; i++) { 6571 halfDist += points[i].distanceTo(points[i + 1]) / 2; 6572 } 6573 6574 // The line is so small in the current view that all points are on the same pixel. 6575 if (halfDist === 0) { 6576 center = points[0]; 6577 } else { 6578 for (i = 0, dist = 0; i < len - 1; i++) { 6579 p1 = points[i]; 6580 p2 = points[i + 1]; 6581 segDist = p1.distanceTo(p2); 6582 dist += segDist; 6583 6584 if (dist > halfDist) { 6585 ratio = (dist - halfDist) / segDist; 6586 center = [ 6587 p2.x - ratio * (p2.x - p1.x), 6588 p2.y - ratio * (p2.y - p1.y) 6589 ]; 6590 break; 6591 } 6592 } 6593 } 6594 6595 var latlngCenter = crs.unproject(toPoint(center)); 6596 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 6597 } 6598 6599 var LineUtil = { 6600 __proto__: null, 6601 simplify: simplify, 6602 pointToSegmentDistance: pointToSegmentDistance, 6603 closestPointOnSegment: closestPointOnSegment, 6604 clipSegment: clipSegment, 6605 _getEdgeIntersection: _getEdgeIntersection, 6606 _getBitCode: _getBitCode, 6607 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6608 isFlat: isFlat, 6609 _flat: _flat, 6610 polylineCenter: polylineCenter 6611 }; 6612 6613 /* 6614 * @namespace Projection 6615 * @section 6616 * Leaflet comes with a set of already defined Projections out of the box: 6617 * 6618 * @projection L.Projection.LonLat 6619 * 6620 * Equirectangular, or Plate Carree projection â the most simple projection, 6621 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6622 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6623 * `EPSG:4326` and `Simple` CRS. 6624 */ 6625 6626 var LonLat = { 6627 project: function (latlng) { 6628 return new Point(latlng.lng, latlng.lat); 6629 }, 6630 6631 unproject: function (point) { 6632 return new LatLng(point.y, point.x); 6633 }, 6634 6635 bounds: new Bounds([-180, -90], [180, 90]) 6636 }; 6637 6638 /* 6639 * @namespace Projection 6640 * @projection L.Projection.Mercator 6641 * 6642 * Elliptical Mercator projection â more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS. 6643 */ 6644 6645 var Mercator = { 6646 R: 6378137, 6647 R_MINOR: 6356752.314245179, 6648 6649 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6650 6651 project: function (latlng) { 6652 var d = Math.PI / 180, 6653 r = this.R, 6654 y = latlng.lat * d, 6655 tmp = this.R_MINOR / r, 6656 e = Math.sqrt(1 - tmp * tmp), 6657 con = e * Math.sin(y); 6658 6659 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6660 y = -r * Math.log(Math.max(ts, 1E-10)); 6661 6662 return new Point(latlng.lng * d * r, y); 6663 }, 6664 6665 unproject: function (point) { 6666 var d = 180 / Math.PI, 6667 r = this.R, 6668 tmp = this.R_MINOR / r, 6669 e = Math.sqrt(1 - tmp * tmp), 6670 ts = Math.exp(-point.y / r), 6671 phi = Math.PI / 2 - 2 * Math.atan(ts); 6672 6673 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6674 con = e * Math.sin(phi); 6675 con = Math.pow((1 - con) / (1 + con), e / 2); 6676 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6677 phi += dphi; 6678 } 6679 6680 return new LatLng(phi * d, point.x * d / r); 6681 } 6682 }; 6683 6684 /* 6685 * @class Projection 6686 6687 * An object with methods for projecting geographical coordinates of the world onto 6688 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection). 6689 6690 * @property bounds: Bounds 6691 * The bounds (specified in CRS units) where the projection is valid 6692 6693 * @method project(latlng: LatLng): Point 6694 * Projects geographical coordinates into a 2D point. 6695 * Only accepts actual `L.LatLng` instances, not arrays. 6696 6697 * @method unproject(point: Point): LatLng 6698 * The inverse of `project`. Projects a 2D point into a geographical location. 6699 * Only accepts actual `L.Point` instances, not arrays. 6700 6701 * Note that the projection instances do not inherit from Leaflet's `Class` object, 6702 * and can't be instantiated. Also, new classes can't inherit from them, 6703 * and methods can't be added to them with the `include` function. 6704 6705 */ 6706 6707 var index = { 6708 __proto__: null, 6709 LonLat: LonLat, 6710 Mercator: Mercator, 6711 SphericalMercator: SphericalMercator 6712 }; 6713 6714 /* 6715 * @namespace CRS 6716 * @crs L.CRS.EPSG3395 6717 * 6718 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6719 */ 6720 var EPSG3395 = extend({}, Earth, { 6721 code: 'EPSG:3395', 6722 projection: Mercator, 6723 6724 transformation: (function () { 6725 var scale = 0.5 / (Math.PI * Mercator.R); 6726 return toTransformation(scale, 0.5, -scale, 0.5); 6727 }()) 6728 }); 6729 6730 /* 6731 * @namespace CRS 6732 * @crs L.CRS.EPSG4326 6733 * 6734 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6735 * 6736 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6737 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6738 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6739 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 6740 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6741 */ 6742 6743 var EPSG4326 = extend({}, Earth, { 6744 code: 'EPSG:4326', 6745 projection: LonLat, 6746 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 6747 }); 6748 6749 /* 6750 * @namespace CRS 6751 * @crs L.CRS.Simple 6752 * 6753 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6754 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6755 * axis should still be inverted (going from bottom to top). `distance()` returns 6756 * simple euclidean distance. 6757 */ 6758 6759 var Simple = extend({}, CRS, { 6760 projection: LonLat, 6761 transformation: toTransformation(1, 0, -1, 0), 6762 6763 scale: function (zoom) { 6764 return Math.pow(2, zoom); 6765 }, 6766 6767 zoom: function (scale) { 6768 return Math.log(scale) / Math.LN2; 6769 }, 6770 6771 distance: function (latlng1, latlng2) { 6772 var dx = latlng2.lng - latlng1.lng, 6773 dy = latlng2.lat - latlng1.lat; 6774 6775 return Math.sqrt(dx * dx + dy * dy); 6776 }, 6777 6778 infinite: true 6779 }); 6780 6781 CRS.Earth = Earth; 6782 CRS.EPSG3395 = EPSG3395; 6783 CRS.EPSG3857 = EPSG3857; 6784 CRS.EPSG900913 = EPSG900913; 6785 CRS.EPSG4326 = EPSG4326; 6786 CRS.Simple = Simple; 6787 6788 /* 6789 * @class Layer 6790 * @inherits Evented 6791 * @aka L.Layer 6792 * @aka ILayer 6793 * 6794 * A set of methods from the Layer base class that all Leaflet layers use. 6795 * Inherits all methods, options and events from `L.Evented`. 6796 * 6797 * @example 6798 * 6799 * ```js 6800 * var layer = L.marker(latlng).addTo(map); 6801 * layer.addTo(map); 6802 * layer.remove(); 6803 * ``` 6804 * 6805 * @event add: Event 6806 * Fired after the layer is added to a map 6807 * 6808 * @event remove: Event 6809 * Fired after the layer is removed from a map 6810 */ 6811 6812 6813 var Layer = Evented.extend({ 6814 6815 // Classes extending `L.Layer` will inherit the following options: 6816 options: { 6817 // @option pane: String = 'overlayPane' 6818 // 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. 6819 pane: 'overlayPane', 6820 6821 // @option attribution: String = null 6822 // 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. 6823 attribution: null, 6824 6825 bubblingMouseEvents: true 6826 }, 6827 6828 /* @section 6829 * Classes extending `L.Layer` will inherit the following methods: 6830 * 6831 * @method addTo(map: Map|LayerGroup): this 6832 * Adds the layer to the given map or layer group. 6833 */ 6834 addTo: function (map) { 6835 map.addLayer(this); 6836 return this; 6837 }, 6838 6839 // @method remove: this 6840 // Removes the layer from the map it is currently active on. 6841 remove: function () { 6842 return this.removeFrom(this._map || this._mapToAdd); 6843 }, 6844 6845 // @method removeFrom(map: Map): this 6846 // Removes the layer from the given map 6847 // 6848 // @alternative 6849 // @method removeFrom(group: LayerGroup): this 6850 // Removes the layer from the given `LayerGroup` 6851 removeFrom: function (obj) { 6852 if (obj) { 6853 obj.removeLayer(this); 6854 } 6855 return this; 6856 }, 6857 6858 // @method getPane(name? : String): HTMLElement 6859 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 6860 getPane: function (name) { 6861 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 6862 }, 6863 6864 addInteractiveTarget: function (targetEl) { 6865 this._map._targets[stamp(targetEl)] = this; 6866 return this; 6867 }, 6868 6869 removeInteractiveTarget: function (targetEl) { 6870 delete this._map._targets[stamp(targetEl)]; 6871 return this; 6872 }, 6873 6874 // @method getAttribution: String 6875 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 6876 getAttribution: function () { 6877 return this.options.attribution; 6878 }, 6879 6880 _layerAdd: function (e) { 6881 var map = e.target; 6882 6883 // check in case layer gets added and then removed before the map is ready 6884 if (!map.hasLayer(this)) { return; } 6885 6886 this._map = map; 6887 this._zoomAnimated = map._zoomAnimated; 6888 6889 if (this.getEvents) { 6890 var events = this.getEvents(); 6891 map.on(events, this); 6892 this.once('remove', function () { 6893 map.off(events, this); 6894 }, this); 6895 } 6896 6897 this.onAdd(map); 6898 6899 this.fire('add'); 6900 map.fire('layeradd', {layer: this}); 6901 } 6902 }); 6903 6904 /* @section Extension methods 6905 * @uninheritable 6906 * 6907 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 6908 * 6909 * @method onAdd(map: Map): this 6910 * 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). 6911 * 6912 * @method onRemove(map: Map): this 6913 * 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). 6914 * 6915 * @method getEvents(): Object 6916 * 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. 6917 * 6918 * @method getAttribution(): String 6919 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 6920 * 6921 * @method beforeAdd(map: Map): this 6922 * 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. 6923 */ 6924 6925 6926 /* @namespace Map 6927 * @section Layer events 6928 * 6929 * @event layeradd: LayerEvent 6930 * Fired when a new layer is added to the map. 6931 * 6932 * @event layerremove: LayerEvent 6933 * Fired when some layer is removed from the map 6934 * 6935 * @section Methods for Layers and Controls 6936 */ 6937 Map.include({ 6938 // @method addLayer(layer: Layer): this 6939 // Adds the given layer to the map 6940 addLayer: function (layer) { 6941 if (!layer._layerAdd) { 6942 throw new Error('The provided object is not a Layer.'); 6943 } 6944 6945 var id = stamp(layer); 6946 if (this._layers[id]) { return this; } 6947 this._layers[id] = layer; 6948 6949 layer._mapToAdd = this; 6950 6951 if (layer.beforeAdd) { 6952 layer.beforeAdd(this); 6953 } 6954 6955 this.whenReady(layer._layerAdd, layer); 6956 6957 return this; 6958 }, 6959 6960 // @method removeLayer(layer: Layer): this 6961 // Removes the given layer from the map. 6962 removeLayer: function (layer) { 6963 var id = stamp(layer); 6964 6965 if (!this._layers[id]) { return this; } 6966 6967 if (this._loaded) { 6968 layer.onRemove(this); 6969 } 6970 6971 delete this._layers[id]; 6972 6973 if (this._loaded) { 6974 this.fire('layerremove', {layer: layer}); 6975 layer.fire('remove'); 6976 } 6977 6978 layer._map = layer._mapToAdd = null; 6979 6980 return this; 6981 }, 6982 6983 // @method hasLayer(layer: Layer): Boolean 6984 // Returns `true` if the given layer is currently added to the map 6985 hasLayer: function (layer) { 6986 return stamp(layer) in this._layers; 6987 }, 6988 6989 /* @method eachLayer(fn: Function, context?: Object): this 6990 * Iterates over the layers of the map, optionally specifying context of the iterator function. 6991 * ``` 6992 * map.eachLayer(function(layer){ 6993 * layer.bindPopup('Hello'); 6994 * }); 6995 * ``` 6996 */ 6997 eachLayer: function (method, context) { 6998 for (var i in this._layers) { 6999 method.call(context, this._layers[i]); 7000 } 7001 return this; 7002 }, 7003 7004 _addLayers: function (layers) { 7005 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 7006 7007 for (var i = 0, len = layers.length; i < len; i++) { 7008 this.addLayer(layers[i]); 7009 } 7010 }, 7011 7012 _addZoomLimit: function (layer) { 7013 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 7014 this._zoomBoundLayers[stamp(layer)] = layer; 7015 this._updateZoomLevels(); 7016 } 7017 }, 7018 7019 _removeZoomLimit: function (layer) { 7020 var id = stamp(layer); 7021 7022 if (this._zoomBoundLayers[id]) { 7023 delete this._zoomBoundLayers[id]; 7024 this._updateZoomLevels(); 7025 } 7026 }, 7027 7028 _updateZoomLevels: function () { 7029 var minZoom = Infinity, 7030 maxZoom = -Infinity, 7031 oldZoomSpan = this._getZoomSpan(); 7032 7033 for (var i in this._zoomBoundLayers) { 7034 var options = this._zoomBoundLayers[i].options; 7035 7036 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 7037 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 7038 } 7039 7040 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 7041 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 7042 7043 // @section Map state change events 7044 // @event zoomlevelschange: Event 7045 // Fired when the number of zoomlevels on the map is changed due 7046 // to adding or removing a layer. 7047 if (oldZoomSpan !== this._getZoomSpan()) { 7048 this.fire('zoomlevelschange'); 7049 } 7050 7051 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 7052 this.setZoom(this._layersMaxZoom); 7053 } 7054 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 7055 this.setZoom(this._layersMinZoom); 7056 } 7057 } 7058 }); 7059 7060 /* 7061 * @class LayerGroup 7062 * @aka L.LayerGroup 7063 * @inherits Interactive layer 7064 * 7065 * Used to group several layers and handle them as one. If you add it to the map, 7066 * any layers added or removed from the group will be added/removed on the map as 7067 * well. Extends `Layer`. 7068 * 7069 * @example 7070 * 7071 * ```js 7072 * L.layerGroup([marker1, marker2]) 7073 * .addLayer(polyline) 7074 * .addTo(map); 7075 * ``` 7076 */ 7077 7078 var LayerGroup = Layer.extend({ 7079 7080 initialize: function (layers, options) { 7081 setOptions(this, options); 7082 7083 this._layers = {}; 7084 7085 var i, len; 7086 7087 if (layers) { 7088 for (i = 0, len = layers.length; i < len; i++) { 7089 this.addLayer(layers[i]); 7090 } 7091 } 7092 }, 7093 7094 // @method addLayer(layer: Layer): this 7095 // Adds the given layer to the group. 7096 addLayer: function (layer) { 7097 var id = this.getLayerId(layer); 7098 7099 this._layers[id] = layer; 7100 7101 if (this._map) { 7102 this._map.addLayer(layer); 7103 } 7104 7105 return this; 7106 }, 7107 7108 // @method removeLayer(layer: Layer): this 7109 // Removes the given layer from the group. 7110 // @alternative 7111 // @method removeLayer(id: Number): this 7112 // Removes the layer with the given internal ID from the group. 7113 removeLayer: function (layer) { 7114 var id = layer in this._layers ? layer : this.getLayerId(layer); 7115 7116 if (this._map && this._layers[id]) { 7117 this._map.removeLayer(this._layers[id]); 7118 } 7119 7120 delete this._layers[id]; 7121 7122 return this; 7123 }, 7124 7125 // @method hasLayer(layer: Layer): Boolean 7126 // Returns `true` if the given layer is currently added to the group. 7127 // @alternative 7128 // @method hasLayer(id: Number): Boolean 7129 // Returns `true` if the given internal ID is currently added to the group. 7130 hasLayer: function (layer) { 7131 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer); 7132 return layerId in this._layers; 7133 }, 7134 7135 // @method clearLayers(): this 7136 // Removes all the layers from the group. 7137 clearLayers: function () { 7138 return this.eachLayer(this.removeLayer, this); 7139 }, 7140 7141 // @method invoke(methodName: String, â¦): this 7142 // Calls `methodName` on every layer contained in this group, passing any 7143 // additional parameters. Has no effect if the layers contained do not 7144 // implement `methodName`. 7145 invoke: function (methodName) { 7146 var args = Array.prototype.slice.call(arguments, 1), 7147 i, layer; 7148 7149 for (i in this._layers) { 7150 layer = this._layers[i]; 7151 7152 if (layer[methodName]) { 7153 layer[methodName].apply(layer, args); 7154 } 7155 } 7156 7157 return this; 7158 }, 7159 7160 onAdd: function (map) { 7161 this.eachLayer(map.addLayer, map); 7162 }, 7163 7164 onRemove: function (map) { 7165 this.eachLayer(map.removeLayer, map); 7166 }, 7167 7168 // @method eachLayer(fn: Function, context?: Object): this 7169 // Iterates over the layers of the group, optionally specifying context of the iterator function. 7170 // ```js 7171 // group.eachLayer(function (layer) { 7172 // layer.bindPopup('Hello'); 7173 // }); 7174 // ``` 7175 eachLayer: function (method, context) { 7176 for (var i in this._layers) { 7177 method.call(context, this._layers[i]); 7178 } 7179 return this; 7180 }, 7181 7182 // @method getLayer(id: Number): Layer 7183 // Returns the layer with the given internal ID. 7184 getLayer: function (id) { 7185 return this._layers[id]; 7186 }, 7187 7188 // @method getLayers(): Layer[] 7189 // Returns an array of all the layers added to the group. 7190 getLayers: function () { 7191 var layers = []; 7192 this.eachLayer(layers.push, layers); 7193 return layers; 7194 }, 7195 7196 // @method setZIndex(zIndex: Number): this 7197 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 7198 setZIndex: function (zIndex) { 7199 return this.invoke('setZIndex', zIndex); 7200 }, 7201 7202 // @method getLayerId(layer: Layer): Number 7203 // Returns the internal ID for a layer 7204 getLayerId: function (layer) { 7205 return stamp(layer); 7206 } 7207 }); 7208 7209 7210 // @factory L.layerGroup(layers?: Layer[], options?: Object) 7211 // Create a layer group, optionally given an initial set of layers and an `options` object. 7212 var layerGroup = function (layers, options) { 7213 return new LayerGroup(layers, options); 7214 }; 7215 7216 /* 7217 * @class FeatureGroup 7218 * @aka L.FeatureGroup 7219 * @inherits LayerGroup 7220 * 7221 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 7222 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 7223 * * Events are propagated to the `FeatureGroup`, so if the group has an event 7224 * handler, it will handle events from any of the layers. This includes mouse events 7225 * and custom events. 7226 * * Has `layeradd` and `layerremove` events 7227 * 7228 * @example 7229 * 7230 * ```js 7231 * L.featureGroup([marker1, marker2, polyline]) 7232 * .bindPopup('Hello world!') 7233 * .on('click', function() { alert('Clicked on a member of the group!'); }) 7234 * .addTo(map); 7235 * ``` 7236 */ 7237 7238 var FeatureGroup = LayerGroup.extend({ 7239 7240 addLayer: function (layer) { 7241 if (this.hasLayer(layer)) { 7242 return this; 7243 } 7244 7245 layer.addEventParent(this); 7246 7247 LayerGroup.prototype.addLayer.call(this, layer); 7248 7249 // @event layeradd: LayerEvent 7250 // Fired when a layer is added to this `FeatureGroup` 7251 return this.fire('layeradd', {layer: layer}); 7252 }, 7253 7254 removeLayer: function (layer) { 7255 if (!this.hasLayer(layer)) { 7256 return this; 7257 } 7258 if (layer in this._layers) { 7259 layer = this._layers[layer]; 7260 } 7261 7262 layer.removeEventParent(this); 7263 7264 LayerGroup.prototype.removeLayer.call(this, layer); 7265 7266 // @event layerremove: LayerEvent 7267 // Fired when a layer is removed from this `FeatureGroup` 7268 return this.fire('layerremove', {layer: layer}); 7269 }, 7270 7271 // @method setStyle(style: Path options): this 7272 // Sets the given path options to each layer of the group that has a `setStyle` method. 7273 setStyle: function (style) { 7274 return this.invoke('setStyle', style); 7275 }, 7276 7277 // @method bringToFront(): this 7278 // Brings the layer group to the top of all other layers 7279 bringToFront: function () { 7280 return this.invoke('bringToFront'); 7281 }, 7282 7283 // @method bringToBack(): this 7284 // Brings the layer group to the back of all other layers 7285 bringToBack: function () { 7286 return this.invoke('bringToBack'); 7287 }, 7288 7289 // @method getBounds(): LatLngBounds 7290 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 7291 getBounds: function () { 7292 var bounds = new LatLngBounds(); 7293 7294 for (var id in this._layers) { 7295 var layer = this._layers[id]; 7296 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 7297 } 7298 return bounds; 7299 } 7300 }); 7301 7302 // @factory L.featureGroup(layers?: Layer[], options?: Object) 7303 // Create a feature group, optionally given an initial set of layers and an `options` object. 7304 var featureGroup = function (layers, options) { 7305 return new FeatureGroup(layers, options); 7306 }; 7307 7308 /* 7309 * @class Icon 7310 * @aka L.Icon 7311 * 7312 * Represents an icon to provide when creating a marker. 7313 * 7314 * @example 7315 * 7316 * ```js 7317 * var myIcon = L.icon({ 7318 * iconUrl: 'my-icon.png', 7319 * iconRetinaUrl: '[email protected]', 7320 * iconSize: [38, 95], 7321 * iconAnchor: [22, 94], 7322 * popupAnchor: [-3, -76], 7323 * shadowUrl: 'my-icon-shadow.png', 7324 * shadowRetinaUrl: '[email protected]', 7325 * shadowSize: [68, 95], 7326 * shadowAnchor: [22, 94] 7327 * }); 7328 * 7329 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 7330 * ``` 7331 * 7332 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 7333 * 7334 */ 7335 7336 var Icon = Class.extend({ 7337 7338 /* @section 7339 * @aka Icon options 7340 * 7341 * @option iconUrl: String = null 7342 * **(required)** The URL to the icon image (absolute or relative to your script path). 7343 * 7344 * @option iconRetinaUrl: String = null 7345 * The URL to a retina sized version of the icon image (absolute or relative to your 7346 * script path). Used for Retina screen devices. 7347 * 7348 * @option iconSize: Point = null 7349 * Size of the icon image in pixels. 7350 * 7351 * @option iconAnchor: Point = null 7352 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 7353 * will be aligned so that this point is at the marker's geographical location. Centered 7354 * by default if size is specified, also can be set in CSS with negative margins. 7355 * 7356 * @option popupAnchor: Point = [0, 0] 7357 * The coordinates of the point from which popups will "open", relative to the icon anchor. 7358 * 7359 * @option tooltipAnchor: Point = [0, 0] 7360 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7361 * 7362 * @option shadowUrl: String = null 7363 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7364 * 7365 * @option shadowRetinaUrl: String = null 7366 * 7367 * @option shadowSize: Point = null 7368 * Size of the shadow image in pixels. 7369 * 7370 * @option shadowAnchor: Point = null 7371 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7372 * as iconAnchor if not specified). 7373 * 7374 * @option className: String = '' 7375 * A custom class name to assign to both icon and shadow images. Empty by default. 7376 */ 7377 7378 options: { 7379 popupAnchor: [0, 0], 7380 tooltipAnchor: [0, 0], 7381 7382 // @option crossOrigin: Boolean|String = false 7383 // Whether the crossOrigin attribute will be added to the tiles. 7384 // 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. 7385 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 7386 crossOrigin: false 7387 }, 7388 7389 initialize: function (options) { 7390 setOptions(this, options); 7391 }, 7392 7393 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7394 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7395 // styled according to the options. 7396 createIcon: function (oldIcon) { 7397 return this._createIcon('icon', oldIcon); 7398 }, 7399 7400 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7401 // As `createIcon`, but for the shadow beneath it. 7402 createShadow: function (oldIcon) { 7403 return this._createIcon('shadow', oldIcon); 7404 }, 7405 7406 _createIcon: function (name, oldIcon) { 7407 var src = this._getIconUrl(name); 7408 7409 if (!src) { 7410 if (name === 'icon') { 7411 throw new Error('iconUrl not set in Icon options (see the docs).'); 7412 } 7413 return null; 7414 } 7415 7416 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7417 this._setIconStyles(img, name); 7418 7419 if (this.options.crossOrigin || this.options.crossOrigin === '') { 7420 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 7421 } 7422 7423 return img; 7424 }, 7425 7426 _setIconStyles: function (img, name) { 7427 var options = this.options; 7428 var sizeOption = options[name + 'Size']; 7429 7430 if (typeof sizeOption === 'number') { 7431 sizeOption = [sizeOption, sizeOption]; 7432 } 7433 7434 var size = toPoint(sizeOption), 7435 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7436 size && size.divideBy(2, true)); 7437 7438 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7439 7440 if (anchor) { 7441 img.style.marginLeft = (-anchor.x) + 'px'; 7442 img.style.marginTop = (-anchor.y) + 'px'; 7443 } 7444 7445 if (size) { 7446 img.style.width = size.x + 'px'; 7447 img.style.height = size.y + 'px'; 7448 } 7449 }, 7450 7451 _createImg: function (src, el) { 7452 el = el || document.createElement('img'); 7453 el.src = src; 7454 return el; 7455 }, 7456 7457 _getIconUrl: function (name) { 7458 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7459 } 7460 }); 7461 7462 7463 // @factory L.icon(options: Icon options) 7464 // Creates an icon instance with the given options. 7465 function icon(options) { 7466 return new Icon(options); 7467 } 7468 7469 /* 7470 * @miniclass Icon.Default (Icon) 7471 * @aka L.Icon.Default 7472 * @section 7473 * 7474 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7475 * no icon is specified. Points to the blue marker image distributed with Leaflet 7476 * releases. 7477 * 7478 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7479 * (which is a set of `Icon options`). 7480 * 7481 * If you want to _completely_ replace the default icon, override the 7482 * `L.Marker.prototype.options.icon` with your own icon instead. 7483 */ 7484 7485 var IconDefault = Icon.extend({ 7486 7487 options: { 7488 iconUrl: 'marker-icon.png', 7489 iconRetinaUrl: 'marker-icon-2x.png', 7490 shadowUrl: 'marker-shadow.png', 7491 iconSize: [25, 41], 7492 iconAnchor: [12, 41], 7493 popupAnchor: [1, -34], 7494 tooltipAnchor: [16, -28], 7495 shadowSize: [41, 41] 7496 }, 7497 7498 _getIconUrl: function (name) { 7499 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only 7500 IconDefault.imagePath = this._detectIconPath(); 7501 } 7502 7503 // @option imagePath: String 7504 // `Icon.Default` will try to auto-detect the location of the 7505 // blue icon images. If you are placing these images in a non-standard 7506 // way, set this option to point to the right path. 7507 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7508 }, 7509 7510 _stripUrl: function (path) { // separate function to use in tests 7511 var strip = function (str, re, idx) { 7512 var match = re.exec(str); 7513 return match && match[idx]; 7514 }; 7515 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2); 7516 return path && strip(path, /^(.*)marker-icon\.png$/, 1); 7517 }, 7518 7519 _detectIconPath: function () { 7520 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7521 var path = getStyle(el, 'background-image') || 7522 getStyle(el, 'backgroundImage'); // IE8 7523 7524 document.body.removeChild(el); 7525 path = this._stripUrl(path); 7526 if (path) { return path; } 7527 var link = document.querySelector('link[href$="leaflet.css"]'); 7528 if (!link) { return ''; } 7529 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1); 7530 } 7531 }); 7532 7533 /* 7534 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7535 */ 7536 7537 7538 /* @namespace Marker 7539 * @section Interaction handlers 7540 * 7541 * 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: 7542 * 7543 * ```js 7544 * marker.dragging.disable(); 7545 * ``` 7546 * 7547 * @property dragging: Handler 7548 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7549 */ 7550 7551 var MarkerDrag = Handler.extend({ 7552 initialize: function (marker) { 7553 this._marker = marker; 7554 }, 7555 7556 addHooks: function () { 7557 var icon = this._marker._icon; 7558 7559 if (!this._draggable) { 7560 this._draggable = new Draggable(icon, icon, true); 7561 } 7562 7563 this._draggable.on({ 7564 dragstart: this._onDragStart, 7565 predrag: this._onPreDrag, 7566 drag: this._onDrag, 7567 dragend: this._onDragEnd 7568 }, this).enable(); 7569 7570 addClass(icon, 'leaflet-marker-draggable'); 7571 }, 7572 7573 removeHooks: function () { 7574 this._draggable.off({ 7575 dragstart: this._onDragStart, 7576 predrag: this._onPreDrag, 7577 drag: this._onDrag, 7578 dragend: this._onDragEnd 7579 }, this).disable(); 7580 7581 if (this._marker._icon) { 7582 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7583 } 7584 }, 7585 7586 moved: function () { 7587 return this._draggable && this._draggable._moved; 7588 }, 7589 7590 _adjustPan: function (e) { 7591 var marker = this._marker, 7592 map = marker._map, 7593 speed = this._marker.options.autoPanSpeed, 7594 padding = this._marker.options.autoPanPadding, 7595 iconPos = getPosition(marker._icon), 7596 bounds = map.getPixelBounds(), 7597 origin = map.getPixelOrigin(); 7598 7599 var panBounds = toBounds( 7600 bounds.min._subtract(origin).add(padding), 7601 bounds.max._subtract(origin).subtract(padding) 7602 ); 7603 7604 if (!panBounds.contains(iconPos)) { 7605 // Compute incremental movement 7606 var movement = toPoint( 7607 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7608 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7609 7610 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7611 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7612 ).multiplyBy(speed); 7613 7614 map.panBy(movement, {animate: false}); 7615 7616 this._draggable._newPos._add(movement); 7617 this._draggable._startPos._add(movement); 7618 7619 setPosition(marker._icon, this._draggable._newPos); 7620 this._onDrag(e); 7621 7622 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7623 } 7624 }, 7625 7626 _onDragStart: function () { 7627 // @section Dragging events 7628 // @event dragstart: Event 7629 // Fired when the user starts dragging the marker. 7630 7631 // @event movestart: Event 7632 // Fired when the marker starts moving (because of dragging). 7633 7634 this._oldLatLng = this._marker.getLatLng(); 7635 7636 // When using ES6 imports it could not be set when `Popup` was not imported as well 7637 this._marker.closePopup && this._marker.closePopup(); 7638 7639 this._marker 7640 .fire('movestart') 7641 .fire('dragstart'); 7642 }, 7643 7644 _onPreDrag: function (e) { 7645 if (this._marker.options.autoPan) { 7646 cancelAnimFrame(this._panRequest); 7647 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7648 } 7649 }, 7650 7651 _onDrag: function (e) { 7652 var marker = this._marker, 7653 shadow = marker._shadow, 7654 iconPos = getPosition(marker._icon), 7655 latlng = marker._map.layerPointToLatLng(iconPos); 7656 7657 // update shadow position 7658 if (shadow) { 7659 setPosition(shadow, iconPos); 7660 } 7661 7662 marker._latlng = latlng; 7663 e.latlng = latlng; 7664 e.oldLatLng = this._oldLatLng; 7665 7666 // @event drag: Event 7667 // Fired repeatedly while the user drags the marker. 7668 marker 7669 .fire('move', e) 7670 .fire('drag', e); 7671 }, 7672 7673 _onDragEnd: function (e) { 7674 // @event dragend: DragEndEvent 7675 // Fired when the user stops dragging the marker. 7676 7677 cancelAnimFrame(this._panRequest); 7678 7679 // @event moveend: Event 7680 // Fired when the marker stops moving (because of dragging). 7681 delete this._oldLatLng; 7682 this._marker 7683 .fire('moveend') 7684 .fire('dragend', e); 7685 } 7686 }); 7687 7688 /* 7689 * @class Marker 7690 * @inherits Interactive layer 7691 * @aka L.Marker 7692 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7693 * 7694 * @example 7695 * 7696 * ```js 7697 * L.marker([50.5, 30.5]).addTo(map); 7698 * ``` 7699 */ 7700 7701 var Marker = Layer.extend({ 7702 7703 // @section 7704 // @aka Marker options 7705 options: { 7706 // @option icon: Icon = * 7707 // Icon instance to use for rendering the marker. 7708 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7709 // If not specified, a common instance of `L.Icon.Default` is used. 7710 icon: new IconDefault(), 7711 7712 // Option inherited from "Interactive layer" abstract class 7713 interactive: true, 7714 7715 // @option keyboard: Boolean = true 7716 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7717 keyboard: true, 7718 7719 // @option title: String = '' 7720 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7721 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 7722 title: '', 7723 7724 // @option alt: String = 'Marker' 7725 // Text for the `alt` attribute of the icon image. 7726 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 7727 alt: 'Marker', 7728 7729 // @option zIndexOffset: Number = 0 7730 // 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). 7731 zIndexOffset: 0, 7732 7733 // @option opacity: Number = 1.0 7734 // The opacity of the marker. 7735 opacity: 1, 7736 7737 // @option riseOnHover: Boolean = false 7738 // If `true`, the marker will get on top of others when you hover the mouse over it. 7739 riseOnHover: false, 7740 7741 // @option riseOffset: Number = 250 7742 // The z-index offset used for the `riseOnHover` feature. 7743 riseOffset: 250, 7744 7745 // @option pane: String = 'markerPane' 7746 // `Map pane` where the markers icon will be added. 7747 pane: 'markerPane', 7748 7749 // @option shadowPane: String = 'shadowPane' 7750 // `Map pane` where the markers shadow will be added. 7751 shadowPane: 'shadowPane', 7752 7753 // @option bubblingMouseEvents: Boolean = false 7754 // When `true`, a mouse event on this marker will trigger the same event on the map 7755 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7756 bubblingMouseEvents: false, 7757 7758 // @option autoPanOnFocus: Boolean = true 7759 // When `true`, the map will pan whenever the marker is focused (via 7760 // e.g. pressing `tab` on the keyboard) to ensure the marker is 7761 // visible within the map's bounds 7762 autoPanOnFocus: true, 7763 7764 // @section Draggable marker options 7765 // @option draggable: Boolean = false 7766 // Whether the marker is draggable with mouse/touch or not. 7767 draggable: false, 7768 7769 // @option autoPan: Boolean = false 7770 // Whether to pan the map when dragging this marker near its edge or not. 7771 autoPan: false, 7772 7773 // @option autoPanPadding: Point = Point(50, 50) 7774 // Distance (in pixels to the left/right and to the top/bottom) of the 7775 // map edge to start panning the map. 7776 autoPanPadding: [50, 50], 7777 7778 // @option autoPanSpeed: Number = 10 7779 // Number of pixels the map should pan by. 7780 autoPanSpeed: 10 7781 }, 7782 7783 /* @section 7784 * 7785 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 7786 */ 7787 7788 initialize: function (latlng, options) { 7789 setOptions(this, options); 7790 this._latlng = toLatLng(latlng); 7791 }, 7792 7793 onAdd: function (map) { 7794 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 7795 7796 if (this._zoomAnimated) { 7797 map.on('zoomanim', this._animateZoom, this); 7798 } 7799 7800 this._initIcon(); 7801 this.update(); 7802 }, 7803 7804 onRemove: function (map) { 7805 if (this.dragging && this.dragging.enabled()) { 7806 this.options.draggable = true; 7807 this.dragging.removeHooks(); 7808 } 7809 delete this.dragging; 7810 7811 if (this._zoomAnimated) { 7812 map.off('zoomanim', this._animateZoom, this); 7813 } 7814 7815 this._removeIcon(); 7816 this._removeShadow(); 7817 }, 7818 7819 getEvents: function () { 7820 return { 7821 zoom: this.update, 7822 viewreset: this.update 7823 }; 7824 }, 7825 7826 // @method getLatLng: LatLng 7827 // Returns the current geographical position of the marker. 7828 getLatLng: function () { 7829 return this._latlng; 7830 }, 7831 7832 // @method setLatLng(latlng: LatLng): this 7833 // Changes the marker position to the given point. 7834 setLatLng: function (latlng) { 7835 var oldLatLng = this._latlng; 7836 this._latlng = toLatLng(latlng); 7837 this.update(); 7838 7839 // @event move: Event 7840 // 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`. 7841 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7842 }, 7843 7844 // @method setZIndexOffset(offset: Number): this 7845 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 7846 setZIndexOffset: function (offset) { 7847 this.options.zIndexOffset = offset; 7848 return this.update(); 7849 }, 7850 7851 // @method getIcon: Icon 7852 // Returns the current icon used by the marker 7853 getIcon: function () { 7854 return this.options.icon; 7855 }, 7856 7857 // @method setIcon(icon: Icon): this 7858 // Changes the marker icon. 7859 setIcon: function (icon) { 7860 7861 this.options.icon = icon; 7862 7863 if (this._map) { 7864 this._initIcon(); 7865 this.update(); 7866 } 7867 7868 if (this._popup) { 7869 this.bindPopup(this._popup, this._popup.options); 7870 } 7871 7872 return this; 7873 }, 7874 7875 getElement: function () { 7876 return this._icon; 7877 }, 7878 7879 update: function () { 7880 7881 if (this._icon && this._map) { 7882 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 7883 this._setPos(pos); 7884 } 7885 7886 return this; 7887 }, 7888 7889 _initIcon: function () { 7890 var options = this.options, 7891 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7892 7893 var icon = options.icon.createIcon(this._icon), 7894 addIcon = false; 7895 7896 // if we're not reusing the icon, remove the old one and init new one 7897 if (icon !== this._icon) { 7898 if (this._icon) { 7899 this._removeIcon(); 7900 } 7901 addIcon = true; 7902 7903 if (options.title) { 7904 icon.title = options.title; 7905 } 7906 7907 if (icon.tagName === 'IMG') { 7908 icon.alt = options.alt || ''; 7909 } 7910 } 7911 7912 addClass(icon, classToAdd); 7913 7914 if (options.keyboard) { 7915 icon.tabIndex = '0'; 7916 icon.setAttribute('role', 'button'); 7917 } 7918 7919 this._icon = icon; 7920 7921 if (options.riseOnHover) { 7922 this.on({ 7923 mouseover: this._bringToFront, 7924 mouseout: this._resetZIndex 7925 }); 7926 } 7927 7928 if (this.options.autoPanOnFocus) { 7929 on(icon, 'focus', this._panOnFocus, this); 7930 } 7931 7932 var newShadow = options.icon.createShadow(this._shadow), 7933 addShadow = false; 7934 7935 if (newShadow !== this._shadow) { 7936 this._removeShadow(); 7937 addShadow = true; 7938 } 7939 7940 if (newShadow) { 7941 addClass(newShadow, classToAdd); 7942 newShadow.alt = ''; 7943 } 7944 this._shadow = newShadow; 7945 7946 7947 if (options.opacity < 1) { 7948 this._updateOpacity(); 7949 } 7950 7951 7952 if (addIcon) { 7953 this.getPane().appendChild(this._icon); 7954 } 7955 this._initInteraction(); 7956 if (newShadow && addShadow) { 7957 this.getPane(options.shadowPane).appendChild(this._shadow); 7958 } 7959 }, 7960 7961 _removeIcon: function () { 7962 if (this.options.riseOnHover) { 7963 this.off({ 7964 mouseover: this._bringToFront, 7965 mouseout: this._resetZIndex 7966 }); 7967 } 7968 7969 if (this.options.autoPanOnFocus) { 7970 off(this._icon, 'focus', this._panOnFocus, this); 7971 } 7972 7973 remove(this._icon); 7974 this.removeInteractiveTarget(this._icon); 7975 7976 this._icon = null; 7977 }, 7978 7979 _removeShadow: function () { 7980 if (this._shadow) { 7981 remove(this._shadow); 7982 } 7983 this._shadow = null; 7984 }, 7985 7986 _setPos: function (pos) { 7987 7988 if (this._icon) { 7989 setPosition(this._icon, pos); 7990 } 7991 7992 if (this._shadow) { 7993 setPosition(this._shadow, pos); 7994 } 7995 7996 this._zIndex = pos.y + this.options.zIndexOffset; 7997 7998 this._resetZIndex(); 7999 }, 8000 8001 _updateZIndex: function (offset) { 8002 if (this._icon) { 8003 this._icon.style.zIndex = this._zIndex + offset; 8004 } 8005 }, 8006 8007 _animateZoom: function (opt) { 8008 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 8009 8010 this._setPos(pos); 8011 }, 8012 8013 _initInteraction: function () { 8014 8015 if (!this.options.interactive) { return; } 8016 8017 addClass(this._icon, 'leaflet-interactive'); 8018 8019 this.addInteractiveTarget(this._icon); 8020 8021 if (MarkerDrag) { 8022 var draggable = this.options.draggable; 8023 if (this.dragging) { 8024 draggable = this.dragging.enabled(); 8025 this.dragging.disable(); 8026 } 8027 8028 this.dragging = new MarkerDrag(this); 8029 8030 if (draggable) { 8031 this.dragging.enable(); 8032 } 8033 } 8034 }, 8035 8036 // @method setOpacity(opacity: Number): this 8037 // Changes the opacity of the marker. 8038 setOpacity: function (opacity) { 8039 this.options.opacity = opacity; 8040 if (this._map) { 8041 this._updateOpacity(); 8042 } 8043 8044 return this; 8045 }, 8046 8047 _updateOpacity: function () { 8048 var opacity = this.options.opacity; 8049 8050 if (this._icon) { 8051 setOpacity(this._icon, opacity); 8052 } 8053 8054 if (this._shadow) { 8055 setOpacity(this._shadow, opacity); 8056 } 8057 }, 8058 8059 _bringToFront: function () { 8060 this._updateZIndex(this.options.riseOffset); 8061 }, 8062 8063 _resetZIndex: function () { 8064 this._updateZIndex(0); 8065 }, 8066 8067 _panOnFocus: function () { 8068 var map = this._map; 8069 if (!map) { return; } 8070 8071 var iconOpts = this.options.icon.options; 8072 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0); 8073 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0); 8074 8075 map.panInside(this._latlng, { 8076 paddingTopLeft: anchor, 8077 paddingBottomRight: size.subtract(anchor) 8078 }); 8079 }, 8080 8081 _getPopupAnchor: function () { 8082 return this.options.icon.options.popupAnchor; 8083 }, 8084 8085 _getTooltipAnchor: function () { 8086 return this.options.icon.options.tooltipAnchor; 8087 } 8088 }); 8089 8090 8091 // factory L.marker(latlng: LatLng, options? : Marker options) 8092 8093 // @factory L.marker(latlng: LatLng, options? : Marker options) 8094 // Instantiates a Marker object given a geographical point and optionally an options object. 8095 function marker(latlng, options) { 8096 return new Marker(latlng, options); 8097 } 8098 8099 /* 8100 * @class Path 8101 * @aka L.Path 8102 * @inherits Interactive layer 8103 * 8104 * An abstract class that contains options and constants shared between vector 8105 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 8106 */ 8107 8108 var Path = Layer.extend({ 8109 8110 // @section 8111 // @aka Path options 8112 options: { 8113 // @option stroke: Boolean = true 8114 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 8115 stroke: true, 8116 8117 // @option color: String = '#3388ff' 8118 // Stroke color 8119 color: '#3388ff', 8120 8121 // @option weight: Number = 3 8122 // Stroke width in pixels 8123 weight: 3, 8124 8125 // @option opacity: Number = 1.0 8126 // Stroke opacity 8127 opacity: 1, 8128 8129 // @option lineCap: String= 'round' 8130 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 8131 lineCap: 'round', 8132 8133 // @option lineJoin: String = 'round' 8134 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 8135 lineJoin: 'round', 8136 8137 // @option dashArray: String = null 8138 // 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). 8139 dashArray: null, 8140 8141 // @option dashOffset: String = null 8142 // 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). 8143 dashOffset: null, 8144 8145 // @option fill: Boolean = depends 8146 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 8147 fill: false, 8148 8149 // @option fillColor: String = * 8150 // Fill color. Defaults to the value of the [`color`](#path-color) option 8151 fillColor: null, 8152 8153 // @option fillOpacity: Number = 0.2 8154 // Fill opacity. 8155 fillOpacity: 0.2, 8156 8157 // @option fillRule: String = 'evenodd' 8158 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 8159 fillRule: 'evenodd', 8160 8161 // className: '', 8162 8163 // Option inherited from "Interactive layer" abstract class 8164 interactive: true, 8165 8166 // @option bubblingMouseEvents: Boolean = true 8167 // When `true`, a mouse event on this path will trigger the same event on the map 8168 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 8169 bubblingMouseEvents: true 8170 }, 8171 8172 beforeAdd: function (map) { 8173 // Renderer is set here because we need to call renderer.getEvents 8174 // before this.getEvents. 8175 this._renderer = map.getRenderer(this); 8176 }, 8177 8178 onAdd: function () { 8179 this._renderer._initPath(this); 8180 this._reset(); 8181 this._renderer._addPath(this); 8182 }, 8183 8184 onRemove: function () { 8185 this._renderer._removePath(this); 8186 }, 8187 8188 // @method redraw(): this 8189 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 8190 redraw: function () { 8191 if (this._map) { 8192 this._renderer._updatePath(this); 8193 } 8194 return this; 8195 }, 8196 8197 // @method setStyle(style: Path options): this 8198 // Changes the appearance of a Path based on the options in the `Path options` object. 8199 setStyle: function (style) { 8200 setOptions(this, style); 8201 if (this._renderer) { 8202 this._renderer._updateStyle(this); 8203 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) { 8204 this._updateBounds(); 8205 } 8206 } 8207 return this; 8208 }, 8209 8210 // @method bringToFront(): this 8211 // Brings the layer to the top of all path layers. 8212 bringToFront: function () { 8213 if (this._renderer) { 8214 this._renderer._bringToFront(this); 8215 } 8216 return this; 8217 }, 8218 8219 // @method bringToBack(): this 8220 // Brings the layer to the bottom of all path layers. 8221 bringToBack: function () { 8222 if (this._renderer) { 8223 this._renderer._bringToBack(this); 8224 } 8225 return this; 8226 }, 8227 8228 getElement: function () { 8229 return this._path; 8230 }, 8231 8232 _reset: function () { 8233 // defined in child classes 8234 this._project(); 8235 this._update(); 8236 }, 8237 8238 _clickTolerance: function () { 8239 // used when doing hit detection for Canvas layers 8240 return (this.options.stroke ? this.options.weight / 2 : 0) + 8241 (this._renderer.options.tolerance || 0); 8242 } 8243 }); 8244 8245 /* 8246 * @class CircleMarker 8247 * @aka L.CircleMarker 8248 * @inherits Path 8249 * 8250 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 8251 */ 8252 8253 var CircleMarker = Path.extend({ 8254 8255 // @section 8256 // @aka CircleMarker options 8257 options: { 8258 fill: true, 8259 8260 // @option radius: Number = 10 8261 // Radius of the circle marker, in pixels 8262 radius: 10 8263 }, 8264 8265 initialize: function (latlng, options) { 8266 setOptions(this, options); 8267 this._latlng = toLatLng(latlng); 8268 this._radius = this.options.radius; 8269 }, 8270 8271 // @method setLatLng(latLng: LatLng): this 8272 // Sets the position of a circle marker to a new location. 8273 setLatLng: function (latlng) { 8274 var oldLatLng = this._latlng; 8275 this._latlng = toLatLng(latlng); 8276 this.redraw(); 8277 8278 // @event move: Event 8279 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 8280 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 8281 }, 8282 8283 // @method getLatLng(): LatLng 8284 // Returns the current geographical position of the circle marker 8285 getLatLng: function () { 8286 return this._latlng; 8287 }, 8288 8289 // @method setRadius(radius: Number): this 8290 // Sets the radius of a circle marker. Units are in pixels. 8291 setRadius: function (radius) { 8292 this.options.radius = this._radius = radius; 8293 return this.redraw(); 8294 }, 8295 8296 // @method getRadius(): Number 8297 // Returns the current radius of the circle 8298 getRadius: function () { 8299 return this._radius; 8300 }, 8301 8302 setStyle : function (options) { 8303 var radius = options && options.radius || this._radius; 8304 Path.prototype.setStyle.call(this, options); 8305 this.setRadius(radius); 8306 return this; 8307 }, 8308 8309 _project: function () { 8310 this._point = this._map.latLngToLayerPoint(this._latlng); 8311 this._updateBounds(); 8312 }, 8313 8314 _updateBounds: function () { 8315 var r = this._radius, 8316 r2 = this._radiusY || r, 8317 w = this._clickTolerance(), 8318 p = [r + w, r2 + w]; 8319 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 8320 }, 8321 8322 _update: function () { 8323 if (this._map) { 8324 this._updatePath(); 8325 } 8326 }, 8327 8328 _updatePath: function () { 8329 this._renderer._updateCircle(this); 8330 }, 8331 8332 _empty: function () { 8333 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 8334 }, 8335 8336 // Needed by the `Canvas` renderer for interactivity 8337 _containsPoint: function (p) { 8338 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 8339 } 8340 }); 8341 8342 8343 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 8344 // Instantiates a circle marker object given a geographical point, and an optional options object. 8345 function circleMarker(latlng, options) { 8346 return new CircleMarker(latlng, options); 8347 } 8348 8349 /* 8350 * @class Circle 8351 * @aka L.Circle 8352 * @inherits CircleMarker 8353 * 8354 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 8355 * 8356 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 8357 * 8358 * @example 8359 * 8360 * ```js 8361 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 8362 * ``` 8363 */ 8364 8365 var Circle = CircleMarker.extend({ 8366 8367 initialize: function (latlng, options, legacyOptions) { 8368 if (typeof options === 'number') { 8369 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 8370 options = extend({}, legacyOptions, {radius: options}); 8371 } 8372 setOptions(this, options); 8373 this._latlng = toLatLng(latlng); 8374 8375 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 8376 8377 // @section 8378 // @aka Circle options 8379 // @option radius: Number; Radius of the circle, in meters. 8380 this._mRadius = this.options.radius; 8381 }, 8382 8383 // @method setRadius(radius: Number): this 8384 // Sets the radius of a circle. Units are in meters. 8385 setRadius: function (radius) { 8386 this._mRadius = radius; 8387 return this.redraw(); 8388 }, 8389 8390 // @method getRadius(): Number 8391 // Returns the current radius of a circle. Units are in meters. 8392 getRadius: function () { 8393 return this._mRadius; 8394 }, 8395 8396 // @method getBounds(): LatLngBounds 8397 // Returns the `LatLngBounds` of the path. 8398 getBounds: function () { 8399 var half = [this._radius, this._radiusY || this._radius]; 8400 8401 return new LatLngBounds( 8402 this._map.layerPointToLatLng(this._point.subtract(half)), 8403 this._map.layerPointToLatLng(this._point.add(half))); 8404 }, 8405 8406 setStyle: Path.prototype.setStyle, 8407 8408 _project: function () { 8409 8410 var lng = this._latlng.lng, 8411 lat = this._latlng.lat, 8412 map = this._map, 8413 crs = map.options.crs; 8414 8415 if (crs.distance === Earth.distance) { 8416 var d = Math.PI / 180, 8417 latR = (this._mRadius / Earth.R) / d, 8418 top = map.project([lat + latR, lng]), 8419 bottom = map.project([lat - latR, lng]), 8420 p = top.add(bottom).divideBy(2), 8421 lat2 = map.unproject(p).lat, 8422 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8423 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8424 8425 if (isNaN(lngR) || lngR === 0) { 8426 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8427 } 8428 8429 this._point = p.subtract(map.getPixelOrigin()); 8430 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 8431 this._radiusY = p.y - top.y; 8432 8433 } else { 8434 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8435 8436 this._point = map.latLngToLayerPoint(this._latlng); 8437 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8438 } 8439 8440 this._updateBounds(); 8441 } 8442 }); 8443 8444 // @factory L.circle(latlng: LatLng, options?: Circle options) 8445 // Instantiates a circle object given a geographical point, and an options object 8446 // which contains the circle radius. 8447 // @alternative 8448 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8449 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8450 // Do not use in new applications or plugins. 8451 function circle(latlng, options, legacyOptions) { 8452 return new Circle(latlng, options, legacyOptions); 8453 } 8454 8455 /* 8456 * @class Polyline 8457 * @aka L.Polyline 8458 * @inherits Path 8459 * 8460 * A class for drawing polyline overlays on a map. Extends `Path`. 8461 * 8462 * @example 8463 * 8464 * ```js 8465 * // create a red polyline from an array of LatLng points 8466 * var latlngs = [ 8467 * [45.51, -122.68], 8468 * [37.77, -122.43], 8469 * [34.04, -118.2] 8470 * ]; 8471 * 8472 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8473 * 8474 * // zoom the map to the polyline 8475 * map.fitBounds(polyline.getBounds()); 8476 * ``` 8477 * 8478 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8479 * 8480 * ```js 8481 * // create a red polyline from an array of arrays of LatLng points 8482 * var latlngs = [ 8483 * [[45.51, -122.68], 8484 * [37.77, -122.43], 8485 * [34.04, -118.2]], 8486 * [[40.78, -73.91], 8487 * [41.83, -87.62], 8488 * [32.76, -96.72]] 8489 * ]; 8490 * ``` 8491 */ 8492 8493 8494 var Polyline = Path.extend({ 8495 8496 // @section 8497 // @aka Polyline options 8498 options: { 8499 // @option smoothFactor: Number = 1.0 8500 // How much to simplify the polyline on each zoom level. More means 8501 // better performance and smoother look, and less means more accurate representation. 8502 smoothFactor: 1.0, 8503 8504 // @option noClip: Boolean = false 8505 // Disable polyline clipping. 8506 noClip: false 8507 }, 8508 8509 initialize: function (latlngs, options) { 8510 setOptions(this, options); 8511 this._setLatLngs(latlngs); 8512 }, 8513 8514 // @method getLatLngs(): LatLng[] 8515 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8516 getLatLngs: function () { 8517 return this._latlngs; 8518 }, 8519 8520 // @method setLatLngs(latlngs: LatLng[]): this 8521 // Replaces all the points in the polyline with the given array of geographical points. 8522 setLatLngs: function (latlngs) { 8523 this._setLatLngs(latlngs); 8524 return this.redraw(); 8525 }, 8526 8527 // @method isEmpty(): Boolean 8528 // Returns `true` if the Polyline has no LatLngs. 8529 isEmpty: function () { 8530 return !this._latlngs.length; 8531 }, 8532 8533 // @method closestLayerPoint(p: Point): Point 8534 // Returns the point closest to `p` on the Polyline. 8535 closestLayerPoint: function (p) { 8536 var minDistance = Infinity, 8537 minPoint = null, 8538 closest = _sqClosestPointOnSegment, 8539 p1, p2; 8540 8541 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8542 var points = this._parts[j]; 8543 8544 for (var i = 1, len = points.length; i < len; i++) { 8545 p1 = points[i - 1]; 8546 p2 = points[i]; 8547 8548 var sqDist = closest(p, p1, p2, true); 8549 8550 if (sqDist < minDistance) { 8551 minDistance = sqDist; 8552 minPoint = closest(p, p1, p2); 8553 } 8554 } 8555 } 8556 if (minPoint) { 8557 minPoint.distance = Math.sqrt(minDistance); 8558 } 8559 return minPoint; 8560 }, 8561 8562 // @method getCenter(): LatLng 8563 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline. 8564 getCenter: function () { 8565 // throws error when not yet added to map as this center calculation requires projected coordinates 8566 if (!this._map) { 8567 throw new Error('Must add layer to map before using getCenter()'); 8568 } 8569 return polylineCenter(this._defaultShape(), this._map.options.crs); 8570 }, 8571 8572 // @method getBounds(): LatLngBounds 8573 // Returns the `LatLngBounds` of the path. 8574 getBounds: function () { 8575 return this._bounds; 8576 }, 8577 8578 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this 8579 // Adds a given point to the polyline. By default, adds to the first ring of 8580 // the polyline in case of a multi-polyline, but can be overridden by passing 8581 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8582 addLatLng: function (latlng, latlngs) { 8583 latlngs = latlngs || this._defaultShape(); 8584 latlng = toLatLng(latlng); 8585 latlngs.push(latlng); 8586 this._bounds.extend(latlng); 8587 return this.redraw(); 8588 }, 8589 8590 _setLatLngs: function (latlngs) { 8591 this._bounds = new LatLngBounds(); 8592 this._latlngs = this._convertLatLngs(latlngs); 8593 }, 8594 8595 _defaultShape: function () { 8596 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8597 }, 8598 8599 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8600 _convertLatLngs: function (latlngs) { 8601 var result = [], 8602 flat = isFlat(latlngs); 8603 8604 for (var i = 0, len = latlngs.length; i < len; i++) { 8605 if (flat) { 8606 result[i] = toLatLng(latlngs[i]); 8607 this._bounds.extend(result[i]); 8608 } else { 8609 result[i] = this._convertLatLngs(latlngs[i]); 8610 } 8611 } 8612 8613 return result; 8614 }, 8615 8616 _project: function () { 8617 var pxBounds = new Bounds(); 8618 this._rings = []; 8619 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8620 8621 if (this._bounds.isValid() && pxBounds.isValid()) { 8622 this._rawPxBounds = pxBounds; 8623 this._updateBounds(); 8624 } 8625 }, 8626 8627 _updateBounds: function () { 8628 var w = this._clickTolerance(), 8629 p = new Point(w, w); 8630 8631 if (!this._rawPxBounds) { 8632 return; 8633 } 8634 8635 this._pxBounds = new Bounds([ 8636 this._rawPxBounds.min.subtract(p), 8637 this._rawPxBounds.max.add(p) 8638 ]); 8639 }, 8640 8641 // recursively turns latlngs into a set of rings with projected coordinates 8642 _projectLatlngs: function (latlngs, result, projectedBounds) { 8643 var flat = latlngs[0] instanceof LatLng, 8644 len = latlngs.length, 8645 i, ring; 8646 8647 if (flat) { 8648 ring = []; 8649 for (i = 0; i < len; i++) { 8650 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8651 projectedBounds.extend(ring[i]); 8652 } 8653 result.push(ring); 8654 } else { 8655 for (i = 0; i < len; i++) { 8656 this._projectLatlngs(latlngs[i], result, projectedBounds); 8657 } 8658 } 8659 }, 8660 8661 // clip polyline by renderer bounds so that we have less to render for performance 8662 _clipPoints: function () { 8663 var bounds = this._renderer._bounds; 8664 8665 this._parts = []; 8666 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8667 return; 8668 } 8669 8670 if (this.options.noClip) { 8671 this._parts = this._rings; 8672 return; 8673 } 8674 8675 var parts = this._parts, 8676 i, j, k, len, len2, segment, points; 8677 8678 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8679 points = this._rings[i]; 8680 8681 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8682 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8683 8684 if (!segment) { continue; } 8685 8686 parts[k] = parts[k] || []; 8687 parts[k].push(segment[0]); 8688 8689 // if segment goes out of screen, or it's the last one, it's the end of the line part 8690 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8691 parts[k].push(segment[1]); 8692 k++; 8693 } 8694 } 8695 } 8696 }, 8697 8698 // simplify each clipped part of the polyline for performance 8699 _simplifyPoints: function () { 8700 var parts = this._parts, 8701 tolerance = this.options.smoothFactor; 8702 8703 for (var i = 0, len = parts.length; i < len; i++) { 8704 parts[i] = simplify(parts[i], tolerance); 8705 } 8706 }, 8707 8708 _update: function () { 8709 if (!this._map) { return; } 8710 8711 this._clipPoints(); 8712 this._simplifyPoints(); 8713 this._updatePath(); 8714 }, 8715 8716 _updatePath: function () { 8717 this._renderer._updatePoly(this); 8718 }, 8719 8720 // Needed by the `Canvas` renderer for interactivity 8721 _containsPoint: function (p, closed) { 8722 var i, j, k, len, len2, part, 8723 w = this._clickTolerance(); 8724 8725 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8726 8727 // hit detection for polylines 8728 for (i = 0, len = this._parts.length; i < len; i++) { 8729 part = this._parts[i]; 8730 8731 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8732 if (!closed && (j === 0)) { continue; } 8733 8734 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8735 return true; 8736 } 8737 } 8738 } 8739 return false; 8740 } 8741 }); 8742 8743 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8744 // Instantiates a polyline object given an array of geographical points and 8745 // optionally an options object. You can create a `Polyline` object with 8746 // multiple separate lines (`MultiPolyline`) by passing an array of arrays 8747 // of geographic points. 8748 function polyline(latlngs, options) { 8749 return new Polyline(latlngs, options); 8750 } 8751 8752 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8753 Polyline._flat = _flat; 8754 8755 /* 8756 * @class Polygon 8757 * @aka L.Polygon 8758 * @inherits Polyline 8759 * 8760 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8761 * 8762 * 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. 8763 * 8764 * 8765 * @example 8766 * 8767 * ```js 8768 * // create a red polygon from an array of LatLng points 8769 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 8770 * 8771 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8772 * 8773 * // zoom the map to the polygon 8774 * map.fitBounds(polygon.getBounds()); 8775 * ``` 8776 * 8777 * 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: 8778 * 8779 * ```js 8780 * var latlngs = [ 8781 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8782 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8783 * ]; 8784 * ``` 8785 * 8786 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8787 * 8788 * ```js 8789 * var latlngs = [ 8790 * [ // first polygon 8791 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8792 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8793 * ], 8794 * [ // second polygon 8795 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 8796 * ] 8797 * ]; 8798 * ``` 8799 */ 8800 8801 var Polygon = Polyline.extend({ 8802 8803 options: { 8804 fill: true 8805 }, 8806 8807 isEmpty: function () { 8808 return !this._latlngs.length || !this._latlngs[0].length; 8809 }, 8810 8811 // @method getCenter(): LatLng 8812 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon. 8813 getCenter: function () { 8814 // throws error when not yet added to map as this center calculation requires projected coordinates 8815 if (!this._map) { 8816 throw new Error('Must add layer to map before using getCenter()'); 8817 } 8818 return polygonCenter(this._defaultShape(), this._map.options.crs); 8819 }, 8820 8821 _convertLatLngs: function (latlngs) { 8822 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 8823 len = result.length; 8824 8825 // remove last point if it equals first one 8826 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 8827 result.pop(); 8828 } 8829 return result; 8830 }, 8831 8832 _setLatLngs: function (latlngs) { 8833 Polyline.prototype._setLatLngs.call(this, latlngs); 8834 if (isFlat(this._latlngs)) { 8835 this._latlngs = [this._latlngs]; 8836 } 8837 }, 8838 8839 _defaultShape: function () { 8840 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8841 }, 8842 8843 _clipPoints: function () { 8844 // polygons need a different clipping algorithm so we redefine that 8845 8846 var bounds = this._renderer._bounds, 8847 w = this.options.weight, 8848 p = new Point(w, w); 8849 8850 // increase clip padding by stroke width to avoid stroke on clip edges 8851 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8852 8853 this._parts = []; 8854 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8855 return; 8856 } 8857 8858 if (this.options.noClip) { 8859 this._parts = this._rings; 8860 return; 8861 } 8862 8863 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8864 clipped = clipPolygon(this._rings[i], bounds, true); 8865 if (clipped.length) { 8866 this._parts.push(clipped); 8867 } 8868 } 8869 }, 8870 8871 _updatePath: function () { 8872 this._renderer._updatePoly(this, true); 8873 }, 8874 8875 // Needed by the `Canvas` renderer for interactivity 8876 _containsPoint: function (p) { 8877 var inside = false, 8878 part, p1, p2, i, j, k, len, len2; 8879 8880 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8881 8882 // ray casting algorithm for detecting if point is in polygon 8883 for (i = 0, len = this._parts.length; i < len; i++) { 8884 part = this._parts[i]; 8885 8886 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8887 p1 = part[j]; 8888 p2 = part[k]; 8889 8890 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)) { 8891 inside = !inside; 8892 } 8893 } 8894 } 8895 8896 // also check if it's on polygon stroke 8897 return inside || Polyline.prototype._containsPoint.call(this, p, true); 8898 } 8899 8900 }); 8901 8902 8903 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8904 function polygon(latlngs, options) { 8905 return new Polygon(latlngs, options); 8906 } 8907 8908 /* 8909 * @class GeoJSON 8910 * @aka L.GeoJSON 8911 * @inherits FeatureGroup 8912 * 8913 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 8914 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 8915 * 8916 * @example 8917 * 8918 * ```js 8919 * L.geoJSON(data, { 8920 * style: function (feature) { 8921 * return {color: feature.properties.color}; 8922 * } 8923 * }).bindPopup(function (layer) { 8924 * return layer.feature.properties.description; 8925 * }).addTo(map); 8926 * ``` 8927 */ 8928 8929 var GeoJSON = FeatureGroup.extend({ 8930 8931 /* @section 8932 * @aka GeoJSON options 8933 * 8934 * @option pointToLayer: Function = * 8935 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 8936 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 8937 * The default is to spawn a default `Marker`: 8938 * ```js 8939 * function(geoJsonPoint, latlng) { 8940 * return L.marker(latlng); 8941 * } 8942 * ``` 8943 * 8944 * @option style: Function = * 8945 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, 8946 * called internally when data is added. 8947 * The default value is to not override any defaults: 8948 * ```js 8949 * function (geoJsonFeature) { 8950 * return {} 8951 * } 8952 * ``` 8953 * 8954 * @option onEachFeature: Function = * 8955 * A `Function` that will be called once for each created `Feature`, after it has 8956 * been created and styled. Useful for attaching events and popups to features. 8957 * The default is to do nothing with the newly created layers: 8958 * ```js 8959 * function (feature, layer) {} 8960 * ``` 8961 * 8962 * @option filter: Function = * 8963 * A `Function` that will be used to decide whether to include a feature or not. 8964 * The default is to include all features: 8965 * ```js 8966 * function (geoJsonFeature) { 8967 * return true; 8968 * } 8969 * ``` 8970 * Note: dynamically changing the `filter` option will have effect only on newly 8971 * added data. It will _not_ re-evaluate already included features. 8972 * 8973 * @option coordsToLatLng: Function = * 8974 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 8975 * The default is the `coordsToLatLng` static method. 8976 * 8977 * @option markersInheritOptions: Boolean = false 8978 * Whether default Markers for "Point" type Features inherit from group options. 8979 */ 8980 8981 initialize: function (geojson, options) { 8982 setOptions(this, options); 8983 8984 this._layers = {}; 8985 8986 if (geojson) { 8987 this.addData(geojson); 8988 } 8989 }, 8990 8991 // @method addData( <GeoJSON> data ): this 8992 // Adds a GeoJSON object to the layer. 8993 addData: function (geojson) { 8994 var features = isArray(geojson) ? geojson : geojson.features, 8995 i, len, feature; 8996 8997 if (features) { 8998 for (i = 0, len = features.length; i < len; i++) { 8999 // only add this if geometry or geometries are set and not null 9000 feature = features[i]; 9001 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 9002 this.addData(feature); 9003 } 9004 } 9005 return this; 9006 } 9007 9008 var options = this.options; 9009 9010 if (options.filter && !options.filter(geojson)) { return this; } 9011 9012 var layer = geometryToLayer(geojson, options); 9013 if (!layer) { 9014 return this; 9015 } 9016 layer.feature = asFeature(geojson); 9017 9018 layer.defaultOptions = layer.options; 9019 this.resetStyle(layer); 9020 9021 if (options.onEachFeature) { 9022 options.onEachFeature(geojson, layer); 9023 } 9024 9025 return this.addLayer(layer); 9026 }, 9027 9028 // @method resetStyle( <Path> layer? ): this 9029 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 9030 // If `layer` is omitted, the style of all features in the current layer is reset. 9031 resetStyle: function (layer) { 9032 if (layer === undefined) { 9033 return this.eachLayer(this.resetStyle, this); 9034 } 9035 // reset any custom styles 9036 layer.options = extend({}, layer.defaultOptions); 9037 this._setLayerStyle(layer, this.options.style); 9038 return this; 9039 }, 9040 9041 // @method setStyle( <Function> style ): this 9042 // Changes styles of GeoJSON vector layers with the given style function. 9043 setStyle: function (style) { 9044 return this.eachLayer(function (layer) { 9045 this._setLayerStyle(layer, style); 9046 }, this); 9047 }, 9048 9049 _setLayerStyle: function (layer, style) { 9050 if (layer.setStyle) { 9051 if (typeof style === 'function') { 9052 style = style(layer.feature); 9053 } 9054 layer.setStyle(style); 9055 } 9056 } 9057 }); 9058 9059 // @section 9060 // There are several static functions which can be called without instantiating L.GeoJSON: 9061 9062 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 9063 // Creates a `Layer` from a given GeoJSON feature. Can use a custom 9064 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 9065 // functions if provided as options. 9066 function geometryToLayer(geojson, options) { 9067 9068 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 9069 coords = geometry ? geometry.coordinates : null, 9070 layers = [], 9071 pointToLayer = options && options.pointToLayer, 9072 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 9073 latlng, latlngs, i, len; 9074 9075 if (!coords && !geometry) { 9076 return null; 9077 } 9078 9079 switch (geometry.type) { 9080 case 'Point': 9081 latlng = _coordsToLatLng(coords); 9082 return _pointToLayer(pointToLayer, geojson, latlng, options); 9083 9084 case 'MultiPoint': 9085 for (i = 0, len = coords.length; i < len; i++) { 9086 latlng = _coordsToLatLng(coords[i]); 9087 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 9088 } 9089 return new FeatureGroup(layers); 9090 9091 case 'LineString': 9092 case 'MultiLineString': 9093 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 9094 return new Polyline(latlngs, options); 9095 9096 case 'Polygon': 9097 case 'MultiPolygon': 9098 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 9099 return new Polygon(latlngs, options); 9100 9101 case 'GeometryCollection': 9102 for (i = 0, len = geometry.geometries.length; i < len; i++) { 9103 var geoLayer = geometryToLayer({ 9104 geometry: geometry.geometries[i], 9105 type: 'Feature', 9106 properties: geojson.properties 9107 }, options); 9108 9109 if (geoLayer) { 9110 layers.push(geoLayer); 9111 } 9112 } 9113 return new FeatureGroup(layers); 9114 9115 case 'FeatureCollection': 9116 for (i = 0, len = geometry.features.length; i < len; i++) { 9117 var featureLayer = geometryToLayer(geometry.features[i], options); 9118 9119 if (featureLayer) { 9120 layers.push(featureLayer); 9121 } 9122 } 9123 return new FeatureGroup(layers); 9124 9125 default: 9126 throw new Error('Invalid GeoJSON object.'); 9127 } 9128 } 9129 9130 function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 9131 return pointToLayerFn ? 9132 pointToLayerFn(geojson, latlng) : 9133 new Marker(latlng, options && options.markersInheritOptions && options); 9134 } 9135 9136 // @function coordsToLatLng(coords: Array): LatLng 9137 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 9138 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 9139 function coordsToLatLng(coords) { 9140 return new LatLng(coords[1], coords[0], coords[2]); 9141 } 9142 9143 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 9144 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 9145 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 9146 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 9147 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 9148 var latlngs = []; 9149 9150 for (var i = 0, len = coords.length, latlng; i < len; i++) { 9151 latlng = levelsDeep ? 9152 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 9153 (_coordsToLatLng || coordsToLatLng)(coords[i]); 9154 9155 latlngs.push(latlng); 9156 } 9157 9158 return latlngs; 9159 } 9160 9161 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array 9162 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 9163 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 9164 function latLngToCoords(latlng, precision) { 9165 latlng = toLatLng(latlng); 9166 return latlng.alt !== undefined ? 9167 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 9168 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 9169 } 9170 9171 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array 9172 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 9173 // `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. 9174 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 9175 function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 9176 var coords = []; 9177 9178 for (var i = 0, len = latlngs.length; i < len; i++) { 9179 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion 9180 coords.push(levelsDeep ? 9181 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) : 9182 latLngToCoords(latlngs[i], precision)); 9183 } 9184 9185 if (!levelsDeep && closed && coords.length > 0) { 9186 coords.push(coords[0].slice()); 9187 } 9188 9189 return coords; 9190 } 9191 9192 function getFeature(layer, newGeometry) { 9193 return layer.feature ? 9194 extend({}, layer.feature, {geometry: newGeometry}) : 9195 asFeature(newGeometry); 9196 } 9197 9198 // @function asFeature(geojson: Object): Object 9199 // Normalize GeoJSON geometries/features into GeoJSON features. 9200 function asFeature(geojson) { 9201 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 9202 return geojson; 9203 } 9204 9205 return { 9206 type: 'Feature', 9207 properties: {}, 9208 geometry: geojson 9209 }; 9210 } 9211 9212 var PointToGeoJSON = { 9213 toGeoJSON: function (precision) { 9214 return getFeature(this, { 9215 type: 'Point', 9216 coordinates: latLngToCoords(this.getLatLng(), precision) 9217 }); 9218 } 9219 }; 9220 9221 // @namespace Marker 9222 // @section Other methods 9223 // @method toGeoJSON(precision?: Number|false): Object 9224 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9225 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 9226 Marker.include(PointToGeoJSON); 9227 9228 // @namespace CircleMarker 9229 // @method toGeoJSON(precision?: Number|false): Object 9230 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9231 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 9232 Circle.include(PointToGeoJSON); 9233 CircleMarker.include(PointToGeoJSON); 9234 9235 9236 // @namespace Polyline 9237 // @method toGeoJSON(precision?: Number|false): Object 9238 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9239 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 9240 Polyline.include({ 9241 toGeoJSON: function (precision) { 9242 var multi = !isFlat(this._latlngs); 9243 9244 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 9245 9246 return getFeature(this, { 9247 type: (multi ? 'Multi' : '') + 'LineString', 9248 coordinates: coords 9249 }); 9250 } 9251 }); 9252 9253 // @namespace Polygon 9254 // @method toGeoJSON(precision?: Number|false): Object 9255 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9256 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 9257 Polygon.include({ 9258 toGeoJSON: function (precision) { 9259 var holes = !isFlat(this._latlngs), 9260 multi = holes && !isFlat(this._latlngs[0]); 9261 9262 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 9263 9264 if (!holes) { 9265 coords = [coords]; 9266 } 9267 9268 return getFeature(this, { 9269 type: (multi ? 'Multi' : '') + 'Polygon', 9270 coordinates: coords 9271 }); 9272 } 9273 }); 9274 9275 9276 // @namespace LayerGroup 9277 LayerGroup.include({ 9278 toMultiPoint: function (precision) { 9279 var coords = []; 9280 9281 this.eachLayer(function (layer) { 9282 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 9283 }); 9284 9285 return getFeature(this, { 9286 type: 'MultiPoint', 9287 coordinates: coords 9288 }); 9289 }, 9290 9291 // @method toGeoJSON(precision?: Number|false): Object 9292 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9293 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 9294 toGeoJSON: function (precision) { 9295 9296 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 9297 9298 if (type === 'MultiPoint') { 9299 return this.toMultiPoint(precision); 9300 } 9301 9302 var isGeometryCollection = type === 'GeometryCollection', 9303 jsons = []; 9304 9305 this.eachLayer(function (layer) { 9306 if (layer.toGeoJSON) { 9307 var json = layer.toGeoJSON(precision); 9308 if (isGeometryCollection) { 9309 jsons.push(json.geometry); 9310 } else { 9311 var feature = asFeature(json); 9312 // Squash nested feature collections 9313 if (feature.type === 'FeatureCollection') { 9314 jsons.push.apply(jsons, feature.features); 9315 } else { 9316 jsons.push(feature); 9317 } 9318 } 9319 } 9320 }); 9321 9322 if (isGeometryCollection) { 9323 return getFeature(this, { 9324 geometries: jsons, 9325 type: 'GeometryCollection' 9326 }); 9327 } 9328 9329 return { 9330 type: 'FeatureCollection', 9331 features: jsons 9332 }; 9333 } 9334 }); 9335 9336 // @namespace GeoJSON 9337 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9338 // Creates a GeoJSON layer. Optionally accepts an object in 9339 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 9340 // (you can alternatively add it later with `addData` method) and an `options` object. 9341 function geoJSON(geojson, options) { 9342 return new GeoJSON(geojson, options); 9343 } 9344 9345 // Backward compatibility. 9346 var geoJson = geoJSON; 9347 9348 /* 9349 * @class ImageOverlay 9350 * @aka L.ImageOverlay 9351 * @inherits Interactive layer 9352 * 9353 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 9354 * 9355 * @example 9356 * 9357 * ```js 9358 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 9359 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 9360 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 9361 * ``` 9362 */ 9363 9364 var ImageOverlay = Layer.extend({ 9365 9366 // @section 9367 // @aka ImageOverlay options 9368 options: { 9369 // @option opacity: Number = 1.0 9370 // The opacity of the image overlay. 9371 opacity: 1, 9372 9373 // @option alt: String = '' 9374 // Text for the `alt` attribute of the image (useful for accessibility). 9375 alt: '', 9376 9377 // @option interactive: Boolean = false 9378 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 9379 interactive: false, 9380 9381 // @option crossOrigin: Boolean|String = false 9382 // Whether the crossOrigin attribute will be added to the image. 9383 // 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. 9384 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 9385 crossOrigin: false, 9386 9387 // @option errorOverlayUrl: String = '' 9388 // URL to the overlay image to show in place of the overlay that failed to load. 9389 errorOverlayUrl: '', 9390 9391 // @option zIndex: Number = 1 9392 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 9393 zIndex: 1, 9394 9395 // @option className: String = '' 9396 // A custom class name to assign to the image. Empty by default. 9397 className: '' 9398 }, 9399 9400 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 9401 this._url = url; 9402 this._bounds = toLatLngBounds(bounds); 9403 9404 setOptions(this, options); 9405 }, 9406 9407 onAdd: function () { 9408 if (!this._image) { 9409 this._initImage(); 9410 9411 if (this.options.opacity < 1) { 9412 this._updateOpacity(); 9413 } 9414 } 9415 9416 if (this.options.interactive) { 9417 addClass(this._image, 'leaflet-interactive'); 9418 this.addInteractiveTarget(this._image); 9419 } 9420 9421 this.getPane().appendChild(this._image); 9422 this._reset(); 9423 }, 9424 9425 onRemove: function () { 9426 remove(this._image); 9427 if (this.options.interactive) { 9428 this.removeInteractiveTarget(this._image); 9429 } 9430 }, 9431 9432 // @method setOpacity(opacity: Number): this 9433 // Sets the opacity of the overlay. 9434 setOpacity: function (opacity) { 9435 this.options.opacity = opacity; 9436 9437 if (this._image) { 9438 this._updateOpacity(); 9439 } 9440 return this; 9441 }, 9442 9443 setStyle: function (styleOpts) { 9444 if (styleOpts.opacity) { 9445 this.setOpacity(styleOpts.opacity); 9446 } 9447 return this; 9448 }, 9449 9450 // @method bringToFront(): this 9451 // Brings the layer to the top of all overlays. 9452 bringToFront: function () { 9453 if (this._map) { 9454 toFront(this._image); 9455 } 9456 return this; 9457 }, 9458 9459 // @method bringToBack(): this 9460 // Brings the layer to the bottom of all overlays. 9461 bringToBack: function () { 9462 if (this._map) { 9463 toBack(this._image); 9464 } 9465 return this; 9466 }, 9467 9468 // @method setUrl(url: String): this 9469 // Changes the URL of the image. 9470 setUrl: function (url) { 9471 this._url = url; 9472 9473 if (this._image) { 9474 this._image.src = url; 9475 } 9476 return this; 9477 }, 9478 9479 // @method setBounds(bounds: LatLngBounds): this 9480 // Update the bounds that this ImageOverlay covers 9481 setBounds: function (bounds) { 9482 this._bounds = toLatLngBounds(bounds); 9483 9484 if (this._map) { 9485 this._reset(); 9486 } 9487 return this; 9488 }, 9489 9490 getEvents: function () { 9491 var events = { 9492 zoom: this._reset, 9493 viewreset: this._reset 9494 }; 9495 9496 if (this._zoomAnimated) { 9497 events.zoomanim = this._animateZoom; 9498 } 9499 9500 return events; 9501 }, 9502 9503 // @method setZIndex(value: Number): this 9504 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9505 setZIndex: function (value) { 9506 this.options.zIndex = value; 9507 this._updateZIndex(); 9508 return this; 9509 }, 9510 9511 // @method getBounds(): LatLngBounds 9512 // Get the bounds that this ImageOverlay covers 9513 getBounds: function () { 9514 return this._bounds; 9515 }, 9516 9517 // @method getElement(): HTMLElement 9518 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9519 // used by this overlay. 9520 getElement: function () { 9521 return this._image; 9522 }, 9523 9524 _initImage: function () { 9525 var wasElementSupplied = this._url.tagName === 'IMG'; 9526 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9527 9528 addClass(img, 'leaflet-image-layer'); 9529 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9530 if (this.options.className) { addClass(img, this.options.className); } 9531 9532 img.onselectstart = falseFn; 9533 img.onmousemove = falseFn; 9534 9535 // @event load: Event 9536 // Fired when the ImageOverlay layer has loaded its image 9537 img.onload = bind(this.fire, this, 'load'); 9538 img.onerror = bind(this._overlayOnError, this, 'error'); 9539 9540 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9541 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9542 } 9543 9544 if (this.options.zIndex) { 9545 this._updateZIndex(); 9546 } 9547 9548 if (wasElementSupplied) { 9549 this._url = img.src; 9550 return; 9551 } 9552 9553 img.src = this._url; 9554 img.alt = this.options.alt; 9555 }, 9556 9557 _animateZoom: function (e) { 9558 var scale = this._map.getZoomScale(e.zoom), 9559 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9560 9561 setTransform(this._image, offset, scale); 9562 }, 9563 9564 _reset: function () { 9565 var image = this._image, 9566 bounds = new Bounds( 9567 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9568 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9569 size = bounds.getSize(); 9570 9571 setPosition(image, bounds.min); 9572 9573 image.style.width = size.x + 'px'; 9574 image.style.height = size.y + 'px'; 9575 }, 9576 9577 _updateOpacity: function () { 9578 setOpacity(this._image, this.options.opacity); 9579 }, 9580 9581 _updateZIndex: function () { 9582 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9583 this._image.style.zIndex = this.options.zIndex; 9584 } 9585 }, 9586 9587 _overlayOnError: function () { 9588 // @event error: Event 9589 // Fired when the ImageOverlay layer fails to load its image 9590 this.fire('error'); 9591 9592 var errorUrl = this.options.errorOverlayUrl; 9593 if (errorUrl && this._url !== errorUrl) { 9594 this._url = errorUrl; 9595 this._image.src = errorUrl; 9596 } 9597 }, 9598 9599 // @method getCenter(): LatLng 9600 // Returns the center of the ImageOverlay. 9601 getCenter: function () { 9602 return this._bounds.getCenter(); 9603 } 9604 }); 9605 9606 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9607 // Instantiates an image overlay object given the URL of the image and the 9608 // geographical bounds it is tied to. 9609 var imageOverlay = function (url, bounds, options) { 9610 return new ImageOverlay(url, bounds, options); 9611 }; 9612 9613 /* 9614 * @class VideoOverlay 9615 * @aka L.VideoOverlay 9616 * @inherits ImageOverlay 9617 * 9618 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9619 * 9620 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9621 * HTML5 element. 9622 * 9623 * @example 9624 * 9625 * ```js 9626 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9627 * videoBounds = [[ 32, -130], [ 13, -100]]; 9628 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9629 * ``` 9630 */ 9631 9632 var VideoOverlay = ImageOverlay.extend({ 9633 9634 // @section 9635 // @aka VideoOverlay options 9636 options: { 9637 // @option autoplay: Boolean = true 9638 // Whether the video starts playing automatically when loaded. 9639 // On some browsers autoplay will only work with `muted: true` 9640 autoplay: true, 9641 9642 // @option loop: Boolean = true 9643 // Whether the video will loop back to the beginning when played. 9644 loop: true, 9645 9646 // @option keepAspectRatio: Boolean = true 9647 // Whether the video will save aspect ratio after the projection. 9648 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 9649 keepAspectRatio: true, 9650 9651 // @option muted: Boolean = false 9652 // Whether the video starts on mute when loaded. 9653 muted: false, 9654 9655 // @option playsInline: Boolean = true 9656 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode. 9657 playsInline: true 9658 }, 9659 9660 _initImage: function () { 9661 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9662 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9663 9664 addClass(vid, 'leaflet-image-layer'); 9665 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9666 if (this.options.className) { addClass(vid, this.options.className); } 9667 9668 vid.onselectstart = falseFn; 9669 vid.onmousemove = falseFn; 9670 9671 // @event load: Event 9672 // Fired when the video has finished loading the first frame 9673 vid.onloadeddata = bind(this.fire, this, 'load'); 9674 9675 if (wasElementSupplied) { 9676 var sourceElements = vid.getElementsByTagName('source'); 9677 var sources = []; 9678 for (var j = 0; j < sourceElements.length; j++) { 9679 sources.push(sourceElements[j].src); 9680 } 9681 9682 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9683 return; 9684 } 9685 9686 if (!isArray(this._url)) { this._url = [this._url]; } 9687 9688 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) { 9689 vid.style['objectFit'] = 'fill'; 9690 } 9691 vid.autoplay = !!this.options.autoplay; 9692 vid.loop = !!this.options.loop; 9693 vid.muted = !!this.options.muted; 9694 vid.playsInline = !!this.options.playsInline; 9695 for (var i = 0; i < this._url.length; i++) { 9696 var source = create$1('source'); 9697 source.src = this._url[i]; 9698 vid.appendChild(source); 9699 } 9700 } 9701 9702 // @method getElement(): HTMLVideoElement 9703 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9704 // used by this overlay. 9705 }); 9706 9707 9708 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9709 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9710 // geographical bounds it is tied to. 9711 9712 function videoOverlay(video, bounds, options) { 9713 return new VideoOverlay(video, bounds, options); 9714 } 9715 9716 /* 9717 * @class SVGOverlay 9718 * @aka L.SVGOverlay 9719 * @inherits ImageOverlay 9720 * 9721 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`. 9722 * 9723 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. 9724 * 9725 * @example 9726 * 9727 * ```js 9728 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9729 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); 9730 * svgElement.setAttribute('viewBox', "0 0 200 200"); 9731 * 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"/>'; 9732 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ]; 9733 * L.svgOverlay(svgElement, svgElementBounds).addTo(map); 9734 * ``` 9735 */ 9736 9737 var SVGOverlay = ImageOverlay.extend({ 9738 _initImage: function () { 9739 var el = this._image = this._url; 9740 9741 addClass(el, 'leaflet-image-layer'); 9742 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); } 9743 if (this.options.className) { addClass(el, this.options.className); } 9744 9745 el.onselectstart = falseFn; 9746 el.onmousemove = falseFn; 9747 } 9748 9749 // @method getElement(): SVGElement 9750 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement) 9751 // used by this overlay. 9752 }); 9753 9754 9755 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options) 9756 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to. 9757 // A viewBox attribute is required on the SVG element to zoom in and out properly. 9758 9759 function svgOverlay(el, bounds, options) { 9760 return new SVGOverlay(el, bounds, options); 9761 } 9762 9763 /* 9764 * @class DivOverlay 9765 * @inherits Interactive layer 9766 * @aka L.DivOverlay 9767 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins. 9768 */ 9769 9770 // @namespace DivOverlay 9771 var DivOverlay = Layer.extend({ 9772 9773 // @section 9774 // @aka DivOverlay options 9775 options: { 9776 // @option interactive: Boolean = false 9777 // If true, the popup/tooltip will listen to the mouse events. 9778 interactive: false, 9779 9780 // @option offset: Point = Point(0, 0) 9781 // The offset of the overlay position. 9782 offset: [0, 0], 9783 9784 // @option className: String = '' 9785 // A custom CSS class name to assign to the overlay. 9786 className: '', 9787 9788 // @option pane: String = undefined 9789 // `Map pane` where the overlay will be added. 9790 pane: undefined, 9791 9792 // @option content: String|HTMLElement|Function = '' 9793 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be 9794 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay. 9795 content: '' 9796 }, 9797 9798 initialize: function (options, source) { 9799 if (options && (options instanceof LatLng || isArray(options))) { 9800 this._latlng = toLatLng(options); 9801 setOptions(this, source); 9802 } else { 9803 setOptions(this, options); 9804 this._source = source; 9805 } 9806 if (this.options.content) { 9807 this._content = this.options.content; 9808 } 9809 }, 9810 9811 // @method openOn(map: Map): this 9812 // Adds the overlay to the map. 9813 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`. 9814 openOn: function (map) { 9815 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 9816 if (!map.hasLayer(this)) { 9817 map.addLayer(this); 9818 } 9819 return this; 9820 }, 9821 9822 // @method close(): this 9823 // Closes the overlay. 9824 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)` 9825 // and `layer.closePopup()`/`.closeTooltip()`. 9826 close: function () { 9827 if (this._map) { 9828 this._map.removeLayer(this); 9829 } 9830 return this; 9831 }, 9832 9833 // @method toggle(layer?: Layer): this 9834 // Opens or closes the overlay bound to layer depending on its current state. 9835 // Argument may be omitted only for overlay bound to layer. 9836 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`. 9837 toggle: function (layer) { 9838 if (this._map) { 9839 this.close(); 9840 } else { 9841 if (arguments.length) { 9842 this._source = layer; 9843 } else { 9844 layer = this._source; 9845 } 9846 this._prepareOpen(); 9847 9848 // open the overlay on the map 9849 this.openOn(layer._map); 9850 } 9851 return this; 9852 }, 9853 9854 onAdd: function (map) { 9855 this._zoomAnimated = map._zoomAnimated; 9856 9857 if (!this._container) { 9858 this._initLayout(); 9859 } 9860 9861 if (map._fadeAnimated) { 9862 setOpacity(this._container, 0); 9863 } 9864 9865 clearTimeout(this._removeTimeout); 9866 this.getPane().appendChild(this._container); 9867 this.update(); 9868 9869 if (map._fadeAnimated) { 9870 setOpacity(this._container, 1); 9871 } 9872 9873 this.bringToFront(); 9874 9875 if (this.options.interactive) { 9876 addClass(this._container, 'leaflet-interactive'); 9877 this.addInteractiveTarget(this._container); 9878 } 9879 }, 9880 9881 onRemove: function (map) { 9882 if (map._fadeAnimated) { 9883 setOpacity(this._container, 0); 9884 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200); 9885 } else { 9886 remove(this._container); 9887 } 9888 9889 if (this.options.interactive) { 9890 removeClass(this._container, 'leaflet-interactive'); 9891 this.removeInteractiveTarget(this._container); 9892 } 9893 }, 9894 9895 // @namespace DivOverlay 9896 // @method getLatLng: LatLng 9897 // Returns the geographical point of the overlay. 9898 getLatLng: function () { 9899 return this._latlng; 9900 }, 9901 9902 // @method setLatLng(latlng: LatLng): this 9903 // Sets the geographical point where the overlay will open. 9904 setLatLng: function (latlng) { 9905 this._latlng = toLatLng(latlng); 9906 if (this._map) { 9907 this._updatePosition(); 9908 this._adjustPan(); 9909 } 9910 return this; 9911 }, 9912 9913 // @method getContent: String|HTMLElement 9914 // Returns the content of the overlay. 9915 getContent: function () { 9916 return this._content; 9917 }, 9918 9919 // @method setContent(htmlContent: String|HTMLElement|Function): this 9920 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function. 9921 // The function should return a `String` or `HTMLElement` to be used in the overlay. 9922 setContent: function (content) { 9923 this._content = content; 9924 this.update(); 9925 return this; 9926 }, 9927 9928 // @method getElement: String|HTMLElement 9929 // Returns the HTML container of the overlay. 9930 getElement: function () { 9931 return this._container; 9932 }, 9933 9934 // @method update: null 9935 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded. 9936 update: function () { 9937 if (!this._map) { return; } 9938 9939 this._container.style.visibility = 'hidden'; 9940 9941 this._updateContent(); 9942 this._updateLayout(); 9943 this._updatePosition(); 9944 9945 this._container.style.visibility = ''; 9946 9947 this._adjustPan(); 9948 }, 9949 9950 getEvents: function () { 9951 var events = { 9952 zoom: this._updatePosition, 9953 viewreset: this._updatePosition 9954 }; 9955 9956 if (this._zoomAnimated) { 9957 events.zoomanim = this._animateZoom; 9958 } 9959 return events; 9960 }, 9961 9962 // @method isOpen: Boolean 9963 // Returns `true` when the overlay is visible on the map. 9964 isOpen: function () { 9965 return !!this._map && this._map.hasLayer(this); 9966 }, 9967 9968 // @method bringToFront: this 9969 // Brings this overlay in front of other overlays (in the same map pane). 9970 bringToFront: function () { 9971 if (this._map) { 9972 toFront(this._container); 9973 } 9974 return this; 9975 }, 9976 9977 // @method bringToBack: this 9978 // Brings this overlay to the back of other overlays (in the same map pane). 9979 bringToBack: function () { 9980 if (this._map) { 9981 toBack(this._container); 9982 } 9983 return this; 9984 }, 9985 9986 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup) 9987 _prepareOpen: function (latlng) { 9988 var source = this._source; 9989 if (!source._map) { return false; } 9990 9991 if (source instanceof FeatureGroup) { 9992 source = null; 9993 var layers = this._source._layers; 9994 for (var id in layers) { 9995 if (layers[id]._map) { 9996 source = layers[id]; 9997 break; 9998 } 9999 } 10000 if (!source) { return false; } // Unable to get source layer. 10001 10002 // set overlay source to this layer 10003 this._source = source; 10004 } 10005 10006 if (!latlng) { 10007 if (source.getCenter) { 10008 latlng = source.getCenter(); 10009 } else if (source.getLatLng) { 10010 latlng = source.getLatLng(); 10011 } else if (source.getBounds) { 10012 latlng = source.getBounds().getCenter(); 10013 } else { 10014 throw new Error('Unable to get source layer LatLng.'); 10015 } 10016 } 10017 this.setLatLng(latlng); 10018 10019 if (this._map) { 10020 // update the overlay (content, layout, etc...) 10021 this.update(); 10022 } 10023 10024 return true; 10025 }, 10026 10027 _updateContent: function () { 10028 if (!this._content) { return; } 10029 10030 var node = this._contentNode; 10031 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 10032 10033 if (typeof content === 'string') { 10034 node.innerHTML = content; 10035 } else { 10036 while (node.hasChildNodes()) { 10037 node.removeChild(node.firstChild); 10038 } 10039 node.appendChild(content); 10040 } 10041 10042 // @namespace DivOverlay 10043 // @section DivOverlay events 10044 // @event contentupdate: Event 10045 // Fired when the content of the overlay is updated 10046 this.fire('contentupdate'); 10047 }, 10048 10049 _updatePosition: function () { 10050 if (!this._map) { return; } 10051 10052 var pos = this._map.latLngToLayerPoint(this._latlng), 10053 offset = toPoint(this.options.offset), 10054 anchor = this._getAnchor(); 10055 10056 if (this._zoomAnimated) { 10057 setPosition(this._container, pos.add(anchor)); 10058 } else { 10059 offset = offset.add(pos).add(anchor); 10060 } 10061 10062 var bottom = this._containerBottom = -offset.y, 10063 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 10064 10065 // bottom position the overlay in case the height of the overlay changes (images loading etc) 10066 this._container.style.bottom = bottom + 'px'; 10067 this._container.style.left = left + 'px'; 10068 }, 10069 10070 _getAnchor: function () { 10071 return [0, 0]; 10072 } 10073 10074 }); 10075 10076 Map.include({ 10077 _initOverlay: function (OverlayClass, content, latlng, options) { 10078 var overlay = content; 10079 if (!(overlay instanceof OverlayClass)) { 10080 overlay = new OverlayClass(options).setContent(content); 10081 } 10082 if (latlng) { 10083 overlay.setLatLng(latlng); 10084 } 10085 return overlay; 10086 } 10087 }); 10088 10089 10090 Layer.include({ 10091 _initOverlay: function (OverlayClass, old, content, options) { 10092 var overlay = content; 10093 if (overlay instanceof OverlayClass) { 10094 setOptions(overlay, options); 10095 overlay._source = this; 10096 } else { 10097 overlay = (old && !options) ? old : new OverlayClass(options, this); 10098 overlay.setContent(content); 10099 } 10100 return overlay; 10101 } 10102 }); 10103 10104 /* 10105 * @class Popup 10106 * @inherits DivOverlay 10107 * @aka L.Popup 10108 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 10109 * open popups while making sure that only one popup is open at one time 10110 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 10111 * 10112 * @example 10113 * 10114 * If you want to just bind a popup to marker click and then open it, it's really easy: 10115 * 10116 * ```js 10117 * marker.bindPopup(popupContent).openPopup(); 10118 * ``` 10119 * Path overlays like polylines also have a `bindPopup` method. 10120 * 10121 * A popup can be also standalone: 10122 * 10123 * ```js 10124 * var popup = L.popup() 10125 * .setLatLng(latlng) 10126 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 10127 * .openOn(map); 10128 * ``` 10129 * or 10130 * ```js 10131 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>') 10132 * .openOn(map); 10133 * ``` 10134 */ 10135 10136 10137 // @namespace Popup 10138 var Popup = DivOverlay.extend({ 10139 10140 // @section 10141 // @aka Popup options 10142 options: { 10143 // @option pane: String = 'popupPane' 10144 // `Map pane` where the popup will be added. 10145 pane: 'popupPane', 10146 10147 // @option offset: Point = Point(0, 7) 10148 // The offset of the popup position. 10149 offset: [0, 7], 10150 10151 // @option maxWidth: Number = 300 10152 // Max width of the popup, in pixels. 10153 maxWidth: 300, 10154 10155 // @option minWidth: Number = 50 10156 // Min width of the popup, in pixels. 10157 minWidth: 50, 10158 10159 // @option maxHeight: Number = null 10160 // If set, creates a scrollable container of the given height 10161 // inside a popup if its content exceeds it. 10162 // The scrollable container can be styled using the 10163 // `leaflet-popup-scrolled` CSS class selector. 10164 maxHeight: null, 10165 10166 // @option autoPan: Boolean = true 10167 // Set it to `false` if you don't want the map to do panning animation 10168 // to fit the opened popup. 10169 autoPan: true, 10170 10171 // @option autoPanPaddingTopLeft: Point = null 10172 // The margin between the popup and the top left corner of the map 10173 // view after autopanning was performed. 10174 autoPanPaddingTopLeft: null, 10175 10176 // @option autoPanPaddingBottomRight: Point = null 10177 // The margin between the popup and the bottom right corner of the map 10178 // view after autopanning was performed. 10179 autoPanPaddingBottomRight: null, 10180 10181 // @option autoPanPadding: Point = Point(5, 5) 10182 // Equivalent of setting both top left and bottom right autopan padding to the same value. 10183 autoPanPadding: [5, 5], 10184 10185 // @option keepInView: Boolean = false 10186 // Set it to `true` if you want to prevent users from panning the popup 10187 // off of the screen while it is open. 10188 keepInView: false, 10189 10190 // @option closeButton: Boolean = true 10191 // Controls the presence of a close button in the popup. 10192 closeButton: true, 10193 10194 // @option autoClose: Boolean = true 10195 // Set it to `false` if you want to override the default behavior of 10196 // the popup closing when another popup is opened. 10197 autoClose: true, 10198 10199 // @option closeOnEscapeKey: Boolean = true 10200 // Set it to `false` if you want to override the default behavior of 10201 // the ESC key for closing of the popup. 10202 closeOnEscapeKey: true, 10203 10204 // @option closeOnClick: Boolean = * 10205 // Set it if you want to override the default behavior of the popup closing when user clicks 10206 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 10207 10208 // @option className: String = '' 10209 // A custom CSS class name to assign to the popup. 10210 className: '' 10211 }, 10212 10213 // @namespace Popup 10214 // @method openOn(map: Map): this 10215 // Alternative to `map.openPopup(popup)`. 10216 // Adds the popup to the map and closes the previous one. 10217 openOn: function (map) { 10218 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 10219 10220 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) { 10221 map.removeLayer(map._popup); 10222 } 10223 map._popup = this; 10224 10225 return DivOverlay.prototype.openOn.call(this, map); 10226 }, 10227 10228 onAdd: function (map) { 10229 DivOverlay.prototype.onAdd.call(this, map); 10230 10231 // @namespace Map 10232 // @section Popup events 10233 // @event popupopen: PopupEvent 10234 // Fired when a popup is opened in the map 10235 map.fire('popupopen', {popup: this}); 10236 10237 if (this._source) { 10238 // @namespace Layer 10239 // @section Popup events 10240 // @event popupopen: PopupEvent 10241 // Fired when a popup bound to this layer is opened 10242 this._source.fire('popupopen', {popup: this}, true); 10243 // For non-path layers, we toggle the popup when clicking 10244 // again the layer, so prevent the map to reopen it. 10245 if (!(this._source instanceof Path)) { 10246 this._source.on('preclick', stopPropagation); 10247 } 10248 } 10249 }, 10250 10251 onRemove: function (map) { 10252 DivOverlay.prototype.onRemove.call(this, map); 10253 10254 // @namespace Map 10255 // @section Popup events 10256 // @event popupclose: PopupEvent 10257 // Fired when a popup in the map is closed 10258 map.fire('popupclose', {popup: this}); 10259 10260 if (this._source) { 10261 // @namespace Layer 10262 // @section Popup events 10263 // @event popupclose: PopupEvent 10264 // Fired when a popup bound to this layer is closed 10265 this._source.fire('popupclose', {popup: this}, true); 10266 if (!(this._source instanceof Path)) { 10267 this._source.off('preclick', stopPropagation); 10268 } 10269 } 10270 }, 10271 10272 getEvents: function () { 10273 var events = DivOverlay.prototype.getEvents.call(this); 10274 10275 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 10276 events.preclick = this.close; 10277 } 10278 10279 if (this.options.keepInView) { 10280 events.moveend = this._adjustPan; 10281 } 10282 10283 return events; 10284 }, 10285 10286 _initLayout: function () { 10287 var prefix = 'leaflet-popup', 10288 container = this._container = create$1('div', 10289 prefix + ' ' + (this.options.className || '') + 10290 ' leaflet-zoom-animated'); 10291 10292 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container); 10293 this._contentNode = create$1('div', prefix + '-content', wrapper); 10294 10295 disableClickPropagation(container); 10296 disableScrollPropagation(this._contentNode); 10297 on(container, 'contextmenu', stopPropagation); 10298 10299 this._tipContainer = create$1('div', prefix + '-tip-container', container); 10300 this._tip = create$1('div', prefix + '-tip', this._tipContainer); 10301 10302 if (this.options.closeButton) { 10303 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container); 10304 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399 10305 closeButton.setAttribute('aria-label', 'Close popup'); 10306 closeButton.href = '#close'; 10307 closeButton.innerHTML = '<span aria-hidden="true">×</span>'; 10308 10309 on(closeButton, 'click', function (ev) { 10310 preventDefault(ev); 10311 this.close(); 10312 }, this); 10313 } 10314 }, 10315 10316 _updateLayout: function () { 10317 var container = this._contentNode, 10318 style = container.style; 10319 10320 style.width = ''; 10321 style.whiteSpace = 'nowrap'; 10322 10323 var width = container.offsetWidth; 10324 width = Math.min(width, this.options.maxWidth); 10325 width = Math.max(width, this.options.minWidth); 10326 10327 style.width = (width + 1) + 'px'; 10328 style.whiteSpace = ''; 10329 10330 style.height = ''; 10331 10332 var height = container.offsetHeight, 10333 maxHeight = this.options.maxHeight, 10334 scrolledClass = 'leaflet-popup-scrolled'; 10335 10336 if (maxHeight && height > maxHeight) { 10337 style.height = maxHeight + 'px'; 10338 addClass(container, scrolledClass); 10339 } else { 10340 removeClass(container, scrolledClass); 10341 } 10342 10343 this._containerWidth = this._container.offsetWidth; 10344 }, 10345 10346 _animateZoom: function (e) { 10347 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 10348 anchor = this._getAnchor(); 10349 setPosition(this._container, pos.add(anchor)); 10350 }, 10351 10352 _adjustPan: function () { 10353 if (!this.options.autoPan) { return; } 10354 if (this._map._panAnim) { this._map._panAnim.stop(); } 10355 10356 // We can endlessly recurse if keepInView is set and the view resets. 10357 // Let's guard against that by exiting early if we're responding to our own autopan. 10358 if (this._autopanning) { 10359 this._autopanning = false; 10360 return; 10361 } 10362 10363 var map = this._map, 10364 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0, 10365 containerHeight = this._container.offsetHeight + marginBottom, 10366 containerWidth = this._containerWidth, 10367 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 10368 10369 layerPos._add(getPosition(this._container)); 10370 10371 var containerPos = map.layerPointToContainerPoint(layerPos), 10372 padding = toPoint(this.options.autoPanPadding), 10373 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), 10374 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), 10375 size = map.getSize(), 10376 dx = 0, 10377 dy = 0; 10378 10379 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 10380 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 10381 } 10382 if (containerPos.x - dx - paddingTL.x < 0) { // left 10383 dx = containerPos.x - paddingTL.x; 10384 } 10385 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 10386 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 10387 } 10388 if (containerPos.y - dy - paddingTL.y < 0) { // top 10389 dy = containerPos.y - paddingTL.y; 10390 } 10391 10392 // @namespace Map 10393 // @section Popup events 10394 // @event autopanstart: Event 10395 // Fired when the map starts autopanning when opening a popup. 10396 if (dx || dy) { 10397 // Track that we're autopanning, as this function will be re-ran on moveend 10398 if (this.options.keepInView) { 10399 this._autopanning = true; 10400 } 10401 10402 map 10403 .fire('autopanstart') 10404 .panBy([dx, dy]); 10405 } 10406 }, 10407 10408 _getAnchor: function () { 10409 // Where should we anchor the popup on the source layer? 10410 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 10411 } 10412 10413 }); 10414 10415 // @namespace Popup 10416 // @factory L.popup(options?: Popup options, source?: Layer) 10417 // 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. 10418 // @alternative 10419 // @factory L.popup(latlng: LatLng, options?: Popup options) 10420 // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location. 10421 var popup = function (options, source) { 10422 return new Popup(options, source); 10423 }; 10424 10425 10426 /* @namespace Map 10427 * @section Interaction Options 10428 * @option closePopupOnClick: Boolean = true 10429 * Set it to `false` if you don't want popups to close when user clicks the map. 10430 */ 10431 Map.mergeOptions({ 10432 closePopupOnClick: true 10433 }); 10434 10435 10436 // @namespace Map 10437 // @section Methods for Layers and Controls 10438 Map.include({ 10439 // @method openPopup(popup: Popup): this 10440 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 10441 // @alternative 10442 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 10443 // Creates a popup with the specified content and options and opens it in the given point on a map. 10444 openPopup: function (popup, latlng, options) { 10445 this._initOverlay(Popup, popup, latlng, options) 10446 .openOn(this); 10447 10448 return this; 10449 }, 10450 10451 // @method closePopup(popup?: Popup): this 10452 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 10453 closePopup: function (popup) { 10454 popup = arguments.length ? popup : this._popup; 10455 if (popup) { 10456 popup.close(); 10457 } 10458 return this; 10459 } 10460 }); 10461 10462 /* 10463 * @namespace Layer 10464 * @section Popup methods example 10465 * 10466 * All layers share a set of methods convenient for binding popups to it. 10467 * 10468 * ```js 10469 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 10470 * layer.openPopup(); 10471 * layer.closePopup(); 10472 * ``` 10473 * 10474 * 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. 10475 */ 10476 10477 // @section Popup methods 10478 Layer.include({ 10479 10480 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 10481 // Binds a popup to the layer with the passed `content` and sets up the 10482 // necessary event listeners. If a `Function` is passed it will receive 10483 // the layer as the first argument and should return a `String` or `HTMLElement`. 10484 bindPopup: function (content, options) { 10485 this._popup = this._initOverlay(Popup, this._popup, content, options); 10486 if (!this._popupHandlersAdded) { 10487 this.on({ 10488 click: this._openPopup, 10489 keypress: this._onKeyPress, 10490 remove: this.closePopup, 10491 move: this._movePopup 10492 }); 10493 this._popupHandlersAdded = true; 10494 } 10495 10496 return this; 10497 }, 10498 10499 // @method unbindPopup(): this 10500 // Removes the popup previously bound with `bindPopup`. 10501 unbindPopup: function () { 10502 if (this._popup) { 10503 this.off({ 10504 click: this._openPopup, 10505 keypress: this._onKeyPress, 10506 remove: this.closePopup, 10507 move: this._movePopup 10508 }); 10509 this._popupHandlersAdded = false; 10510 this._popup = null; 10511 } 10512 return this; 10513 }, 10514 10515 // @method openPopup(latlng?: LatLng): this 10516 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 10517 openPopup: function (latlng) { 10518 if (this._popup) { 10519 if (!(this instanceof FeatureGroup)) { 10520 this._popup._source = this; 10521 } 10522 if (this._popup._prepareOpen(latlng || this._latlng)) { 10523 // open the popup on the map 10524 this._popup.openOn(this._map); 10525 } 10526 } 10527 return this; 10528 }, 10529 10530 // @method closePopup(): this 10531 // Closes the popup bound to this layer if it is open. 10532 closePopup: function () { 10533 if (this._popup) { 10534 this._popup.close(); 10535 } 10536 return this; 10537 }, 10538 10539 // @method togglePopup(): this 10540 // Opens or closes the popup bound to this layer depending on its current state. 10541 togglePopup: function () { 10542 if (this._popup) { 10543 this._popup.toggle(this); 10544 } 10545 return this; 10546 }, 10547 10548 // @method isPopupOpen(): boolean 10549 // Returns `true` if the popup bound to this layer is currently open. 10550 isPopupOpen: function () { 10551 return (this._popup ? this._popup.isOpen() : false); 10552 }, 10553 10554 // @method setPopupContent(content: String|HTMLElement|Popup): this 10555 // Sets the content of the popup bound to this layer. 10556 setPopupContent: function (content) { 10557 if (this._popup) { 10558 this._popup.setContent(content); 10559 } 10560 return this; 10561 }, 10562 10563 // @method getPopup(): Popup 10564 // Returns the popup bound to this layer. 10565 getPopup: function () { 10566 return this._popup; 10567 }, 10568 10569 _openPopup: function (e) { 10570 if (!this._popup || !this._map) { 10571 return; 10572 } 10573 // prevent map click 10574 stop(e); 10575 10576 var target = e.layer || e.target; 10577 if (this._popup._source === target && !(target instanceof Path)) { 10578 // treat it like a marker and figure out 10579 // if we should toggle it open/closed 10580 if (this._map.hasLayer(this._popup)) { 10581 this.closePopup(); 10582 } else { 10583 this.openPopup(e.latlng); 10584 } 10585 return; 10586 } 10587 this._popup._source = target; 10588 this.openPopup(e.latlng); 10589 }, 10590 10591 _movePopup: function (e) { 10592 this._popup.setLatLng(e.latlng); 10593 }, 10594 10595 _onKeyPress: function (e) { 10596 if (e.originalEvent.keyCode === 13) { 10597 this._openPopup(e); 10598 } 10599 } 10600 }); 10601 10602 /* 10603 * @class Tooltip 10604 * @inherits DivOverlay 10605 * @aka L.Tooltip 10606 * Used to display small texts on top of map layers. 10607 * 10608 * @example 10609 * If you want to just bind a tooltip to marker: 10610 * 10611 * ```js 10612 * marker.bindTooltip("my tooltip text").openTooltip(); 10613 * ``` 10614 * Path overlays like polylines also have a `bindTooltip` method. 10615 * 10616 * A tooltip can be also standalone: 10617 * 10618 * ```js 10619 * var tooltip = L.tooltip() 10620 * .setLatLng(latlng) 10621 * .setContent('Hello world!<br />This is a nice tooltip.') 10622 * .addTo(map); 10623 * ``` 10624 * or 10625 * ```js 10626 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'}) 10627 * .addTo(map); 10628 * ``` 10629 * 10630 * 10631 * Note about tooltip offset. Leaflet takes two options in consideration 10632 * for computing tooltip offsetting: 10633 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 10634 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 10635 * move it to the bottom. Negatives will move to the left and top. 10636 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 10637 * should adapt this value if you use a custom icon. 10638 */ 10639 10640 10641 // @namespace Tooltip 10642 var Tooltip = DivOverlay.extend({ 10643 10644 // @section 10645 // @aka Tooltip options 10646 options: { 10647 // @option pane: String = 'tooltipPane' 10648 // `Map pane` where the tooltip will be added. 10649 pane: 'tooltipPane', 10650 10651 // @option offset: Point = Point(0, 0) 10652 // Optional offset of the tooltip position. 10653 offset: [0, 0], 10654 10655 // @option direction: String = 'auto' 10656 // Direction where to open the tooltip. Possible values are: `right`, `left`, 10657 // `top`, `bottom`, `center`, `auto`. 10658 // `auto` will dynamically switch between `right` and `left` according to the tooltip 10659 // position on the map. 10660 direction: 'auto', 10661 10662 // @option permanent: Boolean = false 10663 // Whether to open the tooltip permanently or only on mouseover. 10664 permanent: false, 10665 10666 // @option sticky: Boolean = false 10667 // If true, the tooltip will follow the mouse instead of being fixed at the feature center. 10668 sticky: false, 10669 10670 // @option opacity: Number = 0.9 10671 // Tooltip container opacity. 10672 opacity: 0.9 10673 }, 10674 10675 onAdd: function (map) { 10676 DivOverlay.prototype.onAdd.call(this, map); 10677 this.setOpacity(this.options.opacity); 10678 10679 // @namespace Map 10680 // @section Tooltip events 10681 // @event tooltipopen: TooltipEvent 10682 // Fired when a tooltip is opened in the map. 10683 map.fire('tooltipopen', {tooltip: this}); 10684 10685 if (this._source) { 10686 this.addEventParent(this._source); 10687 10688 // @namespace Layer 10689 // @section Tooltip events 10690 // @event tooltipopen: TooltipEvent 10691 // Fired when a tooltip bound to this layer is opened. 10692 this._source.fire('tooltipopen', {tooltip: this}, true); 10693 } 10694 }, 10695 10696 onRemove: function (map) { 10697 DivOverlay.prototype.onRemove.call(this, map); 10698 10699 // @namespace Map 10700 // @section Tooltip events 10701 // @event tooltipclose: TooltipEvent 10702 // Fired when a tooltip in the map is closed. 10703 map.fire('tooltipclose', {tooltip: this}); 10704 10705 if (this._source) { 10706 this.removeEventParent(this._source); 10707 10708 // @namespace Layer 10709 // @section Tooltip events 10710 // @event tooltipclose: TooltipEvent 10711 // Fired when a tooltip bound to this layer is closed. 10712 this._source.fire('tooltipclose', {tooltip: this}, true); 10713 } 10714 }, 10715 10716 getEvents: function () { 10717 var events = DivOverlay.prototype.getEvents.call(this); 10718 10719 if (!this.options.permanent) { 10720 events.preclick = this.close; 10721 } 10722 10723 return events; 10724 }, 10725 10726 _initLayout: function () { 10727 var prefix = 'leaflet-tooltip', 10728 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 10729 10730 this._contentNode = this._container = create$1('div', className); 10731 10732 this._container.setAttribute('role', 'tooltip'); 10733 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this)); 10734 }, 10735 10736 _updateLayout: function () {}, 10737 10738 _adjustPan: function () {}, 10739 10740 _setPosition: function (pos) { 10741 var subX, subY, 10742 map = this._map, 10743 container = this._container, 10744 centerPoint = map.latLngToContainerPoint(map.getCenter()), 10745 tooltipPoint = map.layerPointToContainerPoint(pos), 10746 direction = this.options.direction, 10747 tooltipWidth = container.offsetWidth, 10748 tooltipHeight = container.offsetHeight, 10749 offset = toPoint(this.options.offset), 10750 anchor = this._getAnchor(); 10751 10752 if (direction === 'top') { 10753 subX = tooltipWidth / 2; 10754 subY = tooltipHeight; 10755 } else if (direction === 'bottom') { 10756 subX = tooltipWidth / 2; 10757 subY = 0; 10758 } else if (direction === 'center') { 10759 subX = tooltipWidth / 2; 10760 subY = tooltipHeight / 2; 10761 } else if (direction === 'right') { 10762 subX = 0; 10763 subY = tooltipHeight / 2; 10764 } else if (direction === 'left') { 10765 subX = tooltipWidth; 10766 subY = tooltipHeight / 2; 10767 } else if (tooltipPoint.x < centerPoint.x) { 10768 direction = 'right'; 10769 subX = 0; 10770 subY = tooltipHeight / 2; 10771 } else { 10772 direction = 'left'; 10773 subX = tooltipWidth + (offset.x + anchor.x) * 2; 10774 subY = tooltipHeight / 2; 10775 } 10776 10777 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor); 10778 10779 removeClass(container, 'leaflet-tooltip-right'); 10780 removeClass(container, 'leaflet-tooltip-left'); 10781 removeClass(container, 'leaflet-tooltip-top'); 10782 removeClass(container, 'leaflet-tooltip-bottom'); 10783 addClass(container, 'leaflet-tooltip-' + direction); 10784 setPosition(container, pos); 10785 }, 10786 10787 _updatePosition: function () { 10788 var pos = this._map.latLngToLayerPoint(this._latlng); 10789 this._setPosition(pos); 10790 }, 10791 10792 setOpacity: function (opacity) { 10793 this.options.opacity = opacity; 10794 10795 if (this._container) { 10796 setOpacity(this._container, opacity); 10797 } 10798 }, 10799 10800 _animateZoom: function (e) { 10801 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 10802 this._setPosition(pos); 10803 }, 10804 10805 _getAnchor: function () { 10806 // Where should we anchor the tooltip on the source layer? 10807 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 10808 } 10809 10810 }); 10811 10812 // @namespace Tooltip 10813 // @factory L.tooltip(options?: Tooltip options, source?: Layer) 10814 // 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. 10815 // @alternative 10816 // @factory L.tooltip(latlng: LatLng, options?: Tooltip options) 10817 // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location. 10818 var tooltip = function (options, source) { 10819 return new Tooltip(options, source); 10820 }; 10821 10822 // @namespace Map 10823 // @section Methods for Layers and Controls 10824 Map.include({ 10825 10826 // @method openTooltip(tooltip: Tooltip): this 10827 // Opens the specified tooltip. 10828 // @alternative 10829 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 10830 // Creates a tooltip with the specified content and options and open it. 10831 openTooltip: function (tooltip, latlng, options) { 10832 this._initOverlay(Tooltip, tooltip, latlng, options) 10833 .openOn(this); 10834 10835 return this; 10836 }, 10837 10838 // @method closeTooltip(tooltip: Tooltip): this 10839 // Closes the tooltip given as parameter. 10840 closeTooltip: function (tooltip) { 10841 tooltip.close(); 10842 return this; 10843 } 10844 10845 }); 10846 10847 /* 10848 * @namespace Layer 10849 * @section Tooltip methods example 10850 * 10851 * All layers share a set of methods convenient for binding tooltips to it. 10852 * 10853 * ```js 10854 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 10855 * layer.openTooltip(); 10856 * layer.closeTooltip(); 10857 * ``` 10858 */ 10859 10860 // @section Tooltip methods 10861 Layer.include({ 10862 10863 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 10864 // Binds a tooltip to the layer with the passed `content` and sets up the 10865 // necessary event listeners. If a `Function` is passed it will receive 10866 // the layer as the first argument and should return a `String` or `HTMLElement`. 10867 bindTooltip: function (content, options) { 10868 10869 if (this._tooltip && this.isTooltipOpen()) { 10870 this.unbindTooltip(); 10871 } 10872 10873 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options); 10874 this._initTooltipInteractions(); 10875 10876 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 10877 this.openTooltip(); 10878 } 10879 10880 return this; 10881 }, 10882 10883 // @method unbindTooltip(): this 10884 // Removes the tooltip previously bound with `bindTooltip`. 10885 unbindTooltip: function () { 10886 if (this._tooltip) { 10887 this._initTooltipInteractions(true); 10888 this.closeTooltip(); 10889 this._tooltip = null; 10890 } 10891 return this; 10892 }, 10893 10894 _initTooltipInteractions: function (remove) { 10895 if (!remove && this._tooltipHandlersAdded) { return; } 10896 var onOff = remove ? 'off' : 'on', 10897 events = { 10898 remove: this.closeTooltip, 10899 move: this._moveTooltip 10900 }; 10901 if (!this._tooltip.options.permanent) { 10902 events.mouseover = this._openTooltip; 10903 events.mouseout = this.closeTooltip; 10904 events.click = this._openTooltip; 10905 if (this._map) { 10906 this._addFocusListeners(); 10907 } else { 10908 events.add = this._addFocusListeners; 10909 } 10910 } else { 10911 events.add = this._openTooltip; 10912 } 10913 if (this._tooltip.options.sticky) { 10914 events.mousemove = this._moveTooltip; 10915 } 10916 this[onOff](events); 10917 this._tooltipHandlersAdded = !remove; 10918 }, 10919 10920 // @method openTooltip(latlng?: LatLng): this 10921 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 10922 openTooltip: function (latlng) { 10923 if (this._tooltip) { 10924 if (!(this instanceof FeatureGroup)) { 10925 this._tooltip._source = this; 10926 } 10927 if (this._tooltip._prepareOpen(latlng)) { 10928 // open the tooltip on the map 10929 this._tooltip.openOn(this._map); 10930 10931 if (this.getElement) { 10932 this._setAriaDescribedByOnLayer(this); 10933 } else if (this.eachLayer) { 10934 this.eachLayer(this._setAriaDescribedByOnLayer, this); 10935 } 10936 } 10937 } 10938 return this; 10939 }, 10940 10941 // @method closeTooltip(): this 10942 // Closes the tooltip bound to this layer if it is open. 10943 closeTooltip: function () { 10944 if (this._tooltip) { 10945 return this._tooltip.close(); 10946 } 10947 }, 10948 10949 // @method toggleTooltip(): this 10950 // Opens or closes the tooltip bound to this layer depending on its current state. 10951 toggleTooltip: function () { 10952 if (this._tooltip) { 10953 this._tooltip.toggle(this); 10954 } 10955 return this; 10956 }, 10957 10958 // @method isTooltipOpen(): boolean 10959 // Returns `true` if the tooltip bound to this layer is currently open. 10960 isTooltipOpen: function () { 10961 return this._tooltip.isOpen(); 10962 }, 10963 10964 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this 10965 // Sets the content of the tooltip bound to this layer. 10966 setTooltipContent: function (content) { 10967 if (this._tooltip) { 10968 this._tooltip.setContent(content); 10969 } 10970 return this; 10971 }, 10972 10973 // @method getTooltip(): Tooltip 10974 // Returns the tooltip bound to this layer. 10975 getTooltip: function () { 10976 return this._tooltip; 10977 }, 10978 10979 _addFocusListeners: function () { 10980 if (this.getElement) { 10981 this._addFocusListenersOnLayer(this); 10982 } else if (this.eachLayer) { 10983 this.eachLayer(this._addFocusListenersOnLayer, this); 10984 } 10985 }, 10986 10987 _addFocusListenersOnLayer: function (layer) { 10988 var el = typeof layer.getElement === 'function' && layer.getElement(); 10989 if (el) { 10990 on(el, 'focus', function () { 10991 this._tooltip._source = layer; 10992 this.openTooltip(); 10993 }, this); 10994 on(el, 'blur', this.closeTooltip, this); 10995 } 10996 }, 10997 10998 _setAriaDescribedByOnLayer: function (layer) { 10999 var el = typeof layer.getElement === 'function' && layer.getElement(); 11000 if (el) { 11001 el.setAttribute('aria-describedby', this._tooltip._container.id); 11002 } 11003 }, 11004 11005 11006 _openTooltip: function (e) { 11007 if (!this._tooltip || !this._map) { 11008 return; 11009 } 11010 11011 // If the map is moving, we will show the tooltip after it's done. 11012 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) { 11013 this._openOnceFlag = true; 11014 var that = this; 11015 this._map.once('moveend', function () { 11016 that._openOnceFlag = false; 11017 that._openTooltip(e); 11018 }); 11019 return; 11020 } 11021 11022 this._tooltip._source = e.layer || e.target; 11023 11024 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined); 11025 }, 11026 11027 _moveTooltip: function (e) { 11028 var latlng = e.latlng, containerPoint, layerPoint; 11029 if (this._tooltip.options.sticky && e.originalEvent) { 11030 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 11031 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 11032 latlng = this._map.layerPointToLatLng(layerPoint); 11033 } 11034 this._tooltip.setLatLng(latlng); 11035 } 11036 }); 11037 11038 /* 11039 * @class DivIcon 11040 * @aka L.DivIcon 11041 * @inherits Icon 11042 * 11043 * Represents a lightweight icon for markers that uses a simple `<div>` 11044 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 11045 * 11046 * @example 11047 * ```js 11048 * var myIcon = L.divIcon({className: 'my-div-icon'}); 11049 * // you can set .my-div-icon styles in CSS 11050 * 11051 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 11052 * ``` 11053 * 11054 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 11055 */ 11056 11057 var DivIcon = Icon.extend({ 11058 options: { 11059 // @section 11060 // @aka DivIcon options 11061 iconSize: [12, 12], // also can be set through CSS 11062 11063 // iconAnchor: (Point), 11064 // popupAnchor: (Point), 11065 11066 // @option html: String|HTMLElement = '' 11067 // Custom HTML code to put inside the div element, empty by default. Alternatively, 11068 // an instance of `HTMLElement`. 11069 html: false, 11070 11071 // @option bgPos: Point = [0, 0] 11072 // Optional relative position of the background, in pixels 11073 bgPos: null, 11074 11075 className: 'leaflet-div-icon' 11076 }, 11077 11078 createIcon: function (oldIcon) { 11079 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 11080 options = this.options; 11081 11082 if (options.html instanceof Element) { 11083 empty(div); 11084 div.appendChild(options.html); 11085 } else { 11086 div.innerHTML = options.html !== false ? options.html : ''; 11087 } 11088 11089 if (options.bgPos) { 11090 var bgPos = toPoint(options.bgPos); 11091 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 11092 } 11093 this._setIconStyles(div, 'icon'); 11094 11095 return div; 11096 }, 11097 11098 createShadow: function () { 11099 return null; 11100 } 11101 }); 11102 11103 // @factory L.divIcon(options: DivIcon options) 11104 // Creates a `DivIcon` instance with the given options. 11105 function divIcon(options) { 11106 return new DivIcon(options); 11107 } 11108 11109 Icon.Default = IconDefault; 11110 11111 /* 11112 * @class GridLayer 11113 * @inherits Layer 11114 * @aka L.GridLayer 11115 * 11116 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 11117 * 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. 11118 * 11119 * 11120 * @section Synchronous usage 11121 * @example 11122 * 11123 * 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. 11124 * 11125 * ```js 11126 * var CanvasLayer = L.GridLayer.extend({ 11127 * createTile: function(coords){ 11128 * // create a <canvas> element for drawing 11129 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 11130 * 11131 * // setup tile width and height according to the options 11132 * var size = this.getTileSize(); 11133 * tile.width = size.x; 11134 * tile.height = size.y; 11135 * 11136 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 11137 * var ctx = tile.getContext('2d'); 11138 * 11139 * // return the tile so it can be rendered on screen 11140 * return tile; 11141 * } 11142 * }); 11143 * ``` 11144 * 11145 * @section Asynchronous usage 11146 * @example 11147 * 11148 * 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. 11149 * 11150 * ```js 11151 * var CanvasLayer = L.GridLayer.extend({ 11152 * createTile: function(coords, done){ 11153 * var error; 11154 * 11155 * // create a <canvas> element for drawing 11156 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 11157 * 11158 * // setup tile width and height according to the options 11159 * var size = this.getTileSize(); 11160 * tile.width = size.x; 11161 * tile.height = size.y; 11162 * 11163 * // draw something asynchronously and pass the tile to the done() callback 11164 * setTimeout(function() { 11165 * done(error, tile); 11166 * }, 1000); 11167 * 11168 * return tile; 11169 * } 11170 * }); 11171 * ``` 11172 * 11173 * @section 11174 */ 11175 11176 11177 var GridLayer = Layer.extend({ 11178 11179 // @section 11180 // @aka GridLayer options 11181 options: { 11182 // @option tileSize: Number|Point = 256 11183 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 11184 tileSize: 256, 11185 11186 // @option opacity: Number = 1.0 11187 // Opacity of the tiles. Can be used in the `createTile()` function. 11188 opacity: 1, 11189 11190 // @option updateWhenIdle: Boolean = (depends) 11191 // Load new tiles only when panning ends. 11192 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 11193 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 11194 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 11195 updateWhenIdle: Browser.mobile, 11196 11197 // @option updateWhenZooming: Boolean = true 11198 // 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. 11199 updateWhenZooming: true, 11200 11201 // @option updateInterval: Number = 200 11202 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 11203 updateInterval: 200, 11204 11205 // @option zIndex: Number = 1 11206 // The explicit zIndex of the tile layer. 11207 zIndex: 1, 11208 11209 // @option bounds: LatLngBounds = undefined 11210 // If set, tiles will only be loaded inside the set `LatLngBounds`. 11211 bounds: null, 11212 11213 // @option minZoom: Number = 0 11214 // The minimum zoom level down to which this layer will be displayed (inclusive). 11215 minZoom: 0, 11216 11217 // @option maxZoom: Number = undefined 11218 // The maximum zoom level up to which this layer will be displayed (inclusive). 11219 maxZoom: undefined, 11220 11221 // @option maxNativeZoom: Number = undefined 11222 // Maximum zoom number the tile source has available. If it is specified, 11223 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 11224 // from `maxNativeZoom` level and auto-scaled. 11225 maxNativeZoom: undefined, 11226 11227 // @option minNativeZoom: Number = undefined 11228 // Minimum zoom number the tile source has available. If it is specified, 11229 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 11230 // from `minNativeZoom` level and auto-scaled. 11231 minNativeZoom: undefined, 11232 11233 // @option noWrap: Boolean = false 11234 // Whether the layer is wrapped around the antimeridian. If `true`, the 11235 // GridLayer will only be displayed once at low zoom levels. Has no 11236 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 11237 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 11238 // tiles outside the CRS limits. 11239 noWrap: false, 11240 11241 // @option pane: String = 'tilePane' 11242 // `Map pane` where the grid layer will be added. 11243 pane: 'tilePane', 11244 11245 // @option className: String = '' 11246 // A custom class name to assign to the tile layer. Empty by default. 11247 className: '', 11248 11249 // @option keepBuffer: Number = 2 11250 // When panning the map, keep this many rows and columns of tiles before unloading them. 11251 keepBuffer: 2 11252 }, 11253 11254 initialize: function (options) { 11255 setOptions(this, options); 11256 }, 11257 11258 onAdd: function () { 11259 this._initContainer(); 11260 11261 this._levels = {}; 11262 this._tiles = {}; 11263 11264 this._resetView(); // implicit _update() call 11265 }, 11266 11267 beforeAdd: function (map) { 11268 map._addZoomLimit(this); 11269 }, 11270 11271 onRemove: function (map) { 11272 this._removeAllTiles(); 11273 remove(this._container); 11274 map._removeZoomLimit(this); 11275 this._container = null; 11276 this._tileZoom = undefined; 11277 }, 11278 11279 // @method bringToFront: this 11280 // Brings the tile layer to the top of all tile layers. 11281 bringToFront: function () { 11282 if (this._map) { 11283 toFront(this._container); 11284 this._setAutoZIndex(Math.max); 11285 } 11286 return this; 11287 }, 11288 11289 // @method bringToBack: this 11290 // Brings the tile layer to the bottom of all tile layers. 11291 bringToBack: function () { 11292 if (this._map) { 11293 toBack(this._container); 11294 this._setAutoZIndex(Math.min); 11295 } 11296 return this; 11297 }, 11298 11299 // @method getContainer: HTMLElement 11300 // Returns the HTML element that contains the tiles for this layer. 11301 getContainer: function () { 11302 return this._container; 11303 }, 11304 11305 // @method setOpacity(opacity: Number): this 11306 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 11307 setOpacity: function (opacity) { 11308 this.options.opacity = opacity; 11309 this._updateOpacity(); 11310 return this; 11311 }, 11312 11313 // @method setZIndex(zIndex: Number): this 11314 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 11315 setZIndex: function (zIndex) { 11316 this.options.zIndex = zIndex; 11317 this._updateZIndex(); 11318 11319 return this; 11320 }, 11321 11322 // @method isLoading: Boolean 11323 // Returns `true` if any tile in the grid layer has not finished loading. 11324 isLoading: function () { 11325 return this._loading; 11326 }, 11327 11328 // @method redraw: this 11329 // Causes the layer to clear all the tiles and request them again. 11330 redraw: function () { 11331 if (this._map) { 11332 this._removeAllTiles(); 11333 var tileZoom = this._clampZoom(this._map.getZoom()); 11334 if (tileZoom !== this._tileZoom) { 11335 this._tileZoom = tileZoom; 11336 this._updateLevels(); 11337 } 11338 this._update(); 11339 } 11340 return this; 11341 }, 11342 11343 getEvents: function () { 11344 var events = { 11345 viewprereset: this._invalidateAll, 11346 viewreset: this._resetView, 11347 zoom: this._resetView, 11348 moveend: this._onMoveEnd 11349 }; 11350 11351 if (!this.options.updateWhenIdle) { 11352 // update tiles on move, but not more often than once per given interval 11353 if (!this._onMove) { 11354 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 11355 } 11356 11357 events.move = this._onMove; 11358 } 11359 11360 if (this._zoomAnimated) { 11361 events.zoomanim = this._animateZoom; 11362 } 11363 11364 return events; 11365 }, 11366 11367 // @section Extension methods 11368 // Layers extending `GridLayer` shall reimplement the following method. 11369 // @method createTile(coords: Object, done?: Function): HTMLElement 11370 // Called only internally, must be overridden by classes extending `GridLayer`. 11371 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 11372 // is specified, it must be called when the tile has finished loading and drawing. 11373 createTile: function () { 11374 return document.createElement('div'); 11375 }, 11376 11377 // @section 11378 // @method getTileSize: Point 11379 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 11380 getTileSize: function () { 11381 var s = this.options.tileSize; 11382 return s instanceof Point ? s : new Point(s, s); 11383 }, 11384 11385 _updateZIndex: function () { 11386 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 11387 this._container.style.zIndex = this.options.zIndex; 11388 } 11389 }, 11390 11391 _setAutoZIndex: function (compare) { 11392 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 11393 11394 var layers = this.getPane().children, 11395 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 11396 11397 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 11398 11399 zIndex = layers[i].style.zIndex; 11400 11401 if (layers[i] !== this._container && zIndex) { 11402 edgeZIndex = compare(edgeZIndex, +zIndex); 11403 } 11404 } 11405 11406 if (isFinite(edgeZIndex)) { 11407 this.options.zIndex = edgeZIndex + compare(-1, 1); 11408 this._updateZIndex(); 11409 } 11410 }, 11411 11412 _updateOpacity: function () { 11413 if (!this._map) { return; } 11414 11415 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 11416 if (Browser.ielt9) { return; } 11417 11418 setOpacity(this._container, this.options.opacity); 11419 11420 var now = +new Date(), 11421 nextFrame = false, 11422 willPrune = false; 11423 11424 for (var key in this._tiles) { 11425 var tile = this._tiles[key]; 11426 if (!tile.current || !tile.loaded) { continue; } 11427 11428 var fade = Math.min(1, (now - tile.loaded) / 200); 11429 11430 setOpacity(tile.el, fade); 11431 if (fade < 1) { 11432 nextFrame = true; 11433 } else { 11434 if (tile.active) { 11435 willPrune = true; 11436 } else { 11437 this._onOpaqueTile(tile); 11438 } 11439 tile.active = true; 11440 } 11441 } 11442 11443 if (willPrune && !this._noPrune) { this._pruneTiles(); } 11444 11445 if (nextFrame) { 11446 cancelAnimFrame(this._fadeFrame); 11447 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11448 } 11449 }, 11450 11451 _onOpaqueTile: falseFn, 11452 11453 _initContainer: function () { 11454 if (this._container) { return; } 11455 11456 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || '')); 11457 this._updateZIndex(); 11458 11459 if (this.options.opacity < 1) { 11460 this._updateOpacity(); 11461 } 11462 11463 this.getPane().appendChild(this._container); 11464 }, 11465 11466 _updateLevels: function () { 11467 11468 var zoom = this._tileZoom, 11469 maxZoom = this.options.maxZoom; 11470 11471 if (zoom === undefined) { return undefined; } 11472 11473 for (var z in this._levels) { 11474 z = Number(z); 11475 if (this._levels[z].el.children.length || z === zoom) { 11476 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 11477 this._onUpdateLevel(z); 11478 } else { 11479 remove(this._levels[z].el); 11480 this._removeTilesAtZoom(z); 11481 this._onRemoveLevel(z); 11482 delete this._levels[z]; 11483 } 11484 } 11485 11486 var level = this._levels[zoom], 11487 map = this._map; 11488 11489 if (!level) { 11490 level = this._levels[zoom] = {}; 11491 11492 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 11493 level.el.style.zIndex = maxZoom; 11494 11495 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 11496 level.zoom = zoom; 11497 11498 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 11499 11500 // force the browser to consider the newly added element for transition 11501 falseFn(level.el.offsetWidth); 11502 11503 this._onCreateLevel(level); 11504 } 11505 11506 this._level = level; 11507 11508 return level; 11509 }, 11510 11511 _onUpdateLevel: falseFn, 11512 11513 _onRemoveLevel: falseFn, 11514 11515 _onCreateLevel: falseFn, 11516 11517 _pruneTiles: function () { 11518 if (!this._map) { 11519 return; 11520 } 11521 11522 var key, tile; 11523 11524 var zoom = this._map.getZoom(); 11525 if (zoom > this.options.maxZoom || 11526 zoom < this.options.minZoom) { 11527 this._removeAllTiles(); 11528 return; 11529 } 11530 11531 for (key in this._tiles) { 11532 tile = this._tiles[key]; 11533 tile.retain = tile.current; 11534 } 11535 11536 for (key in this._tiles) { 11537 tile = this._tiles[key]; 11538 if (tile.current && !tile.active) { 11539 var coords = tile.coords; 11540 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 11541 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 11542 } 11543 } 11544 } 11545 11546 for (key in this._tiles) { 11547 if (!this._tiles[key].retain) { 11548 this._removeTile(key); 11549 } 11550 } 11551 }, 11552 11553 _removeTilesAtZoom: function (zoom) { 11554 for (var key in this._tiles) { 11555 if (this._tiles[key].coords.z !== zoom) { 11556 continue; 11557 } 11558 this._removeTile(key); 11559 } 11560 }, 11561 11562 _removeAllTiles: function () { 11563 for (var key in this._tiles) { 11564 this._removeTile(key); 11565 } 11566 }, 11567 11568 _invalidateAll: function () { 11569 for (var z in this._levels) { 11570 remove(this._levels[z].el); 11571 this._onRemoveLevel(Number(z)); 11572 delete this._levels[z]; 11573 } 11574 this._removeAllTiles(); 11575 11576 this._tileZoom = undefined; 11577 }, 11578 11579 _retainParent: function (x, y, z, minZoom) { 11580 var x2 = Math.floor(x / 2), 11581 y2 = Math.floor(y / 2), 11582 z2 = z - 1, 11583 coords2 = new Point(+x2, +y2); 11584 coords2.z = +z2; 11585 11586 var key = this._tileCoordsToKey(coords2), 11587 tile = this._tiles[key]; 11588 11589 if (tile && tile.active) { 11590 tile.retain = true; 11591 return true; 11592 11593 } else if (tile && tile.loaded) { 11594 tile.retain = true; 11595 } 11596 11597 if (z2 > minZoom) { 11598 return this._retainParent(x2, y2, z2, minZoom); 11599 } 11600 11601 return false; 11602 }, 11603 11604 _retainChildren: function (x, y, z, maxZoom) { 11605 11606 for (var i = 2 * x; i < 2 * x + 2; i++) { 11607 for (var j = 2 * y; j < 2 * y + 2; j++) { 11608 11609 var coords = new Point(i, j); 11610 coords.z = z + 1; 11611 11612 var key = this._tileCoordsToKey(coords), 11613 tile = this._tiles[key]; 11614 11615 if (tile && tile.active) { 11616 tile.retain = true; 11617 continue; 11618 11619 } else if (tile && tile.loaded) { 11620 tile.retain = true; 11621 } 11622 11623 if (z + 1 < maxZoom) { 11624 this._retainChildren(i, j, z + 1, maxZoom); 11625 } 11626 } 11627 } 11628 }, 11629 11630 _resetView: function (e) { 11631 var animating = e && (e.pinch || e.flyTo); 11632 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 11633 }, 11634 11635 _animateZoom: function (e) { 11636 this._setView(e.center, e.zoom, true, e.noUpdate); 11637 }, 11638 11639 _clampZoom: function (zoom) { 11640 var options = this.options; 11641 11642 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) { 11643 return options.minNativeZoom; 11644 } 11645 11646 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) { 11647 return options.maxNativeZoom; 11648 } 11649 11650 return zoom; 11651 }, 11652 11653 _setView: function (center, zoom, noPrune, noUpdate) { 11654 var tileZoom = Math.round(zoom); 11655 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 11656 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 11657 tileZoom = undefined; 11658 } else { 11659 tileZoom = this._clampZoom(tileZoom); 11660 } 11661 11662 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 11663 11664 if (!noUpdate || tileZoomChanged) { 11665 11666 this._tileZoom = tileZoom; 11667 11668 if (this._abortLoading) { 11669 this._abortLoading(); 11670 } 11671 11672 this._updateLevels(); 11673 this._resetGrid(); 11674 11675 if (tileZoom !== undefined) { 11676 this._update(center); 11677 } 11678 11679 if (!noPrune) { 11680 this._pruneTiles(); 11681 } 11682 11683 // Flag to prevent _updateOpacity from pruning tiles during 11684 // a zoom anim or a pinch gesture 11685 this._noPrune = !!noPrune; 11686 } 11687 11688 this._setZoomTransforms(center, zoom); 11689 }, 11690 11691 _setZoomTransforms: function (center, zoom) { 11692 for (var i in this._levels) { 11693 this._setZoomTransform(this._levels[i], center, zoom); 11694 } 11695 }, 11696 11697 _setZoomTransform: function (level, center, zoom) { 11698 var scale = this._map.getZoomScale(zoom, level.zoom), 11699 translate = level.origin.multiplyBy(scale) 11700 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 11701 11702 if (Browser.any3d) { 11703 setTransform(level.el, translate, scale); 11704 } else { 11705 setPosition(level.el, translate); 11706 } 11707 }, 11708 11709 _resetGrid: function () { 11710 var map = this._map, 11711 crs = map.options.crs, 11712 tileSize = this._tileSize = this.getTileSize(), 11713 tileZoom = this._tileZoom; 11714 11715 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 11716 if (bounds) { 11717 this._globalTileRange = this._pxBoundsToTileRange(bounds); 11718 } 11719 11720 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 11721 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 11722 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 11723 ]; 11724 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 11725 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 11726 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 11727 ]; 11728 }, 11729 11730 _onMoveEnd: function () { 11731 if (!this._map || this._map._animatingZoom) { return; } 11732 11733 this._update(); 11734 }, 11735 11736 _getTiledPixelBounds: function (center) { 11737 var map = this._map, 11738 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 11739 scale = map.getZoomScale(mapZoom, this._tileZoom), 11740 pixelCenter = map.project(center, this._tileZoom).floor(), 11741 halfSize = map.getSize().divideBy(scale * 2); 11742 11743 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 11744 }, 11745 11746 // Private method to load tiles in the grid's active zoom level according to map bounds 11747 _update: function (center) { 11748 var map = this._map; 11749 if (!map) { return; } 11750 var zoom = this._clampZoom(map.getZoom()); 11751 11752 if (center === undefined) { center = map.getCenter(); } 11753 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 11754 11755 var pixelBounds = this._getTiledPixelBounds(center), 11756 tileRange = this._pxBoundsToTileRange(pixelBounds), 11757 tileCenter = tileRange.getCenter(), 11758 queue = [], 11759 margin = this.options.keepBuffer, 11760 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 11761 tileRange.getTopRight().add([margin, -margin])); 11762 11763 // Sanity check: panic if the tile range contains Infinity somewhere. 11764 if (!(isFinite(tileRange.min.x) && 11765 isFinite(tileRange.min.y) && 11766 isFinite(tileRange.max.x) && 11767 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 11768 11769 for (var key in this._tiles) { 11770 var c = this._tiles[key].coords; 11771 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 11772 this._tiles[key].current = false; 11773 } 11774 } 11775 11776 // _update just loads more tiles. If the tile zoom level differs too much 11777 // from the map's, let _setView reset levels and prune old tiles. 11778 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 11779 11780 // create a queue of coordinates to load tiles from 11781 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 11782 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 11783 var coords = new Point(i, j); 11784 coords.z = this._tileZoom; 11785 11786 if (!this._isValidTile(coords)) { continue; } 11787 11788 var tile = this._tiles[this._tileCoordsToKey(coords)]; 11789 if (tile) { 11790 tile.current = true; 11791 } else { 11792 queue.push(coords); 11793 } 11794 } 11795 } 11796 11797 // sort tile queue to load tiles in order of their distance to center 11798 queue.sort(function (a, b) { 11799 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 11800 }); 11801 11802 if (queue.length !== 0) { 11803 // if it's the first batch of tiles to load 11804 if (!this._loading) { 11805 this._loading = true; 11806 // @event loading: Event 11807 // Fired when the grid layer starts loading tiles. 11808 this.fire('loading'); 11809 } 11810 11811 // create DOM fragment to append tiles in one batch 11812 var fragment = document.createDocumentFragment(); 11813 11814 for (i = 0; i < queue.length; i++) { 11815 this._addTile(queue[i], fragment); 11816 } 11817 11818 this._level.el.appendChild(fragment); 11819 } 11820 }, 11821 11822 _isValidTile: function (coords) { 11823 var crs = this._map.options.crs; 11824 11825 if (!crs.infinite) { 11826 // don't load tile if it's out of bounds and not wrapped 11827 var bounds = this._globalTileRange; 11828 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 11829 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 11830 } 11831 11832 if (!this.options.bounds) { return true; } 11833 11834 // don't load tile if it doesn't intersect the bounds in options 11835 var tileBounds = this._tileCoordsToBounds(coords); 11836 return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 11837 }, 11838 11839 _keyToBounds: function (key) { 11840 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 11841 }, 11842 11843 _tileCoordsToNwSe: function (coords) { 11844 var map = this._map, 11845 tileSize = this.getTileSize(), 11846 nwPoint = coords.scaleBy(tileSize), 11847 sePoint = nwPoint.add(tileSize), 11848 nw = map.unproject(nwPoint, coords.z), 11849 se = map.unproject(sePoint, coords.z); 11850 return [nw, se]; 11851 }, 11852 11853 // converts tile coordinates to its geographical bounds 11854 _tileCoordsToBounds: function (coords) { 11855 var bp = this._tileCoordsToNwSe(coords), 11856 bounds = new LatLngBounds(bp[0], bp[1]); 11857 11858 if (!this.options.noWrap) { 11859 bounds = this._map.wrapLatLngBounds(bounds); 11860 } 11861 return bounds; 11862 }, 11863 // converts tile coordinates to key for the tile cache 11864 _tileCoordsToKey: function (coords) { 11865 return coords.x + ':' + coords.y + ':' + coords.z; 11866 }, 11867 11868 // converts tile cache key to coordinates 11869 _keyToTileCoords: function (key) { 11870 var k = key.split(':'), 11871 coords = new Point(+k[0], +k[1]); 11872 coords.z = +k[2]; 11873 return coords; 11874 }, 11875 11876 _removeTile: function (key) { 11877 var tile = this._tiles[key]; 11878 if (!tile) { return; } 11879 11880 remove(tile.el); 11881 11882 delete this._tiles[key]; 11883 11884 // @event tileunload: TileEvent 11885 // Fired when a tile is removed (e.g. when a tile goes off the screen). 11886 this.fire('tileunload', { 11887 tile: tile.el, 11888 coords: this._keyToTileCoords(key) 11889 }); 11890 }, 11891 11892 _initTile: function (tile) { 11893 addClass(tile, 'leaflet-tile'); 11894 11895 var tileSize = this.getTileSize(); 11896 tile.style.width = tileSize.x + 'px'; 11897 tile.style.height = tileSize.y + 'px'; 11898 11899 tile.onselectstart = falseFn; 11900 tile.onmousemove = falseFn; 11901 11902 // update opacity on tiles in IE7-8 because of filter inheritance problems 11903 if (Browser.ielt9 && this.options.opacity < 1) { 11904 setOpacity(tile, this.options.opacity); 11905 } 11906 }, 11907 11908 _addTile: function (coords, container) { 11909 var tilePos = this._getTilePos(coords), 11910 key = this._tileCoordsToKey(coords); 11911 11912 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 11913 11914 this._initTile(tile); 11915 11916 // if createTile is defined with a second argument ("done" callback), 11917 // we know that tile is async and will be ready later; otherwise 11918 if (this.createTile.length < 2) { 11919 // mark tile as ready, but delay one frame for opacity animation to happen 11920 requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 11921 } 11922 11923 setPosition(tile, tilePos); 11924 11925 // save tile in cache 11926 this._tiles[key] = { 11927 el: tile, 11928 coords: coords, 11929 current: true 11930 }; 11931 11932 container.appendChild(tile); 11933 // @event tileloadstart: TileEvent 11934 // Fired when a tile is requested and starts loading. 11935 this.fire('tileloadstart', { 11936 tile: tile, 11937 coords: coords 11938 }); 11939 }, 11940 11941 _tileReady: function (coords, err, tile) { 11942 if (err) { 11943 // @event tileerror: TileErrorEvent 11944 // Fired when there is an error loading a tile. 11945 this.fire('tileerror', { 11946 error: err, 11947 tile: tile, 11948 coords: coords 11949 }); 11950 } 11951 11952 var key = this._tileCoordsToKey(coords); 11953 11954 tile = this._tiles[key]; 11955 if (!tile) { return; } 11956 11957 tile.loaded = +new Date(); 11958 if (this._map._fadeAnimated) { 11959 setOpacity(tile.el, 0); 11960 cancelAnimFrame(this._fadeFrame); 11961 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11962 } else { 11963 tile.active = true; 11964 this._pruneTiles(); 11965 } 11966 11967 if (!err) { 11968 addClass(tile.el, 'leaflet-tile-loaded'); 11969 11970 // @event tileload: TileEvent 11971 // Fired when a tile loads. 11972 this.fire('tileload', { 11973 tile: tile.el, 11974 coords: coords 11975 }); 11976 } 11977 11978 if (this._noTilesToLoad()) { 11979 this._loading = false; 11980 // @event load: Event 11981 // Fired when the grid layer loaded all visible tiles. 11982 this.fire('load'); 11983 11984 if (Browser.ielt9 || !this._map._fadeAnimated) { 11985 requestAnimFrame(this._pruneTiles, this); 11986 } else { 11987 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 11988 // to trigger a pruning. 11989 setTimeout(bind(this._pruneTiles, this), 250); 11990 } 11991 } 11992 }, 11993 11994 _getTilePos: function (coords) { 11995 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 11996 }, 11997 11998 _wrapCoords: function (coords) { 11999 var newCoords = new Point( 12000 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 12001 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y); 12002 newCoords.z = coords.z; 12003 return newCoords; 12004 }, 12005 12006 _pxBoundsToTileRange: function (bounds) { 12007 var tileSize = this.getTileSize(); 12008 return new Bounds( 12009 bounds.min.unscaleBy(tileSize).floor(), 12010 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 12011 }, 12012 12013 _noTilesToLoad: function () { 12014 for (var key in this._tiles) { 12015 if (!this._tiles[key].loaded) { return false; } 12016 } 12017 return true; 12018 } 12019 }); 12020 12021 // @factory L.gridLayer(options?: GridLayer options) 12022 // Creates a new instance of GridLayer with the supplied options. 12023 function gridLayer(options) { 12024 return new GridLayer(options); 12025 } 12026 12027 /* 12028 * @class TileLayer 12029 * @inherits GridLayer 12030 * @aka L.TileLayer 12031 * 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`. 12032 * 12033 * @example 12034 * 12035 * ```js 12036 * 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); 12037 * ``` 12038 * 12039 * @section URL template 12040 * @example 12041 * 12042 * A string of the following form: 12043 * 12044 * ``` 12045 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 12046 * ``` 12047 * 12048 * `{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. 12049 * 12050 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 12051 * 12052 * ``` 12053 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 12054 * ``` 12055 */ 12056 12057 12058 var TileLayer = GridLayer.extend({ 12059 12060 // @section 12061 // @aka TileLayer options 12062 options: { 12063 // @option minZoom: Number = 0 12064 // The minimum zoom level down to which this layer will be displayed (inclusive). 12065 minZoom: 0, 12066 12067 // @option maxZoom: Number = 18 12068 // The maximum zoom level up to which this layer will be displayed (inclusive). 12069 maxZoom: 18, 12070 12071 // @option subdomains: String|String[] = 'abc' 12072 // 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. 12073 subdomains: 'abc', 12074 12075 // @option errorTileUrl: String = '' 12076 // URL to the tile image to show in place of the tile that failed to load. 12077 errorTileUrl: '', 12078 12079 // @option zoomOffset: Number = 0 12080 // The zoom number used in tile URLs will be offset with this value. 12081 zoomOffset: 0, 12082 12083 // @option tms: Boolean = false 12084 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 12085 tms: false, 12086 12087 // @option zoomReverse: Boolean = false 12088 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 12089 zoomReverse: false, 12090 12091 // @option detectRetina: Boolean = false 12092 // 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. 12093 detectRetina: false, 12094 12095 // @option crossOrigin: Boolean|String = false 12096 // Whether the crossOrigin attribute will be added to the tiles. 12097 // 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. 12098 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 12099 crossOrigin: false, 12100 12101 // @option referrerPolicy: Boolean|String = false 12102 // Whether the referrerPolicy attribute will be added to the tiles. 12103 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided. 12104 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer 12105 // (e.g. to validate an API token). 12106 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values. 12107 referrerPolicy: false 12108 }, 12109 12110 initialize: function (url, options) { 12111 12112 this._url = url; 12113 12114 options = setOptions(this, options); 12115 12116 // detecting retina displays, adjusting tileSize and zoom levels 12117 if (options.detectRetina && Browser.retina && options.maxZoom > 0) { 12118 12119 options.tileSize = Math.floor(options.tileSize / 2); 12120 12121 if (!options.zoomReverse) { 12122 options.zoomOffset++; 12123 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1); 12124 } else { 12125 options.zoomOffset--; 12126 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1); 12127 } 12128 12129 options.minZoom = Math.max(0, options.minZoom); 12130 } else if (!options.zoomReverse) { 12131 // make sure maxZoom is gte minZoom 12132 options.maxZoom = Math.max(options.minZoom, options.maxZoom); 12133 } else { 12134 // make sure minZoom is lte maxZoom 12135 options.minZoom = Math.min(options.maxZoom, options.minZoom); 12136 } 12137 12138 if (typeof options.subdomains === 'string') { 12139 options.subdomains = options.subdomains.split(''); 12140 } 12141 12142 this.on('tileunload', this._onTileRemove); 12143 }, 12144 12145 // @method setUrl(url: String, noRedraw?: Boolean): this 12146 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 12147 // If the URL does not change, the layer will not be redrawn unless 12148 // the noRedraw parameter is set to false. 12149 setUrl: function (url, noRedraw) { 12150 if (this._url === url && noRedraw === undefined) { 12151 noRedraw = true; 12152 } 12153 12154 this._url = url; 12155 12156 if (!noRedraw) { 12157 this.redraw(); 12158 } 12159 return this; 12160 }, 12161 12162 // @method createTile(coords: Object, done?: Function): HTMLElement 12163 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 12164 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 12165 // callback is called when the tile has been loaded. 12166 createTile: function (coords, done) { 12167 var tile = document.createElement('img'); 12168 12169 on(tile, 'load', bind(this._tileOnLoad, this, done, tile)); 12170 on(tile, 'error', bind(this._tileOnError, this, done, tile)); 12171 12172 if (this.options.crossOrigin || this.options.crossOrigin === '') { 12173 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 12174 } 12175 12176 // for this new option we follow the documented behavior 12177 // more closely by only setting the property when string 12178 if (typeof this.options.referrerPolicy === 'string') { 12179 tile.referrerPolicy = this.options.referrerPolicy; 12180 } 12181 12182 // The alt attribute is set to the empty string, 12183 // allowing screen readers to ignore the decorative image tiles. 12184 // https://www.w3.org/WAI/tutorials/images/decorative/ 12185 // https://www.w3.org/TR/html-aria/#el-img-empty-alt 12186 tile.alt = ''; 12187 12188 tile.src = this.getTileUrl(coords); 12189 12190 return tile; 12191 }, 12192 12193 // @section Extension methods 12194 // @uninheritable 12195 // Layers extending `TileLayer` might reimplement the following method. 12196 // @method getTileUrl(coords: Object): String 12197 // Called only internally, returns the URL for a tile given its coordinates. 12198 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 12199 getTileUrl: function (coords) { 12200 var data = { 12201 r: Browser.retina ? '@2x' : '', 12202 s: this._getSubdomain(coords), 12203 x: coords.x, 12204 y: coords.y, 12205 z: this._getZoomForUrl() 12206 }; 12207 if (this._map && !this._map.options.crs.infinite) { 12208 var invertedY = this._globalTileRange.max.y - coords.y; 12209 if (this.options.tms) { 12210 data['y'] = invertedY; 12211 } 12212 data['-y'] = invertedY; 12213 } 12214 12215 return template(this._url, extend(data, this.options)); 12216 }, 12217 12218 _tileOnLoad: function (done, tile) { 12219 // For https://github.com/Leaflet/Leaflet/issues/3332 12220 if (Browser.ielt9) { 12221 setTimeout(bind(done, this, null, tile), 0); 12222 } else { 12223 done(null, tile); 12224 } 12225 }, 12226 12227 _tileOnError: function (done, tile, e) { 12228 var errorUrl = this.options.errorTileUrl; 12229 if (errorUrl && tile.getAttribute('src') !== errorUrl) { 12230 tile.src = errorUrl; 12231 } 12232 done(e, tile); 12233 }, 12234 12235 _onTileRemove: function (e) { 12236 e.tile.onload = null; 12237 }, 12238 12239 _getZoomForUrl: function () { 12240 var zoom = this._tileZoom, 12241 maxZoom = this.options.maxZoom, 12242 zoomReverse = this.options.zoomReverse, 12243 zoomOffset = this.options.zoomOffset; 12244 12245 if (zoomReverse) { 12246 zoom = maxZoom - zoom; 12247 } 12248 12249 return zoom + zoomOffset; 12250 }, 12251 12252 _getSubdomain: function (tilePoint) { 12253 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 12254 return this.options.subdomains[index]; 12255 }, 12256 12257 // stops loading all tiles in the background layer 12258 _abortLoading: function () { 12259 var i, tile; 12260 for (i in this._tiles) { 12261 if (this._tiles[i].coords.z !== this._tileZoom) { 12262 tile = this._tiles[i].el; 12263 12264 tile.onload = falseFn; 12265 tile.onerror = falseFn; 12266 12267 if (!tile.complete) { 12268 tile.src = emptyImageUrl; 12269 var coords = this._tiles[i].coords; 12270 remove(tile); 12271 delete this._tiles[i]; 12272 // @event tileabort: TileEvent 12273 // Fired when a tile was loading but is now not wanted. 12274 this.fire('tileabort', { 12275 tile: tile, 12276 coords: coords 12277 }); 12278 } 12279 } 12280 } 12281 }, 12282 12283 _removeTile: function (key) { 12284 var tile = this._tiles[key]; 12285 if (!tile) { return; } 12286 12287 // Cancels any pending http requests associated with the tile 12288 tile.el.setAttribute('src', emptyImageUrl); 12289 12290 return GridLayer.prototype._removeTile.call(this, key); 12291 }, 12292 12293 _tileReady: function (coords, err, tile) { 12294 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) { 12295 return; 12296 } 12297 12298 return GridLayer.prototype._tileReady.call(this, coords, err, tile); 12299 } 12300 }); 12301 12302 12303 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 12304 // Instantiates a tile layer object given a `URL template` and optionally an options object. 12305 12306 function tileLayer(url, options) { 12307 return new TileLayer(url, options); 12308 } 12309 12310 /* 12311 * @class TileLayer.WMS 12312 * @inherits TileLayer 12313 * @aka L.TileLayer.WMS 12314 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 12315 * 12316 * @example 12317 * 12318 * ```js 12319 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 12320 * layers: 'nexrad-n0r-900913', 12321 * format: 'image/png', 12322 * transparent: true, 12323 * attribution: "Weather data © 2012 IEM Nexrad" 12324 * }); 12325 * ``` 12326 */ 12327 12328 var TileLayerWMS = TileLayer.extend({ 12329 12330 // @section 12331 // @aka TileLayer.WMS options 12332 // If any custom options not documented here are used, they will be sent to the 12333 // WMS server as extra parameters in each request URL. This can be useful for 12334 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 12335 defaultWmsParams: { 12336 service: 'WMS', 12337 request: 'GetMap', 12338 12339 // @option layers: String = '' 12340 // **(required)** Comma-separated list of WMS layers to show. 12341 layers: '', 12342 12343 // @option styles: String = '' 12344 // Comma-separated list of WMS styles. 12345 styles: '', 12346 12347 // @option format: String = 'image/jpeg' 12348 // WMS image format (use `'image/png'` for layers with transparency). 12349 format: 'image/jpeg', 12350 12351 // @option transparent: Boolean = false 12352 // If `true`, the WMS service will return images with transparency. 12353 transparent: false, 12354 12355 // @option version: String = '1.1.1' 12356 // Version of the WMS service to use 12357 version: '1.1.1' 12358 }, 12359 12360 options: { 12361 // @option crs: CRS = null 12362 // Coordinate Reference System to use for the WMS requests, defaults to 12363 // map CRS. Don't change this if you're not sure what it means. 12364 crs: null, 12365 12366 // @option uppercase: Boolean = false 12367 // If `true`, WMS request parameter keys will be uppercase. 12368 uppercase: false 12369 }, 12370 12371 initialize: function (url, options) { 12372 12373 this._url = url; 12374 12375 var wmsParams = extend({}, this.defaultWmsParams); 12376 12377 // all keys that are not TileLayer options go to WMS params 12378 for (var i in options) { 12379 if (!(i in this.options)) { 12380 wmsParams[i] = options[i]; 12381 } 12382 } 12383 12384 options = setOptions(this, options); 12385 12386 var realRetina = options.detectRetina && Browser.retina ? 2 : 1; 12387 var tileSize = this.getTileSize(); 12388 wmsParams.width = tileSize.x * realRetina; 12389 wmsParams.height = tileSize.y * realRetina; 12390 12391 this.wmsParams = wmsParams; 12392 }, 12393 12394 onAdd: function (map) { 12395 12396 this._crs = this.options.crs || map.options.crs; 12397 this._wmsVersion = parseFloat(this.wmsParams.version); 12398 12399 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 12400 this.wmsParams[projectionKey] = this._crs.code; 12401 12402 TileLayer.prototype.onAdd.call(this, map); 12403 }, 12404 12405 getTileUrl: function (coords) { 12406 12407 var tileBounds = this._tileCoordsToNwSe(coords), 12408 crs = this._crs, 12409 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), 12410 min = bounds.min, 12411 max = bounds.max, 12412 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? 12413 [min.y, min.x, max.y, max.x] : 12414 [min.x, min.y, max.x, max.y]).join(','), 12415 url = TileLayer.prototype.getTileUrl.call(this, coords); 12416 return url + 12417 getParamString(this.wmsParams, url, this.options.uppercase) + 12418 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 12419 }, 12420 12421 // @method setParams(params: Object, noRedraw?: Boolean): this 12422 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 12423 setParams: function (params, noRedraw) { 12424 12425 extend(this.wmsParams, params); 12426 12427 if (!noRedraw) { 12428 this.redraw(); 12429 } 12430 12431 return this; 12432 } 12433 }); 12434 12435 12436 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 12437 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 12438 function tileLayerWMS(url, options) { 12439 return new TileLayerWMS(url, options); 12440 } 12441 12442 TileLayer.WMS = TileLayerWMS; 12443 tileLayer.wms = tileLayerWMS; 12444 12445 /* 12446 * @class Renderer 12447 * @inherits Layer 12448 * @aka L.Renderer 12449 * 12450 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 12451 * DOM container of the renderer, its bounds, and its zoom animation. 12452 * 12453 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 12454 * itself can be added or removed to the map. All paths use a renderer, which can 12455 * be implicit (the map will decide the type of renderer and use it automatically) 12456 * or explicit (using the [`renderer`](#path-renderer) option of the path). 12457 * 12458 * Do not use this class directly, use `SVG` and `Canvas` instead. 12459 * 12460 * @event update: Event 12461 * Fired when the renderer updates its bounds, center and zoom, for example when 12462 * its map has moved 12463 */ 12464 12465 var Renderer = Layer.extend({ 12466 12467 // @section 12468 // @aka Renderer options 12469 options: { 12470 // @option padding: Number = 0.1 12471 // How much to extend the clip area around the map view (relative to its size) 12472 // e.g. 0.1 would be 10% of map view in each direction 12473 padding: 0.1 12474 }, 12475 12476 initialize: function (options) { 12477 setOptions(this, options); 12478 stamp(this); 12479 this._layers = this._layers || {}; 12480 }, 12481 12482 onAdd: function () { 12483 if (!this._container) { 12484 this._initContainer(); // defined by renderer implementations 12485 12486 // always keep transform-origin as 0 0 12487 addClass(this._container, 'leaflet-zoom-animated'); 12488 } 12489 12490 this.getPane().appendChild(this._container); 12491 this._update(); 12492 this.on('update', this._updatePaths, this); 12493 }, 12494 12495 onRemove: function () { 12496 this.off('update', this._updatePaths, this); 12497 this._destroyContainer(); 12498 }, 12499 12500 getEvents: function () { 12501 var events = { 12502 viewreset: this._reset, 12503 zoom: this._onZoom, 12504 moveend: this._update, 12505 zoomend: this._onZoomEnd 12506 }; 12507 if (this._zoomAnimated) { 12508 events.zoomanim = this._onAnimZoom; 12509 } 12510 return events; 12511 }, 12512 12513 _onAnimZoom: function (ev) { 12514 this._updateTransform(ev.center, ev.zoom); 12515 }, 12516 12517 _onZoom: function () { 12518 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 12519 }, 12520 12521 _updateTransform: function (center, zoom) { 12522 var scale = this._map.getZoomScale(zoom, this._zoom), 12523 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 12524 currentCenterPoint = this._map.project(this._center, zoom), 12525 12526 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint) 12527 .subtract(this._map._getNewPixelOrigin(center, zoom)); 12528 12529 if (Browser.any3d) { 12530 setTransform(this._container, topLeftOffset, scale); 12531 } else { 12532 setPosition(this._container, topLeftOffset); 12533 } 12534 }, 12535 12536 _reset: function () { 12537 this._update(); 12538 this._updateTransform(this._center, this._zoom); 12539 12540 for (var id in this._layers) { 12541 this._layers[id]._reset(); 12542 } 12543 }, 12544 12545 _onZoomEnd: function () { 12546 for (var id in this._layers) { 12547 this._layers[id]._project(); 12548 } 12549 }, 12550 12551 _updatePaths: function () { 12552 for (var id in this._layers) { 12553 this._layers[id]._update(); 12554 } 12555 }, 12556 12557 _update: function () { 12558 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 12559 // Subclasses are responsible of firing the 'update' event. 12560 var p = this.options.padding, 12561 size = this._map.getSize(), 12562 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 12563 12564 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 12565 12566 this._center = this._map.getCenter(); 12567 this._zoom = this._map.getZoom(); 12568 } 12569 }); 12570 12571 /* 12572 * @class Canvas 12573 * @inherits Renderer 12574 * @aka L.Canvas 12575 * 12576 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 12577 * Inherits `Renderer`. 12578 * 12579 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not 12580 * available in all web browsers, notably IE8, and overlapping geometries might 12581 * not display properly in some edge cases. 12582 * 12583 * @example 12584 * 12585 * Use Canvas by default for all paths in the map: 12586 * 12587 * ```js 12588 * var map = L.map('map', { 12589 * renderer: L.canvas() 12590 * }); 12591 * ``` 12592 * 12593 * Use a Canvas renderer with extra padding for specific vector geometries: 12594 * 12595 * ```js 12596 * var map = L.map('map'); 12597 * var myRenderer = L.canvas({ padding: 0.5 }); 12598 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12599 * var circle = L.circle( center, { renderer: myRenderer } ); 12600 * ``` 12601 */ 12602 12603 var Canvas = Renderer.extend({ 12604 12605 // @section 12606 // @aka Canvas options 12607 options: { 12608 // @option tolerance: Number = 0 12609 // How much to extend the click tolerance around a path/object on the map. 12610 tolerance: 0 12611 }, 12612 12613 getEvents: function () { 12614 var events = Renderer.prototype.getEvents.call(this); 12615 events.viewprereset = this._onViewPreReset; 12616 return events; 12617 }, 12618 12619 _onViewPreReset: function () { 12620 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 12621 this._postponeUpdatePaths = true; 12622 }, 12623 12624 onAdd: function () { 12625 Renderer.prototype.onAdd.call(this); 12626 12627 // Redraw vectors since canvas is cleared upon removal, 12628 // in case of removing the renderer itself from the map. 12629 this._draw(); 12630 }, 12631 12632 _initContainer: function () { 12633 var container = this._container = document.createElement('canvas'); 12634 12635 on(container, 'mousemove', this._onMouseMove, this); 12636 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); 12637 on(container, 'mouseout', this._handleMouseOut, this); 12638 container['_leaflet_disable_events'] = true; 12639 12640 this._ctx = container.getContext('2d'); 12641 }, 12642 12643 _destroyContainer: function () { 12644 cancelAnimFrame(this._redrawRequest); 12645 delete this._ctx; 12646 remove(this._container); 12647 off(this._container); 12648 delete this._container; 12649 }, 12650 12651 _updatePaths: function () { 12652 if (this._postponeUpdatePaths) { return; } 12653 12654 var layer; 12655 this._redrawBounds = null; 12656 for (var id in this._layers) { 12657 layer = this._layers[id]; 12658 layer._update(); 12659 } 12660 this._redraw(); 12661 }, 12662 12663 _update: function () { 12664 if (this._map._animatingZoom && this._bounds) { return; } 12665 12666 Renderer.prototype._update.call(this); 12667 12668 var b = this._bounds, 12669 container = this._container, 12670 size = b.getSize(), 12671 m = Browser.retina ? 2 : 1; 12672 12673 setPosition(container, b.min); 12674 12675 // set canvas size (also clearing it); use double size on retina 12676 container.width = m * size.x; 12677 container.height = m * size.y; 12678 container.style.width = size.x + 'px'; 12679 container.style.height = size.y + 'px'; 12680 12681 if (Browser.retina) { 12682 this._ctx.scale(2, 2); 12683 } 12684 12685 // translate so we use the same path coordinates after canvas element moves 12686 this._ctx.translate(-b.min.x, -b.min.y); 12687 12688 // Tell paths to redraw themselves 12689 this.fire('update'); 12690 }, 12691 12692 _reset: function () { 12693 Renderer.prototype._reset.call(this); 12694 12695 if (this._postponeUpdatePaths) { 12696 this._postponeUpdatePaths = false; 12697 this._updatePaths(); 12698 } 12699 }, 12700 12701 _initPath: function (layer) { 12702 this._updateDashArray(layer); 12703 this._layers[stamp(layer)] = layer; 12704 12705 var order = layer._order = { 12706 layer: layer, 12707 prev: this._drawLast, 12708 next: null 12709 }; 12710 if (this._drawLast) { this._drawLast.next = order; } 12711 this._drawLast = order; 12712 this._drawFirst = this._drawFirst || this._drawLast; 12713 }, 12714 12715 _addPath: function (layer) { 12716 this._requestRedraw(layer); 12717 }, 12718 12719 _removePath: function (layer) { 12720 var order = layer._order; 12721 var next = order.next; 12722 var prev = order.prev; 12723 12724 if (next) { 12725 next.prev = prev; 12726 } else { 12727 this._drawLast = prev; 12728 } 12729 if (prev) { 12730 prev.next = next; 12731 } else { 12732 this._drawFirst = next; 12733 } 12734 12735 delete layer._order; 12736 12737 delete this._layers[stamp(layer)]; 12738 12739 this._requestRedraw(layer); 12740 }, 12741 12742 _updatePath: function (layer) { 12743 // Redraw the union of the layer's old pixel 12744 // bounds and the new pixel bounds. 12745 this._extendRedrawBounds(layer); 12746 layer._project(); 12747 layer._update(); 12748 // The redraw will extend the redraw bounds 12749 // with the new pixel bounds. 12750 this._requestRedraw(layer); 12751 }, 12752 12753 _updateStyle: function (layer) { 12754 this._updateDashArray(layer); 12755 this._requestRedraw(layer); 12756 }, 12757 12758 _updateDashArray: function (layer) { 12759 if (typeof layer.options.dashArray === 'string') { 12760 var parts = layer.options.dashArray.split(/[, ]+/), 12761 dashArray = [], 12762 dashValue, 12763 i; 12764 for (i = 0; i < parts.length; i++) { 12765 dashValue = Number(parts[i]); 12766 // Ignore dash array containing invalid lengths 12767 if (isNaN(dashValue)) { return; } 12768 dashArray.push(dashValue); 12769 } 12770 layer.options._dashArray = dashArray; 12771 } else { 12772 layer.options._dashArray = layer.options.dashArray; 12773 } 12774 }, 12775 12776 _requestRedraw: function (layer) { 12777 if (!this._map) { return; } 12778 12779 this._extendRedrawBounds(layer); 12780 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 12781 }, 12782 12783 _extendRedrawBounds: function (layer) { 12784 if (layer._pxBounds) { 12785 var padding = (layer.options.weight || 0) + 1; 12786 this._redrawBounds = this._redrawBounds || new Bounds(); 12787 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 12788 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 12789 } 12790 }, 12791 12792 _redraw: function () { 12793 this._redrawRequest = null; 12794 12795 if (this._redrawBounds) { 12796 this._redrawBounds.min._floor(); 12797 this._redrawBounds.max._ceil(); 12798 } 12799 12800 this._clear(); // clear layers in redraw bounds 12801 this._draw(); // draw layers 12802 12803 this._redrawBounds = null; 12804 }, 12805 12806 _clear: function () { 12807 var bounds = this._redrawBounds; 12808 if (bounds) { 12809 var size = bounds.getSize(); 12810 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 12811 } else { 12812 this._ctx.save(); 12813 this._ctx.setTransform(1, 0, 0, 1, 0, 0); 12814 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 12815 this._ctx.restore(); 12816 } 12817 }, 12818 12819 _draw: function () { 12820 var layer, bounds = this._redrawBounds; 12821 this._ctx.save(); 12822 if (bounds) { 12823 var size = bounds.getSize(); 12824 this._ctx.beginPath(); 12825 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 12826 this._ctx.clip(); 12827 } 12828 12829 this._drawing = true; 12830 12831 for (var order = this._drawFirst; order; order = order.next) { 12832 layer = order.layer; 12833 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 12834 layer._updatePath(); 12835 } 12836 } 12837 12838 this._drawing = false; 12839 12840 this._ctx.restore(); // Restore state before clipping. 12841 }, 12842 12843 _updatePoly: function (layer, closed) { 12844 if (!this._drawing) { return; } 12845 12846 var i, j, len2, p, 12847 parts = layer._parts, 12848 len = parts.length, 12849 ctx = this._ctx; 12850 12851 if (!len) { return; } 12852 12853 ctx.beginPath(); 12854 12855 for (i = 0; i < len; i++) { 12856 for (j = 0, len2 = parts[i].length; j < len2; j++) { 12857 p = parts[i][j]; 12858 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 12859 } 12860 if (closed) { 12861 ctx.closePath(); 12862 } 12863 } 12864 12865 this._fillStroke(ctx, layer); 12866 12867 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 12868 }, 12869 12870 _updateCircle: function (layer) { 12871 12872 if (!this._drawing || layer._empty()) { return; } 12873 12874 var p = layer._point, 12875 ctx = this._ctx, 12876 r = Math.max(Math.round(layer._radius), 1), 12877 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 12878 12879 if (s !== 1) { 12880 ctx.save(); 12881 ctx.scale(1, s); 12882 } 12883 12884 ctx.beginPath(); 12885 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 12886 12887 if (s !== 1) { 12888 ctx.restore(); 12889 } 12890 12891 this._fillStroke(ctx, layer); 12892 }, 12893 12894 _fillStroke: function (ctx, layer) { 12895 var options = layer.options; 12896 12897 if (options.fill) { 12898 ctx.globalAlpha = options.fillOpacity; 12899 ctx.fillStyle = options.fillColor || options.color; 12900 ctx.fill(options.fillRule || 'evenodd'); 12901 } 12902 12903 if (options.stroke && options.weight !== 0) { 12904 if (ctx.setLineDash) { 12905 ctx.setLineDash(layer.options && layer.options._dashArray || []); 12906 } 12907 ctx.globalAlpha = options.opacity; 12908 ctx.lineWidth = options.weight; 12909 ctx.strokeStyle = options.color; 12910 ctx.lineCap = options.lineCap; 12911 ctx.lineJoin = options.lineJoin; 12912 ctx.stroke(); 12913 } 12914 }, 12915 12916 // Canvas obviously doesn't have mouse events for individual drawn objects, 12917 // so we emulate that by calculating what's under the mouse on mousemove/click manually 12918 12919 _onClick: function (e) { 12920 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 12921 12922 for (var order = this._drawFirst; order; order = order.next) { 12923 layer = order.layer; 12924 if (layer.options.interactive && layer._containsPoint(point)) { 12925 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) { 12926 clickedLayer = layer; 12927 } 12928 } 12929 } 12930 this._fireEvent(clickedLayer ? [clickedLayer] : false, e); 12931 }, 12932 12933 _onMouseMove: function (e) { 12934 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 12935 12936 var point = this._map.mouseEventToLayerPoint(e); 12937 this._handleMouseHover(e, point); 12938 }, 12939 12940 12941 _handleMouseOut: function (e) { 12942 var layer = this._hoveredLayer; 12943 if (layer) { 12944 // if we're leaving the layer, fire mouseout 12945 removeClass(this._container, 'leaflet-interactive'); 12946 this._fireEvent([layer], e, 'mouseout'); 12947 this._hoveredLayer = null; 12948 this._mouseHoverThrottled = false; 12949 } 12950 }, 12951 12952 _handleMouseHover: function (e, point) { 12953 if (this._mouseHoverThrottled) { 12954 return; 12955 } 12956 12957 var layer, candidateHoveredLayer; 12958 12959 for (var order = this._drawFirst; order; order = order.next) { 12960 layer = order.layer; 12961 if (layer.options.interactive && layer._containsPoint(point)) { 12962 candidateHoveredLayer = layer; 12963 } 12964 } 12965 12966 if (candidateHoveredLayer !== this._hoveredLayer) { 12967 this._handleMouseOut(e); 12968 12969 if (candidateHoveredLayer) { 12970 addClass(this._container, 'leaflet-interactive'); // change cursor 12971 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 12972 this._hoveredLayer = candidateHoveredLayer; 12973 } 12974 } 12975 12976 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e); 12977 12978 this._mouseHoverThrottled = true; 12979 setTimeout(bind(function () { 12980 this._mouseHoverThrottled = false; 12981 }, this), 32); 12982 }, 12983 12984 _fireEvent: function (layers, e, type) { 12985 this._map._fireDOMEvent(e, type || e.type, layers); 12986 }, 12987 12988 _bringToFront: function (layer) { 12989 var order = layer._order; 12990 12991 if (!order) { return; } 12992 12993 var next = order.next; 12994 var prev = order.prev; 12995 12996 if (next) { 12997 next.prev = prev; 12998 } else { 12999 // Already last 13000 return; 13001 } 13002 if (prev) { 13003 prev.next = next; 13004 } else if (next) { 13005 // Update first entry unless this is the 13006 // single entry 13007 this._drawFirst = next; 13008 } 13009 13010 order.prev = this._drawLast; 13011 this._drawLast.next = order; 13012 13013 order.next = null; 13014 this._drawLast = order; 13015 13016 this._requestRedraw(layer); 13017 }, 13018 13019 _bringToBack: function (layer) { 13020 var order = layer._order; 13021 13022 if (!order) { return; } 13023 13024 var next = order.next; 13025 var prev = order.prev; 13026 13027 if (prev) { 13028 prev.next = next; 13029 } else { 13030 // Already first 13031 return; 13032 } 13033 if (next) { 13034 next.prev = prev; 13035 } else if (prev) { 13036 // Update last entry unless this is the 13037 // single entry 13038 this._drawLast = prev; 13039 } 13040 13041 order.prev = null; 13042 13043 order.next = this._drawFirst; 13044 this._drawFirst.prev = order; 13045 this._drawFirst = order; 13046 13047 this._requestRedraw(layer); 13048 } 13049 }); 13050 13051 // @factory L.canvas(options?: Renderer options) 13052 // Creates a Canvas renderer with the given options. 13053 function canvas(options) { 13054 return Browser.canvas ? new Canvas(options) : null; 13055 } 13056 13057 /* 13058 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 13059 */ 13060 13061 13062 var vmlCreate = (function () { 13063 try { 13064 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 13065 return function (name) { 13066 return document.createElement('<lvml:' + name + ' class="lvml">'); 13067 }; 13068 } catch (e) { 13069 // Do not return fn from catch block so `e` can be garbage collected 13070 // See https://github.com/Leaflet/Leaflet/pull/7279 13071 } 13072 return function (name) { 13073 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 13074 }; 13075 })(); 13076 13077 13078 /* 13079 * @class SVG 13080 * 13081 * 13082 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 13083 * with old versions of Internet Explorer. 13084 */ 13085 13086 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences 13087 var vmlMixin = { 13088 13089 _initContainer: function () { 13090 this._container = create$1('div', 'leaflet-vml-container'); 13091 }, 13092 13093 _update: function () { 13094 if (this._map._animatingZoom) { return; } 13095 Renderer.prototype._update.call(this); 13096 this.fire('update'); 13097 }, 13098 13099 _initPath: function (layer) { 13100 var container = layer._container = vmlCreate('shape'); 13101 13102 addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 13103 13104 container.coordsize = '1 1'; 13105 13106 layer._path = vmlCreate('path'); 13107 container.appendChild(layer._path); 13108 13109 this._updateStyle(layer); 13110 this._layers[stamp(layer)] = layer; 13111 }, 13112 13113 _addPath: function (layer) { 13114 var container = layer._container; 13115 this._container.appendChild(container); 13116 13117 if (layer.options.interactive) { 13118 layer.addInteractiveTarget(container); 13119 } 13120 }, 13121 13122 _removePath: function (layer) { 13123 var container = layer._container; 13124 remove(container); 13125 layer.removeInteractiveTarget(container); 13126 delete this._layers[stamp(layer)]; 13127 }, 13128 13129 _updateStyle: function (layer) { 13130 var stroke = layer._stroke, 13131 fill = layer._fill, 13132 options = layer.options, 13133 container = layer._container; 13134 13135 container.stroked = !!options.stroke; 13136 container.filled = !!options.fill; 13137 13138 if (options.stroke) { 13139 if (!stroke) { 13140 stroke = layer._stroke = vmlCreate('stroke'); 13141 } 13142 container.appendChild(stroke); 13143 stroke.weight = options.weight + 'px'; 13144 stroke.color = options.color; 13145 stroke.opacity = options.opacity; 13146 13147 if (options.dashArray) { 13148 stroke.dashStyle = isArray(options.dashArray) ? 13149 options.dashArray.join(' ') : 13150 options.dashArray.replace(/( *, *)/g, ' '); 13151 } else { 13152 stroke.dashStyle = ''; 13153 } 13154 stroke.endcap = options.lineCap.replace('butt', 'flat'); 13155 stroke.joinstyle = options.lineJoin; 13156 13157 } else if (stroke) { 13158 container.removeChild(stroke); 13159 layer._stroke = null; 13160 } 13161 13162 if (options.fill) { 13163 if (!fill) { 13164 fill = layer._fill = vmlCreate('fill'); 13165 } 13166 container.appendChild(fill); 13167 fill.color = options.fillColor || options.color; 13168 fill.opacity = options.fillOpacity; 13169 13170 } else if (fill) { 13171 container.removeChild(fill); 13172 layer._fill = null; 13173 } 13174 }, 13175 13176 _updateCircle: function (layer) { 13177 var p = layer._point.round(), 13178 r = Math.round(layer._radius), 13179 r2 = Math.round(layer._radiusY || r); 13180 13181 this._setPath(layer, layer._empty() ? 'M0 0' : 13182 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 13183 }, 13184 13185 _setPath: function (layer, path) { 13186 layer._path.v = path; 13187 }, 13188 13189 _bringToFront: function (layer) { 13190 toFront(layer._container); 13191 }, 13192 13193 _bringToBack: function (layer) { 13194 toBack(layer._container); 13195 } 13196 }; 13197 13198 var create = Browser.vml ? vmlCreate : svgCreate; 13199 13200 /* 13201 * @class SVG 13202 * @inherits Renderer 13203 * @aka L.SVG 13204 * 13205 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 13206 * Inherits `Renderer`. 13207 * 13208 * Due to [technical limitations](https://caniuse.com/svg), SVG is not 13209 * available in all web browsers, notably Android 2.x and 3.x. 13210 * 13211 * Although SVG is not available on IE7 and IE8, these browsers support 13212 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 13213 * (a now deprecated technology), and the SVG renderer will fall back to VML in 13214 * this case. 13215 * 13216 * @example 13217 * 13218 * Use SVG by default for all paths in the map: 13219 * 13220 * ```js 13221 * var map = L.map('map', { 13222 * renderer: L.svg() 13223 * }); 13224 * ``` 13225 * 13226 * Use a SVG renderer with extra padding for specific vector geometries: 13227 * 13228 * ```js 13229 * var map = L.map('map'); 13230 * var myRenderer = L.svg({ padding: 0.5 }); 13231 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 13232 * var circle = L.circle( center, { renderer: myRenderer } ); 13233 * ``` 13234 */ 13235 13236 var SVG = Renderer.extend({ 13237 13238 _initContainer: function () { 13239 this._container = create('svg'); 13240 13241 // makes it possible to click through svg root; we'll reset it back in individual paths 13242 this._container.setAttribute('pointer-events', 'none'); 13243 13244 this._rootGroup = create('g'); 13245 this._container.appendChild(this._rootGroup); 13246 }, 13247 13248 _destroyContainer: function () { 13249 remove(this._container); 13250 off(this._container); 13251 delete this._container; 13252 delete this._rootGroup; 13253 delete this._svgSize; 13254 }, 13255 13256 _update: function () { 13257 if (this._map._animatingZoom && this._bounds) { return; } 13258 13259 Renderer.prototype._update.call(this); 13260 13261 var b = this._bounds, 13262 size = b.getSize(), 13263 container = this._container; 13264 13265 // set size of svg-container if changed 13266 if (!this._svgSize || !this._svgSize.equals(size)) { 13267 this._svgSize = size; 13268 container.setAttribute('width', size.x); 13269 container.setAttribute('height', size.y); 13270 } 13271 13272 // movement: update container viewBox so that we don't have to change coordinates of individual layers 13273 setPosition(container, b.min); 13274 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 13275 13276 this.fire('update'); 13277 }, 13278 13279 // methods below are called by vector layers implementations 13280 13281 _initPath: function (layer) { 13282 var path = layer._path = create('path'); 13283 13284 // @namespace Path 13285 // @option className: String = null 13286 // Custom class name set on an element. Only for SVG renderer. 13287 if (layer.options.className) { 13288 addClass(path, layer.options.className); 13289 } 13290 13291 if (layer.options.interactive) { 13292 addClass(path, 'leaflet-interactive'); 13293 } 13294 13295 this._updateStyle(layer); 13296 this._layers[stamp(layer)] = layer; 13297 }, 13298 13299 _addPath: function (layer) { 13300 if (!this._rootGroup) { this._initContainer(); } 13301 this._rootGroup.appendChild(layer._path); 13302 layer.addInteractiveTarget(layer._path); 13303 }, 13304 13305 _removePath: function (layer) { 13306 remove(layer._path); 13307 layer.removeInteractiveTarget(layer._path); 13308 delete this._layers[stamp(layer)]; 13309 }, 13310 13311 _updatePath: function (layer) { 13312 layer._project(); 13313 layer._update(); 13314 }, 13315 13316 _updateStyle: function (layer) { 13317 var path = layer._path, 13318 options = layer.options; 13319 13320 if (!path) { return; } 13321 13322 if (options.stroke) { 13323 path.setAttribute('stroke', options.color); 13324 path.setAttribute('stroke-opacity', options.opacity); 13325 path.setAttribute('stroke-width', options.weight); 13326 path.setAttribute('stroke-linecap', options.lineCap); 13327 path.setAttribute('stroke-linejoin', options.lineJoin); 13328 13329 if (options.dashArray) { 13330 path.setAttribute('stroke-dasharray', options.dashArray); 13331 } else { 13332 path.removeAttribute('stroke-dasharray'); 13333 } 13334 13335 if (options.dashOffset) { 13336 path.setAttribute('stroke-dashoffset', options.dashOffset); 13337 } else { 13338 path.removeAttribute('stroke-dashoffset'); 13339 } 13340 } else { 13341 path.setAttribute('stroke', 'none'); 13342 } 13343 13344 if (options.fill) { 13345 path.setAttribute('fill', options.fillColor || options.color); 13346 path.setAttribute('fill-opacity', options.fillOpacity); 13347 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 13348 } else { 13349 path.setAttribute('fill', 'none'); 13350 } 13351 }, 13352 13353 _updatePoly: function (layer, closed) { 13354 this._setPath(layer, pointsToPath(layer._parts, closed)); 13355 }, 13356 13357 _updateCircle: function (layer) { 13358 var p = layer._point, 13359 r = Math.max(Math.round(layer._radius), 1), 13360 r2 = Math.max(Math.round(layer._radiusY), 1) || r, 13361 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 13362 13363 // drawing a circle with two half-arcs 13364 var d = layer._empty() ? 'M0 0' : 13365 'M' + (p.x - r) + ',' + p.y + 13366 arc + (r * 2) + ',0 ' + 13367 arc + (-r * 2) + ',0 '; 13368 13369 this._setPath(layer, d); 13370 }, 13371 13372 _setPath: function (layer, path) { 13373 layer._path.setAttribute('d', path); 13374 }, 13375 13376 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 13377 _bringToFront: function (layer) { 13378 toFront(layer._path); 13379 }, 13380 13381 _bringToBack: function (layer) { 13382 toBack(layer._path); 13383 } 13384 }); 13385 13386 if (Browser.vml) { 13387 SVG.include(vmlMixin); 13388 } 13389 13390 // @namespace SVG 13391 // @factory L.svg(options?: Renderer options) 13392 // Creates a SVG renderer with the given options. 13393 function svg(options) { 13394 return Browser.svg || Browser.vml ? new SVG(options) : null; 13395 } 13396 13397 Map.include({ 13398 // @namespace Map; @method getRenderer(layer: Path): Renderer 13399 // Returns the instance of `Renderer` that should be used to render the given 13400 // `Path`. It will ensure that the `renderer` options of the map and paths 13401 // are respected, and that the renderers do exist on the map. 13402 getRenderer: function (layer) { 13403 // @namespace Path; @option renderer: Renderer 13404 // Use this specific instance of `Renderer` for this path. Takes 13405 // precedence over the map's [default renderer](#map-renderer). 13406 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 13407 13408 if (!renderer) { 13409 renderer = this._renderer = this._createRenderer(); 13410 } 13411 13412 if (!this.hasLayer(renderer)) { 13413 this.addLayer(renderer); 13414 } 13415 return renderer; 13416 }, 13417 13418 _getPaneRenderer: function (name) { 13419 if (name === 'overlayPane' || name === undefined) { 13420 return false; 13421 } 13422 13423 var renderer = this._paneRenderers[name]; 13424 if (renderer === undefined) { 13425 renderer = this._createRenderer({pane: name}); 13426 this._paneRenderers[name] = renderer; 13427 } 13428 return renderer; 13429 }, 13430 13431 _createRenderer: function (options) { 13432 // @namespace Map; @option preferCanvas: Boolean = false 13433 // Whether `Path`s should be rendered on a `Canvas` renderer. 13434 // By default, all `Path`s are rendered in a `SVG` renderer. 13435 return (this.options.preferCanvas && canvas(options)) || svg(options); 13436 } 13437 }); 13438 13439 /* 13440 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. 13441 */ 13442 13443 /* 13444 * @class Rectangle 13445 * @aka L.Rectangle 13446 * @inherits Polygon 13447 * 13448 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 13449 * 13450 * @example 13451 * 13452 * ```js 13453 * // define rectangle geographical bounds 13454 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 13455 * 13456 * // create an orange rectangle 13457 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 13458 * 13459 * // zoom the map to the rectangle bounds 13460 * map.fitBounds(bounds); 13461 * ``` 13462 * 13463 */ 13464 13465 13466 var Rectangle = Polygon.extend({ 13467 initialize: function (latLngBounds, options) { 13468 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 13469 }, 13470 13471 // @method setBounds(latLngBounds: LatLngBounds): this 13472 // Redraws the rectangle with the passed bounds. 13473 setBounds: function (latLngBounds) { 13474 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 13475 }, 13476 13477 _boundsToLatLngs: function (latLngBounds) { 13478 latLngBounds = toLatLngBounds(latLngBounds); 13479 return [ 13480 latLngBounds.getSouthWest(), 13481 latLngBounds.getNorthWest(), 13482 latLngBounds.getNorthEast(), 13483 latLngBounds.getSouthEast() 13484 ]; 13485 } 13486 }); 13487 13488 13489 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 13490 function rectangle(latLngBounds, options) { 13491 return new Rectangle(latLngBounds, options); 13492 } 13493 13494 SVG.create = create; 13495 SVG.pointsToPath = pointsToPath; 13496 13497 GeoJSON.geometryToLayer = geometryToLayer; 13498 GeoJSON.coordsToLatLng = coordsToLatLng; 13499 GeoJSON.coordsToLatLngs = coordsToLatLngs; 13500 GeoJSON.latLngToCoords = latLngToCoords; 13501 GeoJSON.latLngsToCoords = latLngsToCoords; 13502 GeoJSON.getFeature = getFeature; 13503 GeoJSON.asFeature = asFeature; 13504 13505 /* 13506 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 13507 * (zoom to a selected bounding box), enabled by default. 13508 */ 13509 13510 // @namespace Map 13511 // @section Interaction Options 13512 Map.mergeOptions({ 13513 // @option boxZoom: Boolean = true 13514 // Whether the map can be zoomed to a rectangular area specified by 13515 // dragging the mouse while pressing the shift key. 13516 boxZoom: true 13517 }); 13518 13519 var BoxZoom = Handler.extend({ 13520 initialize: function (map) { 13521 this._map = map; 13522 this._container = map._container; 13523 this._pane = map._panes.overlayPane; 13524 this._resetStateTimeout = 0; 13525 map.on('unload', this._destroy, this); 13526 }, 13527 13528 addHooks: function () { 13529 on(this._container, 'mousedown', this._onMouseDown, this); 13530 }, 13531 13532 removeHooks: function () { 13533 off(this._container, 'mousedown', this._onMouseDown, this); 13534 }, 13535 13536 moved: function () { 13537 return this._moved; 13538 }, 13539 13540 _destroy: function () { 13541 remove(this._pane); 13542 delete this._pane; 13543 }, 13544 13545 _resetState: function () { 13546 this._resetStateTimeout = 0; 13547 this._moved = false; 13548 }, 13549 13550 _clearDeferredResetState: function () { 13551 if (this._resetStateTimeout !== 0) { 13552 clearTimeout(this._resetStateTimeout); 13553 this._resetStateTimeout = 0; 13554 } 13555 }, 13556 13557 _onMouseDown: function (e) { 13558 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 13559 13560 // Clear the deferred resetState if it hasn't executed yet, otherwise it 13561 // will interrupt the interaction and orphan a box element in the container. 13562 this._clearDeferredResetState(); 13563 this._resetState(); 13564 13565 disableTextSelection(); 13566 disableImageDrag(); 13567 13568 this._startPoint = this._map.mouseEventToContainerPoint(e); 13569 13570 on(document, { 13571 contextmenu: stop, 13572 mousemove: this._onMouseMove, 13573 mouseup: this._onMouseUp, 13574 keydown: this._onKeyDown 13575 }, this); 13576 }, 13577 13578 _onMouseMove: function (e) { 13579 if (!this._moved) { 13580 this._moved = true; 13581 13582 this._box = create$1('div', 'leaflet-zoom-box', this._container); 13583 addClass(this._container, 'leaflet-crosshair'); 13584 13585 this._map.fire('boxzoomstart'); 13586 } 13587 13588 this._point = this._map.mouseEventToContainerPoint(e); 13589 13590 var bounds = new Bounds(this._point, this._startPoint), 13591 size = bounds.getSize(); 13592 13593 setPosition(this._box, bounds.min); 13594 13595 this._box.style.width = size.x + 'px'; 13596 this._box.style.height = size.y + 'px'; 13597 }, 13598 13599 _finish: function () { 13600 if (this._moved) { 13601 remove(this._box); 13602 removeClass(this._container, 'leaflet-crosshair'); 13603 } 13604 13605 enableTextSelection(); 13606 enableImageDrag(); 13607 13608 off(document, { 13609 contextmenu: stop, 13610 mousemove: this._onMouseMove, 13611 mouseup: this._onMouseUp, 13612 keydown: this._onKeyDown 13613 }, this); 13614 }, 13615 13616 _onMouseUp: function (e) { 13617 if ((e.which !== 1) && (e.button !== 1)) { return; } 13618 13619 this._finish(); 13620 13621 if (!this._moved) { return; } 13622 // Postpone to next JS tick so internal click event handling 13623 // still see it as "moved". 13624 this._clearDeferredResetState(); 13625 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 13626 13627 var bounds = new LatLngBounds( 13628 this._map.containerPointToLatLng(this._startPoint), 13629 this._map.containerPointToLatLng(this._point)); 13630 13631 this._map 13632 .fitBounds(bounds) 13633 .fire('boxzoomend', {boxZoomBounds: bounds}); 13634 }, 13635 13636 _onKeyDown: function (e) { 13637 if (e.keyCode === 27) { 13638 this._finish(); 13639 this._clearDeferredResetState(); 13640 this._resetState(); 13641 } 13642 } 13643 }); 13644 13645 // @section Handlers 13646 // @property boxZoom: Handler 13647 // Box (shift-drag with mouse) zoom handler. 13648 Map.addInitHook('addHandler', 'boxZoom', BoxZoom); 13649 13650 /* 13651 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 13652 */ 13653 13654 // @namespace Map 13655 // @section Interaction Options 13656 13657 Map.mergeOptions({ 13658 // @option doubleClickZoom: Boolean|String = true 13659 // Whether the map can be zoomed in by double clicking on it and 13660 // zoomed out by double clicking while holding shift. If passed 13661 // `'center'`, double-click zoom will zoom to the center of the 13662 // view regardless of where the mouse was. 13663 doubleClickZoom: true 13664 }); 13665 13666 var DoubleClickZoom = Handler.extend({ 13667 addHooks: function () { 13668 this._map.on('dblclick', this._onDoubleClick, this); 13669 }, 13670 13671 removeHooks: function () { 13672 this._map.off('dblclick', this._onDoubleClick, this); 13673 }, 13674 13675 _onDoubleClick: function (e) { 13676 var map = this._map, 13677 oldZoom = map.getZoom(), 13678 delta = map.options.zoomDelta, 13679 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 13680 13681 if (map.options.doubleClickZoom === 'center') { 13682 map.setZoom(zoom); 13683 } else { 13684 map.setZoomAround(e.containerPoint, zoom); 13685 } 13686 } 13687 }); 13688 13689 // @section Handlers 13690 // 13691 // Map properties include interaction handlers that allow you to control 13692 // interaction behavior in runtime, enabling or disabling certain features such 13693 // as dragging or touch zoom (see `Handler` methods). For example: 13694 // 13695 // ```js 13696 // map.doubleClickZoom.disable(); 13697 // ``` 13698 // 13699 // @property doubleClickZoom: Handler 13700 // Double click zoom handler. 13701 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); 13702 13703 /* 13704 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 13705 */ 13706 13707 // @namespace Map 13708 // @section Interaction Options 13709 Map.mergeOptions({ 13710 // @option dragging: Boolean = true 13711 // Whether the map is draggable with mouse/touch or not. 13712 dragging: true, 13713 13714 // @section Panning Inertia Options 13715 // @option inertia: Boolean = * 13716 // If enabled, panning of the map will have an inertia effect where 13717 // the map builds momentum while dragging and continues moving in 13718 // the same direction for some time. Feels especially nice on touch 13719 // devices. Enabled by default. 13720 inertia: true, 13721 13722 // @option inertiaDeceleration: Number = 3000 13723 // The rate with which the inertial movement slows down, in pixels/second². 13724 inertiaDeceleration: 3400, // px/s^2 13725 13726 // @option inertiaMaxSpeed: Number = Infinity 13727 // Max speed of the inertial movement, in pixels/second. 13728 inertiaMaxSpeed: Infinity, // px/s 13729 13730 // @option easeLinearity: Number = 0.2 13731 easeLinearity: 0.2, 13732 13733 // TODO refactor, move to CRS 13734 // @option worldCopyJump: Boolean = false 13735 // With this option enabled, the map tracks when you pan to another "copy" 13736 // of the world and seamlessly jumps to the original one so that all overlays 13737 // like markers and vector layers are still visible. 13738 worldCopyJump: false, 13739 13740 // @option maxBoundsViscosity: Number = 0.0 13741 // If `maxBounds` is set, this option will control how solid the bounds 13742 // are when dragging the map around. The default value of `0.0` allows the 13743 // user to drag outside the bounds at normal speed, higher values will 13744 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 13745 // solid, preventing the user from dragging outside the bounds. 13746 maxBoundsViscosity: 0.0 13747 }); 13748 13749 var Drag = Handler.extend({ 13750 addHooks: function () { 13751 if (!this._draggable) { 13752 var map = this._map; 13753 13754 this._draggable = new Draggable(map._mapPane, map._container); 13755 13756 this._draggable.on({ 13757 dragstart: this._onDragStart, 13758 drag: this._onDrag, 13759 dragend: this._onDragEnd 13760 }, this); 13761 13762 this._draggable.on('predrag', this._onPreDragLimit, this); 13763 if (map.options.worldCopyJump) { 13764 this._draggable.on('predrag', this._onPreDragWrap, this); 13765 map.on('zoomend', this._onZoomEnd, this); 13766 13767 map.whenReady(this._onZoomEnd, this); 13768 } 13769 } 13770 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 13771 this._draggable.enable(); 13772 this._positions = []; 13773 this._times = []; 13774 }, 13775 13776 removeHooks: function () { 13777 removeClass(this._map._container, 'leaflet-grab'); 13778 removeClass(this._map._container, 'leaflet-touch-drag'); 13779 this._draggable.disable(); 13780 }, 13781 13782 moved: function () { 13783 return this._draggable && this._draggable._moved; 13784 }, 13785 13786 moving: function () { 13787 return this._draggable && this._draggable._moving; 13788 }, 13789 13790 _onDragStart: function () { 13791 var map = this._map; 13792 13793 map._stop(); 13794 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 13795 var bounds = toLatLngBounds(this._map.options.maxBounds); 13796 13797 this._offsetLimit = toBounds( 13798 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 13799 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 13800 .add(this._map.getSize())); 13801 13802 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 13803 } else { 13804 this._offsetLimit = null; 13805 } 13806 13807 map 13808 .fire('movestart') 13809 .fire('dragstart'); 13810 13811 if (map.options.inertia) { 13812 this._positions = []; 13813 this._times = []; 13814 } 13815 }, 13816 13817 _onDrag: function (e) { 13818 if (this._map.options.inertia) { 13819 var time = this._lastTime = +new Date(), 13820 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 13821 13822 this._positions.push(pos); 13823 this._times.push(time); 13824 13825 this._prunePositions(time); 13826 } 13827 13828 this._map 13829 .fire('move', e) 13830 .fire('drag', e); 13831 }, 13832 13833 _prunePositions: function (time) { 13834 while (this._positions.length > 1 && time - this._times[0] > 50) { 13835 this._positions.shift(); 13836 this._times.shift(); 13837 } 13838 }, 13839 13840 _onZoomEnd: function () { 13841 var pxCenter = this._map.getSize().divideBy(2), 13842 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 13843 13844 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 13845 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 13846 }, 13847 13848 _viscousLimit: function (value, threshold) { 13849 return value - (value - threshold) * this._viscosity; 13850 }, 13851 13852 _onPreDragLimit: function () { 13853 if (!this._viscosity || !this._offsetLimit) { return; } 13854 13855 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 13856 13857 var limit = this._offsetLimit; 13858 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 13859 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 13860 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 13861 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 13862 13863 this._draggable._newPos = this._draggable._startPos.add(offset); 13864 }, 13865 13866 _onPreDragWrap: function () { 13867 // TODO refactor to be able to adjust map pane position after zoom 13868 var worldWidth = this._worldWidth, 13869 halfWidth = Math.round(worldWidth / 2), 13870 dx = this._initialWorldOffset, 13871 x = this._draggable._newPos.x, 13872 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 13873 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 13874 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 13875 13876 this._draggable._absPos = this._draggable._newPos.clone(); 13877 this._draggable._newPos.x = newX; 13878 }, 13879 13880 _onDragEnd: function (e) { 13881 var map = this._map, 13882 options = map.options, 13883 13884 noInertia = !options.inertia || e.noInertia || this._times.length < 2; 13885 13886 map.fire('dragend', e); 13887 13888 if (noInertia) { 13889 map.fire('moveend'); 13890 13891 } else { 13892 this._prunePositions(+new Date()); 13893 13894 var direction = this._lastPos.subtract(this._positions[0]), 13895 duration = (this._lastTime - this._times[0]) / 1000, 13896 ease = options.easeLinearity, 13897 13898 speedVector = direction.multiplyBy(ease / duration), 13899 speed = speedVector.distanceTo([0, 0]), 13900 13901 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 13902 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 13903 13904 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 13905 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 13906 13907 if (!offset.x && !offset.y) { 13908 map.fire('moveend'); 13909 13910 } else { 13911 offset = map._limitOffset(offset, map.options.maxBounds); 13912 13913 requestAnimFrame(function () { 13914 map.panBy(offset, { 13915 duration: decelerationDuration, 13916 easeLinearity: ease, 13917 noMoveStart: true, 13918 animate: true 13919 }); 13920 }); 13921 } 13922 } 13923 } 13924 }); 13925 13926 // @section Handlers 13927 // @property dragging: Handler 13928 // Map dragging handler (by both mouse and touch). 13929 Map.addInitHook('addHandler', 'dragging', Drag); 13930 13931 /* 13932 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 13933 */ 13934 13935 // @namespace Map 13936 // @section Keyboard Navigation Options 13937 Map.mergeOptions({ 13938 // @option keyboard: Boolean = true 13939 // Makes the map focusable and allows users to navigate the map with keyboard 13940 // arrows and `+`/`-` keys. 13941 keyboard: true, 13942 13943 // @option keyboardPanDelta: Number = 80 13944 // Amount of pixels to pan when pressing an arrow key. 13945 keyboardPanDelta: 80 13946 }); 13947 13948 var Keyboard = Handler.extend({ 13949 13950 keyCodes: { 13951 left: [37], 13952 right: [39], 13953 down: [40], 13954 up: [38], 13955 zoomIn: [187, 107, 61, 171], 13956 zoomOut: [189, 109, 54, 173] 13957 }, 13958 13959 initialize: function (map) { 13960 this._map = map; 13961 13962 this._setPanDelta(map.options.keyboardPanDelta); 13963 this._setZoomDelta(map.options.zoomDelta); 13964 }, 13965 13966 addHooks: function () { 13967 var container = this._map._container; 13968 13969 // make the container focusable by tabbing 13970 if (container.tabIndex <= 0) { 13971 container.tabIndex = '0'; 13972 } 13973 13974 on(container, { 13975 focus: this._onFocus, 13976 blur: this._onBlur, 13977 mousedown: this._onMouseDown 13978 }, this); 13979 13980 this._map.on({ 13981 focus: this._addHooks, 13982 blur: this._removeHooks 13983 }, this); 13984 }, 13985 13986 removeHooks: function () { 13987 this._removeHooks(); 13988 13989 off(this._map._container, { 13990 focus: this._onFocus, 13991 blur: this._onBlur, 13992 mousedown: this._onMouseDown 13993 }, this); 13994 13995 this._map.off({ 13996 focus: this._addHooks, 13997 blur: this._removeHooks 13998 }, this); 13999 }, 14000 14001 _onMouseDown: function () { 14002 if (this._focused) { return; } 14003 14004 var body = document.body, 14005 docEl = document.documentElement, 14006 top = body.scrollTop || docEl.scrollTop, 14007 left = body.scrollLeft || docEl.scrollLeft; 14008 14009 this._map._container.focus(); 14010 14011 window.scrollTo(left, top); 14012 }, 14013 14014 _onFocus: function () { 14015 this._focused = true; 14016 this._map.fire('focus'); 14017 }, 14018 14019 _onBlur: function () { 14020 this._focused = false; 14021 this._map.fire('blur'); 14022 }, 14023 14024 _setPanDelta: function (panDelta) { 14025 var keys = this._panKeys = {}, 14026 codes = this.keyCodes, 14027 i, len; 14028 14029 for (i = 0, len = codes.left.length; i < len; i++) { 14030 keys[codes.left[i]] = [-1 * panDelta, 0]; 14031 } 14032 for (i = 0, len = codes.right.length; i < len; i++) { 14033 keys[codes.right[i]] = [panDelta, 0]; 14034 } 14035 for (i = 0, len = codes.down.length; i < len; i++) { 14036 keys[codes.down[i]] = [0, panDelta]; 14037 } 14038 for (i = 0, len = codes.up.length; i < len; i++) { 14039 keys[codes.up[i]] = [0, -1 * panDelta]; 14040 } 14041 }, 14042 14043 _setZoomDelta: function (zoomDelta) { 14044 var keys = this._zoomKeys = {}, 14045 codes = this.keyCodes, 14046 i, len; 14047 14048 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 14049 keys[codes.zoomIn[i]] = zoomDelta; 14050 } 14051 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 14052 keys[codes.zoomOut[i]] = -zoomDelta; 14053 } 14054 }, 14055 14056 _addHooks: function () { 14057 on(document, 'keydown', this._onKeyDown, this); 14058 }, 14059 14060 _removeHooks: function () { 14061 off(document, 'keydown', this._onKeyDown, this); 14062 }, 14063 14064 _onKeyDown: function (e) { 14065 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 14066 14067 var key = e.keyCode, 14068 map = this._map, 14069 offset; 14070 14071 if (key in this._panKeys) { 14072 if (!map._panAnim || !map._panAnim._inProgress) { 14073 offset = this._panKeys[key]; 14074 if (e.shiftKey) { 14075 offset = toPoint(offset).multiplyBy(3); 14076 } 14077 14078 if (map.options.maxBounds) { 14079 offset = map._limitOffset(toPoint(offset), map.options.maxBounds); 14080 } 14081 14082 if (map.options.worldCopyJump) { 14083 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset))); 14084 map.panTo(newLatLng); 14085 } else { 14086 map.panBy(offset); 14087 } 14088 } 14089 } else if (key in this._zoomKeys) { 14090 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 14091 14092 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) { 14093 map.closePopup(); 14094 14095 } else { 14096 return; 14097 } 14098 14099 stop(e); 14100 } 14101 }); 14102 14103 // @section Handlers 14104 // @section Handlers 14105 // @property keyboard: Handler 14106 // Keyboard navigation handler. 14107 Map.addInitHook('addHandler', 'keyboard', Keyboard); 14108 14109 /* 14110 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 14111 */ 14112 14113 // @namespace Map 14114 // @section Interaction Options 14115 Map.mergeOptions({ 14116 // @section Mouse wheel options 14117 // @option scrollWheelZoom: Boolean|String = true 14118 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 14119 // it will zoom to the center of the view regardless of where the mouse was. 14120 scrollWheelZoom: true, 14121 14122 // @option wheelDebounceTime: Number = 40 14123 // Limits the rate at which a wheel can fire (in milliseconds). By default 14124 // user can't zoom via wheel more often than once per 40 ms. 14125 wheelDebounceTime: 40, 14126 14127 // @option wheelPxPerZoomLevel: Number = 60 14128 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 14129 // mean a change of one full zoom level. Smaller values will make wheel-zooming 14130 // faster (and vice versa). 14131 wheelPxPerZoomLevel: 60 14132 }); 14133 14134 var ScrollWheelZoom = Handler.extend({ 14135 addHooks: function () { 14136 on(this._map._container, 'wheel', this._onWheelScroll, this); 14137 14138 this._delta = 0; 14139 }, 14140 14141 removeHooks: function () { 14142 off(this._map._container, 'wheel', this._onWheelScroll, this); 14143 }, 14144 14145 _onWheelScroll: function (e) { 14146 var delta = getWheelDelta(e); 14147 14148 var debounce = this._map.options.wheelDebounceTime; 14149 14150 this._delta += delta; 14151 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 14152 14153 if (!this._startTime) { 14154 this._startTime = +new Date(); 14155 } 14156 14157 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 14158 14159 clearTimeout(this._timer); 14160 this._timer = setTimeout(bind(this._performZoom, this), left); 14161 14162 stop(e); 14163 }, 14164 14165 _performZoom: function () { 14166 var map = this._map, 14167 zoom = map.getZoom(), 14168 snap = this._map.options.zoomSnap || 0; 14169 14170 map._stop(); // stop panning and fly animations if any 14171 14172 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 14173 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 14174 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 14175 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 14176 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 14177 14178 this._delta = 0; 14179 this._startTime = null; 14180 14181 if (!delta) { return; } 14182 14183 if (map.options.scrollWheelZoom === 'center') { 14184 map.setZoom(zoom + delta); 14185 } else { 14186 map.setZoomAround(this._lastMousePos, zoom + delta); 14187 } 14188 } 14189 }); 14190 14191 // @section Handlers 14192 // @property scrollWheelZoom: Handler 14193 // Scroll wheel zoom handler. 14194 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); 14195 14196 /* 14197 * L.Map.TapHold is used to simulate `contextmenu` event on long hold, 14198 * which otherwise is not fired by mobile Safari. 14199 */ 14200 14201 var tapHoldDelay = 600; 14202 14203 // @namespace Map 14204 // @section Interaction Options 14205 Map.mergeOptions({ 14206 // @section Touch interaction options 14207 // @option tapHold: Boolean 14208 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari. 14209 tapHold: Browser.touchNative && Browser.safari && Browser.mobile, 14210 14211 // @option tapTolerance: Number = 15 14212 // The max number of pixels a user can shift his finger during touch 14213 // for it to be considered a valid tap. 14214 tapTolerance: 15 14215 }); 14216 14217 var TapHold = Handler.extend({ 14218 addHooks: function () { 14219 on(this._map._container, 'touchstart', this._onDown, this); 14220 }, 14221 14222 removeHooks: function () { 14223 off(this._map._container, 'touchstart', this._onDown, this); 14224 }, 14225 14226 _onDown: function (e) { 14227 clearTimeout(this._holdTimeout); 14228 if (e.touches.length !== 1) { return; } 14229 14230 var first = e.touches[0]; 14231 this._startPos = this._newPos = new Point(first.clientX, first.clientY); 14232 14233 this._holdTimeout = setTimeout(bind(function () { 14234 this._cancel(); 14235 if (!this._isTapValid()) { return; } 14236 14237 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events 14238 on(document, 'touchend', preventDefault); 14239 on(document, 'touchend touchcancel', this._cancelClickPrevent); 14240 this._simulateEvent('contextmenu', first); 14241 }, this), tapHoldDelay); 14242 14243 on(document, 'touchend touchcancel contextmenu', this._cancel, this); 14244 on(document, 'touchmove', this._onMove, this); 14245 }, 14246 14247 _cancelClickPrevent: function cancelClickPrevent() { 14248 off(document, 'touchend', preventDefault); 14249 off(document, 'touchend touchcancel', cancelClickPrevent); 14250 }, 14251 14252 _cancel: function () { 14253 clearTimeout(this._holdTimeout); 14254 off(document, 'touchend touchcancel contextmenu', this._cancel, this); 14255 off(document, 'touchmove', this._onMove, this); 14256 }, 14257 14258 _onMove: function (e) { 14259 var first = e.touches[0]; 14260 this._newPos = new Point(first.clientX, first.clientY); 14261 }, 14262 14263 _isTapValid: function () { 14264 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 14265 }, 14266 14267 _simulateEvent: function (type, e) { 14268 var simulatedEvent = new MouseEvent(type, { 14269 bubbles: true, 14270 cancelable: true, 14271 view: window, 14272 // detail: 1, 14273 screenX: e.screenX, 14274 screenY: e.screenY, 14275 clientX: e.clientX, 14276 clientY: e.clientY, 14277 // button: 2, 14278 // buttons: 2 14279 }); 14280 14281 simulatedEvent._simulated = true; 14282 14283 e.target.dispatchEvent(simulatedEvent); 14284 } 14285 }); 14286 14287 // @section Handlers 14288 // @property tapHold: Handler 14289 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari). 14290 Map.addInitHook('addHandler', 'tapHold', TapHold); 14291 14292 /* 14293 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. 14294 */ 14295 14296 // @namespace Map 14297 // @section Interaction Options 14298 Map.mergeOptions({ 14299 // @section Touch interaction options 14300 // @option touchZoom: Boolean|String = * 14301 // Whether the map can be zoomed by touch-dragging with two fingers. If 14302 // passed `'center'`, it will zoom to the center of the view regardless of 14303 // where the touch events (fingers) were. Enabled for touch-capable web 14304 // browsers. 14305 touchZoom: Browser.touch, 14306 14307 // @option bounceAtZoomLimits: Boolean = true 14308 // Set it to false if you don't want the map to zoom beyond min/max zoom 14309 // and then bounce back when pinch-zooming. 14310 bounceAtZoomLimits: true 14311 }); 14312 14313 var TouchZoom = Handler.extend({ 14314 addHooks: function () { 14315 addClass(this._map._container, 'leaflet-touch-zoom'); 14316 on(this._map._container, 'touchstart', this._onTouchStart, this); 14317 }, 14318 14319 removeHooks: function () { 14320 removeClass(this._map._container, 'leaflet-touch-zoom'); 14321 off(this._map._container, 'touchstart', this._onTouchStart, this); 14322 }, 14323 14324 _onTouchStart: function (e) { 14325 var map = this._map; 14326 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 14327 14328 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 14329 p2 = map.mouseEventToContainerPoint(e.touches[1]); 14330 14331 this._centerPoint = map.getSize()._divideBy(2); 14332 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 14333 if (map.options.touchZoom !== 'center') { 14334 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 14335 } 14336 14337 this._startDist = p1.distanceTo(p2); 14338 this._startZoom = map.getZoom(); 14339 14340 this._moved = false; 14341 this._zooming = true; 14342 14343 map._stop(); 14344 14345 on(document, 'touchmove', this._onTouchMove, this); 14346 on(document, 'touchend touchcancel', this._onTouchEnd, this); 14347 14348 preventDefault(e); 14349 }, 14350 14351 _onTouchMove: function (e) { 14352 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 14353 14354 var map = this._map, 14355 p1 = map.mouseEventToContainerPoint(e.touches[0]), 14356 p2 = map.mouseEventToContainerPoint(e.touches[1]), 14357 scale = p1.distanceTo(p2) / this._startDist; 14358 14359 this._zoom = map.getScaleZoom(scale, this._startZoom); 14360 14361 if (!map.options.bounceAtZoomLimits && ( 14362 (this._zoom < map.getMinZoom() && scale < 1) || 14363 (this._zoom > map.getMaxZoom() && scale > 1))) { 14364 this._zoom = map._limitZoom(this._zoom); 14365 } 14366 14367 if (map.options.touchZoom === 'center') { 14368 this._center = this._startLatLng; 14369 if (scale === 1) { return; } 14370 } else { 14371 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 14372 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 14373 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 14374 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 14375 } 14376 14377 if (!this._moved) { 14378 map._moveStart(true, false); 14379 this._moved = true; 14380 } 14381 14382 cancelAnimFrame(this._animRequest); 14383 14384 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined); 14385 this._animRequest = requestAnimFrame(moveFn, this, true); 14386 14387 preventDefault(e); 14388 }, 14389 14390 _onTouchEnd: function () { 14391 if (!this._moved || !this._zooming) { 14392 this._zooming = false; 14393 return; 14394 } 14395 14396 this._zooming = false; 14397 cancelAnimFrame(this._animRequest); 14398 14399 off(document, 'touchmove', this._onTouchMove, this); 14400 off(document, 'touchend touchcancel', this._onTouchEnd, this); 14401 14402 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 14403 if (this._map.options.zoomAnimation) { 14404 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 14405 } else { 14406 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 14407 } 14408 } 14409 }); 14410 14411 // @section Handlers 14412 // @property touchZoom: Handler 14413 // Touch zoom handler. 14414 Map.addInitHook('addHandler', 'touchZoom', TouchZoom); 14415 14416 Map.BoxZoom = BoxZoom; 14417 Map.DoubleClickZoom = DoubleClickZoom; 14418 Map.Drag = Drag; 14419 Map.Keyboard = Keyboard; 14420 Map.ScrollWheelZoom = ScrollWheelZoom; 14421 Map.TapHold = TapHold; 14422 Map.TouchZoom = TouchZoom; 14423 14424 exports.Bounds = Bounds; 14425 exports.Browser = Browser; 14426 exports.CRS = CRS; 14427 exports.Canvas = Canvas; 14428 exports.Circle = Circle; 14429 exports.CircleMarker = CircleMarker; 14430 exports.Class = Class; 14431 exports.Control = Control; 14432 exports.DivIcon = DivIcon; 14433 exports.DivOverlay = DivOverlay; 14434 exports.DomEvent = DomEvent; 14435 exports.DomUtil = DomUtil; 14436 exports.Draggable = Draggable; 14437 exports.Evented = Evented; 14438 exports.FeatureGroup = FeatureGroup; 14439 exports.GeoJSON = GeoJSON; 14440 exports.GridLayer = GridLayer; 14441 exports.Handler = Handler; 14442 exports.Icon = Icon; 14443 exports.ImageOverlay = ImageOverlay; 14444 exports.LatLng = LatLng; 14445 exports.LatLngBounds = LatLngBounds; 14446 exports.Layer = Layer; 14447 exports.LayerGroup = LayerGroup; 14448 exports.LineUtil = LineUtil; 14449 exports.Map = Map; 14450 exports.Marker = Marker; 14451 exports.Mixin = Mixin; 14452 exports.Path = Path; 14453 exports.Point = Point; 14454 exports.PolyUtil = PolyUtil; 14455 exports.Polygon = Polygon; 14456 exports.Polyline = Polyline; 14457 exports.Popup = Popup; 14458 exports.PosAnimation = PosAnimation; 14459 exports.Projection = index; 14460 exports.Rectangle = Rectangle; 14461 exports.Renderer = Renderer; 14462 exports.SVG = SVG; 14463 exports.SVGOverlay = SVGOverlay; 14464 exports.TileLayer = TileLayer; 14465 exports.Tooltip = Tooltip; 14466 exports.Transformation = Transformation; 14467 exports.Util = Util; 14468 exports.VideoOverlay = VideoOverlay; 14469 exports.bind = bind; 14470 exports.bounds = toBounds; 14471 exports.canvas = canvas; 14472 exports.circle = circle; 14473 exports.circleMarker = circleMarker; 14474 exports.control = control; 14475 exports.divIcon = divIcon; 14476 exports.extend = extend; 14477 exports.featureGroup = featureGroup; 14478 exports.geoJSON = geoJSON; 14479 exports.geoJson = geoJson; 14480 exports.gridLayer = gridLayer; 14481 exports.icon = icon; 14482 exports.imageOverlay = imageOverlay; 14483 exports.latLng = toLatLng; 14484 exports.latLngBounds = toLatLngBounds; 14485 exports.layerGroup = layerGroup; 14486 exports.map = createMap; 14487 exports.marker = marker; 14488 exports.point = toPoint; 14489 exports.polygon = polygon; 14490 exports.polyline = polyline; 14491 exports.popup = popup; 14492 exports.rectangle = rectangle; 14493 exports.setOptions = setOptions; 14494 exports.stamp = stamp; 14495 exports.svg = svg; 14496 exports.svgOverlay = svgOverlay; 14497 exports.tileLayer = tileLayer; 14498 exports.tooltip = tooltip; 14499 exports.transformation = toTransformation; 14500 exports.version = version; 14501 exports.videoOverlay = videoOverlay; 14502 14503 var oldL = window.L; 14504 exports.noConflict = function() { 14505 window.L = oldL; 14506 return this; 14507 } 14508 // Always export us to window global (see #2364) 14509 window.L = exports; 14510 14511})); 14512//# sourceMappingURL=leaflet-src.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.