1/* 2 Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com 3 (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade 4*/ 5(function (window, document, undefined) { 6var L = { 7 version: "1.0.3" 8}; 9 10function expose() { 11 var oldL = window.L; 12 13 L.noConflict = function () { 14 window.L = oldL; 15 return this; 16 }; 17 18 window.L = L; 19} 20 21// define Leaflet for Node module pattern loaders, including Browserify 22if (typeof module === 'object' && typeof module.exports === 'object') { 23 module.exports = L; 24 25// define Leaflet as an AMD module 26} else if (typeof define === 'function' && define.amd) { 27 define(L); 28} 29 30// define Leaflet as a global L variable, saving the original L to restore later if needed 31if (typeof window !== 'undefined') { 32 expose(); 33} 34 35 36 37/* 38 * @namespace Util 39 * 40 * Various utility functions, used by Leaflet internally. 41 */ 42 43L.Util = { 44 45 // @function extend(dest: Object, src?: Object): Object 46 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 47 extend: function (dest) { 48 var i, j, len, src; 49 50 for (j = 1, len = arguments.length; j < len; j++) { 51 src = arguments[j]; 52 for (i in src) { 53 dest[i] = src[i];
54 } 55 } 56 return dest; 57 }, 58 59 // @function create(proto: Object, properties?: Object): Object 60 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 61 create: Object.create || (function () { 62 function F() {} 63 return function (proto) { 64 F.prototype = proto; 65 return new F(); 66 }; 67 })(), 68 69 // @function bind(fn: Function, â¦): Function 70 // 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). 71 // Has a `L.bind()` shortcut. 72 bind: function (fn, obj) { 73 var slice = Array.prototype.slice; 74 75 if (fn.bind) { 76 return fn.bind.apply(fn, slice.call(arguments, 1)); 77 } 78 79 var args = slice.call(arguments, 2); 80 81 return function () { 82 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 83 }; 84 }, 85 86 // @function stamp(obj: Object): Number 87 // Returns the unique ID of an object, assiging it one if it doesn't have it. 88 stamp: function (obj) { 89 /*eslint-disable */ 90 obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId; 91 return obj._leaflet_id; 92 /*eslint-enable */ 93 }, 94 95 // @property lastId: Number 96 // Last unique ID used by [`stamp()`](#util-stamp) 97 lastId: 0, 98
99 // @function throttle(fn: Function, time: Number, context: Object): Function 100 // Returns a function which executes function `fn` with the given scope `context` 101 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function 102 // `fn` will be called no more than one time per given amount of `time`. The arguments 103 // received by the bound function will be any arguments passed when binding the 104 // function, followed by any arguments passed when invoking the bound function. 105 // Has an `L.bind` shortcut. 106 throttle: function (fn, time, context) { 107 var lock, args, wrapperFn, later; 108 109 later = function () { 110 // reset lock and call if queued 111 lock = false; 112 if (args) { 113 wrapperFn.apply(context, args); 114 args = false; 115 } 116 }; 117 118 wrapperFn = function () { 119 if (lock) { 120 // called too soon, queue to call later 121 args = arguments; 122 123 } else { 124 // call and lock until later 125 fn.apply(context, arguments); 126 setTimeout(later, time); 127 lock = true; 128 } 129 }; 130 131 return wrapperFn; 132 }, 133 134 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number 135 // Returns the number `num` modulo `range` in such a way so it lies within 136 // `range[0]` and `range[1]`. The returned value will be always smaller than 137 // `range[1]` unless `includeMax` is set to `true`. 138 wrapNum: function (x, range, includeMax) { 139 var max = range[1], 140 min = range[0], 141 d = max - min; 142 return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 143 }, 144 145 // @function falseFn(): Function 146 // Returns a function which always returns `false`. 147 falseFn: function () { return false; }, 148 149 // @function formatNum(num: Number, digits?: Number): Number 150 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default. 151 formatNum: function (num, digits) { 152 var pow = Math.pow(10, digits || 5); 153 return Math.round(num * pow) / pow; 154 }, 155 156 // @function trim(str: String): String 157 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) 158 trim: function (str) { 159 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); 160 }, 161 162 // @function splitWords(str: String): String[] 163 // Trims and splits the string on whitespace and returns the array of parts. 164 splitWords: function (str) { 165 return L.Util.trim(str).split(/\s+/); 166 }, 167 168 // @function setOptions(obj: Object, options: Object): Object 169 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. 170 setOptions: function (obj, options) { 171 if (!obj.hasOwnProperty('options')) { 172 obj.options = obj.options ? L.Util.create(obj.options) : {}; 173 } 174 for (var i in options) { 175 obj.options[i] = options[i]; 176 } 177 return obj.options; 178 }, 179 180 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String 181 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` 182 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will 183 // be appended at the end. If `uppercase` is `true`, the parameter names will 184 // be uppercased (e.g. `'?A=foo&B=bar'`) 185 getParamString: function (obj, existingUrl, uppercase) { 186 var params = []; 187 for (var i in obj) { 188 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); 189 } 190 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); 191 }, 192 193 // @function template(str: String, data: Object): String 194 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` 195 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string 196 // `('Hello foo, bar')`. You can also specify functions instead of strings for 197 // data values â they will be evaluated passing `data` as an argument. 198 template: function (str, data) { 199 return str.replace(L.Util.templateRe, function (str, key) { 200 var value = data[key]; 201 202 if (value === undefined) { 203 throw new Error('No value provided for variable ' + str); 204 205 } else if (typeof value === 'function') { 206 value = value(data); 207 } 208 return value; 209 }); 210 }, 211 212 templateRe: /\{ *([\w_\-]+) *\}/g, 213 214 // @function isArray(obj): Boolean 215 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) 216 isArray: Array.isArray || function (obj) { 217 return (Object.prototype.toString.call(obj) === '[object Array]'); 218 }, 219 220 // @function indexOf(array: Array, el: Object): Number 221 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) 222 indexOf: function (array, el) { 223 for (var i = 0; i < array.length; i++) { 224 if (array[i] === el) { return i; } 225 } 226 return -1; 227 }, 228 229 // @property emptyImageUrl: String 230 // Data URI string containing a base64-encoded empty GIF image. 231 // Used as a hack to free memory from unused images on WebKit-powered 232 // mobile devices (by setting image `src` to this string). 233 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' 234}; 235 236(function () { 237 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 238 239 function getPrefixed(name) { 240 return window['webkit' + name] || window['moz' + name] || window['ms' + name]; 241 } 242 243 var lastTime = 0; 244 245 // fallback for IE 7-8 246 function timeoutDefer(fn) { 247 var time = +new Date(), 248 timeToCall = Math.max(0, 16 - (time - lastTime)); 249 250 lastTime = time + timeToCall; 251 return window.setTimeout(fn, timeToCall); 252 } 253 254 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255 cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || 256 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; 257 258 259 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number 260 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to 261 // `context` if given. When `immediate` is set, `fn` is called immediately if 262 // the browser doesn't have native support for 263 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), 264 // otherwise it's delayed. Returns a request ID that can be used to cancel the request. 265 L.Util.requestAnimFrame = function (fn, context, immediate) { 266 if (immediate && requestFn === timeoutDefer) { 267 fn.call(context); 268 } else { 269 return requestFn.call(window, L.bind(fn, context)); 270 } 271 }; 272 273 // @function cancelAnimFrame(id: Number): undefined 274 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). 275 L.Util.cancelAnimFrame = function (id) { 276 if (id) { 277 cancelFn.call(window, id); 278 } 279 }; 280})(); 281 282// shortcuts for most used utility functions 283L.extend = L.Util.extend; 284L.bind = L.Util.bind; 285L.stamp = L.Util.stamp; 286L.setOptions = L.Util.setOptions; 287 288 289 290 291// @class Class 292// @aka L.Class 293 294// @section 295// @uninheritable 296 297// Thanks to John Resig and Dean Edwards for inspiration! 298 299L.Class = function () {}; 300 301L.Class.extend = function (props) { 302 303 // @function extend(props: Object): Function 304 // [Extends the current class](#class-inheritance) given the properties to be included. 305 // Returns a Javascript function that is a class constructor (to be called with `new`). 306 var NewClass = function () { 307 308 // call the constructor 309 if (this.initialize) { 310 this.initialize.apply(this, arguments); 311 } 312 313 // call all constructor hooks 314 this.callInitHooks(); 315 }; 316 317 var parentProto = NewClass.__super__ = this.prototype; 318 319 var proto = L.Util.create(parentProto); 320 proto.constructor = NewClass; 321 322 NewClass.prototype = proto; 323 324 // inherit parent's statics 325 for (var i in this) { 326 if (this.hasOwnProperty(i) && i !== 'prototype') { 327 NewClass[i] = this[i]; 328 } 329 } 330 331 // mix static properties into the class 332 if (props.statics) { 333 L.extend(NewClass, props.statics); 334 delete props.statics; 335 } 336 337 // mix includes into the prototype 338 if (props.includes) { 339 L.Util.extend.apply(null, [proto].concat(props.includes)); 340 delete props.includes; 341 } 342 343 // merge options 344 if (proto.options) { 345 props.options = L.Util.extend(L.Util.create(proto.options), props.options); 346 } 347 348 // mix given properties into the prototype 349 L.extend(proto, props); 350 351 proto._initHooks = []; 352 353 // add method for calling all hooks 354 proto.callInitHooks = function () { 355 356 if (this._initHooksCalled) { return; } 357 358 if (parentProto.callInitHooks) { 359 parentProto.callInitHooks.call(this); 360 } 361 362 this._initHooksCalled = true; 363 364 for (var i = 0, len = proto._initHooks.length; i < len; i++) { 365 proto._initHooks[i].call(this); 366 } 367 }; 368 369 return NewClass; 370}; 371 372 373// @function include(properties: Object): this 374// [Includes a mixin](#class-includes) into the current class. 375L.Class.include = function (props) { 376 L.extend(this.prototype, props); 377 return this; 378}; 379 380// @function mergeOptions(options: Object): this 381// [Merges `options`](#class-options) into the defaults of the class. 382L.Class.mergeOptions = function (options) { 383 L.extend(this.prototype.options, options); 384 return this; 385}; 386 387// @function addInitHook(fn: Function): this 388// Adds a [constructor hook](#class-constructor-hooks) to the class. 389L.Class.addInitHook = function (fn) { // (Function) || (String, args...) 390 var args = Array.prototype.slice.call(arguments, 1); 391 392 var init = typeof fn === 'function' ? fn : function () { 393 this[fn].apply(this, args); 394 }; 395 396 this.prototype._initHooks = this.prototype._initHooks || []; 397 this.prototype._initHooks.push(init); 398 return this; 399};
400 401 402 403/* 404 * @class Evented 405 * @aka L.Evented 406 * @inherits Class 407 * 408 * 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). 409 * 410 * @example 411 * 412 * ```js 413 * map.on('click', function(e) { 414 * alert(e.latlng); 415 * } ); 416 * ``` 417 * 418 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 419 * 420 * ```js 421 * function onClick(e) { ... } 422 * 423 * map.on('click', onClick); 424 * map.off('click', onClick); 425 * ``` 426 */ 427 428 429L.Evented = L.Class.extend({ 430 431 /* @method on(type: String, fn: Function, context?: Object): this 432 * 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'`). 433 * 434 * @alternative 435 * @method on(eventMap: Object): this 436 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 437 */ 438 on: function (types, fn, context) { 439 440 // types can be a map of types/handlers 441 if (typeof types === 'object') { 442 for (var type in types) { 443 // we don't process space-separated events here for performance; 444 // it's a hot path since Layer uses the on(obj) syntax 445 this._on(type, types[type], fn); 446 } 447 448 } else { 449 // types can be a string of space-separated words 450 types = L.Util.splitWords(types); 451 452 for (var i = 0, len = types.length; i < len; i++) { 453 this._on(types[i], fn, context); 454 } 455 } 456 457 return this; 458 }, 459 460 /* @method off(type: String, fn?: Function, context?: Object): this 461 * 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. 462 * 463 * @alternative 464 * @method off(eventMap: Object): this 465 * Removes a set of type/listener pairs. 466 * 467 * @alternative 468 * @method off: this 469 * Removes all listeners to all events on the object. 470 */ 471 off: function (types, fn, context) { 472 473 if (!types) { 474 // clear all listeners if called without arguments 475 delete this._events; 476 477 } else if (typeof types === 'object') { 478 for (var type in types) { 479 this._off(type, types[type], fn); 480 } 481 482 } else { 483 types = L.Util.splitWords(types); 484 485 for (var i = 0, len = types.length; i < len; i++) { 486 this._off(types[i], fn, context); 487 } 488 } 489 490 return this; 491 }, 492 493 // attach listener (without syntactic sugar now) 494 _on: function (type, fn, context) { 495 this._events = this._events || {}; 496 497 /* get/init listeners for type */ 498 var typeListeners = this._events[type]; 499 if (!typeListeners) { 500 typeListeners = []; 501 this._events[type] = typeListeners; 502 } 503 504 if (context === this) { 505 // Less memory footprint.
506 context = undefined; 507 } 508 var newListener = {fn: fn, ctx: context}, 509 listeners = typeListeners; 510 511 // check if fn already there 512 for (var i = 0, len = listeners.length; i < len; i++) { 513 if (listeners[i].fn === fn && listeners[i].ctx === context) { 514 return; 515 } 516 } 517 518 listeners.push(newListener); 519 }, 520 521 _off: function (type, fn, context) { 522 var listeners, 523 i, 524 len; 525 526 if (!this._events) { return; } 527 528 listeners = this._events[type]; 529 530 if (!listeners) { 531 return; 532 } 533 534 if (!fn) { 535 // Set all removed listeners to noop so they are not called if remove happens in fire 536 for (i = 0, len = listeners.length; i < len; i++) { 537 listeners[i].fn = L.Util.falseFn; 538 } 539 // clear all listeners for a type if function isn't specified 540 delete this._events[type]; 541 return; 542 } 543 544 if (context === this) { 545 context = undefined; 546 } 547 548 if (listeners) { 549 550 // find fn and remove it 551 for (i = 0, len = listeners.length; i < len; i++) { 552 var l = listeners[i]; 553 if (l.ctx !== context) { continue; } 554 if (l.fn === fn) { 555 556 // set the removed listener to noop so that's not called if remove happens in fire 557 l.fn = L.Util.falseFn; 558 559 if (this._firingCount) { 560 /* copy array in case events are being fired */ 561 this._events[type] = listeners = listeners.slice(); 562 } 563 listeners.splice(i, 1); 564 565 return; 566 } 567 } 568 } 569 }, 570 571 // @method fire(type: String, data?: Object, propagate?: Boolean): this 572 // Fires an event of the specified type. You can optionally provide an data 573 // object â the first argument of the listener function will contain its 574 // properties. The event can optionally be propagated to event parents. 575 fire: function (type, data, propagate) { 576 if (!this.listens(type, propagate)) { return this; } 577 578 var event = L.Util.extend({}, data, {type: type, target: this}); 579 580 if (this._events) { 581 var listeners = this._events[type]; 582 583 if (listeners) { 584 this._firingCount = (this._firingCount + 1) || 1; 585 for (var i = 0, len = listeners.length; i < len; i++) { 586 var l = listeners[i]; 587 l.fn.call(l.ctx || this, event); 588 } 589 590 this._firingCount--; 591 } 592 } 593 594 if (propagate) { 595 // propagate the event to parents (set with addEventParent) 596 this._propagateEvent(event); 597 } 598 599 return this; 600 }, 601 602 // @method listens(type: String): Boolean 603 // Returns `true` if a particular event type has any listeners attached to it. 604 listens: function (type, propagate) { 605 var listeners = this._events && this._events[type]; 606 if (listeners && listeners.length) { return true; } 607 608 if (propagate) { 609 // also check parents for listeners if event propagates 610 for (var id in this._eventParents) { 611 if (this._eventParents[id].listens(type, propagate)) { return true; } 612 } 613 } 614 return false; 615 }, 616 617 // @method once(â¦): this 618 // Behaves as [`on(â¦)`](#evented-on), except the listener will only get fired once and then removed. 619 once: function (types, fn, context) { 620 621 if (typeof types === 'object') { 622 for (var type in types) { 623 this.once(type, types[type], fn); 624 } 625 return this; 626 } 627 628 var handler = L.bind(function () { 629 this 630 .off(types, fn, context) 631 .off(types, handler, context); 632 }, this); 633 634 // add a listener that's executed once and removed after that 635 return this 636 .on(types, fn, context) 637 .on(types, handler, context); 638 }, 639 640 // @method addEventParent(obj: Evented): this 641 // Adds an event parent - an `Evented` that will receive propagated events 642 addEventParent: function (obj) { 643 this._eventParents = this._eventParents || {}; 644 this._eventParents[L.stamp(obj)] = obj; 645 return this; 646 }, 647 648 // @method removeEventParent(obj: Evented): this 649 // Removes an event parent, so it will stop receiving propagated events 650 removeEventParent: function (obj) { 651 if (this._eventParents) { 652 delete this._eventParents[L.stamp(obj)]; 653 } 654 return this; 655 }, 656
657 _propagateEvent: function (e) { 658 for (var id in this._eventParents) { 659 this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true); 660 } 661 } 662}); 663 664var proto = L.Evented.prototype; 665 666// aliases; we should ditch those eventually 667 668// @method addEventListener(â¦): this 669// Alias to [`on(â¦)`](#evented-on) 670proto.addEventListener = proto.on; 671 672// @method removeEventListener(â¦): this 673// Alias to [`off(â¦)`](#evented-off) 674 675// @method clearAllEventListeners(â¦): this 676// Alias to [`off()`](#evented-off) 677proto.removeEventListener = proto.clearAllEventListeners = proto.off; 678 679// @method addOneTimeEventListener(â¦): this 680// Alias to [`once(â¦)`](#evented-once) 681proto.addOneTimeEventListener = proto.once; 682 683// @method fireEvent(â¦): this 684// Alias to [`fire(â¦)`](#evented-fire) 685proto.fireEvent = proto.fire; 686 687// @method hasEventListeners(â¦): Boolean 688// Alias to [`listens(â¦)`](#evented-listens) 689proto.hasEventListeners = proto.listens; 690 691L.Mixin = {Events: proto}; 692 693 694 695/* 696 * @namespace Browser 697 * @aka L.Browser 698 * 699 * A namespace with static properties for browser/feature detection used by Leaflet internally. 700 * 701 * @example 702 * 703 * ```js 704 * if (L.Browser.ielt9) { 705 * alert('Upgrade your browser, dude!'); 706 * } 707 * ``` 708 */ 709 710(function () { 711 712 var ua = navigator.userAgent.toLowerCase(), 713 doc = document.documentElement, 714 715 ie = 'ActiveXObject' in window, 716 717 webkit = ua.indexOf('webkit') !== -1, 718 phantomjs = ua.indexOf('phantom') !== -1, 719 android23 = ua.search('android [23]') !== -1, 720 chrome = ua.indexOf('chrome') !== -1, 721 gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie, 722 723 win = navigator.platform.indexOf('Win') === 0, 724 725 mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1, 726 msPointer = !window.PointerEvent && window.MSPointerEvent, 727 pointer = window.PointerEvent || msPointer, 728 729 ie3d = ie && ('transition' in doc.style), 730 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, 731 gecko3d = 'MozPerspective' in doc.style, 732 opera12 = 'OTransition' in doc.style; 733 734 735 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || 736 (window.DocumentTouch && document instanceof window.DocumentTouch)); 737 738 L.Browser = { 739 740 // @property ie: Boolean 741 // `true` for all Internet Explorer versions (not Edge). 742 ie: ie, 743 744 // @property ielt9: Boolean 745 // `true` for Internet Explorer versions less than 9. 746 ielt9: ie && !document.addEventListener, 747 748 // @property edge: Boolean 749 // `true` for the Edge web browser. 750 edge: 'msLaunchUri' in navigator && !('documentMode' in document), 751 752 // @property webkit: Boolean 753 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 754 webkit: webkit, 755 756 // @property gecko: Boolean 757 // `true` for gecko-based browsers like Firefox. 758 gecko: gecko, 759 760 // @property android: Boolean 761 // `true` for any browser running on an Android platform. 762 android: ua.indexOf('android') !== -1, 763 764 // @property android23: Boolean 765 // `true` for browsers running on Android 2 or Android 3. 766 android23: android23, 767 768 // @property chrome: Boolean 769 // `true` for the Chrome browser. 770 chrome: chrome, 771 772 // @property safari: Boolean 773 // `true` for the Safari browser. 774 safari: !chrome && ua.indexOf('safari') !== -1, 775 776 777 // @property win: Boolean 778 // `true` when the browser is running in a Windows platform 779 win: win, 780 781 782 // @property ie3d: Boolean 783 // `true` for all Internet Explorer versions supporting CSS transforms. 784 ie3d: ie3d, 785 786 // @property webkit3d: Boolean 787 // `true` for webkit-based browsers supporting CSS transforms. 788 webkit3d: webkit3d, 789 790 // @property gecko3d: Boolean 791 // `true` for gecko-based browsers supporting CSS transforms. 792 gecko3d: gecko3d, 793 794 // @property opera12: Boolean 795 // `true` for the Opera browser supporting CSS transforms (version 12 or later). 796 opera12: opera12, 797 798 // @property any3d: Boolean 799 // `true` for all browsers supporting CSS transforms. 800 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs, 801 802 803 // @property mobile: Boolean 804 // `true` for all browsers running in a mobile device. 805 mobile: mobile, 806 807 // @property mobileWebkit: Boolean 808 // `true` for all webkit-based browsers in a mobile device. 809 mobileWebkit: mobile && webkit, 810 811 // @property mobileWebkit3d: Boolean 812 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 813 mobileWebkit3d: mobile && webkit3d, 814 815 // @property mobileOpera: Boolean 816 // `true` for the Opera browser in a mobile device. 817 mobileOpera: mobile && window.opera, 818 819 // @property mobileGecko: Boolean 820 // `true` for gecko-based browsers running in a mobile device. 821 mobileGecko: mobile && gecko, 822 823 824 // @property touch: Boolean 825 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 826 // This does not necessarily mean that the browser is running in a computer with 827 // a touchscreen, it only means that the browser is capable of understanding 828 // touch events. 829 touch: !!touch, 830 831 // @property msPointer: Boolean 832 // `true` for browsers implementing the Microsoft touch events model (notably IE10). 833 msPointer: !!msPointer, 834 835 // @property pointer: Boolean 836 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us
vendor: 4,961 bytes, lines 836-1018
836/library/dn433244%28v=vs.85%29.aspx). 837 pointer: !!pointer, 838 839 840 // @property retina: Boolean 841 // `true` for browsers on a high-resolution "retina" screen. 842 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1 843 }; 844 845}()); 846 847 848 849/* 850 * @class Point 851 * @aka L.Point 852 * 853 * Represents a point with `x` and `y` coordinates in pixels. 854 * 855 * @example 856 * 857 * ```js 858 * var point = L.point(200, 300); 859 * ``` 860 * 861 * 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: 862 * 863 * ```js 864 * map.panBy([200, 300]); 865 * map.panBy(L.point(200, 300)); 866 * ``` 867 */ 868 869L.Point = function (x, y, round) { 870 // @property x: Number; The `x` coordinate of the point 871 this.x = (round ? Math.round(x) : x); 872 // @property y: Number; The `y` coordinate of the point 873 this.y = (round ? Math.round(y) : y); 874}; 875 876L.Point.prototype = { 877 878 // @method clone(): Point 879 // Returns a copy of the current point. 880 clone: function () { 881 return new L.Point(this.x, this.y); 882 }, 883 884 // @method add(otherPoint: Point): Point 885 // Returns the result of addition of the current and the given points. 886 add: function (point) { 887 // non-destructive, returns a new point 888 return this.clone()._add(L.point(point)); 889 }, 890 891 _add: function (point) { 892 // destructive, used directly for performance in situations where it's safe to modify existing point 893 this.x += point.x; 894 this.y += point.y; 895 return this; 896 }, 897 898 // @method subtract(otherPoint: Point): Point 899 // Returns the result of subtraction of the given point from the current. 900 subtract: function (point) { 901 return this.clone()._subtract(L.point(point)); 902 }, 903 904 _subtract: function (point) { 905 this.x -= point.x; 906 this.y -= point.y; 907 return this; 908 }, 909 910 // @method divideBy(num: Number): Point 911 // Returns the result of division of the current point by the given number. 912 divideBy: function (num) { 913 return this.clone()._divideBy(num); 914 }, 915 916 _divideBy: function (num) { 917 this.x /= num; 918 this.y /= num; 919 return this; 920 }, 921 922 // @method multiplyBy(num: Number): Point 923 // Returns the result of multiplication of the current point by the given number. 924 multiplyBy: function (num) { 925 return this.clone()._multiplyBy(num); 926 }, 927 928 _multiplyBy: function (num) { 929 this.x *= num; 930 this.y *= num; 931 return this; 932 }, 933 934 // @method scaleBy(scale: Point): Point 935 // Multiply each coordinate of the current point by each coordinate of 936 // `scale`. In linear algebra terms, multiply the point by the 937 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 938 // defined by `scale`. 939 scaleBy: function (point) { 940 return new L.Point(this.x * point.x, this.y * point.y); 941 }, 942 943 // @method unscaleBy(scale: Point): Point 944 // Inverse of `scaleBy`. Divide each coordinate of the current point by 945 // each coordinate of `scale`. 946 unscaleBy: function (point) { 947 return new L.Point(this.x / point.x, this.y / point.y); 948 }, 949 950 // @method round(): Point 951 // Returns a copy of the current point with rounded coordinates. 952 round: function () { 953 return this.clone()._round(); 954 }, 955 956 _round: function () { 957 this.x = Math.round(this.x); 958 this.y = Math.round(this.y); 959 return this; 960 }, 961 962 // @method floor(): Point 963 // Returns a copy of the current point with floored coordinates (rounded down). 964 floor: function () { 965 return this.clone()._floor(); 966 }, 967 968 _floor: function () { 969 this.x = Math.floor(this.x); 970 this.y = Math.floor(this.y); 971 return this; 972 }, 973 974 // @method ceil(): Point 975 // Returns a copy of the current point with ceiled coordinates (rounded up). 976 ceil: function () { 977 return this.clone()._ceil(); 978 }, 979 980 _ceil: function () { 981 this.x = Math.ceil(this.x); 982 this.y = Math.ceil(this.y); 983 return this; 984 }, 985 986 // @method distanceTo(otherPoint: Point): Number 987 // Returns the cartesian distance between the current and the given points. 988 distanceTo: function (point) { 989 point = L.point(point); 990 991 var x = point.x - this.x, 992 y = point.y - this.y; 993 994 return Math.sqrt(x * x + y * y); 995 }, 996 997 // @method equals(otherPoint: Point): Boolean 998 // Returns `true` if the given point has the same coordinates. 999 equals: function (point) { 1000 point = L.point(point); 1001 1002 return point.x === this.x && 1003 point.y === this.y; 1004 }, 1005 1006 // @method contains(otherPoint: Point): Boolean 1007 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 1008 contains: function (point) { 1009 point = L.point(point); 1010 1011 return Math.abs(point.x) <= Math.abs(this.x) && 1012 Math.abs(point.y) <= Math.abs(this.y); 1013 }, 1014 1015 // @method toString(): String 1016 // Returns a string representation of the point for debugging purposes. 1017 toString: function () { 1018 return 'Point(' +
1019 L.Util.formatNum(this.x) + ', ' + 1020 L.Util.formatNum(this.y) + ')'; 1021 } 1022}; 1023 1024// @factory L.point(x: Number, y: Number, round?: Boolean) 1025// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 1026 1027// @alternative 1028// @factory L.point(coords: Number[]) 1029// Expects an array of the form `[x, y]` instead. 1030 1031// @alternative 1032// @factory L.point(coords: Object) 1033// Expects a plain object of the form `{x: Number, y: Number}` instead. 1034L.point = function (x, y, round) { 1035 if (x instanceof L.Point) { 1036 return x; 1037 } 1038 if (L.Util.isArray(x)) { 1039 return new L.Point(x[0], x[1]); 1040 } 1041 if (x === undefined || x === null) { 1042 return x; 1043 } 1044 if (typeof x === 'object' && 'x' in x && 'y' in x) { 1045 return new L.Point(x.x, x.y); 1046 } 1047 return new L.Point(x, y, round); 1048}; 1049 1050 1051 1052/* 1053 * @class Bounds 1054 * @aka L.Bounds 1055 * 1056 * Represents a rectangular area in pixel coordinates. 1057 * 1058 * @example 1059 * 1060 * ```js 1061 * var p1 = L.point(10, 10), 1062 * p2 = L.point(40, 60), 1063 * bounds = L.bounds(p1, p2); 1064 * ``` 1065 * 1066 * 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: 1067 * 1068 * ```js 1069 * otherBounds.intersects([[10, 10], [40, 60]]); 1070 * ``` 1071 */ 1072 1073L.Bounds = function (a, b) { 1074 if (!a) { return; } 1075 1076 var points = b ? [a, b] : a; 1077 1078 for (var i = 0, len = points.length; i < len; i++) { 1079 this.extend(points[i]); 1080 } 1081}; 1082 1083L.Bounds.prototype = { 1084 // @method extend(point: Point): this 1085 // Extends the bounds to contain the given point. 1086 extend: function (point) { // (Point) 1087 point = L.point(point); 1088 1089 // @property min: Point 1090 // The top left corner of the rectangle. 1091 // @property max: Point 1092 // The bottom right corner of the rectangle. 1093 if (!this.min && !this.max) { 1094 this.min = point.clone(); 1095 this.max = point.clone(); 1096 } else { 1097 this.min.x = Math.min(point.x, this.min.x); 1098 this.max.x = Math.max(point.x, this.max.x); 1099 this.min.y = Math.min(point.y, this.min.y); 1100 this.max.y = Math.max(point.y, this.max.y); 1101 } 1102 return this; 1103 }, 1104 1105 // @method getCenter(round?: Boolean): Point 1106 // Returns the center point of the bounds. 1107 getCenter: function (round) { 1108 return new L.Point( 1109 (this.min.x + this.max.x) / 2, 1110 (this.min.y + this.max.y) / 2, round); 1111 }, 1112 1113 // @method getBottomLeft(): Point 1114 // Returns the bottom-left point of the bounds. 1115 getBottomLeft: function () { 1116 return new L.Point(this.min.x, this.max.y); 1117 }, 1118 1119 // @method getTopRight(): Point 1120 // Returns the top-right point of the bounds. 1121 getTopRight: function () { // -> Point 1122 return new L.Point(this.max.x, this.min.y); 1123 }, 1124 1125 // @method getSize(): Point 1126 // Returns the size of the given bounds 1127 getSize: function () { 1128 return this.max.subtract(this.min); 1129 }, 1130 1131 // @method contains(otherBounds: Bounds): Boolean 1132 // Returns `true` if the rectangle contains the given one. 1133 // @alternative 1134 // @method contains(point: Point): Boolean 1135 // Returns `true` if the rectangle contains the given point. 1136 contains: function (obj) { 1137 var min, max; 1138 1139 if (typeof obj[0] === 'number' || obj instanceof L.Point) { 1140 obj = L.point(obj); 1141 } else { 1142 obj = L.bounds(obj); 1143 } 1144 1145 if (obj instanceof L.Bounds) { 1146 min = obj.min; 1147 max = obj.max; 1148 } else { 1149 min = max = obj; 1150 } 1151 1152 return (min.x >= this.min.x) && 1153 (max.x <= this.max.x) && 1154 (min.y >= this.min.y) && 1155 (max.y <= this.max.y); 1156 }, 1157 1158 // @method intersects(otherBounds: Bounds): Boolean 1159 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1160 // intersect if they have at least one point in common. 1161 intersects: function (bounds) { // (Bounds) -> Boolean 1162 bounds = L.bounds(bounds); 1163 1164 var min = this.min, 1165 max = this.max, 1166 min2 = bounds.min, 1167 max2 = bounds.max, 1168 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1169 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1170 1171 return xIntersects && yIntersects; 1172 }, 1173 1174 // @method overlaps(otherBounds: Bounds): Boolean 1175 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1176 // overlap if their intersection is an area. 1177 overlaps: function (bounds) { // (Bounds) -> Boolean 1178 bounds = L.bounds(bounds); 1179 1180 var min = this.min, 1181 max = this.max, 1182 min2 = bounds.min, 1183 max2 = bounds.max, 1184 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1185 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1186 1187 return xOverlaps && yOverlaps; 1188 }, 1189 1190 isValid: function () { 1191 return !!(this.min && this.max); 1192 } 1193}; 1194 1195 1196// @factory L.bounds(topLeft: Point, bottomRight: Point) 1197// Creates a Bounds object from two coordinates (usually top-left and bottom-right corners). 1198// @alternative 1199// @factory L.bounds(points: Point[]) 1200// Creates a Bounds object from the points it contains 1201L.bounds = function (a, b) { 1202 if (!a || a instanceof L.Bounds) { 1203 return a; 1204 } 1205 return new L.Bounds(a, b); 1206};
1207 1208 1209 1210/* 1211 * @class Transformation 1212 * @aka L.Transformation 1213 * 1214 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1215 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1216 * the reverse. Used by Leaflet in its projections code. 1217 * 1218 * @example 1219 * 1220 * ```js 1221 * var transformation = new L.Transformation(2, 5, -1, 10), 1222 * p = L.point(1, 2), 1223 * p2 = transformation.transform(p), // L.point(7, 8) 1224 * p3 = transformation.untransform(p2); // L.point(1, 2) 1225 * ``` 1226 */ 1227 1228 1229// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1230// Creates a `Transformation` object with the given coefficients. 1231L.Transformation = function (a, b, c, d) { 1232 this._a = a; 1233 this._b = b; 1234 this._c = c; 1235 this._d = d; 1236}; 1237 1238L.Transformation.prototype = { 1239 // @method transform(point: Point, scale?: Number): Point 1240 // Returns a transformed point, optionally multiplied by the given scale. 1241 // Only accepts actual `L.Point` instances, not arrays. 1242 transform: function (point, scale) { // (Point, Number) -> Point 1243 return this._transform(point.clone(), scale); 1244 }, 1245 1246 // destructive transform (faster) 1247 _transform: function (point, scale) { 1248 scale = scale || 1; 1249 point.x = scale * (this._a * point.x + this._b); 1250 point.y = scale * (this._c * point.y + this._d); 1251 return point; 1252 }, 1253 1254 // @method untransform(point: Point, scale?: Number): Point 1255 // Returns the reverse transformation of the given point, optionally divided 1256 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1257 untransform: function (point, scale) { 1258 scale = scale || 1; 1259 return new L.Point( 1260 (point.x / scale - this._b) / this._a, 1261 (point.y / scale - this._d) / this._c); 1262 } 1263}; 1264 1265 1266 1267/* 1268 * @namespace DomUtil 1269 * 1270 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 1271 * tree, used by Leaflet internally. 1272 * 1273 * Most functions expecting or returning a `HTMLElement` also work for 1274 * SVG elements. The only difference is that classes refer to CSS classes 1275 * in HTML and SVG classes in SVG. 1276 */ 1277 1278L.DomUtil = { 1279 1280 // @function get(id: String|HTMLElement): HTMLElement 1281 // Returns an element given its DOM id, or returns the element itself 1282 // if it was passed directly. 1283 get: function (id) { 1284 return typeof id === 'string' ? document.getElementById(id) : id; 1285 }, 1286 1287 // @function getStyle(el: HTMLElement, styleAttrib: String): String 1288 // Returns the value for a certain style attribute on an element, 1289 // including computed values or values set through CSS. 1290 getStyle: function (el, style) { 1291 1292 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 1293 1294 if ((!value || value === 'auto') && document.defaultView) { 1295 var css = document.defaultView.getComputedStyle(el, null); 1296 value = css ? css[style] : null; 1297 } 1298 1299 return value === 'auto' ? null : value; 1300 }, 1301 1302 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 1303 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 1304 create: function (tagName, className, container) { 1305 1306 var el = document.createElement(tagName); 1307 el.className = className || ''; 1308 1309 if (container) { 1310 container.appendChild(el); 1311 } 1312 1313 return el; 1314 }, 1315 1316 // @function remove(el: HTMLElement) 1317 // Removes `el` from its parent element 1318 remove: function (el) { 1319 var parent = el.parentNode; 1320 if (parent) { 1321 parent.removeChild(el); 1322 } 1323 }, 1324 1325 // @function empty(el: HTMLElement) 1326 // Removes all of `el`'s children elements from `el` 1327 empty: function (el) { 1328 while (el.firstChild) { 1329 el.removeChild(el.firstChild); 1330 } 1331 }, 1332 1333 // @function toFront(el: HTMLElement) 1334 // Makes `el` the last children of its parent, so it renders in front of the other children. 1335 toFront: function (el) { 1336 el.parentNode.appendChild(el); 1337 }, 1338 1339 // @function toBack(el: HTMLElement) 1340 // Makes `el` the first children of its parent, so it renders back from the other children. 1341 toBack: function (el) { 1342 var parent = el.parentNode; 1343 parent.insertBefore(el, parent.firstChild); 1344 }, 1345 1346 // @function hasClass(el: HTMLElement, name: String): Boolean 1347 // Returns `true` if the element's class attribute contains `name`. 1348 hasClass: function (el, name) { 1349 if (el.classList !== undefined) { 1350 return el.classList.contains(name); 1351 } 1352 var className = L.DomUtil.getClass(el); 1353 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 1354 }, 1355 1356 // @function addClass(el: HTMLElement, name: String) 1357 // Adds `name` to the element's class attribute. 1358 addClass: function (el, name) { 1359 if (el.classList !== undefined) { 1360 var classes = L.Util.splitWords(name); 1361 for (var i = 0, len = classes.length; i < len; i++) { 1362 el.classList.add(classes[i]); 1363 } 1364 } else if (!L.DomUtil.hasClass(el, name)) { 1365 var className = L.DomUtil.getClass(el); 1366 L.DomUtil.setClass(el, (className ? className + ' ' : '') + name); 1367 } 1368 }, 1369 1370 // @function removeClass(el: HTMLElement, name: String) 1371 // Removes `name` from the element's class attribute. 1372 removeClass: function (el, name) { 1373 if (el.classList !== undefined) { 1374 el.classList.remove(name); 1375 } else { 1376 L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 1377 } 1378 }, 1379 1380 // @function setClass(el: HTMLElement, name: String) 1381 // Sets the element's class. 1382 setClass: function (el, name) { 1383 if (el.className.baseVal === undefined) { 1384 el.className = name; 1385 } else { 1386 // in case of SVG element 1387 el.className.baseVal = name; 1388 } 1389 }, 1390 1391 // @function getClass(el: HTMLElement): String 1392 // Returns the element's class. 1393 getClass: function (el) { 1394 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 1395 }, 1396 1397 // @function setOpacity(el: HTMLElement, opacity: Number) 1398 // Set the opacity of an element (including old IE support). 1399 // `opacity` must be a number from `0` to `1`. 1400 setOpacity: function (el, value) { 1401 1402 if ('opacity' in el.style) { 1403 el.style.opacity = value; 1404 1405 } else if ('filter' in el.style) { 1406 L.DomUtil._setOpacityIE(el, value); 1407 } 1408 }, 1409 1410 _setOpacityIE: function (el, value) { 1411 var filter = false, 1412 filterName = 'DXImageTransform.Microsoft.Alpha'; 1413
1414 // filters collection throws an error if we try to retrieve a filter that doesn't exist 1415 try { 1416 filter = el.filters.item(filterName); 1417 } catch (e) { 1418 // don't set opacity to 1 if we haven't already set an opacity, 1419 // it isn't needed and breaks transparent pngs. 1420 if (value === 1) { return; } 1421 } 1422 1423 value = Math.round(value * 100); 1424 1425 if (filter) { 1426 filter.Enabled = (value !== 100); 1427 filter.Opacity = value; 1428 } else { 1429 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 1430 } 1431 }, 1432 1433 // @function testProp(props: String[]): String|false 1434 // Goes through the array of style names and returns the first name 1435 // that is a valid style name for an element. If no such name is found, 1436 // it returns false. Useful for vendor-prefixed styles like `transform`. 1437 testProp: function (props) { 1438 1439 var style = document.documentElement.style; 1440 1441 for (var i = 0; i < props.length; i++) { 1442 if (props[i] in style) { 1443 return props[i]; 1444 } 1445 } 1446 return false; 1447 }, 1448 1449 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 1450 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 1451 // and optionally scaled by `scale`. Does not have an effect if the 1452 // browser doesn't support 3D CSS transforms. 1453 setTransform: function (el, offset, scale) { 1454 var pos = offset || new L.Point(0, 0); 1455 1456 el.style[L.DomUtil.TRANSFORM] = 1457 (L.Browser.ie3d ? 1458 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 1459 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 1460 (scale ? ' scale(' + scale + ')' : ''); 1461 }, 1462 1463 // @function setPosition(el: HTMLElement, position: Point) 1464 // Sets the position of `el` to coordinates specified by `position`, 1465 // using CSS translate or top/left positioning depending on the browser 1466 // (used by Leaflet internally to position its layers). 1467 setPosition: function (el, point) { // (HTMLElement, Point[, Boolean]) 1468 1469 /*eslint-disable */ 1470 el._leaflet_pos = point; 1471 /*eslint-enable */ 1472 1473 if (L.Browser.any3d) { 1474 L.DomUtil.setTransform(el, point); 1475 } else { 1476 el.style.left = point.x + 'px'; 1477 el.style.top = point.y + 'px'; 1478 } 1479 }, 1480 1481 // @function getPosition(el: HTMLElement): Point 1482 // Returns the coordinates of an element previously positioned with setPosition. 1483 getPosition: function (el) { 1484 // this method is only used for elements previously positioned using setPosition, 1485 // so it's safe to cache the position for performance 1486 1487 return el._leaflet_pos || new L.Point(0, 0); 1488 } 1489}; 1490 1491 1492(function () { 1493 // prefix style property names 1494 1495 // @property TRANSFORM: String 1496 // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit). 1497 L.DomUtil.TRANSFORM = L.DomUtil.testProp( 1498 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 1499 1500 1501 // webkitTransition comes first because some browser versions that drop vendor prefix don't do 1502 // the same for the transitionend event, in particular the Android 4.1 stock browser 1503 1504 // @property TRANSITION: String 1505 // Vendor-prefixed transform style name. 1506 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp( 1507 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 1508 1509 L.DomUtil.TRANSITION_END = 1510 transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend'; 1511 1512 // @function disableTextSelection() 1513 // Prevents the user from generating `selectstart` DOM events, usually generated 1514 // when the user drags the mouse through a page with text. Used internally 1515 // by Leaflet to override the behaviour of any click-and-drag interaction on 1516 // the map. Affects drag interactions on the whole document. 1517 1518 // @function enableTextSelection() 1519 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 1520 if ('onselectstart' in document) { 1521 L.DomUtil.disableTextSelection = function () { 1522 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); 1523 }; 1524 L.DomUtil.enableTextSelection = function () { 1525 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); 1526 }; 1527 1528 } else { 1529 var userSelectProperty = L.DomUtil.testProp( 1530 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 1531 1532 L.DomUtil.disableTextSelection = function () { 1533 if (userSelectProperty) { 1534 var style = document.documentElement.style; 1535 this._userSelect = style[userSelectProperty]; 1536 style[userSelectProperty] = 'none'; 1537 } 1538 }; 1539 L.DomUtil.enableTextSelection = function () { 1540 if (userSelectProperty) { 1541 document.documentElement.style[userSelectProperty] = this._userSelect; 1542 delete this._userSelect; 1543 } 1544 }; 1545 } 1546 1547 // @function disableImageDrag() 1548 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1549 // for `dragstart` DOM events, usually generated when the user drags an image. 1550 L.DomUtil.disableImageDrag = function () { 1551 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); 1552 }; 1553 1554 // @function enableImageDrag() 1555 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 1556 L.DomUtil.enableImageDrag = function () { 1557 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); 1558 }; 1559 1560 // @function preventOutline(el: HTMLElement) 1561 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 1562 // of the element `el` invisible. Used internally by Leaflet to prevent 1563 // focusable elements from displaying an outline when the user performs a 1564 // drag interaction on them. 1565 L.DomUtil.preventOutline = function (element) { 1566 while (element.tabIndex === -1) { 1567 element = element.parentNode; 1568 } 1569 if (!element || !element.style) { return; } 1570 L.DomUtil.restoreOutline(); 1571 this._outlineElement = element; 1572 this._outlineStyle = element.style.outline; 1573 element.style.outline = 'none'; 1574 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this); 1575 }; 1576 1577 // @function restoreOutline() 1578 // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 1579 L.DomUtil.restoreOutline = function () { 1580 if (!this._outlineElement) { return; } 1581 this._outlineElement.style.outline = this._outlineStyle; 1582 delete this._outlineElement; 1583 delete this._outlineStyle; 1584 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this); 1585 }; 1586})(); 1587 1588 1589 1590/* @class LatLng 1591 * @aka L.LatLng 1592 * 1593 * Represents a geographical point with a certain latitude and longitude. 1594 * 1595 * @example 1596 * 1597 * ``` 1598 * var latlng = L.latLng(50.5, 30.5); 1599 * ``` 1600 * 1601 * 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: 1602 * 1603 * ``` 1604 * map.panTo([50, 30]); 1605 * map.panTo({lon: 30, lat: 50}); 1606 * map.panTo({lat: 50, lng: 30}); 1607 * map.panTo(L.latLng(50, 30)); 1608 * ``` 1609 */ 1610 1611L.LatLng = function (lat, lng, alt) { 1612 if (isNaN(lat) || isNaN(lng)) { 1613 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1614 } 1615 1616 // @property lat: Number 1617 // Latitude in degrees 1618 this.lat = +lat; 1619 1620 // @property lng: Number 1621 // Longitude in degrees 1622 this.lng = +lng; 1623 1624 // @property alt: Number 1625 // Altitude in meters (optional) 1626 if (alt !== undefined) { 1627 this.alt = +alt; 1628 } 1629}; 1630 1631L.LatLng.prototype = { 1632 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1633 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number. 1634 equals: function (obj, maxMargin) { 1635 if (!obj) { return false; } 1636 1637 obj = L.latLng(obj); 1638 1639 var margin = Math.max( 1640 Math.abs(this.lat - obj.lat), 1641 Math.abs(this.lng - obj.lng)); 1642 1643 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1644 }, 1645 1646 // @method toString(): String 1647 // Returns a string representation of the point (for debugging purposes). 1648 toString: function (precision) { 1649 return 'LatLng(' + 1650 L.Util.formatNum(this.lat, precision) + ', ' + 1651 L.Util.formatNum(this.lng, precision) + ')'; 1652 }, 1653 1654 // @method distanceTo(otherLatLng: LatLng): Number 1655 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula). 1656 distanceTo: function (other) { 1657 return L.CRS.Earth.distance(this, L.latLng(other)); 1658 }, 1659 1660 // @method wrap(): LatLng 1661 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1662 wrap: function () { 1663 return L.CRS.Earth.wrapLatLng(this); 1664 }, 1665 1666 // @method toBounds(sizeInMeters: Number): LatLngBounds 1667 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1668 toBounds: function (sizeInMeters) { 1669 var latAccuracy = 180 * sizeInMeters / 40075017, 1670 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1671 1672 return L.latLngBounds( 1673 [this.lat - latAccuracy, this.lng - lngAccuracy], 1674 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1675 }, 1676 1677 clone: function () { 1678 return new L.LatLng(this.lat, this.lng, this.alt); 1679 } 1680}; 1681 1682 1683 1684// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1685// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1686 1687// @alternative 1688// @factory L.latLng(coords: Array): LatLng 1689// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1690 1691// @alternative 1692// @factory L.latLng(coords: Object): LatLng 1693// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1694 1695L.latLng = function (a, b, c) { 1696 if (a instanceof L.LatLng) { 1697 return a; 1698 } 1699 if (L.Util.isArray(a) && typeof a[0] !== 'object') { 1700 if (a.length === 3) { 1701 return new L.LatLng(a[0], a[1], a[2]); 1702 } 1703 if (a.length === 2) { 1704 return new L.LatLng(a[0], a[1]); 1705 } 1706 return null; 1707 } 1708 if (a === undefined || a === null) { 1709 return a; 1710 } 1711 if (typeof a === 'object' && 'lat' in a) { 1712 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1713 } 1714 if (b === undefined) { 1715 return null; 1716 } 1717 return new L.LatLng(a, b, c); 1718}; 1719 1720 1721 1722/* 1723 * @class LatLngBounds 1724 * @aka L.LatLngBounds 1725 * 1726 * Represents a rectangular geographical area on a map. 1727 * 1728 * @example 1729 * 1730 * ```js 1731 * var corner1 = L.latLng(40.712, -74.227), 1732 * corner2 = L.latLng(40.774, -74.125), 1733 * bounds = L.latLngBounds(corner1, corner2); 1734 * ``` 1735 * 1736 * 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: 1737 * 1738 * ```js 1739 * map.fitBounds([ 1740 * [40.712, -74.227], 1741 * [40.774, -74.125] 1742 * ]); 1743 * ``` 1744 * 1745 * 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. 1746 */ 1747 1748L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1749 if (!corner1) { return; } 1750 1751 var latlngs = corner2 ? [corner1, corner2] : corner1; 1752 1753 for (var i = 0, len = latlngs.length; i < len; i++) { 1754 this.extend(latlngs[i]); 1755 } 1756}; 1757 1758L.LatLngBounds.prototype = { 1759 1760 // @method extend(latlng: LatLng): this 1761 // Extend the bounds to contain the given point 1762 1763 // @alternative 1764 // @method extend(otherBounds: LatLngBounds): this 1765 // Extend the bounds to contain the given bounds 1766 extend: function (obj) { 1767 var sw = this._southWest, 1768 ne = this._northEast, 1769 sw2, ne2; 1770 1771 if (obj instanceof L.LatLng) { 1772 sw2 = obj; 1773 ne2 = obj; 1774 1775 } else if (obj instanceof L.LatLngBounds) { 1776 sw2 = obj._southWest; 1777 ne2 = obj._northEast; 1778 1779 if (!sw2 || !ne2) { return this; } 1780 1781 } else { 1782 return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this; 1783 } 1784 1785 if (!sw && !ne) { 1786 this._southWest = new L.LatLng(sw2.lat, sw2.lng); 1787 this._northEast = new L.LatLng(ne2.lat, ne2.lng); 1788 } else { 1789 sw.lat = Math.min(sw2.lat, sw.lat); 1790 sw.lng = Math.min(sw2.lng, sw.lng); 1791 ne.lat = Math.max(ne2.lat, ne.lat); 1792 ne.lng = Math.max(ne2.lng, ne.lng); 1793 } 1794 1795 return this; 1796 }, 1797 1798 // @method pad(bufferRatio: Number): LatLngBounds 1799 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction. 1800 pad: function (bufferRatio) { 1801 var sw = this._southWest, 1802 ne = this._northEast, 1803 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1804 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1805 1806 return new L.LatLngBounds(
1807 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1808 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1809 }, 1810 1811 // @method getCenter(): LatLng 1812 // Returns the center point of the bounds. 1813 getCenter: function () { 1814 return new L.LatLng( 1815 (this._southWest.lat + this._northEast.lat) / 2, 1816 (this._southWest.lng + this._northEast.lng) / 2); 1817 }, 1818 1819 // @method getSouthWest(): LatLng 1820 // Returns the south-west point of the bounds. 1821 getSouthWest: function () { 1822 return this._southWest; 1823 }, 1824 1825 // @method getNorthEast(): LatLng 1826 // Returns the north-east point of the bounds. 1827 getNorthEast: function () { 1828 return this._northEast; 1829 }, 1830 1831 // @method getNorthWest(): LatLng 1832 // Returns the north-west point of the bounds. 1833 getNorthWest: function () { 1834 return new L.LatLng(this.getNorth(), this.getWest()); 1835 }, 1836 1837 // @method getSouthEast(): LatLng 1838 // Returns the south-east point of the bounds. 1839 getSouthEast: function () { 1840 return new L.LatLng(this.getSouth(), this.getEast()); 1841 }, 1842 1843 // @method getWest(): Number 1844 // Returns the west longitude of the bounds 1845 getWest: function () { 1846 return this._southWest.lng; 1847 }, 1848 1849 // @method getSouth(): Number 1850 // Returns the south latitude of the bounds 1851 getSouth: function () { 1852 return this._southWest.lat; 1853 }, 1854 1855 // @method getEast(): Number 1856 // Returns the east longitude of the bounds 1857 getEast: function () { 1858 return this._northEast.lng; 1859 }, 1860 1861 // @method getNorth(): Number 1862 // Returns the north latitude of the bounds 1863 getNorth: function () { 1864 return this._northEast.lat; 1865 }, 1866 1867 // @method contains(otherBounds: LatLngBounds): Boolean 1868 // Returns `true` if the rectangle contains the given one. 1869 1870 // @alternative 1871 // @method contains (latlng: LatLng): Boolean 1872 // Returns `true` if the rectangle contains the given point. 1873 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1874 if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) { 1875 obj = L.latLng(obj); 1876 } else { 1877 obj = L.latLngBounds(obj); 1878 } 1879 1880 var sw = this._southWest, 1881 ne = this._northEast, 1882 sw2, ne2; 1883 1884 if (obj instanceof L.LatLngBounds) {
vendor: 1,093 bytes, lines 1885-1917
1885 sw2 = obj.getSouthWest(); 1886 ne2 = obj.getNorthEast(); 1887 } else { 1888 sw2 = ne2 = obj; 1889 } 1890 1891 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1892 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1893 }, 1894 1895 // @method intersects(otherBounds: LatLngBounds): Boolean 1896 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1897 intersects: function (bounds) { 1898 bounds = L.latLngBounds(bounds); 1899 1900 var sw = this._southWest, 1901 ne = this._northEast, 1902 sw2 = bounds.getSouthWest(), 1903 ne2 = bounds.getNorthEast(), 1904 1905 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1906 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1907 1908 return latIntersects && lngIntersects; 1909 }, 1910 1911 // @method overlaps(otherBounds: Bounds): Boolean 1912 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1913 overlaps: function (bounds) { 1914 bounds = L.latLngBounds(bounds); 1915 1916 var sw = this._southWest, 1917 ne = this._northEast,
1918 sw2 = bounds.getSouthWest(), 1919 ne2 = bounds.getNorthEast(), 1920 1921 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1922 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1923 1924 return latOverlaps && lngOverlaps; 1925 }, 1926 1927 // @method toBBoxString(): String 1928 // 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. 1929 toBBoxString: function () { 1930 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1931 }, 1932 1933 // @method equals(otherBounds: LatLngBounds): Boolean 1934 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. 1935 equals: function (bounds) { 1936 if (!bounds) { return false; } 1937 1938 bounds = L.latLngBounds(bounds); 1939 1940 return this._southWest.equals(bounds.getSouthWest()) && 1941 this._northEast.equals(bounds.getNorthEast()); 1942 }, 1943 1944 // @method isValid(): Boolean 1945 // Returns `true` if the bounds are properly initialized. 1946 isValid: function () { 1947 return !!(this._southWest && this._northEast); 1948 } 1949}; 1950 1951// TODO International date line? 1952 1953// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1954// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1955 1956// @alternative 1957// @factory L.latLngBounds(latlngs: LatLng[]) 1958// 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). 1959L.latLngBounds = function (a, b) { 1960 if (a instanceof L.LatLngBounds) { 1961 return a; 1962 } 1963 return new L.LatLngBounds(a, b); 1964}; 1965 1966 1967 1968/* 1969 * @namespace Projection 1970 * @section 1971 * Leaflet comes with a set of already defined Projections out of the box: 1972 * 1973 * @projection L.Projection.LonLat 1974 * 1975 * Equirectangular, or Plate Carree projection â the most simple projection, 1976 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 1977 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 1978 * `EPSG:3395` and `Simple` CRS. 1979 */ 1980 1981L.Projection = {}; 1982 1983L.Projection.LonLat = { 1984 project: function (latlng) { 1985 return new L.Point(latlng.lng, latlng.lat); 1986 }, 1987 1988 unproject: function (point) { 1989 return new L.LatLng(point.y, point.x); 1990 }, 1991 1992 bounds: L.bounds([-180, -90], [180, 90]) 1993}; 1994 1995 1996 1997/* 1998 * @namespace Projection 1999 * @projection L.Projection.SphericalMercator 2000 * 2001 * Spherical Mercator projection â the most common projection for online maps, 2002 * used by almost all free and commercial tile providers. Assumes that Earth is 2003 * a sphere. Used by the `EPSG:3857` CRS. 2004 */ 2005 2006L.Projection.SphericalMercator = { 2007 2008 R: 6378137, 2009 MAX_LATITUDE: 85.0511287798, 2010 2011 project: function (latlng) { 2012 var d = Math.PI / 180, 2013 max = this.MAX_LATITUDE, 2014 lat = Math.max(Math.min(max, latlng.lat), -max), 2015 sin = Math.sin(lat * d); 2016 2017 return new L.Point( 2018 this.R * latlng.lng * d, 2019 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 2020 }, 2021 2022 unproject: function (point) { 2023 var d = 180 / Math.PI; 2024 2025 return new L.LatLng( 2026 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 2027 point.x * d / this.R); 2028 }, 2029 2030 bounds: (function () { 2031 var d = 6378137 * Math.PI; 2032 return L.bounds([-d, -d], [d, d]); 2033 })() 2034}; 2035 2036 2037 2038/* 2039 * @class CRS 2040 * @aka L.CRS 2041 * Abstract class that defines coordinate reference systems for projecting 2042 * geographical points into pixel (screen) coordinates and back (and to
2043 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 2044 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). 2045 * 2046 * Leaflet defines the most usual CRSs by default. If you want to use a 2047 * CRS not defined by default, take a look at the 2048 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 2049 */ 2050 2051L.CRS = { 2052 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 2053 // Projects geographical coordinates into pixel coordinates for a given zoom. 2054 latLngToPoint: function (latlng, zoom) { 2055 var projectedPoint = this.projection.project(latlng), 2056 scale = this.scale(zoom); 2057 2058 return this.transformation._transform(projectedPoint, scale); 2059 }, 2060 2061 // @method pointToLatLng(point: Point, zoom: Number): LatLng 2062 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 2063 // zoom into geographical coordinates. 2064 pointToLatLng: function (point, zoom) { 2065 var scale = this.scale(zoom), 2066 untransformedPoint = this.transformation.untransform(point, scale); 2067 2068 return this.projection.unproject(untransformedPoint); 2069 }, 2070 2071 // @method project(latlng: LatLng): Point 2072 // Projects geographical coordinates into coordinates in units accepted for 2073 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 2074 project: function (latlng) { 2075 return this.projection.project(latlng); 2076 }, 2077 2078 // @method unproject(point: Point): LatLng 2079 // Given a projected coordinate returns the corresponding LatLng. 2080 // The inverse of `project`. 2081 unproject: function (point) { 2082 return this.projection.unproject(point); 2083 }, 2084 2085 // @method scale(zoom: Number): Number 2086 // Returns the scale used when transforming projected coordinates into 2087 // pixel coordinates for a particular zoom. For example, it returns 2088 // `256 * 2^zoom` for Mercator-based CRS. 2089 scale: function (zoom) { 2090 return 256 * Math.pow(2, zoom); 2091 }, 2092 2093 // @method zoom(scale: Number): Number 2094 // Inverse of `scale()`, returns the zoom level corresponding to a scale 2095 // factor of `scale`. 2096 zoom: function (scale) { 2097 return Math.log(scale / 256) / Math.LN2; 2098 }, 2099 2100 // @method getProjectedBounds(zoom: Number): Bounds 2101 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 2102 getProjectedBounds: function (zoom) { 2103 if (this.infinite) { return null; } 2104 2105 var b = this.projection.bounds, 2106 s = this.scale(zoom), 2107 min = this.transformation.transform(b.min, s), 2108 max = this.transformation.transform(b.max, s); 2109 2110 return L.bounds(min, max); 2111 }, 2112 2113 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 2114 // Returns the distance between two geographical coordinates. 2115 2116 // @property code: String 2117 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 2118 // 2119 // @property wrapLng: Number[] 2120 // An array of two numbers defining whether the longitude (horizontal) coordinate 2121 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 2122 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 2123 // 2124 // @property wrapLat: Number[] 2125 // Like `wrapLng`, but for the latitude (vertical) axis. 2126 2127 // wrapLng: [min, max], 2128 // wrapLat: [min, max], 2129 2130 // @property infinite: Boolean 2131 // If true, the coordinate space will be unbounded (infinite in both axes) 2132 infinite: false, 2133 2134 // @method wrapLatLng(latlng: LatLng): LatLng 2135 // Returns a `LatLng` where lat and lng has been wrapped according to the 2136 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 2137 // Only accepts actual `L.LatLng` instances, not arrays. 2138 wrapLatLng: function (latlng) { 2139 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 2140 lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 2141 alt = latlng.alt; 2142 2143 return L.latLng(lat, lng, alt); 2144 }, 2145 2146 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 2147 // Returns a `LatLngBounds` with the same size as the given one, ensuring 2148 // that its center is within the CRS's bounds. 2149 // Only accepts actual `L.LatLngBounds` instances, not arrays. 2150 wrapLatLngBounds: function (bounds) { 2151 var center = bounds.getCenter(), 2152 newCenter = this.wrapLatLng(center), 2153 latShift = center.lat - newCenter.lat, 2154 lngShift = center.lng - newCenter.lng; 2155 2156 if (latShift === 0 && lngShift === 0) { 2157 return bounds; 2158 } 2159
2160 var sw = bounds.getSouthWest(), 2161 ne = bounds.getNorthEast(), 2162 newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}), 2163 newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift}); 2164 2165 return new L.LatLngBounds(newSw, newNe); 2166 } 2167}; 2168 2169 2170 2171/* 2172 * @namespace CRS 2173 * @crs L.CRS.Simple 2174 * 2175 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 2176 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 2177 * axis should still be inverted (going from bottom to top). `distance()` returns 2178 * simple euclidean distance. 2179 */ 2180 2181L.CRS.Simple = L.extend({}, L.CRS, { 2182 projection: L.Projection.LonLat, 2183 transformation: new L.Transformation(1, 0, -1, 0), 2184 2185 scale: function (zoom) { 2186 return Math.pow(2, zoom); 2187 }, 2188 2189 zoom: function (scale) { 2190 return Math.log(scale) / Math.LN2; 2191 }, 2192 2193 distance: function (latlng1, latlng2) { 2194 var dx = latlng2.lng - latlng1.lng, 2195 dy = latlng2.lat - latlng1.lat; 2196 2197 return Math.sqrt(dx * dx + dy * dy); 2198 }, 2199 2200 infinite: true 2201}); 2202 2203 2204 2205/* 2206 * @namespace CRS 2207 * @crs L.CRS.Earth 2208 * 2209 * Serves as the base for CRS that are global such that they cover the earth. 2210 * Can only be used as the base for other CRS and cannot be used directly, 2211 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 2212 * meters. 2213 */ 2214 2215L.CRS.Earth = L.extend({}, L.CRS, { 2216 wrapLng: [-180, 180], 2217 2218 // Mean Earth Radius, as recommended for use by 2219 // the International Union of Geodesy and Geophysics, 2220 // see http://rosettacode.org/wiki/Haversine_formula 2221 R: 6371000, 2222 2223 // distance between two geographical points using spherical law of cosines approximation 2224 distance: function (latlng1, latlng2) { 2225 var rad = Math.PI / 180, 2226 lat1 = latlng1.lat * rad, 2227 lat2 = latlng2.lat * rad, 2228 a = Math.sin(lat1) * Math.sin(lat2) + 2229 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad); 2230 2231 return this.R * Math.acos(Math.min(a, 1)); 2232 } 2233}); 2234 2235 2236 2237/* 2238 * @namespace CRS 2239 * @crs L.CRS.EPSG3857 2240 * 2241 * The most common CRS for online maps, used by almost all free and commercial 2242 * tile providers. Uses Spherical Mercator projection. Set in by default in 2243 * Map's `crs` option. 2244 */ 2245 2246L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, { 2247 code: 'EPSG:3857', 2248 projection: L.Projection.SphericalMercator, 2249 2250 transformation: (function () { 2251 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R); 2252 return new L.Transformation(scale, 0.5, -scale, 0.5); 2253 }()) 2254}); 2255 2256L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { 2257 code: 'EPSG:900913' 2258}); 2259 2260 2261 2262/* 2263 * @namespace CRS 2264 * @crs L.CRS.EPSG4326 2265 * 2266 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 2267 * 2268 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 2269 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 2270 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 2271 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 2272 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 2273 */ 2274 2275L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, { 2276 code: 'EPSG:4326', 2277 projection: L.Projection.LonLat, 2278 transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5) 2279}); 2280 2281 2282 2283/* 2284 * @class Map 2285 * @aka L.Map 2286 * @inherits Evented 2287 * 2288 * The central class of the API â it is used to create a map on a page and manipulate it. 2289 * 2290 * @example 2291 * 2292 * ```js 2293 * // initialize the map on the "map" div with a given center and zoom 2294 * var map = L.map('map', { 2295 * center: [51.505, -0.09], 2296 * zoom: 13 2297 * }); 2298 * ``` 2299 * 2300 */ 2301 2302L.Map = L.Evented.extend({ 2303 2304 options: { 2305 // @section Map State Options 2306 // @option crs: CRS = L.CRS.EPSG3857 2307 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 2308 // sure what it means. 2309 crs: L.CRS.EPSG3857, 2310 2311 // @option center: LatLng = undefined 2312 // Initial geographic center of the map 2313 center: undefined, 2314
2315 // @option zoom: Number = undefined 2316 // Initial map zoom level 2317 zoom: undefined, 2318 2319 // @option minZoom: Number = undefined 2320 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers. 2321 minZoom: undefined, 2322 2323 // @option maxZoom: Number = undefined 2324 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers. 2325 maxZoom: undefined, 2326 2327 // @option layers: Layer[] = [] 2328 // Array of layers that will be added to the map initially 2329 layers: [], 2330 2331 // @option maxBounds: LatLngBounds = null 2332 // When this option is set, the map restricts the view to the given 2333 // geographical bounds, bouncing the user back if the user tries to pan 2334 // outside the view. To set the restriction dynamically, use 2335 // [`setMaxBounds`](#map-setmaxbounds) method. 2336 maxBounds: undefined, 2337 2338 // @option renderer: Renderer = * 2339 // The default method for drawing vector layers on the map. `L.SVG` 2340 // or `L.Canvas` by default depending on browser support. 2341 renderer: undefined, 2342 2343 2344 // @section Animation Options 2345 // @option zoomAnimation: Boolean = true 2346 // Whether the map zoom animation is enabled. By default it's enabled 2347 // in all browsers that support CSS3 Transitions except Android. 2348 zoomAnimation: true, 2349 2350 // @option zoomAnimationThreshold: Number = 4 2351 // Won't animate zoom if the zoom difference exceeds this value. 2352 zoomAnimationThreshold: 4, 2353 2354 // @option fadeAnimation: Boolean = true 2355 // Whether the tile fade animation is enabled. By default it's enabled 2356 // in all browsers that support CSS3 Transitions except Android. 2357 fadeAnimation: true, 2358 2359 // @option markerZoomAnimation: Boolean = true 2360 // Whether markers animate their zoom with the zoom animation, if disabled 2361 // they will disappear for the length of the animation. By default it's 2362 // enabled in all browsers that support CSS3 Transitions except Android. 2363 markerZoomAnimation: true, 2364 2365 // @option transform3DLimit: Number = 2^23 2366 // Defines the maximum size of a CSS translation transform. The default 2367 // value should not be changed unless a web browser positions layers in 2368 // the wrong place after doing a large `panBy`. 2369 transform3DLimit: 8388608, // Precision limit of a 32-bit float 2370 2371 // @section Interaction Options 2372 // @option zoomSnap: Number = 1 2373 // Forces the map's zoom level to always be a multiple of this, particularly 2374 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 2375 // By default, the zoom level snaps to the nearest integer; lower values 2376 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 2377 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 2378 zoomSnap: 1, 2379 2380 // @option zoomDelta: Number = 1 2381 // Controls how much the map's zoom level will change after a 2382 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 2383 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 2384 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 2385 zoomDelta: 1, 2386 2387 // @option trackResize: Boolean = true 2388 // Whether the map automatically handles browser window resize to update itself. 2389 trackResize: true 2390 }, 2391 2392 initialize: function (id, options) { // (HTMLElement or String, Object) 2393 options = L.setOptions(this, options); 2394 2395 this._initContainer(id); 2396 this._initLayout(); 2397 2398 // hack for https://github.com/Leaflet/Leaflet/issues/1980 2399 this._onResize = L.bind(this._onResize, this); 2400 2401 this._initEvents(); 2402 2403 if (options.maxBounds) { 2404 this.setMaxBounds(options.maxBounds); 2405 } 2406 2407 if (options.zoom !== undefined) { 2408 this._zoom = this._limitZoom(options.zoom); 2409 } 2410 2411 if (options.center && options.zoom !== undefined) { 2412 this.setView(L.latLng(options.center), options.zoom, {reset: true}); 2413 } 2414 2415 this._handlers = []; 2416 this._layers = {}; 2417 this._zoomBoundLayers = {}; 2418 this._sizeChanged = true; 2419 2420 this.callInitHooks(); 2421 2422 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 2423 this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera && 2424 this.options.zoomAnimation; 2425 2426 // zoom transitions run with the same duration for all layers, so if one of transitionend events 2427 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 2428 if (this._zoomAnimated) { 2429 this._createAnimProxy(); 2430 L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); 2431 } 2432 2433 this._addLayers(this.options.layers); 2434 }, 2435 2436 2437 // @section Methods for modifying map state 2438 2439 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 2440 // Sets the view of the map (geographical center and zoom) with the given 2441 // animation options. 2442 setView: function (center, zoom, options) { 2443 2444 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 2445 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds); 2446 options = options || {}; 2447 2448 this._stop(); 2449 2450 if (this._loaded && !options.reset && options !== true) { 2451 2452 if (options.animate !== undefined) { 2453 options.zoom = L.extend({animate: options.animate}, options.zoom); 2454 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan); 2455 } 2456 2457 // try animating pan or zoom 2458 var moved = (this._zoom !== zoom) ?
vendor: 18,281 bytes, lines 2459-3039
2459 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 2460 this._tryAnimatedPan(center, options.pan); 2461 2462 if (moved) { 2463 // prevent resize handler call, the view will refresh after animation anyway 2464 clearTimeout(this._sizeTimer); 2465 return this; 2466 } 2467 } 2468 2469 // animation didn't start, just reset the map view 2470 this._resetView(center, zoom); 2471 2472 return this; 2473 }, 2474 2475 // @method setZoom(zoom: Number, options: Zoom/pan options): this 2476 // Sets the zoom of the map. 2477 setZoom: function (zoom, options) { 2478 if (!this._loaded) { 2479 this._zoom = zoom; 2480 return this; 2481 } 2482 return this.setView(this.getCenter(), zoom, {zoom: options}); 2483 }, 2484 2485 // @method zoomIn(delta?: Number, options?: Zoom options): this 2486 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 2487 zoomIn: function (delta, options) { 2488 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1); 2489 return this.setZoom(this._zoom + delta, options); 2490 }, 2491 2492 // @method zoomOut(delta?: Number, options?: Zoom options): this 2493 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 2494 zoomOut: function (delta, options) { 2495 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1); 2496 return this.setZoom(this._zoom - delta, options); 2497 }, 2498 2499 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 2500 // Zooms the map while keeping a specified geographical point on the map 2501 // stationary (e.g. used internally for scroll zoom and double-click zoom). 2502 // @alternative 2503 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 2504 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 2505 setZoomAround: function (latlng, zoom, options) { 2506 var scale = this.getZoomScale(zoom), 2507 viewHalf = this.getSize().divideBy(2), 2508 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), 2509 2510 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 2511 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 2512 2513 return this.setView(newCenter, zoom, {zoom: options}); 2514 }, 2515 2516 _getBoundsCenterZoom: function (bounds, options) { 2517 2518 options = options || {}; 2519 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); 2520 2521 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]), 2522 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), 2523 2524 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 2525 2526 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 2527 2528 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 2529 2530 swPoint = this.project(bounds.getSouthWest(), zoom), 2531 nePoint = this.project(bounds.getNorthEast(), zoom), 2532 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 2533 2534 return { 2535 center: center, 2536 zoom: zoom 2537 }; 2538 }, 2539 2540 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 2541 // Sets a map view that contains the given geographical bounds with the 2542 // maximum zoom level possible. 2543 fitBounds: function (bounds, options) { 2544 2545 bounds = L.latLngBounds(bounds); 2546 2547 if (!bounds.isValid()) { 2548 throw new Error('Bounds are not valid.'); 2549 } 2550 2551 var target = this._getBoundsCenterZoom(bounds, options); 2552 return this.setView(target.center, target.zoom, options); 2553 }, 2554 2555 // @method fitWorld(options?: fitBounds options): this 2556 // Sets a map view that mostly contains the whole world with the maximum 2557 // zoom level possible. 2558 fitWorld: function (options) { 2559 return this.fitBounds([[-90, -180], [90, 180]], options); 2560 }, 2561 2562 // @method panTo(latlng: LatLng, options?: Pan options): this 2563 // Pans the map to a given center. 2564 panTo: function (center, options) { // (LatLng) 2565 return this.setView(center, this._zoom, {pan: options}); 2566 }, 2567 2568 // @method panBy(offset: Point): this 2569 // Pans the map by a given number of pixels (animated). 2570 panBy: function (offset, options) { 2571 offset = L.point(offset).round(); 2572 options = options || {}; 2573 2574 if (!offset.x && !offset.y) { 2575 return this.fire('moveend'); 2576 } 2577 // If we pan too far, Chrome gets issues with tiles 2578 // and makes them disappear or appear in the wrong place (slightly offset) #2602 2579 if (options.animate !== true && !this.getSize().contains(offset)) { 2580 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 2581 return this; 2582 } 2583 2584 if (!this._panAnim) { 2585 this._panAnim = new L.PosAnimation(); 2586 2587 this._panAnim.on({ 2588 'step': this._onPanTransitionStep, 2589 'end': this._onPanTransitionEnd 2590 }, this); 2591 } 2592 2593 // don't fire movestart if animating inertia 2594 if (!options.noMoveStart) { 2595 this.fire('movestart'); 2596 } 2597 2598 // animate pan unless animate: false specified 2599 if (options.animate !== false) { 2600 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); 2601 2602 var newPos = this._getMapPanePos().subtract(offset).round(); 2603 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 2604 } else { 2605 this._rawPanBy(offset); 2606 this.fire('move').fire('moveend'); 2607 } 2608 2609 return this; 2610 }, 2611 2612 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 2613 // Sets the view of the map (geographical center and zoom) performing a smooth 2614 // pan-zoom animation. 2615 flyTo: function (targetCenter, targetZoom, options) { 2616 2617 options = options || {}; 2618 if (options.animate === false || !L.Browser.any3d) { 2619 return this.setView(targetCenter, targetZoom, options); 2620 } 2621 2622 this._stop(); 2623 2624 var from = this.project(this.getCenter()), 2625 to = this.project(targetCenter), 2626 size = this.getSize(), 2627 startZoom = this._zoom; 2628 2629 targetCenter = L.latLng(targetCenter); 2630 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 2631 2632 var w0 = Math.max(size.x, size.y), 2633 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 2634 u1 = (to.distanceTo(from)) || 1, 2635 rho = 1.42, 2636 rho2 = rho * rho; 2637 2638 function r(i) { 2639 var s1 = i ? -1 : 1, 2640 s2 = i ? w1 : w0, 2641 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 2642 b1 = 2 * s2 * rho2 * u1, 2643 b = t1 / b1, 2644 sq = Math.sqrt(b * b + 1) - b; 2645 2646 // workaround for floating point precision bug when sq = 0, log = -Infinite, 2647 // thus triggering an infinite loop in flyTo 2648 var log = sq < 0.000000001 ? -18 : Math.log(sq); 2649 2650 return log; 2651 } 2652 2653 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 2654 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 2655 function tanh(n) { return sinh(n) / cosh(n); } 2656 2657 var r0 = r(0); 2658 2659 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 2660 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 2661 2662 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 2663 2664 var start = Date.now(), 2665 S = (r(1) - r0) / rho, 2666 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 2667 2668 function frame() { 2669 var t = (Date.now() - start) / duration, 2670 s = easeOut(t) * S; 2671 2672 if (t <= 1) { 2673 this._flyToFrame = L.Util.requestAnimFrame(frame, this); 2674 2675 this._move( 2676 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 2677 this.getScaleZoom(w0 / w(s), startZoom), 2678 {flyTo: true}); 2679 2680 } else { 2681 this 2682 ._move(targetCenter, targetZoom) 2683 ._moveEnd(true); 2684 } 2685 } 2686 2687 this._moveStart(true); 2688 2689 frame.call(this); 2690 return this; 2691 }, 2692 2693 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 2694 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 2695 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 2696 flyToBounds: function (bounds, options) { 2697 var target = this._getBoundsCenterZoom(bounds, options); 2698 return this.flyTo(target.center, target.zoom, options); 2699 }, 2700 2701 // @method setMaxBounds(bounds: Bounds): this 2702 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 2703 setMaxBounds: function (bounds) { 2704 bounds = L.latLngBounds(bounds); 2705 2706 if (!bounds.isValid()) { 2707 this.options.maxBounds = null; 2708 return this.off('moveend', this._panInsideMaxBounds); 2709 } else if (this.options.maxBounds) { 2710 this.off('moveend', this._panInsideMaxBounds); 2711 } 2712 2713 this.options.maxBounds = bounds; 2714 2715 if (this._loaded) { 2716 this._panInsideMaxBounds(); 2717 } 2718 2719 return this.on('moveend', this._panInsideMaxBounds); 2720 }, 2721 2722 // @method setMinZoom(zoom: Number): this 2723 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 2724 setMinZoom: function (zoom) { 2725 this.options.minZoom = zoom; 2726 2727 if (this._loaded && this.getZoom() < this.options.minZoom) { 2728 return this.setZoom(zoom); 2729 } 2730 2731 return this; 2732 }, 2733 2734 // @method setMaxZoom(zoom: Number): this 2735 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 2736 setMaxZoom: function (zoom) { 2737 this.options.maxZoom = zoom; 2738 2739 if (this._loaded && (this.getZoom() > this.options.maxZoom)) { 2740 return this.setZoom(zoom); 2741 } 2742 2743 return this; 2744 }, 2745 2746 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 2747 // 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. 2748 panInsideBounds: function (bounds, options) { 2749 this._enforcingBounds = true; 2750 var center = this.getCenter(), 2751 newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds)); 2752 2753 if (!center.equals(newCenter)) { 2754 this.panTo(newCenter, options); 2755 } 2756 2757 this._enforcingBounds = false; 2758 return this; 2759 }, 2760 2761 // @method invalidateSize(options: Zoom/Pan options): this 2762 // Checks if the map container size changed and updates the map if so â 2763 // call it after you've changed the map size dynamically, also animating 2764 // pan by default. If `options.pan` is `false`, panning will not occur. 2765 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 2766 // that it doesn't happen often even if the method is called many 2767 // times in a row. 2768 2769 // @alternative 2770 // @method invalidateSize(animate: Boolean): this 2771 // Checks if the map container size changed and updates the map if so â 2772 // call it after you've changed the map size dynamically, also animating 2773 // pan by default. 2774 invalidateSize: function (options) { 2775 if (!this._loaded) { return this; } 2776 2777 options = L.extend({ 2778 animate: false, 2779 pan: true 2780 }, options === true ? {animate: true} : options); 2781 2782 var oldSize = this.getSize(); 2783 this._sizeChanged = true; 2784 this._lastCenter = null; 2785 2786 var newSize = this.getSize(), 2787 oldCenter = oldSize.divideBy(2).round(), 2788 newCenter = newSize.divideBy(2).round(), 2789 offset = oldCenter.subtract(newCenter); 2790 2791 if (!offset.x && !offset.y) { return this; } 2792 2793 if (options.animate && options.pan) { 2794 this.panBy(offset); 2795 2796 } else { 2797 if (options.pan) { 2798 this._rawPanBy(offset); 2799 } 2800 2801 this.fire('move'); 2802 2803 if (options.debounceMoveend) { 2804 clearTimeout(this._sizeTimer); 2805 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); 2806 } else { 2807 this.fire('moveend'); 2808 } 2809 } 2810 2811 // @section Map state change events 2812 // @event resize: ResizeEvent 2813 // Fired when the map is resized. 2814 return this.fire('resize', { 2815 oldSize: oldSize, 2816 newSize: newSize 2817 }); 2818 }, 2819 2820 // @section Methods for modifying map state 2821 // @method stop(): this 2822 // Stops the currently running `panTo` or `flyTo` animation, if any. 2823 stop: function () { 2824 this.setZoom(this._limitZoom(this._zoom)); 2825 if (!this.options.zoomSnap) { 2826 this.fire('viewreset'); 2827 } 2828 return this._stop(); 2829 }, 2830 2831 // @section Geolocation methods 2832 // @method locate(options?: Locate options): this 2833 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 2834 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 2835 // and optionally sets the map view to the user's location with respect to 2836 // detection accuracy (or to the world view if geolocation failed). 2837 // Note that, if your page doesn't use HTTPS, this method will fail in 2838 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 2839 // See `Locate options` for more details. 2840 locate: function (options) { 2841 2842 options = this._locateOptions = L.extend({ 2843 timeout: 10000, 2844 watch: false 2845 // setView: false 2846 // maxZoom: <Number> 2847 // maximumAge: 0 2848 // enableHighAccuracy: false 2849 }, options); 2850 2851 if (!('geolocation' in navigator)) { 2852 this._handleGeolocationError({ 2853 code: 0, 2854 message: 'Geolocation not supported.' 2855 }); 2856 return this; 2857 } 2858 2859 var onResponse = L.bind(this._handleGeolocationResponse, this), 2860 onError = L.bind(this._handleGeolocationError, this); 2861 2862 if (options.watch) { 2863 this._locationWatchId = 2864 navigator.geolocation.watchPosition(onResponse, onError, options); 2865 } else { 2866 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 2867 } 2868 return this; 2869 }, 2870 2871 // @method stopLocate(): this 2872 // Stops watching location previously initiated by `map.locate({watch: true})` 2873 // and aborts resetting the map view if map.locate was called with 2874 // `{setView: true}`. 2875 stopLocate: function () { 2876 if (navigator.geolocation && navigator.geolocation.clearWatch) { 2877 navigator.geolocation.clearWatch(this._locationWatchId); 2878 } 2879 if (this._locateOptions) { 2880 this._locateOptions.setView = false; 2881 } 2882 return this; 2883 }, 2884 2885 _handleGeolocationError: function (error) { 2886 var c = error.code, 2887 message = error.message || 2888 (c === 1 ? 'permission denied' : 2889 (c === 2 ? 'position unavailable' : 'timeout')); 2890 2891 if (this._locateOptions.setView && !this._loaded) { 2892 this.fitWorld(); 2893 } 2894 2895 // @section Location events 2896 // @event locationerror: ErrorEvent 2897 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 2898 this.fire('locationerror', { 2899 code: c, 2900 message: 'Geolocation error: ' + message + '.' 2901 }); 2902 }, 2903 2904 _handleGeolocationResponse: function (pos) { 2905 var lat = pos.coords.latitude, 2906 lng = pos.coords.longitude, 2907 latlng = new L.LatLng(lat, lng), 2908 bounds = latlng.toBounds(pos.coords.accuracy), 2909 options = this._locateOptions; 2910 2911 if (options.setView) { 2912 var zoom = this.getBoundsZoom(bounds); 2913 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 2914 } 2915 2916 var data = { 2917 latlng: latlng, 2918 bounds: bounds, 2919 timestamp: pos.timestamp 2920 }; 2921 2922 for (var i in pos.coords) { 2923 if (typeof pos.coords[i] === 'number') { 2924 data[i] = pos.coords[i]; 2925 } 2926 } 2927 2928 // @event locationfound: LocationEvent 2929 // Fired when geolocation (using the [`locate`](#map-locate) method) 2930 // went successfully. 2931 this.fire('locationfound', data); 2932 }, 2933 2934 // TODO handler.addTo 2935 // TODO Appropiate docs section? 2936 // @section Other Methods 2937 // @method addHandler(name: String, HandlerClass: Function): this 2938 // Adds a new `Handler` to the map, given its name and constructor function. 2939 addHandler: function (name, HandlerClass) { 2940 if (!HandlerClass) { return this; } 2941 2942 var handler = this[name] = new HandlerClass(this); 2943 2944 this._handlers.push(handler); 2945 2946 if (this.options[name]) { 2947 handler.enable(); 2948 } 2949 2950 return this; 2951 }, 2952 2953 // @method remove(): this 2954 // Destroys the map and clears all related event listeners. 2955 remove: function () { 2956 2957 this._initEvents(true); 2958 2959 if (this._containerId !== this._container._leaflet_id) { 2960 throw new Error('Map container is being reused by another instance'); 2961 } 2962 2963 try { 2964 // throws error in IE6-8 2965 delete this._container._leaflet_id; 2966 delete this._containerId; 2967 } catch (e) { 2968 /*eslint-disable */ 2969 this._container._leaflet_id = undefined; 2970 /*eslint-enable */ 2971 this._containerId = undefined; 2972 } 2973 2974 L.DomUtil.remove(this._mapPane); 2975 2976 if (this._clearControlPos) { 2977 this._clearControlPos(); 2978 } 2979 2980 this._clearHandlers(); 2981 2982 if (this._loaded) { 2983 // @section Map state change events 2984 // @event unload: Event 2985 // Fired when the map is destroyed with [remove](#map-remove) method. 2986 this.fire('unload'); 2987 } 2988 2989 for (var i in this._layers) { 2990 this._layers[i].remove(); 2991 } 2992 2993 return this; 2994 }, 2995 2996 // @section Other Methods 2997 // @method createPane(name: String, container?: HTMLElement): HTMLElement 2998 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 2999 // then returns it. The pane is created as a children of `container`, or 3000 // as a children of the main map pane if not set. 3001 createPane: function (name, container) { 3002 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3003 pane = L.DomUtil.create('div', className, container || this._mapPane); 3004 3005 if (name) { 3006 this._panes[name] = pane; 3007 } 3008 return pane; 3009 }, 3010 3011 // @section Methods for Getting Map State 3012 3013 // @method getCenter(): LatLng 3014 // Returns the geographical center of the map view 3015 getCenter: function () { 3016 this._checkIfLoaded(); 3017 3018 if (this._lastCenter && !this._moved()) { 3019 return this._lastCenter; 3020 } 3021 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3022 }, 3023 3024 // @method getZoom(): Number 3025 // Returns the current zoom level of the map view 3026 getZoom: function () { 3027 return this._zoom; 3028 }, 3029 3030 // @method getBounds(): LatLngBounds 3031 // Returns the geographical bounds visible in the current map view 3032 getBounds: function () { 3033 var bounds = this.getPixelBounds(), 3034 sw = this.unproject(bounds.getBottomLeft()), 3035 ne = this.unproject(bounds.getTopRight()); 3036 3037 return new L.LatLngBounds(sw, ne); 3038 }, 3039
3040 // @method getMinZoom(): Number 3041 // 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. 3042 getMinZoom: function () { 3043 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3044 }, 3045 3046 // @method getMaxZoom(): Number 3047 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3048 getMaxZoom: function () { 3049 return this.options.maxZoom === undefined ? 3050 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3051 this.options.maxZoom; 3052 }, 3053 3054 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number 3055 // Returns the maximum zoom level on which the given bounds fit to the map 3056 // view in its entirety. If `inside` (optional) is set to `true`, the method 3057 // instead returns the minimum zoom level on which the map view fits into 3058 // the given bounds in its entirety. 3059 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3060 bounds = L.latLngBounds(bounds); 3061 padding = L.point(padding || [0, 0]); 3062 3063 var zoom = this.getZoom() || 0, 3064 min = this.getMinZoom(), 3065 max = this.getMaxZoom(), 3066 nw = bounds.getNorthWest(), 3067 se = bounds.getSouthEast(), 3068 size = this.getSize().subtract(padding), 3069 boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3070 snap = L.Browser.any3d ? this.options.zoomSnap : 1; 3071 3072 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y); 3073 zoom = this.getScaleZoom(scale, zoom); 3074 3075 if (snap) { 3076 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3077 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3078 } 3079 3080 return Math.max(min, Math.min(max, zoom)); 3081 }, 3082 3083 // @method getSize(): Point 3084 // Returns the current size of the map container (in pixels). 3085 getSize: function () { 3086 if (!this._size || this._sizeChanged) { 3087 this._size = new L.Point( 3088 this._container.clientWidth || 0, 3089 this._container.clientHeight || 0); 3090 3091 this._sizeChanged = false; 3092 } 3093 return this._size.clone(); 3094 }, 3095 3096 // @method getPixelBounds(): Bounds 3097 // Returns the bounds of the current map view in projected pixel 3098 // coordinates (sometimes useful in layer and overlay implementations). 3099 getPixelBounds: function (center, zoom) { 3100 var topLeftPoint = this._getTopLeftPoint(center, zoom); 3101 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 3102 }, 3103 3104 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 3105 // the map pane? "left point of the map layer" can be confusing, specially 3106 // since there can be negative offsets. 3107 // @method getPixelOrigin(): Point 3108 // Returns the projected pixel coordinates of the top left point of 3109 // the map layer (useful in custom layer and overlay implementations). 3110 getPixelOrigin: function () { 3111 this._checkIfLoaded(); 3112 return this._pixelOrigin; 3113 }, 3114 3115 // @method getPixelWorldBounds(zoom?: Number): Bounds 3116 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 3117 // If `zoom` is omitted, the map's current zoom level is used. 3118 getPixelWorldBounds: function (zoom) { 3119 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 3120 }, 3121 3122 // @section Other Methods 3123 3124 // @method getPane(pane: String|HTMLElement): HTMLElement 3125 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 3126 getPane: function (pane) { 3127 return typeof pane === 'string' ? this._panes[pane] : pane; 3128 }, 3129 3130 // @method getPanes(): Object
vendor: 6,095 bytes, lines 3131-3267
3131 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 3132 // the panes as values. 3133 getPanes: function () { 3134 return this._panes; 3135 }, 3136 3137 // @method getContainer: HTMLElement 3138 // Returns the HTML element that contains the map. 3139 getContainer: function () { 3140 return this._container; 3141 }, 3142 3143 3144 // @section Conversion Methods 3145 3146 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 3147 // Returns the scale factor to be applied to a map transition from zoom level 3148 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 3149 getZoomScale: function (toZoom, fromZoom) { 3150 // TODO replace with universal implementation after refactoring projections 3151 var crs = this.options.crs; 3152 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3153 return crs.scale(toZoom) / crs.scale(fromZoom); 3154 }, 3155 3156 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 3157 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 3158 // level and everything is scaled by a factor of `scale`. Inverse of 3159 // [`getZoomScale`](#map-getZoomScale). 3160 getScaleZoom: function (scale, fromZoom) { 3161 var crs = this.options.crs; 3162 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3163 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 3164 return isNaN(zoom) ? Infinity : zoom; 3165 }, 3166 3167 // @method project(latlng: LatLng, zoom: Number): Point 3168 // Projects a geographical coordinate `LatLng` according to the projection 3169 // of the map's CRS, then scales it according to `zoom` and the CRS's 3170 // `Transformation`. The result is pixel coordinate relative to 3171 // the CRS origin. 3172 project: function (latlng, zoom) { 3173 zoom = zoom === undefined ? this._zoom : zoom; 3174 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); 3175 }, 3176 3177 // @method unproject(point: Point, zoom: Number): LatLng 3178 // Inverse of [`project`](#map-project). 3179 unproject: function (point, zoom) { 3180 zoom = zoom === undefined ? this._zoom : zoom; 3181 return this.options.crs.pointToLatLng(L.point(point), zoom); 3182 }, 3183 3184 // @method layerPointToLatLng(point: Point): LatLng 3185 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 3186 // returns the corresponding geographical coordinate (for the current zoom level). 3187 layerPointToLatLng: function (point) { 3188 var projectedPoint = L.point(point).add(this.getPixelOrigin()); 3189 return this.unproject(projectedPoint); 3190 }, 3191 3192 // @method latLngToLayerPoint(latlng: LatLng): Point 3193 // Given a geographical coordinate, returns the corresponding pixel coordinate 3194 // relative to the [origin pixel](#map-getpixelorigin). 3195 latLngToLayerPoint: function (latlng) { 3196 var projectedPoint = this.project(L.latLng(latlng))._round(); 3197 return projectedPoint._subtract(this.getPixelOrigin()); 3198 }, 3199 3200 // @method wrapLatLng(latlng: LatLng): LatLng 3201 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 3202 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 3203 // CRS's bounds. 3204 // By default this means longitude is wrapped around the dateline so its 3205 // value is between -180 and +180 degrees. 3206 wrapLatLng: function (latlng) { 3207 return this.options.crs.wrapLatLng(L.latLng(latlng)); 3208 }, 3209 3210 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 3211 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 3212 // its center is within the CRS's bounds. 3213 // By default this means the center longitude is wrapped around the dateline so its 3214 // value is between -180 and +180 degrees, and the majority of the bounds 3215 // overlaps the CRS's bounds. 3216 wrapLatLngBounds: function (latlng) { 3217 return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng)); 3218 }, 3219 3220 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 3221 // Returns the distance between two geographical coordinates according to 3222 // the map's CRS. By default this measures distance in meters. 3223 distance: function (latlng1, latlng2) { 3224 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2)); 3225 }, 3226 3227 // @method containerPointToLayerPoint(point: Point): Point 3228 // Given a pixel coordinate relative to the map container, returns the corresponding 3229 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 3230 containerPointToLayerPoint: function (point) { // (Point) 3231 return L.point(point).subtract(this._getMapPanePos()); 3232 }, 3233 3234 // @method layerPointToContainerPoint(point: Point): Point 3235 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 3236 // returns the corresponding pixel coordinate relative to the map container. 3237 layerPointToContainerPoint: function (point) { // (Point) 3238 return L.point(point).add(this._getMapPanePos()); 3239 }, 3240 3241 // @method containerPointToLatLng(point: Point): LatLng 3242 // Given a pixel coordinate relative to the map container, returns 3243 // the corresponding geographical coordinate (for the current zoom level). 3244 containerPointToLatLng: function (point) { 3245 var layerPoint = this.containerPointToLayerPoint(L.point(point)); 3246 return this.layerPointToLatLng(layerPoint); 3247 }, 3248 3249 // @method latLngToContainerPoint(latlng: LatLng): Point 3250 // Given a geographical coordinate, returns the corresponding pixel coordinate 3251 // relative to the map container. 3252 latLngToContainerPoint: function (latlng) { 3253 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); 3254 }, 3255 3256 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 3257 // Given a MouseEvent object, returns the pixel coordinate relative to the 3258 // map container where the event took place. 3259 mouseEventToContainerPoint: function (e) { 3260 return L.DomEvent.getMousePosition(e, this._container); 3261 }, 3262 3263 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 3264 // Given a MouseEvent object, returns the pixel coordinate relative to 3265 // the [origin pixel](#map-getpixelorigin) where the event took place. 3266 mouseEventToLayerPoint: function (e) { 3267 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3268 }, 3269 3270 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 3271 // Given a MouseEvent object, returns geographical coordinate where the 3272 // event took place. 3273 mouseEventToLatLng: function (e) { // (MouseEvent) 3274 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 3275 }, 3276 3277 3278 // map initialization methods 3279 3280 _initContainer: function (id) { 3281 var container = this._container = L.DomUtil.get(id); 3282 3283 if (!container) { 3284 throw new Error('Map container not found.'); 3285 } else if (container._leaflet_id) { 3286 throw new Error('Map container is already initialized.'); 3287 } 3288 3289 L.DomEvent.addListener(container, 'scroll', this._onScroll, this); 3290 this._containerId = L.Util.stamp(container); 3291 }, 3292 3293 _initLayout: function () { 3294 var container = this._container; 3295 3296 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d; 3297 3298 L.DomUtil.addClass(container, 'leaflet-container' + 3299 (L.Browser.touch ? ' leaflet-touch' : '') + 3300 (L.Browser.retina ? ' leaflet-retina' : '') + 3301 (L.Browser.ielt9 ? ' leaflet-oldie' : '') + 3302 (L.Browser.safari ? ' leaflet-safari' : '') + 3303 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 3304 3305 var position = L.DomUtil.getStyle(container, 'position'); 3306 3307 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { 3308 container.style.position = 'relative'; 3309 } 3310 3311 this._initPanes(); 3312 3313 if (this._initControlPos) { 3314 this._initControlPos(); 3315 } 3316 }, 3317 3318 _initPanes: function () { 3319 var panes = this._panes = {}; 3320 this._paneRenderers = {}; 3321 3322 // @section 3323 // 3324 // Panes are DOM elements used to control the ordering of layers on the map. You 3325 // can access panes with [`map.getPane`](#map-getpane) or 3326 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 3327 // [`map.createPane`](#map-createpane) method. 3328 // 3329 // Every map has the following default panes that differ only in zIndex. 3330 // 3331 // @pane mapPane: HTMLElement = 'auto' 3332 // Pane that contains all other map panes 3333 3334 this._mapPane = this.createPane('mapPane', this._container); 3335 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); 3336 3337 // @pane tilePane: HTMLElement = 200 3338 // Pane for `GridLayer`s and `TileLayer`s 3339 this.createPane('tilePane'); 3340 // @pane overlayPane: HTMLElement = 400 3341 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s 3342 this.createPane('shadowPane'); 3343 // @pane shadowPane: HTMLElement = 500 3344 // Pane for overlay shadows (e.g. `Marker` shadows) 3345 this.createPane('overlayPane'); 3346 // @pane markerPane: HTMLElement = 600 3347 // Pane for `Icon`s of `Marker`s 3348 this.createPane('markerPane'); 3349 // @pane tooltipPane: HTMLElement = 650 3350 // Pane for tooltip. 3351 this.createPane('tooltipPane'); 3352 // @pane popupPane: HTMLElement = 700 3353 // Pane for `Popup`s. 3354 this.createPane('popupPane'); 3355 3356 if (!this.options.markerZoomAnimation) { 3357 L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); 3358 L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); 3359 } 3360 }, 3361 3362 3363 // private methods that modify map state 3364 3365 // @section Map state change events 3366 _resetView: function (center, zoom) { 3367 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); 3368 3369 var loading = !this._loaded; 3370 this._loaded = true; 3371 zoom = this._limitZoom(zoom); 3372 3373 this.fire('viewprereset'); 3374 3375 var zoomChanged = this._zoom !== zoom; 3376 this 3377 ._moveStart(zoomChanged) 3378 ._move(center, zoom) 3379 ._moveEnd(zoomChanged); 3380 3381 // @event viewreset: Event 3382 // Fired when the map needs to redraw its content (this usually happens 3383 // on map zoom or load). Very useful for creating custom overlays. 3384 this.fire('viewreset'); 3385 3386 // @event load: Event 3387 // Fired when the map is initialized (when its center and zoom are set 3388 // for the first time). 3389 if (loading) { 3390 this.fire('load'); 3391 } 3392 }, 3393 3394 _moveStart: function (zoomChanged) { 3395 // @event zoomstart: Event 3396 // Fired when the map zoom is about to change (e.g. before zoom animation). 3397 // @event movestart: Event 3398 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 3399 if (zoomChanged) { 3400 this.fire('zoomstart'); 3401 } 3402 return this.fire('movestart'); 3403 }, 3404 3405 _move: function (center, zoom, data) { 3406 if (zoom === undefined) { 3407 zoom = this._zoom; 3408 } 3409 var zoomChanged = this._zoom !== zoom; 3410 3411 this._zoom = zoom; 3412 this._lastCenter = center; 3413 this._pixelOrigin = this._getNewPixelOrigin(center); 3414 3415 // @event zoom: Event 3416 // Fired repeatedly during any change in zoom level, including zoom 3417 // and fly animations. 3418 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 3419 this.fire('zoom', data); 3420 } 3421 3422 // @event move: Event 3423 // Fired repeatedly during any movement of the map, including pan and 3424 // fly animations. 3425 return this.fire('move', data); 3426 }, 3427 3428 _moveEnd: function (zoomChanged) { 3429 // @event zoomend: Event 3430 // Fired when the map has changed, after any animations. 3431 if (zoomChanged) { 3432 this.fire('zoomend'); 3433 } 3434 3435 // @event moveend: Event 3436 // Fired when the center of the map stops changing (e.g. user stopped 3437 // dragging the map). 3438 return this.fire('moveend'); 3439 }, 3440 3441 _stop: function () {
3442 L.Util.cancelAnimFrame(this._flyToFrame); 3443 if (this._panAnim) { 3444 this._panAnim.stop(); 3445 } 3446 return this; 3447 }, 3448 3449 _rawPanBy: function (offset) { 3450 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 3451 }, 3452 3453 _getZoomSpan: function () { 3454 return this.getMaxZoom() - this.getMinZoom(); 3455 }, 3456 3457 _panInsideMaxBounds: function () { 3458 if (!this._enforcingBounds) { 3459 this.panInsideBounds(this.options.maxBounds); 3460 } 3461 }, 3462 3463 _checkIfLoaded: function () { 3464 if (!this._loaded) { 3465 throw new Error('Set map center and zoom first.'); 3466 } 3467 }, 3468 3469 // DOM event handling 3470 3471 // @section Interaction events 3472 _initEvents: function (remove) { 3473 if (!L.DomEvent) { return; } 3474 3475 this._targets = {}; 3476 this._targets[L.stamp(this._container)] = this; 3477 3478 var onOff = remove ? 'off' : 'on'; 3479 3480 // @event click: MouseEvent 3481 // Fired when the user clicks (or taps) the map. 3482 // @event dblclick: MouseEvent 3483 // Fired when the user double-clicks (or double-taps) the map. 3484 // @event mousedown: MouseEvent 3485 // Fired when the user pushes the mouse button on the map. 3486 // @event mouseup: MouseEvent 3487 // Fired when the user releases the mouse button on the map. 3488 // @event mouseover: MouseEvent 3489 // Fired when the mouse enters the map. 3490 // @event mouseout: MouseEvent 3491 // Fired when the mouse leaves the map. 3492 // @event mousemove: MouseEvent 3493 // Fired while the mouse moves over the map. 3494 // @event contextmenu: MouseEvent 3495 // Fired when the user pushes the right mouse button on the map, prevents 3496 // default browser context menu from showing if there are listeners on 3497 // this event. Also fired on mobile when the user holds a single touch 3498 // for a second (also called long press). 3499 // @event keypress: KeyboardEvent 3500 // Fired when the user presses a key from the keyboard while the map is focused. 3501 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' + 3502 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); 3503 3504 if (this.options.trackResize) { 3505 L.DomEvent[onOff](window, 'resize', this._onResize, this); 3506 } 3507 3508 if (L.Browser.any3d && this.options.transform3DLimit) { 3509 this[onOff]('moveend', this._onMoveEnd); 3510 } 3511 }, 3512 3513 _onResize: function () { 3514 L.Util.cancelAnimFrame(this._resizeRequest); 3515 this._resizeRequest = L.Util.requestAnimFrame( 3516 function () { this.invalidateSize({debounceMoveend: true}); }, this); 3517 }, 3518 3519 _onScroll: function () { 3520 this._container.scrollTop = 0; 3521 this._container.scrollLeft = 0; 3522 }, 3523 3524 _onMoveEnd: function () { 3525 var pos = this._getMapPanePos(); 3526 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 3527 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 3528 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ 3529 this._resetView(this.getCenter(), this.getZoom()); 3530 } 3531 }, 3532 3533 _findEventTargets: function (e, type) { 3534 var targets = [], 3535 target, 3536 isHover = type === 'mouseout' || type === 'mouseover', 3537 src = e.target || e.srcElement, 3538 dragging = false; 3539 3540 while (src) { 3541 target = this._targets[L.stamp(src)]; 3542 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { 3543 // Prevent firing click after you just dragged an object. 3544 dragging = true; 3545 break; 3546 } 3547 if (target && target.listens(type, true)) { 3548 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; } 3549 targets.push(target); 3550 if (isHover) { break; } 3551 } 3552 if (src === this._container) { break; } 3553 src = src.parentNode; 3554 } 3555 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) { 3556 targets = [this]; 3557 } 3558 return targets; 3559 }, 3560 3561 _handleDOMEvent: function (e) { 3562 if (!this._loaded || L.DomEvent._skipped(e)) { return; } 3563 3564 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type; 3565 3566 if (type === 'mousedown') { 3567 // prevents outline when clicking on keyboard-focusable element 3568 L.DomUtil.preventOutline(e.target || e.srcElement); 3569 } 3570
3571 this._fireDOMEvent(e, type); 3572 }, 3573 3574 _fireDOMEvent: function (e, type, targets) { 3575 3576 if (e.type === 'click') { 3577 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 3578 // @event preclick: MouseEvent 3579 // Fired before mouse click on the map (sometimes useful when you 3580 // want something to happen on click before any existing click 3581 // handlers start running). 3582 var synth = L.Util.extend({}, e); 3583 synth.type = 'preclick'; 3584 this._fireDOMEvent(synth, synth.type, targets); 3585 } 3586 3587 if (e._stopped) { return; } 3588 3589 // Find the layer the event is propagating from and its parents. 3590 targets = (targets || []).concat(this._findEventTargets(e, type)); 3591 3592 if (!targets.length) { return; } 3593 3594 var target = targets[0]; 3595 if (type === 'contextmenu' && target.listens(type, true)) { 3596 L.DomEvent.preventDefault(e); 3597 } 3598 3599 var data = { 3600 originalEvent: e 3601 }; 3602 3603 if (e.type !== 'keypress') { 3604 var isMarker = target instanceof L.Marker; 3605 data.containerPoint = isMarker ? 3606 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 3607 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 3608 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 3609 } 3610 3611 for (var i = 0; i < targets.length; i++) { 3612 targets[i].fire(type, data, true); 3613 if (data.originalEvent._stopped || 3614 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; } 3615 } 3616 }, 3617 3618 _draggableMoved: function (obj) { 3619 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 3620 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 3621 }, 3622 3623 _clearHandlers: function () { 3624 for (var i = 0, len = this._handlers.length; i < len; i++) { 3625 this._handlers[i].disable(); 3626 } 3627 }, 3628 3629 // @section Other Methods 3630 3631 // @method whenReady(fn: Function, context?: Object): this 3632 // Runs the given function `fn` when the map gets initialized with 3633 // a view (center and zoom) and at least one layer, or immediately 3634 // if it's already initialized, optionally passing a function context. 3635 whenReady: function (callback, context) { 3636 if (this._loaded) { 3637 callback.call(context || this, {target: this}); 3638 } else { 3639 this.on('load', callback, context); 3640 } 3641 return this; 3642 }, 3643 3644 3645 // private methods for getting map state 3646 3647 _getMapPanePos: function () { 3648 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0); 3649 }, 3650 3651 _moved: function () { 3652 var pos = this._getMapPanePos(); 3653 return pos && !pos.equals([0, 0]); 3654 }, 3655 3656 _getTopLeftPoint: function (center, zoom) { 3657 var pixelOrigin = center && zoom !== undefined ? 3658 this._getNewPixelOrigin(center, zoom) : 3659 this.getPixelOrigin(); 3660 return pixelOrigin.subtract(this._getMapPanePos()); 3661 }, 3662 3663 _getNewPixelOrigin: function (center, zoom) { 3664 var viewHalf = this.getSize()._divideBy(2); 3665 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 3666 }, 3667 3668 _latLngToNewLayerPoint: function (latlng, zoom, center) { 3669 var topLeft = this._getNewPixelOrigin(center, zoom); 3670 return this.project(latlng, zoom)._subtract(topLeft); 3671 }, 3672 3673 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 3674 var topLeft = this._getNewPixelOrigin(center, zoom); 3675 return L.bounds([
3676 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 3677 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 3678 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 3679 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 3680 ]); 3681 }, 3682 3683 // layer point of the current center 3684 _getCenterLayerPoint: function () { 3685 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 3686 }, 3687 3688 // offset of the specified place to the current center in pixels 3689 _getCenterOffset: function (latlng) { 3690 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 3691 }, 3692 3693 // adjust center for view to get inside bounds 3694 _limitCenter: function (center, zoom, bounds) { 3695 3696 if (!bounds) { return center; } 3697 3698 var centerPoint = this.project(center, zoom), 3699 viewHalf = this.getSize().divideBy(2), 3700 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 3701 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 3702 3703 // If offset is less than a pixel, ignore. 3704 // This prevents unstable projections from getting into 3705 // an infinite loop of tiny offsets. 3706 if (offset.round().equals([0, 0])) { 3707 return center; 3708 } 3709 3710 return this.unproject(centerPoint.add(offset), zoom); 3711 }, 3712 3713 // adjust offset for view to get inside bounds 3714 _limitOffset: function (offset, bounds) { 3715 if (!bounds) { return offset; } 3716 3717 var viewBounds = this.getPixelBounds(), 3718 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 3719 3720 return offset.add(this._getBoundsOffset(newBounds, bounds)); 3721 }, 3722 3723 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 3724 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 3725 var projectedMaxBounds = L.bounds( 3726 this.project(maxBounds.getNorthEast(), zoom),
vendor: 4,411 bytes, lines 3727-3867
3727 this.project(maxBounds.getSouthWest(), zoom) 3728 ), 3729 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 3730 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 3731 3732 dx = this._rebound(minOffset.x, -maxOffset.x), 3733 dy = this._rebound(minOffset.y, -maxOffset.y); 3734 3735 return new L.Point(dx, dy); 3736 }, 3737 3738 _rebound: function (left, right) { 3739 return left + right > 0 ? 3740 Math.round(left - right) / 2 : 3741 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 3742 }, 3743 3744 _limitZoom: function (zoom) { 3745 var min = this.getMinZoom(), 3746 max = this.getMaxZoom(), 3747 snap = L.Browser.any3d ? this.options.zoomSnap : 1; 3748 if (snap) { 3749 zoom = Math.round(zoom / snap) * snap; 3750 } 3751 return Math.max(min, Math.min(max, zoom)); 3752 }, 3753 3754 _onPanTransitionStep: function () { 3755 this.fire('move'); 3756 }, 3757 3758 _onPanTransitionEnd: function () { 3759 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim'); 3760 this.fire('moveend'); 3761 }, 3762 3763 _tryAnimatedPan: function (center, options) { 3764 // difference between the new and current centers in pixels 3765 var offset = this._getCenterOffset(center)._floor(); 3766 3767 // don't animate too far unless animate: true specified in options 3768 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 3769 3770 this.panBy(offset, options); 3771 3772 return true; 3773 }, 3774 3775 _createAnimProxy: function () { 3776 3777 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated'); 3778 this._panes.mapPane.appendChild(proxy); 3779 3780 this.on('zoomanim', function (e) { 3781 var prop = L.DomUtil.TRANSFORM, 3782 transform = proxy.style[prop]; 3783 3784 L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 3785 3786 // workaround for case when transform is the same and so transitionend event is not fired 3787 if (transform === proxy.style[prop] && this._animatingZoom) { 3788 this._onZoomTransitionEnd(); 3789 } 3790 }, this); 3791 3792 this.on('load moveend', function () { 3793 var c = this.getCenter(), 3794 z = this.getZoom(); 3795 L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1)); 3796 }, this); 3797 }, 3798 3799 _catchTransitionEnd: function (e) { 3800 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 3801 this._onZoomTransitionEnd(); 3802 } 3803 }, 3804 3805 _nothingToAnimate: function () { 3806 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 3807 }, 3808 3809 _tryAnimatedZoom: function (center, zoom, options) { 3810 3811 if (this._animatingZoom) { return true; } 3812 3813 options = options || {}; 3814 3815 // don't animate if disabled, not supported or zoom difference is too large 3816 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 3817 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 3818 3819 // offset is the pixel coords of the zoom origin relative to the current center 3820 var scale = this.getZoomScale(zoom), 3821 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 3822 3823 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 3824 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 3825 3826 L.Util.requestAnimFrame(function () { 3827 this 3828 ._moveStart(true) 3829 ._animateZoom(center, zoom, true); 3830 }, this); 3831 3832 return true; 3833 }, 3834 3835 _animateZoom: function (center, zoom, startAnim, noUpdate) { 3836 if (startAnim) { 3837 this._animatingZoom = true; 3838 3839 // remember what center/zoom to set after animation 3840 this._animateToCenter = center; 3841 this._animateToZoom = zoom; 3842 3843 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); 3844 } 3845 3846 // @event zoomanim: ZoomAnimEvent 3847 // Fired on every frame of a zoom animation 3848 this.fire('zoomanim', { 3849 center: center, 3850 zoom: zoom, 3851 noUpdate: noUpdate 3852 }); 3853 3854 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 3855 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250); 3856 }, 3857 3858 _onZoomTransitionEnd: function () { 3859 if (!this._animatingZoom) { return; } 3860 3861 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); 3862 3863 this._animatingZoom = false; 3864 3865 this._move(this._animateToCenter, this._animateToZoom); 3866 3867 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
3868 L.Util.requestAnimFrame(function () { 3869 this._moveEnd(true); 3870 }, this); 3871 } 3872}); 3873 3874// @section 3875 3876// @factory L.map(id: String, options?: Map options) 3877// Instantiates a map object given the DOM ID of a `<div>` element 3878// and optionally an object literal with `Map options`. 3879// 3880// @alternative 3881// @factory L.map(el: HTMLElement, options?: Map options) 3882// Instantiates a map object given an instance of a `<div>` HTML element 3883// and optionally an object literal with `Map options`. 3884L.map = function (id, options) { 3885 return new L.Map(id, options); 3886}; 3887 3888 3889 3890 3891/* 3892 * @class Layer 3893 * @inherits Evented 3894 * @aka L.Layer 3895 * @aka ILayer 3896 * 3897 * A set of methods from the Layer base class that all Leaflet layers use. 3898 * Inherits all methods, options and events from `L.Evented`. 3899 * 3900 * @example 3901 * 3902 * ```js 3903 * var layer = L.Marker(latlng).addTo(map); 3904 * layer.addTo(map); 3905 * layer.remove(); 3906 * ``` 3907 * 3908 * @event add: Event 3909 * Fired after the layer is added to a map 3910 * 3911 * @event remove: Event 3912 * Fired after the layer is removed from a map 3913 */ 3914 3915 3916L.Layer = L.Evented.extend({ 3917 3918 // Classes extending `L.Layer` will inherit the following options: 3919 options: { 3920 // @option pane: String = 'overlayPane' 3921 // 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. 3922 pane: 'overlayPane', 3923 nonBubblingEvents: [], // Array of events that should not be bubbled to DOM parents (like the map), 3924 3925 // @option attribution: String = null 3926 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox". 3927 attribution: null 3928 }, 3929 3930 /* @section 3931 * Classes extending `L.Layer` will inherit the following methods: 3932 * 3933 * @method addTo(map: Map): this 3934 * Adds the layer to the given map 3935 */ 3936 addTo: function (map) { 3937 map.addLayer(this); 3938 return this; 3939 }, 3940 3941 // @method remove: this 3942 // Removes the layer from the map it is currently active on. 3943 remove: function () { 3944 return this.removeFrom(this._map || this._mapToAdd); 3945 }, 3946 3947 // @method removeFrom(map: Map): this 3948 // Removes the layer from the given map 3949 removeFrom: function (obj) { 3950 if (obj) { 3951 obj.removeLayer(this); 3952 } 3953 return this; 3954 }, 3955 3956 // @method getPane(name? : String): HTMLElement 3957 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 3958 getPane: function (name) { 3959 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 3960 }, 3961 3962 addInteractiveTarget: function (targetEl) { 3963 this._map._targets[L.stamp(targetEl)] = this; 3964 return this; 3965 }, 3966 3967 removeInteractiveTarget: function (targetEl) { 3968 delete this._map._targets[L.stamp(targetEl)]; 3969 return this; 3970 }, 3971 3972 // @method getAttribution: String 3973 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 3974 getAttribution: function () { 3975 return this.options.attribution; 3976 }, 3977 3978 _layerAdd: function (e) { 3979 var map = e.target; 3980 3981 // check in case layer gets added and then removed before the map is ready 3982 if (!map.hasLayer(this)) { return; } 3983 3984 this._map = map; 3985 this._zoomAnimated = map._zoomAnimated; 3986 3987 if (this.getEvents) { 3988 var events = this.getEvents(); 3989 map.on(events, this); 3990 this.once('remove', function () { 3991 map.off(events, this); 3992 }, this); 3993 } 3994 3995 this.onAdd(map); 3996 3997 if (this.getAttribution && map.attributionControl) { 3998 map.attributionControl.addAttribution(this.getAttribution()); 3999 } 4000 4001 this.fire('add'); 4002 map.fire('layeradd', {layer: this}); 4003 } 4004}); 4005 4006/* @section Extension methods 4007 * @uninheritable 4008 * 4009 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 4010 * 4011 * @method onAdd(map: Map): this 4012 * 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). 4013 * 4014 * @method onRemove(map: Map): this 4015 * 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). 4016 * 4017 * @method getEvents(): Object 4018 * 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. 4019 * 4020 * @method getAttribution(): String 4021 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 4022 * 4023 * @method beforeAdd(map: Map): this 4024 * 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. 4025 */ 4026 4027 4028/* @namespace Map 4029 * @section Layer events 4030 * 4031 * @event layeradd: LayerEvent 4032 * Fired when a new layer is added to the map. 4033 * 4034 * @event layerremove: LayerEvent 4035 * Fired when some layer is removed from the map 4036 * 4037 * @section Methods for Layers and Controls 4038 */ 4039L.Map.include({ 4040 // @method addLayer(layer: Layer): this 4041 // Adds the given layer to the map 4042 addLayer: function (layer) { 4043 var id = L.stamp(layer); 4044 if (this._layers[id]) { return this; } 4045 this._layers[id] = layer; 4046 4047 layer._mapToAdd = this; 4048 4049 if (layer.beforeAdd) { 4050 layer.beforeAdd(this); 4051 } 4052 4053 this.whenReady(layer._layerAdd, layer); 4054 4055 return this; 4056 }, 4057 4058 // @method removeLayer(layer: Layer): this 4059 // Removes the given layer from the map. 4060 removeLayer: function (layer) { 4061 var id = L.stamp(layer); 4062 4063 if (!this._layers[id]) { return this; } 4064 4065 if (this._loaded) { 4066 layer.onRemove(this); 4067 } 4068 4069 if (layer.getAttribution && this.attributionControl) { 4070 this.attributionControl.removeAttribution(layer.getAttribution()); 4071 } 4072 4073 delete this._layers[id]; 4074 4075 if (this._loaded) { 4076 this.fire('layerremove', {layer: layer}); 4077 layer.fire('remove'); 4078 } 4079 4080 layer._map = layer._mapToAdd = null; 4081 4082 return this; 4083 }, 4084 4085 // @method hasLayer(layer: Layer): Boolean 4086 // Returns `true` if the given layer is currently added to the map 4087 hasLayer: function (layer) { 4088 return !!layer && (L.stamp(layer) in this._layers); 4089 }, 4090 4091 /* @method eachLayer(fn: Function, context?: Object): this 4092 * Iterates over the layers of the map, optionally specifying context of the iterator function. 4093 * ``` 4094 * map.eachLayer(function(layer){ 4095 * layer.bindPopup('Hello'); 4096 * }); 4097 * ``` 4098 */ 4099 eachLayer: function (method, context) { 4100 for (var i in this._layers) { 4101 method.call(context, this._layers[i]); 4102 } 4103 return this; 4104 }, 4105 4106 _addLayers: function (layers) { 4107 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; 4108 4109 for (var i = 0, len = layers.length; i < len; i++) { 4110 this.addLayer(layers[i]); 4111 } 4112 }, 4113 4114 _addZoomLimit: function (layer) { 4115 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 4116 this._zoomBoundLayers[L.stamp(layer)] = layer; 4117 this._updateZoomLevels(); 4118 } 4119 }, 4120 4121 _removeZoomLimit: function (layer) { 4122 var id = L.stamp(layer); 4123 4124 if (this._zoomBoundLayers[id]) { 4125 delete this._zoomBoundLayers[id]; 4126 this._updateZoomLevels(); 4127 } 4128 }, 4129 4130 _updateZoomLevels: function () { 4131 var minZoom = Infinity, 4132 maxZoom = -Infinity, 4133 oldZoomSpan = this._getZoomSpan(); 4134 4135 for (var i in this._zoomBoundLayers) { 4136 var options = this._zoomBoundLayers[i].options; 4137
4138 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 4139 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 4140 } 4141 4142 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 4143 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 4144 4145 // @section Map state change events 4146 // @event zoomlevelschange: Event 4147 // Fired when the number of zoomlevels on the map is changed due 4148 // to adding or removing a layer. 4149 if (oldZoomSpan !== this._getZoomSpan()) { 4150 this.fire('zoomlevelschange'); 4151 } 4152 4153 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 4154 this.setZoom(this._layersMaxZoom); 4155 } 4156 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 4157 this.setZoom(this._layersMinZoom); 4158 } 4159 } 4160}); 4161 4162 4163 4164/* 4165 * @namespace DomEvent 4166 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 4167 */ 4168 4169// Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 4170 4171 4172 4173var eventsKey = '_leaflet_events'; 4174 4175L.DomEvent = { 4176 4177 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 4178 // Adds a listener function (`fn`) to a particular DOM event type of the 4179 // element `el`. You can optionally specify the context of the listener 4180 // (object the `this` keyword will point to). You can also pass several 4181 // space-separated types (e.g. `'click dblclick'`). 4182 4183 // @alternative 4184 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this 4185 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 4186 on: function (obj, types, fn, context) { 4187 4188 if (typeof types === 'object') { 4189 for (var type in types) { 4190 this._on(obj, type, types[type], fn); 4191 } 4192 } else { 4193 types = L.Util.splitWords(types); 4194 4195 for (var i = 0, len = types.length; i < len; i++) { 4196 this._on(obj, types[i], fn, context); 4197 } 4198 } 4199 4200 return this; 4201 }, 4202 4203 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 4204 // Removes a previously added listener function. If no function is specified, 4205 // it will remove all the listeners of that particular DOM event from the element. 4206 // Note that if you passed a custom context to on, you must pass the same 4207 // context to `off` in order to remove the listener. 4208 4209 // @alternative 4210 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this 4211 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 4212 off: function (obj, types, fn, context) { 4213 4214 if (typeof types === 'object') { 4215 for (var type in types) { 4216 this._off(obj, type, types[type], fn); 4217 } 4218 } else { 4219 types = L.Util.splitWords(types); 4220 4221 for (var i = 0, len = types.length; i < len; i++) { 4222 this._off(obj, types[i], fn, context); 4223 } 4224 } 4225 4226 return this; 4227 }, 4228 4229 _on: function (obj, type, fn, context) { 4230 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''); 4231 4232 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 4233 4234 var handler = function (e) { 4235 return fn.call(context || obj, e || window.event); 4236 }; 4237 4238 var originalHandler = handler; 4239 4240 if (L.Browser.pointer && type.indexOf('touch') === 0) { 4241 this.addPointerListener(obj, type, handler, id); 4242 4243 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener && 4244 !(L.Browser.pointer && L.Browser.chrome)) { 4245 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener 4246 // See #5180 4247 this.addDoubleTapListener(obj, handler, id); 4248 4249 } else if ('addEventListener' in obj) { 4250 4251 if (type === 'mousewheel') { 4252 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); 4253 4254 } else if ((type === 'mouseenter') || (type === 'mouseleave')) { 4255 handler = function (e) { 4256 e = e || window.event; 4257 if (L.DomEvent._isExternalTarget(obj, e)) { 4258 originalHandler(e); 4259 } 4260 }; 4261 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); 4262 4263 } else { 4264 if (type === 'click' && L.Browser.android) { 4265 handler = function (e) { 4266 return L.DomEvent._filterClick(e, originalHandler); 4267 }; 4268 } 4269 obj.addEventListener(type, handler, false); 4270 } 4271 4272 } else if ('attachEvent' in obj) { 4273 obj.attachEvent('on' + type, handler); 4274 } 4275 4276 obj[eventsKey] = obj[eventsKey] || {}; 4277 obj[eventsKey][id] = handler; 4278 4279 return this; 4280 }, 4281 4282 _off: function (obj, type, fn, context) { 4283 4284 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''), 4285 handler = obj[eventsKey] && obj[eventsKey][id]; 4286 4287 if (!handler) { return this; } 4288 4289 if (L.Browser.pointer && type.indexOf('touch') === 0) { 4290 this.removePointerListener(obj, type, id); 4291 4292 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { 4293 this.removeDoubleTapListener(obj, id); 4294 4295 } else if ('removeEventListener' in obj) { 4296 4297 if (type === 'mousewheel') { 4298 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); 4299 4300 } else { 4301 obj.removeEventListener( 4302 type === 'mouseenter' ? 'mouseover' : 4303 type === 'mouseleave' ? 'mouseout' : type, handler, false); 4304 } 4305 4306 } else if ('detachEvent' in obj) { 4307 obj.detachEvent('on' + type, handler); 4308 } 4309 4310 obj[eventsKey][id] = null; 4311 4312 return this; 4313 }, 4314 4315 // @function stopPropagation(ev: DOMEvent): this 4316 // Stop the given event from propagation to parent elements. Used inside the listener functions: 4317 // ```js 4318 // L.DomEvent.on(div, 'click', function (ev) { 4319 // L.DomEvent.stopPropagation(ev); 4320 // }); 4321 // ``` 4322 stopPropagation: function (e) { 4323 4324 if (e.stopPropagation) { 4325 e.stopPropagation(); 4326 } else if (e.originalEvent) { // In case of Leaflet event. 4327 e.originalEvent._stopped = true; 4328 } else { 4329 e.cancelBubble = true; 4330 } 4331 L.DomEvent._skipped(e); 4332 4333 return this; 4334 }, 4335 4336 // @function disableScrollPropagation(el: HTMLElement): this 4337 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). 4338 disableScrollPropagation: function (el) { 4339 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation); 4340 }, 4341 4342 // @function disableClickPropagation(el: HTMLElement): this 4343 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, 4344 // `'mousedown'` and `'touchstart'` events (plus browser variants). 4345 disableClickPropagation: function (el) { 4346 var stop = L.DomEvent.stopPropagation; 4347
4348 L.DomEvent.on(el, L.Draggable.START.join(' '), stop); 4349 4350 return L.DomEvent.on(el, { 4351 click: L.DomEvent._fakeStop, 4352 dblclick: stop 4353 }); 4354 }, 4355 4356 // @function preventDefault(ev: DOMEvent): this 4357 // Prevents the default action of the DOM Event `ev` from happening (such as 4358 // following a link in the href of the a element, or doing a POST request 4359 // with page reload when a `<form>` is submitted). 4360 // Use it inside listener functions. 4361 preventDefault: function (e) { 4362 4363 if (e.preventDefault) { 4364 e.preventDefault(); 4365 } else { 4366 e.returnValue = false; 4367 } 4368 return this; 4369 }, 4370 4371 // @function stop(ev): this 4372 // Does `stopPropagation` and `preventDefault` at the same time. 4373 stop: function (e) { 4374 return L.DomEvent 4375 .preventDefault(e) 4376 .stopPropagation(e); 4377 }, 4378 4379 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 4380 // Gets normalized mouse position from a DOM event relative to the 4381 // `container` or to the whole page if not specified. 4382 getMousePosition: function (e, container) { 4383 if (!container) { 4384 return new L.Point(e.clientX, e.clientY); 4385 } 4386 4387 var rect = container.getBoundingClientRect(); 4388 4389 return new L.Point( 4390 e.clientX - rect.left - container.clientLeft, 4391 e.clientY - rect.top - container.clientTop); 4392 }, 4393 4394 // Chrome on Win scrolls double the pixels as in other platforms (see #4538), 4395 // and Firefox scrolls device pixels, not CSS pixels 4396 _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 : 4397 L.Browser.gecko ? window.devicePixelRatio : 4398 1, 4399 4400 // @function getWheelDelta(ev: DOMEvent): Number 4401 // Gets normalized wheel delta from a mousewheel DOM event, in vertical 4402 // pixels scrolled (negative if scrolling down). 4403 // Events from pointing devices without precise scrolling are mapped to 4404 // a best guess of 60 pixels. 4405 getWheelDelta: function (e) { 4406 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 4407 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels 4408 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 4409 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 4410 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 4411 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 4412 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 4413 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 4414 0; 4415 }, 4416 4417 _skipEvents: {}, 4418 4419 _fakeStop: function (e) { 4420 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e) 4421 L.DomEvent._skipEvents[e.type] = true; 4422 }, 4423 4424 _skipped: function (e) { 4425 var skipped = this._skipEvents[e.type]; 4426 // reset when checking, as it's only used in map container and propagates outside of the map 4427 this._skipEvents[e.type] = false; 4428 return skipped; 4429 }, 4430 4431 // check if element really left/entered the event target (for mouseenter/mouseleave) 4432 _isExternalTarget: function (el, e) { 4433 4434 var related = e.relatedTarget; 4435 4436 if (!related) { return true; } 4437 4438 try { 4439 while (related && (related !== el)) { 4440 related = related.parentNode; 4441 } 4442 } catch (err) { 4443 return false; 4444 } 4445 return (related !== el); 4446 }, 4447 4448 // this is a horrible workaround for a bug in Android where a single touch triggers two click events 4449 _filterClick: function (e, handler) { 4450 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), 4451 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); 4452 4453 // are they closer together than 500ms yet more than 100ms? 4454 // Android typically triggers them ~300ms apart while multiple listeners 4455 // on the same event should be triggered far faster; 4456 // or check if click is simulated on the element, and if it is, reject any non-simulated events 4457 4458 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { 4459 L.DomEvent.stop(e); 4460 return; 4461 } 4462 L.DomEvent._lastClick = timeStamp; 4463 4464 handler(e); 4465 } 4466}; 4467 4468// @function addListener(â¦): this 4469// Alias to [`L.DomEvent.on`](#domevent-on) 4470L.DomEvent.addListener = L.DomEvent.on; 4471 4472// @function removeListener(â¦): this 4473// Alias to [`L.DomEvent.off`](#domevent-off) 4474L.DomEvent.removeListener = L.DomEvent.off; 4475 4476 4477 4478/* 4479 * @class PosAnimation 4480 * @aka L.PosAnimation 4481 * @inherits Evented 4482 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 4483 * 4484 * @example 4485 * ```js 4486 * var fx = new L.PosAnimation(); 4487 * fx.run(el, [300, 500], 0.5); 4488 * ``` 4489 * 4490 * @constructor L.PosAnimation() 4491 * Creates a `PosAnimation` object. 4492 * 4493 */ 4494 4495L.PosAnimation = L.Evented.extend({ 4496 4497 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?:
vendor: 24,665 bytes, lines 4497-5398
4497Number) 4498 // Run an animation of a given element to a new position, optionally setting 4499 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 4500 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), 4501 // `0.5` by default). 4502 run: function (el, newPos, duration, easeLinearity) { 4503 this.stop(); 4504 4505 this._el = el; 4506 this._inProgress = true; 4507 this._duration = duration || 0.25; 4508 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 4509 4510 this._startPos = L.DomUtil.getPosition(el); 4511 this._offset = newPos.subtract(this._startPos); 4512 this._startTime = +new Date(); 4513 4514 // @event start: Event 4515 // Fired when the animation starts 4516 this.fire('start'); 4517 4518 this._animate(); 4519 }, 4520 4521 // @method stop() 4522 // Stops the animation (if currently running). 4523 stop: function () { 4524 if (!this._inProgress) { return; } 4525 4526 this._step(true); 4527 this._complete(); 4528 }, 4529 4530 _animate: function () { 4531 // animation loop 4532 this._animId = L.Util.requestAnimFrame(this._animate, this); 4533 this._step(); 4534 }, 4535 4536 _step: function (round) { 4537 var elapsed = (+new Date()) - this._startTime, 4538 duration = this._duration * 1000; 4539 4540 if (elapsed < duration) { 4541 this._runFrame(this._easeOut(elapsed / duration), round); 4542 } else { 4543 this._runFrame(1); 4544 this._complete(); 4545 } 4546 }, 4547 4548 _runFrame: function (progress, round) { 4549 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 4550 if (round) { 4551 pos._round(); 4552 } 4553 L.DomUtil.setPosition(this._el, pos); 4554 4555 // @event step: Event 4556 // Fired continuously during the animation. 4557 this.fire('step'); 4558 }, 4559 4560 _complete: function () { 4561 L.Util.cancelAnimFrame(this._animId); 4562 4563 this._inProgress = false; 4564 // @event end: Event 4565 // Fired when the animation ends. 4566 this.fire('end'); 4567 }, 4568 4569 _easeOut: function (t) { 4570 return 1 - Math.pow(1 - t, this._easeOutPower); 4571 } 4572}); 4573 4574 4575 4576/* 4577 * @namespace Projection 4578 * @projection L.Projection.Mercator 4579 * 4580 * Elliptical Mercator projection â more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS. 4581 */ 4582 4583L.Projection.Mercator = { 4584 R: 6378137, 4585 R_MINOR: 6356752.314245179, 4586 4587 bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 4588 4589 project: function (latlng) { 4590 var d = Math.PI / 180, 4591 r = this.R, 4592 y = latlng.lat * d, 4593 tmp = this.R_MINOR / r, 4594 e = Math.sqrt(1 - tmp * tmp), 4595 con = e * Math.sin(y); 4596 4597 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 4598 y = -r * Math.log(Math.max(ts, 1E-10)); 4599 4600 return new L.Point(latlng.lng * d * r, y); 4601 }, 4602 4603 unproject: function (point) { 4604 var d = 180 / Math.PI, 4605 r = this.R, 4606 tmp = this.R_MINOR / r, 4607 e = Math.sqrt(1 - tmp * tmp), 4608 ts = Math.exp(-point.y / r), 4609 phi = Math.PI / 2 - 2 * Math.atan(ts); 4610 4611 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 4612 con = e * Math.sin(phi); 4613 con = Math.pow((1 - con) / (1 + con), e / 2); 4614 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 4615 phi += dphi; 4616 } 4617 4618 return new L.LatLng(phi * d, point.x * d / r); 4619 } 4620}; 4621 4622 4623 4624/* 4625 * @namespace CRS 4626 * @crs L.CRS.EPSG3395 4627 * 4628 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 4629 */ 4630 4631L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, { 4632 code: 'EPSG:3395', 4633 projection: L.Projection.Mercator, 4634 4635 transformation: (function () { 4636 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R); 4637 return new L.Transformation(scale, 0.5, -scale, 0.5); 4638 }()) 4639}); 4640 4641 4642 4643/* 4644 * @class GridLayer 4645 * @inherits Layer 4646 * @aka L.GridLayer 4647 * 4648 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 4649 * 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. 4650 * 4651 * 4652 * @section Synchronous usage 4653 * @example 4654 * 4655 * 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. 4656 * 4657 * ```js 4658 * var CanvasLayer = L.GridLayer.extend({ 4659 * createTile: function(coords){ 4660 * // create a <canvas> element for drawing 4661 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 4662 * 4663 * // setup tile width and height according to the options 4664 * var size = this.getTileSize(); 4665 * tile.width = size.x; 4666 * tile.height = size.y; 4667 * 4668 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 4669 * var ctx = tile.getContext('2d'); 4670 * 4671 * // return the tile so it can be rendered on screen 4672 * return tile; 4673 * } 4674 * }); 4675 * ``` 4676 * 4677 * @section Asynchronous usage 4678 * @example 4679 * 4680 * 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. 4681 * 4682 * ```js 4683 * var CanvasLayer = L.GridLayer.extend({ 4684 * createTile: function(coords, done){ 4685 * var error; 4686 * 4687 * // create a <canvas> element for drawing 4688 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 4689 * 4690 * // setup tile width and height according to the options 4691 * var size = this.getTileSize(); 4692 * tile.width = size.x; 4693 * tile.height = size.y; 4694 * 4695 * // draw something asynchronously and pass the tile to the done() callback 4696 * setTimeout(function() { 4697 * done(error, tile); 4698 * }, 1000); 4699 * 4700 * return tile; 4701 * } 4702 * }); 4703 * ``` 4704 * 4705 * @section 4706 */ 4707 4708 4709L.GridLayer = L.Layer.extend({ 4710 4711 // @section 4712 // @aka GridLayer options 4713 options: { 4714 // @option tileSize: Number|Point = 256 4715 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 4716 tileSize: 256, 4717 4718 // @option opacity: Number = 1.0 4719 // Opacity of the tiles. Can be used in the `createTile()` function. 4720 opacity: 1, 4721 4722 // @option updateWhenIdle: Boolean = depends 4723 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`. 4724 updateWhenIdle: L.Browser.mobile, 4725 4726 // @option updateWhenZooming: Boolean = true 4727 // 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. 4728 updateWhenZooming: true, 4729 4730 // @option updateInterval: Number = 200 4731 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 4732 updateInterval: 200, 4733 4734 // @option zIndex: Number = 1 4735 // The explicit zIndex of the tile layer. 4736 zIndex: 1, 4737 4738 // @option bounds: LatLngBounds = undefined 4739 // If set, tiles will only be loaded inside the set `LatLngBounds`. 4740 bounds: null, 4741 4742 // @option minZoom: Number = 0 4743 // The minimum zoom level that tiles will be loaded at. By default the entire map. 4744 minZoom: 0, 4745 4746 // @option maxZoom: Number = undefined 4747 // The maximum zoom level that tiles will be loaded at. 4748 maxZoom: undefined, 4749 4750 // @option noWrap: Boolean = false 4751 // Whether the layer is wrapped around the antimeridian. If `true`, the 4752 // GridLayer will only be displayed once at low zoom levels. Has no 4753 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 4754 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 4755 // tiles outside the CRS limits. 4756 noWrap: false, 4757 4758 // @option pane: String = 'tilePane' 4759 // `Map pane` where the grid layer will be added. 4760 pane: 'tilePane', 4761 4762 // @option className: String = '' 4763 // A custom class name to assign to the tile layer. Empty by default. 4764 className: '', 4765 4766 // @option keepBuffer: Number = 2 4767 // When panning the map, keep this many rows and columns of tiles before unloading them. 4768 keepBuffer: 2 4769 }, 4770 4771 initialize: function (options) { 4772 L.setOptions(this, options); 4773 }, 4774 4775 onAdd: function () { 4776 this._initContainer(); 4777 4778 this._levels = {}; 4779 this._tiles = {}; 4780 4781 this._resetView(); 4782 this._update(); 4783 }, 4784 4785 beforeAdd: function (map) { 4786 map._addZoomLimit(this); 4787 }, 4788 4789 onRemove: function (map) { 4790 this._removeAllTiles(); 4791 L.DomUtil.remove(this._container); 4792 map._removeZoomLimit(this); 4793 this._container = null; 4794 this._tileZoom = null; 4795 }, 4796 4797 // @method bringToFront: this 4798 // Brings the tile layer to the top of all tile layers. 4799 bringToFront: function () { 4800 if (this._map) { 4801 L.DomUtil.toFront(this._container); 4802 this._setAutoZIndex(Math.max); 4803 } 4804 return this; 4805 }, 4806 4807 // @method bringToBack: this 4808 // Brings the tile layer to the bottom of all tile layers. 4809 bringToBack: function () { 4810 if (this._map) { 4811 L.DomUtil.toBack(this._container); 4812 this._setAutoZIndex(Math.min); 4813 } 4814 return this; 4815 }, 4816 4817 // @method getContainer: HTMLElement 4818 // Returns the HTML element that contains the tiles for this layer. 4819 getContainer: function () { 4820 return this._container; 4821 }, 4822 4823 // @method setOpacity(opacity: Number): this 4824 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 4825 setOpacity: function (opacity) { 4826 this.options.opacity = opacity; 4827 this._updateOpacity(); 4828 return this; 4829 }, 4830 4831 // @method setZIndex(zIndex: Number): this 4832 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 4833 setZIndex: function (zIndex) { 4834 this.options.zIndex = zIndex; 4835 this._updateZIndex(); 4836 4837 return this; 4838 }, 4839 4840 // @method isLoading: Boolean 4841 // Returns `true` if any tile in the grid layer has not finished loading. 4842 isLoading: function () { 4843 return this._loading; 4844 }, 4845 4846 // @method redraw: this 4847 // Causes the layer to clear all the tiles and request them again. 4848 redraw: function () { 4849 if (this._map) { 4850 this._removeAllTiles(); 4851 this._update(); 4852 } 4853 return this; 4854 }, 4855 4856 getEvents: function () { 4857 var events = { 4858 viewprereset: this._invalidateAll, 4859 viewreset: this._resetView, 4860 zoom: this._resetView, 4861 moveend: this._onMoveEnd 4862 }; 4863 4864 if (!this.options.updateWhenIdle) { 4865 // update tiles on move, but not more often than once per given interval 4866 if (!this._onMove) { 4867 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this); 4868 } 4869 4870 events.move = this._onMove; 4871 } 4872 4873 if (this._zoomAnimated) { 4874 events.zoomanim = this._animateZoom; 4875 } 4876 4877 return events; 4878 }, 4879 4880 // @section Extension methods 4881 // Layers extending `GridLayer` shall reimplement the following method. 4882 // @method createTile(coords: Object, done?: Function): HTMLElement 4883 // Called only internally, must be overriden by classes extending `GridLayer`. 4884 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 4885 // is specified, it must be called when the tile has finished loading and drawing. 4886 createTile: function () { 4887 return document.createElement('div'); 4888 }, 4889 4890 // @section 4891 // @method getTileSize: Point 4892 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 4893 getTileSize: function () { 4894 var s = this.options.tileSize; 4895 return s instanceof L.Point ? s : new L.Point(s, s); 4896 }, 4897 4898 _updateZIndex: function () { 4899 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 4900 this._container.style.zIndex = this.options.zIndex; 4901 } 4902 }, 4903 4904 _setAutoZIndex: function (compare) { 4905 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 4906 4907 var layers = this.getPane().children, 4908 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 4909 4910 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 4911 4912 zIndex = layers[i].style.zIndex; 4913 4914 if (layers[i] !== this._container && zIndex) { 4915 edgeZIndex = compare(edgeZIndex, +zIndex); 4916 } 4917 } 4918 4919 if (isFinite(edgeZIndex)) { 4920 this.options.zIndex = edgeZIndex + compare(-1, 1); 4921 this._updateZIndex(); 4922 } 4923 }, 4924 4925 _updateOpacity: function () { 4926 if (!this._map) { return; } 4927 4928 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 4929 if (L.Browser.ielt9) { return; } 4930 4931 L.DomUtil.setOpacity(this._container, this.options.opacity); 4932 4933 var now = +new Date(), 4934 nextFrame = false, 4935 willPrune = false; 4936 4937 for (var key in this._tiles) { 4938 var tile = this._tiles[key]; 4939 if (!tile.current || !tile.loaded) { continue; } 4940 4941 var fade = Math.min(1, (now - tile.loaded) / 200); 4942 4943 L.DomUtil.setOpacity(tile.el, fade); 4944 if (fade < 1) { 4945 nextFrame = true; 4946 } else { 4947 if (tile.active) { willPrune = true; } 4948 tile.active = true; 4949 } 4950 } 4951 4952 if (willPrune && !this._noPrune) { this._pruneTiles(); } 4953 4954 if (nextFrame) { 4955 L.Util.cancelAnimFrame(this._fadeFrame); 4956 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); 4957 } 4958 }, 4959 4960 _initContainer: function () { 4961 if (this._container) { return; } 4962 4963 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || '')); 4964 this._updateZIndex(); 4965 4966 if (this.options.opacity < 1) { 4967 this._updateOpacity(); 4968 } 4969 4970 this.getPane().appendChild(this._container); 4971 }, 4972 4973 _updateLevels: function () { 4974 4975 var zoom = this._tileZoom, 4976 maxZoom = this.options.maxZoom; 4977 4978 if (zoom === undefined) { return undefined; } 4979 4980 for (var z in this._levels) { 4981 if (this._levels[z].el.children.length || z === zoom) { 4982 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 4983 } else { 4984 L.DomUtil.remove(this._levels[z].el); 4985 this._removeTilesAtZoom(z); 4986 delete this._levels[z]; 4987 } 4988 } 4989 4990 var level = this._levels[zoom], 4991 map = this._map; 4992 4993 if (!level) { 4994 level = this._levels[zoom] = {}; 4995 4996 level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 4997 level.el.style.zIndex = maxZoom; 4998 4999 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 5000 level.zoom = zoom; 5001 5002 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 5003 5004 // force the browser to consider the newly added element for transition 5005 L.Util.falseFn(level.el.offsetWidth); 5006 } 5007 5008 this._level = level; 5009 5010 return level; 5011 }, 5012 5013 _pruneTiles: function () { 5014 if (!this._map) { 5015 return; 5016 } 5017 5018 var key, tile; 5019 5020 var zoom = this._map.getZoom(); 5021 if (zoom > this.options.maxZoom || 5022 zoom < this.options.minZoom) { 5023 this._removeAllTiles(); 5024 return; 5025 } 5026 5027 for (key in this._tiles) { 5028 tile = this._tiles[key]; 5029 tile.retain = tile.current; 5030 } 5031 5032 for (key in this._tiles) { 5033 tile = this._tiles[key]; 5034 if (tile.current && !tile.active) { 5035 var coords = tile.coords; 5036 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 5037 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 5038 } 5039 } 5040 } 5041 5042 for (key in this._tiles) { 5043 if (!this._tiles[key].retain) { 5044 this._removeTile(key); 5045 } 5046 } 5047 }, 5048 5049 _removeTilesAtZoom: function (zoom) { 5050 for (var key in this._tiles) { 5051 if (this._tiles[key].coords.z !== zoom) { 5052 continue; 5053 } 5054 this._removeTile(key); 5055 } 5056 }, 5057 5058 _removeAllTiles: function () { 5059 for (var key in this._tiles) { 5060 this._removeTile(key); 5061 } 5062 }, 5063 5064 _invalidateAll: function () { 5065 for (var z in this._levels) { 5066 L.DomUtil.remove(this._levels[z].el); 5067 delete this._levels[z]; 5068 } 5069 this._removeAllTiles(); 5070 5071 this._tileZoom = null; 5072 }, 5073 5074 _retainParent: function (x, y, z, minZoom) { 5075 var x2 = Math.floor(x / 2), 5076 y2 = Math.floor(y / 2), 5077 z2 = z - 1, 5078 coords2 = new L.Point(+x2, +y2); 5079 coords2.z = +z2; 5080 5081 var key = this._tileCoordsToKey(coords2), 5082 tile = this._tiles[key]; 5083 5084 if (tile && tile.active) { 5085 tile.retain = true; 5086 return true; 5087 5088 } else if (tile && tile.loaded) { 5089 tile.retain = true; 5090 } 5091 5092 if (z2 > minZoom) { 5093 return this._retainParent(x2, y2, z2, minZoom); 5094 } 5095 5096 return false; 5097 }, 5098 5099 _retainChildren: function (x, y, z, maxZoom) { 5100 5101 for (var i = 2 * x; i < 2 * x + 2; i++) { 5102 for (var j = 2 * y; j < 2 * y + 2; j++) { 5103 5104 var coords = new L.Point(i, j); 5105 coords.z = z + 1; 5106 5107 var key = this._tileCoordsToKey(coords), 5108 tile = this._tiles[key]; 5109 5110 if (tile && tile.active) { 5111 tile.retain = true; 5112 continue; 5113 5114 } else if (tile && tile.loaded) { 5115 tile.retain = true; 5116 } 5117 5118 if (z + 1 < maxZoom) { 5119 this._retainChildren(i, j, z + 1, maxZoom); 5120 } 5121 } 5122 } 5123 }, 5124 5125 _resetView: function (e) { 5126 var animating = e && (e.pinch || e.flyTo); 5127 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 5128 }, 5129 5130 _animateZoom: function (e) { 5131 this._setView(e.center, e.zoom, true, e.noUpdate); 5132 }, 5133 5134 _setView: function (center, zoom, noPrune, noUpdate) { 5135 var tileZoom = Math.round(zoom); 5136 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 5137 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 5138 tileZoom = undefined; 5139 } 5140 5141 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 5142 5143 if (!noUpdate || tileZoomChanged) { 5144 5145 this._tileZoom = tileZoom; 5146 5147 if (this._abortLoading) { 5148 this._abortLoading(); 5149 } 5150 5151 this._updateLevels(); 5152 this._resetGrid(); 5153 5154 if (tileZoom !== undefined) { 5155 this._update(center); 5156 } 5157 5158 if (!noPrune) { 5159 this._pruneTiles(); 5160 } 5161 5162 // Flag to prevent _updateOpacity from pruning tiles during 5163 // a zoom anim or a pinch gesture 5164 this._noPrune = !!noPrune; 5165 } 5166 5167 this._setZoomTransforms(center, zoom); 5168 }, 5169 5170 _setZoomTransforms: function (center, zoom) { 5171 for (var i in this._levels) { 5172 this._setZoomTransform(this._levels[i], center, zoom); 5173 } 5174 }, 5175 5176 _setZoomTransform: function (level, center, zoom) { 5177 var scale = this._map.getZoomScale(zoom, level.zoom), 5178 translate = level.origin.multiplyBy(scale) 5179 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 5180 5181 if (L.Browser.any3d) { 5182 L.DomUtil.setTransform(level.el, translate, scale); 5183 } else { 5184 L.DomUtil.setPosition(level.el, translate); 5185 } 5186 }, 5187 5188 _resetGrid: function () { 5189 var map = this._map, 5190 crs = map.options.crs, 5191 tileSize = this._tileSize = this.getTileSize(), 5192 tileZoom = this._tileZoom; 5193 5194 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 5195 if (bounds) { 5196 this._globalTileRange = this._pxBoundsToTileRange(bounds); 5197 } 5198 5199 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 5200 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 5201 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 5202 ]; 5203 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 5204 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 5205 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 5206 ]; 5207 }, 5208 5209 _onMoveEnd: function () { 5210 if (!this._map || this._map._animatingZoom) { return; } 5211 5212 this._update(); 5213 }, 5214 5215 _getTiledPixelBounds: function (center) { 5216 var map = this._map, 5217 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 5218 scale = map.getZoomScale(mapZoom, this._tileZoom), 5219 pixelCenter = map.project(center, this._tileZoom).floor(), 5220 halfSize = map.getSize().divideBy(scale * 2); 5221 5222 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 5223 }, 5224 5225 // Private method to load tiles in the grid's active zoom level according to map bounds 5226 _update: function (center) { 5227 var map = this._map; 5228 if (!map) { return; } 5229 var zoom = map.getZoom(); 5230 5231 if (center === undefined) { center = map.getCenter(); } 5232 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 5233 5234 var pixelBounds = this._getTiledPixelBounds(center), 5235 tileRange = this._pxBoundsToTileRange(pixelBounds), 5236 tileCenter = tileRange.getCenter(), 5237 queue = [], 5238 margin = this.options.keepBuffer, 5239 noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 5240 tileRange.getTopRight().add([margin, -margin])); 5241 5242 for (var key in this._tiles) { 5243 var c = this._tiles[key].coords; 5244 if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) { 5245 this._tiles[key].current = false; 5246 } 5247 } 5248 5249 // _update just loads more tiles. If the tile zoom level differs too much 5250 // from the map's, let _setView reset levels and prune old tiles. 5251 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 5252 5253 // create a queue of coordinates to load tiles from 5254 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 5255 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 5256 var coords = new L.Point(i, j); 5257 coords.z = this._tileZoom; 5258 5259 if (!this._isValidTile(coords)) { continue; } 5260 5261 var tile = this._tiles[this._tileCoordsToKey(coords)]; 5262 if (tile) { 5263 tile.current = true; 5264 } else { 5265 queue.push(coords); 5266 } 5267 } 5268 } 5269 5270 // sort tile queue to load tiles in order of their distance to center 5271 queue.sort(function (a, b) { 5272 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 5273 }); 5274 5275 if (queue.length !== 0) { 5276 // if it's the first batch of tiles to load 5277 if (!this._loading) { 5278 this._loading = true; 5279 // @event loading: Event 5280 // Fired when the grid layer starts loading tiles. 5281 this.fire('loading'); 5282 } 5283 5284 // create DOM fragment to append tiles in one batch 5285 var fragment = document.createDocumentFragment(); 5286 5287 for (i = 0; i < queue.length; i++) { 5288 this._addTile(queue[i], fragment); 5289 } 5290 5291 this._level.el.appendChild(fragment); 5292 } 5293 }, 5294 5295 _isValidTile: function (coords) { 5296 var crs = this._map.options.crs; 5297 5298 if (!crs.infinite) { 5299 // don't load tile if it's out of bounds and not wrapped 5300 var bounds = this._globalTileRange; 5301 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 5302 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 5303 } 5304 5305 if (!this.options.bounds) { return true; } 5306 5307 // don't load tile if it doesn't intersect the bounds in options 5308 var tileBounds = this._tileCoordsToBounds(coords); 5309 return L.latLngBounds(this.options.bounds).overlaps(tileBounds); 5310 }, 5311 5312 _keyToBounds: function (key) { 5313 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 5314 }, 5315 5316 // converts tile coordinates to its geographical bounds 5317 _tileCoordsToBounds: function (coords) { 5318 5319 var map = this._map, 5320 tileSize = this.getTileSize(), 5321 5322 nwPoint = coords.scaleBy(tileSize), 5323 sePoint = nwPoint.add(tileSize), 5324 5325 nw = map.unproject(nwPoint, coords.z), 5326 se = map.unproject(sePoint, coords.z), 5327 bounds = new L.LatLngBounds(nw, se); 5328 5329 if (!this.options.noWrap) { 5330 map.wrapLatLngBounds(bounds); 5331 } 5332 5333 return bounds; 5334 }, 5335 5336 // converts tile coordinates to key for the tile cache 5337 _tileCoordsToKey: function (coords) { 5338 return coords.x + ':' + coords.y + ':' + coords.z; 5339 }, 5340 5341 // converts tile cache key to coordinates 5342 _keyToTileCoords: function (key) { 5343 var k = key.split(':'), 5344 coords = new L.Point(+k[0], +k[1]); 5345 coords.z = +k[2]; 5346 return coords; 5347 }, 5348 5349 _removeTile: function (key) { 5350 var tile = this._tiles[key]; 5351 if (!tile) { return; } 5352 5353 L.DomUtil.remove(tile.el); 5354 5355 delete this._tiles[key]; 5356 5357 // @event tileunload: TileEvent 5358 // Fired when a tile is removed (e.g. when a tile goes off the screen). 5359 this.fire('tileunload', { 5360 tile: tile.el, 5361 coords: this._keyToTileCoords(key) 5362 }); 5363 }, 5364 5365 _initTile: function (tile) { 5366 L.DomUtil.addClass(tile, 'leaflet-tile'); 5367 5368 var tileSize = this.getTileSize(); 5369 tile.style.width = tileSize.x + 'px'; 5370 tile.style.height = tileSize.y + 'px'; 5371 5372 tile.onselectstart = L.Util.falseFn; 5373 tile.onmousemove = L.Util.falseFn; 5374 5375 // update opacity on tiles in IE7-8 because of filter inheritance problems 5376 if (L.Browser.ielt9 && this.options.opacity < 1) { 5377 L.DomUtil.setOpacity(tile, this.options.opacity); 5378 } 5379 5380 // without this hack, tiles disappear after zoom on Chrome for Android 5381 // https://github.com/Leaflet/Leaflet/issues/2078 5382 if (L.Browser.android && !L.Browser.android23) { 5383 tile.style.WebkitBackfaceVisibility = 'hidden'; 5384 } 5385 }, 5386 5387 _addTile: function (coords, container) { 5388 var tilePos = this._getTilePos(coords), 5389 key = this._tileCoordsToKey(coords); 5390 5391 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords)); 5392 5393 this._initTile(tile); 5394 5395 // if createTile is defined with a second argument ("done" callback), 5396 // we know that tile is async and will be ready later; otherwise 5397 if (this.createTile.length < 2) { 5398 // mark tile as ready, but delay one frame for opacity animation to happen
5399 L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile)); 5400 } 5401 5402 L.DomUtil.setPosition(tile, tilePos); 5403 5404 // save tile in cache 5405 this._tiles[key] = { 5406 el: tile, 5407 coords: coords, 5408 current: true 5409 }; 5410 5411 container.appendChild(tile); 5412 // @event tileloadstart: TileEvent 5413 // Fired when a tile is requested and starts loading. 5414 this.fire('tileloadstart', { 5415 tile: tile, 5416 coords: coords 5417 }); 5418 }, 5419 5420 _tileReady: function (coords, err, tile) { 5421 if (!this._map) { return; } 5422 5423 if (err) { 5424 // @event tileerror: TileErrorEvent 5425 // Fired when there is an error loading a tile. 5426 this.fire('tileerror', { 5427 error: err, 5428 tile: tile, 5429 coords: coords 5430 }); 5431 } 5432 5433 var key = this._tileCoordsToKey(coords); 5434 5435 tile = this._tiles[key]; 5436 if (!tile) { return; } 5437 5438 tile.loaded = +new Date(); 5439 if (this._map._fadeAnimated) { 5440 L.DomUtil.setOpacity(tile.el, 0); 5441 L.Util.cancelAnimFrame(this._fadeFrame); 5442 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); 5443 } else { 5444 tile.active = true; 5445 this._pruneTiles(); 5446 } 5447 5448 if (!err) { 5449 L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded'); 5450 5451 // @event tileload: TileEvent 5452 // Fired when a tile loads. 5453 this.fire('tileload', { 5454 tile: tile.el, 5455 coords: coords 5456 }); 5457 } 5458 5459 if (this._noTilesToLoad()) { 5460 this._loading = false; 5461 // @event load: Event 5462 // Fired when the grid layer loaded all visible tiles. 5463 this.fire('load'); 5464 5465 if (L.Browser.ielt9 || !this._map._fadeAnimated) { 5466 L.Util.requestAnimFrame(this._pruneTiles, this); 5467 } else { 5468 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 5469 // to trigger a pruning. 5470 setTimeout(L.bind(this._pruneTiles, this), 250); 5471 } 5472 } 5473 }, 5474 5475 _getTilePos: function (coords) { 5476 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 5477 }, 5478 5479 _wrapCoords: function (coords) { 5480 var newCoords = new L.Point( 5481 this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x, 5482 this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y); 5483 newCoords.z = coords.z; 5484 return newCoords; 5485 }, 5486 5487 _pxBoundsToTileRange: function (bounds) { 5488 var tileSize = this.getTileSize(); 5489 return new L.Bounds( 5490 bounds.min.unscaleBy(tileSize).floor(), 5491 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 5492 }, 5493 5494 _noTilesToLoad: function () { 5495 for (var key in this._tiles) { 5496 if (!this._tiles[key].loaded) { return false; } 5497 } 5498 return true; 5499 } 5500}); 5501 5502// @factory L.gridLayer(options?: GridLayer options) 5503// Creates a new instance of GridLayer with the supplied options. 5504L.gridLayer = function (options) { 5505 return new L.GridLayer(options); 5506}; 5507 5508 5509 5510/* 5511 * @class TileLayer 5512 * @inherits GridLayer 5513 * @aka L.TileLayer 5514 * Used to load and display tile layers on the map. Extends `GridLayer`. 5515 * 5516 * @example 5517 * 5518 * ```js 5519 * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map); 5520 * ``` 5521 * 5522 * @section URL template 5523 * @example 5524 * 5525 * A string of the following form: 5526 * 5527 * ``` 5528 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 5529 * ``` 5530 * 5531 * `{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. 5532 * 5533 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 5534 * 5535 * ``` 5536 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 5537 * ``` 5538 */ 5539 5540 5541L.TileLayer = L.GridLayer.extend({ 5542 5543 // @section 5544 // @aka TileLayer options 5545 options: { 5546 // @option minZoom: Number = 0 5547 // Minimum zoom number. 5548 minZoom: 0, 5549 5550 // @option maxZoom: Number = 18 5551 // Maximum zoom number. 5552 maxZoom: 18, 5553 5554 // @option maxNativeZoom: Number = null 5555 // Maximum zoom number the tile source has available. If it is specified, 5556 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 5557 // from `maxNativeZoom` level and auto-scaled. 5558 maxNativeZoom: null, 5559
5560 // @option minNativeZoom: Number = null 5561 // Minimum zoom number the tile source has available. If it is specified, 5562 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 5563 // from `minNativeZoom` level and auto-scaled. 5564 minNativeZoom: null, 5565 5566 // @option subdomains: String|String[] = 'abc' 5567 // 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. 5568 subdomains: 'abc', 5569 5570 // @option errorTileUrl: String = '' 5571 // URL to the tile image to show in place of the tile that failed to load. 5572 errorTileUrl: '', 5573 5574 // @option zoomOffset: Number = 0 5575 // The zoom number used in tile URLs will be offset with this value. 5576 zoomOffset: 0, 5577 5578 // @option tms: Boolean = false 5579 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 5580 tms: false, 5581 5582 // @option zoomReverse: Boolean = false 5583 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 5584 zoomReverse: false, 5585 5586 // @option detectRetina: Boolean = false 5587 // 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. 5588 detectRetina: false, 5589 5590 // @option crossOrigin: Boolean = false 5591 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data. 5592 crossOrigin: false 5593 }, 5594 5595 initialize: function (url, options) { 5596 5597 this._url = url; 5598 5599 options = L.setOptions(this, options); 5600 5601 // detecting retina displays, adjusting tileSize and zoom levels 5602 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { 5603 5604 options.tileSize = Math.floor(options.tileSize / 2); 5605 5606 if (!options.zoomReverse) { 5607 options.zoomOffset++; 5608 options.maxZoom--; 5609 } else { 5610 options.zoomOffset--; 5611 options.minZoom++; 5612 } 5613 5614 options.minZoom = Math.max(0, options.minZoom); 5615 } 5616 5617 if (typeof options.subdomains === 'string') { 5618 options.subdomains = options.subdomains.split(''); 5619 } 5620 5621 // for https://github.com/Leaflet/Leaflet/issues/137 5622 if (!L.Browser.android) { 5623 this.on('tileunload', this._onTileRemove); 5624 } 5625 }, 5626 5627 // @method setUrl(url: String, noRedraw?: Boolean): this 5628 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 5629 setUrl: function (url, noRedraw) { 5630 this._url = url; 5631 5632 if (!noRedraw) { 5633 this.redraw(); 5634 } 5635 return this; 5636 }, 5637 5638 // @method createTile(coords: Object, done?: Function): HTMLElement 5639 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 5640 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done` 5641 // callback is called when the tile has been loaded. 5642 createTile: function (coords, done) { 5643 var tile = document.createElement('img'); 5644 5645 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile)); 5646 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile)); 5647 5648 if (this.options.crossOrigin) { 5649 tile.crossOrigin = ''; 5650 } 5651 5652 /* 5653 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons 5654 http://www.w3.org/TR/WCAG20-TECHS/H67 5655 */ 5656 tile.alt = ''; 5657 5658 /* 5659 Set role="presentation" to force screen readers to ignore this 5660 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation 5661 */ 5662 tile.setAttribute('role', 'presentation'); 5663 5664 tile.src = this.getTileUrl(coords); 5665 5666 return tile; 5667 }, 5668 5669 // @section Extension methods 5670 // @uninheritable 5671 // Layers extending `TileLayer` might reimplement the following method. 5672 // @method getTileUrl(coords: Object): String 5673 // Called only internally, returns the URL for a tile given its coordinates. 5674 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 5675 getTileUrl: function (coords) { 5676 var data = { 5677 r: L.Browser.retina ? '@2x' : '', 5678 s: this._getSubdomain(coords), 5679 x: coords.x, 5680 y: coords.y, 5681 z: this._getZoomForUrl() 5682 }; 5683 if (this._map && !this._map.options.crs.infinite) { 5684 var invertedY = this._globalTileRange.max.y - coords.y; 5685 if (this.options.tms) { 5686 data['y'] = invertedY; 5687 }
5688 data['-y'] = invertedY; 5689 } 5690 5691 return L.Util.template(this._url, L.extend(data, this.options)); 5692 }, 5693 5694 _tileOnLoad: function (done, tile) { 5695 // For https://github.com/Leaflet/Leaflet/issues/3332 5696 if (L.Browser.ielt9) { 5697 setTimeout(L.bind(done, this, null, tile), 0); 5698 } else { 5699 done(null, tile); 5700 } 5701 }, 5702 5703 _tileOnError: function (done, tile, e) { 5704 var errorUrl = this.options.errorTileUrl; 5705 if (errorUrl && tile.src !== errorUrl) { 5706 tile.src = errorUrl; 5707 } 5708 done(e, tile); 5709 }, 5710 5711 getTileSize: function () { 5712 var map = this._map, 5713 tileSize = L.GridLayer.prototype.getTileSize.call(this), 5714 zoom = this._tileZoom + this.options.zoomOffset, 5715 minNativeZoom = this.options.minNativeZoom, 5716 maxNativeZoom = this.options.maxNativeZoom; 5717 5718 // decrease tile size when scaling below minNativeZoom 5719 if (minNativeZoom !== null && zoom < minNativeZoom) { 5720 return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round(); 5721 } 5722 5723 // increase tile size when scaling above maxNativeZoom 5724 if (maxNativeZoom !== null && zoom > maxNativeZoom) { 5725 return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round(); 5726 } 5727 5728 return tileSize; 5729 }, 5730 5731 _onTileRemove: function (e) { 5732 e.tile.onload = null; 5733 }, 5734 5735 _getZoomForUrl: function () { 5736 var zoom = this._tileZoom, 5737 maxZoom = this.options.maxZoom, 5738 zoomReverse = this.options.zoomReverse, 5739 zoomOffset = this.options.zoomOffset, 5740 minNativeZoom = this.options.minNativeZoom, 5741 maxNativeZoom = this.options.maxNativeZoom; 5742 5743 if (zoomReverse) { 5744 zoom = maxZoom - zoom; 5745 } 5746 5747 zoom += zoomOffset; 5748 5749 if (minNativeZoom !== null && zoom < minNativeZoom) { 5750 return minNativeZoom; 5751 } 5752 5753 if (maxNativeZoom !== null && zoom > maxNativeZoom) { 5754 return maxNativeZoom; 5755 } 5756 5757 return zoom; 5758 }, 5759 5760 _getSubdomain: function (tilePoint) { 5761 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 5762 return this.options.subdomains[index]; 5763 }, 5764 5765 // stops loading all tiles in the background layer 5766 _abortLoading: function () { 5767 var i, tile; 5768 for (i in this._tiles) { 5769 if (this._tiles[i].coords.z !== this._tileZoom) { 5770 tile = this._tiles[i].el; 5771 5772 tile.onload = L.Util.falseFn; 5773 tile.onerror = L.Util.falseFn; 5774 5775 if (!tile.complete) { 5776 tile.src = L.Util.emptyImageUrl; 5777 L.DomUtil.remove(tile); 5778 } 5779 } 5780 } 5781 } 5782}); 5783 5784 5785// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 5786// Instantiates a tile layer object given a `URL template` and optionally an options object. 5787 5788L.tileLayer = function (url, options) { 5789 return new L.TileLayer(url, options); 5790}; 5791 5792 5793 5794/* 5795 * @class TileLayer.WMS 5796 * @inherits TileLayer 5797 * @aka L.TileLayer.WMS 5798 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 5799 * 5800 * @example 5801 * 5802 * ```js 5803 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 5804 * layers: 'nexrad-n0r-900913', 5805 * format: 'image/png', 5806 * transparent: true, 5807 * attribution: "Weather data © 2012 IEM Nexrad" 5808 * }); 5809 * ``` 5810 */ 5811 5812L.TileLayer.WMS = L.TileLayer.extend({ 5813 5814 // @section 5815 // @aka TileLayer.WMS options 5816 // If any custom options not documented here are used, they will be sent to the 5817 // WMS server as extra parameters in each request URL. This can be useful for 5818 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 5819 defaultWmsParams: { 5820 service: 'WMS', 5821 request: 'GetMap', 5822 5823 // @option layers: String = '' 5824 // **(required)** Comma-separated list of WMS layers to show. 5825 layers: '', 5826 5827 // @option styles: String = '' 5828 // Comma-separated list of WMS styles. 5829 styles: '', 5830 5831 // @option format: String = 'image/jpeg' 5832 // WMS image format (use `'image/png'` for layers with transparency). 5833 format: 'image/jpeg', 5834 5835 // @option transparent: Boolean = false 5836 // If `true`, the WMS service will return images with transparency. 5837 transparent: false, 5838 5839 // @option version: String = '1.1.1' 5840 // Version of the WMS service to use 5841 version: '1.1.1' 5842 }, 5843 5844 options: {
5845 // @option crs: CRS = null 5846 // Coordinate Reference System to use for the WMS requests, defaults to 5847 // map CRS. Don't change this if you're not sure what it means. 5848 crs: null, 5849 5850 // @option uppercase: Boolean = false 5851 // If `true`, WMS request parameter keys will be uppercase. 5852 uppercase: false 5853 }, 5854 5855 initialize: function (url, options) { 5856 5857 this._url = url; 5858 5859 var wmsParams = L.extend({}, this.defaultWmsParams); 5860 5861 // all keys that are not TileLayer options go to WMS params 5862 for (var i in options) { 5863 if (!(i in this.options)) { 5864 wmsParams[i] = options[i]; 5865 } 5866 } 5867 5868 options = L.setOptions(this, options); 5869 5870 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1); 5871 5872 this.wmsParams = wmsParams; 5873 }, 5874 5875 onAdd: function (map) { 5876 5877 this._crs = this.options.crs || map.options.crs; 5878 this._wmsVersion = parseFloat(this.wmsParams.version); 5879 5880 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 5881 this.wmsParams[projectionKey] = this._crs.code; 5882 5883 L.TileLayer.prototype.onAdd.call(this, map); 5884 }, 5885 5886 getTileUrl: function (coords) { 5887 5888 var tileBounds = this._tileCoordsToBounds(coords), 5889 nw = this._crs.project(tileBounds.getNorthWest()), 5890 se = this._crs.project(tileBounds.getSouthEast()), 5891 5892 bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ? 5893 [se.y, nw.x, nw.y, se.x] : 5894 [nw.x, se.y, se.x, nw.y]).join(','), 5895 5896 url = L.TileLayer.prototype.getTileUrl.call(this, coords); 5897 5898 return url + 5899 L.Util.getParamString(this.wmsParams, url, this.options.uppercase) + 5900 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 5901 }, 5902 5903 // @method setParams(params: Object, noRedraw?: Boolean): this 5904 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 5905 setParams: function (params, noRedraw) { 5906 5907 L.extend(this.wmsParams, params); 5908 5909 if (!noRedraw) { 5910 this.redraw(); 5911 } 5912 5913 return this; 5914 } 5915}); 5916 5917 5918// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 5919// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 5920L.tileLayer.wms = function (url, options) { 5921 return new L.TileLayer.WMS(url, options); 5922}; 5923 5924 5925 5926/* 5927 * @class ImageOverlay 5928 * @aka L.ImageOverlay 5929 * @inherits Interactive layer 5930 * 5931 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 5932 * 5933 * @example 5934 * 5935 * ```js 5936 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 5937 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 5938 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 5939 * ``` 5940 */ 5941 5942L.ImageOverlay = L.Layer.extend({ 5943 5944 // @section 5945 // @aka ImageOverlay options 5946 options: { 5947 // @option opacity: Number = 1.0 5948 // The opacity of the image overlay. 5949 opacity: 1, 5950 5951 // @option alt: String = '' 5952 // Text for the `alt` attribute of the image (useful for accessibility). 5953 alt: '', 5954 5955 // @option interactive: Boolean = false 5956 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 5957 interactive: false, 5958 5959 // @option crossOrigin: Boolean = false 5960 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data. 5961 crossOrigin: false 5962 }, 5963 5964 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 5965 this._url = url; 5966 this._bounds = L.latLngBounds(bounds); 5967 5968 L.setOptions(this, options); 5969 }, 5970 5971 onAdd: function () { 5972 if (!this._image) { 5973 this._initImage(); 5974 5975 if (this.options.opacity < 1) { 5976 this._updateOpacity(); 5977 } 5978 } 5979 5980 if (this.options.interactive) { 5981 L.DomUtil.addClass(this._image, 'leaflet-interactive'); 5982 this.addInteractiveTarget(this._image); 5983 } 5984 5985 this.getPane().appendChild(this._image); 5986 this._reset(); 5987 }, 5988 5989 onRemove: function () { 5990 L.DomUtil.remove(this._image); 5991 if (this.options.interactive) { 5992 this.removeInteractiveTarget(this._image); 5993 } 5994 }, 5995 5996 // @method setOpacity(opacity: Number): this 5997 // Sets the opacity of the overlay. 5998 setOpacity: function (opacity) { 5999 this.options.opacity = opacity; 6000 6001 if (this._image) { 6002 this._updateOpacity(); 6003 } 6004 return this; 6005 }, 6006 6007 setStyle: function (styleOpts) { 6008 if (styleOpts.opacity) { 6009 this.setOpacity(styleOpts.opacity); 6010 } 6011 return this; 6012 }, 6013 6014 // @method bringToFront(): this 6015 // Brings the layer to the top of all overlays. 6016 bringToFront: function () { 6017 if (this._map) { 6018 L.DomUtil.toFront(this._image); 6019 } 6020 return this; 6021 }, 6022 6023 // @method bringToBack(): this 6024 // Brings the layer to the bottom of all overlays. 6025 bringToBack: function () { 6026 if (this._map) { 6027 L.DomUtil.toBack(this._image); 6028 } 6029 return this; 6030 }, 6031 6032 // @method setUrl(url: String): this 6033 // Changes the URL of the image. 6034 setUrl: function (url) { 6035 this._url = url; 6036 6037 if (this._image) { 6038 this._image.src = url; 6039 } 6040 return this; 6041 }, 6042 6043 // @method setBounds(bounds: LatLngBounds): this
6044 // Update the bounds that this ImageOverlay covers 6045 setBounds: function (bounds) { 6046 this._bounds = bounds; 6047 6048 if (this._map) { 6049 this._reset(); 6050 } 6051 return this; 6052 }, 6053 6054 getEvents: function () { 6055 var events = { 6056 zoom: this._reset, 6057 viewreset: this._reset 6058 }; 6059 6060 if (this._zoomAnimated) { 6061 events.zoomanim = this._animateZoom; 6062 } 6063 6064 return events; 6065 }, 6066 6067 // @method getBounds(): LatLngBounds 6068 // Get the bounds that this ImageOverlay covers 6069 getBounds: function () { 6070 return this._bounds; 6071 }, 6072 6073 // @method getElement(): HTMLElement 6074 // Get the img element that represents the ImageOverlay on the map 6075 getElement: function () { 6076 return this._image; 6077 }, 6078 6079 _initImage: function () { 6080 var img = this._image = L.DomUtil.create('img', 6081 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '')); 6082 6083 img.onselectstart = L.Util.falseFn; 6084 img.onmousemove = L.Util.falseFn; 6085 6086 img.onload = L.bind(this.fire, this, 'load'); 6087 6088 if (this.options.crossOrigin) { 6089 img.crossOrigin = ''; 6090 } 6091 6092 img.src = this._url; 6093 img.alt = this.options.alt; 6094 }, 6095 6096 _animateZoom: function (e) { 6097 var scale = this._map.getZoomScale(e.zoom), 6098 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 6099 6100 L.DomUtil.setTransform(this._image, offset, scale); 6101 }, 6102 6103 _reset: function () { 6104 var image = this._image, 6105 bounds = new L.Bounds( 6106 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 6107 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 6108 size = bounds.getSize(); 6109 6110 L.DomUtil.setPosition(image, bounds.min); 6111 6112 image.style.width = size.x + 'px'; 6113 image.style.height = size.y + 'px'; 6114 }, 6115 6116 _updateOpacity: function () { 6117 L.DomUtil.setOpacity(this._image, this.options.opacity); 6118 } 6119}); 6120 6121// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 6122// Instantiates an image overlay object given the URL of the image and the 6123// geographical bounds it is tied to. 6124L.imageOverlay = function (url, bounds, options) { 6125 return new L.ImageOverlay(url, bounds, options); 6126}; 6127 6128 6129 6130/* 6131 * @class Icon 6132 * @aka L.Icon 6133 * @inherits Layer 6134 * 6135 * Represents an icon to provide when creating a marker. 6136 * 6137 * @example 6138 * 6139 * ```js 6140 * var myIcon = L.icon({ 6141 * iconUrl: 'my-icon.png', 6142 * iconRetinaUrl: '[email protected]', 6143 * iconSize: [38, 95], 6144 * iconAnchor: [22, 94], 6145 * popupAnchor: [-3, -76], 6146 * shadowUrl: 'my-icon-shadow.png', 6147 * shadowRetinaUrl: '[email protected]', 6148 * shadowSize: [68, 95], 6149 * shadowAnchor: [22, 94] 6150 * }); 6151 * 6152 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 6153 * ``` 6154 * 6155 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 6156 * 6157 */ 6158 6159L.Icon = L.Class.extend({ 6160 6161 /* @section 6162 * @aka Icon options 6163 * 6164 * @option iconUrl: String = null 6165 * **(required)** The URL to the icon image (absolute or relative to your script path). 6166 * 6167 * @option iconRetinaUrl: String = null 6168 * The URL to a retina sized version of the icon image (absolute or relative to your 6169 * script path). Used for Retina screen devices. 6170 * 6171 * @option iconSize: Point = null 6172 * Size of the icon image in pixels. 6173 * 6174 * @option iconAnchor: Point = null 6175 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 6176 * will be aligned so that this point is at the marker's geographical location. Centered 6177 * by default if size is specified, also can be set in CSS with negative margins. 6178 * 6179 * @option popupAnchor: Point = null 6180 * The coordinates of the point from which popups will "open", relative to the icon anchor. 6181 * 6182 * @option shadowUrl: String = null 6183 * The URL to the icon shadow image. If not specified, no shadow image will be created. 6184 * 6185 * @option shadowRetinaUrl: String = null 6186 * 6187 * @option shadowSize: Point = null 6188 * Size of the shadow image in pixels. 6189 * 6190 * @option shadowAnchor: Point = null 6191 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 6192 * as iconAnchor if not specified). 6193 * 6194 * @option className: String = '' 6195 * A custom class name to assign to both icon and shadow images. Empty by default. 6196 */ 6197 6198 initialize: function (options) { 6199 L.setOptions(this, options); 6200 }, 6201 6202 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6203 // Called internally when the icon has to be shown, returns a `<img>` HTML element 6204 // styled according to the options. 6205 createIcon: function (oldIcon) { 6206 return this._createIcon('icon', oldIcon); 6207 }, 6208 6209 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 6210 // As `createIcon`, but for the shadow beneath it. 6211 createShadow: function (oldIcon) { 6212 return this._createIcon('shadow', oldIcon); 6213 }, 6214 6215 _createIcon: function (name, oldIcon) { 6216 var src = this._getIconUrl(name); 6217 6218 if (!src) { 6219 if (name === 'icon') { 6220 throw new Error('iconUrl not set in Icon options (see the docs).'); 6221 } 6222 return null; 6223 } 6224 6225 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 6226 this._setIconStyles(img, name); 6227 6228 return img; 6229 }, 6230 6231 _setIconStyles: function (img, name) { 6232 var options = this.options; 6233 var sizeOption = options[name + 'Size']; 6234 6235 if (typeof sizeOption === 'number') { 6236 sizeOption = [sizeOption, sizeOption]; 6237 } 6238 6239 var size = L.point(sizeOption), 6240 anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 6241 size && size.divideBy(2, true)); 6242 6243 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 6244 6245 if (anchor) { 6246 img.style.marginLeft = (-anchor.x) + 'px'; 6247 img.style.marginTop = (-anchor.y) + 'px'; 6248 } 6249 6250 if (size) { 6251 img.style.width = size.x + 'px'; 6252 img.style.height = size.y + 'px'; 6253 } 6254 }, 6255 6256 _createImg: function (src, el) { 6257 el = el || document.createElement('img'); 6258 el.src = src; 6259 return el; 6260 }, 6261 6262 _getIconUrl: function (name) { 6263 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 6264 } 6265}); 6266 6267 6268// @factory L.icon(options: Icon options) 6269// Creates an icon instance with the given options. 6270L.icon = function (options) { 6271 return new L.Icon(options); 6272}; 6273 6274 6275 6276/* 6277 * @miniclass Icon.Default (Icon) 6278 * @aka L.Icon.Default 6279 * @section 6280 * 6281 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 6282 * no icon is specified. Points to the blue marker image distributed with Leaflet 6283 * releases. 6284 * 6285 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 6286 * (which is a set of `Icon options`). 6287 * 6288 * If you want to _completely_ replace the default icon, override the 6289 * `L.Marker.prototype.options.icon` with your own icon instead. 6290 */ 6291 6292L.Icon.Default = L.Icon.extend({ 6293 6294 options: { 6295 iconUrl: 'marker-icon.png', 6296 iconRetinaUrl: 'marker-icon-2x.png', 6297 shadowUrl: 'marker-shadow.png', 6298 iconSize: [25, 41], 6299 iconAnchor: [12, 41], 6300 popupAnchor: [1, -34], 6301 tooltipAnchor: [16, -28], 6302 shadowSize: [41, 41] 6303 }, 6304 6305 _getIconUrl: function (name) { 6306 if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only 6307 L.Icon.Default.imagePath = this._detectIconPath(); 6308 } 6309 6310 // @option imagePath: String 6311 // `L.Icon.Default` will try to auto-detect the absolute location of the 6312 // blue icon images. If you are placing these images in a non-standard 6313 // way, set this option to point to the right absolute path. 6314 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name); 6315 }, 6316 6317 _detectIconPath: function () { 6318 var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body); 6319 var path = L.DomUtil.getStyle(el, 'background-image') || 6320 L.DomUtil.getStyle(el, 'backgroundImage'); // IE8 6321 6322 document.body.removeChild(el); 6323 6324 return path.indexOf('url') === 0 ? 6325 path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : ''; 6326 } 6327}); 6328 6329 6330 6331/* 6332 * @class Marker 6333 * @inherits Interactive layer 6334 * @aka L.Marker 6335 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 6336 * 6337 * @example 6338 * 6339 * ```js 6340 * L.marker([50.5, 30.5]).addTo(map); 6341 * ``` 6342 */ 6343 6344L.Marker = L.Layer.extend({ 6345 6346 // @section 6347 // @aka Marker options 6348 options: { 6349 // @option icon: Icon = * 6350 // Icon class to use for rendering the marker. See [Icon documentation](#L.Icon) for details on how to customize the marker icon. If not specified, a new `L.Icon.Default` is used. 6351 icon: new L.Icon.Default(), 6352 6353 // Option inherited from "Interactive layer" abstract class 6354 interactive: true, 6355
vendor: 15,270 bytes, lines 6356-6975
6356 // @option draggable: Boolean = false 6357 // Whether the marker is draggable with mouse/touch or not. 6358 draggable: false, 6359 6360 // @option keyboard: Boolean = true 6361 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 6362 keyboard: true, 6363 6364 // @option title: String = '' 6365 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 6366 title: '', 6367 6368 // @option alt: String = '' 6369 // Text for the `alt` attribute of the icon image (useful for accessibility). 6370 alt: '', 6371 6372 // @option zIndexOffset: Number = 0 6373 // 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). 6374 zIndexOffset: 0, 6375 6376 // @option opacity: Number = 1.0 6377 // The opacity of the marker. 6378 opacity: 1, 6379 6380 // @option riseOnHover: Boolean = false 6381 // If `true`, the marker will get on top of others when you hover the mouse over it. 6382 riseOnHover: false, 6383 6384 // @option riseOffset: Number = 250 6385 // The z-index offset used for the `riseOnHover` feature. 6386 riseOffset: 250, 6387 6388 // @option pane: String = 'markerPane' 6389 // `Map pane` where the markers icon will be added. 6390 pane: 'markerPane', 6391 6392 // FIXME: shadowPane is no longer a valid option 6393 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'] 6394 }, 6395 6396 /* @section 6397 * 6398 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 6399 */ 6400 6401 initialize: function (latlng, options) { 6402 L.setOptions(this, options); 6403 this._latlng = L.latLng(latlng); 6404 }, 6405 6406 onAdd: function (map) { 6407 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 6408 6409 if (this._zoomAnimated) { 6410 map.on('zoomanim', this._animateZoom, this); 6411 } 6412 6413 this._initIcon(); 6414 this.update(); 6415 }, 6416 6417 onRemove: function (map) { 6418 if (this.dragging && this.dragging.enabled()) { 6419 this.options.draggable = true; 6420 this.dragging.removeHooks(); 6421 } 6422 6423 if (this._zoomAnimated) { 6424 map.off('zoomanim', this._animateZoom, this); 6425 } 6426 6427 this._removeIcon(); 6428 this._removeShadow(); 6429 }, 6430 6431 getEvents: function () { 6432 return { 6433 zoom: this.update, 6434 viewreset: this.update 6435 }; 6436 }, 6437 6438 // @method getLatLng: LatLng 6439 // Returns the current geographical position of the marker. 6440 getLatLng: function () { 6441 return this._latlng; 6442 }, 6443 6444 // @method setLatLng(latlng: LatLng): this 6445 // Changes the marker position to the given point. 6446 setLatLng: function (latlng) { 6447 var oldLatLng = this._latlng; 6448 this._latlng = L.latLng(latlng); 6449 this.update(); 6450 6451 // @event move: Event 6452 // 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`. 6453 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 6454 }, 6455 6456 // @method setZIndexOffset(offset: Number): this 6457 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 6458 setZIndexOffset: function (offset) { 6459 this.options.zIndexOffset = offset; 6460 return this.update(); 6461 }, 6462 6463 // @method setIcon(icon: Icon): this 6464 // Changes the marker icon. 6465 setIcon: function (icon) { 6466 6467 this.options.icon = icon; 6468 6469 if (this._map) { 6470 this._initIcon(); 6471 this.update(); 6472 } 6473 6474 if (this._popup) { 6475 this.bindPopup(this._popup, this._popup.options); 6476 } 6477 6478 return this; 6479 }, 6480 6481 getElement: function () { 6482 return this._icon; 6483 }, 6484 6485 update: function () { 6486 6487 if (this._icon) { 6488 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 6489 this._setPos(pos); 6490 } 6491 6492 return this; 6493 }, 6494 6495 _initIcon: function () { 6496 var options = this.options, 6497 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 6498 6499 var icon = options.icon.createIcon(this._icon), 6500 addIcon = false; 6501 6502 // if we're not reusing the icon, remove the old one and init new one 6503 if (icon !== this._icon) { 6504 if (this._icon) { 6505 this._removeIcon(); 6506 } 6507 addIcon = true; 6508 6509 if (options.title) { 6510 icon.title = options.title; 6511 } 6512 if (options.alt) { 6513 icon.alt = options.alt; 6514 } 6515 } 6516 6517 L.DomUtil.addClass(icon, classToAdd); 6518 6519 if (options.keyboard) { 6520 icon.tabIndex = '0'; 6521 } 6522 6523 this._icon = icon; 6524 6525 if (options.riseOnHover) { 6526 this.on({ 6527 mouseover: this._bringToFront, 6528 mouseout: this._resetZIndex 6529 }); 6530 } 6531 6532 var newShadow = options.icon.createShadow(this._shadow), 6533 addShadow = false; 6534 6535 if (newShadow !== this._shadow) { 6536 this._removeShadow(); 6537 addShadow = true; 6538 } 6539 6540 if (newShadow) { 6541 L.DomUtil.addClass(newShadow, classToAdd); 6542 newShadow.alt = ''; 6543 } 6544 this._shadow = newShadow; 6545 6546 6547 if (options.opacity < 1) { 6548 this._updateOpacity(); 6549 } 6550 6551 6552 if (addIcon) { 6553 this.getPane().appendChild(this._icon); 6554 } 6555 this._initInteraction(); 6556 if (newShadow && addShadow) { 6557 this.getPane('shadowPane').appendChild(this._shadow); 6558 } 6559 }, 6560 6561 _removeIcon: function () { 6562 if (this.options.riseOnHover) { 6563 this.off({ 6564 mouseover: this._bringToFront, 6565 mouseout: this._resetZIndex 6566 }); 6567 } 6568 6569 L.DomUtil.remove(this._icon); 6570 this.removeInteractiveTarget(this._icon); 6571 6572 this._icon = null; 6573 }, 6574 6575 _removeShadow: function () { 6576 if (this._shadow) { 6577 L.DomUtil.remove(this._shadow); 6578 } 6579 this._shadow = null; 6580 }, 6581 6582 _setPos: function (pos) { 6583 L.DomUtil.setPosition(this._icon, pos); 6584 6585 if (this._shadow) { 6586 L.DomUtil.setPosition(this._shadow, pos); 6587 } 6588 6589 this._zIndex = pos.y + this.options.zIndexOffset; 6590 6591 this._resetZIndex(); 6592 }, 6593 6594 _updateZIndex: function (offset) { 6595 this._icon.style.zIndex = this._zIndex + offset; 6596 }, 6597 6598 _animateZoom: function (opt) { 6599 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 6600 6601 this._setPos(pos); 6602 }, 6603 6604 _initInteraction: function () { 6605 6606 if (!this.options.interactive) { return; } 6607 6608 L.DomUtil.addClass(this._icon, 'leaflet-interactive'); 6609 6610 this.addInteractiveTarget(this._icon); 6611 6612 if (L.Handler.MarkerDrag) { 6613 var draggable = this.options.draggable; 6614 if (this.dragging) { 6615 draggable = this.dragging.enabled(); 6616 this.dragging.disable(); 6617 } 6618 6619 this.dragging = new L.Handler.MarkerDrag(this); 6620 6621 if (draggable) { 6622 this.dragging.enable(); 6623 } 6624 } 6625 }, 6626 6627 // @method setOpacity(opacity: Number): this 6628 // Changes the opacity of the marker. 6629 setOpacity: function (opacity) { 6630 this.options.opacity = opacity; 6631 if (this._map) { 6632 this._updateOpacity(); 6633 } 6634 6635 return this; 6636 }, 6637 6638 _updateOpacity: function () { 6639 var opacity = this.options.opacity; 6640 6641 L.DomUtil.setOpacity(this._icon, opacity); 6642 6643 if (this._shadow) { 6644 L.DomUtil.setOpacity(this._shadow, opacity); 6645 } 6646 }, 6647 6648 _bringToFront: function () { 6649 this._updateZIndex(this.options.riseOffset); 6650 }, 6651 6652 _resetZIndex: function () { 6653 this._updateZIndex(0); 6654 }, 6655 6656 _getPopupAnchor: function () { 6657 return this.options.icon.options.popupAnchor || [0, 0]; 6658 }, 6659 6660 _getTooltipAnchor: function () { 6661 return this.options.icon.options.tooltipAnchor || [0, 0]; 6662 } 6663}); 6664 6665 6666// factory L.marker(latlng: LatLng, options? : Marker options) 6667 6668// @factory L.marker(latlng: LatLng, options? : Marker options) 6669// Instantiates a Marker object given a geographical point and optionally an options object. 6670L.marker = function (latlng, options) { 6671 return new L.Marker(latlng, options); 6672}; 6673 6674 6675 6676/* 6677 * @class DivIcon 6678 * @aka L.DivIcon 6679 * @inherits Icon 6680 * 6681 * Represents a lightweight icon for markers that uses a simple `<div>` 6682 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 6683 * 6684 * @example 6685 * ```js 6686 * var myIcon = L.divIcon({className: 'my-div-icon'}); 6687 * // you can set .my-div-icon styles in CSS 6688 * 6689 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 6690 * ``` 6691 * 6692 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 6693 */ 6694 6695L.DivIcon = L.Icon.extend({ 6696 options: { 6697 // @section 6698 // @aka DivIcon options 6699 iconSize: [12, 12], // also can be set through CSS 6700 6701 // iconAnchor: (Point), 6702 // popupAnchor: (Point), 6703 6704 // @option html: String = '' 6705 // Custom HTML code to put inside the div element, empty by default. 6706 html: false, 6707 6708 // @option bgPos: Point = [0, 0] 6709 // Optional relative position of the background, in pixels 6710 bgPos: null, 6711 6712 className: 'leaflet-div-icon' 6713 }, 6714 6715 createIcon: function (oldIcon) { 6716 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 6717 options = this.options; 6718 6719 div.innerHTML = options.html !== false ? options.html : ''; 6720 6721 if (options.bgPos) { 6722 var bgPos = L.point(options.bgPos); 6723 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 6724 } 6725 this._setIconStyles(div, 'icon'); 6726 6727 return div; 6728 }, 6729 6730 createShadow: function () { 6731 return null; 6732 } 6733}); 6734 6735// @factory L.divIcon(options: DivIcon options) 6736// Creates a `DivIcon` instance with the given options. 6737L.divIcon = function (options) { 6738 return new L.DivIcon(options); 6739}; 6740 6741 6742 6743/* 6744 * @class DivOverlay 6745 * @inherits Layer 6746 * @aka L.DivOverlay 6747 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins. 6748 */ 6749 6750// @namespace DivOverlay 6751L.DivOverlay = L.Layer.extend({ 6752 6753 // @section 6754 // @aka DivOverlay options 6755 options: { 6756 // @option offset: Point = Point(0, 7) 6757 // The offset of the popup position. Useful to control the anchor 6758 // of the popup when opening it on some overlays. 6759 offset: [0, 7], 6760 6761 // @option className: String = '' 6762 // A custom CSS class name to assign to the popup. 6763 className: '', 6764 6765 // @option pane: String = 'popupPane' 6766 // `Map pane` where the popup will be added. 6767 pane: 'popupPane' 6768 }, 6769 6770 initialize: function (options, source) { 6771 L.setOptions(this, options); 6772 6773 this._source = source; 6774 }, 6775 6776 onAdd: function (map) { 6777 this._zoomAnimated = map._zoomAnimated; 6778 6779 if (!this._container) { 6780 this._initLayout(); 6781 } 6782 6783 if (map._fadeAnimated) { 6784 L.DomUtil.setOpacity(this._container, 0); 6785 } 6786 6787 clearTimeout(this._removeTimeout); 6788 this.getPane().appendChild(this._container); 6789 this.update(); 6790 6791 if (map._fadeAnimated) { 6792 L.DomUtil.setOpacity(this._container, 1); 6793 } 6794 6795 this.bringToFront(); 6796 }, 6797 6798 onRemove: function (map) { 6799 if (map._fadeAnimated) { 6800 L.DomUtil.setOpacity(this._container, 0); 6801 this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200); 6802 } else { 6803 L.DomUtil.remove(this._container); 6804 } 6805 }, 6806 6807 // @namespace Popup 6808 // @method getLatLng: LatLng 6809 // Returns the geographical point of popup. 6810 getLatLng: function () { 6811 return this._latlng; 6812 }, 6813 6814 // @method setLatLng(latlng: LatLng): this 6815 // Sets the geographical point where the popup will open. 6816 setLatLng: function (latlng) { 6817 this._latlng = L.latLng(latlng); 6818 if (this._map) { 6819 this._updatePosition(); 6820 this._adjustPan(); 6821 } 6822 return this; 6823 }, 6824 6825 // @method getContent: String|HTMLElement 6826 // Returns the content of the popup. 6827 getContent: function () { 6828 return this._content; 6829 }, 6830 6831 // @method setContent(htmlContent: String|HTMLElement|Function): this 6832 // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup. 6833 setContent: function (content) { 6834 this._content = content; 6835 this.update(); 6836 return this; 6837 }, 6838 6839 // @method getElement: String|HTMLElement 6840 // Alias for [getContent()](#popup-getcontent) 6841 getElement: function () { 6842 return this._container; 6843 }, 6844 6845 // @method update: null 6846 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded. 6847 update: function () { 6848 if (!this._map) { return; } 6849 6850 this._container.style.visibility = 'hidden'; 6851 6852 this._updateContent(); 6853 this._updateLayout(); 6854 this._updatePosition(); 6855 6856 this._container.style.visibility = ''; 6857 6858 this._adjustPan(); 6859 }, 6860 6861 getEvents: function () { 6862 var events = { 6863 zoom: this._updatePosition, 6864 viewreset: this._updatePosition 6865 }; 6866 6867 if (this._zoomAnimated) { 6868 events.zoomanim = this._animateZoom; 6869 } 6870 return events; 6871 }, 6872 6873 // @method isOpen: Boolean 6874 // Returns `true` when the popup is visible on the map. 6875 isOpen: function () { 6876 return !!this._map && this._map.hasLayer(this); 6877 }, 6878 6879 // @method bringToFront: this 6880 // Brings this popup in front of other popups (in the same map pane). 6881 bringToFront: function () { 6882 if (this._map) { 6883 L.DomUtil.toFront(this._container); 6884 } 6885 return this; 6886 }, 6887 6888 // @method bringToBack: this 6889 // Brings this popup to the back of other popups (in the same map pane). 6890 bringToBack: function () { 6891 if (this._map) { 6892 L.DomUtil.toBack(this._container); 6893 } 6894 return this; 6895 }, 6896 6897 _updateContent: function () { 6898 if (!this._content) { return; } 6899 6900 var node = this._contentNode; 6901 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 6902 6903 if (typeof content === 'string') { 6904 node.innerHTML = content; 6905 } else { 6906 while (node.hasChildNodes()) { 6907 node.removeChild(node.firstChild); 6908 } 6909 node.appendChild(content); 6910 } 6911 this.fire('contentupdate'); 6912 }, 6913 6914 _updatePosition: function () { 6915 if (!this._map) { return; } 6916 6917 var pos = this._map.latLngToLayerPoint(this._latlng), 6918 offset = L.point(this.options.offset), 6919 anchor = this._getAnchor(); 6920 6921 if (this._zoomAnimated) { 6922 L.DomUtil.setPosition(this._container, pos.add(anchor)); 6923 } else { 6924 offset = offset.add(pos).add(anchor); 6925 } 6926 6927 var bottom = this._containerBottom = -offset.y, 6928 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 6929 6930 // bottom position the popup in case the height of the popup changes (images loading etc) 6931 this._container.style.bottom = bottom + 'px'; 6932 this._container.style.left = left + 'px'; 6933 }, 6934 6935 _getAnchor: function () { 6936 return [0, 0]; 6937 } 6938 6939}); 6940 6941 6942 6943/* 6944 * @class Popup 6945 * @inherits DivOverlay 6946 * @aka L.Popup 6947 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 6948 * open popups while making sure that only one popup is open at one time 6949 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 6950 * 6951 * @example 6952 * 6953 * If you want to just bind a popup to marker click and then open it, it's really easy: 6954 * 6955 * ```js 6956 * marker.bindPopup(popupContent).openPopup(); 6957 * ``` 6958 * Path overlays like polylines also have a `bindPopup` method. 6959 * Here's a more complicated way to open a popup on a map: 6960 * 6961 * ```js 6962 * var popup = L.popup() 6963 * .setLatLng(latlng) 6964 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 6965 * .openOn(map); 6966 * ``` 6967 */ 6968 6969 6970// @namespace Popup 6971L.Popup = L.DivOverlay.extend({ 6972 6973 // @section 6974 // @aka Popup options 6975 options: {
6976 // @option maxWidth: Number = 300 6977 // Max width of the popup, in pixels. 6978 maxWidth: 300, 6979 6980 // @option minWidth: Number = 50 6981 // Min width of the popup, in pixels. 6982 minWidth: 50, 6983 6984 // @option maxHeight: Number = null 6985 // If set, creates a scrollable container of the given height 6986 // inside a popup if its content exceeds it. 6987 maxHeight: null, 6988 6989 // @option autoPan: Boolean = true 6990 // Set it to `false` if you don't want the map to do panning animation 6991 // to fit the opened popup. 6992 autoPan: true, 6993 6994 // @option autoPanPaddingTopLeft: Point = null 6995 // The margin between the popup and the top left corner of the map 6996 // view after autopanning was performed. 6997 autoPanPaddingTopLeft: null, 6998 6999 // @option autoPanPaddingBottomRight: Point = null 7000 // The margin between the popup and the bottom right corner of the map 7001 // view after autopanning was performed. 7002 autoPanPaddingBottomRight: null, 7003 7004 // @option autoPanPadding: Point = Point(5, 5) 7005 // Equivalent of setting both top left and bottom right autopan padding to the same value. 7006 autoPanPadding: [5, 5], 7007 7008 // @option keepInView: Boolean = false 7009 // Set it to `true` if you want to prevent users from panning the popup 7010 // off of the screen while it is open. 7011 keepInView: false, 7012 7013 // @option closeButton: Boolean = true 7014 // Controls the presence of a close button in the popup. 7015 closeButton: true, 7016 7017 // @option autoClose: Boolean = true 7018 // Set it to `false` if you want to override the default behavior of 7019 // the popup closing when user clicks the map (set globally by 7020 // the Map's [closePopupOnClick](#map-closepopuponclick) option). 7021 autoClose: true, 7022 7023 // @option className: String = '' 7024 // A custom CSS class name to assign to the popup. 7025 className: '' 7026 }, 7027 7028 // @namespace Popup 7029 // @method openOn(map: Map): this 7030 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`. 7031 openOn: function (map) { 7032 map.openPopup(this); 7033 return this; 7034 }, 7035 7036 onAdd: function (map) { 7037 L.DivOverlay.prototype.onAdd.call(this, map); 7038 7039 // @namespace Map 7040 // @section Popup events 7041 // @event popupopen: PopupEvent 7042 // Fired when a popup is opened in the map 7043 map.fire('popupopen', {popup: this}); 7044 7045 if (this._source) { 7046 // @namespace Layer 7047 // @section Popup events 7048 // @event popupopen: PopupEvent 7049 // Fired when a popup bound to this layer is opened 7050 this._source.fire('popupopen', {popup: this}, true); 7051 // For non-path layers, we toggle the popup when clicking 7052 // again the layer, so prevent the map to reopen it. 7053 if (!(this._source instanceof L.Path)) { 7054 this._source.on('preclick', L.DomEvent.stopPropagation); 7055 } 7056 } 7057 }, 7058 7059 onRemove: function (map) { 7060 L.DivOverlay.prototype.onRemove.call(this, map); 7061 7062 // @namespace Map 7063 // @section Popup events 7064 // @event popupclose: PopupEvent 7065 // Fired when a popup in the map is closed 7066 map.fire('popupclose', {popup: this}); 7067 7068 if (this._source) { 7069 // @namespace Layer 7070 // @section Popup events 7071 // @event popupclose: PopupEvent 7072 // Fired when a popup bound to this layer is closed 7073 this._source.fire('popupclose', {popup: this}, true); 7074 if (!(this._source instanceof L.Path)) { 7075 this._source.off('preclick', L.DomEvent.stopPropagation); 7076 } 7077 } 7078 }, 7079 7080 getEvents: function () { 7081 var events = L.DivOverlay.prototype.getEvents.call(this); 7082 7083 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 7084 events.preclick = this._close; 7085 } 7086 7087 if (this.options.keepInView) { 7088 events.moveend = this._adjustPan; 7089 } 7090 7091 return events; 7092 }, 7093 7094 _close: function () { 7095 if (this._map) { 7096 this._map.closePopup(this); 7097 } 7098 }, 7099 7100 _initLayout: function () { 7101 var prefix = 'leaflet-popup', 7102 container = this._container = L.DomUtil.create('div', 7103 prefix + ' ' + (this.options.className || '') + 7104 ' leaflet-zoom-animated'); 7105 7106 if (this.options.closeButton) { 7107 var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container); 7108 closeButton.href = '#close'; 7109 closeButton.innerHTML = '×'; 7110 7111 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); 7112 } 7113 7114 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container); 7115 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); 7116 7117 L.DomEvent 7118 .disableClickPropagation(wrapper) 7119 .disableScrollPropagation(this._contentNode) 7120 .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); 7121 7122 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); 7123 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); 7124 }, 7125 7126 _updateLayout: function () { 7127 var container = this._contentNode, 7128 style = container.style; 7129 7130 style.width = ''; 7131 style.whiteSpace = 'nowrap'; 7132 7133 var width = container.offsetWidth; 7134 width = Math.min(width, this.options.maxWidth); 7135 width = Math.max(width, this.options.minWidth); 7136 7137 style.width = (width + 1) + 'px'; 7138 style.whiteSpace = ''; 7139 7140 style.height = ''; 7141 7142 var height = container.offsetHeight, 7143 maxHeight = this.options.maxHeight, 7144 scrolledClass = 'leaflet-popup-scrolled'; 7145 7146 if (maxHeight && height > maxHeight) { 7147 style.height = maxHeight + 'px'; 7148 L.DomUtil.addClass(container, scrolledClass); 7149 } else { 7150 L.DomUtil.removeClass(container, scrolledClass); 7151 } 7152 7153 this._containerWidth = this._container.offsetWidth; 7154 }, 7155 7156 _animateZoom: function (e) { 7157 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 7158 anchor = this._getAnchor(); 7159 L.DomUtil.setPosition(this._container, pos.add(anchor)); 7160 }, 7161 7162 _adjustPan: function () { 7163 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } 7164 7165 var map = this._map, 7166 marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0, 7167 containerHeight = this._container.offsetHeight + marginBottom, 7168 containerWidth = this._containerWidth, 7169 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); 7170 7171 layerPos._add(L.DomUtil.getPosition(this._container));
7172 7173 var containerPos = map.layerPointToContainerPoint(layerPos), 7174 padding = L.point(this.options.autoPanPadding), 7175 paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), 7176 paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), 7177 size = map.getSize(), 7178 dx = 0, 7179 dy = 0; 7180 7181 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 7182 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 7183 } 7184 if (containerPos.x - dx - paddingTL.x < 0) { // left 7185 dx = containerPos.x - paddingTL.x; 7186 } 7187 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 7188 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 7189 } 7190 if (containerPos.y - dy - paddingTL.y < 0) { // top 7191 dy = containerPos.y - paddingTL.y; 7192 } 7193 7194 // @namespace Map 7195 // @section Popup events 7196 // @event autopanstart: Event 7197 // Fired when the map starts autopanning when opening a popup. 7198 if (dx || dy) { 7199 map 7200 .fire('autopanstart') 7201 .panBy([dx, dy]); 7202 } 7203 }, 7204 7205 _onCloseButtonClick: function (e) { 7206 this._close(); 7207 L.DomEvent.stop(e); 7208 }, 7209 7210 _getAnchor: function () { 7211 // Where should we anchor the popup on the source layer? 7212 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 7213 } 7214 7215}); 7216 7217// @namespace Popup 7218// @factory L.popup(options?: Popup options, source?: Layer) 7219// 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. 7220L.popup = function (options, source) { 7221 return new L.Popup(options, source); 7222}; 7223 7224 7225/* @namespace Map 7226 * @section Interaction Options 7227 * @option closePopupOnClick: Boolean = true 7228 * Set it to `false` if you don't want popups to close when user clicks the map. 7229 */ 7230L.Map.mergeOptions({ 7231 closePopupOnClick: true 7232}); 7233 7234 7235// @namespace Map 7236// @section Methods for Layers and Controls 7237L.Map.include({ 7238 // @method openPopup(popup: Popup): this 7239 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 7240 // @alternative 7241 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 7242 // Creates a popup with the specified content and options and opens it in the given point on a map. 7243 openPopup: function (popup, latlng, options) { 7244 if (!(popup instanceof L.Popup)) { 7245 popup = new L.Popup(options).setContent(popup); 7246 } 7247 7248 if (latlng) { 7249 popup.setLatLng(latlng); 7250 } 7251 7252 if (this.hasLayer(popup)) { 7253 return this; 7254 } 7255 7256 if (this._popup && this._popup.options.autoClose) { 7257 this.closePopup(); 7258 } 7259 7260 this._popup = popup; 7261 return this.addLayer(popup); 7262 }, 7263 7264 // @method closePopup(popup?: Popup): this 7265 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 7266 closePopup: function (popup) { 7267 if (!popup || popup === this._popup) { 7268 popup = this._popup; 7269 this._popup = null; 7270 } 7271 if (popup) { 7272 this.removeLayer(popup); 7273 } 7274 return this; 7275 } 7276}); 7277 7278/* 7279 * @namespace Layer 7280 * @section Popup methods example 7281 * 7282 * All layers share a set of methods convenient for binding popups to it. 7283 * 7284 * ```js 7285 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 7286 * layer.openPopup(); 7287 * layer.closePopup(); 7288 * ``` 7289 * 7290 * 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. 7291 */ 7292 7293// @section Popup methods 7294L.Layer.include({ 7295 7296 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 7297 // Binds a popup to the layer with the passed `content` and sets up the 7298 // neccessary event listeners. If a `Function` is passed it will receive 7299 // the layer as the first argument and should return a `String` or `HTMLElement`. 7300 bindPopup: function (content, options) { 7301 7302 if (content instanceof L.Popup) { 7303 L.setOptions(content, options); 7304 this._popup = content; 7305 content._source = this; 7306 } else { 7307 if (!this._popup || options) { 7308 this._popup = new L.Popup(options, this); 7309 } 7310 this._popup.setContent(content); 7311 } 7312 7313 if (!this._popupHandlersAdded) { 7314 this.on({ 7315 click: this._openPopup, 7316 remove: this.closePopup, 7317 move: this._movePopup 7318 }); 7319 this._popupHandlersAdded = true; 7320 } 7321 7322 return this; 7323 }, 7324 7325 // @method unbindPopup(): this 7326 // Removes the popup previously bound with `bindPopup`. 7327 unbindPopup: function () { 7328 if (this._popup) { 7329 this.off({ 7330 click: this._openPopup, 7331 remove: this.closePopup, 7332 move: this._movePopup 7333 }); 7334 this._popupHandlersAdded = false; 7335 this._popup = null; 7336 } 7337 return this; 7338 }, 7339 7340 // @method openPopup(latlng?: LatLng): this 7341 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed. 7342 openPopup: function (layer, latlng) { 7343 if (!(layer instanceof L.Layer)) { 7344 latlng = layer; 7345 layer = this; 7346 } 7347 7348 if (layer instanceof L.FeatureGroup) { 7349 for (var id in this._layers) { 7350 layer = this._layers[id]; 7351 break; 7352 } 7353 } 7354 7355 if (!latlng) { 7356 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng(); 7357 } 7358 7359 if (this._popup && this._map) { 7360 // set popup source to this layer 7361 this._popup._source = layer; 7362 7363 // update the popup (content, layout, ect...) 7364 this._popup.update(); 7365 7366 // open the popup on the map 7367 this._map.openPopup(this._popup, latlng); 7368 } 7369 7370 return this; 7371 }, 7372 7373 // @method closePopup(): this 7374 // Closes the popup bound to this layer if it is open. 7375 closePopup: function () { 7376 if (this._popup) { 7377 this._popup._close(); 7378 } 7379 return this; 7380 }, 7381 7382 // @method togglePopup(): this 7383 // Opens or closes the popup bound to this layer depending on its current state. 7384 togglePopup: function (target) { 7385 if (this._popup) { 7386 if (this._popup._map) { 7387 this.closePopup(); 7388 } else { 7389 this.openPopup(target); 7390 } 7391 } 7392 return this; 7393 }, 7394 7395 // @method isPopupOpen(): boolean 7396 // Returns `true` if the popup bound to this layer is currently open. 7397 isPopupOpen: function () { 7398 return (this._popup ? this._popup.isOpen() : false); 7399 }, 7400 7401 // @method setPopupContent(content: String|HTMLElement|Popup): this 7402 // Sets the content of the popup bound to this layer. 7403 setPopupContent: function (content) { 7404 if (this._popup) { 7405 this._popup.setContent(content); 7406 } 7407 return this; 7408 }, 7409 7410 // @method getPopup(): Popup 7411 // Returns the popup bound to this layer. 7412 getPopup: function () { 7413 return this._popup; 7414 }, 7415 7416 _openPopup: function (e) { 7417 var layer = e.layer || e.target; 7418 7419 if (!this._popup) { 7420 return; 7421 } 7422 7423 if (!this._map) { 7424 return; 7425 } 7426 7427 // prevent map click 7428 L.DomEvent.stop(e); 7429 7430 // if this inherits from Path its a vector and we can just 7431 // open the popup at the new location 7432 if (layer instanceof L.Path) { 7433 this.openPopup(e.layer || e.target, e.latlng); 7434 return; 7435 } 7436 7437 // otherwise treat it like a marker and figure out 7438 // if we should toggle it open/closed 7439 if (this._map.hasLayer(this._popup) && this._popup._source === layer) { 7440 this.closePopup(); 7441 } else { 7442 this.openPopup(layer, e.latlng); 7443 } 7444 }, 7445 7446 _movePopup: function (e) { 7447 this._popup.setLatLng(e.latlng); 7448 } 7449}); 7450 7451 7452 7453/* 7454 * @class Tooltip 7455 * @inherits DivOverlay 7456 * @aka L.Tooltip 7457 * Used to display small texts on top of map layers. 7458 * 7459 * @example 7460 * 7461 * ```js 7462 * marker.bindTooltip("my tooltip text").openTooltip(); 7463 * ``` 7464 * Note about tooltip offset. Leaflet takes two options in consideration
7465 * for computing tooltip offseting: 7466 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 7467 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 7468 * move it to the bottom. Negatives will move to the left and top. 7469 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 7470 * should adapt this value if you use a custom icon. 7471 */ 7472 7473 7474// @namespace Tooltip 7475L.Tooltip = L.DivOverlay.extend({ 7476 7477 // @section 7478 // @aka Tooltip options 7479 options: { 7480 // @option pane: String = 'tooltipPane' 7481 // `Map pane` where the tooltip will be added. 7482 pane: 'tooltipPane', 7483 7484 // @option offset: Point = Point(0, 0) 7485 // Optional offset of the tooltip position. 7486 offset: [0, 0], 7487 7488 // @option direction: String = 'auto' 7489 // Direction where to open the tooltip. Possible values are: `right`, `left`, 7490 // `top`, `bottom`, `center`, `auto`. 7491 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip 7492 // position on the map. 7493 direction: 'auto', 7494 7495 // @option permanent: Boolean = false 7496 // Whether to open the tooltip permanently or only on mouseover. 7497 permanent: false, 7498 7499 // @option sticky: Boolean = false 7500 // If true, the tooltip will follow the mouse instead of being fixed at the feature center. 7501 sticky: false, 7502 7503 // @option interactive: Boolean = false 7504 // If true, the tooltip will listen to the feature events. 7505 interactive: false, 7506 7507 // @option opacity: Number = 0.9 7508 // Tooltip container opacity. 7509 opacity: 0.9 7510 }, 7511 7512 onAdd: function (map) { 7513 L.DivOverlay.prototype.onAdd.call(this, map); 7514 this.setOpacity(this.options.opacity); 7515 7516 // @namespace Map 7517 // @section Tooltip events 7518 // @event tooltipopen: TooltipEvent 7519 // Fired when a tooltip is opened in the map. 7520 map.fire('tooltipopen', {tooltip: this}); 7521 7522 if (this._source) { 7523 // @namespace Layer 7524 // @section Tooltip events 7525 // @event tooltipopen: TooltipEvent 7526 // Fired when a tooltip bound to this layer is opened. 7527 this._source.fire('tooltipopen', {tooltip: this}, true); 7528 } 7529 }, 7530 7531 onRemove: function (map) { 7532 L.DivOverlay.prototype.onRemove.call(this, map); 7533 7534 // @namespace Map 7535 // @section Tooltip events 7536 // @event tooltipclose: TooltipEvent 7537 // Fired when a tooltip in the map is closed. 7538 map.fire('tooltipclose', {tooltip: this}); 7539 7540 if (this._source) { 7541 // @namespace Layer 7542 // @section Tooltip events 7543 // @event tooltipclose: TooltipEvent 7544 // Fired when a tooltip bound to this layer is closed. 7545 this._source.fire('tooltipclose', {tooltip: this}, true); 7546 } 7547 }, 7548 7549 getEvents: function () { 7550 var events = L.DivOverlay.prototype.getEvents.call(this); 7551 7552 if (L.Browser.touch && !this.options.permanent) { 7553 events.preclick = this._close; 7554 } 7555 7556 return events; 7557 }, 7558 7559 _close: function () { 7560 if (this._map) { 7561 this._map.closeTooltip(this); 7562 } 7563 }, 7564 7565 _initLayout: function () { 7566 var prefix = 'leaflet-tooltip', 7567 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7568 7569 this._contentNode = this._container = L.DomUtil.create('div', className); 7570 }, 7571 7572 _updateLayout: function () {}, 7573 7574 _adjustPan: function () {}, 7575 7576 _setPosition: function (pos) { 7577 var map = this._map, 7578 container = this._container, 7579 centerPoint = map.latLngToContainerPoint(map.getCenter()), 7580 tooltipPoint = map.layerPointToContainerPoint(pos), 7581 direction = this.options.direction, 7582 tooltipWidth = container.offsetWidth, 7583 tooltipHeight = container.offsetHeight, 7584 offset = L.point(this.options.offset), 7585 anchor = this._getAnchor(); 7586 7587 if (direction === 'top') { 7588 pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true)); 7589 } else if (direction === 'bottom') { 7590 pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true)); 7591 } else if (direction === 'center') { 7592 pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true)); 7593 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) { 7594 direction = 'right'; 7595 pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true)); 7596 } else { 7597 direction = 'left'; 7598 pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true)); 7599 } 7600 7601 L.DomUtil.removeClass(container, 'leaflet-tooltip-right'); 7602 L.DomUtil.removeClass(container, 'leaflet-tooltip-left'); 7603 L.DomUtil.removeClass(container, 'leaflet-tooltip-top'); 7604 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
7605 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction); 7606 L.DomUtil.setPosition(container, pos); 7607 }, 7608 7609 _updatePosition: function () { 7610 var pos = this._map.latLngToLayerPoint(this._latlng); 7611 this._setPosition(pos); 7612 }, 7613 7614 setOpacity: function (opacity) { 7615 this.options.opacity = opacity; 7616 7617 if (this._container) { 7618 L.DomUtil.setOpacity(this._container, opacity); 7619 } 7620 }, 7621 7622 _animateZoom: function (e) { 7623 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 7624 this._setPosition(pos); 7625 }, 7626 7627 _getAnchor: function () { 7628 // Where should we anchor the tooltip on the source layer? 7629 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 7630 } 7631 7632}); 7633 7634// @namespace Tooltip 7635// @factory L.tooltip(options?: Tooltip options, source?: Layer) 7636// 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. 7637L.tooltip = function (options, source) { 7638 return new L.Tooltip(options, source); 7639}; 7640 7641// @namespace Map 7642// @section Methods for Layers and Controls 7643L.Map.include({ 7644 7645 // @method openTooltip(tooltip: Tooltip): this 7646 // Opens the specified tooltip. 7647 // @alternative 7648 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 7649 // Creates a tooltip with the specified content and options and open it. 7650 openTooltip: function (tooltip, latlng, options) { 7651 if (!(tooltip instanceof L.Tooltip)) { 7652 tooltip = new L.Tooltip(options).setContent(tooltip); 7653 } 7654 7655 if (latlng) { 7656 tooltip.setLatLng(latlng); 7657 } 7658 7659 if (this.hasLayer(tooltip)) { 7660 return this; 7661 } 7662 7663 return this.addLayer(tooltip); 7664 }, 7665 7666 // @method closeTooltip(tooltip?: Tooltip): this 7667 // Closes the tooltip given as parameter. 7668 closeTooltip: function (tooltip) { 7669 if (tooltip) { 7670 this.removeLayer(tooltip); 7671 } 7672 return this; 7673 } 7674 7675}); 7676 7677/* 7678 * @namespace Layer 7679 * @section Tooltip methods example 7680 * 7681 * All layers share a set of methods convenient for binding tooltips to it. 7682 * 7683 * ```js 7684 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 7685 * layer.openTooltip(); 7686 * layer.closeTooltip(); 7687 * ``` 7688 */ 7689 7690// @section Tooltip methods 7691L.Layer.include({ 7692 7693 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 7694 // Binds a tooltip to the layer with the passed `content` and sets up the 7695 // neccessary event listeners. If a `Function` is passed it will receive 7696 // the layer as the first argument and should return a `String` or `HTMLElement`. 7697 bindTooltip: function (content, options) { 7698 7699 if (content instanceof L.Tooltip) { 7700 L.setOptions(content, options); 7701 this._tooltip = content; 7702 content._source = this; 7703 } else { 7704 if (!this._tooltip || options) { 7705 this._tooltip = L.tooltip(options, this); 7706 } 7707 this._tooltip.setContent(content); 7708 7709 } 7710 7711 this._initTooltipInteractions(); 7712 7713 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 7714 this.openTooltip(); 7715 } 7716 7717 return this; 7718 }, 7719 7720 // @method unbindTooltip(): this 7721 // Removes the tooltip previously bound with `bindTooltip`. 7722 unbindTooltip: function () { 7723 if (this._tooltip) { 7724 this._initTooltipInteractions(true); 7725 this.closeTooltip(); 7726 this._tooltip = null; 7727 } 7728 return this; 7729 }, 7730 7731 _initTooltipInteractions: function (remove) { 7732 if (!remove && this._tooltipHandlersAdded) { return; } 7733 var onOff = remove ? 'off' : 'on', 7734 events = { 7735 remove: this.closeTooltip, 7736 move: this._moveTooltip 7737 }; 7738 if (!this._tooltip.options.permanent) { 7739 events.mouseover = this._openTooltip; 7740 events.mouseout = this.closeTooltip; 7741 if (this._tooltip.options.sticky) { 7742 events.mousemove = this._moveTooltip; 7743 } 7744 if (L.Browser.touch) { 7745 events.click = this._openTooltip; 7746 } 7747 } else { 7748 events.add = this._openTooltip; 7749 } 7750 this[onOff](events); 7751 this._tooltipHandlersAdded = !remove; 7752 }, 7753 7754 // @method openTooltip(latlng?: LatLng): this 7755 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed. 7756 openTooltip: function (layer, latlng) { 7757 if (!(layer instanceof L.Layer)) { 7758 latlng = layer; 7759 layer = this; 7760 } 7761 7762 if (layer instanceof L.FeatureGroup) { 7763 for (var id in this._layers) { 7764 layer = this._layers[id]; 7765 break; 7766 } 7767 } 7768 7769 if (!latlng) { 7770 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng(); 7771 } 7772 7773 if (this._tooltip && this._map) { 7774 7775 // set tooltip source to this layer 7776 this._tooltip._source = layer; 7777 7778 // update the tooltip (content, layout, ect...) 7779 this._tooltip.update(); 7780 7781 // open the tooltip on the map 7782 this._map.openTooltip(this._tooltip, latlng); 7783 7784 // Tooltip container may not be defined if not permanent and never 7785 // opened. 7786 if (this._tooltip.options.interactive && this._tooltip._container) { 7787 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable'); 7788 this.addInteractiveTarget(this._tooltip._container); 7789 } 7790 } 7791 7792 return this; 7793 }, 7794 7795 // @method closeTooltip(): this 7796 // Closes the tooltip bound to this layer if it is open. 7797 closeTooltip: function () { 7798 if (this._tooltip) { 7799 this._tooltip._close(); 7800 if (this._tooltip.options.interactive && this._tooltip._container) { 7801 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable'); 7802 this.removeInteractiveTarget(this._tooltip._container); 7803 } 7804 } 7805 return this; 7806 }, 7807 7808 // @method toggleTooltip(): this 7809 // Opens or closes the tooltip bound to this layer depending on its current state. 7810 toggleTooltip: function (target) { 7811 if (this._tooltip) { 7812 if (this._tooltip._map) { 7813 this.closeTooltip(); 7814 } else { 7815 this.openTooltip(target); 7816 } 7817 } 7818 return this; 7819 }, 7820 7821 // @method isTooltipOpen(): boolean 7822 // Returns `true` if the tooltip bound to this layer is currently open. 7823 isTooltipOpen: function () { 7824 return this._tooltip.isOpen(); 7825 }, 7826 7827 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
vendor: 8,308 bytes, lines 7828-8144
7828 // Sets the content of the tooltip bound to this layer. 7829 setTooltipContent: function (content) { 7830 if (this._tooltip) { 7831 this._tooltip.setContent(content); 7832 } 7833 return this; 7834 }, 7835 7836 // @method getTooltip(): Tooltip 7837 // Returns the tooltip bound to this layer. 7838 getTooltip: function () { 7839 return this._tooltip; 7840 }, 7841 7842 _openTooltip: function (e) { 7843 var layer = e.layer || e.target; 7844 7845 if (!this._tooltip || !this._map) { 7846 return; 7847 } 7848 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined); 7849 }, 7850 7851 _moveTooltip: function (e) { 7852 var latlng = e.latlng, containerPoint, layerPoint; 7853 if (this._tooltip.options.sticky && e.originalEvent) { 7854 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 7855 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 7856 latlng = this._map.layerPointToLatLng(layerPoint); 7857 } 7858 this._tooltip.setLatLng(latlng); 7859 } 7860}); 7861 7862 7863 7864/* 7865 * @class LayerGroup 7866 * @aka L.LayerGroup 7867 * @inherits Layer 7868 * 7869 * Used to group several layers and handle them as one. If you add it to the map, 7870 * any layers added or removed from the group will be added/removed on the map as 7871 * well. Extends `Layer`. 7872 * 7873 * @example 7874 * 7875 * ```js 7876 * L.layerGroup([marker1, marker2]) 7877 * .addLayer(polyline) 7878 * .addTo(map); 7879 * ``` 7880 */ 7881 7882L.LayerGroup = L.Layer.extend({ 7883 7884 initialize: function (layers) { 7885 this._layers = {}; 7886 7887 var i, len; 7888 7889 if (layers) { 7890 for (i = 0, len = layers.length; i < len; i++) { 7891 this.addLayer(layers[i]); 7892 } 7893 } 7894 }, 7895 7896 // @method addLayer(layer: Layer): this 7897 // Adds the given layer to the group. 7898 addLayer: function (layer) { 7899 var id = this.getLayerId(layer); 7900 7901 this._layers[id] = layer; 7902 7903 if (this._map) { 7904 this._map.addLayer(layer); 7905 } 7906 7907 return this; 7908 }, 7909 7910 // @method removeLayer(layer: Layer): this 7911 // Removes the given layer from the group. 7912 // @alternative 7913 // @method removeLayer(id: Number): this 7914 // Removes the layer with the given internal ID from the group. 7915 removeLayer: function (layer) { 7916 var id = layer in this._layers ? layer : this.getLayerId(layer); 7917 7918 if (this._map && this._layers[id]) { 7919 this._map.removeLayer(this._layers[id]); 7920 } 7921 7922 delete this._layers[id]; 7923 7924 return this; 7925 }, 7926 7927 // @method hasLayer(layer: Layer): Boolean 7928 // Returns `true` if the given layer is currently added to the group. 7929 hasLayer: function (layer) { 7930 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); 7931 }, 7932 7933 // @method clearLayers(): this 7934 // Removes all the layers from the group. 7935 clearLayers: function () { 7936 for (var i in this._layers) { 7937 this.removeLayer(this._layers[i]); 7938 } 7939 return this; 7940 }, 7941 7942 // @method invoke(methodName: String, â¦): this 7943 // Calls `methodName` on every layer contained in this group, passing any 7944 // additional parameters. Has no effect if the layers contained do not 7945 // implement `methodName`. 7946 invoke: function (methodName) { 7947 var args = Array.prototype.slice.call(arguments, 1), 7948 i, layer; 7949 7950 for (i in this._layers) { 7951 layer = this._layers[i]; 7952 7953 if (layer[methodName]) { 7954 layer[methodName].apply(layer, args); 7955 } 7956 } 7957 7958 return this; 7959 }, 7960 7961 onAdd: function (map) { 7962 for (var i in this._layers) { 7963 map.addLayer(this._layers[i]); 7964 } 7965 }, 7966 7967 onRemove: function (map) { 7968 for (var i in this._layers) { 7969 map.removeLayer(this._layers[i]); 7970 } 7971 }, 7972 7973 // @method eachLayer(fn: Function, context?: Object): this 7974 // Iterates over the layers of the group, optionally specifying context of the iterator function. 7975 // ```js 7976 // group.eachLayer(function (layer) { 7977 // layer.bindPopup('Hello'); 7978 // }); 7979 // ``` 7980 eachLayer: function (method, context) { 7981 for (var i in this._layers) { 7982 method.call(context, this._layers[i]); 7983 } 7984 return this; 7985 }, 7986 7987 // @method getLayer(id: Number): Layer 7988 // Returns the layer with the given internal ID. 7989 getLayer: function (id) { 7990 return this._layers[id]; 7991 }, 7992 7993 // @method getLayers(): Layer[] 7994 // Returns an array of all the layers added to the group. 7995 getLayers: function () { 7996 var layers = []; 7997 7998 for (var i in this._layers) { 7999 layers.push(this._layers[i]); 8000 } 8001 return layers; 8002 }, 8003 8004 // @method setZIndex(zIndex: Number): this 8005 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 8006 setZIndex: function (zIndex) { 8007 return this.invoke('setZIndex', zIndex); 8008 }, 8009 8010 // @method getLayerId(layer: Layer): Number 8011 // Returns the internal ID for a layer 8012 getLayerId: function (layer) { 8013 return L.stamp(layer); 8014 } 8015}); 8016 8017 8018// @factory L.layerGroup(layers: Layer[]) 8019// Create a layer group, optionally given an initial set of layers. 8020L.layerGroup = function (layers) { 8021 return new L.LayerGroup(layers); 8022}; 8023 8024 8025 8026/* 8027 * @class FeatureGroup 8028 * @aka L.FeatureGroup 8029 * @inherits LayerGroup 8030 * 8031 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 8032 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 8033 * * Events are propagated to the `FeatureGroup`, so if the group has an event 8034 * handler, it will handle events from any of the layers. This includes mouse events 8035 * and custom events. 8036 * * Has `layeradd` and `layerremove` events 8037 * 8038 * @example 8039 * 8040 * ```js 8041 * L.featureGroup([marker1, marker2, polyline]) 8042 * .bindPopup('Hello world!') 8043 * .on('click', function() { alert('Clicked on a member of the group!'); }) 8044 * .addTo(map); 8045 * ``` 8046 */ 8047 8048L.FeatureGroup = L.LayerGroup.extend({ 8049 8050 addLayer: function (layer) { 8051 if (this.hasLayer(layer)) { 8052 return this; 8053 } 8054 8055 layer.addEventParent(this); 8056 8057 L.LayerGroup.prototype.addLayer.call(this, layer); 8058 8059 // @event layeradd: LayerEvent 8060 // Fired when a layer is added to this `FeatureGroup` 8061 return this.fire('layeradd', {layer: layer}); 8062 }, 8063 8064 removeLayer: function (layer) { 8065 if (!this.hasLayer(layer)) { 8066 return this; 8067 } 8068 if (layer in this._layers) { 8069 layer = this._layers[layer]; 8070 } 8071 8072 layer.removeEventParent(this); 8073 8074 L.LayerGroup.prototype.removeLayer.call(this, layer); 8075 8076 // @event layerremove: LayerEvent 8077 // Fired when a layer is removed from this `FeatureGroup` 8078 return this.fire('layerremove', {layer: layer}); 8079 }, 8080 8081 // @method setStyle(style: Path options): this 8082 // Sets the given path options to each layer of the group that has a `setStyle` method. 8083 setStyle: function (style) { 8084 return this.invoke('setStyle', style); 8085 }, 8086 8087 // @method bringToFront(): this 8088 // Brings the layer group to the top of all other layers 8089 bringToFront: function () { 8090 return this.invoke('bringToFront'); 8091 }, 8092 8093 // @method bringToBack(): this 8094 // Brings the layer group to the top of all other layers 8095 bringToBack: function () { 8096 return this.invoke('bringToBack'); 8097 }, 8098 8099 // @method getBounds(): LatLngBounds 8100 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 8101 getBounds: function () { 8102 var bounds = new L.LatLngBounds(); 8103 8104 for (var id in this._layers) { 8105 var layer = this._layers[id]; 8106 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 8107 } 8108 return bounds; 8109 } 8110}); 8111 8112// @factory L.featureGroup(layers: Layer[]) 8113// Create a feature group, optionally given an initial set of layers. 8114L.featureGroup = function (layers) { 8115 return new L.FeatureGroup(layers); 8116}; 8117 8118 8119 8120/* 8121 * @class Renderer 8122 * @inherits Layer 8123 * @aka L.Renderer 8124 * 8125 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 8126 * DOM container of the renderer, its bounds, and its zoom animation. 8127 * 8128 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 8129 * itself can be added or removed to the map. All paths use a renderer, which can 8130 * be implicit (the map will decide the type of renderer and use it automatically) 8131 * or explicit (using the [`renderer`](#path-renderer) option of the path). 8132 * 8133 * Do not use this class directly, use `SVG` and `Canvas` instead. 8134 * 8135 * @event update: Event 8136 * Fired when the renderer updates its bounds, center and zoom, for example when 8137 * its map has moved 8138 */ 8139 8140L.Renderer = L.Layer.extend({ 8141 8142 // @section 8143 // @aka Renderer options 8144 options: {
8145 // @option padding: Number = 0.1 8146 // How much to extend the clip area around the map view (relative to its size) 8147 // e.g. 0.1 would be 10% of map view in each direction 8148 padding: 0.1 8149 }, 8150 8151 initialize: function (options) { 8152 L.setOptions(this, options); 8153 L.stamp(this); 8154 this._layers = this._layers || {}; 8155 }, 8156 8157 onAdd: function () { 8158 if (!this._container) { 8159 this._initContainer(); // defined by renderer implementations 8160 8161 if (this._zoomAnimated) { 8162 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated'); 8163 } 8164 } 8165 8166 this.getPane().appendChild(this._container); 8167 this._update(); 8168 this.on('update', this._updatePaths, this); 8169 }, 8170 8171 onRemove: function () { 8172 L.DomUtil.remove(this._container); 8173 this.off('update', this._updatePaths, this); 8174 }, 8175 8176 getEvents: function () { 8177 var events = { 8178 viewreset: this._reset, 8179 zoom: this._onZoom, 8180 moveend: this._update, 8181 zoomend: this._onZoomEnd 8182 }; 8183 if (this._zoomAnimated) { 8184 events.zoomanim = this._onAnimZoom; 8185 } 8186 return events; 8187 }, 8188 8189 _onAnimZoom: function (ev) { 8190 this._updateTransform(ev.center, ev.zoom); 8191 }, 8192 8193 _onZoom: function () { 8194 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 8195 }, 8196 8197 _updateTransform: function (center, zoom) { 8198 var scale = this._map.getZoomScale(zoom, this._zoom), 8199 position = L.DomUtil.getPosition(this._container), 8200 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 8201 currentCenterPoint = this._map.project(this._center, zoom), 8202 destCenterPoint = this._map.project(center, zoom), 8203 centerOffset = destCenterPoint.subtract(currentCenterPoint), 8204 8205 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset); 8206 8207 if (L.Browser.any3d) { 8208 L.DomUtil.setTransform(this._container, topLeftOffset, scale); 8209 } else { 8210 L.DomUtil.setPosition(this._container, topLeftOffset); 8211 } 8212 }, 8213 8214 _reset: function () { 8215 this._update(); 8216 this._updateTransform(this._center, this._zoom); 8217 8218 for (var id in this._layers) { 8219 this._layers[id]._reset(); 8220 } 8221 }, 8222 8223 _onZoomEnd: function () { 8224 for (var id in this._layers) { 8225 this._layers[id]._project(); 8226 } 8227 }, 8228 8229 _updatePaths: function () { 8230 for (var id in this._layers) { 8231 this._layers[id]._update(); 8232 } 8233 }, 8234 8235 _update: function () { 8236 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 8237 // Subclasses are responsible of firing the 'update' event. 8238 var p = this.options.padding, 8239 size = this._map.getSize(), 8240 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 8241 8242 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 8243 8244 this._center = this._map.getCenter(); 8245 this._zoom = this._map.getZoom(); 8246 } 8247}); 8248 8249 8250L.Map.include({ 8251 // @namespace Map; @method getRenderer(layer: Path): Renderer 8252 // Returns the instance of `Renderer` that should be used to render the given 8253 // `Path`. It will ensure that the `renderer` options of the map and paths 8254 // are respected, and that the renderers do exist on the map. 8255 getRenderer: function (layer) { 8256 // @namespace Path; @option renderer: Renderer 8257 // Use this specific instance of `Renderer` for this path. Takes 8258 // precedence over the map's [default renderer](#map-renderer). 8259 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 8260 8261 if (!renderer) { 8262 // @namespace Map; @option preferCanvas: Boolean = false 8263 // Whether `Path`s should be rendered on a `Canvas` renderer. 8264 // By default, all `Path`s are rendered in a `SVG` renderer. 8265 renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg(); 8266 } 8267 8268 if (!this.hasLayer(renderer)) { 8269 this.addLayer(renderer); 8270 } 8271 return renderer; 8272 }, 8273 8274 _getPaneRenderer: function (name) { 8275 if (name === 'overlayPane' || name === undefined) { 8276 return false; 8277 } 8278 8279 var renderer = this._paneRenderers[name]; 8280 if (renderer === undefined) { 8281 renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name})); 8282 this._paneRenderers[name] = renderer; 8283 } 8284 return renderer; 8285 } 8286}); 8287 8288 8289 8290/* 8291 * @class Path 8292 * @aka L.Path 8293 * @inherits Interactive layer 8294 * 8295 * An abstract class that contains options and constants shared between vector 8296 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 8297 */ 8298 8299L.Path = L.Layer.extend({ 8300 8301 // @section 8302 // @aka Path options 8303 options: {
8304 // @option stroke: Boolean = true 8305 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 8306 stroke: true, 8307 8308 // @option color: String = '#3388ff' 8309 // Stroke color 8310 color: '#3388ff', 8311 8312 // @option weight: Number = 3 8313 // Stroke width in pixels 8314 weight: 3, 8315 8316 // @option opacity: Number = 1.0 8317 // Stroke opacity 8318 opacity: 1, 8319 8320 // @option lineCap: String= 'round' 8321 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 8322 lineCap: 'round', 8323 8324 // @option lineJoin: String = 'round' 8325 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 8326 lineJoin: 'round', 8327 8328 // @option dashArray: String = null 8329 // 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). 8330 dashArray: null, 8331 8332 // @option dashOffset: String = null 8333 // 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). 8334 dashOffset: null, 8335 8336 // @option fill: Boolean = depends 8337 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 8338 fill: false, 8339 8340 // @option fillColor: String = * 8341 // Fill color. Defaults to the value of the [`color`](#path-color) option 8342 fillColor: null, 8343 8344 // @option fillOpacity: Number = 0.2 8345 // Fill opacity. 8346 fillOpacity: 0.2, 8347 8348 // @option fillRule: String = 'evenodd' 8349 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 8350 fillRule: 'evenodd', 8351 8352 // className: '', 8353 8354 // Option inherited from "Interactive layer" abstract class 8355 interactive: true 8356 }, 8357 8358 beforeAdd: function (map) { 8359 // Renderer is set here because we need to call renderer.getEvents 8360 // before this.getEvents. 8361 this._renderer = map.getRenderer(this); 8362 }, 8363 8364 onAdd: function () { 8365 this._renderer._initPath(this); 8366 this._reset(); 8367 this._renderer._addPath(this); 8368 }, 8369 8370 onRemove: function () { 8371 this._renderer._removePath(this); 8372 }, 8373 8374 // @method redraw(): this 8375 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 8376 redraw: function () { 8377 if (this._map) { 8378 this._renderer._updatePath(this); 8379 } 8380 return this; 8381 }, 8382 8383 // @method setStyle(style: Path options): this 8384 // Changes the appearance of a Path based on the options in the `Path options` object. 8385 setStyle: function (style) { 8386 L.setOptions(this, style); 8387 if (this._renderer) { 8388 this._renderer._updateStyle(this); 8389 } 8390 return this; 8391 }, 8392 8393 // @method bringToFront(): this 8394 // Brings the layer to the top of all path layers. 8395 bringToFront: function () { 8396 if (this._renderer) { 8397 this._renderer._bringToFront(this); 8398 } 8399 return this; 8400 }, 8401 8402 // @method bringToBack(): this 8403 // Brings the layer to the bottom of all path layers. 8404 bringToBack: function () { 8405 if (this._renderer) { 8406 this._renderer._bringToBack(this); 8407 } 8408 return this; 8409 }, 8410 8411 getElement: function () { 8412 return this._path; 8413 }, 8414 8415 _reset: function () { 8416 // defined in children classes 8417 this._project(); 8418 this._update(); 8419 }, 8420 8421 _clickTolerance: function () { 8422 // used when doing hit detection for Canvas layers 8423 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0); 8424 } 8425}); 8426 8427 8428 8429/* 8430 * @namespace LineUtil 8431 * 8432 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast. 8433 */ 8434 8435L.LineUtil = { 8436 8437 // Simplify polyline with vertex reduction and Douglas-Peucker simplification. 8438 // Improves rendering performance dramatically by lessening the number of points to draw. 8439 8440 // @function simplify(points: Point[], tolerance: Number): Point[] 8441 // Dramatically reduces the number of points in a polyline while retaining 8442 // its shape and returns a new array of simplified points, using the 8443 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). 8444 // Used for a huge performance boost when processing/displaying Leaflet polylines for 8445 // each zoom level and also reducing visual noise. tolerance affects the amount of 8446 // simplification (lesser value means higher quality but slower and with more points). 8447 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). 8448 simplify: function (points, tolerance) { 8449 if (!tolerance || !points.length) { 8450 return points.slice(); 8451 } 8452 8453 var sqTolerance = tolerance * tolerance; 8454 8455 // stage 1: vertex reduction 8456 points = this._reducePoints(points, sqTolerance); 8457 8458 // stage 2: Douglas-Peucker simplification 8459 points = this._simplifyDP(points, sqTolerance); 8460 8461 return points; 8462 }, 8463 8464 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 8465 // Returns the distance between point `p` and segment `p1` to `p2`. 8466 pointToSegmentDistance: function (p, p1, p2) { 8467 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); 8468 }, 8469 8470 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 8471 // Returns the closest point from a point `p` on a segment `p1` to `p2`. 8472 closestPointOnSegment: function (p, p1, p2) { 8473 return this._sqClosestPointOnSegment(p, p1, p2); 8474 }, 8475 8476 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm 8477 _simplifyDP: function (points, sqTolerance) { 8478 8479 var len = points.length, 8480 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 8481 markers = new ArrayConstructor(len); 8482 8483 markers[0] = markers[len - 1] = 1; 8484 8485 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 8486 8487 var i, 8488 newPoints = []; 8489 8490 for (i = 0; i < len; i++) { 8491 if (markers[i]) { 8492 newPoints.push(points[i]); 8493 } 8494 } 8495 8496 return newPoints; 8497 }, 8498 8499 _simplifyDPStep: function (points, markers, sqTolerance, first, last) { 8500 8501 var maxSqDist = 0, 8502 index, i, sqDist; 8503 8504 for (i = first + 1; i <= last - 1; i++) { 8505 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); 8506 8507 if (sqDist > maxSqDist) { 8508 index = i; 8509 maxSqDist = sqDist; 8510 } 8511 } 8512 8513 if (maxSqDist > sqTolerance) { 8514 markers[index] = 1; 8515 8516 this._simplifyDPStep(points, markers, sqTolerance, first, index); 8517 this._simplifyDPStep(points, markers, sqTolerance, index, last); 8518 } 8519 }, 8520 8521 // reduce points that are too close to each other to a single point 8522 _reducePoints: function (points, sqTolerance) { 8523 var reducedPoints = [points[0]]; 8524 8525 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 8526 if (this._sqDist(points[i], points[prev]) > sqTolerance) { 8527 reducedPoints.push(points[i]); 8528 prev = i; 8529 } 8530 } 8531 if (prev < len - 1) { 8532 reducedPoints.push(points[len - 1]); 8533 } 8534 return reducedPoints; 8535 }, 8536 8537 8538 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, roun
8538d?: Boolean): Point[]|Boolean 8539 // Clips the segment a to b by rectangular bounds with the 8540 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 8541 // (modifying the segment points directly!). Used by Leaflet to only show polyline 8542 // points that are on the screen or near, increasing performance. 8543 clipSegment: function (a, b, bounds, useLastCode, round) { 8544 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), 8545 codeB = this._getBitCode(b, bounds), 8546 8547 codeOut, p, newCode; 8548 8549 // save 2nd code to avoid calculating it on the next segment 8550 this._lastCode = codeB; 8551 8552 while (true) { 8553 // if a,b is inside the clip window (trivial accept) 8554 if (!(codeA | codeB)) { 8555 return [a, b]; 8556 } 8557 8558 // if a,b is outside the clip window (trivial reject) 8559 if (codeA & codeB) { 8560 return false; 8561 } 8562 8563 // other cases 8564 codeOut = codeA || codeB; 8565 p = this._getEdgeIntersection(a, b, codeOut, bounds, round); 8566 newCode = this._getBitCode(p, bounds); 8567 8568 if (codeOut === codeA) { 8569 a = p; 8570 codeA = newCode; 8571 } else { 8572 b = p; 8573 codeB = newCode; 8574 } 8575 } 8576 }, 8577
vendor: 5,078 bytes, lines 8578-8795
8578 _getEdgeIntersection: function (a, b, code, bounds, round) { 8579 var dx = b.x - a.x, 8580 dy = b.y - a.y, 8581 min = bounds.min, 8582 max = bounds.max, 8583 x, y; 8584 8585 if (code & 8) { // top 8586 x = a.x + dx * (max.y - a.y) / dy; 8587 y = max.y; 8588 8589 } else if (code & 4) { // bottom 8590 x = a.x + dx * (min.y - a.y) / dy; 8591 y = min.y; 8592 8593 } else if (code & 2) { // right 8594 x = max.x; 8595 y = a.y + dy * (max.x - a.x) / dx; 8596 8597 } else if (code & 1) { // left 8598 x = min.x; 8599 y = a.y + dy * (min.x - a.x) / dx; 8600 } 8601 8602 return new L.Point(x, y, round); 8603 }, 8604 8605 _getBitCode: function (p, bounds) { 8606 var code = 0; 8607 8608 if (p.x < bounds.min.x) { // left 8609 code |= 1; 8610 } else if (p.x > bounds.max.x) { // right 8611 code |= 2; 8612 } 8613 8614 if (p.y < bounds.min.y) { // bottom 8615 code |= 4; 8616 } else if (p.y > bounds.max.y) { // top 8617 code |= 8; 8618 } 8619 8620 return code; 8621 }, 8622 8623 // square distance (to avoid unnecessary Math.sqrt calls) 8624 _sqDist: function (p1, p2) { 8625 var dx = p2.x - p1.x, 8626 dy = p2.y - p1.y; 8627 return dx * dx + dy * dy; 8628 }, 8629 8630 // return closest point on segment or distance to that point 8631 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { 8632 var x = p1.x, 8633 y = p1.y, 8634 dx = p2.x - x, 8635 dy = p2.y - y, 8636 dot = dx * dx + dy * dy, 8637 t; 8638 8639 if (dot > 0) { 8640 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 8641 8642 if (t > 1) { 8643 x = p2.x; 8644 y = p2.y; 8645 } else if (t > 0) { 8646 x += dx * t; 8647 y += dy * t; 8648 } 8649 } 8650 8651 dx = p.x - x; 8652 dy = p.y - y; 8653 8654 return sqDist ? dx * dx + dy * dy : new L.Point(x, y); 8655 } 8656}; 8657 8658 8659 8660/* 8661 * @class Polyline 8662 * @aka L.Polyline 8663 * @inherits Path 8664 * 8665 * A class for drawing polyline overlays on a map. Extends `Path`. 8666 * 8667 * @example 8668 * 8669 * ```js 8670 * // create a red polyline from an array of LatLng points 8671 * var latlngs = [ 8672 * [45.51, -122.68], 8673 * [37.77, -122.43], 8674 * [34.04, -118.2] 8675 * ]; 8676 * 8677 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8678 * 8679 * // zoom the map to the polyline 8680 * map.fitBounds(polyline.getBounds()); 8681 * ``` 8682 * 8683 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8684 * 8685 * ```js 8686 * // create a red polyline from an array of arrays of LatLng points 8687 * var latlngs = [ 8688 * [[45.51, -122.68], 8689 * [37.77, -122.43], 8690 * [34.04, -118.2]], 8691 * [[40.78, -73.91], 8692 * [41.83, -87.62], 8693 * [32.76, -96.72]] 8694 * ]; 8695 * ``` 8696 */ 8697 8698L.Polyline = L.Path.extend({ 8699 8700 // @section 8701 // @aka Polyline options 8702 options: { 8703 // @option smoothFactor: Number = 1.0 8704 // How much to simplify the polyline on each zoom level. More means 8705 // better performance and smoother look, and less means more accurate representation. 8706 smoothFactor: 1.0, 8707 8708 // @option noClip: Boolean = false 8709 // Disable polyline clipping. 8710 noClip: false 8711 }, 8712 8713 initialize: function (latlngs, options) { 8714 L.setOptions(this, options); 8715 this._setLatLngs(latlngs); 8716 }, 8717 8718 // @method getLatLngs(): LatLng[] 8719 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8720 getLatLngs: function () { 8721 return this._latlngs; 8722 }, 8723 8724 // @method setLatLngs(latlngs: LatLng[]): this 8725 // Replaces all the points in the polyline with the given array of geographical points. 8726 setLatLngs: function (latlngs) { 8727 this._setLatLngs(latlngs); 8728 return this.redraw(); 8729 }, 8730 8731 // @method isEmpty(): Boolean 8732 // Returns `true` if the Polyline has no LatLngs. 8733 isEmpty: function () { 8734 return !this._latlngs.length; 8735 }, 8736 8737 closestLayerPoint: function (p) { 8738 var minDistance = Infinity, 8739 minPoint = null, 8740 closest = L.LineUtil._sqClosestPointOnSegment, 8741 p1, p2; 8742 8743 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8744 var points = this._parts[j]; 8745 8746 for (var i = 1, len = points.length; i < len; i++) { 8747 p1 = points[i - 1]; 8748 p2 = points[i]; 8749 8750 var sqDist = closest(p, p1, p2, true); 8751 8752 if (sqDist < minDistance) { 8753 minDistance = sqDist; 8754 minPoint = closest(p, p1, p2); 8755 } 8756 } 8757 } 8758 if (minPoint) { 8759 minPoint.distance = Math.sqrt(minDistance); 8760 } 8761 return minPoint; 8762 }, 8763 8764 // @method getCenter(): LatLng 8765 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline. 8766 getCenter: function () { 8767 // throws error when not yet added to map as this center calculation requires projected coordinates 8768 if (!this._map) { 8769 throw new Error('Must add layer to map before using getCenter()'); 8770 } 8771 8772 var i, halfDist, segDist, dist, p1, p2, ratio, 8773 points = this._rings[0], 8774 len = points.length; 8775 8776 if (!len) { return null; } 8777 8778 // polyline centroid algorithm; only uses the first ring if there are multiple 8779 8780 for (i = 0, halfDist = 0; i < len - 1; i++) { 8781 halfDist += points[i].distanceTo(points[i + 1]) / 2; 8782 } 8783 8784 // The line is so small in the current view that all points are on the same pixel. 8785 if (halfDist === 0) { 8786 return this._map.layerPointToLatLng(points[0]); 8787 } 8788 8789 for (i = 0, dist = 0; i < len - 1; i++) { 8790 p1 = points[i]; 8791 p2 = points[i + 1]; 8792 segDist = p1.distanceTo(p2); 8793 dist += segDist; 8794 8795 if (dist > halfDist) {
8796 ratio = (dist - halfDist) / segDist; 8797 return this._map.layerPointToLatLng([ 8798 p2.x - ratio * (p2.x - p1.x), 8799 p2.y - ratio * (p2.y - p1.y) 8800 ]); 8801 } 8802 } 8803 }, 8804 8805 // @method getBounds(): LatLngBounds 8806 // Returns the `LatLngBounds` of the path. 8807 getBounds: function () { 8808 return this._bounds; 8809 }, 8810 8811 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this 8812 // Adds a given point to the polyline. By default, adds to the first ring of 8813 // the polyline in case of a multi-polyline, but can be overridden by passing 8814 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8815 addLatLng: function (latlng, latlngs) { 8816 latlngs = latlngs || this._defaultShape(); 8817 latlng = L.latLng(latlng); 8818 latlngs.push(latlng); 8819 this._bounds.extend(latlng); 8820 return this.redraw(); 8821 }, 8822 8823 _setLatLngs: function (latlngs) { 8824 this._bounds = new L.LatLngBounds(); 8825 this._latlngs = this._convertLatLngs(latlngs); 8826 }, 8827 8828 _defaultShape: function () { 8829 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8830 }, 8831 8832 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8833 _convertLatLngs: function (latlngs) { 8834 var result = [], 8835 flat = L.Polyline._flat(latlngs); 8836 8837 for (var i = 0, len = latlngs.length; i < len; i++) { 8838 if (flat) { 8839 result[i] = L.latLng(latlngs[i]); 8840 this._bounds.extend(result[i]); 8841 } else { 8842 result[i] = this._convertLatLngs(latlngs[i]); 8843 } 8844 } 8845 8846 return result; 8847 }, 8848 8849 _project: function () { 8850 var pxBounds = new L.Bounds(); 8851 this._rings = []; 8852 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8853 8854 var w = this._clickTolerance(), 8855 p = new L.Point(w, w); 8856 8857 if (this._bounds.isValid() && pxBounds.isValid()) { 8858 pxBounds.min._subtract(p); 8859 pxBounds.max._add(p); 8860 this._pxBounds = pxBounds; 8861 } 8862 }, 8863 8864 // recursively turns latlngs into a set of rings with projected coordinates 8865 _projectLatlngs: function (latlngs, result, projectedBounds) { 8866 var flat = latlngs[0] instanceof L.LatLng, 8867 len = latlngs.length, 8868 i, ring; 8869 8870 if (flat) { 8871 ring = []; 8872 for (i = 0; i < len; i++) { 8873 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8874 projectedBounds.extend(ring[i]); 8875 } 8876 result.push(ring); 8877 } else { 8878 for (i = 0; i < len; i++) { 8879 this._projectLatlngs(latlngs[i], result, projectedBounds); 8880 } 8881 } 8882 }, 8883 8884 // clip polyline by renderer bounds so that we have less to render for performance 8885 _clipPoints: function () { 8886 var bounds = this._renderer._bounds; 8887 8888 this._parts = []; 8889 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8890 return; 8891 } 8892 8893 if (this.options.noClip) { 8894 this._parts = this._rings; 8895 return; 8896 } 8897 8898 var parts = this._parts, 8899 i, j, k, len, len2, segment, points; 8900 8901 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8902 points = this._rings[i]; 8903 8904 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8905 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true); 8906 8907 if (!segment) { continue; } 8908 8909 parts[k] = parts[k] || []; 8910 parts[k].push(segment[0]); 8911 8912 // if segment goes out of screen, or it's the last one, it's the end of the line part 8913 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8914 parts[k].push(segment[1]); 8915 k++; 8916 } 8917 } 8918 } 8919 }, 8920 8921 // simplify each clipped part of the polyline for performance 8922 _simplifyPoints: function () { 8923 var parts = this._parts, 8924 tolerance = this.options.smoothFactor; 8925 8926 for (var i = 0, len = parts.length; i < len; i++) { 8927 parts[i] = L.LineUtil.simplify(parts[i], tolerance); 8928 } 8929 }, 8930 8931 _update: function () { 8932 if (!this._map) { return; } 8933 8934 this._clipPoints(); 8935 this._simplifyPoints(); 8936 this._updatePath(); 8937 }, 8938 8939 _updatePath: function () { 8940 this._renderer._updatePoly(this); 8941 } 8942}); 8943 8944// @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8945// Instantiates a polyline object given an array of geographical points and 8946// optionally an options object. You can create a `Polyline` object with 8947// multiple separate lines (`MultiPolyline`) by passing an array of arrays 8948// of geographic points. 8949L.polyline = function (latlngs, options) { 8950 return new L.Polyline(latlngs, options); 8951}; 8952 8953L.Polyline._flat = function (latlngs) { 8954 // true if it's a flat array of latlngs; false if nested 8955 return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 8956}; 8957 8958 8959 8960/* 8961 * @namespace PolyUtil 8962 * Various utility functions for polygon geometries. 8963 */ 8964 8965L.PolyUtil = {}; 8966 8967/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 8968 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)). 8969 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 8970 * performance. Note that polygon points needs different algorithm for clipping 8971 * than polyline, so there's a seperate method for it. 8972 */ 8973L.PolyUtil.clipPolygon = function (points, bounds, round) { 8974 var clippedPoints, 8975 edges = [1, 4, 2, 8], 8976 i, j, k, 8977 a, b, 8978 len, edge, p, 8979 lu = L.LineUtil; 8980 8981 for (i = 0, len = points.length; i < len; i++) { 8982 points[i]._code = lu._getBitCode(points[i], bounds); 8983 } 8984 8985 // for each edge (left, bottom, right, top) 8986 for (k = 0; k < 4; k++) { 8987 edge = edges[k]; 8988 clippedPoints = []; 8989 8990 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 8991 a = points[i]; 8992 b = points[j];
8993 8994 // if a is inside the clip window 8995 if (!(a._code & edge)) { 8996 // if b is outside the clip window (a->b goes out of screen) 8997 if (b._code & edge) { 8998 p = lu._getEdgeIntersection(b, a, edge, bounds, round); 8999 p._code = lu._getBitCode(p, bounds); 9000 clippedPoints.push(p); 9001 } 9002 clippedPoints.push(a); 9003 9004 // else if b is inside the clip window (a->b enters the screen) 9005 } else if (!(b._code & edge)) { 9006 p = lu._getEdgeIntersection(b, a, edge, bounds, round); 9007 p._code = lu._getBitCode(p, bounds); 9008 clippedPoints.push(p); 9009 } 9010 } 9011 points = clippedPoints; 9012 } 9013 9014 return points; 9015}; 9016 9017 9018 9019/* 9020 * @class Polygon 9021 * @aka L.Polygon 9022 * @inherits Polyline 9023 * 9024 * A class for drawing polygon overlays on a map. Extends `Polyline`. 9025 * 9026 * 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. 9027 * 9028 * 9029 * @example 9030 * 9031 * ```js 9032 * // create a red polygon from an array of LatLng points 9033 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 9034 * 9035 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 9036 * 9037 * // zoom the map to the polygon 9038 * map.fitBounds(polygon.getBounds()); 9039 * ``` 9040 * 9041 * 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: 9042 * 9043 * ```js 9044 * var latlngs = [ 9045 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 9046 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 9047 * ]; 9048 * ``` 9049 * 9050 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 9051 * 9052 * ```js 9053 * var latlngs = [ 9054 * [ // first polygon 9055 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 9056 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 9057 * ], 9058 * [ // second polygon 9059 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 9060 * ] 9061 * ]; 9062 * ``` 9063 */ 9064 9065L.Polygon = L.Polyline.extend({ 9066 9067 options: { 9068 fill: true 9069 }, 9070 9071 isEmpty: function () { 9072 return !this._latlngs.length || !this._latlngs[0].length; 9073 }, 9074 9075 getCenter: function () { 9076 // throws error when not yet added to map as this center calculation requires projected coordinates 9077 if (!this._map) { 9078 throw new Error('Must add layer to map before using getCenter()'); 9079 } 9080 9081 var i, j, p1, p2, f, area, x, y, center, 9082 points = this._rings[0], 9083 len = points.length; 9084 9085 if (!len) { return null; } 9086 9087 // polygon centroid algorithm; only uses the first ring if there are multiple 9088 9089 area = x = y = 0; 9090 9091 for (i = 0, j = len - 1; i < len; j = i++) { 9092 p1 = points[i]; 9093 p2 = points[j]; 9094 9095 f = p1.y * p2.x - p2.y * p1.x; 9096 x += (p1.x + p2.x) * f; 9097 y += (p1.y + p2.y) * f; 9098 area += f * 3; 9099 } 9100 9101 if (area === 0) { 9102 // Polygon is so small that all points are on same pixel. 9103 center = points[0]; 9104 } else { 9105 center = [x / area, y / area]; 9106 } 9107 return this._map.layerPointToLatLng(center); 9108 }, 9109 9110 _convertLatLngs: function (latlngs) { 9111 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs), 9112 len = result.length; 9113 9114 // remove last point if it equals first one 9115 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) { 9116 result.pop(); 9117 } 9118 return result; 9119 }, 9120 9121 _setLatLngs: function (latlngs) { 9122 L.Polyline.prototype._setLatLngs.call(this, latlngs); 9123 if (L.Polyline._flat(this._latlngs)) { 9124 this._latlngs = [this._latlngs]; 9125 } 9126 }, 9127 9128 _defaultShape: function () { 9129 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 9130 }, 9131 9132 _clipPoints: function () { 9133 // polygons need a different clipping algorithm so we redefine that 9134 9135 var bounds = this._renderer._bounds, 9136 w = this.options.weight, 9137 p = new L.Point(w, w); 9138 9139 // increase clip padding by stroke width to avoid stroke on clip edges 9140 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p)); 9141 9142 this._parts = []; 9143 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 9144 return; 9145 } 9146 9147 if (this.options.noClip) { 9148 this._parts = this._rings; 9149 return; 9150 } 9151 9152 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 9153 clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true); 9154 if (clipped.length) { 9155 this._parts.push(clipped); 9156 } 9157 } 9158 }, 9159 9160 _updatePath: function () { 9161 this._renderer._updatePoly(this, true); 9162 } 9163}); 9164 9165 9166// @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 9167L.polygon = function (latlngs, options) { 9168 return new L.Polygon(latlngs, options); 9169}; 9170 9171 9172 9173/* 9174 * L.Rectangle extends Polygon and creates a rectangle when passed a
9174LatLngBounds object. 9175 */ 9176 9177/* 9178 * @class Rectangle 9179 * @aka L.Retangle 9180 * @inherits Polygon 9181 * 9182 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 9183 * 9184 * @example 9185 * 9186 * ```js 9187 * // define rectangle geographical bounds 9188 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 9189 * 9190 * // create an orange rectangle 9191 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 9192 * 9193 * // zoom the map to the rectangle bounds 9194 * map.fitBounds(bounds); 9195 * ``` 9196 * 9197 */ 9198 9199 9200L.Rectangle = L.Polygon.extend({ 9201 initialize: function (latLngBounds, options) { 9202 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 9203 }, 9204 9205 // @method setBounds(latLngBounds: LatLngBounds): this 9206 // Redraws the rectangle with the passed bounds. 9207 setBounds: function (latLngBounds) { 9208 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 9209 }, 9210 9211 _boundsToLatLngs: function (latLngBounds) { 9212 latLngBounds = L.latLngBounds(latLngBounds); 9213 return [ 9214 latLngBounds.getSouthWest(), 9215 latLngBounds.getNorthWest(), 9216 latLngBounds.getNorthEast(), 9217 latLngBounds.getSouthEast() 9218 ]; 9219 } 9220}); 9221 9222 9223// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 9224L.rectangle = function (latLngBounds, options) { 9225 return new L.Rectangle(latLngBounds, options); 9226}; 9227 9228 9229 9230/* 9231 * @class CircleMarker 9232 * @aka L.CircleMarker 9233 * @inherits Path 9234 * 9235 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 9236 */ 9237 9238L.CircleMarker = L.Path.extend({ 9239 9240 // @section 9241 // @aka CircleMarker options 9242 options: { 9243 fill: true, 9244 9245 // @option radius: Number = 10 9246 // Radius of the circle marker, in pixels 9247 radius: 10 9248 }, 9249 9250 initialize: function (latlng, options) { 9251 L.setOptions(this, options); 9252 this._latlng = L.latLng(latlng); 9253 this._radius = this.options.radius; 9254 }, 9255 9256 // @method setLatLng(latLng: LatLng): this 9257 // Sets the position of a circle marker to a new location. 9258 setLatLng: function (latlng) { 9259 this._latlng = L.latLng(latlng); 9260 this.redraw(); 9261 return this.fire('move', {latlng: this._latlng}); 9262 }, 9263 9264 // @method getLatLng(): LatLng 9265 // Returns the current geographical position of the circle marker 9266 getLatLng: function () { 9267 return this._latlng; 9268 }, 9269 9270 // @method setRadius(radius: Number): this 9271 // Sets the radius of a circle marker. Units are in pixels. 9272 setRadius: function (radius) { 9273 this.options.radius = this._radius = radius; 9274 return this.redraw(); 9275 }, 9276 9277 // @method getRadius(): Number 9278 // Returns the current radius of the circle 9279 getRadius: function () { 9280 return this._radius; 9281 }, 9282 9283 setStyle : function (options) { 9284 var radius = options && options.radius || this._radius; 9285 L.Path.prototype.setStyle.call(this, options); 9286 this.setRadius(radius); 9287 return this; 9288 }, 9289 9290 _project: function () { 9291 this._point = this._map.latLngToLayerPoint(this._latlng); 9292 this._updateBounds(); 9293 }, 9294 9295 _updateBounds: function () { 9296 var r = this._radius, 9297 r2 = this._radiusY || r, 9298 w = this._clickTolerance(), 9299 p = [r + w, r2 + w]; 9300 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p)); 9301 }, 9302 9303 _update: function () { 9304 if (this._map) { 9305 this._updatePath(); 9306 } 9307 }, 9308 9309 _updatePath: function () { 9310 this._renderer._updateCircle(this); 9311 }, 9312 9313 _empty: function () { 9314 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 9315 } 9316}); 9317 9318 9319// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 9320// Instantiates a circle marker object given a geographical point, and an optional options object. 9321L.circleMarker = function (latlng, options) { 9322 return new L.CircleMarker(latlng, options); 9323}; 9324 9325 9326 9327/* 9328 * @class Circle 9329 * @aka L.Circle 9330 * @inherits CircleMarker 9331 * 9332 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 9333 * 9334 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 9335 * 9336 * @example 9337 * 9338 * ```js 9339 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 9340 * ``` 9341 */ 9342 9343L.Circle = L.CircleMarker.extend({ 9344 9345 initialize: function (latlng, options, legacyOptions) { 9346 if (typeof options === 'number') { 9347 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 9348 options = L.extend({}, legacyOptions, {radius: options}); 9349 } 9350 L.setOptions(this, options); 9351 this._latlng = L.latLng(latlng); 9352 9353 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 9354 9355 // @section 9356 // @aka Circle options 9357 // @option radius: Number; Radius of the circle, in meters. 9358 this._mRadius = this.options.radius; 9359 }, 9360 9361 // @method setRadius(radius: Number): this
9362 // Sets the radius of a circle. Units are in meters. 9363 setRadius: function (radius) { 9364 this._mRadius = radius; 9365 return this.redraw(); 9366 }, 9367 9368 // @method getRadius(): Number 9369 // Returns the current radius of a circle. Units are in meters. 9370 getRadius: function () { 9371 return this._mRadius; 9372 }, 9373 9374 // @method getBounds(): LatLngBounds 9375 // Returns the `LatLngBounds` of the path. 9376 getBounds: function () { 9377 var half = [this._radius, this._radiusY || this._radius]; 9378 9379 return new L.LatLngBounds( 9380 this._map.layerPointToLatLng(this._point.subtract(half)), 9381 this._map.layerPointToLatLng(this._point.add(half))); 9382 }, 9383 9384 setStyle: L.Path.prototype.setStyle, 9385 9386 _project: function () { 9387 9388 var lng = this._latlng.lng, 9389 lat = this._latlng.lat, 9390 map = this._map, 9391 crs = map.options.crs; 9392 9393 if (crs.distance === L.CRS.Earth.distance) { 9394 var d = Math.PI / 180, 9395 latR = (this._mRadius / L.CRS.Earth.R) / d, 9396 top = map.project([lat + latR, lng]), 9397 bottom = map.project([lat - latR, lng]), 9398 p = top.add(bottom).divideBy(2), 9399 lat2 = map.unproject(p).lat, 9400 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 9401 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 9402 9403 if (isNaN(lngR) || lngR === 0) { 9404 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 9405 } 9406 9407 this._point = p.subtract(map.getPixelOrigin()); 9408 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1); 9409 this._radiusY = Math.max(Math.round(p.y - top.y), 1); 9410 9411 } else { 9412 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 9413 9414 this._point = map.latLngToLayerPoint(this._latlng); 9415 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 9416 } 9417 9418 this._updateBounds(); 9419 } 9420}); 9421 9422// @factory L.circle(latlng: LatLng, options?: Circle options) 9423// Instantiates a circle object given a geographical point, and an options object 9424// which contains the circle radius. 9425// @alternative 9426// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 9427// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 9428// Do not use in new applications or plugins. 9429L.circle = function (latlng, options, legacyOptions) { 9430 return new L.Circle(latlng, options, legacyOptions); 9431}; 9432 9433 9434 9435/* 9436 * @class SVG 9437 * @inherits Renderer 9438 * @aka L.SVG 9439 * 9440 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 9441 * Inherits `Renderer`. 9442 * 9443 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not 9444 * available in all web browsers, notably Android 2.x and 3.x. 9445 * 9446 * Although SVG is not available on IE7 and IE8, these browsers support 9447 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 9448 * (a now deprecated technology), and the SVG renderer will fall back to VML in 9449 * this case. 9450 * 9451 * @example 9452 * 9453 * Use SVG by default for all paths in the map: 9454 * 9455 * ```js 9456 * var map = L.map('map', { 9457 * renderer: L.svg() 9458 * }); 9459 * ``` 9460 * 9461 * Use a SVG renderer with extra padding for specific vector geometries: 9462 * 9463 * ```js 9464 * var map = L.map('map'); 9465 * var myRenderer = L.svg({ padding: 0.5 }); 9466 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 9467 * var circle = L.circle( center, { renderer: myRenderer } ); 9468 * ``` 9469 */ 9470 9471L.SVG = L.Renderer.extend({ 9472 9473 getEvents: function () { 9474 var events = L.Renderer.prototype.getEvents.call(this); 9475 events.zoomstart = this._onZoomStart; 9476 return events; 9477 }, 9478 9479 _initContainer: function () { 9480 this._container = L.SVG.create('svg'); 9481 9482 // makes it possible to click through svg root; we'll reset it back in individual paths 9483 this._container.setAttribute('pointer-events', 'none'); 9484 9485 this._rootGroup = L.SVG.create('g'); 9486 this._container.appendChild(this._rootGroup); 9487 }, 9488 9489 _onZoomStart: function () { 9490 // Drag-then-pinch interactions might mess up the center and zoom. 9491 // In this case, the easiest way to prevent this is re-do the renderer 9492 // bounds and padding when the zooming starts. 9493 this._update(); 9494 }, 9495 9496 _update: function () { 9497 if (this._map._animatingZoom && this._bounds) { return; } 9498 9499 L.Renderer.prototype._update.call(this); 9500 9501 var b = this._bounds, 9502 size = b.getSize(), 9503 container = this._container; 9504 9505 // set size of svg-container if changed 9506 if (!this._svgSize || !this._svgSize.equals(size)) { 9507 this._svgSize = size; 9508 container.setAttribute('width', size.x); 9509 container.setAttribute('height', size.y); 9510 } 9511 9512 // movement: update container viewBox so that we don't have to change coordinates of individual layers 9513 L.DomUtil.setPosition(container, b.min); 9514 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 9515 9516 this.fire('update'); 9517 }, 9518 9519 // methods below are called by vector layers implementations 9520 9521 _initPath: function (layer) { 9522 var path = layer._path = L.SVG.create('path'); 9523 9524 // @namespace Path 9525 // @option className: String = null 9526 // Custom class name set on an element. Only for SVG renderer. 9527 if (layer.options.className) { 9528 L.DomUtil.addClass(path, layer.options.className); 9529 } 9530 9531 if (layer.options.interactive) { 9532 L.DomUtil.addClass(path, 'leaflet-interactive'); 9533 } 9534 9535 this._updateStyle(layer); 9536 this._layers[L.stamp(layer)] = layer; 9537 }, 9538 9539 _addPath: function (layer) { 9540 this._rootGroup.appendChild(layer._path); 9541 layer.addInteractiveTarget(layer._path); 9542 }, 9543 9544 _removePath: function (layer) { 9545 L.DomUtil.remove(layer._path); 9546 layer.removeInteractiveTarget(layer._path); 9547 delete this._layers[L.stamp(layer)]; 9548 }, 9549 9550 _updatePath: function (layer) { 9551 layer._project(); 9552 layer._update(); 9553 }, 9554 9555 _updateStyle: function (layer) { 9556 var path = layer._path, 9557 options = layer.options; 9558 9559 if (!path) { return; } 9560 9561 if (options.stroke) { 9562 path.setAttribute('stroke', options.color); 9563 path.setAttribute('stroke-opacity', options.opacity); 9564 path.setAttribute('stroke-width', options.weight); 9565 path.setAttribute('stroke-linecap', options.lineCap); 9566 path.setAttribute('stroke-linejoin', options.lineJoin); 9567 9568 if (options.dashArray) { 9569 path.setAttribute('stroke-dasharray', options.dashArray); 9570 } else { 9571 path.removeAttribute('stroke-dasharray'); 9572 } 9573 9574 if (options.dashOffset) { 9575 path.setAttribute('stroke-dashoffset', options.dashOffset); 9576 } else { 9577 path.removeAttribute('stroke-dashoffset'); 9578 } 9579 } else { 9580 path.setAttribute('stroke', 'none'); 9581 } 9582 9583 if (options.fill) { 9584 path.setAttribute('fill', options.fillColor || options.color); 9585 path.setAttribute('fill-opacity', options.fillOpacity); 9586 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 9587 } else { 9588 path.setAttribute('fill', 'none'); 9589 } 9590 }, 9591 9592 _updatePoly: function (layer, closed) { 9593 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed)); 9594 }, 9595 9596 _updateCircle: function (layer) { 9597 var p = layer._point, 9598 r = layer._radius, 9599 r2 = layer._radiusY || r, 9600 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 9601 9602 // drawing a circle with two half-arcs 9603 var d = layer._empty() ? 'M0 0' : 9604 'M' + (p.x - r) + ',' + p.y + 9605 arc + (r * 2) + ',0 ' + 9606 arc + (-r * 2) + ',0 '; 9607 9608 this._setPath(layer, d); 9609 }, 9610 9611 _setPath: function (layer, path) { 9612 layer._path.setAttribute('d', path); 9613 }, 9614 9615 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 9616 _bringToFront: function (layer) { 9617 L.DomUtil.toFront(layer._path); 9618 }, 9619 9620 _bringToBack: function (layer) { 9621 L.DomUtil.toBack(layer._path); 9622 } 9623}); 9624 9625 9626// @namespace SVG; @section 9627// There are several static functions which can be called without instantiating L.SVG: 9628L.extend(L.SVG, { 9629 // @function create(name: String): SVGElement 9630 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 9631 // corresponding to the class name passed. For example, using 'line' will return 9632 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 9633 create: function (name) { 9634 return document.createElementNS('http://www.w3.org/2000/svg', name); 9635 }, 9636 9637 // @function pointsToPath(rings: Point[], closed: Boolean): String 9638 // Generates a SVG path string for multiple rings, with each ring turning 9639 // into "M..L..L.." instructions 9640 pointsToPath: function (rings, closed) { 9641 var str = '', 9642 i, j, len, len2, points, p; 9643 9644 for (i = 0, len = rings.length; i < len; i++) { 9645 points = rings[i]; 9646 9647 for (j = 0, len2 = points.length; j < len2; j++) { 9648 p = points[j]; 9649 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 9650 } 9651 9652 // closes the ring for polygons; "x" is VML syntax 9653 str += closed ? (L.Browser.svg ? 'z' : 'x') : ''; 9654 } 9655 9656 // SVG complains about empty path strings 9657 return str || 'M0 0'; 9658 } 9659}); 9660 9661// @namespace Browser; @property svg: Boolean 9662// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 9663L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect); 9664 9665 9666// @namespace SVG 9667// @factory L.svg(options?: Renderer options) 9668// Creates a SVG renderer with the given options. 9669L.svg = function (options) { 9670 return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null; 9671}; 9672 9673 9674 9675/* 9676 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 9677 */ 9678 9679/* 9680 * @class SVG 9681 * 9682 * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case. 9683 *
9684 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 9685 * with old versions of Internet Explorer. 9686 */ 9687 9688// @namespace Browser; @property vml: Boolean 9689// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 9690L.Browser.vml = !L.Browser.svg && (function () { 9691 try { 9692 var div = document.createElement('div'); 9693 div.innerHTML = '<v:shape adj="1"/>'; 9694 9695 var shape = div.firstChild; 9696 shape.style.behavior = 'url(#default#VML)'; 9697 9698 return shape && (typeof shape.adj === 'object'); 9699 9700 } catch (e) { 9701 return false; 9702 } 9703}()); 9704 9705// redefine some SVG methods to handle VML syntax which is similar but with some differences 9706L.SVG.include(!L.Browser.vml ? {} : { 9707 9708 _initContainer: function () { 9709 this._container = L.DomUtil.create('div', 'leaflet-vml-container'); 9710 }, 9711 9712 _update: function () { 9713 if (this._map._animatingZoom) { return; } 9714 L.Renderer.prototype._update.call(this); 9715 this.fire('update'); 9716 }, 9717 9718 _initPath: function (layer) { 9719 var container = layer._container = L.SVG.create('shape'); 9720 9721 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 9722 9723 container.coordsize = '1 1'; 9724 9725 layer._path = L.SVG.create('path'); 9726 container.appendChild(layer._path); 9727 9728 this._updateStyle(layer); 9729 this._layers[L.stamp(layer)] = layer; 9730 }, 9731 9732 _addPath: function (layer) { 9733 var container = layer._container; 9734 this._container.appendChild(container); 9735 9736 if (layer.options.interactive) { 9737 layer.addInteractiveTarget(container); 9738 } 9739 }, 9740 9741 _removePath: function (layer) { 9742 var container = layer._container; 9743 L.DomUtil.remove(container); 9744 layer.removeInteractiveTarget(container); 9745 delete this._layers[L.stamp(layer)]; 9746 }, 9747 9748 _updateStyle: function (layer) { 9749 var stroke = layer._stroke, 9750 fill = layer._fill, 9751 options = layer.options, 9752 container = layer._container; 9753 9754 container.stroked = !!options.stroke; 9755 container.filled = !!options.fill; 9756 9757 if (options.stroke) { 9758 if (!stroke) { 9759 stroke = layer._stroke = L.SVG.create('stroke'); 9760 } 9761 container.appendChild(stroke); 9762 stroke.weight = options.weight + 'px'; 9763 stroke.color = options.color; 9764 stroke.opacity = options.opacity; 9765 9766 if (options.dashArray) { 9767 stroke.dashStyle = L.Util.isArray(options.dashArray) ? 9768 options.dashArray.join(' ') : 9769 options.dashArray.replace(/( *, *)/g, ' '); 9770 } else { 9771 stroke.dashStyle = ''; 9772 } 9773 stroke.endcap = options.lineCap.replace('butt', 'flat'); 9774 stroke.joinstyle = options.lineJoin; 9775 9776 } else if (stroke) { 9777 container.removeChild(stroke); 9778 layer._stroke = null; 9779 } 9780 9781 if (options.fill) { 9782 if (!fill) { 9783 fill = layer._fill = L.SVG.create('fill'); 9784 } 9785 container.appendChild(fill); 9786 fill.color = options.fillColor || options.color; 9787 fill.opacity = options.fillOpacity; 9788 9789 } else if (fill) { 9790 container.removeChild(fill); 9791 layer._fill = null; 9792 } 9793 }, 9794 9795 _updateCircle: function (layer) { 9796 var p = layer._point.round(), 9797 r = Math.round(layer._radius), 9798 r2 = Math.round(layer._radiusY || r); 9799 9800 this._setPath(layer, layer._empty() ? 'M0 0' : 9801 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 9802 }, 9803 9804 _setPath: function (layer, path) { 9805 layer._path.v = path; 9806 }, 9807 9808 _bringToFront: function (layer) { 9809 L.DomUtil.toFront(layer._container); 9810 }, 9811 9812 _bringToBack: function (layer) { 9813 L.DomUtil.toBack(layer._container); 9814 } 9815}); 9816 9817if (L.Browser.vml) { 9818 L.SVG.create = (function () { 9819 try { 9820 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 9821 return function (name) { 9822 return document.createElement('<lvml:' + name + ' class="lvml">'); 9823 }; 9824 } catch (e) { 9825 return function (name) { 9826 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 9827 }; 9828 } 9829 })(); 9830} 9831 9832 9833 9834/* 9835 * @class Canvas 9836 * @inherits Renderer 9837 * @aka L.Canvas 9838 * 9839 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 9840 * Inherits `Renderer`. 9841 * 9842 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not 9843 * available in all web browsers, notably IE8, and overlapping geometries might 9844 * not display properly in some edge cases. 9845 * 9846 * @example 9847 * 9848 * Use Canvas by default for all paths in the map: 9849 * 9850 * ```js 9851 * var map = L.map('map', { 9852 * renderer: L.canvas() 9853 * }); 9854 * ``` 9855 * 9856 * Use a Canvas renderer with extra padding for specific vector geometries: 9857 * 9858 * ```js 9859 * var map = L.map('map'); 9860 * var myRenderer = L.canvas({ padding: 0.5 }); 9861 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 9862 * var circle = L.circle( center, { renderer: myRenderer } ); 9863 * ``` 9864 */ 9865 9866L.Canvas = L.Renderer.extend({ 9867 getEvents: function () { 9868 var events = L.Renderer.prototype.getEvents.call(this); 9869 events.viewprereset = this._onViewPreReset; 9870 return events; 9871 }, 9872 9873 _onViewPreReset: function () { 9874 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 9875 this._postponeUpdatePaths = true; 9876 }, 9877 9878 onAdd: function () { 9879 L.Renderer.prototype.onAdd.call(this); 9880 9881 // Redraw vectors since canvas is cleared upon removal, 9882 // in case of removing the renderer itself from the map. 9883 this._draw(); 9884 }, 9885 9886 _initContainer: function () { 9887 var container = this._container = document.createElement('canvas'); 9888 9889 L.DomEvent 9890 .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this) 9891 .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this) 9892 .on(container, 'mouseout', this._handleMouseOut, this); 9893 9894 this._ctx = container.getContext('2d'); 9895 }, 9896 9897 _updatePaths: function () { 9898 if (this._postponeUpdatePaths) { return; } 9899 9900 var layer; 9901 this._redrawBounds = null; 9902 for (var id in this._layers) { 9903 layer = this._layers[id]; 9904 layer._update(); 9905 } 9906 this._redraw(); 9907 }, 9908 9909 _update: function () { 9910 if (this._map._animatingZoom && this._bounds) { return; } 9911 9912 this._drawnLayers = {}; 9913 9914 L.Renderer.prototype._update.call(this); 9915 9916 var b = this._bounds, 9917 container = this._container, 9918 size = b.getSize(), 9919 m = L.Browser.retina ? 2 : 1; 9920 9921 L.DomUtil.setPosition(container, b.min); 9922 9923 // set canvas size (also clearing it); use double size on retina 9924 container.width = m * size.x; 9925 container.height = m * size.y; 9926 container.style.width = size.x + 'px'; 9927 container.style.height = size.y + 'px'; 9928 9929 if (L.Browser.retina) { 9930 this._ctx.scale(2, 2); 9931 } 9932 9933 // translate so we use the same path coordinates after canvas element moves 9934 this._ctx.translate(-b.min.x, -b.min.y); 9935 9936 // Tell paths to redraw themselves 9937 this.fire('update'); 9938 }, 9939 9940 _reset: function () { 9941 L.Renderer.prototype._reset.call(this); 9942 9943 if (this._postponeUpdatePaths) { 9944 this._postponeUpdatePaths = false;
vendor: 4,215 bytes, lines 9945-10132
9945 this._updatePaths(); 9946 } 9947 }, 9948 9949 _initPath: function (layer) { 9950 this._updateDashArray(layer); 9951 this._layers[L.stamp(layer)] = layer; 9952 9953 var order = layer._order = { 9954 layer: layer, 9955 prev: this._drawLast, 9956 next: null 9957 }; 9958 if (this._drawLast) { this._drawLast.next = order; } 9959 this._drawLast = order; 9960 this._drawFirst = this._drawFirst || this._drawLast; 9961 }, 9962 9963 _addPath: function (layer) { 9964 this._requestRedraw(layer); 9965 }, 9966 9967 _removePath: function (layer) { 9968 var order = layer._order; 9969 var next = order.next; 9970 var prev = order.prev; 9971 9972 if (next) { 9973 next.prev = prev; 9974 } else { 9975 this._drawLast = prev; 9976 } 9977 if (prev) { 9978 prev.next = next; 9979 } else { 9980 this._drawFirst = next; 9981 } 9982 9983 delete layer._order; 9984 9985 delete this._layers[L.stamp(layer)]; 9986 9987 this._requestRedraw(layer); 9988 }, 9989 9990 _updatePath: function (layer) { 9991 // Redraw the union of the layer's old pixel 9992 // bounds and the new pixel bounds. 9993 this._extendRedrawBounds(layer); 9994 layer._project(); 9995 layer._update(); 9996 // The redraw will extend the redraw bounds 9997 // with the new pixel bounds. 9998 this._requestRedraw(layer); 9999 }, 10000 10001 _updateStyle: function (layer) { 10002 this._updateDashArray(layer); 10003 this._requestRedraw(layer); 10004 }, 10005 10006 _updateDashArray: function (layer) { 10007 if (layer.options.dashArray) { 10008 var parts = layer.options.dashArray.split(','), 10009 dashArray = [], 10010 i; 10011 for (i = 0; i < parts.length; i++) { 10012 dashArray.push(Number(parts[i])); 10013 } 10014 layer.options._dashArray = dashArray; 10015 } 10016 }, 10017 10018 _requestRedraw: function (layer) { 10019 if (!this._map) { return; } 10020 10021 this._extendRedrawBounds(layer); 10022 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this); 10023 }, 10024 10025 _extendRedrawBounds: function (layer) { 10026 var padding = (layer.options.weight || 0) + 1; 10027 this._redrawBounds = this._redrawBounds || new L.Bounds(); 10028 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 10029 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 10030 }, 10031 10032 _redraw: function () { 10033 this._redrawRequest = null; 10034 10035 if (this._redrawBounds) { 10036 this._redrawBounds.min._floor(); 10037 this._redrawBounds.max._ceil(); 10038 } 10039 10040 this._clear(); // clear layers in redraw bounds 10041 this._draw(); // draw layers 10042 10043 this._redrawBounds = null; 10044 }, 10045 10046 _clear: function () { 10047 var bounds = this._redrawBounds; 10048 if (bounds) { 10049 var size = bounds.getSize(); 10050 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 10051 } else { 10052 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 10053 } 10054 }, 10055 10056 _draw: function () { 10057 var layer, bounds = this._redrawBounds; 10058 this._ctx.save(); 10059 if (bounds) { 10060 var size = bounds.getSize(); 10061 this._ctx.beginPath(); 10062 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 10063 this._ctx.clip(); 10064 } 10065 10066 this._drawing = true; 10067 10068 for (var order = this._drawFirst; order; order = order.next) { 10069 layer = order.layer; 10070 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 10071 layer._updatePath(); 10072 } 10073 } 10074 10075 this._drawing = false; 10076 10077 this._ctx.restore(); // Restore state before clipping. 10078 }, 10079 10080 _updatePoly: function (layer, closed) { 10081 if (!this._drawing) { return; } 10082 10083 var i, j, len2, p, 10084 parts = layer._parts, 10085 len = parts.length, 10086 ctx = this._ctx; 10087 10088 if (!len) { return; } 10089 10090 this._drawnLayers[layer._leaflet_id] = layer; 10091 10092 ctx.beginPath(); 10093 10094 if (ctx.setLineDash) { 10095 ctx.setLineDash(layer.options && layer.options._dashArray || []); 10096 } 10097 10098 for (i = 0; i < len; i++) { 10099 for (j = 0, len2 = parts[i].length; j < len2; j++) { 10100 p = parts[i][j]; 10101 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 10102 } 10103 if (closed) { 10104 ctx.closePath(); 10105 } 10106 } 10107 10108 this._fillStroke(ctx, layer); 10109 10110 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 10111 }, 10112 10113 _updateCircle: function (layer) { 10114 10115 if (!this._drawing || layer._empty()) { return; } 10116 10117 var p = layer._point, 10118 ctx = this._ctx, 10119 r = layer._radius, 10120 s = (layer._radiusY || r) / r; 10121 10122 this._drawnLayers[layer._leaflet_id] = layer; 10123 10124 if (s !== 1) { 10125 ctx.save(); 10126 ctx.scale(1, s); 10127 } 10128 10129 ctx.beginPath(); 10130 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 10131 10132 if (s !== 1) {
10133 ctx.restore(); 10134 } 10135 10136 this._fillStroke(ctx, layer); 10137 }, 10138 10139 _fillStroke: function (ctx, layer) { 10140 var options = layer.options; 10141 10142 if (options.fill) { 10143 ctx.globalAlpha = options.fillOpacity; 10144 ctx.fillStyle = options.fillColor || options.color; 10145 ctx.fill(options.fillRule || 'evenodd'); 10146 } 10147 10148 if (options.stroke && options.weight !== 0) { 10149 ctx.globalAlpha = options.opacity; 10150 ctx.lineWidth = options.weight; 10151 ctx.strokeStyle = options.color; 10152 ctx.lineCap = options.lineCap; 10153 ctx.lineJoin = options.lineJoin; 10154 ctx.stroke(); 10155 } 10156 }, 10157 10158 // Canvas obviously doesn't have mouse events for individual drawn objects, 10159 // so we emulate that by calculating what's under the mouse on mousemove/click manually 10160 10161 _onClick: function (e) { 10162 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 10163 10164 for (var order = this._drawFirst; order; order = order.next) { 10165 layer = order.layer; 10166 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) { 10167 clickedLayer = layer; 10168 } 10169 } 10170 if (clickedLayer) { 10171 L.DomEvent._fakeStop(e); 10172 this._fireEvent([clickedLayer], e); 10173 } 10174 }, 10175 10176 _onMouseMove: function (e) { 10177 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 10178 10179 var point = this._map.mouseEventToLayerPoint(e); 10180 this._handleMouseHover(e, point); 10181 }, 10182 10183 10184 _handleMouseOut: function (e) { 10185 var layer = this._hoveredLayer; 10186 if (layer) { 10187 // if we're leaving the layer, fire mouseout 10188 L.DomUtil.removeClass(this._container, 'leaflet-interactive'); 10189 this._fireEvent([layer], e, 'mouseout'); 10190 this._hoveredLayer = null; 10191 } 10192 }, 10193 10194 _handleMouseHover: function (e, point) { 10195 var layer, candidateHoveredLayer; 10196 10197 for (var order = this._drawFirst; order; order = order.next) { 10198 layer = order.layer; 10199 if (layer.options.interactive && layer._containsPoint(point)) { 10200 candidateHoveredLayer = layer; 10201 } 10202 } 10203 10204 if (candidateHoveredLayer !== this._hoveredLayer) { 10205 this._handleMouseOut(e); 10206 10207 if (candidateHoveredLayer) { 10208 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor 10209 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 10210 this._hoveredLayer = candidateHoveredLayer; 10211 } 10212 } 10213 10214 if (this._hoveredLayer) { 10215 this._fireEvent([this._hoveredLayer], e); 10216 } 10217 }, 10218 10219 _fireEvent: function (layers, e, type) { 10220 this._map._fireDOMEvent(e, type || e.type, layers); 10221 }, 10222 10223 _bringToFront: function (layer) { 10224 var order = layer._order; 10225 var next = order.next; 10226 var prev = order.prev; 10227 10228 if (next) { 10229 next.prev = prev; 10230 } else { 10231 // Already last 10232 return; 10233 } 10234 if (prev) { 10235 prev.next = next; 10236 } else if (next) { 10237 // Update first entry unless this is the 10238 // signle entry 10239 this._drawFirst = next; 10240 } 10241 10242 order.prev = this._drawLast; 10243 this._drawLast.next = order; 10244 10245 order.next = null; 10246 this._drawLast = order; 10247 10248 this._requestRedraw(layer); 10249 }, 10250 10251 _bringToBack: function (layer) { 10252 var order = layer._order; 10253 var next = order.next; 10254 var prev = order.prev; 10255 10256 if (prev) { 10257 prev.next = next; 10258 } else { 10259 // Already first 10260 return; 10261 } 10262 if (next) { 10263 next.prev = prev; 10264 } else if (prev) { 10265 // Update last entry unless this is the 10266 // signle entry 10267 this._drawLast = prev; 10268 } 10269 10270 order.prev = null; 10271 10272 order.next = this._drawFirst; 10273 this._drawFirst.prev = order; 10274 this._drawFirst = order; 10275 10276 this._requestRedraw(layer); 10277 } 10278}); 10279 10280// @namespace Browser; @property canvas: Boolean 10281// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 10282L.Browser.canvas = (function () { 10283 return !!document.createElement('canvas').getContext; 10284}()); 10285 10286// @namespace Canvas 10287// @factory L.canvas(options?: Renderer options) 10288// Creates a Canvas renderer with the given options. 10289L.canvas = function (options) { 10290 return L.Browser.canvas ? new L.Canvas(options) : null; 10291}; 10292 10293L.Polyline.prototype._containsPoint = function (p, closed) { 10294 var i, j, k, len, len2, part, 10295 w = this._clickTolerance(); 10296 10297 if (!this._pxBounds.contains(p)) { return false; } 10298 10299 // hit detection for polylines 10300 for (i = 0, len = this._parts.length; i < len; i++) { 10301 part = this._parts[i]; 10302 10303 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 10304 if (!closed && (j === 0)) { continue; } 10305 10306 if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) { 10307 return true; 10308 } 10309 } 10310 } 10311 return false;
vendor: 4,146 bytes, lines 10312-10461
10312}; 10313 10314L.Polygon.prototype._containsPoint = function (p) { 10315 var inside = false, 10316 part, p1, p2, i, j, k, len, len2; 10317 10318 if (!this._pxBounds.contains(p)) { return false; } 10319 10320 // ray casting algorithm for detecting if point is in polygon 10321 for (i = 0, len = this._parts.length; i < len; i++) { 10322 part = this._parts[i]; 10323 10324 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 10325 p1 = part[j]; 10326 p2 = part[k]; 10327 10328 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)) { 10329 inside = !inside; 10330 } 10331 } 10332 } 10333 10334 // also check if it's on polygon stroke 10335 return inside || L.Polyline.prototype._containsPoint.call(this, p, true); 10336}; 10337 10338L.CircleMarker.prototype._containsPoint = function (p) { 10339 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 10340}; 10341 10342 10343 10344/* 10345 * @class GeoJSON 10346 * @aka L.GeoJSON 10347 * @inherits FeatureGroup 10348 * 10349 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 10350 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 10351 * 10352 * @example 10353 * 10354 * ```js 10355 * L.geoJSON(data, { 10356 * style: function (feature) { 10357 * return {color: feature.properties.color}; 10358 * } 10359 * }).bindPopup(function (layer) { 10360 * return layer.feature.properties.description; 10361 * }).addTo(map); 10362 * ``` 10363 */ 10364 10365L.GeoJSON = L.FeatureGroup.extend({ 10366 10367 /* @section 10368 * @aka GeoJSON options 10369 * 10370 * @option pointToLayer: Function = * 10371 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 10372 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 10373 * The default is to spawn a default `Marker`: 10374 * ```js 10375 * function(geoJsonPoint, latlng) { 10376 * return L.marker(latlng); 10377 * } 10378 * ``` 10379 * 10380 * @option style: Function = * 10381 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, 10382 * called internally when data is added. 10383 * The default value is to not override any defaults: 10384 * ```js 10385 * function (geoJsonFeature) { 10386 * return {} 10387 * } 10388 * ``` 10389 * 10390 * @option onEachFeature: Function = * 10391 * A `Function` that will be called once for each created `Feature`, after it has 10392 * been created and styled. Useful for attaching events and popups to features. 10393 * The default is to do nothing with the newly created layers: 10394 * ```js 10395 * function (feature, layer) {} 10396 * ``` 10397 * 10398 * @option filter: Function = * 10399 * A `Function` that will be used to decide whether to include a feature or not. 10400 * The default is to include all features: 10401 * ```js 10402 * function (geoJsonFeature) { 10403 * return true; 10404 * } 10405 * ``` 10406 * Note: dynamically changing the `filter` option will have effect only on newly 10407 * added data. It will _not_ re-evaluate already included features. 10408 * 10409 * @option coordsToLatLng: Function = * 10410 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 10411 * The default is the `coordsToLatLng` static method. 10412 */ 10413 10414 initialize: function (geojson, options) { 10415 L.setOptions(this, options); 10416 10417 this._layers = {}; 10418 10419 if (geojson) { 10420 this.addData(geojson); 10421 } 10422 }, 10423 10424 // @method addData( <GeoJSON> data ): this 10425 // Adds a GeoJSON object to the layer. 10426 addData: function (geojson) { 10427 var features = L.Util.isArray(geojson) ? geojson : geojson.features, 10428 i, len, feature; 10429 10430 if (features) { 10431 for (i = 0, len = features.length; i < len; i++) { 10432 // only add this if geometry or geometries are set and not null 10433 feature = features[i]; 10434 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 10435 this.addData(feature); 10436 } 10437 } 10438 return this; 10439 } 10440 10441 var options = this.options; 10442 10443 if (options.filter && !options.filter(geojson)) { return this; } 10444 10445 var layer = L.GeoJSON.geometryToLayer(geojson, options); 10446 if (!layer) { 10447 return this; 10448 } 10449 layer.feature = L.GeoJSON.asFeature(geojson); 10450 10451 layer.defaultOptions = layer.options; 10452 this.resetStyle(layer); 10453 10454 if (options.onEachFeature) { 10455 options.onEachFeature(geojson, layer); 10456 } 10457 10458 return this.addLayer(layer); 10459 }, 10460 10461 // @method resetStyle( <Path>
10461 layer ): this 10462 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 10463 resetStyle: function (layer) { 10464 // reset any custom styles 10465 layer.options = L.Util.extend({}, layer.defaultOptions); 10466 this._setLayerStyle(layer, this.options.style); 10467 return this; 10468 }, 10469 10470 // @method setStyle( <Function> style ): this 10471 // Changes styles of GeoJSON vector layers with the given style function. 10472 setStyle: function (style) { 10473 return this.eachLayer(function (layer) { 10474 this._setLayerStyle(layer, style); 10475 }, this); 10476 }, 10477 10478 _setLayerStyle: function (layer, style) { 10479 if (typeof style === 'function') { 10480 style = style(layer.feature); 10481 } 10482 if (layer.setStyle) { 10483 layer.setStyle(style); 10484 } 10485 } 10486}); 10487 10488// @section 10489// There are several static functions which can be called without instantiating L.GeoJSON: 10490L.extend(L.GeoJSON, { 10491 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 10492 // Creates a `Layer` from a given GeoJSON feature. Can use a custom 10493 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 10494 // functions if provided as options. 10495 geometryToLayer: function (geojson, options) { 10496 10497 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 10498 coords = geometry ? geometry.coordinates : null, 10499 layers = [], 10500 pointToLayer = options && options.pointToLayer, 10501 coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng, 10502 latlng, latlngs, i, len; 10503 10504 if (!coords && !geometry) { 10505 return null; 10506 } 10507 10508 switch (geometry.type) { 10509 case 'Point': 10510 latlng = coordsToLatLng(coords); 10511 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); 10512 10513 case 'MultiPoint': 10514 for (i = 0, len = coords.length; i < len; i++) { 10515 latlng = coordsToLatLng(coords[i]); 10516 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng)); 10517 } 10518 return new L.FeatureGroup(layers); 10519 10520 case 'LineString': 10521 case 'MultiLineString': 10522 latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng); 10523 return new L.Polyline(latlngs, options); 10524 10525 case 'Polygon': 10526 case 'MultiPolygon': 10527 latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng); 10528 return new L.Polygon(latlngs, options); 10529 10530 case 'GeometryCollection': 10531 for (i = 0, len = geometry.geometries.length; i < len; i++) { 10532 var layer = this.geometryToLayer({ 10533 geometry: geometry.geometries[i], 10534 type: 'Feature', 10535 properties: geojson.properties 10536 }, options); 10537 10538 if (layer) { 10539 layers.push(layer); 10540 } 10541 } 10542 return new L.FeatureGroup(layers); 10543 10544 default: 10545 throw new Error('Invalid GeoJSON object.'); 10546 } 10547 }, 10548 10549 // @function coordsToLatLng(coords: Array): LatLng 10550 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 10551 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 10552 coordsToLatLng: function (coords) { 10553 return new L.LatLng(coords[1], coords[0], coords[2]); 10554 }, 10555 10556 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 10557 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 10558 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 10559 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 10560 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { 10561 var latlngs = []; 10562 10563 for (var i = 0, len = coords.length, latlng; i < len; i++) { 10564 latlng = levelsDeep ? 10565 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) : 10566 (coordsToLatLng || this.coordsToLatLng)(coords[i]); 10567 10568 latlngs.push(latlng); 10569 } 10570 10571 return latlngs; 10572 }, 10573 10574 // @function latLngToCoords(latlng: LatLng): Array 10575 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 10576 latLngToCoords: function (latlng) { 10577 return latlng.alt !== undefined ? 10578 [latlng.lng, latlng.lat, latlng.alt] : 10579 [latlng.lng, latlng.lat]; 10580 }, 10581 10582 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number,
10582closed?: Boolean): Array 10583 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 10584 // `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. 10585 latLngsToCoords: function (latlngs, levelsDeep, closed) { 10586 var coords = []; 10587 10588 for (var i = 0, len = latlngs.length; i < len; i++) { 10589 coords.push(levelsDeep ? 10590 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) : 10591 L.GeoJSON.latLngToCoords(latlngs[i])); 10592 } 10593 10594 if (!levelsDeep && closed) { 10595 coords.push(coords[0]); 10596 } 10597 10598 return coords; 10599 }, 10600 10601 getFeature: function (layer, newGeometry) { 10602 return layer.feature ? 10603 L.extend({}, layer.feature, {geometry: newGeometry}) : 10604 L.GeoJSON.asFeature(newGeometry); 10605 }, 10606 10607 // @function asFeature(geojson: Object): Object 10608 // Normalize GeoJSON geometries/features into GeoJSON features. 10609 asFeature: function (geojson) { 10610 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 10611 return geojson; 10612 } 10613 10614 return { 10615 type: 'Feature', 10616 properties: {}, 10617 geometry: geojson 10618 }; 10619 } 10620}); 10621 10622var PointToGeoJSON = { 10623 toGeoJSON: function () { 10624 return L.GeoJSON.getFeature(this, { 10625 type: 'Point', 10626 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) 10627 }); 10628 } 10629}; 10630 10631// @namespace Marker 10632// @method toGeoJSON(): Object 10633// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 10634L.Marker.include(PointToGeoJSON); 10635 10636// @namespace CircleMarker 10637// @method toGeoJSON(): Object 10638// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 10639L.Circle.include(PointToGeoJSON); 10640L.CircleMarker.include(PointToGeoJSON); 10641 10642 10643// @namespace Polyline 10644// @method toGeoJSON(): Object 10645// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 10646L.Polyline.prototype.toGeoJSON = function () { 10647 var multi = !L.Polyline._flat(this._latlngs); 10648 10649 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0); 10650 10651 return L.GeoJSON.getFeature(this, { 10652 type: (multi ? 'Multi' : '') + 'LineString', 10653 coordinates: coords 10654 }); 10655}; 10656 10657// @namespace Polygon 10658// @method toGeoJSON(): Object 10659// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 10660L.Polygon.prototype.toGeoJSON = function () { 10661 var holes = !L.Polyline._flat(this._latlngs), 10662 multi = holes && !L.Polyline._flat(this._latlngs[0]); 10663 10664 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true); 10665 10666 if (!holes) { 10667 coords = [coords]; 10668 } 10669 10670 return L.GeoJSON.getFeature(this, { 10671 type: (multi ? 'Multi' : '') + 'Polygon', 10672 coordinates: coords 10673 }); 10674}; 10675 10676 10677// @namespace LayerGroup 10678L.LayerGroup.include({ 10679 toMultiPoint: function () { 10680 var coords = []; 10681 10682 this.eachLayer(function (layer) { 10683 coords.push(layer.toGeoJSON().geometry.coordinates); 10684 }); 10685 10686 return L.GeoJSON.getFeature(this, { 10687 type: 'MultiPoint', 10688 coordinates: coords 10689 }); 10690 }, 10691 10692 // @method toGeoJSON(): Object 10693 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`). 10694 toGeoJSON: function () { 10695 10696 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 10697 10698 if (type === 'MultiPoint') { 10699 return this.toMultiPoint(); 10700 } 10701 10702 var isGeometryCollection = type === 'GeometryCollection', 10703 jsons = []; 10704 10705 this.eachLayer(function (layer) { 10706 if (layer.toGeoJSON) { 10707 var json = layer.toGeoJSON(); 10708 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); 10709 } 10710 }); 10711 10712 if (isGeometryCollection) { 10713 return L.GeoJSON.getFeature(this, { 10714 geometries: jsons, 10715 type: 'GeometryCollection' 10716 }); 10717 } 10718 10719 return { 10720 type: 'FeatureCollection', 10721 features: jsons 10722 }; 10723 } 10724}); 10725 10726// @namespace GeoJSON 10727// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 10728// Creates a GeoJSON layer. Optionally accepts an object in 10729// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map 10730// (you can alternatively add it later with `addData` method) and an `options` object. 10731L.geoJSON = function (geojson, options) { 10732 return new L.GeoJSON(geojson, options); 10733}; 10734// Backward compatibility. 10735L.geoJson = L.geoJSON; 10736 10737 10738 10739/* 10740 * @class Draggable 10741 * @aka L.Draggable 10742 * @inherits Evented 10743 * 10744 * A class for making DOM elements draggable (including touch support). 10745 * Used internally for map and marker dragging. Only works for elements 10746 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 10747 * 10748 * @example 10749 * ```js 10750 * var draggable = new L.Draggable(elementToDrag); 10751 * draggable.enable(); 10752 * ``` 10753 */ 10754 10755L.Draggable = L.Evented.extend({ 10756 10757 options: {
10758 // @option clickTolerance: Number = 3 10759 // The max number of pixels a user can shift the mouse pointer during a click 10760 // for it to be considered a valid click (as opposed to a mouse drag). 10761 clickTolerance: 3 10762 }, 10763 10764 statics: { 10765 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], 10766 END: { 10767 mousedown: 'mouseup', 10768 touchstart: 'touchend', 10769 pointerdown: 'touchend', 10770 MSPointerDown: 'touchend' 10771 }, 10772 MOVE: { 10773 mousedown: 'mousemove', 10774 touchstart: 'touchmove', 10775 pointerdown: 'touchmove', 10776 MSPointerDown: 'touchmove' 10777 } 10778 }, 10779 10780 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean) 10781 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 10782 initialize: function (element, dragStartTarget, preventOutline) { 10783 this._element = element; 10784 this._dragStartTarget = dragStartTarget || element; 10785 this._preventOutline = preventOutline; 10786 }, 10787 10788 // @method enable() 10789 // Enables the dragging ability 10790 enable: function () { 10791 if (this._enabled) { return; } 10792 10793 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); 10794 10795 this._enabled = true; 10796 }, 10797 10798 // @method disable() 10799 // Disables the dragging ability 10800 disable: function () { 10801 if (!this._enabled) { return; } 10802 10803 // If we're currently dragging this draggable, 10804 // disabling it counts as first ending the drag. 10805 if (L.Draggable._dragging === this) { 10806 this.finishDrag(); 10807 } 10808 10809 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); 10810 10811 this._enabled = false; 10812 this._moved = false; 10813 }, 10814 10815 _onDown: function (e) { 10816 // Ignore simulated events, since we handle both touch and 10817 // mouse explicitly; otherwise we risk getting duplicates of 10818 // touch events, see #4315. 10819 // Also ignore the event if disabled; this happens in IE11 10820 // under some circumstances, see #3666. 10821 if (e._simulated || !this._enabled) { return; } 10822 10823 this._moved = false; 10824 10825 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; } 10826 10827 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 10828 L.Draggable._dragging = this; // Prevent dragging multiple objects at once. 10829 10830 if (this._preventOutline) { 10831 L.DomUtil.preventOutline(this._element); 10832 } 10833 10834 L.DomUtil.disableImageDrag(); 10835 L.DomUtil.disableTextSelection(); 10836 10837 if (this._moving) { return; } 10838 10839 // @event down: Event 10840 // Fired when a drag is about to start. 10841 this.fire('down'); 10842 10843 var first = e.touches ? e.touches[0] : e; 10844 10845 this._startPoint = new L.Point(first.clientX, first.clientY); 10846 10847 L.DomEvent 10848 .on(document, L.Draggable.MOVE[e.type], this._onMove, this) 10849 .on(document, L.Draggable.END[e.type], this._onUp, this); 10850 }, 10851 10852 _onMove: function (e) { 10853 // Ignore simulated events, since we handle both touch and 10854 // mouse explicitly; otherwise we risk getting duplicates of 10855 // touch events, see #4315. 10856 // Also ignore the event if disabled; this happens in IE11 10857 // under some circumstances, see #3666. 10858 if (e._simulated || !this._enabled) { return; } 10859 10860 if (e.touches && e.touches.length > 1) { 10861 this._moved = true; 10862 return; 10863 } 10864 10865 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 10866 newPoint = new L.Point(first.clientX, first.clientY), 10867 offset = newPoint.subtract(this._startPoint); 10868 10869 if (!offset.x && !offset.y) { return; } 10870 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 10871 10872 L.DomEvent.preventDefault(e); 10873 10874 if (!this._moved) { 10875 // @event dragstart: Event 10876 // Fired when a drag starts 10877 this.fire('dragstart'); 10878 10879 this._moved = true; 10880 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); 10881 10882 L.DomUtil.addClass(document.body, 'leaflet-dragging'); 10883 10884 this._lastTarget = e.target || e.srcElement; 10885 // IE and Edge do not give the <use> element, so fetch it 10886 // if necessary 10887 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { 10888 this._lastTarget = this._lastTarget.correspondingUseElement; 10889 } 10890 L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); 10891 } 10892 10893 this._newPos = this._startPos.add(offset); 10894 this._moving = true; 10895
10896 L.Util.cancelAnimFrame(this._animRequest); 10897 this._lastEvent = e; 10898 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true); 10899 }, 10900 10901 _updatePosition: function () { 10902 var e = {originalEvent: this._lastEvent}; 10903 10904 // @event predrag: Event 10905 // Fired continuously during dragging *before* each corresponding 10906 // update of the element's position. 10907 this.fire('predrag', e); 10908 L.DomUtil.setPosition(this._element, this._newPos); 10909 10910 // @event drag: Event 10911 // Fired continuously during dragging. 10912 this.fire('drag', e); 10913 }, 10914 10915 _onUp: function (e) { 10916 // Ignore simulated events, since we handle both touch and 10917 // mouse explicitly; otherwise we risk getting duplicates of 10918 // touch events, see #4315. 10919 // Also ignore the event if disabled; this happens in IE11 10920 // under some circumstances, see #3666. 10921 if (e._simulated || !this._enabled) { return; } 10922 this.finishDrag(); 10923 }, 10924 10925 finishDrag: function () { 10926 L.DomUtil.removeClass(document.body, 'leaflet-dragging'); 10927 10928 if (this._lastTarget) { 10929 L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); 10930 this._lastTarget = null; 10931 } 10932 10933 for (var i in L.Draggable.MOVE) { 10934 L.DomEvent 10935 .off(document, L.Draggable.MOVE[i], this._onMove, this) 10936 .off(document, L.Draggable.END[i], this._onUp, this); 10937 } 10938 10939 L.DomUtil.enableImageDrag(); 10940 L.DomUtil.enableTextSelection(); 10941 10942 if (this._moved && this._moving) { 10943 // ensure drag is not fired after dragend 10944 L.Util.cancelAnimFrame(this._animRequest); 10945 10946 // @event dragend: DragEndEvent 10947 // Fired when the drag ends. 10948 this.fire('dragend', { 10949 distance: this._newPos.distanceTo(this._startPos) 10950 }); 10951 } 10952 10953 this._moving = false; 10954 L.Draggable._dragging = false; 10955 } 10956 10957}); 10958 10959 10960 10961/* 10962 L.Handler is a base class for handler classes that are used internally to inject 10963 interaction features like dragging to classes like Map and Marker. 10964*/ 10965 10966// @class Handler 10967// @aka L.Handler 10968// Abstract class for map interaction handlers 10969 10970L.Handler = L.Class.extend({ 10971 initialize: function (map) { 10972 this._map = map; 10973 }, 10974 10975 // @method enable(): this 10976 // Enables the handler 10977 enable: function () { 10978 if (this._enabled) { return this; } 10979 10980 this._enabled = true; 10981 this.addHooks(); 10982 return this; 10983 }, 10984 10985 // @method disable(): this 10986 // Disables the handler 10987 disable: function () { 10988 if (!this._enabled) { return this; } 10989 10990 this._enabled = false; 10991 this.removeHooks(); 10992 return this; 10993 }, 10994 10995 // @method enabled(): Boolean 10996 // Returns `true` if the handler is enabled 10997 enabled: function () { 10998 return !!this._enabled; 10999 } 11000 11001 // @section Extension methods 11002 // Classes inheriting from `Handler` must implement the two following methods: 11003 // @method addHooks() 11004 // Called when the handler is enabled, should add event hooks. 11005 // @method removeHooks() 11006 // Called when the handler is disabled, should remove the event hooks added previously. 11007}); 11008 11009 11010 11011/* 11012 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 11013 */ 11014 11015// @namespace Map 11016// @section Interaction Options 11017L.Map.mergeOptions({ 11018 // @option dragging: Boolean = true 11019 // Whether the map be draggable with mouse/touch or not. 11020 dragging: true, 11021 11022 // @section Panning Inertia Options 11023 // @option inertia: Boolean = * 11024 // If enabled, panning of the map will have an inertia effect where 11025 // the map builds momentum while dragging and continues moving in 11026 // the same direction for some time. Feels especially nice on touch 11027 // devices. Enabled by default unless running on old Android devices. 11028 inertia: !L.Browser.android23, 11029 11030 // @option inertiaDeceleration: Number = 3000 11031 // The rate with which the inertial movement slows down, in pixels/second². 11032 inertiaDeceleration: 3400, // px/s^2 11033 11034 // @option inertiaMaxSpeed: Number = Infinity 11035 // Max speed of the inertial movement, in pixels/second. 11036 inertiaMaxSpeed: Infinity, // px/s 11037 11038 // @option easeLinearity: Number = 0.2 11039 easeLinearity: 0.2, 11040 11041 // TODO refactor, move to CRS 11042 // @option worldCopyJump: Boolean = false 11043 // With this option enabled, the map tracks when you pan to another "copy" 11044 // of the world and seamlessly jumps to the original one so that all overlays 11045 // like markers and vector layers are still visible.
vendor: 5,208 bytes, lines 11046-11219
11046 worldCopyJump: false, 11047 11048 // @option maxBoundsViscosity: Number = 0.0 11049 // If `maxBounds` is set, this option will control how solid the bounds 11050 // are when dragging the map around. The default value of `0.0` allows the 11051 // user to drag outside the bounds at normal speed, higher values will 11052 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 11053 // solid, preventing the user from dragging outside the bounds. 11054 maxBoundsViscosity: 0.0 11055}); 11056 11057L.Map.Drag = L.Handler.extend({ 11058 addHooks: function () { 11059 if (!this._draggable) { 11060 var map = this._map; 11061 11062 this._draggable = new L.Draggable(map._mapPane, map._container); 11063 11064 this._draggable.on({ 11065 down: this._onDown, 11066 dragstart: this._onDragStart, 11067 drag: this._onDrag, 11068 dragend: this._onDragEnd 11069 }, this); 11070 11071 this._draggable.on('predrag', this._onPreDragLimit, this); 11072 if (map.options.worldCopyJump) { 11073 this._draggable.on('predrag', this._onPreDragWrap, this); 11074 map.on('zoomend', this._onZoomEnd, this); 11075 11076 map.whenReady(this._onZoomEnd, this); 11077 } 11078 } 11079 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 11080 this._draggable.enable(); 11081 this._positions = []; 11082 this._times = []; 11083 }, 11084 11085 removeHooks: function () { 11086 L.DomUtil.removeClass(this._map._container, 'leaflet-grab'); 11087 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag'); 11088 this._draggable.disable(); 11089 }, 11090 11091 moved: function () { 11092 return this._draggable && this._draggable._moved; 11093 }, 11094 11095 moving: function () { 11096 return this._draggable && this._draggable._moving; 11097 }, 11098 11099 _onDown: function () { 11100 this._map._stop(); 11101 }, 11102 11103 _onDragStart: function () { 11104 var map = this._map; 11105 11106 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 11107 var bounds = L.latLngBounds(this._map.options.maxBounds); 11108 11109 this._offsetLimit = L.bounds( 11110 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 11111 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 11112 .add(this._map.getSize())); 11113 11114 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 11115 } else { 11116 this._offsetLimit = null; 11117 } 11118 11119 map 11120 .fire('movestart') 11121 .fire('dragstart'); 11122 11123 if (map.options.inertia) { 11124 this._positions = []; 11125 this._times = []; 11126 } 11127 }, 11128 11129 _onDrag: function (e) { 11130 if (this._map.options.inertia) { 11131 var time = this._lastTime = +new Date(), 11132 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 11133 11134 this._positions.push(pos); 11135 this._times.push(time); 11136 11137 if (time - this._times[0] > 50) { 11138 this._positions.shift(); 11139 this._times.shift(); 11140 } 11141 } 11142 11143 this._map 11144 .fire('move', e) 11145 .fire('drag', e); 11146 }, 11147 11148 _onZoomEnd: function () { 11149 var pxCenter = this._map.getSize().divideBy(2), 11150 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 11151 11152 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 11153 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 11154 }, 11155 11156 _viscousLimit: function (value, threshold) { 11157 return value - (value - threshold) * this._viscosity; 11158 }, 11159 11160 _onPreDragLimit: function () { 11161 if (!this._viscosity || !this._offsetLimit) { return; } 11162 11163 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 11164 11165 var limit = this._offsetLimit; 11166 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 11167 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 11168 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 11169 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 11170 11171 this._draggable._newPos = this._draggable._startPos.add(offset); 11172 }, 11173 11174 _onPreDragWrap: function () { 11175 // TODO refactor to be able to adjust map pane position after zoom 11176 var worldWidth = this._worldWidth, 11177 halfWidth = Math.round(worldWidth / 2), 11178 dx = this._initialWorldOffset, 11179 x = this._draggable._newPos.x, 11180 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 11181 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 11182 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 11183 11184 this._draggable._absPos = this._draggable._newPos.clone(); 11185 this._draggable._newPos.x = newX; 11186 }, 11187 11188 _onDragEnd: function (e) { 11189 var map = this._map, 11190 options = map.options, 11191 11192 noInertia = !options.inertia || this._times.length < 2; 11193 11194 map.fire('dragend', e); 11195 11196 if (noInertia) { 11197 map.fire('moveend'); 11198 11199 } else { 11200 11201 var direction = this._lastPos.subtract(this._positions[0]), 11202 duration = (this._lastTime - this._times[0]) / 1000, 11203 ease = options.easeLinearity, 11204 11205 speedVector = direction.multiplyBy(ease / duration), 11206 speed = speedVector.distanceTo([0, 0]), 11207 11208 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 11209 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 11210 11211 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 11212 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 11213 11214 if (!offset.x && !offset.y) { 11215 map.fire('moveend'); 11216 11217 } else { 11218 offset = map._limitOffset(offset, map.options.maxBounds); 11219
11220 L.Util.requestAnimFrame(function () { 11221 map.panBy(offset, { 11222 duration: decelerationDuration, 11223 easeLinearity: ease, 11224 noMoveStart: true, 11225 animate: true 11226 }); 11227 }); 11228 } 11229 } 11230 } 11231}); 11232 11233// @section Handlers 11234// @property dragging: Handler 11235// Map dragging handler (by both mouse and touch). 11236L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); 11237 11238 11239 11240/* 11241 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 11242 */ 11243 11244// @namespace Map 11245// @section Interaction Options 11246 11247L.Map.mergeOptions({ 11248 // @option doubleClickZoom: Boolean|String = true 11249 // Whether the map can be zoomed in by double clicking on it and 11250 // zoomed out by double clicking while holding shift. If passed 11251 // `'center'`, double-click zoom will zoom to the center of the 11252 // view regardless of where the mouse was. 11253 doubleClickZoom: true 11254}); 11255 11256L.Map.DoubleClickZoom = L.Handler.extend({ 11257 addHooks: function () { 11258 this._map.on('dblclick', this._onDoubleClick, this); 11259 }, 11260 11261 removeHooks: function () { 11262 this._map.off('dblclick', this._onDoubleClick, this); 11263 }, 11264 11265 _onDoubleClick: function (e) { 11266 var map = this._map, 11267 oldZoom = map.getZoom(), 11268 delta = map.options.zoomDelta, 11269 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 11270 11271 if (map.options.doubleClickZoom === 'center') { 11272 map.setZoom(zoom); 11273 } else { 11274 map.setZoomAround(e.containerPoint, zoom); 11275 } 11276 } 11277}); 11278 11279// @section Handlers 11280// 11281// Map properties include interaction handlers that allow you to control 11282// interaction behavior in runtime, enabling or disabling certain features such 11283// as dragging or touch zoom (see `Handler` methods). For example: 11284// 11285// ```js 11286// map.doubleClickZoom.disable(); 11287// ``` 11288// 11289// @property doubleClickZoom: Handler 11290// Double click zoom handler. 11291L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); 11292 11293 11294 11295/* 11296 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 11297 */ 11298 11299// @namespace Map 11300// @section Interaction Options 11301L.Map.mergeOptions({ 11302 // @section Mousewheel options 11303 // @option scrollWheelZoom: Boolean|String = true 11304 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 11305 // it will zoom to the center of the view regardless of where the mouse was. 11306 scrollWheelZoom: true, 11307 11308 // @option wheelDebounceTime: Number = 40 11309 // Limits the rate at which a wheel can fire (in milliseconds). By default 11310 // user can't zoom via wheel more often than once per 40 ms. 11311 wheelDebounceTime: 40, 11312 11313 // @option wheelPxPerZoomLevel: Number = 60 11314 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 11315 // mean a change of one full zoom level. Smaller values will make wheel-zooming 11316 // faster (and vice versa). 11317 wheelPxPerZoomLevel: 60 11318}); 11319 11320L.Map.ScrollWheelZoom = L.Handler.extend({ 11321 addHooks: function () { 11322 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); 11323 11324 this._delta = 0; 11325 }, 11326 11327 removeHooks: function () { 11328 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this); 11329 }, 11330 11331 _onWheelScroll: function (e) { 11332 var delta = L.DomEvent.getWheelDelta(e); 11333 11334 var debounce = this._map.options.wheelDebounceTime; 11335 11336 this._delta += delta; 11337 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 11338 11339 if (!this._startTime) { 11340 this._startTime = +new Date(); 11341 } 11342 11343 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 11344 11345 clearTimeout(this._timer); 11346 this._timer = setTimeout(L.bind(this._performZoom, this), left); 11347 11348 L.DomEvent.stop(e); 11349 }, 11350 11351 _performZoom: function () { 11352 var map = this._map, 11353 zoom = map.getZoom(), 11354 snap = this._map.options.zoomSnap || 0; 11355 11356 map._stop(); // stop panning and fly animations if any 11357 11358 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 11359 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 11360 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 11361 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 11362 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 11363 11364 this._delta = 0; 11365 this._startTime = null; 11366 11367 if (!delta) { return; } 11368 11369 if (map.options.scrollWheelZoom === 'center') { 11370 map.setZoom(zoom + delta); 11371 } else { 11372 map.setZoomAround(this._lastMousePos, zoom + delta); 11373 } 11374 } 11375}); 11376 11377// @section Handlers 11378// @property scrollWheelZoom: Handler 11379// Scroll wheel zoom handler. 11380L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); 11381 11382 11383 11384/* 11385 * Extends the event handling code with double tap support for mobile browsers. 11386 */ 11387 11388L.extend(L.DomEvent, { 11389 11390 _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', 11391 _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', 11392 11393 // inspired by Zepto touch code by Thomas Fuchs 11394 addDoubleTapListener: function (obj, handler, id) { 11395 var last, touch, 11396 doubleTap = false,
11397 delay = 250; 11398 11399 function onTouchStart(e) { 11400 var count; 11401 11402 if (L.Browser.pointer) { 11403 if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; } 11404 count = L.DomEvent._pointersCount; 11405 } else { 11406 count = e.touches.length; 11407 } 11408 11409 if (count > 1) { return; } 11410 11411 var now = Date.now(), 11412 delta = now - (last || now); 11413 11414 touch = e.touches ? e.touches[0] : e; 11415 doubleTap = (delta > 0 && delta <= delay); 11416 last = now; 11417 } 11418 11419 function onTouchEnd(e) { 11420 if (doubleTap && !touch.cancelBubble) { 11421 if (L.Browser.pointer) { 11422 if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; } 11423 11424 // work around .type being readonly with MSPointer* events 11425 var newTouch = {}, 11426 prop, i; 11427 11428 for (i in touch) { 11429 prop = touch[i]; 11430 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop; 11431 } 11432 touch = newTouch; 11433 } 11434 touch.type = 'dblclick'; 11435 handler(touch); 11436 last = null; 11437 } 11438 } 11439 11440 var pre = '_leaflet_', 11441 touchstart = this._touchstart, 11442 touchend = this._touchend; 11443 11444 obj[pre + touchstart + id] = onTouchStart; 11445 obj[pre + touchend + id] = onTouchEnd; 11446 obj[pre + 'dblclick' + id] = handler; 11447 11448 obj.addEventListener(touchstart, onTouchStart, false); 11449 obj.addEventListener(touchend, onTouchEnd, false); 11450 11451 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), 11452 // the browser doesn't fire touchend/pointerup events but does fire 11453 // native dblclicks. See #4127. 11454 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. 11455 obj.addEventListener('dblclick', handler, false); 11456 11457 return this; 11458 }, 11459 11460 removeDoubleTapListener: function (obj, id) { 11461 var pre = '_leaflet_', 11462 touchstart = obj[pre + this._touchstart + id], 11463 touchend = obj[pre + this._touchend + id], 11464 dblclick = obj[pre + 'dblclick' + id]; 11465 11466 obj.removeEventListener(this._touchstart, touchstart, false); 11467 obj.removeEventListener(this._touchend, touchend, false); 11468 if (!L.Browser.edge) { 11469 obj.removeEventListener('dblclick', dblclick, false); 11470 } 11471 11472 return this; 11473 } 11474}); 11475 11476 11477 11478/* 11479 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 11480 */ 11481 11482L.extend(L.DomEvent, { 11483 11484 POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown', 11485 POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove', 11486 POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup', 11487 POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', 11488 TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'], 11489 11490 _pointers: {}, 11491 _pointersCount: 0, 11492 11493 // Provides a touch events wrapper for (ms)pointer events. 11494 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 11495 11496 addPointerListener: function (obj, type, handler, id) { 11497 11498 if (type === 'touchstart') { 11499 this._addPointerStart(obj, handler, id); 11500 11501 } else if (type === 'touchmove') { 11502 this._addPointerMove(obj, handler, id); 11503 11504 } else if (type === 'touchend') { 11505 this._addPointerEnd(obj, handler, id); 11506 } 11507 11508 return this; 11509 }, 11510 11511 removePointerListener: function (obj, type, id) { 11512 var handler = obj['_leaflet_' + type + id]; 11513 11514 if (type === 'touchstart') { 11515 obj.removeEventListener(this.POINTER_DOWN, handler, false); 11516 11517 } else if (type === 'touchmove') { 11518 obj.removeEventListener(this.POINTER_MOVE, handler, false); 11519 11520 } else if (type === 'touchend') { 11521 obj.removeEventListener(this.POINTER_UP, handler, false); 11522 obj.removeEventListener(this.POINTER_CANCEL, handler, false); 11523 } 11524 11525 return this; 11526 }, 11527 11528 _addPointerStart: function (obj, handler, id) { 11529 var onDown = L.bind(function (e) { 11530 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { 11531 // In IE11, some touch events needs to fire for form controls, or 11532 // the controls will stop working. We keep a whitelist of tag names that 11533 // need these events. For other target tags, we prevent default on the event. 11534 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { 11535 L.DomEvent.preventDefault(e); 11536 } else { 11537 return; 11538 } 11539 } 11540 11541 this._handlePointer(e, handler); 11542 }, this); 11543 11544 obj['_leaflet_touchstart' + id] = onDown; 11545 obj.addEventListener(this.POINTER_DOWN, onDown, false); 11546 11547 // need to keep track of what pointers and how many are active to provide e.touches emulation 11548 if (!this._pointerDocListener) { 11549 var pointerUp = L.bind(this._globalPointerUp, this); 11550 11551 // we listen documentElement as any drags that end by moving the touch off the screen get fired there 11552 document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
11553 document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true); 11554 document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true); 11555 document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true); 11556 11557 this._pointerDocListener = true; 11558 } 11559 }, 11560 11561 _globalPointerDown: function (e) { 11562 this._pointers[e.pointerId] = e; 11563 this._pointersCount++; 11564 }, 11565 11566 _globalPointerMove: function (e) { 11567 if (this._pointers[e.pointerId]) { 11568 this._pointers[e.pointerId] = e; 11569 } 11570 }, 11571 11572 _globalPointerUp: function (e) { 11573 delete this._pointers[e.pointerId]; 11574 this._pointersCount--; 11575 }, 11576 11577 _handlePointer: function (e, handler) { 11578 e.touches = []; 11579 for (var i in this._pointers) { 11580 e.touches.push(this._pointers[i]); 11581 } 11582 e.changedTouches = [e]; 11583 11584 handler(e); 11585 }, 11586 11587 _addPointerMove: function (obj, handler, id) { 11588 var onMove = L.bind(function (e) { 11589 // don't fire touch moves when mouse isn't down 11590 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } 11591 11592 this._handlePointer(e, handler); 11593 }, this); 11594 11595 obj['_leaflet_touchmove' + id] = onMove; 11596 obj.addEventListener(this.POINTER_MOVE, onMove, false); 11597 }, 11598 11599 _addPointerEnd: function (obj, handler, id) { 11600 var onUp = L.bind(function (e) { 11601 this._handlePointer(e, handler); 11602 }, this); 11603 11604 obj['_leaflet_touchend' + id] = onUp; 11605 obj.addEventListener(this.POINTER_UP, onUp, false); 11606 obj.addEventListener(this.POINTER_CANCEL, onUp, false); 11607 } 11608}); 11609 11610 11611 11612/* 11613 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. 11614 */ 11615 11616// @namespace Map 11617// @section Interaction Options 11618L.Map.mergeOptions({ 11619 // @section Touch interaction options 11620 // @option touchZoom: Boolean|String = * 11621 // Whether the map can be zoomed by touch-dragging with two fingers. If 11622 // passed `'center'`, it will zoom to the center of the view regardless of 11623 // where the touch events (fingers) were. Enabled for touch-capable web 11624 // browsers except for old Androids. 11625 touchZoom: L.Browser.touch && !L.Browser.android23, 11626 11627 // @option bounceAtZoomLimits: Boolean = true 11628 // Set it to false if you don't want the map to zoom beyond min/max zoom 11629 // and then bounce back when pinch-zooming. 11630 bounceAtZoomLimits: true 11631}); 11632 11633L.Map.TouchZoom = L.Handler.extend({ 11634 addHooks: function () { 11635 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom'); 11636 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); 11637 }, 11638 11639 removeHooks: function () { 11640 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom'); 11641 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); 11642 }, 11643 11644 _onTouchStart: function (e) { 11645 var map = this._map; 11646 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 11647 11648 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 11649 p2 = map.mouseEventToContainerPoint(e.touches[1]); 11650 11651 this._centerPoint = map.getSize()._divideBy(2); 11652 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 11653 if (map.options.touchZoom !== 'center') { 11654 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 11655 } 11656 11657 this._startDist = p1.distanceTo(p2); 11658 this._startZoom = map.getZoom(); 11659 11660 this._moved = false; 11661 this._zooming = true; 11662 11663 map._stop(); 11664 11665 L.DomEvent 11666 .on(document, 'touchmove', this._onTouchMove, this) 11667 .on(document, 'touchend', this._onTouchEnd, this); 11668 11669 L.DomEvent.preventDefault(e); 11670 }, 11671 11672 _onTouchMove: function (e) { 11673 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 11674 11675 var map = this._map, 11676 p1 = map.mouseEventToContainerPoint(e.touches[0]), 11677 p2 = map.mouseEventToContainerPoint(e.touches[1]), 11678 scale = p1.distanceTo(p2) / this._startDist; 11679 11680 11681 this._zoom = map.getScaleZoom(scale, this._startZoom); 11682 11683 if (!map.options.bounceAtZoomLimits && ( 11684 (this._zoom < map.getMinZoom() && scale < 1) || 11685 (this._zoom > map.getMaxZoom() && scale > 1))) { 11686 this._zoom = map._limitZoom(this._zoom); 11687 } 11688 11689 if (map.options.touchZoom === 'center') { 11690 this._center = this._startLatLng; 11691 if (scale === 1) { return; } 11692 } else { 11693 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 11694 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 11695 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 11696 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 11697 } 11698 11699 if (!this._moved) { 11700 map._moveStart(true); 11701 this._moved = true; 11702 } 11703
11704 L.Util.cancelAnimFrame(this._animRequest); 11705 11706 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); 11707 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true); 11708 11709 L.DomEvent.preventDefault(e); 11710 }, 11711 11712 _onTouchEnd: function () { 11713 if (!this._moved || !this._zooming) { 11714 this._zooming = false; 11715 return; 11716 } 11717 11718 this._zooming = false; 11719 L.Util.cancelAnimFrame(this._animRequest); 11720 11721 L.DomEvent 11722 .off(document, 'touchmove', this._onTouchMove) 11723 .off(document, 'touchend', this._onTouchEnd); 11724 11725 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 11726 if (this._map.options.zoomAnimation) { 11727 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 11728 } else { 11729 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 11730 } 11731 } 11732}); 11733 11734// @section Handlers 11735// @property touchZoom: Handler 11736// Touch zoom handler. 11737L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); 11738 11739 11740 11741/* 11742 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. 11743 */ 11744 11745// @namespace Map 11746// @section Interaction Options 11747L.Map.mergeOptions({ 11748 // @section Touch interaction options 11749 // @option tap: Boolean = true 11750 // Enables mobile hacks for supporting instant taps (fixing 200ms click 11751 // delay on iOS/Android) and touch holds (fired as `contextmenu` events). 11752 tap: true, 11753 11754 // @option tapTolerance: Number = 15 11755 // The max number of pixels a user can shift his finger during touch 11756 // for it to be considered a valid tap. 11757 tapTolerance: 15 11758}); 11759 11760L.Map.Tap = L.Handler.extend({ 11761 addHooks: function () { 11762 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); 11763 }, 11764 11765 removeHooks: function () { 11766 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); 11767 }, 11768 11769 _onDown: function (e) { 11770 if (!e.touches) { return; } 11771 11772 L.DomEvent.preventDefault(e); 11773 11774 this._fireClick = true; 11775 11776 // don't simulate click or track longpress if more than 1 touch 11777 if (e.touches.length > 1) { 11778 this._fireClick = false; 11779 clearTimeout(this._holdTimeout); 11780 return; 11781 } 11782 11783 var first = e.touches[0], 11784 el = first.target; 11785 11786 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); 11787 11788 // if touching a link, highlight it 11789 if (el.tagName && el.tagName.toLowerCase() === 'a') { 11790 L.DomUtil.addClass(el, 'leaflet-active'); 11791 } 11792 11793 // simulate long hold but setting a timeout 11794 this._holdTimeout = setTimeout(L.bind(function () { 11795 if (this._isTapValid()) { 11796 this._fireClick = false; 11797 this._onUp(); 11798 this._simulateEvent('contextmenu', first); 11799 } 11800 }, this), 1000); 11801 11802 this._simulateEvent('mousedown', first); 11803 11804 L.DomEvent.on(document, { 11805 touchmove: this._onMove, 11806 touchend: this._onUp 11807 }, this); 11808 }, 11809 11810 _onUp: function (e) { 11811 clearTimeout(this._holdTimeout); 11812 11813 L.DomEvent.off(document, { 11814 touchmove: this._onMove, 11815 touchend: this._onUp 11816 }, this); 11817 11818 if (this._fireClick && e && e.changedTouches) { 11819 11820 var first = e.changedTouches[0], 11821 el = first.target; 11822 11823 if (el && el.tagName && el.tagName.toLowerCase() === 'a') { 11824 L.DomUtil.removeClass(el, 'leaflet-active'); 11825 } 11826 11827 this._simulateEvent('mouseup', first); 11828 11829 // simulate click if the touch didn't move too much 11830 if (this._isTapValid()) { 11831 this._simulateEvent('click', first); 11832 } 11833 } 11834 }, 11835 11836 _isTapValid: function () { 11837 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 11838 }, 11839 11840 _onMove: function (e) { 11841 var first = e.touches[0]; 11842 this._newPos = new L.Point(first.clientX, first.clientY); 11843 this._simulateEvent('mousemove', first); 11844 }, 11845 11846 _simulateEvent: function (type, e) { 11847 var simulatedEvent = document.createEvent('MouseEvents'); 11848 11849 simulatedEvent._simulated = true; 11850 e.target._simulatedClick = true; 11851 11852 simulatedEvent.initMouseEvent( 11853 type, true, true, window, 1, 11854 e.screenX, e.screenY, 11855 e.clientX, e.clientY, 11856 false, false, false, false, 0, null); 11857 11858 e.target.dispatchEvent(simulatedEvent); 11859 } 11860}); 11861 11862// @section Handlers 11863// @property tap: Handler 11864// Mobile touch hacks (quick tap and touch hold) handler. 11865if (L.Browser.touch && !L.Browser.pointer) { 11866 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); 11867} 11868 11869 11870 11871/* 11872 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 11873 * (zoom to a selected bounding box), enabled by default. 11874 */ 11875 11876// @namespace Map 11877// @section Interaction Options 11878L.Map.mergeOptions({ 11879 // @option boxZoom: Boolean = true 11880 // Whether the map can be zoomed to a rectangular area specified by 11881 // dragging the mouse while pressing the shift key. 11882 boxZoom: true 11883}); 11884 11885L.Map.BoxZoom = L.Handler.extend({ 11886 initialize: function (map) { 11887 this._map = map; 11888 this._container = map._container; 11889 this._pane = map._panes.overlayPane; 11890 }, 11891 11892 addHooks: function () { 11893 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); 11894 }, 11895 11896 removeHooks: function () { 11897 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); 11898 }, 11899 11900 moved: function () { 11901 return this._moved; 11902 }, 11903 11904 _resetState: function () { 11905 this._moved = false;
11906 }, 11907 11908 _onMouseDown: function (e) { 11909 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 11910 11911 this._resetState(); 11912 11913 L.DomUtil.disableTextSelection(); 11914 L.DomUtil.disableImageDrag(); 11915 11916 this._startPoint = this._map.mouseEventToContainerPoint(e); 11917 11918 L.DomEvent.on(document, { 11919 contextmenu: L.DomEvent.stop, 11920 mousemove: this._onMouseMove, 11921 mouseup: this._onMouseUp, 11922 keydown: this._onKeyDown 11923 }, this); 11924 }, 11925 11926 _onMouseMove: function (e) { 11927 if (!this._moved) { 11928 this._moved = true; 11929 11930 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container); 11931 L.DomUtil.addClass(this._container, 'leaflet-crosshair'); 11932 11933 this._map.fire('boxzoomstart'); 11934 } 11935 11936 this._point = this._map.mouseEventToContainerPoint(e); 11937 11938 var bounds = new L.Bounds(this._point, this._startPoint), 11939 size = bounds.getSize(); 11940 11941 L.DomUtil.setPosition(this._box, bounds.min); 11942 11943 this._box.style.width = size.x + 'px'; 11944 this._box.style.height = size.y + 'px'; 11945 }, 11946 11947 _finish: function () { 11948 if (this._moved) { 11949 L.DomUtil.remove(this._box); 11950 L.DomUtil.removeClass(this._container, 'leaflet-crosshair'); 11951 } 11952 11953 L.DomUtil.enableTextSelection(); 11954 L.DomUtil.enableImageDrag(); 11955 11956 L.DomEvent.off(document, { 11957 contextmenu: L.DomEvent.stop, 11958 mousemove: this._onMouseMove, 11959 mouseup: this._onMouseUp, 11960 keydown: this._onKeyDown 11961 }, this); 11962 }, 11963 11964 _onMouseUp: function (e) { 11965 if ((e.which !== 1) && (e.button !== 1)) { return; } 11966 11967 this._finish(); 11968 11969 if (!this._moved) { return; } 11970 // Postpone to next JS tick so internal click event handling 11971 // still see it as "moved". 11972 setTimeout(L.bind(this._resetState, this), 0); 11973 11974 var bounds = new L.LatLngBounds( 11975 this._map.containerPointToLatLng(this._startPoint), 11976 this._map.containerPointToLatLng(this._point)); 11977 11978 this._map 11979 .fitBounds(bounds) 11980 .fire('boxzoomend', {boxZoomBounds: bounds}); 11981 }, 11982 11983 _onKeyDown: function (e) { 11984 if (e.keyCode === 27) { 11985 this._finish(); 11986 } 11987 } 11988}); 11989 11990// @section Handlers 11991// @property boxZoom: Handler 11992// Box (shift-drag with mouse) zoom handler. 11993L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); 11994 11995 11996 11997/* 11998 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 11999 */ 12000 12001// @namespace Map 12002// @section Keyboard Navigation Options 12003L.Map.mergeOptions({ 12004 // @option keyboard: Boolean = true 12005 // Makes the map focusable and allows users to navigate the map with keyboard 12006 // arrows and `+`/`-` keys. 12007 keyboard: true, 12008 12009 // @option keyboardPanDelta: Number = 80 12010 // Amount of pixels to pan when pressing an arrow key. 12011 keyboardPanDelta: 80 12012}); 12013 12014L.Map.Keyboard = L.Handler.extend({ 12015 12016 keyCodes: { 12017 left: [37], 12018 right: [39], 12019 down: [40], 12020 up: [38], 12021 zoomIn: [187, 107, 61, 171], 12022 zoomOut: [189, 109, 54, 173] 12023 }, 12024 12025 initialize: function (map) { 12026 this._map = map; 12027 12028 this._setPanDelta(map.options.keyboardPanDelta); 12029 this._setZoomDelta(map.options.zoomDelta); 12030 }, 12031 12032 addHooks: function () { 12033 var container = this._map._container; 12034 12035 // make the container focusable by tabbing 12036 if (container.tabIndex <= 0) { 12037 container.tabIndex = '0'; 12038 } 12039 12040 L.DomEvent.on(container, { 12041 focus: this._onFocus, 12042 blur: this._onBlur, 12043 mousedown: this._onMouseDown 12044 }, this); 12045 12046 this._map.on({ 12047 focus: this._addHooks, 12048 blur: this._removeHooks 12049 }, this); 12050 }, 12051 12052 removeHooks: function () { 12053 this._removeHooks(); 12054 12055 L.DomEvent.off(this._map._container, { 12056 focus: this._onFocus, 12057 blur: this._onBlur, 12058 mousedown: this._onMouseDown 12059 }, this); 12060 12061 this._map.off({ 12062 focus: this._addHooks, 12063 blur: this._removeHooks 12064 }, this); 12065 }, 12066 12067 _onMouseDown: function () { 12068 if (this._focused) { return; } 12069 12070 var body = document.body, 12071 docEl = document.documentElement, 12072 top = body.scrollTop || docEl.scrollTop, 12073 left = body.scrollLeft || docEl.scrollLeft; 12074 12075 this._map._container.focus(); 12076 12077 window.scrollTo(left, top); 12078 }, 12079 12080 _onFocus: function () { 12081 this._focused = true; 12082 this._map.fire('focus'); 12083 }, 12084 12085 _onBlur: function () { 12086 this._focused = false; 12087 this._map.fire('blur'); 12088 }, 12089 12090 _setPanDelta: function (panDelta) { 12091 var keys = this._panKeys = {}, 12092 codes = this.keyCodes, 12093 i, len; 12094 12095 for (i = 0, len = codes.left.length; i < len; i++) { 12096 keys[codes.left[i]] = [-1 * panDelta, 0]; 12097 } 12098 for (i = 0, len = codes.right.length; i < len; i++) { 12099 keys[codes.right[i]] = [panDelta, 0]; 12100 } 12101 for (i = 0, len = codes.down.length; i < len; i++) { 12102 keys[codes.down[i]] = [0, panDelta]; 12103 } 12104 for (i = 0, len = codes.up.length; i < len; i++) { 12105 keys[codes.up[i]] = [0, -1 * panDelta]; 12106 } 12107 }, 12108 12109 _setZoomDelta: function (zoomDelta) { 12110 var keys = this._zoomKeys = {}, 12111 codes = this.keyCodes, 12112 i, len; 12113 12114 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 12115 keys[codes.zoomIn[i]] = zoomDelta; 12116 } 12117 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 12118 keys[codes.zoomOut[i]] = -zoomDelta; 12119 } 12120 }, 12121 12122 _addHooks: function () { 12123 L.DomEvent.on(document, 'keydown', this._onKeyDown, this); 12124 }, 12125 12126 _removeHooks: function () { 12127 L.DomEvent.off(document, 'keydown', this._onKeyDown, this); 12128 }, 12129 12130 _onKeyDown: function (e) { 12131 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 12132 12133 var key = e.keyCode, 12134 map = this._map, 12135 offset; 12136 12137 if (key in this._panKeys) { 12138 12139 if (map._panAnim && map._panAnim._inProgress) { return; } 12140 12141 offset = this._panKeys[key]; 12142 if (e.shiftKey) { 12143 offset = L.point(offset).multiplyBy(3); 12144 } 12145 12146 map.panBy(offset); 12147 12148 if (map.options.maxBounds) { 12149 map.panInsideBounds(map.options.maxBounds); 12150 } 12151 12152 } else if (key in this._zoomKeys) { 12153 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 12154 12155 } else if (key === 27) { 12156 map.closePopup(); 12157 12158 } else { 12159 return; 12160 } 12161 12162 L.DomEvent.stop(e); 12163 } 12164}); 12165 12166// @section Handlers 12167// @section Handlers 12168// @property keyboard: Handler 12169// Keyboard navigation handler. 12170L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); 12171 12172 12173 12174/* 12175 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 12176 */ 12177 12178 12179/* @namespace Marker 12180 * @section Interaction handlers 12181 * 12182 * 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: 12183 * 12184 * ```js 12185 * marker.dragging.disable(); 12186 * ``` 12187 * 12188 * @property dragging: Handler 12189 * Marker dragging handler (by both mouse and touch). 12190 */ 12191 12192L.Handler.MarkerDrag = L.Handler.extend({ 12193 initialize: function (marker) { 12194 this._marker = marker; 12195 }, 12196 12197 addHooks: function () { 12198 var icon = this._marker._icon; 12199 12200 if (!this._draggable) { 12201 this._draggable = new L.Draggable(icon, icon, true); 12202 } 12203 12204 this._draggable.on({ 12205 dragstart: this._onDragStart, 12206 drag: this._onDrag, 12207 dragend: this._onDragEnd 12208 }, this).enable(); 12209 12210 L.DomUtil.addClass(icon, 'leaflet-marker-draggable'); 12211 }, 12212 12213 removeHooks: function () { 12214 this._draggable.off({ 12215 dragstart: this._onDragStart, 12216 drag: this._onDrag, 12217 dragend: this._onDragEnd 12218 }, this).disable(); 12219 12220 if (this._marker._icon) { 12221 L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); 12222 } 12223 }, 12224 12225 moved: function () { 12226 return this._draggable && this._draggable._moved; 12227 }, 12228 12229 _onDragStart: function () { 12230 // @section Dragging events 12231 // @event dragstart: Event 12232 // Fired when the user starts dragging the marker. 12233 12234 // @event movestart: Event 12235 // Fired when the marker starts moving (because of dragging). 12236 12237 this._oldLatLng = this._marker.getLatLng(); 12238 this._marker 12239 .closePopup() 12240 .fire('movestart') 12241 .fire('dragstart'); 12242 }, 12243 12244 _onDrag: function (e) { 12245 var marker = this._marker, 12246 shadow = marker._shadow, 12247 iconPos = L.DomUtil.getPosition(marker._icon), 12248 latlng = marker._map.layerPointToLatLng(iconPos); 12249 12250 // update shadow position 12251 if (shadow) { 12252 L.DomUtil.setPosition(shadow, iconPos); 12253 } 12254 12255 marker._latlng = latlng; 12256 e.latlng = latlng; 12257 e.oldLatLng = this._oldLatLng; 12258 12259 // @event drag: Event 12260 // Fired repeatedly while the user drags the marker. 12261 marker 12262 .fire('move', e) 12263 .fire('drag', e); 12264 }, 12265 12266 _onDragEnd: function (e) { 12267 // @event dragend: DragEndEvent 12268 // Fired when the user stops dragging the marker. 12269 12270 // @event moveend: Event 12271 // Fired when the marker stops moving (because of dragging). 12272 delete this._oldLatLng; 12273 this._marker 12274 .fire('moveend') 12275 .fire('dragend', e); 12276 } 12277}); 12278 12279 12280 12281/* 12282 * @class Control 12283 * @aka L.Control 12284 * @inherits Class 12285 * 12286 * L.Control is a base class for implementing map controls. Handles positioning. 12287 * All other controls extend from this class. 12288 */ 12289
vendor: 4,188 bytes, lines 12290-12457
12290L.Control = L.Class.extend({ 12291 // @section 12292 // @aka Control options 12293 options: { 12294 // @option position: String = 'topright' 12295 // The position of the control (one of the map corners). Possible values are `'topleft'`, 12296 // `'topright'`, `'bottomleft'` or `'bottomright'` 12297 position: 'topright' 12298 }, 12299 12300 initialize: function (options) { 12301 L.setOptions(this, options); 12302 }, 12303 12304 /* @section 12305 * Classes extending L.Control will inherit the following methods: 12306 * 12307 * @method getPosition: string 12308 * Returns the position of the control. 12309 */ 12310 getPosition: function () { 12311 return this.options.position; 12312 }, 12313 12314 // @method setPosition(position: string): this 12315 // Sets the position of the control. 12316 setPosition: function (position) { 12317 var map = this._map; 12318 12319 if (map) { 12320 map.removeControl(this); 12321 } 12322 12323 this.options.position = position; 12324 12325 if (map) { 12326 map.addControl(this); 12327 } 12328 12329 return this; 12330 }, 12331 12332 // @method getContainer: HTMLElement 12333 // Returns the HTMLElement that contains the control. 12334 getContainer: function () { 12335 return this._container; 12336 }, 12337 12338 // @method addTo(map: Map): this 12339 // Adds the control to the given map. 12340 addTo: function (map) { 12341 this.remove(); 12342 this._map = map; 12343 12344 var container = this._container = this.onAdd(map), 12345 pos = this.getPosition(), 12346 corner = map._controlCorners[pos]; 12347 12348 L.DomUtil.addClass(container, 'leaflet-control'); 12349 12350 if (pos.indexOf('bottom') !== -1) { 12351 corner.insertBefore(container, corner.firstChild); 12352 } else { 12353 corner.appendChild(container); 12354 } 12355 12356 return this; 12357 }, 12358 12359 // @method remove: this 12360 // Removes the control from the map it is currently active on. 12361 remove: function () { 12362 if (!this._map) { 12363 return this; 12364 } 12365 12366 L.DomUtil.remove(this._container); 12367 12368 if (this.onRemove) { 12369 this.onRemove(this._map); 12370 } 12371 12372 this._map = null; 12373 12374 return this; 12375 }, 12376 12377 _refocusOnMap: function (e) { 12378 // if map exists and event is not a keyboard event 12379 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 12380 this._map.getContainer().focus(); 12381 } 12382 } 12383}); 12384 12385L.control = function (options) { 12386 return new L.Control(options); 12387}; 12388 12389/* @section Extension methods 12390 * @uninheritable 12391 * 12392 * Every control should extend from `L.Control` and (re-)implement the following methods. 12393 * 12394 * @method onAdd(map: Map): HTMLElement 12395 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 12396 * 12397 * @method onRemove(map: Map) 12398 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 12399 */ 12400 12401/* @namespace Map 12402 * @section Methods for Layers and Controls 12403 */ 12404L.Map.include({ 12405 // @method addControl(control: Control): this 12406 // Adds the given control to the map 12407 addControl: function (control) { 12408 control.addTo(this); 12409 return this; 12410 }, 12411 12412 // @method removeControl(control: Control): this 12413 // Removes the given control from the map 12414 removeControl: function (control) { 12415 control.remove(); 12416 return this; 12417 }, 12418 12419 _initControlPos: function () { 12420 var corners = this._controlCorners = {}, 12421 l = 'leaflet-', 12422 container = this._controlContainer = 12423 L.DomUtil.create('div', l + 'control-container', this._container); 12424 12425 function createCorner(vSide, hSide) { 12426 var className = l + vSide + ' ' + l + hSide; 12427 12428 corners[vSide + hSide] = L.DomUtil.create('div', className, container); 12429 } 12430 12431 createCorner('top', 'left'); 12432 createCorner('top', 'right'); 12433 createCorner('bottom', 'left'); 12434 createCorner('bottom', 'right'); 12435 }, 12436 12437 _clearControlPos: function () { 12438 L.DomUtil.remove(this._controlContainer); 12439 } 12440}); 12441 12442 12443 12444/* 12445 * @class Control.Zoom 12446 * @aka L.Control.Zoom 12447 * @inherits Control 12448 * 12449 * 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`. 12450 */ 12451 12452L.Control.Zoom = L.Control.extend({ 12453 // @section 12454 // @aka Control.Zoom options 12455 options: { 12456 position: 'topleft', 12457
12458 // @option zoomInText: String = '+' 12459 // The text set on the 'zoom in' button. 12460 zoomInText: '+', 12461 12462 // @option zoomInTitle: String = 'Zoom in' 12463 // The title set on the 'zoom in' button. 12464 zoomInTitle: 'Zoom in', 12465 12466 // @option zoomOutText: String = '-' 12467 // The text set on the 'zoom out' button. 12468 zoomOutText: '-', 12469 12470 // @option zoomOutTitle: String = 'Zoom out' 12471 // The title set on the 'zoom out' button. 12472 zoomOutTitle: 'Zoom out' 12473 }, 12474 12475 onAdd: function (map) { 12476 var zoomName = 'leaflet-control-zoom', 12477 container = L.DomUtil.create('div', zoomName + ' leaflet-bar'), 12478 options = this.options; 12479 12480 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 12481 zoomName + '-in', container, this._zoomIn); 12482 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 12483 zoomName + '-out', container, this._zoomOut); 12484 12485 this._updateDisabled(); 12486 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 12487 12488 return container; 12489 }, 12490 12491 onRemove: function (map) { 12492 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 12493 }, 12494 12495 disable: function () { 12496 this._disabled = true; 12497 this._updateDisabled(); 12498 return this; 12499 }, 12500 12501 enable: function () { 12502 this._disabled = false; 12503 this._updateDisabled(); 12504 return this; 12505 }, 12506 12507 _zoomIn: function (e) { 12508 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 12509 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 12510 } 12511 }, 12512 12513 _zoomOut: function (e) { 12514 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 12515 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 12516 } 12517 }, 12518 12519 _createButton: function (html, title, className, container, fn) { 12520 var link = L.DomUtil.create('a', className, container); 12521 link.innerHTML = html; 12522 link.href = '#'; 12523 link.title = title; 12524 12525 /* 12526 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 12527 */ 12528 link.setAttribute('role', 'button'); 12529 link.setAttribute('aria-label', title); 12530 12531 L.DomEvent 12532 .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation) 12533 .on(link, 'click', L.DomEvent.stop) 12534 .on(link, 'click', fn, this) 12535 .on(link, 'click', this._refocusOnMap, this); 12536 12537 return link; 12538 }, 12539 12540 _updateDisabled: function () { 12541 var map = this._map, 12542 className = 'leaflet-disabled'; 12543 12544 L.DomUtil.removeClass(this._zoomInButton, className); 12545 L.DomUtil.removeClass(this._zoomOutButton, className); 12546 12547 if (this._disabled || map._zoom === map.getMinZoom()) { 12548 L.DomUtil.addClass(this._zoomOutButton, className); 12549 } 12550 if (this._disabled || map._zoom === map.getMaxZoom()) { 12551 L.DomUtil.addClass(this._zoomInButton, className); 12552 } 12553 } 12554}); 12555 12556// @namespace Map 12557// @section Control options 12558// @option zoomControl: Boolean = true 12559// Whether a [zoom control](#control-zoom) is added to the map by default. 12560L.Map.mergeOptions({ 12561 zoomControl: true 12562}); 12563 12564L.Map.addInitHook(function () { 12565 if (this.options.zoomControl) { 12566 this.zoomControl = new L.Control.Zoom(); 12567 this.addControl(this.zoomControl); 12568 } 12569}); 12570 12571// @namespace Control.Zoom 12572// @factory L.control.zoom(options: Control.Zoom options) 12573// Creates a zoom control 12574L.control.zoom = function (options) { 12575 return new L.Control.Zoom(options); 12576}; 12577 12578 12579 12580/* 12581 * @class Control.Attribution 12582 * @aka L.Control.Attribution 12583 * @inherits Control 12584 * 12585 * 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. 12586 */ 12587 12588L.Control.Attribution = L.Control.extend({ 12589 // @section 12590 // @aka Control.Attribution options 12591 options: { 12592 position: 'bottomright', 12593 12594 // @option prefix: String = 'Leaflet' 12595 // The HTML text shown before the attributions. Pass `false` to disable. 12596 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' 12597 }, 12598 12599 initialize: function (options) { 12600 L.setOptions(this, options); 12601 12602 this._attributions = {}; 12603 }, 12604 12605 onAdd: function (map) { 12606 map.attributionControl = this; 12607 this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); 12608 if (L.DomEvent) { 12609 L.DomEvent.disableClickPropagation(this._container); 12610 } 12611 12612 // TODO ugly, refactor 12613 for (var i in map._layers) { 12614 if (map._layers[i].getAttribution) { 12615 this.addAttribution(map._layers[i].getAttribution()); 12616 } 12617 } 12618 12619 this._update(); 12620 12621 return this._container; 12622 }, 12623 12624 // @method setPrefix(prefix: String): this 12625 // Sets the text before the attributions. 12626 setPrefix: function (prefix) { 12627 this.options.prefix = prefix; 12628 this._update(); 12629 return this; 12630 }, 12631 12632 // @method addAttribution(text: String): this 12633 // Adds an attribution text (e.g. `'Vector data © Mapbox'`). 12634 addAttribution: function (text) { 12635 if (!text) { return this; } 12636 12637 if (!this._attributions[text]) { 12638 this._attributions[text] = 0; 12639 } 12640 this._attributions[text]++; 12641 12642 this._update(); 12643 12644 return this; 12645 }, 12646 12647 // @method removeAttribution(text: String): this 12648 // Removes an attribution text. 12649 removeAttribution: function (text) { 12650 if (!text) { return this; } 12651 12652 if (this._attributions[text]) { 12653 this._attributions[text]--; 12654 this._update(); 12655 } 12656 12657 return this; 12658 }, 12659 12660 _update: function () { 12661 if (!this._map) { return; } 12662 12663 var attribs = []; 12664 12665 for (var i in this._attributions) { 12666 if (this._attributions[i]) { 12667 attribs.push(i); 12668 } 12669 } 12670 12671 var prefixAndAttribs = []; 12672 12673 if (this.options.prefix) { 12674 prefixAndAttribs.push(this.options.prefix); 12675 } 12676 if (attribs.length) { 12677 prefixAndAttribs.push(attribs.join(', ')); 12678 } 12679 12680 this._container.innerHTML = prefixAndAttribs.join(' | '); 12681 } 12682}); 12683 12684// @namespace Map 12685// @section Control options 12686// @option attributionControl: Boolean = true 12687// Whether a [attribution control](#control-attribution) is added to the map by default. 12688L.Map.mergeOptions({ 12689 attributionControl: true 12690}); 12691 12692L.Map.addInitHook(function () { 12693 if (this.options.attributionControl) { 12694 new L.Control.Attribution().addTo(this); 12695 } 12696}); 12697 12698// @namespace Control.Attribution 12699// @factory L.control.attribution(options: Control.Attribution options) 12700// Creates an attribution control. 12701L.control.attribution = function (options) { 12702 return new L.Control.Attribution(options); 12703}; 12704 12705 12706 12707/* 12708 * @class Control.Scale 12709 * @aka L.Control.Scale 12710 * @inherits Control 12711 * 12712 * 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`. 12713 * 12714 * @example 12715 * 12716 * ```js 12717 * L.control.scale().addTo(map); 12718 * ``` 12719 */ 12720 12721L.Control.Scale = L.Control.extend({ 12722 // @section 12723 // @aka Control.Scale options 12724 options: { 12725 position: 'bottomleft', 12726 12727 // @option maxWidth: Number = 100 12728 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 12729 maxWidth: 100, 12730 12731 // @option metric: Boolean = True 12732 // Whether to show the metric scale line (m/km). 12733 metric: true, 12734 12735 // @option imperial: Boolean = True 12736 // Whether to show the imperial scale line (mi/ft).
vendor: 7,728 bytes, lines 12737-12999
12737 imperial: true 12738 12739 // @option updateWhenIdle: Boolean = false 12740 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 12741 }, 12742 12743 onAdd: function (map) { 12744 var className = 'leaflet-control-scale', 12745 container = L.DomUtil.create('div', className), 12746 options = this.options; 12747 12748 this._addScales(options, className + '-line', container); 12749 12750 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 12751 map.whenReady(this._update, this); 12752 12753 return container; 12754 }, 12755 12756 onRemove: function (map) { 12757 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 12758 }, 12759 12760 _addScales: function (options, className, container) { 12761 if (options.metric) { 12762 this._mScale = L.DomUtil.create('div', className, container); 12763 } 12764 if (options.imperial) { 12765 this._iScale = L.DomUtil.create('div', className, container); 12766 } 12767 }, 12768 12769 _update: function () { 12770 var map = this._map, 12771 y = map.getSize().y / 2; 12772 12773 var maxMeters = map.distance( 12774 map.containerPointToLatLng([0, y]), 12775 map.containerPointToLatLng([this.options.maxWidth, y])); 12776 12777 this._updateScales(maxMeters); 12778 }, 12779 12780 _updateScales: function (maxMeters) { 12781 if (this.options.metric && maxMeters) { 12782 this._updateMetric(maxMeters); 12783 } 12784 if (this.options.imperial && maxMeters) { 12785 this._updateImperial(maxMeters); 12786 } 12787 }, 12788 12789 _updateMetric: function (maxMeters) { 12790 var meters = this._getRoundNum(maxMeters), 12791 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 12792 12793 this._updateScale(this._mScale, label, meters / maxMeters); 12794 }, 12795 12796 _updateImperial: function (maxMeters) { 12797 var maxFeet = maxMeters * 3.2808399, 12798 maxMiles, miles, feet; 12799 12800 if (maxFeet > 5280) { 12801 maxMiles = maxFeet / 5280; 12802 miles = this._getRoundNum(maxMiles); 12803 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 12804 12805 } else { 12806 feet = this._getRoundNum(maxFeet); 12807 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 12808 } 12809 }, 12810 12811 _updateScale: function (scale, text, ratio) { 12812 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 12813 scale.innerHTML = text; 12814 }, 12815 12816 _getRoundNum: function (num) { 12817 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 12818 d = num / pow10; 12819 12820 d = d >= 10 ? 10 : 12821 d >= 5 ? 5 : 12822 d >= 3 ? 3 : 12823 d >= 2 ? 2 : 1; 12824 12825 return pow10 * d; 12826 } 12827}); 12828 12829 12830// @factory L.control.scale(options?: Control.Scale options) 12831// Creates an scale control with the given options. 12832L.control.scale = function (options) { 12833 return new L.Control.Scale(options); 12834}; 12835 12836 12837 12838/* 12839 * @class Control.Layers 12840 * @aka L.Control.Layers 12841 * @inherits Control 12842 * 12843 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control.html)). Extends `Control`. 12844 * 12845 * @example 12846 * 12847 * ```js 12848 * var baseLayers = { 12849 * "Mapbox": mapbox, 12850 * "OpenStreetMap": osm 12851 * }; 12852 * 12853 * var overlays = { 12854 * "Marker": marker, 12855 * "Roads": roadsLayer 12856 * }; 12857 * 12858 * L.control.layers(baseLayers, overlays).addTo(map); 12859 * ``` 12860 * 12861 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 12862 * 12863 * ```js 12864 * { 12865 * "<someName1>": layer1, 12866 * "<someName2>": layer2 12867 * } 12868 * ``` 12869 * 12870 * The layer names can contain HTML, which allows you to add additional styling to the items: 12871 * 12872 * ```js 12873 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 12874 * ``` 12875 */ 12876 12877 12878L.Control.Layers = L.Control.extend({ 12879 // @section 12880 // @aka Control.Layers options 12881 options: { 12882 // @option collapsed: Boolean = true 12883 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. 12884 collapsed: true, 12885 position: 'topright', 12886 12887 // @option autoZIndex: Boolean = true 12888 // 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. 12889 autoZIndex: true, 12890 12891 // @option hideSingleBase: Boolean = false 12892 // If `true`, the base layers in the control will be hidden when there is only one. 12893 hideSingleBase: false, 12894 12895 // @option sortLayers: Boolean = false 12896 // Whether to sort the layers. When `false`, layers will keep the order 12897 // in which they were added to the control. 12898 sortLayers: false, 12899 12900 // @option sortFunction: Function = * 12901 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 12902 // that will be used for sorting the layers, when `sortLayers` is `true`. 12903 // The function receives both the `L.Layer` instances and their names, as in 12904 // `sortFunction(layerA, layerB, nameA, nameB)`. 12905 // By default, it sorts layers alphabetically by their name. 12906 sortFunction: function (layerA, layerB, nameA, nameB) { 12907 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 12908 } 12909 }, 12910 12911 initialize: function (baseLayers, overlays, options) { 12912 L.setOptions(this, options); 12913 12914 this._layers = []; 12915 this._lastZIndex = 0; 12916 this._handlingClick = false; 12917 12918 for (var i in baseLayers) { 12919 this._addLayer(baseLayers[i], i); 12920 } 12921 12922 for (i in overlays) { 12923 this._addLayer(overlays[i], i, true); 12924 } 12925 }, 12926 12927 onAdd: function (map) { 12928 this._initLayout(); 12929 this._update(); 12930 12931 this._map = map; 12932 map.on('zoomend', this._checkDisabledLayers, this); 12933 12934 return this._container; 12935 }, 12936 12937 onRemove: function () { 12938 this._map.off('zoomend', this._checkDisabledLayers, this); 12939 12940 for (var i = 0; i < this._layers.length; i++) { 12941 this._layers[i].layer.off('add remove', this._onLayerChange, this); 12942 } 12943 }, 12944 12945 // @method addBaseLayer(layer: Layer, name: String): this 12946 // Adds a base layer (radio button entry) with the given name to the control. 12947 addBaseLayer: function (layer, name) { 12948 this._addLayer(layer, name); 12949 return (this._map) ? this._update() : this; 12950 }, 12951 12952 // @method addOverlay(layer: Layer, name: String): this 12953 // Adds an overlay (checkbox entry) with the given name to the control. 12954 addOverlay: function (layer, name) { 12955 this._addLayer(layer, name, true); 12956 return (this._map) ? this._update() : this; 12957 }, 12958 12959 // @method removeLayer(layer: Layer): this 12960 // Remove the given layer from the control. 12961 removeLayer: function (layer) { 12962 layer.off('add remove', this._onLayerChange, this); 12963 12964 var obj = this._getLayer(L.stamp(layer)); 12965 if (obj) { 12966 this._layers.splice(this._layers.indexOf(obj), 1); 12967 } 12968 return (this._map) ? this._update() : this; 12969 }, 12970 12971 // @method expand(): this 12972 // Expand the control container if collapsed. 12973 expand: function () { 12974 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded'); 12975 this._form.style.height = null; 12976 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 12977 if (acceptableHeight < this._form.clientHeight) { 12978 L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar'); 12979 this._form.style.height = acceptableHeight + 'px'; 12980 } else { 12981 L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar'); 12982 } 12983 this._checkDisabledLayers(); 12984 return this; 12985 }, 12986 12987 // @method collapse(): this 12988 // Collapse the control container if expanded. 12989 collapse: function () { 12990 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); 12991 return this; 12992 }, 12993 12994 _initLayout: function () { 12995 var className = 'leaflet-control-layers', 12996 container = this._container = L.DomUtil.create('div', className), 12997 collapsed = this.options.collapsed; 12998 12999 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
13000 container.setAttribute('aria-haspopup', true); 13001 13002 L.DomEvent.disableClickPropagation(container); 13003 if (!L.Browser.touch) { 13004 L.DomEvent.disableScrollPropagation(container); 13005 } 13006 13007 var form = this._form = L.DomUtil.create('form', className + '-list'); 13008 13009 if (collapsed) { 13010 this._map.on('click', this.collapse, this); 13011 13012 if (!L.Browser.android) { 13013 L.DomEvent.on(container, { 13014 mouseenter: this.expand, 13015 mouseleave: this.collapse 13016 }, this); 13017 } 13018 } 13019 13020 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); 13021 link.href = '#'; 13022 link.title = 'Layers'; 13023 13024 if (L.Browser.touch) { 13025 L.DomEvent 13026 .on(link, 'click', L.DomEvent.stop) 13027 .on(link, 'click', this.expand, this); 13028 } else { 13029 L.DomEvent.on(link, 'focus', this.expand, this); 13030 } 13031 13032 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033 13033 L.DomEvent.on(form, 'click', function () { 13034 setTimeout(L.bind(this._onInputClick, this), 0); 13035 }, this); 13036 13037 // TODO keyboard accessibility 13038 13039 if (!collapsed) { 13040 this.expand(); 13041 } 13042 13043 this._baseLayersList = L.DomUtil.create('div', className + '-base', form); 13044 this._separator = L.DomUtil.create('div', className + '-separator', form); 13045 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); 13046 13047 container.appendChild(form); 13048 }, 13049 13050 _getLayer: function (id) { 13051 for (var i = 0; i < this._layers.length; i++) { 13052 13053 if (this._layers[i] && L.stamp(this._layers[i].layer) === id) { 13054 return this._layers[i]; 13055 } 13056 } 13057 }, 13058 13059 _addLayer: function (layer, name, overlay) { 13060 layer.on('add remove', this._onLayerChange, this); 13061 13062 this._layers.push({ 13063 layer: layer, 13064 name: name, 13065 overlay: overlay 13066 }); 13067 13068 if (this.options.sortLayers) { 13069 this._layers.sort(L.bind(function (a, b) { 13070 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 13071 }, this)); 13072 } 13073 13074 if (this.options.autoZIndex && layer.setZIndex) { 13075 this._lastZIndex++; 13076 layer.setZIndex(this._lastZIndex); 13077 } 13078 }, 13079 13080 _update: function () { 13081 if (!this._container) { return this; } 13082 13083 L.DomUtil.empty(this._baseLayersList); 13084 L.DomUtil.empty(this._overlaysList); 13085 13086 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 13087 13088 for (i = 0; i < this._layers.length; i++) { 13089 obj = this._layers[i]; 13090 this._addItem(obj); 13091 overlaysPresent = overlaysPresent || obj.overlay; 13092 baseLayersPresent = baseLayersPresent || !obj.overlay; 13093 baseLayersCount += !obj.overlay ? 1 : 0; 13094 } 13095 13096 // Hide base layers section if there's only one layer. 13097 if (this.options.hideSingleBase) { 13098 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 13099 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 13100 } 13101 13102 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 13103 13104 return this; 13105 }, 13106 13107 _onLayerChange: function (e) { 13108 if (!this._handlingClick) { 13109 this._update(); 13110 } 13111 13112 var obj = this._getLayer(L.stamp(e.target)); 13113 13114 // @namespace Map 13115 // @section Layer events 13116 // @event baselayerchange: LayersControlEvent 13117 // Fired when the base layer is changed through the [layer control](#control-layers). 13118 // @event overlayadd: LayersControlEvent 13119 // Fired when an overlay is selected through the [layer control](#control-layers). 13120 // @event overlayremove: LayersControlEvent 13121 // Fired when an overlay is deselected through the [layer control](#control-layers). 13122 // @namespace Control.Layers 13123 var type = obj.overlay ? 13124 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 13125 (e.type === 'add' ? 'baselayerchange' : null); 13126 13127 if (type) { 13128 this._map.fire(type, obj); 13129 } 13130 }, 13131 13132 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) 13133 _createRadioElement: function (name, checked) { 13134 13135 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 13136 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 13137 13138 var radioFragment = document.createElement('div'); 13139 radioFragment.innerHTML = radioHtml; 13140 13141 return radioFragment.firstChild; 13142 }, 13143 13144 _addItem: function (obj) { 13145 var label = document.createElement('label'), 13146 checked = this._map.hasLayer(obj.layer), 13147 input; 13148 13149 if (obj.overlay) { 13150 input = document.createElement('input'); 13151 input.type = 'checkbox'; 13152 input.className = 'leaflet-control-layers-selector'; 13153 input.defaultChecked = checked; 13154 } else { 13155 input = this._createRadioElement('leaflet-base-layers', checked); 13156 } 13157 13158 input.layerId = L.stamp(obj.layer); 13159 13160 L.DomEvent.on(input, 'click', this._onInputClick, this); 13161 13162 var name = document.createElement('span'); 13163 name.innerHTML = ' ' + obj.name; 13164 13165 // Helps from preventing layer control flicker when checkboxes are disabled 13166 // https://github.com/Leaflet/Leaflet/issues/2771 13167 var holder = document.createElement('div'); 13168 13169 label.appendChild(holder); 13170 holder.appendChild(input); 13171 holder.appendChild(name); 13172 13173 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 13174 container.appendChild(label); 13175 13176 this._checkDisabledLayers(); 13177 return label; 13178 }, 13179 13180 _onInputClick: function () { 13181 var inputs = this._form.getElementsByTagName('input'), 13182 input, layer, hasLayer; 13183 var addedLayers = [], 13184 removedLayers = []; 13185 13186 this._handlingClick = true; 13187
13188 for (var i = inputs.length - 1; i >= 0; i--) { 13189 input = inputs[i]; 13190 layer = this._getLayer(input.layerId).layer; 13191 hasLayer = this._map.hasLayer(layer); 13192 13193 if (input.checked && !hasLayer) { 13194 addedLayers.push(layer); 13195 13196 } else if (!input.checked && hasLayer) { 13197 removedLayers.push(layer); 13198 } 13199 } 13200 13201 // Bugfix issue 2318: Should remove all old layers before readding new ones 13202 for (i = 0; i < removedLayers.length; i++) { 13203 this._map.removeLayer(removedLayers[i]); 13204 } 13205 for (i = 0; i < addedLayers.length; i++) { 13206 this._map.addLayer(addedLayers[i]); 13207 } 13208 13209 this._handlingClick = false; 13210 13211 this._refocusOnMap(); 13212 }, 13213 13214 _checkDisabledLayers: function () { 13215 var inputs = this._form.getElementsByTagName('input'), 13216 input, 13217 layer, 13218 zoom = this._map.getZoom(); 13219 13220 for (var i = inputs.length - 1; i >= 0; i--) { 13221 input = inputs[i]; 13222 layer = this._getLayer(input.layerId).layer; 13223 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 13224 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 13225 13226 } 13227 }, 13228 13229 _expand: function () { 13230 // Backward compatibility, remove me in 1.1. 13231 return this.expand(); 13232 }, 13233 13234 _collapse: function () { 13235 // Backward compatibility, remove me in 1.1. 13236 return this.collapse(); 13237 } 13238 13239}); 13240 13241 13242// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 13243// Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. 13244L.control.layers = function (baseLayers, overlays, options) { 13245 return new L.Control.Layers(baseLayers, overlays, options); 13246}; 13247 13248 13249 13250}(window, document)); 13251//# sourceMappingURL=leaflet-src.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.