1/* 2 Leaflet 1.0.1, 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.1" 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 typeListeners.count++; 520 }, 521 522 _off: function (type, fn, context) { 523 var listeners, 524 i, 525 len; 526 527 if (!this._events) { return; } 528 529 listeners = this._events[type]; 530 531 if (!listeners) { 532 return; 533 } 534 535 if (!fn) { 536 // Set all removed listeners to noop so they are not called if remove happens in fire 537 for (i = 0, len = listeners.length; i < len; i++) { 538 listeners[i].fn = L.Util.falseFn; 539 } 540 // clear all listeners for a type if function isn't specified 541 delete this._events[type]; 542 return; 543 } 544 545 if (context === this) { 546 context = undefined; 547 } 548 549 if (listeners) { 550 551 // find fn and remove it 552 for (i = 0, len = listeners.length; i < len; i++) { 553 var l = listeners[i]; 554 if (l.ctx !== context) { continue; } 555 if (l.fn === fn) { 556 557 // set the removed listener to noop so that's not called if remove happens in fire 558 l.fn = L.Util.falseFn; 559 560 if (this._firingCount) { 561 /* copy array in case events are being fired */ 562 this._events[type] = listeners = listeners.slice(); 563 } 564 listeners.splice(i, 1); 565 566 return; 567 } 568 } 569 } 570 }, 571 572 // @method fire(type: String, data?: Object, propagate?: Boolean): this 573 // Fires an event of the specified type. You can optionally provide an data 574 // object ââ¬â the first argument of the listener function will contain its 575 // properties. The event might can optionally be propagated to event parents. 576 fire: function (type, data, propagate) {
577 if (!this.listens(type, propagate)) { return this; } 578 579 var event = L.Util.extend({}, data, {type: type, target: this}); 580 581 if (this._events) { 582 var listeners = this._events[type]; 583 584 if (listeners) { 585 this._firingCount = (this._firingCount + 1) || 1; 586 for (var i = 0, len = listeners.length; i < len; i++) { 587 var l = listeners[i]; 588 l.fn.call(l.ctx || this, event); 589 } 590 591 this._firingCount--; 592 } 593 } 594 595 if (propagate) { 596 // propagate the event to parents (set with addEventParent) 597 this._propagateEvent(event); 598 } 599 600 return this; 601 }, 602 603 // @method listens(type: String): Boolean 604 // Returns `true` if a particular event type has any listeners attached to it. 605 listens: function (type, propagate) { 606 var listeners = this._events && this._events[type]; 607 if (listeners && listeners.length) { return true; } 608 609 if (propagate) { 610 // also check parents for listeners if event propagates 611 for (var id in this._eventParents) { 612 if (this._eventParents[id].listens(type, propagate)) { return true; } 613 } 614 } 615 return false; 616 }, 617 618 // @method once(ââ¬Â¦): this 619 // Behaves as [`on(ââ¬Â¦)`](#evented-on), except the listener will only get fired once and then removed. 620 once: function (types, fn, context) { 621 622 if (typeof types === 'object') { 623 for (var type in types) { 624 this.once(type, types[type], fn); 625 } 626 return this; 627 } 628 629 var handler = L.bind(function () { 630 this 631 .off(types, fn, context) 632 .off(types, handler, context); 633 }, this); 634 635 // add a listener that's executed once and removed after that 636 return this 637 .on(types, fn, context) 638 .on(types, handler, context); 639 }, 640 641 // @method addEventParent(obj: Evented): this 642 // Adds an event parent - an `Evented` that will receive propagated events 643 addEventParent: function (obj) { 644 this._eventParents = this._eventParents || {}; 645 this._eventParents[L.stamp(obj)] = obj; 646 return this; 647 }, 648 649 // @method removeEventParent(obj: Evented): this 650 // Removes an event parent, so it will stop receiving propagated events 651 removeEventParent: function (obj) { 652 if (this._eventParents) { 653 delete this._eventParents[L.stamp(obj)]; 654 } 655 return this; 656 }, 657 658 _propagateEvent: function (e) { 659 for (var id in this._eventParents) { 660 this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true); 661 } 662 } 663}); 664 665var proto = L.Evented.prototype; 666 667// aliases; we should ditch those eventually 668 669// @method addEventListener(ââ¬Â¦): this 670// Alias to [`on(ââ¬Â¦)`](#evented-on) 671proto.addEventListener = proto.on; 672 673// @method removeEventListener(ââ¬Â¦): this 674// Alias to [`off(ââ¬Â¦)`](#evented-off) 675 676// @method clearAllEventListeners(ââ¬Â¦): this 677// Alias to [`off()`](#evented-off) 678proto.removeEventListener = proto.clearAllEventListeners = proto.off; 679 680// @method addOneTimeEventListener(ââ¬Â¦): this 681// Alias to [`once(ââ¬Â¦)`](#evented-once) 682proto.addOneTimeEventListener = proto.once; 683 684// @method fireEvent(ââ¬Â¦): this 685// Alias to [`fire(ââ¬Â¦)`](#evented-fire) 686proto.fireEvent = proto.fire; 687 688// @method hasEventListeners(ââ¬Â¦): Boolean 689// Alias to [`listens(ââ¬Â¦)`](#evented-listens) 690proto.hasEventListeners = proto.listens; 691 692L.Mixin = {Events: proto}; 693 694 695 696/* 697 * @namespace Browser 698 * @aka L.Browser 699 * 700 * A namespace with static properties for browser/feature detection used by Leaflet internally. 701 * 702 * @example 703 * 704 * ```js 705 * if (L.Browser.ielt9) { 706 * alert('Upgrade your browser, dude!'); 707 * } 708 * ``` 709 */ 710 711(function () { 712 713 var ua = navigator.userAgent.toLowerCase(), 714 doc = document.documentElement, 715 716 ie = 'ActiveXObject' in window, 717 718 webkit = ua.indexOf('webkit') !== -1, 719 phantomjs = ua.indexOf('phantom') !== -1, 720 android23 = ua.search('android [23]') !== -1, 721 chrome = ua.indexOf('chrome') !== -1, 722 gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie, 723 724 win = navigator.platform.indexOf('Win') === 0, 725 726 mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1, 727 msPointer = !window.PointerEvent && window.MSPointerEvent, 728 pointer = window.PointerEvent || msPointer, 729 730 ie3d = ie && ('transition' in doc.style), 731 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, 732 gecko3d = 'MozPerspective' in doc.style, 733 opera12 = 'OTransition' in doc.style; 734 735 736 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || 737 (window.DocumentTouch && document instanceof window.DocumentTouch)); 738 739 L.Browser = { 740 741 // @property ie: Boolean 742 // `true` for all Internet Explorer versions (not Edge). 743 ie: ie, 744 745 // @property ielt9: Boolean 746 // `true` for Internet Explorer versions less than 9. 747 ielt9: ie && !document.addEventListener, 748 749 // @property edge: Boolean 750 // `true` for the Edge web browser. 751 edge: 'msLaunchUri' in navigator && !('documentMode' in document), 752 753 // @property webkit: Boolean 754 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 755 webkit: webkit, 756 757 // @property gecko: Boolean 758 // `true` for gecko-based browsers like Firefox. 759 gecko: gecko, 760 761 // @property android: Boolean 762 // `true` for any browser running on an Android platform. 763 android: ua.indexOf('android') !== -1, 764 765 // @property android23: Boolean 766 // `true` for browsers running on Android 2 or Android 3. 767 android23: android23, 768 769 // @property chrome: Boolean 770 // `true` for the Chrome browser. 771 chrome: chrome, 772 773 // @property safari: Boolean 774 // `true` for the Safari browser. 775 safari: !chrome && ua.indexOf('safari') !== -1, 776 777 778 // @property win: Boolean 779 // `true` when the browser is running in a Windows platform 780 win: win, 781 782 783 // @property ie3d: Boolean
784 // `true` for all Internet Explorer versions supporting CSS transforms. 785 ie3d: ie3d, 786 787 // @property webkit3d: Boolean 788 // `true` for webkit-based browsers supporting CSS transforms. 789 webkit3d: webkit3d, 790 791 // @property gecko3d: Boolean 792 // `true` for gecko-based browsers supporting CSS transforms. 793 gecko3d: gecko3d, 794 795 // @property opera12: Boolean 796 // `true` for the Opera browser supporting CSS transforms (version 12 or later). 797 opera12: opera12, 798 799 // @property any3d: Boolean 800 // `true` for all browsers supporting CSS transforms. 801 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs, 802 803 804 // @property mobile: Boolean 805 // `true` for all browsers running in a mobile device. 806 mobile: mobile, 807 808 // @property mobileWebkit: Boolean 809 // `true` for all webkit-based browsers in a mobile device. 810 mobileWebkit: mobile && webkit, 811 812 // @property mobileWebkit3d: Boolean 813 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 814 mobileWebkit3d: mobile && webkit3d, 815 816 // @property mobileOpera: Boolean 817 // `true` for the Opera browser in a mobile device. 818 mobileOpera: mobile && window.opera, 819 820 // @property mobileGecko: Boolean 821 // `true` for gecko-based browsers running in a mobile device. 822 mobileGecko: mobile && gecko, 823 824 825 // @property touch: Boolean 826 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 827 touch: !!touch, 828 829 // @property msPointer: Boolean 830 // `true` for browsers implementing the Microsoft touch events model (notably IE10). 831 msPointer: !!msPointer, 832 833 // @property pointer: Boolean 834 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 835 pointer: !!pointer, 836 837 838 // @property retina: Boolean 839 // `true` for browsers on a high-resolution "retina" screen. 840 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1 841 }; 842 843}()); 844 845 846 847/* 848 * @class Point 849 * @aka L.Point 850 * 851 * Represents a point with `x` and `y` coordinates in pixels. 852 * 853 * @example 854 * 855 * ```js 856 * var point = L.point(200, 300); 857 * ``` 858 * 859 * 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: 860 * 861 * ```js 862 * map.panBy([200, 300]); 863 * map.panBy(L.point(200, 300)); 864 * ``` 865 */ 866 867L.Point = function (x, y, round) { 868 this.x = (round ? Math.round(x) : x); 869 this.y = (round ? Math.round(y) : y); 870}; 871 872L.Point.prototype = { 873 874 // @method clone(): Point 875 // Returns a copy of the current point. 876 clone: function () { 877 return new L.Point(this.x, this.y); 878 }, 879 880 // @method add(otherPoint: Point): Point 881 // Returns the result of addition of the current and the given points. 882 add: function (point) { 883 // non-destructive, returns a new point 884 return this.clone()._add(L.point(point)); 885 }, 886 887 _add: function (point) { 888 // destructive, used directly for performance in situations where it's safe to modify existing point 889 this.x += point.x; 890 this.y += point.y; 891 return this; 892 }, 893 894 // @method subtract(otherPoint: Point): Point 895 // Returns the result of subtraction of the given point from the current. 896 subtract: function (point) { 897 return this.clone()._subtract(L.point(point)); 898 }, 899 900 _subtract: function (point) { 901 this.x -= point.x; 902 this.y -= point.y; 903 return this; 904 }, 905 906 // @method divideBy(num: Number): Point 907 // Returns the result of division of the current point by the given number. 908 divideBy: function (num) { 909 return this.clone()._divideBy(num); 910 }, 911 912 _divideBy: function (num) { 913 this.x /= num; 914 this.y /= num; 915 return this; 916 }, 917 918 // @method multiplyBy(num: Number): Point 919 // Returns the result of multiplication of the current point by the given number. 920 multiplyBy: function (num) { 921 return this.clone()._multiplyBy(num); 922 }, 923 924 _multiplyBy: function (num) { 925 this.x *= num; 926 this.y *= num; 927 return this; 928 }, 929 930 // @method scaleBy(scale: Point): Point 931 // Multiply each coordinate of the current point by each coordinate of 932 // `scale`. In linear algebra terms, multiply the point by the 933 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 934 // defined by `scale`. 935 scaleBy: function (point) { 936 return new L.Point(this.x * point.x, this.y * point.y); 937 }, 938 939 // @method unscaleBy(scale: Point): Point 940 // Inverse of `scaleBy`. Divide each coordinate of the current point by 941 // each coordinate of `scale`. 942 unscaleBy: function (point) { 943 return new L.Point(this.x / point.x, this.y / point.y); 944 }, 945 946 // @method round(): Point 947 // Returns a copy of the current point with rounded coordinates. 948 round: function () { 949 return this.clone()._round(); 950 }, 951 952 _round: function () { 953 this.x = Math.round(this.x); 954 this.y = Math.round(this.y); 955 return this; 956 }, 957 958 // @method floor(): Point 959 // Returns a copy of the current point with floored coordinates (rounded down). 960 floor: function () { 961 return this.clone()._floor(); 962 }, 963 964 _floor: function () { 965 this.x = Math.floor(this.x); 966 this.y = Math.floor(this.y); 967 return this; 968 }, 969 970 // @method ceil(): Point 971 // Returns a copy of the current point with ceiled coordinates (rounded up). 972 ceil: function () { 973 return this.clone()._ceil(); 974 }, 975 976 _ceil: function () { 977 this.x = Math.ceil(this.x); 978 this.y = Math.ceil(this.y); 979 return this; 980 }, 981 982 // @method distanceTo(otherPoint: Point): Number 983 // Returns the cartesian distance between the current and the given points. 984 distanceTo: function (point) { 985 point = L.point(point); 986 987 var x = point.x - this.x, 988 y = point.y - this.y; 989 990 return Math.sqrt(x * x + y * y); 991 }, 992 993 // @method equals(otherPoint: Point): Boolean 994 // Returns `true` if the given point has the same coordinates. 995 equals: function (point) { 996 point = L.point(point); 997 998 return point.x === this.x && 999 point.y === this.y; 1000 }, 1001 1002 // @method contains(otherPoint: Point): Boolean 1003 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 1004 contains: function (point) { 1005 point = L.point(point); 1006 1007 return Math.abs(point.x) <= Math.abs(this.x) && 1008 Math.abs(point.y) <= Math.abs(this.y); 1009 }, 1010 1011 // @method toString(): String 1012 // Returns a string representation of the point for debugging purposes. 1013 toString: function () { 1014 return 'Point(' +
1015 L.Util.formatNum(this.x) + ', ' + 1016 L.Util.formatNum(this.y) + ')'; 1017 } 1018}; 1019 1020// @factory L.point(x: Number, y: Number, round?: Boolean) 1021// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 1022 1023// @alternative 1024// @factory L.point(coords: Number[]) 1025// Expects an array of the form `[x, y]` instead. 1026 1027// @alternative 1028// @factory L.point(coords: Object) 1029// Expects a plain object of the form `{x: Number, y: Number}` instead. 1030L.point = function (x, y, round) { 1031 if (x instanceof L.Point) { 1032 return x; 1033 } 1034 if (L.Util.isArray(x)) { 1035 return new L.Point(x[0], x[1]); 1036 } 1037 if (x === undefined || x === null) { 1038 return x; 1039 } 1040 if (typeof x === 'object' && 'x' in x && 'y' in x) { 1041 return new L.Point(x.x, x.y); 1042 } 1043 return new L.Point(x, y, round); 1044}; 1045 1046 1047 1048/* 1049 * @class Bounds 1050 * @aka L.Bounds 1051 * 1052 * Represents a rectangular area in pixel coordinates. 1053 * 1054 * @example 1055 * 1056 * ```js 1057 * var p1 = L.point(10, 10), 1058 * p2 = L.point(40, 60), 1059 * bounds = L.bounds(p1, p2); 1060 * ``` 1061 * 1062 * 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: 1063 * 1064 * ```js 1065 * otherBounds.intersects([[10, 10], [40, 60]]); 1066 * ``` 1067 */ 1068 1069L.Bounds = function (a, b) { 1070 if (!a) { return; } 1071 1072 var points = b ? [a, b] : a; 1073 1074 for (var i = 0, len = points.length; i < len; i++) { 1075 this.extend(points[i]); 1076 } 1077}; 1078 1079L.Bounds.prototype = { 1080 // @method extend(point: Point): this 1081 // Extends the bounds to contain the given point. 1082 extend: function (point) { // (Point) 1083 point = L.point(point); 1084 1085 // @property min: Point 1086 // The top left corner of the rectangle. 1087 // @property max: Point 1088 // The bottom right corner of the rectangle. 1089 if (!this.min && !this.max) { 1090 this.min = point.clone(); 1091 this.max = point.clone(); 1092 } else { 1093 this.min.x = Math.min(point.x, this.min.x); 1094 this.max.x = Math.max(point.x, this.max.x); 1095 this.min.y = Math.min(point.y, this.min.y); 1096 this.max.y = Math.max(point.y, this.max.y); 1097 } 1098 return this; 1099 }, 1100 1101 // @method getCenter(round?: Boolean): Point 1102 // Returns the center point of the bounds. 1103 getCenter: function (round) { 1104 return new L.Point( 1105 (this.min.x + this.max.x) / 2, 1106 (this.min.y + this.max.y) / 2, round); 1107 }, 1108 1109 // @method getBottomLeft(): Point 1110 // Returns the bottom-left point of the bounds. 1111 getBottomLeft: function () { 1112 return new L.Point(this.min.x, this.max.y); 1113 }, 1114 1115 // @method getTopRight(): Point 1116 // Returns the top-right point of the bounds. 1117 getTopRight: function () { // -> Point 1118 return new L.Point(this.max.x, this.min.y); 1119 }, 1120 1121 // @method getSize(): Point 1122 // Returns the size of the given bounds 1123 getSize: function () { 1124 return this.max.subtract(this.min); 1125 }, 1126 1127 // @method contains(otherBounds: Bounds): Boolean 1128 // Returns `true` if the rectangle contains the given one. 1129 // @alternative 1130 // @method contains(point: Point): Boolean 1131 // Returns `true` if the rectangle contains the given point. 1132 contains: function (obj) { 1133 var min, max; 1134 1135 if (typeof obj[0] === 'number' || obj instanceof L.Point) { 1136 obj = L.point(obj); 1137 } else { 1138 obj = L.bounds(obj); 1139 } 1140 1141 if (obj instanceof L.Bounds) { 1142 min = obj.min; 1143 max = obj.max; 1144 } else { 1145 min = max = obj; 1146 } 1147 1148 return (min.x >= this.min.x) && 1149 (max.x <= this.max.x) && 1150 (min.y >= this.min.y) && 1151 (max.y <= this.max.y); 1152 }, 1153 1154 // @method intersects(otherBounds: Bounds): Boolean 1155 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1156 // intersect if they have at least one point in common. 1157 intersects: function (bounds) { // (Bounds) -> Boolean 1158 bounds = L.bounds(bounds); 1159 1160 var min = this.min, 1161 max = this.max, 1162 min2 = bounds.min, 1163 max2 = bounds.max, 1164 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1165 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1166 1167 return xIntersects && yIntersects; 1168 }, 1169 1170 // @method overlaps(otherBounds: Bounds): Boolean 1171 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1172 // overlap if their intersection is an area. 1173 overlaps: function (bounds) { // (Bounds) -> Boolean 1174 bounds = L.bounds(bounds); 1175 1176 var min = this.min, 1177 max = this.max, 1178 min2 = bounds.min,
1179 max2 = bounds.max, 1180 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1181 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1182 1183 return xOverlaps && yOverlaps; 1184 }, 1185 1186 isValid: function () { 1187 return !!(this.min && this.max); 1188 } 1189}; 1190 1191 1192// @factory L.bounds(topLeft: Point, bottomRight: Point) 1193// Creates a Bounds object from two coordinates (usually top-left and bottom-right corners). 1194// @alternative 1195// @factory L.bounds(points: Point[]) 1196// Creates a Bounds object from the points it contains 1197L.bounds = function (a, b) { 1198 if (!a || a instanceof L.Bounds) { 1199 return a; 1200 } 1201 return new L.Bounds(a, b); 1202}; 1203 1204 1205 1206/* 1207 * @class Transformation 1208 * @aka L.Transformation 1209 * 1210 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1211 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1212 * the reverse. Used by Leaflet in its projections code. 1213 * 1214 * @example 1215 * 1216 * ```js 1217 * var transformation = new L.Transformation(2, 5, -1, 10), 1218 * p = L.point(1, 2), 1219 * p2 = transformation.transform(p), // L.point(7, 8) 1220 * p3 = transformation.untransform(p2); // L.point(1, 2) 1221 * ``` 1222 */ 1223 1224 1225// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1226// Creates a `Transformation` object with the given coefficients. 1227L.Transformation = function (a, b, c, d) { 1228 this._a = a; 1229 this._b = b; 1230 this._c = c; 1231 this._d = d; 1232}; 1233 1234L.Transformation.prototype = { 1235 // @method transform(point: Point, scale?: Number): Point 1236 // Returns a transformed point, optionally multiplied by the given scale. 1237 // Only accepts real `L.Point` instances, not arrays. 1238 transform: function (point, scale) { // (Point, Number) -> Point 1239 return this._transform(point.clone(), scale); 1240 }, 1241 1242 // destructive transform (faster) 1243 _transform: function (point, scale) { 1244 scale = scale || 1; 1245 point.x = scale * (this._a * point.x + this._b); 1246 point.y = scale * (this._c * point.y + this._d); 1247 return point; 1248 }, 1249 1250 // @method untransform(point: Point, scale?: Number): Point 1251 // Returns the reverse transformation of the given point, optionally divided 1252 // by the given scale. Only accepts real `L.Point` instances, not arrays. 1253 untransform: function (point, scale) { 1254 scale = scale || 1; 1255 return new L.Point( 1256 (point.x / scale - this._b) / this._a, 1257 (point.y / scale - this._d) / this._c); 1258 } 1259}; 1260 1261 1262 1263/* 1264 * @namespace DomUtil 1265 * 1266 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 1267 * tree, used by Leaflet internally. 1268 * 1269 * Most functions expecting or returning a `HTMLElement` also work for 1270 * SVG elements. The only difference is that classes refer to CSS classes 1271 * in HTML and SVG classes in SVG. 1272 */ 1273 1274L.DomUtil = { 1275 1276 // @function get(id: String|HTMLElement): HTMLElement 1277 // Returns an element given its DOM id, or returns the element itself 1278 // if it was passed directly. 1279 get: function (id) { 1280 return typeof id === 'string' ? document.getElementById(id) : id; 1281 }, 1282 1283 // @function getStyle(el: HTMLElement, styleAttrib: String): String 1284 // Returns the value for a certain style attribute on an element, 1285 // including computed values or values set through CSS. 1286 getStyle: function (el, style) { 1287 1288 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 1289 1290 if ((!value || value === 'auto') && document.defaultView) { 1291 var css = document.defaultView.getComputedStyle(el, null); 1292 value = css ? css[style] : null; 1293 } 1294 1295 return value === 'auto' ? null : value; 1296 }, 1297 1298 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 1299 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 1300 create: function (tagName, className, container) { 1301 1302 var el = document.createElement(tagName); 1303 el.className = className || ''; 1304 1305 if (container) { 1306 container.appendChild(el); 1307 } 1308 1309 return el; 1310 }, 1311 1312 // @function remove(el: HTMLElement) 1313 // Removes `el` from its parent element 1314 remove: function (el) { 1315 var parent = el.parentNode; 1316 if (parent) { 1317 parent.removeChild(el); 1318 } 1319 }, 1320 1321 // @function empty(el: HTMLElement) 1322 // Removes all of `el`'s children elements from `el` 1323 empty: function (el) { 1324 while (el.firstChild) { 1325 el.removeChild(el.firstChild); 1326 } 1327 }, 1328 1329 // @function toFront(el: HTMLElement) 1330 // Makes `el` the last children of its parent, so it renders in front of the other children. 1331 toFront: function (el) { 1332 el.parentNode.appendChild(el); 1333 }, 1334 1335 // @function toBack(el: HTMLElement) 1336 // Makes `el` the first children of its parent, so it renders back from the other children. 1337 toBack: function (el) { 1338 var parent = el.parentNode; 1339 parent.insertBefore(el, parent.firstChild); 1340 }, 1341 1342 // @function hasClass(el: HTMLElement, name: String): Boolean 1343 // Returns `true` if the element's class attribute contains `name`. 1344 hasClass: function (el, name) { 1345 if (el.classList !== undefined) { 1346 return el.classList.contains(name); 1347 } 1348 var className = L.DomUtil.getClass(el); 1349 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 1350 }, 1351 1352 // @function addClass(el: HTMLElement, name: String) 1353 // Adds `name` to the element's class attribute. 1354 addClass: function (el, name) { 1355 if (el.classList !== undefined) { 1356 var classes = L.Util.splitWords(name); 1357 for (var i = 0, len = classes.length; i < len; i++) { 1358 el.classList.add(classes[i]); 1359 } 1360 } else if (!L.DomUtil.hasClass(el, name)) { 1361 var className = L.DomUtil.getClass(el); 1362 L.DomUtil.setClass(el, (className ? className + ' ' : '') + name); 1363 } 1364 }, 1365 1366 // @function removeClass(el: HTMLElement, name: String) 1367 // Removes `name` from the element's class attribute. 1368 removeClass: function (el, name) { 1369 if (el.classList !== undefined) { 1370 el.classList.remove(name); 1371 } else { 1372 L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 1373 } 1374 }, 1375 1376 // @function setClass(el: HTMLElement, name: String) 1377 // Sets the element's class. 1378 setClass: function (el, name) { 1379 if (el.className.baseVal === undefined) { 1380 el.className = name; 1381 } else { 1382 // in case of SVG element 1383 el.className.baseVal = name; 1384 } 1385 }, 1386 1387 // @function getClass(el: HTMLElement): String 1388 // Returns the element's class. 1389 getClass: function (el) { 1390 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 1391 }, 1392 1393 // @function setOpacity(el: HTMLElement, opacity: Number)
1394 // Set the opacity of an element (including old IE support). 1395 // `opacity` must be a number from `0` to `1`. 1396 setOpacity: function (el, value) { 1397 1398 if ('opacity' in el.style) { 1399 el.style.opacity = value; 1400 1401 } else if ('filter' in el.style) { 1402 L.DomUtil._setOpacityIE(el, value); 1403 } 1404 }, 1405 1406 _setOpacityIE: function (el, value) { 1407 var filter = false, 1408 filterName = 'DXImageTransform.Microsoft.Alpha'; 1409 1410 // filters collection throws an error if we try to retrieve a filter that doesn't exist 1411 try { 1412 filter = el.filters.item(filterName); 1413 } catch (e) { 1414 // don't set opacity to 1 if we haven't already set an opacity, 1415 // it isn't needed and breaks transparent pngs. 1416 if (value === 1) { return; } 1417 } 1418 1419 value = Math.round(value * 100); 1420 1421 if (filter) { 1422 filter.Enabled = (value !== 100); 1423 filter.Opacity = value; 1424 } else { 1425 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 1426 } 1427 }, 1428 1429 // @function testProp(props: String[]): String|false 1430 // Goes through the array of style names and returns the first name 1431 // that is a valid style name for an element. If no such name is found, 1432 // it returns false. Useful for vendor-prefixed styles like `transform`. 1433 testProp: function (props) { 1434 1435 var style = document.documentElement.style; 1436 1437 for (var i = 0; i < props.length; i++) { 1438 if (props[i] in style) { 1439 return props[i]; 1440 } 1441 } 1442 return false; 1443 }, 1444 1445 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 1446 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 1447 // and optionally scaled by `scale`. Does not have an effect if the 1448 // browser doesn't support 3D CSS transforms. 1449 setTransform: function (el, offset, scale) { 1450 var pos = offset || new L.Point(0, 0); 1451 1452 el.style[L.DomUtil.TRANSFORM] = 1453 (L.Browser.ie3d ? 1454 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 1455 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 1456 (scale ? ' scale(' + scale + ')' : ''); 1457 }, 1458 1459 // @function setPosition(el: HTMLElement, position: Point) 1460 // Sets the position of `el` to coordinates specified by `position`, 1461 // using CSS translate or top/left positioning depending on the browser 1462 // (used by Leaflet internally to position its layers). 1463 setPosition: function (el, point) { // (HTMLElement, Point[, Boolean]) 1464 1465 /*eslint-disable */ 1466 el._leaflet_pos = point; 1467 /*eslint-enable */ 1468 1469 if (L.Browser.any3d) { 1470 L.DomUtil.setTransform(el, point); 1471 } else { 1472 el.style.left = point.x + 'px'; 1473 el.style.top = point.y + 'px'; 1474 } 1475 }, 1476 1477 // @function getPosition(el: HTMLElement): Point 1478 // Returns the coordinates of an element previously positioned with setPosition. 1479 getPosition: function (el) { 1480 // this method is only used for elements previously positioned using setPosition, 1481 // so it's safe to cache the position for performance 1482 1483 return el._leaflet_pos || new L.Point(0, 0); 1484 } 1485}; 1486 1487 1488(function () { 1489 // prefix style property names 1490 1491 // @property TRANSFORM: String 1492 // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit). 1493 L.DomUtil.TRANSFORM = L.DomUtil.testProp( 1494 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 1495 1496 1497 // webkitTransition comes first because some browser versions that drop vendor prefix don't do 1498 // the same for the transitionend event, in particular the Android 4.1 stock browser 1499 1500 // @property TRANSITION: String 1501 // Vendor-prefixed transform style name. 1502 var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp( 1503 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 1504 1505 L.DomUtil.TRANSITION_END = 1506 transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend'; 1507 1508 // @function disableTextSelection() 1509 // Prevents the user from generating `selectstart` DOM events, usually generated 1510 // when the user drags the mouse through a page with text. Used internally 1511 // by Leaflet to override the behaviour of any click-and-drag interaction on 1512 // the map. Affects drag interactions on the whole document. 1513 1514 // @function enableTextSelection() 1515 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 1516 if ('onselectstart' in document) { 1517 L.DomUtil.disableTextSelection = function () { 1518 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); 1519 }; 1520 L.DomUtil.enableTextSelection = function () { 1521 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); 1522 }; 1523 1524 } else { 1525 var userSelectProperty = L.DomUtil.testProp( 1526 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 1527 1528 L.DomUtil.disableTextSelection = function () { 1529 if (userSelectProperty) { 1530 var style = document.documentElement.style; 1531 this._userSelect = style[userSelectProperty]; 1532 style[userSelectProperty] = 'none'; 1533 } 1534 }; 1535 L.DomUtil.enableTextSelection = function () { 1536 if (userSelectProperty) { 1537 document.documentElement.style[userSelectProperty] = this._userSelect; 1538 delete this._userSelect; 1539 } 1540 }; 1541 } 1542 1543 // @function disableImageDrag() 1544 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1545 // for `dragstart` DOM events, usually generated when the user drags an image. 1546 L.DomUtil.disableImageDrag = function () { 1547 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); 1548 }; 1549 1550 // @function enableImageDrag() 1551 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 1552 L.DomUtil.enableImageDrag = function () { 1553 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); 1554 }; 1555 1556 // @function preventOutline(el: HTMLElement) 1557 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 1558 // of the element `el` invisible. Used internally by Leaflet to prevent 1559 // focusable elements from displaying an outline when the user performs a 1560 // drag interaction on them. 1561 L.DomUtil.preventOutline = function (element) { 1562 while (element.tabIndex === -1) { 1563 element = element.parentNode; 1564 } 1565 if (!element || !element.style) { return; } 1566 L.DomUtil.restoreOutline(); 1567 this._outlineElement = element; 1568 this._outlineStyle = element.style.outline; 1569 element.style.outline = 'none'; 1570 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this); 1571 }; 1572 1573 // @function restoreOutline() 1574 // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 1575 L.DomUtil.restoreOutline = function () { 1576 if (!this._outlineElement) { return; } 1577 this._outlineElement.style.outline = this._outlineStyle; 1578 delete this._outlineElement; 1579 delete this._outlineStyle; 1580 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this); 1581 }; 1582})(); 1583 1584 1585 1586/* @class LatLng 1587 * @aka L.LatLng 1588 * 1589 * Represents a geographical point with a certain latitude and longitude. 1590 * 1591 * @example 1592 * 1593 * ``` 1594 * var latlng = L.latLng(50.5, 30.5); 1595 * ``` 1596 * 1597 * 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: 1598 * 1599 * ``` 1600 * map.panTo([50, 30]); 1601 * map.panTo({lon: 30, lat: 50}); 1602 * map.panTo({lat: 50, lng: 30}); 1603 * map.panTo(L.latLng(50, 30)); 1604 * ``` 1605 */ 1606 1607L.LatLng = function (lat, lng, alt) { 1608 if (isNaN(lat) || isNaN(lng)) { 1609 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1610 } 1611 1612 // @property lat: Number 1613 // Latitude in degrees 1614 this.lat = +lat; 1615 1616 // @property lng: Number 1617 // Longitude in degrees 1618 this.lng = +lng; 1619 1620 // @property alt: Number 1621 // Altitude in meters (optional) 1622 if (alt !== undefined) { 1623 this.alt = +alt; 1624 } 1625}; 1626 1627L.LatLng.prototype = { 1628 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1629 // 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. 1630 equals: function (obj, maxMargin) { 1631 if (!obj) { return false; } 1632 1633 obj = L.latLng(obj); 1634 1635 var margin = Math.max( 1636 Math.abs(this.lat - obj.lat), 1637 Math.abs(this.lng - obj.lng)); 1638 1639 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1640 }, 1641 1642 // @method toString(): String 1643 // Returns a string representation of the point (for debugging purposes). 1644 toString: function (precision) { 1645 return 'LatLng(' + 1646 L.Util.formatNum(this.lat, precision) + ', ' + 1647 L.Util.formatNum(this.lng, precision) + ')'; 1648 }, 1649 1650 // @method distanceTo(otherLatLng: LatLng): Number 1651 // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula). 1652 distanceTo: function (other) { 1653 return L.CRS.Earth.distance(this, L.latLng(other)); 1654 }, 1655 1656 // @method wrap(): LatLng 1657 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1658 wrap: function () { 1659 return L.CRS.Earth.wrapLatLng(this); 1660 }, 1661 1662 // @method toBounds(sizeInMeters: Number): LatLngBounds 1663 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`. 1664 toBounds: function (sizeInMeters) { 1665 var latAccuracy = 180 * sizeInMeters / 40075017, 1666 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1667 1668 return L.latLngBounds( 1669 [this.lat - latAccuracy, this.lng - lngAccuracy], 1670 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1671 }, 1672 1673 clone: function () { 1674 return new L.LatLng(this.lat, this.lng, this.alt); 1675 } 1676}; 1677 1678 1679 1680// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1681// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1682 1683// @alternative 1684// @factory L.latLng(coords: Array): LatLng 1685// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1686 1687// @alternative 1688// @factory L.latLng(coords: Object): LatLng 1689// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1690 1691L.latLng = function (a, b, c) { 1692 if (a instanceof L.LatLng) { 1693 return a; 1694 } 1695 if (L.Util.isArray(a) && typeof a[0] !== 'object') { 1696 if (a.length === 3) { 1697 return new L.LatLng(a[0], a[1], a[2]); 1698 } 1699 if (a.length === 2) { 1700 return new L.LatLng(a[0], a[1]); 1701 } 1702 return null; 1703 } 1704 if (a === undefined || a === null) { 1705 return a; 1706 } 1707 if (typeof a === 'object' && 'lat' in a) { 1708 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1709 } 1710 if (b === undefined) { 1711 return null; 1712 } 1713 return new L.LatLng(a, b, c); 1714}; 1715 1716 1717 1718/* 1719 * @class LatLngBounds 1720 * @aka L.LatLngBounds 1721 * 1722 * Represents a rectangular geographical area on a map. 1723 * 1724 * @example 1725 * 1726 * ```js 1727 * var southWest = L.latLng(40.712, -74.227), 1728 * northEast = L.latLng(40.774, -74.125), 1729 * bounds = L.latLngBounds(southWest, northEast); 1730 * ``` 1731 * 1732 * 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: 1733 * 1734 * ```js 1735 * map.fitBounds([ 1736 * [40.712, -74.227], 1737 * [40.774, -74.125] 1738 * ]); 1739 * ``` 1740 */ 1741 1742L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) 1743 if (!southWest) { return; } 1744 1745 var latlngs = northEast ? [southWest, northEast] : southWest; 1746 1747 for (var i = 0, len = latlngs.length; i < len; i++) { 1748 this.extend(latlngs[i]); 1749 } 1750}; 1751 1752L.LatLngBounds.prototype = { 1753 1754 // @method extend(latlng: LatLng): this 1755 // Extend the bounds to contain the given point 1756 1757 // @alternative 1758 // @method extend(otherBounds: LatLngBounds): this 1759 // Extend the bounds to contain the given bounds 1760 extend: function (obj) { 1761 var sw = this._southWest, 1762 ne = this._northEast, 1763 sw2, ne2; 1764 1765 if (obj instanceof L.LatLng) { 1766 sw2 = obj; 1767 ne2 = obj; 1768 1769 } else if (obj instanceof L.LatLngBounds) { 1770 sw2 = obj._southWest; 1771 ne2 = obj._northEast; 1772 1773 if (!sw2 || !ne2) { return this; } 1774 1775 } else { 1776 return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this; 1777 } 1778 1779 if (!sw && !ne) { 1780 this._southWest = new L.LatLng(sw2.lat, sw2.lng); 1781 this._northEast = new L.LatLng(ne2.lat, ne2.lng); 1782 } else { 1783 sw.lat = Math.min(sw2.lat, sw.lat); 1784 sw.lng = Math.min(sw2.lng, sw.lng); 1785 ne.lat = Math.max(ne2.lat, ne.lat); 1786 ne.lng = Math.max(ne2.lng, ne.lng); 1787 } 1788 1789 return this; 1790 }, 1791 1792 // @method pad(bufferRatio: Number): LatLngBounds 1793 // Returns bigger bounds created by extending the current bounds by a given percentage in each direction. 1794 pad: function (bufferRatio) { 1795 var sw = this._southWest, 1796 ne = this._northEast, 1797 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1798 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1799 1800 return new L.LatLngBounds(
1801 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1802 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1803 }, 1804 1805 // @method getCenter(): LatLng 1806 // Returns the center point of the bounds. 1807 getCenter: function () { 1808 return new L.LatLng( 1809 (this._southWest.lat + this._northEast.lat) / 2, 1810 (this._southWest.lng + this._northEast.lng) / 2); 1811 }, 1812 1813 // @method getSouthWest(): LatLng 1814 // Returns the south-west point of the bounds. 1815 getSouthWest: function () { 1816 return this._southWest; 1817 }, 1818 1819 // @method getNorthEast(): LatLng 1820 // Returns the north-east point of the bounds. 1821 getNorthEast: function () { 1822 return this._northEast; 1823 }, 1824 1825 // @method getNorthWest(): LatLng 1826 // Returns the north-west point of the bounds. 1827 getNorthWest: function () { 1828 return new L.LatLng(this.getNorth(), this.getWest()); 1829 }, 1830 1831 // @method getSouthEast(): LatLng 1832 // Returns the south-east point of the bounds. 1833 getSouthEast: function () { 1834 return new L.LatLng(this.getSouth(), this.getEast()); 1835 }, 1836 1837 // @method getWest(): Number 1838 // Returns the west longitude of the bounds 1839 getWest: function () { 1840 return this._southWest.lng; 1841 }, 1842 1843 // @method getSouth(): Number 1844 // Returns the south latitude of the bounds 1845 getSouth: function () { 1846 return this._southWest.lat; 1847 }, 1848 1849 // @method getEast(): Number 1850 // Returns the east longitude of the bounds 1851 getEast: function () { 1852 return this._northEast.lng; 1853 }, 1854 1855 // @method getNorth(): Number 1856 // Returns the north latitude of the bounds 1857 getNorth: function () { 1858 return this._northEast.lat; 1859 }, 1860 1861 // @method contains(otherBounds: LatLngBounds): Boolean 1862 // Returns `true` if the rectangle contains the given one. 1863 1864 // @alternative 1865 // @method contains (latlng: LatLng): Boolean 1866 // Returns `true` if the rectangle contains the given point. 1867 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1868 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) { 1869 obj = L.latLng(obj); 1870 } else { 1871 obj = L.latLngBounds(obj); 1872 } 1873 1874 var sw = this._southWest, 1875 ne = this._northEast, 1876 sw2, ne2; 1877 1878 if (obj instanceof L.LatLngBounds) {
vendor: 1,060 bytes, lines 1879-1911
1879 sw2 = obj.getSouthWest(); 1880 ne2 = obj.getNorthEast(); 1881 } else { 1882 sw2 = ne2 = obj; 1883 } 1884 1885 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1886 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1887 }, 1888 1889 // @method intersects(otherBounds: LatLngBounds): Boolean 1890 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1891 intersects: function (bounds) { 1892 bounds = L.latLngBounds(bounds); 1893 1894 var sw = this._southWest, 1895 ne = this._northEast, 1896 sw2 = bounds.getSouthWest(), 1897 ne2 = bounds.getNorthEast(), 1898 1899 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1900 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1901 1902 return latIntersects && lngIntersects; 1903 }, 1904 1905 // @method overlaps(otherBounds: Bounds): Boolean 1906 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1907 overlaps: function (bounds) { 1908 bounds = L.latLngBounds(bounds); 1909 1910 var sw = this._southWest, 1911 ne = this._northEast,
1912 sw2 = bounds.getSouthWest(), 1913 ne2 = bounds.getNorthEast(), 1914 1915 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1916 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1917 1918 return latOverlaps && lngOverlaps; 1919 }, 1920 1921 // @method toBBoxString(): String 1922 // 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. 1923 toBBoxString: function () { 1924 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1925 }, 1926 1927 // @method equals(otherBounds: LatLngBounds): Boolean 1928 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. 1929 equals: function (bounds) { 1930 if (!bounds) { return false; } 1931 1932 bounds = L.latLngBounds(bounds); 1933 1934 return this._southWest.equals(bounds.getSouthWest()) && 1935 this._northEast.equals(bounds.getNorthEast()); 1936 }, 1937 1938 // @method isValid(): Boolean 1939 // Returns `true` if the bounds are properly initialized. 1940 isValid: function () { 1941 return !!(this._southWest && this._northEast); 1942 } 1943}; 1944 1945// TODO International date line? 1946 1947// @factory L.latLngBounds(southWest: LatLng, northEast: LatLng) 1948// Creates a `LatLngBounds` object by defining south-west and north-east corners of the rectangle. 1949 1950// @alternative 1951// @factory L.latLngBounds(latlngs: LatLng[]) 1952// 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). 1953L.latLngBounds = function (a, b) { 1954 if (a instanceof L.LatLngBounds) { 1955 return a; 1956 } 1957 return new L.LatLngBounds(a, b); 1958}; 1959 1960 1961 1962/* 1963 * @namespace Projection 1964 * @section 1965 * Leaflet comes with a set of already defined Projections out of the box: 1966 * 1967 * @projection L.Projection.LonLat 1968 * 1969 * Equirectangular, or Plate Carree projection ââ¬â the most simple projection, 1970 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 1971 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 1972 * `EPSG:3395` and `Simple` CRS. 1973 */ 1974 1975L.Projection = {}; 1976 1977L.Projection.LonLat = { 1978 project: function (latlng) { 1979 return new L.Point(latlng.lng, latlng.lat); 1980 }, 1981 1982 unproject: function (point) { 1983 return new L.LatLng(point.y, point.x); 1984 }, 1985 1986 bounds: L.bounds([-180, -90], [180, 90]) 1987}; 1988 1989 1990 1991/* 1992 * @namespace Projection 1993 * @projection L.Projection.SphericalMercator 1994 * 1995 * Spherical Mercator projection ââ¬â the most common projection for online maps, 1996 * used by almost all free and commercial tile providers. Assumes that Earth is 1997 * a sphere. Used by the `EPSG:3857` CRS. 1998 */ 1999 2000L.Projection.SphericalMercator = { 2001 2002 R: 6378137, 2003 MAX_LATITUDE: 85.0511287798, 2004 2005 project: function (latlng) { 2006 var d = Math.PI / 180, 2007 max = this.MAX_LATITUDE, 2008 lat = Math.max(Math.min(max, latlng.lat), -max), 2009 sin = Math.sin(lat * d); 2010 2011 return new L.Point( 2012 this.R * latlng.lng * d, 2013 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 2014 }, 2015 2016 unproject: function (point) { 2017 var d = 180 / Math.PI; 2018 2019 return new L.LatLng( 2020 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 2021 point.x * d / this.R); 2022 }, 2023 2024 bounds: (function () { 2025 var d = 6378137 * Math.PI; 2026 return L.bounds([-d, -d], [d, d]); 2027 })() 2028}; 2029 2030 2031 2032/* 2033 * @class CRS 2034 * @aka L.CRS 2035 * Abstract class that defines coordinate reference systems for projecting 2036 * geographical points into pixel (screen) coordinates and back (and to
2037 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 2038 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). 2039 * 2040 * Leaflet defines the most usual CRSs by default. If you want to use a 2041 * CRS not defined by default, take a look at the 2042 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 2043 */ 2044 2045L.CRS = { 2046 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 2047 // Projects geographical coordinates into pixel coordinates for a given zoom. 2048 latLngToPoint: function (latlng, zoom) { 2049 var projectedPoint = this.projection.project(latlng), 2050 scale = this.scale(zoom); 2051 2052 return this.transformation._transform(projectedPoint, scale); 2053 }, 2054 2055 // @method pointToLatLng(point: Point, zoom: Number): LatLng 2056 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 2057 // zoom into geographical coordinates. 2058 pointToLatLng: function (point, zoom) { 2059 var scale = this.scale(zoom), 2060 untransformedPoint = this.transformation.untransform(point, scale); 2061 2062 return this.projection.unproject(untransformedPoint); 2063 }, 2064 2065 // @method project(latlng: LatLng): Point 2066 // Projects geographical coordinates into coordinates in units accepted for 2067 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 2068 project: function (latlng) { 2069 return this.projection.project(latlng); 2070 }, 2071 2072 // @method unproject(point: Point): LatLng 2073 // Given a projected coordinate returns the corresponding LatLng. 2074 // The inverse of `project`. 2075 unproject: function (point) { 2076 return this.projection.unproject(point); 2077 }, 2078 2079 // @method scale(zoom: Number): Number 2080 // Returns the scale used when transforming projected coordinates into 2081 // pixel coordinates for a particular zoom. For example, it returns 2082 // `256 * 2^zoom` for Mercator-based CRS. 2083 scale: function (zoom) { 2084 return 256 * Math.pow(2, zoom); 2085 }, 2086 2087 // @method zoom(scale: Number): Number 2088 // Inverse of `scale()`, returns the zoom level corresponding to a scale 2089 // factor of `scale`. 2090 zoom: function (scale) { 2091 return Math.log(scale / 256) / Math.LN2; 2092 }, 2093 2094 // @method getProjectedBounds(zoom: Number): Bounds 2095 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 2096 getProjectedBounds: function (zoom) { 2097 if (this.infinite) { return null; } 2098 2099 var b = this.projection.bounds, 2100 s = this.scale(zoom), 2101 min = this.transformation.transform(b.min, s), 2102 max = this.transformation.transform(b.max, s); 2103 2104 return L.bounds(min, max); 2105 }, 2106 2107 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 2108 // Returns the distance between two geographical coordinates. 2109 2110 // @property code: String 2111 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 2112 // 2113 // @property wrapLng: Number[] 2114 // An array of two numbers defining whether the longitude (horizontal) coordinate 2115 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 2116 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 2117 // 2118 // @property wrapLat: Number[] 2119 // Like `wrapLng`, but for the latitude (vertical) axis. 2120 2121 // wrapLng: [min, max], 2122 // wrapLat: [min, max], 2123 2124 // @property infinite: Boolean 2125 // If true, the coordinate space will be unbounded (infinite in both axes) 2126 infinite: false, 2127 2128 // @method wrapLatLng(latlng: LatLng): LatLng 2129 // Returns a `LatLng` where lat and lng has been wrapped according to the 2130 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 2131 wrapLatLng: function (latlng) { 2132 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 2133 lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 2134 alt = latlng.alt; 2135 2136 return L.latLng(lat, lng, alt); 2137 } 2138}; 2139 2140 2141 2142/* 2143 * @namespace CRS 2144 * @crs L.CRS.Simple 2145 * 2146 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 2147 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 2148 * axis should still be inverted (going from bottom to top). `distance()` returns 2149 * simple euclidean distance. 2150 */ 2151 2152L.CRS.Simple = L.extend({}, L.CRS, { 2153 projection: L.Projection.LonLat, 2154 transformation: new L.Transformation(1, 0, -1, 0), 2155 2156 scale: function (zoom) { 2157 return Math.pow(2, zoom); 2158 }, 2159 2160 zoom: function (scale) { 2161 return Math.log(scale) / Math.LN2; 2162 }, 2163 2164 distance: function (latlng1, latlng2) { 2165 var dx = latlng2.lng - latlng1.lng, 2166 dy = latlng2.lat - latlng1.lat; 2167 2168 return Math.sqrt(dx * dx + dy * dy); 2169 }, 2170 2171 infinite: true 2172}); 2173 2174 2175 2176/* 2177 * @namespace CRS 2178 * @crs L.CRS.Earth 2179 * 2180 * Serves as the base for CRS that are global such that they cover the earth. 2181 * Can only be used as the base for other CRS and cannot be used directly, 2182 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 2183 * meters. 2184 */ 2185 2186L.CRS.Earth = L.extend({}, L.CRS, { 2187 wrapLng: [-180, 180], 2188
2189 // Mean Earth Radius, as recommended for use by 2190 // the International Union of Geodesy and Geophysics, 2191 // see http://rosettacode.org/wiki/Haversine_formula 2192 R: 6371000, 2193 2194 // distance between two geographical points using spherical law of cosines approximation 2195 distance: function (latlng1, latlng2) { 2196 var rad = Math.PI / 180, 2197 lat1 = latlng1.lat * rad, 2198 lat2 = latlng2.lat * rad, 2199 a = Math.sin(lat1) * Math.sin(lat2) + 2200 Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad); 2201 2202 return this.R * Math.acos(Math.min(a, 1)); 2203 } 2204}); 2205 2206 2207 2208/* 2209 * @namespace CRS 2210 * @crs L.CRS.EPSG3857 2211 * 2212 * The most common CRS for online maps, used by almost all free and commercial 2213 * tile providers. Uses Spherical Mercator projection. Set in by default in 2214 * Map's `crs` option. 2215 */ 2216 2217L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, { 2218 code: 'EPSG:3857', 2219 projection: L.Projection.SphericalMercator, 2220 2221 transformation: (function () { 2222 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R); 2223 return new L.Transformation(scale, 0.5, -scale, 0.5); 2224 }()) 2225}); 2226 2227L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { 2228 code: 'EPSG:900913' 2229}); 2230 2231 2232 2233/* 2234 * @namespace CRS 2235 * @crs L.CRS.EPSG4326 2236 * 2237 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 2238 */ 2239 2240L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, { 2241 code: 'EPSG:4326', 2242 projection: L.Projection.LonLat, 2243 transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5) 2244}); 2245 2246 2247 2248/* 2249 * @class Map 2250 * @aka L.Map 2251 * @inherits Evented 2252 * 2253 * The central class of the API ââ¬â it is used to create a map on a page and manipulate it. 2254 * 2255 * @example 2256 * 2257 * ```js 2258 * // initialize the map on the "map" div with a given center and zoom 2259 * var map = L.map('map', { 2260 * center: [51.505, -0.09], 2261 * zoom: 13 2262 * }); 2263 * ``` 2264 * 2265 */ 2266 2267L.Map = L.Evented.extend({ 2268 2269 options: { 2270 // @section Map State Options 2271 // @option crs: CRS = L.CRS.EPSG3857 2272 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 2273 // sure what it means. 2274 crs: L.CRS.EPSG3857, 2275 2276 // @option center: LatLng = undefined 2277 // Initial geographic center of the map 2278 center: undefined, 2279 2280 // @option zoom: Number = undefined 2281 // Initial map zoom level 2282 zoom: undefined, 2283 2284 // @option minZoom: Number = undefined 2285 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers. 2286 minZoom: undefined, 2287 2288 // @option maxZoom: Number = undefined 2289 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers. 2290 maxZoom: undefined, 2291 2292 // @option layers: Layer[] = [] 2293 // Array of layers that will be added to the map initially 2294 layers: [], 2295 2296 // @option maxBounds: LatLngBounds = null 2297 // When this option is set, the map restricts the view to the given 2298 // geographical bounds, bouncing the user back when he tries to pan 2299 // outside the view. To set the restriction dynamically, use 2300 // [`setMaxBounds`](#map-setmaxbounds) method. 2301 maxBounds: undefined, 2302 2303 // @option renderer: Renderer = * 2304 // The default method for drawing vector layers on the map. `L.SVG` 2305 // or `L.Canvas` by default depending on browser support. 2306 renderer: undefined, 2307 2308 2309 // @section Animation Options 2310 // @option fadeAnimation: Boolean = true 2311 // Whether the tile fade animation is enabled. By default it's enabled 2312 // in all browsers that support CSS3 Transitions except Android. 2313 fadeAnimation: true, 2314 2315 // @option markerZoomAnimation: Boolean = true 2316 // Whether markers animate their zoom with the zoom animation, if disabled 2317 // they will disappear for the length of the animation. By default it's 2318 // enabled in all browsers that support CSS3 Transitions except Android. 2319 markerZoomAnimation: true, 2320 2321 // @option transform3DLimit: Number = 2^23 2322 // Defines the maximum size of a CSS translation transform. The default 2323 // value should not be changed unless a web browser positions layers in 2324 // the wrong place after doing a large `panBy`. 2325 transform3DLimit: 8388608, // Precision limit of a 32-bit float 2326 2327 // @section Interaction Options 2328 // @option zoomSnap: Number = 1 2329 // Forces the map's zoom level to always be a multiple of this, particularly 2330 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 2331 // By default, the zoom level snaps to the nearest integer; lower values 2332 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 2333 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 2334 zoomSnap: 1, 2335 2336 // @option zoomDelta: Number = 1 2337 // Controls how much the map's zoom level will change after a 2338 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 2339 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 2340 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 2341 zoomDelta: 1, 2342 2343 // @option trackResize: Boolean = true 2344 // Whether the map automatically handles browser window resize to update itself. 2345 trackResize: true 2346 }, 2347 2348 initialize: function (id, options) { // (HTMLElement or String, Object) 2349 options = L.setOptions(this, options); 2350 2351 this._initContainer(id); 2352 this._initLayout(); 2353 2354 // hack for https://github.com/Leaflet/Leaflet/issues/1980 2355 this._onResize = L.bind(this._onResize, this); 2356 2357 this._initEvents(); 2358 2359 if (options.maxBounds) { 2360 this.setMaxBounds(options.maxBounds); 2361 } 2362 2363 if (options.zoom !== undefined) { 2364 this._zoom = this._limitZoom(options.zoom); 2365 } 2366 2367 if (options.center && options.zoom !== undefined) { 2368 this.setView(L.latLng(options.center), options.zoom, {reset: true}); 2369 } 2370 2371 this._handlers = []; 2372 this._layers = {}; 2373 this._zoomBoundLayers = {}; 2374 this._sizeChanged = true; 2375 2376 this.callInitHooks(); 2377 2378 this._addLayers(this.options.layers); 2379 }, 2380 2381 2382 // @section Methods for modifying map state 2383 2384 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2385 // Sets the view of the map (geographical center and zoom) with the given 2386 // animation options. 2387 setView: function (center, zoom) { 2388 // replaced by animation-powered implementation in Map.PanAnimation.js 2389 zoom = zoom === undefined ? this.getZoom() : zoom; 2390 this._resetView(L.latLng(center), zoom); 2391 return this; 2392 }, 2393 2394 // @method setZoom(zoom: Number, options: Zoom/pan options): this 2395 // Sets the zoom of the map. 2396 setZoom: function (zoom, options) { 2397 if (!this._loaded) { 2398 this._zoom = zoom; 2399 return this; 2400 } 2401 return this.setView(this.getCenter(), zoom, {zoom: options}); 2402 }, 2403 2404 // @method zoomIn(delta?: Number, options?: Zoom options): this 2405 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 2406 zoomIn: function (delta, options) { 2407 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1); 2408 return this.setZoom(this._zoom + delta, options); 2409 }, 2410 2411 // @method zoomOut(delta?: Number, options?: Zoom options): this 2412 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 2413 zoomOut: function (delta, options) { 2414 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1); 2415 return this.setZoom(this._zoom - delta, options); 2416 }, 2417 2418 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 2419 // Zooms the map while keeping a specified geographical point on the map 2420 // stationary (e.g. used internally for scroll zoom and double-click zoom). 2421 // @alternative 2422 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 2423 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 2424 setZoomAround: function (latlng, zoom, options) { 2425 var scale = this.getZoomScale(zoom), 2426 viewHalf = this.getSize().divideBy(2), 2427 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), 2428 2429 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 2430 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 2431 2432 return this.setView(newCenter, zoom, {zoom: options}); 2433 }, 2434 2435 _getBoundsCenterZoom: function (bounds, options) { 2436 2437 options = options || {}; 2438 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); 2439 2440 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]), 2441 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), 2442 2443 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 2444 2445 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 2446 2447 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 2448
2449 swPoint = this.project(bounds.getSouthWest(), zoom), 2450 nePoint = this.project(bounds.getNorthEast(), zoom), 2451 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 2452 2453 return { 2454 center: center, 2455 zoom: zoom 2456 }; 2457 }, 2458 2459 // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this 2460 // Sets a map view that contains the given geographical bounds with the 2461 // maximum zoom level possible. 2462 fitBounds: function (bounds, options) { 2463 2464 bounds = L.latLngBounds(bounds); 2465 2466 if (!bounds.isValid()) { 2467 throw new Error('Bounds are not valid.'); 2468 } 2469 2470 var target = this._getBoundsCenterZoom(bounds, options); 2471 return this.setView(target.center, target.zoom, options); 2472 }, 2473 2474 // @method fitWorld(options?: fitBounds options): this 2475 // Sets a map view that mostly contains the whole world with the maximum 2476 // zoom level possible. 2477 fitWorld: function (options) { 2478 return this.fitBounds([[-90, -180], [90, 180]], options); 2479 }, 2480 2481 // @method panTo(latlng: LatLng, options?: Pan options): this 2482 // Pans the map to a given center. 2483 panTo: function (center, options) { // (LatLng) 2484 return this.setView(center, this._zoom, {pan: options}); 2485 }, 2486 2487 // @method panBy(offset: Point): this 2488 // Pans the map by a given number of pixels (animated). 2489 panBy: function (offset) { // (Point) 2490 // replaced with animated panBy in Map.PanAnimation.js 2491 this.fire('movestart'); 2492 2493 this._rawPanBy(L.point(offset)); 2494 2495 this.fire('move'); 2496 return this.fire('moveend'); 2497 }, 2498 2499 // @method setMaxBounds(bounds: Bounds): this 2500 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 2501 setMaxBounds: function (bounds) { 2502 bounds = L.latLngBounds(bounds); 2503 2504 if (!bounds.isValid()) { 2505 this.options.maxBounds = null; 2506 return this.off('moveend', this._panInsideMaxBounds); 2507 } else if (this.options.maxBounds) { 2508 this.off('moveend', this._panInsideMaxBounds); 2509 } 2510 2511 this.options.maxBounds = bounds; 2512 2513 if (this._loaded) { 2514 this._panInsideMaxBounds(); 2515 } 2516 2517 return this.on('moveend', this._panInsideMaxBounds); 2518 }, 2519 2520 // @method setMinZoom(zoom: Number): this 2521 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 2522 setMinZoom: function (zoom) { 2523 this.options.minZoom = zoom; 2524 2525 if (this._loaded && this.getZoom() < this.options.minZoom) { 2526 return this.setZoom(zoom); 2527 } 2528 2529 return this; 2530 }, 2531 2532 // @method setMaxZoom(zoom: Number): this 2533 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 2534 setMaxZoom: function (zoom) { 2535 this.options.maxZoom = zoom; 2536 2537 if (this._loaded && (this.getZoom() > this.options.maxZoom)) { 2538 return this.setZoom(zoom); 2539 } 2540 2541 return this; 2542 }, 2543 2544 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 2545 // 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. 2546 panInsideBounds: function (bounds, options) { 2547 this._enforcingBounds = true; 2548 var center = this.getCenter(), 2549 newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds)); 2550 2551 if (!center.equals(newCenter)) { 2552 this.panTo(newCenter, options); 2553 } 2554 2555 this._enforcingBounds = false; 2556 return this; 2557 }, 2558 2559 // @method invalidateSize(options: Zoom/Pan options): this 2560 // Checks if the map container size changed and updates the map if so ââ¬â 2561 // call it after you've changed the map size dynamically, also animating 2562 // pan by default. If `options.pan` is `false`, panning will not occur. 2563 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 2564 // that it doesn't happen often even if the method is called many 2565 // times in a row. 2566 2567 // @alternative 2568 // @method invalidateSize(animate: Boolean): this 2569 // Checks if the map container size changed and updates the map if so ââ¬â 2570 // call it after you've changed the map size dynamically, also animating 2571 // pan by default. 2572 invalidateSize: function (options) { 2573 if (!this._loaded) { return this; } 2574 2575 options = L.extend({ 2576 animate: false, 2577 pan: true 2578 }, options === true ? {animate: true} : options); 2579
2580 var oldSize = this.getSize(); 2581 this._sizeChanged = true; 2582 this._lastCenter = null; 2583 2584 var newSize = this.getSize(), 2585 oldCenter = oldSize.divideBy(2).round(), 2586 newCenter = newSize.divideBy(2).round(), 2587 offset = oldCenter.subtract(newCenter); 2588 2589 if (!offset.x && !offset.y) { return this; } 2590 2591 if (options.animate && options.pan) { 2592 this.panBy(offset); 2593 2594 } else { 2595 if (options.pan) { 2596 this._rawPanBy(offset); 2597 } 2598 2599 this.fire('move'); 2600 2601 if (options.debounceMoveend) { 2602 clearTimeout(this._sizeTimer); 2603 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); 2604 } else { 2605 this.fire('moveend'); 2606 } 2607 } 2608 2609 // @section Map state change events 2610 // @event resize: ResizeEvent 2611 // Fired when the map is resized. 2612 return this.fire('resize', { 2613 oldSize: oldSize, 2614 newSize: newSize 2615 }); 2616 }, 2617 2618 // @section Methods for modifying map state 2619 // @method stop(): this 2620 // Stops the currently running `panTo` or `flyTo` animation, if any. 2621 stop: function () { 2622 this.setZoom(this._limitZoom(this._zoom)); 2623 if (!this.options.zoomSnap) { 2624 this.fire('viewreset'); 2625 } 2626 return this._stop(); 2627 }, 2628 2629 2630 // TODO handler.addTo 2631 // TODO Appropiate docs section? 2632 // @section Other Methods 2633 // @method addHandler(name: String, HandlerClass: Function): this 2634 // Adds a new `Handler` to the map, given its name and constructor function. 2635 addHandler: function (name, HandlerClass) { 2636 if (!HandlerClass) { return this; } 2637 2638 var handler = this[name] = new HandlerClass(this); 2639 2640 this._handlers.push(handler); 2641 2642 if (this.options[name]) { 2643 handler.enable(); 2644 } 2645 2646 return this; 2647 }, 2648 2649 // @method remove(): this 2650 // Destroys the map and clears all related event listeners. 2651 remove: function () { 2652 2653 this._initEvents(true); 2654 2655 if (this._containerId !== this._container._leaflet_id) { 2656 throw new Error('Map container is being reused by another instance'); 2657 } 2658 2659 try { 2660 // throws error in IE6-8 2661 delete this._container._leaflet_id; 2662 delete this._containerId; 2663 } catch (e) { 2664 /*eslint-disable */ 2665 this._container._leaflet_id = undefined; 2666 /*eslint-enable */ 2667 this._containerId = undefined; 2668 } 2669 2670 L.DomUtil.remove(this._mapPane); 2671 2672 if (this._clearControlPos) { 2673 this._clearControlPos(); 2674 } 2675 2676 this._clearHandlers(); 2677 2678 if (this._loaded) { 2679 // @section Map state change events 2680 // @event unload: Event 2681 // Fired when the map is destroyed with [remove](#map-remove) method. 2682 this.fire('unload'); 2683 } 2684 2685 for (var i in this._layers) { 2686 this._layers[i].remove(); 2687 } 2688 2689 return this; 2690 }, 2691 2692 // @section Other Methods 2693 // @method createPane(name: String, container?: HTMLElement): HTMLElement 2694 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 2695 // then returns it. The pane is created as a children of `container`, or 2696 // as a children of the main map pane if not set. 2697 createPane: function (name, container) { 2698 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 2699 pane = L.DomUtil.create('div', className, container || this._mapPane); 2700 2701 if (name) { 2702 this._panes[name] = pane; 2703 } 2704 return pane; 2705 }, 2706 2707 // @section Methods for Getting Map State 2708 2709 // @method getCenter(): LatLng 2710 // Returns the geographical center of the map view 2711 getCenter: function () { 2712 this._checkIfLoaded(); 2713 2714 if (this._lastCenter && !this._moved()) { 2715 return this._lastCenter; 2716 } 2717 return this.layerPointToLatLng(this._getCenterLayerPoint()); 2718 }, 2719 2720 // @method getZoom(): Number 2721 // Returns the current zoom level of the map view 2722 getZoom: function () { 2723 return this._zoom; 2724 }, 2725 2726 // @method getBounds(): LatLngBounds 2727 // Returns the geographical bounds visible in the current map view 2728 getBounds: function () { 2729 var bounds = this.getPixelBounds(), 2730 sw = this.unproject(bounds.getBottomLeft()), 2731 ne = this.unproject(bounds.getTopRight()); 2732 2733 return new L.LatLngBounds(sw, ne); 2734 }, 2735 2736 // @method getMinZoom(): Number 2737 // 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. 2738 getMinZoom: function () { 2739 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 2740 }, 2741 2742 // @method getMaxZoom(): Number 2743 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 2744 getMaxZoom: function () { 2745 return this.options.maxZoom === undefined ?
2746 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 2747 this.options.maxZoom; 2748 }, 2749 2750 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number 2751 // Returns the maximum zoom level on which the given bounds fit to the map 2752 // view in its entirety. If `inside` (optional) is set to `true`, the method 2753 // instead returns the minimum zoom level on which the map view fits into 2754 // the given bounds in its entirety. 2755 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 2756 bounds = L.latLngBounds(bounds); 2757 padding = L.point(padding || [0, 0]); 2758 2759 var zoom = this.getZoom() || 0, 2760 min = this.getMinZoom(), 2761 max = this.getMaxZoom(), 2762 nw = bounds.getNorthWest(), 2763 se = bounds.getSouthEast(), 2764 size = this.getSize().subtract(padding), 2765 boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)), 2766 snap = L.Browser.any3d ? this.options.zoomSnap : 1; 2767 2768 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y); 2769 zoom = this.getScaleZoom(scale, zoom); 2770 2771 if (snap) { 2772 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 2773 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 2774 } 2775 2776 return Math.max(min, Math.min(max, zoom)); 2777 }, 2778 2779 // @method getSize(): Point 2780 // Returns the current size of the map container (in pixels). 2781 getSize: function () { 2782 if (!this._size || this._sizeChanged) { 2783 this._size = new L.Point( 2784 this._container.clientWidth, 2785 this._container.clientHeight); 2786 2787 this._sizeChanged = false; 2788 } 2789 return this._size.clone(); 2790 }, 2791 2792 // @method getPixelBounds(): Bounds 2793 // Returns the bounds of the current map view in projected pixel 2794 // coordinates (sometimes useful in layer and overlay implementations). 2795 getPixelBounds: function (center, zoom) { 2796 var topLeftPoint = this._getTopLeftPoint(center, zoom); 2797 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 2798 }, 2799 2800 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 2801 // the map pane? "left point of the map layer" can be confusing, specially 2802 // since there can be negative offsets. 2803 // @method getPixelOrigin(): Point 2804 // Returns the projected pixel coordinates of the top left point of 2805 // the map layer (useful in custom layer and overlay implementations). 2806 getPixelOrigin: function () { 2807 this._checkIfLoaded(); 2808 return this._pixelOrigin; 2809 }, 2810 2811 // @method getPixelWorldBounds(zoom?: Number): Bounds 2812 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 2813 // If `zoom` is omitted, the map's current zoom level is used. 2814 getPixelWorldBounds: function (zoom) { 2815 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 2816 }, 2817 2818 // @section Other Methods 2819 2820 // @method getPane(pane: String|HTMLElement): HTMLElement 2821 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 2822 getPane: function (pane) { 2823 return typeof pane === 'string' ? this._panes[pane] : pane; 2824 }, 2825 2826 // @method getPanes(): Object
2827 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 2828 // the panes as values. 2829 getPanes: function () { 2830 return this._panes; 2831 }, 2832 2833 // @method getContainer: HTMLElement 2834 // Returns the HTML element that contains the map. 2835 getContainer: function () { 2836 return this._container; 2837 }, 2838 2839 2840 // @section Conversion Methods 2841 2842 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 2843 // Returns the scale factor to be applied to a map transition from zoom level 2844 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 2845 getZoomScale: function (toZoom, fromZoom) { 2846 // TODO replace with universal implementation after refactoring projections 2847 var crs = this.options.crs; 2848 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 2849 return crs.scale(toZoom) / crs.scale(fromZoom); 2850 }, 2851 2852 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 2853 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 2854 // level and everything is scaled by a factor of `scale`. Inverse of 2855 // [`getZoomScale`](#map-getZoomScale). 2856 getScaleZoom: function (scale, fromZoom) { 2857 var crs = this.options.crs; 2858 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 2859 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 2860 return isNaN(zoom) ? Infinity : zoom; 2861 }, 2862 2863 // @method project(latlng: LatLng, zoom: Number): Point 2864 // Projects a geographical coordinate `LatLng` according to the projection 2865 // of the map's CRS, then scales it according to `zoom` and the CRS's 2866 // `Transformation`. The result is pixel coordinate relative to 2867 // the CRS origin. 2868 project: function (latlng, zoom) { 2869 zoom = zoom === undefined ? this._zoom : zoom; 2870 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); 2871 }, 2872 2873 // @method unproject(point: Point, zoom: Number): LatLng 2874 // Inverse of [`project`](#map-project). 2875 unproject: function (point, zoom) { 2876 zoom = zoom === undefined ? this._zoom : zoom; 2877 return this.options.crs.pointToLatLng(L.point(point), zoom); 2878 }, 2879 2880 // @method layerPointToLatLng(point: Point): LatLng 2881 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 2882 // returns the corresponding geographical coordinate (for the current zoom level). 2883 layerPointToLatLng: function (point) { 2884 var projectedPoint = L.point(point).add(this.getPixelOrigin()); 2885 return this.unproject(projectedPoint); 2886 }, 2887 2888 // @method latLngToLayerPoint(latlng: LatLng): Point 2889 // Given a geographical coordinate, returns the corresponding pixel coordinate 2890 // relative to the [origin pixel](#map-getpixelorigin). 2891 latLngToLayerPoint: function (latlng) { 2892 var projectedPoint = this.project(L.latLng(latlng))._round(); 2893 return projectedPoint._subtract(this.getPixelOrigin()); 2894 }, 2895 2896 // @method wrapLatLng(latlng: LatLng): LatLng 2897 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 2898 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 2899 // CRS's bounds. 2900 // By default this means longitude is wrapped around the dateline so its 2901 // value is between -180 and +180 degrees. 2902 wrapLatLng: function (latlng) { 2903 return this.options.crs.wrapLatLng(L.latLng(latlng)); 2904 }, 2905 2906 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 2907 // Returns the distance between two geographical coordinates according to 2908 // the map's CRS. By default this measures distance in meters. 2909 distance: function (latlng1, latlng2) { 2910 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2)); 2911 }, 2912 2913 // @method containerPointToLayerPoint(point: Point): Point 2914 // Given a pixel coordinate relative to the map container, returns the corresponding 2915 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 2916 containerPointToLayerPoint: function (point) { // (Point) 2917 return L.point(point).subtract(this._getMapPanePos()); 2918 }, 2919 2920 // @method layerPointToContainerPoint(point: Point): Point 2921 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 2922 // returns the corresponding pixel coordinate relative to the map container. 2923 layerPointToContainerPoint: function (point) { // (Point) 2924 return L.point(point).add(this._getMapPanePos()); 2925 }, 2926 2927 // @method containerPointToLatLng(point: Point): Point 2928 // Given a pixel coordinate relative to the map container, returns 2929 // the corresponding geographical coordinate (for the current zoom level). 2930 containerPointToLatLng: function (point) { 2931 var layerPoint = this.containerPointToLayerPoint(L.point(point)); 2932 return this.layerPointToLatLng(layerPoint); 2933 }, 2934 2935 // @method latLngToContainerPoint(latlng: LatLng): Point 2936 // Given a geographical coordinate, returns the corresponding pixel coordinate 2937 // relative to the map container. 2938 latLngToContainerPoint: function (latlng) { 2939 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); 2940 }, 2941 2942 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 2943 // Given a MouseEvent object, returns the pixel coordinate relative to the 2944 // map container where the event took place. 2945 mouseEventToContainerPoint: function (e) { 2946 return L.DomEvent.getMousePosition(e, this._container); 2947 }, 2948 2949 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 2950 // Given a MouseEvent object, returns the pixel coordinate relative to 2951 // the [origin pixel](#map-getpixelorigin) where the event took place. 2952 mouseEventToLayerPoint: function (e) { 2953 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 2954 }, 2955 2956 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 2957 // Given a MouseEvent object, returns geographical coordinate where the 2958 // event took place. 2959 mouseEventToLatLng: function (e) { // (MouseEvent) 2960 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 2961 }, 2962 2963 2964 // map initialization methods 2965 2966 _initContainer: function (id) { 2967 var container = this._container = L.DomUtil.get(id); 2968 2969 if (!container) { 2970 throw new Error('Map container not found.'); 2971 } else if (container._leaflet_id) { 2972 throw new Error('Map container is already initialized.'); 2973 } 2974 2975 L.DomEvent.addListener(container, 'scroll', this._onScroll, this); 2976 this._containerId = L.Util.stamp(container); 2977 }, 2978 2979 _initLayout: function () { 2980 var container = this._container; 2981 2982 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d; 2983 2984 L.DomUtil.addClass(container, 'leaflet-container' + 2985 (L.Browser.touch ? ' leaflet-touch' : '') + 2986 (L.Browser.retina ? ' leaflet-retina' : '') + 2987 (L.Browser.ielt9 ? ' leaflet-oldie' : '') + 2988 (L.Browser.safari ? ' leaflet-safari' : '') + 2989 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 2990 2991 var position = L.DomUtil.getStyle(container, 'position'); 2992 2993 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { 2994 container.style.position = 'relative'; 2995 } 2996 2997 this._initPanes(); 2998 2999 if (this._initControlPos) { 3000 this._initControlPos(); 3001 } 3002 }, 3003 3004 _initPanes: function () { 3005 var panes = this._panes = {}; 3006 this._paneRenderers = {}; 3007 3008 // @section 3009 // 3010 // Panes are DOM elements used to control the ordering of layers on the map. You 3011 // can access panes with [`map.getPane`](#map-getpane) or 3012 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 3013 // [`map.createPane`](#map-createpane) method. 3014 // 3015 // Every map has the following default panes that differ only in zIndex. 3016 // 3017 // @pane mapPane: HTMLElement = 'auto'
3018 // Pane that contains all other map panes 3019 3020 this._mapPane = this.createPane('mapPane', this._container); 3021 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); 3022 3023 // @pane tilePane: HTMLElement = 200 3024 // Pane for `GridLayer`s and `TileLayer`s 3025 this.createPane('tilePane'); 3026 // @pane overlayPane: HTMLElement = 400 3027 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s 3028 this.createPane('shadowPane'); 3029 // @pane shadowPane: HTMLElement = 500 3030 // Pane for overlay shadows (e.g. `Marker` shadows) 3031 this.createPane('overlayPane'); 3032 // @pane markerPane: HTMLElement = 600 3033 // Pane for `Icon`s of `Marker`s 3034 this.createPane('markerPane'); 3035 // @pane tooltipPane: HTMLElement = 650 3036 // Pane for tooltip. 3037 this.createPane('tooltipPane'); 3038 // @pane popupPane: HTMLElement = 700 3039 // Pane for `Popup`s. 3040 this.createPane('popupPane'); 3041 3042 if (!this.options.markerZoomAnimation) { 3043 L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); 3044 L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); 3045 } 3046 }, 3047 3048 3049 // private methods that modify map state 3050 3051 // @section Map state change events 3052 _resetView: function (center, zoom) { 3053 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); 3054 3055 var loading = !this._loaded; 3056 this._loaded = true; 3057 zoom = this._limitZoom(zoom); 3058 3059 this.fire('viewprereset'); 3060 3061 var zoomChanged = this._zoom !== zoom; 3062 this 3063 ._moveStart(zoomChanged) 3064 ._move(center, zoom) 3065 ._moveEnd(zoomChanged); 3066 3067 // @event viewreset: Event 3068 // Fired when the map needs to redraw its content (this usually happens 3069 // on map zoom or load). Very useful for creating custom overlays. 3070 this.fire('viewreset'); 3071 3072 // @event load: Event 3073 // Fired when the map is initialized (when its center and zoom are set 3074 // for the first time). 3075 if (loading) { 3076 this.fire('load'); 3077 } 3078 }, 3079 3080 _moveStart: function (zoomChanged) { 3081 // @event zoomstart: Event 3082 // Fired when the map zoom is about to change (e.g. before zoom animation). 3083 // @event movestart: Event 3084 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 3085 if (zoomChanged) { 3086 this.fire('zoomstart'); 3087 } 3088 return this.fire('movestart'); 3089 }, 3090 3091 _move: function (center, zoom, data) { 3092 if (zoom === undefined) { 3093 zoom = this._zoom; 3094 } 3095 var zoomChanged = this._zoom !== zoom; 3096 3097 this._zoom = zoom; 3098 this._lastCenter = center; 3099 this._pixelOrigin = this._getNewPixelOrigin(center); 3100 3101 // @event zoom: Event 3102 // Fired repeatedly during any change in zoom level, including zoom 3103 // and fly animations. 3104 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 3105 this.fire('zoom', data); 3106 } 3107 3108 // @event move: Event 3109 // Fired repeatedly during any movement of the map, including pan and 3110 // fly animations. 3111 return this.fire('move', data); 3112 }, 3113 3114 _moveEnd: function (zoomChanged) { 3115 // @event zoomend: Event 3116 // Fired when the map has changed, after any animations. 3117 if (zoomChanged) { 3118 this.fire('zoomend'); 3119 } 3120 3121 // @event moveend: Event 3122 // Fired when the center of the map stops changing (e.g. user stopped 3123 // dragging the map). 3124 return this.fire('moveend'); 3125 }, 3126 3127 _stop: function () { 3128 L.Util.cancelAnimFrame(this._flyToFrame); 3129 if (this._panAnim) { 3130 this._panAnim.stop(); 3131 } 3132 return this; 3133 }, 3134 3135 _rawPanBy: function (offset) { 3136 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 3137 }, 3138 3139 _getZoomSpan: function () { 3140 return this.getMaxZoom() - this.getMinZoom(); 3141 }, 3142 3143 _panInsideMaxBounds: function () { 3144 if (!this._enforcingBounds) { 3145 this.panInsideBounds(this.options.maxBounds); 3146 } 3147 }, 3148 3149 _checkIfLoaded: function () { 3150 if (!this._loaded) { 3151 throw new Error('Set map center and zoom first.'); 3152 } 3153 }, 3154 3155 // DOM event handling 3156 3157 // @section Interaction events 3158 _initEvents: function (remove) { 3159 if (!L.DomEvent) { return; } 3160 3161 this._targets = {}; 3162 this._targets[L.stamp(this._container)] = this; 3163 3164 var onOff = remove ? 'off' : 'on'; 3165 3166 // @event click: MouseEvent 3167 // Fired when the user clicks (or taps) the map. 3168 // @event dblclick: MouseEvent 3169 // Fired when the user double-clicks (or double-taps) the map. 3170 // @event mousedown: MouseEvent 3171 // Fired when the user pushes the mouse button on the map. 3172 // @event mouseup: MouseEvent 3173 // Fired when the user releases the mouse button on the map. 3174 // @event mouseover: MouseEvent 3175 // Fired when the mouse enters the map. 3176 // @event mouseout: MouseEvent 3177 // Fired when the mouse leaves the map. 3178 // @event mousemove: MouseEvent 3179 // Fired while the mouse moves over the map. 3180 // @event contextmenu: MouseEvent 3181 // Fired when the user pushes the right mouse button on the map, prevents 3182 // default browser context menu from showing if there are listeners on
3183 // this event. Also fired on mobile when the user holds a single touch 3184 // for a second (also called long press). 3185 // @event keypress: KeyboardEvent 3186 // Fired when the user presses a key from the keyboard while the map is focused. 3187 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' + 3188 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); 3189 3190 if (this.options.trackResize) { 3191 L.DomEvent[onOff](window, 'resize', this._onResize, this); 3192 } 3193 3194 if (L.Browser.any3d && this.options.transform3DLimit) { 3195 this[onOff]('moveend', this._onMoveEnd); 3196 } 3197 }, 3198 3199 _onResize: function () { 3200 L.Util.cancelAnimFrame(this._resizeRequest); 3201 this._resizeRequest = L.Util.requestAnimFrame( 3202 function () { this.invalidateSize({debounceMoveend: true}); }, this); 3203 }, 3204 3205 _onScroll: function () { 3206 this._container.scrollTop = 0; 3207 this._container.scrollLeft = 0; 3208 }, 3209 3210 _onMoveEnd: function () { 3211 var pos = this._getMapPanePos(); 3212 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 3213 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 3214 // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ 3215 this._resetView(this.getCenter(), this.getZoom()); 3216 } 3217 }, 3218 3219 _findEventTargets: function (e, type) { 3220 var targets = [], 3221 target, 3222 isHover = type === 'mouseout' || type === 'mouseover', 3223 src = e.target || e.srcElement, 3224 dragging = false; 3225 3226 while (src) { 3227 target = this._targets[L.stamp(src)]; 3228 if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { 3229 // Prevent firing click after you just dragged an object. 3230 dragging = true; 3231 break; 3232 } 3233 if (target && target.listens(type, true)) { 3234 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; } 3235 targets.push(target); 3236 if (isHover) { break; } 3237 } 3238 if (src === this._container) { break; } 3239 src = src.parentNode; 3240 } 3241 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) { 3242 targets = [this]; 3243 } 3244 return targets; 3245 }, 3246 3247 _handleDOMEvent: function (e) { 3248 if (!this._loaded || L.DomEvent._skipped(e)) { return; } 3249 3250 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type; 3251 3252 if (type === 'mousedown') { 3253 // prevents outline when clicking on keyboard-focusable element 3254 L.DomUtil.preventOutline(e.target || e.srcElement); 3255 } 3256 3257 this._fireDOMEvent(e, type); 3258 }, 3259 3260 _fireDOMEvent: function (e, type, targets) { 3261 3262 if (e.type === 'click') { 3263 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 3264 // @event preclick: MouseEvent 3265 // Fired before mouse click on the map (sometimes useful when you 3266 // want something to happen on click before any existing click 3267 // handlers start running). 3268 var synth = L.Util.extend({}, e); 3269 synth.type = 'preclick'; 3270 this._fireDOMEvent(synth, synth.type, targets); 3271 } 3272 3273 if (e._stopped) { return; } 3274 3275 // Find the layer the event is propagating from and its parents. 3276 targets = (targets || []).concat(this._findEventTargets(e, type)); 3277 3278 if (!targets.length) { return; } 3279 3280 var target = targets[0]; 3281 if (type === 'contextmenu' && target.listens(type, true)) { 3282 L.DomEvent.preventDefault(e); 3283 } 3284 3285 var data = { 3286 originalEvent: e 3287 }; 3288 3289 if (e.type !== 'keypress') { 3290 var isMarker = target instanceof L.Marker; 3291 data.containerPoint = isMarker ? 3292 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 3293 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 3294 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 3295 } 3296 3297 for (var i = 0; i < targets.length; i++) { 3298 targets[i].fire(type, data, true); 3299 if (data.originalEvent._stopped || 3300 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; } 3301 } 3302 }, 3303 3304 _draggableMoved: function (obj) { 3305 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 3306 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 3307 }, 3308 3309 _clearHandlers: function () { 3310 for (var i = 0, len = this._handlers.length; i < len; i++) { 3311 this._handlers[i].disable(); 3312 } 3313 }, 3314 3315 // @section Other Methods 3316 3317 // @method whenReady(fn: Function, context?: Object): this 3318 // Runs the given function `fn` when the map gets initialized with 3319 // a view (center and zoom) and at least one layer, or immediately 3320 // if it's already initialized, optionally passing a function context. 3321 whenReady: function (callback, context) { 3322 if (this._loaded) { 3323 callback.call(context || this, {target: this}); 3324 } else { 3325 this.on('load', callback, context); 3326 } 3327 return this; 3328 }, 3329 3330 3331 // private methods for getting map state 3332 3333 _getMapPanePos: function () { 3334 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0); 3335 }, 3336 3337 _moved: function () { 3338 var pos = this._getMapPanePos(); 3339 return pos && !pos.equals([0, 0]); 3340 }, 3341 3342 _getTopLeftPoint: function (center, zoom) { 3343 var pixelOrigin = center && zoom !== undefined ? 3344 this._getNewPixelOrigin(center, zoom) : 3345 this.getPixelOrigin(); 3346 return pixelOrigin.subtract(this._getMapPanePos()); 3347 }, 3348 3349 _getNewPixelOrigin: function (center, zoom) {
3350 var viewHalf = this.getSize()._divideBy(2); 3351 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 3352 }, 3353 3354 _latLngToNewLayerPoint: function (latlng, zoom, center) { 3355 var topLeft = this._getNewPixelOrigin(center, zoom); 3356 return this.project(latlng, zoom)._subtract(topLeft); 3357 }, 3358 3359 // layer point of the current center 3360 _getCenterLayerPoint: function () { 3361 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 3362 }, 3363 3364 // offset of the specified place to the current center in pixels 3365 _getCenterOffset: function (latlng) { 3366 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 3367 }, 3368 3369 // adjust center for view to get inside bounds 3370 _limitCenter: function (center, zoom, bounds) { 3371 3372 if (!bounds) { return center; } 3373 3374 var centerPoint = this.project(center, zoom), 3375 viewHalf = this.getSize().divideBy(2), 3376 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 3377 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 3378 3379 // If offset is less than a pixel, ignore. 3380 // This prevents unstable projections from getting into 3381 // an infinite loop of tiny offsets. 3382 if (offset.round().equals([0, 0])) { 3383 return center; 3384 } 3385 3386 return this.unproject(centerPoint.add(offset), zoom); 3387 }, 3388 3389 // adjust offset for view to get inside bounds 3390 _limitOffset: function (offset, bounds) { 3391 if (!bounds) { return offset; } 3392 3393 var viewBounds = this.getPixelBounds(), 3394 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 3395 3396 return offset.add(this._getBoundsOffset(newBounds, bounds)); 3397 }, 3398 3399 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 3400 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 3401 var projectedMaxBounds = L.bounds( 3402 this.project(maxBounds.getNorthEast(), zoom),
3403 this.project(maxBounds.getSouthWest(), zoom) 3404 ), 3405 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 3406 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 3407 3408 dx = this._rebound(minOffset.x, -maxOffset.x), 3409 dy = this._rebound(minOffset.y, -maxOffset.y); 3410 3411 return new L.Point(dx, dy); 3412 }, 3413 3414 _rebound: function (left, right) { 3415 return left + right > 0 ? 3416 Math.round(left - right) / 2 : 3417 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 3418 }, 3419 3420 _limitZoom: function (zoom) { 3421 var min = this.getMinZoom(), 3422 max = this.getMaxZoom(), 3423 snap = L.Browser.any3d ? this.options.zoomSnap : 1; 3424 if (snap) { 3425 zoom = Math.round(zoom / snap) * snap; 3426 } 3427 return Math.max(min, Math.min(max, zoom)); 3428 } 3429}); 3430 3431// @section 3432 3433// @factory L.map(id: String, options?: Map options) 3434// Instantiates a map object given the DOM ID of a `<div>` element 3435// and optionally an object literal with `Map options`. 3436// 3437// @alternative 3438// @factory L.map(el: HTMLElement, options?: Map options) 3439// Instantiates a map object given an instance of a `<div>` HTML element 3440// and optionally an object literal with `Map options`. 3441L.map = function (id, options) { 3442 return new L.Map(id, options); 3443}; 3444 3445 3446 3447 3448/* 3449 * @class Layer 3450 * @inherits Evented 3451 * @aka L.Layer 3452 * @aka ILayer 3453 * 3454 * A set of methods from the Layer base class that all Leaflet layers use. 3455 * Inherits all methods, options and events from `L.Evented`. 3456 * 3457 * @example 3458 * 3459 * ```js 3460 * var layer = L.Marker(latlng).addTo(map); 3461 * layer.addTo(map); 3462 * layer.remove(); 3463 * ``` 3464 * 3465 * @event add: Event 3466 * Fired after the layer is added to a map 3467 * 3468 * @event remove: Event 3469 * Fired after the layer is removed from a map 3470 */ 3471 3472 3473L.Layer = L.Evented.extend({ 3474 3475 // Classes extending `L.Layer` will inherit the following options: 3476 options: { 3477 // @option pane: String = 'overlayPane' 3478 // 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. 3479 pane: 'overlayPane', 3480 nonBubblingEvents: [] // Array of events that should not be bubbled to DOM parents (like the map) 3481 }, 3482 3483 /* @section 3484 * Classes extending `L.Layer` will inherit the following methods: 3485 * 3486 * @method addTo(map: Map): this 3487 * Adds the layer to the given map 3488 */ 3489 addTo: function (map) { 3490 map.addLayer(this); 3491 return this; 3492 }, 3493 3494 // @method remove: this 3495 // Removes the layer from the map it is currently active on. 3496 remove: function () { 3497 return this.removeFrom(this._map || this._mapToAdd); 3498 }, 3499 3500 // @method removeFrom(map: Map): this 3501 // Removes the layer from the given map 3502 removeFrom: function (obj) { 3503 if (obj) { 3504 obj.removeLayer(this); 3505 } 3506 return this; 3507 }, 3508 3509 // @method getPane(name? : String): HTMLElement 3510 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 3511 getPane: function (name) { 3512 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 3513 }, 3514 3515 addInteractiveTarget: function (targetEl) { 3516 this._map._targets[L.stamp(targetEl)] = this; 3517 return this; 3518 }, 3519 3520 removeInteractiveTarget: function (targetEl) { 3521 delete this._map._targets[L.stamp(targetEl)]; 3522 return this; 3523 }, 3524 3525 _layerAdd: function (e) { 3526 var map = e.target; 3527 3528 // check in case layer gets added and then removed before the map is ready 3529 if (!map.hasLayer(this)) { return; } 3530 3531 this._map = map; 3532 this._zoomAnimated = map._zoomAnimated; 3533 3534 if (this.getEvents) { 3535 var events = this.getEvents(); 3536 map.on(events, this); 3537 this.once('remove', function () { 3538 map.off(events, this); 3539 }, this); 3540 } 3541 3542 this.onAdd(map); 3543 3544 if (this.getAttribution && this._map.attributionControl) { 3545 this._map.attributionControl.addAttribution(this.getAttribution()); 3546 } 3547 3548 this.fire('add'); 3549 map.fire('layeradd', {layer: this}); 3550 } 3551}); 3552 3553/* @section Extension methods 3554 * @uninheritable 3555 * 3556 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 3557 * 3558 * @method onAdd(map: Map): this 3559 * 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). 3560 * 3561 * @method onRemove(map: Map): this 3562 * 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). 3563 * 3564 * @method getEvents(): Object 3565 * 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. 3566 * 3567 * @method getAttribution(): String 3568 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 3569 * 3570 * @method beforeAdd(map: Map): this 3571 * 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. 3572 */ 3573 3574 3575/* @namespace Map 3576 * @section Layer events 3577 * 3578 * @event layeradd: LayerEvent 3579 * Fired when a new layer is added to the map. 3580 * 3581 * @event layerremove: LayerEvent 3582 * Fired when some layer is removed from the map 3583 * 3584 * @section Methods for Layers and Controls 3585 */ 3586L.Map.include({ 3587 // @method addLayer(layer: Layer): this 3588 // Adds the given layer to the map 3589 addLayer: function (layer) { 3590 var id = L.stamp(layer); 3591 if (this._layers[id]) { return this; } 3592 this._layers[id] = layer; 3593 3594 layer._mapToAdd = this; 3595 3596 if (layer.beforeAdd) { 3597 layer.beforeAdd(this); 3598 } 3599 3600 this.whenReady(layer._layerAdd, layer); 3601 3602 return this; 3603 }, 3604 3605 // @method removeLayer(layer: Layer): this 3606 // Removes the given layer from the map. 3607 removeLayer: function (layer) { 3608 var id = L.stamp(layer); 3609 3610 if (!this._layers[id]) { return this; } 3611 3612 if (this._loaded) { 3613 layer.onRemove(this); 3614 } 3615 3616 if (layer.getAttribution && this.attributionControl) { 3617 this.attributionControl.removeAttribution(layer.getAttribution()); 3618 } 3619 3620 delete this._layers[id]; 3621 3622 if (this._loaded) { 3623 this.fire('layerremove', {layer: layer}); 3624 layer.fire('remove'); 3625 } 3626 3627 layer._map = layer._mapToAdd = null; 3628 3629 return this; 3630 }, 3631 3632 // @method hasLayer(layer: Layer): Boolean 3633 // Returns `true` if the given layer is currently added to the map 3634 hasLayer: function (layer) { 3635 return !!layer && (L.stamp(layer) in this._layers); 3636 }, 3637 3638 /* @method eachLayer(fn: Function, context?: Object): this 3639 * Iterates over the layers of the map, optionally specifying context of the iterator function. 3640 * ``` 3641 * map.eachLayer(function(layer){ 3642 * layer.bindPopup('Hello'); 3643 * }); 3644 * ``` 3645 */ 3646 eachLayer: function (method, context) { 3647 for (var i in this._layers) { 3648 method.call(context, this._layers[i]); 3649 } 3650 return this; 3651 }, 3652 3653 _addLayers: function (layers) { 3654 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; 3655 3656 for (var i = 0, len = layers.length; i < len; i++) { 3657 this.addLayer(layers[i]); 3658 } 3659 }, 3660 3661 _addZoomLimit: function (layer) { 3662 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 3663 this._zoomBoundLayers[L.stamp(layer)] = layer; 3664 this._updateZoomLevels(); 3665 } 3666 }, 3667 3668 _removeZoomLimit: function (layer) { 3669 var id = L.stamp(layer); 3670 3671 if (this._zoomBoundLayers[id]) { 3672 delete this._zoomBoundLayers[id]; 3673 this._updateZoomLevels(); 3674 } 3675 }, 3676 3677 _updateZoomLevels: function () { 3678 var minZoom = Infinity, 3679 maxZoom = -Infinity, 3680 oldZoomSpan = this._getZoomSpan(); 3681 3682 for (var i in this._zoomBoundLayers) { 3683 var options = this._zoomBoundLayers[i].options; 3684
3685 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 3686 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 3687 } 3688 3689 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 3690 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 3691 3692 // @section Map state change events 3693 // @event zoomlevelschange: Event 3694 // Fired when the number of zoomlevels on the map is changed due 3695 // to adding or removing a layer. 3696 if (oldZoomSpan !== this._getZoomSpan()) { 3697 this.fire('zoomlevelschange'); 3698 } 3699 } 3700}); 3701 3702 3703 3704/* 3705 * @namespace Projection 3706 * @projection L.Projection.Mercator 3707 * 3708 * 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. 3709 */ 3710 3711L.Projection.Mercator = { 3712 R: 6378137, 3713 R_MINOR: 6356752.314245179, 3714 3715 bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 3716 3717 project: function (latlng) { 3718 var d = Math.PI / 180, 3719 r = this.R, 3720 y = latlng.lat * d, 3721 tmp = this.R_MINOR / r, 3722 e = Math.sqrt(1 - tmp * tmp), 3723 con = e * Math.sin(y); 3724 3725 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 3726 y = -r * Math.log(Math.max(ts, 1E-10)); 3727 3728 return new L.Point(latlng.lng * d * r, y); 3729 }, 3730 3731 unproject: function (point) { 3732 var d = 180 / Math.PI, 3733 r = this.R, 3734 tmp = this.R_MINOR / r, 3735 e = Math.sqrt(1 - tmp * tmp), 3736 ts = Math.exp(-point.y / r), 3737 phi = Math.PI / 2 - 2 * Math.atan(ts); 3738 3739 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 3740 con = e * Math.sin(phi); 3741 con = Math.pow((1 - con) / (1 + con), e / 2); 3742 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 3743 phi += dphi; 3744 } 3745 3746 return new L.LatLng(phi * d, point.x * d / r); 3747 } 3748}; 3749 3750 3751 3752/* 3753 * @namespace CRS 3754 * @crs L.CRS.EPSG3395 3755 * 3756 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 3757 */ 3758 3759L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, { 3760 code: 'EPSG:3395', 3761 projection: L.Projection.Mercator, 3762 3763 transformation: (function () { 3764 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R); 3765 return new L.Transformation(scale, 0.5, -scale, 0.5); 3766 }()) 3767}); 3768 3769 3770 3771/* 3772 * @class GridLayer 3773 * @inherits Layer 3774 * @aka L.GridLayer 3775 * 3776 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 3777 * 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. 3778 * 3779 * 3780 * @section Synchronous usage 3781 * @example 3782 * 3783 * 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. 3784 * 3785 * ```js 3786 * var CanvasLayer = L.GridLayer.extend({ 3787 * createTile: function(coords){ 3788 * // create a <canvas> element for drawing 3789 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 3790 * 3791 * // setup tile width and height according to the options 3792 * var size = this.getTileSize(); 3793 * tile.width = size.x; 3794 * tile.height = size.y; 3795 * 3796 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 3797 * var ctx = tile.getContext('2d'); 3798 * 3799 * // return the tile so it can be rendered on screen 3800 * return tile; 3801 * } 3802 * }); 3803 * ``` 3804 * 3805 * @section Asynchronous usage 3806 * @example 3807 * 3808 * 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. 3809 * 3810 * ```js 3811 * var CanvasLayer = L.GridLayer.extend({ 3812 * createTile: function(coords, done){ 3813 * var error; 3814 * 3815 * // create a <canvas> element for drawing 3816 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 3817 * 3818 * // setup tile width and height according to the options 3819 * var size = this.getTileSize(); 3820 * tile.width = size.x; 3821 * tile.height = size.y; 3822 * 3823 * // draw something asynchronously and pass the tile to the done() callback 3824 * setTimeout(function() { 3825 * done(error, tile); 3826 * }, 1000); 3827 * 3828 * return tile; 3829 * } 3830 * }); 3831 * ``` 3832 * 3833 * @section 3834 */ 3835 3836 3837L.GridLayer = L.Layer.extend({ 3838 3839 // @section 3840 // @aka GridLayer options 3841 options: {
vendor: 19,086 bytes, lines 3842-4534
3842 // @option tileSize: Number|Point = 256 3843 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 3844 tileSize: 256, 3845 3846 // @option opacity: Number = 1.0 3847 // Opacity of the tiles. Can be used in the `createTile()` function. 3848 opacity: 1, 3849 3850 // @option updateWhenIdle: Boolean = depends 3851 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`. 3852 updateWhenIdle: L.Browser.mobile, 3853 3854 // @option updateWhenZooming: Boolean = true 3855 // 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. 3856 updateWhenZooming: true, 3857 3858 // @option updateInterval: Number = 200 3859 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 3860 updateInterval: 200, 3861 3862 // @option attribution: String = null 3863 // String to be shown in the attribution control, describes the layer data, e.g. "é Mapbox". 3864 attribution: null, 3865 3866 // @option zIndex: Number = 1 3867 // The explicit zIndex of the tile layer. 3868 zIndex: 1, 3869 3870 // @option bounds: LatLngBounds = undefined 3871 // If set, tiles will only be loaded inside the set `LatLngBounds`. 3872 bounds: null, 3873 3874 // @option minZoom: Number = 0 3875 // The minimum zoom level that tiles will be loaded at. By default the entire map. 3876 minZoom: 0, 3877 3878 // @option maxZoom: Number = undefined 3879 // The maximum zoom level that tiles will be loaded at. 3880 maxZoom: undefined, 3881 3882 // @option noWrap: Boolean = false 3883 // Whether the layer is wrapped around the antimeridian. If `true`, the 3884 // GridLayer will only be displayed once at low zoom levels. Has no 3885 // effect when the [map CRS](#map-crs) doesn't wrap around. 3886 noWrap: false, 3887 3888 // @option pane: String = 'tilePane' 3889 // `Map pane` where the grid layer will be added. 3890 pane: 'tilePane', 3891 3892 // @option className: String = '' 3893 // A custom class name to assign to the tile layer. Empty by default. 3894 className: '', 3895 3896 // @option keepBuffer: Number = 2 3897 // When panning the map, keep this many rows and columns of tiles before unloading them. 3898 keepBuffer: 2 3899 }, 3900 3901 initialize: function (options) { 3902 L.setOptions(this, options); 3903 }, 3904 3905 onAdd: function () { 3906 this._initContainer(); 3907 3908 this._levels = {}; 3909 this._tiles = {}; 3910 3911 this._resetView(); 3912 this._update(); 3913 }, 3914 3915 beforeAdd: function (map) { 3916 map._addZoomLimit(this); 3917 }, 3918 3919 onRemove: function (map) { 3920 this._removeAllTiles(); 3921 L.DomUtil.remove(this._container); 3922 map._removeZoomLimit(this); 3923 this._container = null; 3924 this._tileZoom = null; 3925 }, 3926 3927 // @method bringToFront: this 3928 // Brings the tile layer to the top of all tile layers. 3929 bringToFront: function () { 3930 if (this._map) { 3931 L.DomUtil.toFront(this._container); 3932 this._setAutoZIndex(Math.max); 3933 } 3934 return this; 3935 }, 3936 3937 // @method bringToBack: this 3938 // Brings the tile layer to the bottom of all tile layers. 3939 bringToBack: function () { 3940 if (this._map) { 3941 L.DomUtil.toBack(this._container); 3942 this._setAutoZIndex(Math.min); 3943 } 3944 return this; 3945 }, 3946 3947 // @method getAttribution: String 3948 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 3949 getAttribution: function () { 3950 return this.options.attribution; 3951 }, 3952 3953 // @method getContainer: HTMLElement 3954 // Returns the HTML element that contains the tiles for this layer. 3955 getContainer: function () { 3956 return this._container; 3957 }, 3958 3959 // @method setOpacity(opacity: Number): this 3960 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 3961 setOpacity: function (opacity) { 3962 this.options.opacity = opacity; 3963 this._updateOpacity(); 3964 return this; 3965 }, 3966 3967 // @method setZIndex(zIndex: Number): this 3968 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 3969 setZIndex: function (zIndex) { 3970 this.options.zIndex = zIndex; 3971 this._updateZIndex(); 3972 3973 return this; 3974 }, 3975 3976 // @method isLoading: Boolean 3977 // Returns `true` if any tile in the grid layer has not finished loading. 3978 isLoading: function () { 3979 return this._loading; 3980 }, 3981 3982 // @method redraw: this 3983 // Causes the layer to clear all the tiles and request them again. 3984 redraw: function () { 3985 if (this._map) { 3986 this._removeAllTiles(); 3987 this._update(); 3988 } 3989 return this; 3990 }, 3991 3992 getEvents: function () { 3993 var events = { 3994 viewprereset: this._invalidateAll, 3995 viewreset: this._resetView, 3996 zoom: this._resetView, 3997 moveend: this._onMoveEnd 3998 }; 3999 4000 if (!this.options.updateWhenIdle) { 4001 // update tiles on move, but not more often than once per given interval 4002 if (!this._onMove) { 4003 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this); 4004 } 4005 4006 events.move = this._onMove; 4007 } 4008 4009 if (this._zoomAnimated) { 4010 events.zoomanim = this._animateZoom; 4011 } 4012 4013 return events; 4014 }, 4015 4016 // @section Extension methods 4017 // Layers extending `GridLayer` shall reimplement the following method. 4018 // @method createTile(coords: Object, done?: Function): HTMLElement 4019 // Called only internally, must be overriden by classes extending `GridLayer`. 4020 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 4021 // is specified, it must be called when the tile has finished loading and drawing. 4022 createTile: function () { 4023 return document.createElement('div'); 4024 }, 4025 4026 // @section 4027 // @method getTileSize: Point 4028 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 4029 getTileSize: function () { 4030 var s = this.options.tileSize; 4031 return s instanceof L.Point ? s : new L.Point(s, s); 4032 }, 4033 4034 _updateZIndex: function () { 4035 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 4036 this._container.style.zIndex = this.options.zIndex; 4037 } 4038 }, 4039 4040 _setAutoZIndex: function (compare) { 4041 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 4042 4043 var layers = this.getPane().children, 4044 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 4045 4046 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 4047 4048 zIndex = layers[i].style.zIndex; 4049 4050 if (layers[i] !== this._container && zIndex) { 4051 edgeZIndex = compare(edgeZIndex, +zIndex); 4052 } 4053 } 4054 4055 if (isFinite(edgeZIndex)) { 4056 this.options.zIndex = edgeZIndex + compare(-1, 1); 4057 this._updateZIndex(); 4058 } 4059 }, 4060 4061 _updateOpacity: function () { 4062 if (!this._map) { return; } 4063 4064 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 4065 if (L.Browser.ielt9) { return; } 4066 4067 L.DomUtil.setOpacity(this._container, this.options.opacity); 4068 4069 var now = +new Date(), 4070 nextFrame = false, 4071 willPrune = false; 4072 4073 for (var key in this._tiles) { 4074 var tile = this._tiles[key]; 4075 if (!tile.current || !tile.loaded) { continue; } 4076 4077 var fade = Math.min(1, (now - tile.loaded) / 200); 4078 4079 L.DomUtil.setOpacity(tile.el, fade); 4080 if (fade < 1) { 4081 nextFrame = true; 4082 } else { 4083 if (tile.active) { willPrune = true; } 4084 tile.active = true; 4085 } 4086 } 4087 4088 if (willPrune && !this._noPrune) { this._pruneTiles(); } 4089 4090 if (nextFrame) { 4091 L.Util.cancelAnimFrame(this._fadeFrame); 4092 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); 4093 } 4094 }, 4095 4096 _initContainer: function () { 4097 if (this._container) { return; } 4098 4099 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || '')); 4100 this._updateZIndex(); 4101 4102 if (this.options.opacity < 1) { 4103 this._updateOpacity(); 4104 } 4105 4106 this.getPane().appendChild(this._container); 4107 }, 4108 4109 _updateLevels: function () { 4110 4111 var zoom = this._tileZoom, 4112 maxZoom = this.options.maxZoom; 4113 4114 if (zoom === undefined) { return undefined; } 4115 4116 for (var z in this._levels) { 4117 if (this._levels[z].el.children.length || z === zoom) { 4118 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 4119 } else { 4120 L.DomUtil.remove(this._levels[z].el); 4121 this._removeTilesAtZoom(z); 4122 delete this._levels[z]; 4123 } 4124 } 4125 4126 var level = this._levels[zoom], 4127 map = this._map; 4128 4129 if (!level) { 4130 level = this._levels[zoom] = {}; 4131 4132 level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 4133 level.el.style.zIndex = maxZoom; 4134 4135 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 4136 level.zoom = zoom; 4137 4138 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 4139 4140 // force the browser to consider the newly added element for transition 4141 L.Util.falseFn(level.el.offsetWidth); 4142 } 4143 4144 this._level = level; 4145 4146 return level; 4147 }, 4148 4149 _pruneTiles: function () { 4150 if (!this._map) { 4151 return; 4152 } 4153 4154 var key, tile; 4155 4156 var zoom = this._map.getZoom(); 4157 if (zoom > this.options.maxZoom || 4158 zoom < this.options.minZoom) { 4159 this._removeAllTiles(); 4160 return; 4161 } 4162 4163 for (key in this._tiles) { 4164 tile = this._tiles[key]; 4165 tile.retain = tile.current; 4166 } 4167 4168 for (key in this._tiles) { 4169 tile = this._tiles[key]; 4170 if (tile.current && !tile.active) { 4171 var coords = tile.coords; 4172 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 4173 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 4174 } 4175 } 4176 } 4177 4178 for (key in this._tiles) { 4179 if (!this._tiles[key].retain) { 4180 this._removeTile(key); 4181 } 4182 } 4183 }, 4184 4185 _removeTilesAtZoom: function (zoom) { 4186 for (var key in this._tiles) { 4187 if (this._tiles[key].coords.z !== zoom) { 4188 continue; 4189 } 4190 this._removeTile(key); 4191 } 4192 }, 4193 4194 _removeAllTiles: function () { 4195 for (var key in this._tiles) { 4196 this._removeTile(key); 4197 } 4198 }, 4199 4200 _invalidateAll: function () { 4201 for (var z in this._levels) { 4202 L.DomUtil.remove(this._levels[z].el); 4203 delete this._levels[z]; 4204 } 4205 this._removeAllTiles(); 4206 4207 this._tileZoom = null; 4208 }, 4209 4210 _retainParent: function (x, y, z, minZoom) { 4211 var x2 = Math.floor(x / 2), 4212 y2 = Math.floor(y / 2), 4213 z2 = z - 1, 4214 coords2 = new L.Point(+x2, +y2); 4215 coords2.z = +z2; 4216 4217 var key = this._tileCoordsToKey(coords2), 4218 tile = this._tiles[key]; 4219 4220 if (tile && tile.active) { 4221 tile.retain = true; 4222 return true; 4223 4224 } else if (tile && tile.loaded) { 4225 tile.retain = true; 4226 } 4227 4228 if (z2 > minZoom) { 4229 return this._retainParent(x2, y2, z2, minZoom); 4230 } 4231 4232 return false; 4233 }, 4234 4235 _retainChildren: function (x, y, z, maxZoom) { 4236 4237 for (var i = 2 * x; i < 2 * x + 2; i++) { 4238 for (var j = 2 * y; j < 2 * y + 2; j++) { 4239 4240 var coords = new L.Point(i, j); 4241 coords.z = z + 1; 4242 4243 var key = this._tileCoordsToKey(coords), 4244 tile = this._tiles[key]; 4245 4246 if (tile && tile.active) { 4247 tile.retain = true; 4248 continue; 4249 4250 } else if (tile && tile.loaded) { 4251 tile.retain = true; 4252 } 4253 4254 if (z + 1 < maxZoom) { 4255 this._retainChildren(i, j, z + 1, maxZoom); 4256 } 4257 } 4258 } 4259 }, 4260 4261 _resetView: function (e) { 4262 var animating = e && (e.pinch || e.flyTo); 4263 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 4264 }, 4265 4266 _animateZoom: function (e) { 4267 this._setView(e.center, e.zoom, true, e.noUpdate); 4268 }, 4269 4270 _setView: function (center, zoom, noPrune, noUpdate) { 4271 var tileZoom = Math.round(zoom); 4272 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 4273 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 4274 tileZoom = undefined; 4275 } 4276 4277 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 4278 4279 if (!noUpdate || tileZoomChanged) { 4280 4281 this._tileZoom = tileZoom; 4282 4283 if (this._abortLoading) { 4284 this._abortLoading(); 4285 } 4286 4287 this._updateLevels(); 4288 this._resetGrid(); 4289 4290 if (tileZoom !== undefined) { 4291 this._update(center); 4292 } 4293 4294 if (!noPrune) { 4295 this._pruneTiles(); 4296 } 4297 4298 // Flag to prevent _updateOpacity from pruning tiles during 4299 // a zoom anim or a pinch gesture 4300 this._noPrune = !!noPrune; 4301 } 4302 4303 this._setZoomTransforms(center, zoom); 4304 }, 4305 4306 _setZoomTransforms: function (center, zoom) { 4307 for (var i in this._levels) { 4308 this._setZoomTransform(this._levels[i], center, zoom); 4309 } 4310 }, 4311 4312 _setZoomTransform: function (level, center, zoom) { 4313 var scale = this._map.getZoomScale(zoom, level.zoom), 4314 translate = level.origin.multiplyBy(scale) 4315 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 4316 4317 if (L.Browser.any3d) { 4318 L.DomUtil.setTransform(level.el, translate, scale); 4319 } else { 4320 L.DomUtil.setPosition(level.el, translate); 4321 } 4322 }, 4323 4324 _resetGrid: function () { 4325 var map = this._map, 4326 crs = map.options.crs, 4327 tileSize = this._tileSize = this.getTileSize(), 4328 tileZoom = this._tileZoom; 4329 4330 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 4331 if (bounds) { 4332 this._globalTileRange = this._pxBoundsToTileRange(bounds); 4333 } 4334 4335 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 4336 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 4337 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 4338 ]; 4339 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 4340 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 4341 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 4342 ]; 4343 }, 4344 4345 _onMoveEnd: function () { 4346 if (!this._map || this._map._animatingZoom) { return; } 4347 4348 this._update(); 4349 }, 4350 4351 _getTiledPixelBounds: function (center) { 4352 var map = this._map, 4353 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 4354 scale = map.getZoomScale(mapZoom, this._tileZoom), 4355 pixelCenter = map.project(center, this._tileZoom).floor(), 4356 halfSize = map.getSize().divideBy(scale * 2); 4357 4358 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 4359 }, 4360 4361 // Private method to load tiles in the grid's active zoom level according to map bounds 4362 _update: function (center) { 4363 var map = this._map; 4364 if (!map) { return; } 4365 var zoom = map.getZoom(); 4366 4367 if (center === undefined) { center = map.getCenter(); } 4368 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 4369 4370 var pixelBounds = this._getTiledPixelBounds(center), 4371 tileRange = this._pxBoundsToTileRange(pixelBounds), 4372 tileCenter = tileRange.getCenter(), 4373 queue = [], 4374 margin = this.options.keepBuffer, 4375 noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 4376 tileRange.getTopRight().add([margin, -margin])); 4377 4378 for (var key in this._tiles) { 4379 var c = this._tiles[key].coords; 4380 if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) { 4381 this._tiles[key].current = false; 4382 } 4383 } 4384 4385 // _update just loads more tiles. If the tile zoom level differs too much 4386 // from the map's, let _setView reset levels and prune old tiles. 4387 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 4388 4389 // create a queue of coordinates to load tiles from 4390 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 4391 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 4392 var coords = new L.Point(i, j); 4393 coords.z = this._tileZoom; 4394 4395 if (!this._isValidTile(coords)) { continue; } 4396 4397 var tile = this._tiles[this._tileCoordsToKey(coords)]; 4398 if (tile) { 4399 tile.current = true; 4400 } else { 4401 queue.push(coords); 4402 } 4403 } 4404 } 4405 4406 // sort tile queue to load tiles in order of their distance to center 4407 queue.sort(function (a, b) { 4408 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 4409 }); 4410 4411 if (queue.length !== 0) { 4412 // if it's the first batch of tiles to load 4413 if (!this._loading) { 4414 this._loading = true; 4415 // @event loading: Event 4416 // Fired when the grid layer starts loading tiles. 4417 this.fire('loading'); 4418 } 4419 4420 // create DOM fragment to append tiles in one batch 4421 var fragment = document.createDocumentFragment(); 4422 4423 for (i = 0; i < queue.length; i++) { 4424 this._addTile(queue[i], fragment); 4425 } 4426 4427 this._level.el.appendChild(fragment); 4428 } 4429 }, 4430 4431 _isValidTile: function (coords) { 4432 var crs = this._map.options.crs; 4433 4434 if (!crs.infinite) { 4435 // don't load tile if it's out of bounds and not wrapped 4436 var bounds = this._globalTileRange; 4437 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 4438 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 4439 } 4440 4441 if (!this.options.bounds) { return true; } 4442 4443 // don't load tile if it doesn't intersect the bounds in options 4444 var tileBounds = this._tileCoordsToBounds(coords); 4445 return L.latLngBounds(this.options.bounds).overlaps(tileBounds); 4446 }, 4447 4448 _keyToBounds: function (key) { 4449 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 4450 }, 4451 4452 // converts tile coordinates to its geographical bounds 4453 _tileCoordsToBounds: function (coords) { 4454 4455 var map = this._map, 4456 tileSize = this.getTileSize(), 4457 4458 nwPoint = coords.scaleBy(tileSize), 4459 sePoint = nwPoint.add(tileSize), 4460 4461 nw = map.unproject(nwPoint, coords.z), 4462 se = map.unproject(sePoint, coords.z); 4463 4464 if (!this.options.noWrap) { 4465 nw = map.wrapLatLng(nw); 4466 se = map.wrapLatLng(se); 4467 } 4468 4469 return new L.LatLngBounds(nw, se); 4470 }, 4471 4472 // converts tile coordinates to key for the tile cache 4473 _tileCoordsToKey: function (coords) { 4474 return coords.x + ':' + coords.y + ':' + coords.z; 4475 }, 4476 4477 // converts tile cache key to coordinates 4478 _keyToTileCoords: function (key) { 4479 var k = key.split(':'), 4480 coords = new L.Point(+k[0], +k[1]); 4481 coords.z = +k[2]; 4482 return coords; 4483 }, 4484 4485 _removeTile: function (key) { 4486 var tile = this._tiles[key]; 4487 if (!tile) { return; } 4488 4489 L.DomUtil.remove(tile.el); 4490 4491 delete this._tiles[key]; 4492 4493 // @event tileunload: TileEvent 4494 // Fired when a tile is removed (e.g. when a tile goes off the screen). 4495 this.fire('tileunload', { 4496 tile: tile.el, 4497 coords: this._keyToTileCoords(key) 4498 }); 4499 }, 4500 4501 _initTile: function (tile) { 4502 L.DomUtil.addClass(tile, 'leaflet-tile'); 4503 4504 var tileSize = this.getTileSize(); 4505 tile.style.width = tileSize.x + 'px'; 4506 tile.style.height = tileSize.y + 'px'; 4507 4508 tile.onselectstart = L.Util.falseFn; 4509 tile.onmousemove = L.Util.falseFn; 4510 4511 // update opacity on tiles in IE7-8 because of filter inheritance problems 4512 if (L.Browser.ielt9 && this.options.opacity < 1) { 4513 L.DomUtil.setOpacity(tile, this.options.opacity); 4514 } 4515 4516 // without this hack, tiles disappear after zoom on Chrome for Android 4517 // https://github.com/Leaflet/Leaflet/issues/2078 4518 if (L.Browser.android && !L.Browser.android23) { 4519 tile.style.WebkitBackfaceVisibility = 'hidden'; 4520 } 4521 }, 4522 4523 _addTile: function (coords, container) { 4524 var tilePos = this._getTilePos(coords), 4525 key = this._tileCoordsToKey(coords); 4526 4527 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords)); 4528 4529 this._initTile(tile); 4530 4531 // if createTile is defined with a second argument ("done" callback), 4532 // we know that tile is async and will be ready later; otherwise 4533 if (this.createTile.length < 2) { 4534 // mark tile as ready, but delay one frame for opacity animation to happen
4535 L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile)); 4536 } 4537 4538 L.DomUtil.setPosition(tile, tilePos); 4539 4540 // save tile in cache 4541 this._tiles[key] = { 4542 el: tile, 4543 coords: coords, 4544 current: true 4545 }; 4546 4547 container.appendChild(tile); 4548 // @event tileloadstart: TileEvent 4549 // Fired when a tile is requested and starts loading. 4550 this.fire('tileloadstart', { 4551 tile: tile, 4552 coords: coords 4553 }); 4554 }, 4555 4556 _tileReady: function (coords, err, tile) { 4557 if (!this._map) { return; } 4558 4559 if (err) { 4560 // @event tileerror: TileErrorEvent 4561 // Fired when there is an error loading a tile. 4562 this.fire('tileerror', { 4563 error: err, 4564 tile: tile, 4565 coords: coords 4566 }); 4567 } 4568 4569 var key = this._tileCoordsToKey(coords); 4570 4571 tile = this._tiles[key]; 4572 if (!tile) { return; } 4573 4574 tile.loaded = +new Date(); 4575 if (this._map._fadeAnimated) { 4576 L.DomUtil.setOpacity(tile.el, 0); 4577 L.Util.cancelAnimFrame(this._fadeFrame); 4578 this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); 4579 } else { 4580 tile.active = true; 4581 this._pruneTiles(); 4582 } 4583 4584 if (!err) { 4585 L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded'); 4586 4587 // @event tileload: TileEvent 4588 // Fired when a tile loads. 4589 this.fire('tileload', { 4590 tile: tile.el, 4591 coords: coords 4592 }); 4593 } 4594 4595 if (this._noTilesToLoad()) { 4596 this._loading = false; 4597 // @event load: Event 4598 // Fired when the grid layer loaded all visible tiles. 4599 this.fire('load'); 4600 4601 if (L.Browser.ielt9 || !this._map._fadeAnimated) { 4602 L.Util.requestAnimFrame(this._pruneTiles, this); 4603 } else { 4604 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 4605 // to trigger a pruning. 4606 setTimeout(L.bind(this._pruneTiles, this), 250); 4607 } 4608 } 4609 }, 4610 4611 _getTilePos: function (coords) { 4612 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 4613 }, 4614 4615 _wrapCoords: function (coords) { 4616 var newCoords = new L.Point( 4617 this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x, 4618 this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y); 4619 newCoords.z = coords.z; 4620 return newCoords; 4621 }, 4622 4623 _pxBoundsToTileRange: function (bounds) { 4624 var tileSize = this.getTileSize(); 4625 return new L.Bounds( 4626 bounds.min.unscaleBy(tileSize).floor(), 4627 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 4628 }, 4629 4630 _noTilesToLoad: function () { 4631 for (var key in this._tiles) { 4632 if (!this._tiles[key].loaded) { return false; } 4633 } 4634 return true; 4635 } 4636}); 4637 4638// @factory L.gridLayer(options?: GridLayer options) 4639// Creates a new instance of GridLayer with the supplied options. 4640L.gridLayer = function (options) { 4641 return new L.GridLayer(options); 4642}; 4643 4644 4645 4646/* 4647 * @class TileLayer 4648 * @inherits GridLayer 4649 * @aka L.TileLayer 4650 * Used to load and display tile layers on the map. Extends `GridLayer`. 4651 * 4652 * @example 4653 * 4654 * ```js 4655 * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map); 4656 * ``` 4657 * 4658 * @section URL template 4659 * @example 4660 * 4661 * A string of the following form: 4662 * 4663 * ``` 4664 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 4665 * ``` 4666 * 4667 * `{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. 4668 * 4669 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 4670 * 4671 * ``` 4672 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 4673 * ``` 4674 */ 4675 4676 4677L.TileLayer = L.GridLayer.extend({ 4678 4679 // @section 4680 // @aka TileLayer options 4681 options: { 4682 // @option minZoom: Number = 0 4683 // Minimum zoom number. 4684 minZoom: 0, 4685 4686 // @option maxZoom: Number = 18 4687 // Maximum zoom number. 4688 maxZoom: 18, 4689 4690 // @option maxNativeZoom: Number = null 4691 // Maximum zoom number the tile source has available. If it is specified, 4692 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 4693 // from `maxNativeZoom` level and auto-scaled. 4694 maxNativeZoom: null, 4695 4696 // @option subdomains: String|String[] = 'abc' 4697 // 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 str
4697ings. 4698 subdomains: 'abc', 4699 4700 // @option errorTileUrl: String = '' 4701 // URL to the tile image to show in place of the tile that failed to load. 4702 errorTileUrl: '', 4703 4704 // @option zoomOffset: Number = 0 4705 // The zoom number used in tile URLs will be offset with this value. 4706 zoomOffset: 0, 4707 4708 // @option tms: Boolean = false 4709 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 4710 tms: false, 4711 4712 // @option zoomReverse: Boolean = false 4713 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 4714 zoomReverse: false, 4715 4716 // @option detectRetina: Boolean = false 4717 // 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. 4718 detectRetina: false, 4719 4720 // @option crossOrigin: Boolean = false 4721 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data. 4722 crossOrigin: false 4723 }, 4724 4725 initialize: function (url, options) { 4726 4727 this._url = url; 4728 4729 options = L.setOptions(this, options); 4730 4731 // detecting retina displays, adjusting tileSize and zoom levels 4732 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { 4733 4734 options.tileSize = Math.floor(options.tileSize / 2); 4735 4736 if (!options.zoomReverse) { 4737 options.zoomOffset++; 4738 options.maxZoom--; 4739 } else { 4740 options.zoomOffset--; 4741 options.minZoom++; 4742 } 4743 4744 options.minZoom = Math.max(0, options.minZoom); 4745 } 4746 4747 if (typeof options.subdomains === 'string') { 4748 options.subdomains = options.subdomains.split(''); 4749 } 4750 4751 // for https://github.com/Leaflet/Leaflet/issues/137 4752 if (!L.Browser.android) { 4753 this.on('tileunload', this._onTileRemove); 4754 } 4755 }, 4756 4757 // @method setUrl(url: String, noRedraw?: Boolean): this 4758 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 4759 setUrl: function (url, noRedraw) { 4760 this._url = url; 4761 4762 if (!noRedraw) { 4763 this.redraw(); 4764 } 4765 return this; 4766 }, 4767 4768 // @method createTile(coords: Object, done?: Function): HTMLElement 4769 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 4770 // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done` 4771 // callback is called when the tile has been loaded. 4772 createTile: function (coords, done) { 4773 var tile = document.createElement('img'); 4774 4775 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile)); 4776 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile)); 4777 4778 if (this.options.crossOrigin) { 4779 tile.crossOrigin = ''; 4780 } 4781 4782 /* 4783 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons 4784 http://www.w3.org/TR/WCAG20-TECHS/H67 4785 */ 4786 tile.alt = ''; 4787 4788 tile.src = this.getTileUrl(coords); 4789 4790 return tile; 4791 }, 4792 4793 // @section Extension methods 4794 // @uninheritable 4795 // Layers extending `TileLayer` might reimplement the following method. 4796 // @method getTileUrl(coords: Object): String 4797 // Called only internally, returns the URL for a tile given its coordinates. 4798 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 4799 getTileUrl: function (coords) { 4800 var data = { 4801 r: L.Browser.retina ? '@2x' : '', 4802 s: this._getSubdomain(coords), 4803 x: coords.x, 4804 y: coords.y, 4805 z: this._getZoomForUrl() 4806 }; 4807 if (this._map && !this._map.options.crs.infinite) { 4808 var invertedY = this._globalTileRange.max.y - coords.y; 4809 if (this.options.tms) { 4810 data['y'] = invertedY; 4811 } 4812 data['-y'] = invertedY; 4813 } 4814 4815 return L.Util.template(this._url, L.extend(data, this.options)); 4816 }, 4817 4818 _tileOnLoad: function (done, tile) { 4819 // For https://github.com/Leaflet/Leaflet/issues/3332 4820 if (L.Browser.ielt9) { 4821 setTimeout(L.bind(done, this, null, tile), 0); 4822 } else { 4823 done(null, tile); 4824 } 4825 }, 4826 4827 _tileOnError: function (done, tile, e) { 4828 var errorUrl = this.options.errorTileUrl; 4829 if (errorUrl) { 4830 tile.src = errorUrl; 4831 } 4832 done(e, tile); 4833 }, 4834 4835 getTileSize: function () { 4836 var map = this._map, 4837 tileSize = L.GridLayer.prototype.getTileSize.call(this), 4838 zoom = this._tileZoom + this.options.zoomOffset, 4839 zoomN = this.options.maxNativeZoom; 4840 4841 // increase tile size when overscaling 4842 return zoomN !== null && zoom > zoomN ? 4843 tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() : 4844 tileSize; 4845 }, 4846 4847 _onTileRemove: function (e) { 4848 e.tile.onload = null; 4849 }, 4850 4851 _getZoomForUrl: function () { 4852 4853 var options = this.options, 4854 zoom = this._tileZoom; 4855 4856 if (options.zoomReverse) { 4857 zoom = options.maxZoom - zoom; 4858 } 4859 4860 zoom += options.zoomOffset; 4861 4862 return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom; 4863 }, 4864 4865 _getSubdomain: function (tilePoint) { 4866 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 4867 return this.options.subdomains[index]; 4868 }, 4869 4870 // stops loading all tiles in the background layer 4871 _abortLoading: function () { 4872 var i, tile; 4873 for (i in this._tiles) { 4874 if (this._tiles[i].coords.z !== this._tileZoom) { 4875 tile = this._tiles[i].el; 4876 4877 tile.onload = L.Util.falseFn; 4878 tile.onerror = L.Util.falseFn; 4879 4880 if (!tile.complete) { 4881 tile.src = L.Util.emptyImageUrl; 4882 L.DomUtil.remove(tile); 4883 } 4884 } 4885 } 4886 } 4887}); 4888 4889
4890// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 4891// Instantiates a tile layer object given a `URL template` and optionally an options object. 4892 4893L.tileLayer = function (url, options) { 4894 return new L.TileLayer(url, options); 4895}; 4896 4897 4898 4899/* 4900 * @class TileLayer.WMS 4901 * @inherits TileLayer 4902 * @aka L.TileLayer.WMS 4903 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 4904 * 4905 * @example 4906 * 4907 * ```js 4908 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 4909 * layers: 'nexrad-n0r-900913', 4910 * format: 'image/png', 4911 * transparent: true, 4912 * attribution: "Weather data é 2012 IEM Nexrad" 4913 * }); 4914 * ``` 4915 */ 4916 4917L.TileLayer.WMS = L.TileLayer.extend({ 4918 4919 // @section 4920 // @aka TileLayer.WMS options 4921 // If any custom options not documented here are used, they will be sent to the 4922 // WMS server as extra parameters in each request URL. This can be useful for 4923 // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 4924 defaultWmsParams: { 4925 service: 'WMS', 4926 request: 'GetMap', 4927 4928 // @option layers: String = '' 4929 // **(required)** Comma-separated list of WMS layers to show. 4930 layers: '', 4931 4932 // @option styles: String = '' 4933 // Comma-separated list of WMS styles. 4934 styles: '', 4935 4936 // @option format: String = 'image/jpeg' 4937 // WMS image format (use `'image/png'` for layers with transparency). 4938 format: 'image/jpeg', 4939 4940 // @option transparent: Boolean = false 4941 // If `true`, the WMS service will return images with transparency. 4942 transparent: false, 4943 4944 // @option version: String = '1.1.1' 4945 // Version of the WMS service to use 4946 version: '1.1.1' 4947 }, 4948 4949 options: { 4950 // @option crs: CRS = null 4951 // Coordinate Reference System to use for the WMS requests, defaults to 4952 // map CRS. Don't change this if you're not sure what it means. 4953 crs: null, 4954 4955 // @option uppercase: Boolean = false 4956 // If `true`, WMS request parameter keys will be uppercase. 4957 uppercase: false 4958 }, 4959 4960 initialize: function (url, options) { 4961 4962 this._url = url; 4963 4964 var wmsParams = L.extend({}, this.defaultWmsParams); 4965 4966 // all keys that are not TileLayer options go to WMS params 4967 for (var i in options) { 4968 if (!(i in this.options)) { 4969 wmsParams[i] = options[i]; 4970 } 4971 } 4972 4973 options = L.setOptions(this, options); 4974 4975 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1); 4976 4977 this.wmsParams = wmsParams; 4978 }, 4979 4980 onAdd: function (map) { 4981 4982 this._crs = this.options.crs || map.options.crs; 4983 this._wmsVersion = parseFloat(this.wmsParams.version); 4984 4985 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 4986 this.wmsParams[projectionKey] = this._crs.code; 4987 4988 L.TileLayer.prototype.onAdd.call(this, map); 4989 }, 4990 4991 getTileUrl: function (coords) { 4992 4993 var tileBounds = this._tileCoordsToBounds(coords), 4994 nw = this._crs.project(tileBounds.getNorthWest()), 4995 se = this._crs.project(tileBounds.getSouthEast()), 4996 4997 bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ? 4998 [se.y, nw.x, nw.y, se.x] : 4999 [nw.x, se.y, se.x, nw.y]).join(','), 5000 5001 url = L.TileLayer.prototype.getTileUrl.call(this, coords); 5002 5003 return url + 5004 L.Util.getParamString(this.wmsParams, url, this.options.uppercase) + 5005 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 5006 }, 5007 5008 // @method setParams(params: Object, noRedraw?: Boolean): this 5009 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 5010 setParams: function (params, noRedraw) { 5011 5012 L.extend(this.wmsParams, params); 5013 5014 if (!noRedraw) { 5015 this.redraw(); 5016 } 5017 5018 return this; 5019 } 5020}); 5021 5022 5023// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 5024// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 5025L.tileLayer.wms = function (url, options) { 5026 return new L.TileLayer.WMS(url, options); 5027}; 5028 5029 5030 5031/* 5032 * @class ImageOverlay 5033 * @aka L.ImageOverlay 5034 * @inherits Interactive layer 5035 *
5036 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 5037 * 5038 * @example 5039 * 5040 * ```js 5041 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 5042 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 5043 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 5044 * ``` 5045 */ 5046 5047L.ImageOverlay = L.Layer.extend({ 5048 5049 // @section 5050 // @aka ImageOverlay options 5051 options: { 5052 // @option opacity: Number = 1.0 5053 // The opacity of the image overlay. 5054 opacity: 1, 5055 5056 // @option alt: String = '' 5057 // Text for the `alt` attribute of the image (useful for accessibility). 5058 alt: '', 5059 5060 // @option interactive: Boolean = false 5061 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 5062 interactive: false, 5063 5064 // @option attribution: String = null 5065 // An optional string containing HTML to be shown on the `Attribution control` 5066 attribution: null, 5067 5068 // @option crossOrigin: Boolean = false 5069 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data. 5070 crossOrigin: false 5071 }, 5072 5073 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 5074 this._url = url; 5075 this._bounds = L.latLngBounds(bounds); 5076 5077 L.setOptions(this, options); 5078 }, 5079 5080 onAdd: function () { 5081 if (!this._image) { 5082 this._initImage(); 5083 5084 if (this.options.opacity < 1) { 5085 this._updateOpacity(); 5086 } 5087 } 5088 5089 if (this.options.interactive) { 5090 L.DomUtil.addClass(this._image, 'leaflet-interactive'); 5091 this.addInteractiveTarget(this._image); 5092 } 5093 5094 this.getPane().appendChild(this._image); 5095 this._reset(); 5096 }, 5097 5098 onRemove: function () { 5099 L.DomUtil.remove(this._image); 5100 if (this.options.interactive) { 5101 this.removeInteractiveTarget(this._image); 5102 } 5103 }, 5104 5105 // @method setOpacity(opacity: Number): this 5106 // Sets the opacity of the overlay. 5107 setOpacity: function (opacity) { 5108 this.options.opacity = opacity; 5109 5110 if (this._image) { 5111 this._updateOpacity(); 5112 } 5113 return this; 5114 }, 5115 5116 setStyle: function (styleOpts) { 5117 if (styleOpts.opacity) { 5118 this.setOpacity(styleOpts.opacity); 5119 } 5120 return this; 5121 }, 5122 5123 // @method bringToFront(): this 5124 // Brings the layer to the top of all overlays. 5125 bringToFront: function () { 5126 if (this._map) { 5127 L.DomUtil.toFront(this._image); 5128 } 5129 return this; 5130 }, 5131 5132 // @method bringToBack(): this 5133 // Brings the layer to the bottom of all overlays. 5134 bringToBack: function () { 5135 if (this._map) { 5136 L.DomUtil.toBack(this._image); 5137 } 5138 return this; 5139 }, 5140 5141 // @method setUrl(url: String): this 5142 // Changes the URL of the image. 5143 setUrl: function (url) { 5144 this._url = url; 5145 5146 if (this._image) { 5147 this._image.src = url; 5148 } 5149 return this; 5150 }, 5151 5152 setBounds: function (bounds) { 5153 this._bounds = bounds; 5154 5155 if (this._map) { 5156 this._reset(); 5157 } 5158 return this; 5159 }, 5160 5161 getAttribution: function () { 5162 return this.options.attribution; 5163 }, 5164 5165 getEvents: function () { 5166 var events = { 5167 zoom: this._reset, 5168 viewreset: this._reset 5169 }; 5170 5171 if (this._zoomAnimated) { 5172 events.zoomanim = this._animateZoom; 5173 } 5174 5175 return events; 5176 }, 5177 5178 getBounds: function () { 5179 return this._bounds; 5180 }, 5181 5182 getElement: function () { 5183 return this._image; 5184 }, 5185 5186 _initImage: function () { 5187 var img = this._image = L.DomUtil.create('img', 5188 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '')); 5189 5190 img.onselectstart = L.Util.falseFn; 5191 img.onmousemove = L.Util.falseFn; 5192 5193 img.onload = L.bind(this.fire, this, 'load'); 5194 5195 if (this.options.crossOrigin) { 5196 img.crossOrigin = ''; 5197 } 5198 5199 img.src = this._url; 5200 img.alt = this.options.alt; 5201 }, 5202 5203 _animateZoom: function (e) { 5204 var scale = this._map.getZoomScale(e.zoom), 5205 offset = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center); 5206 5207 L.DomUtil.setTransform(this._image, offset, scale); 5208 }, 5209 5210 _reset: function () { 5211 var image = this._image, 5212 bounds = new L.Bounds( 5213 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 5214 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 5215 size = bounds.getSize(); 5216 5217 L.DomUtil.setPosition(image, bounds.min); 5218 5219 image.style.width = size.x + 'px'; 5220 image.style.height = size.y + 'px'; 5221 }, 5222 5223 _updateOpacity: function () { 5224 L.DomUtil.setOpacity(this._image, this.options.opacity); 5225 } 5226}); 5227 5228// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 5229// Instantiates an image overlay object given the URL of the image and the 5230// geographical bounds it is tied to. 5231L.imageOverlay = function (url, bounds, options) { 5232 return new L.ImageOverlay(url, bounds, options); 5233}; 5234 5235 5236 5237/* 5238 * @class Icon 5239 * @aka L.Icon 5240 * @inherits Layer 5241 * 5242 * Represents an icon to provide when creating a marker. 5243 * 5244 * @example 5245 * 5246 * ```js 5247 * var myIcon = L.icon({ 5248 * iconUrl: 'my-icon.png', 5249 * iconRetinaUrl: '[email protected]', 5250 * iconSize: [38, 95], 5251 * iconAnchor: [22, 94], 5252 * popupAnchor: [-3, -76], 5253 * shadowUrl: 'my-icon-shadow.png', 5254 * shadowRetinaUrl: '[email protected]', 5255 * shadowSize: [68, 95], 5256 * shadowAnchor: [22, 94] 5257 * }); 5258 * 5259 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 5260 * ``` 5261 * 5262 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 5263 * 5264 */ 5265 5266L.Icon = L.Class.extend({ 5267 5268 /* @section 5269 * @aka Icon options 5270 * 5271 * @option iconUrl: String = null 5272 * **(required)** The URL to the icon image (absolute or relative to your script path). 5273 * 5274 * @option iconRetinaUrl: String = null
5275 * The URL to a retina sized version of the icon image (absolute or relative to your 5276 * script path). Used for Retina screen devices. 5277 * 5278 * @option iconSize: Point = null 5279 * Size of the icon image in pixels. 5280 * 5281 * @option iconAnchor: Point = null 5282 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 5283 * will be aligned so that this point is at the marker's geographical location. Centered 5284 * by default if size is specified, also can be set in CSS with negative margins. 5285 * 5286 * @option popupAnchor: Point = null 5287 * The coordinates of the point from which popups will "open", relative to the icon anchor. 5288 * 5289 * @option shadowUrl: String = null 5290 * The URL to the icon shadow image. If not specified, no shadow image will be created. 5291 * 5292 * @option shadowRetinaUrl: String = null 5293 * 5294 * @option shadowSize: Point = null 5295 * Size of the shadow image in pixels. 5296 * 5297 * @option shadowAnchor: Point = null 5298 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 5299 * as iconAnchor if not specified). 5300 * 5301 * @option className: String = '' 5302 * A custom class name to assign to both icon and shadow images. Empty by default. 5303 */ 5304 5305 initialize: function (options) { 5306 L.setOptions(this, options); 5307 }, 5308 5309 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 5310 // Called internally when the icon has to be shown, returns a `<img>` HTML element 5311 // styled according to the options. 5312 createIcon: function (oldIcon) { 5313 return this._createIcon('icon', oldIcon); 5314 }, 5315 5316 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 5317 // As `createIcon`, but for the shadow beneath it. 5318 createShadow: function (oldIcon) { 5319 return this._createIcon('shadow', oldIcon); 5320 }, 5321 5322 _createIcon: function (name, oldIcon) { 5323 var src = this._getIconUrl(name); 5324 5325 if (!src) { 5326 if (name === 'icon') { 5327 throw new Error('iconUrl not set in Icon options (see the docs).'); 5328 } 5329 return null; 5330 } 5331 5332 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 5333 this._setIconStyles(img, name); 5334 5335 return img; 5336 }, 5337 5338 _setIconStyles: function (img, name) { 5339 var options = this.options; 5340 var sizeOption = options[name + 'Size']; 5341 5342 if (typeof sizeOption === 'number') { 5343 sizeOption = [sizeOption, sizeOption]; 5344 } 5345 5346 var size = L.point(sizeOption), 5347 anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 5348 size && size.divideBy(2, true)); 5349 5350 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 5351 5352 if (anchor) { 5353 img.style.marginLeft = (-anchor.x) + 'px'; 5354 img.style.marginTop = (-anchor.y) + 'px'; 5355 } 5356 5357 if (size) { 5358 img.style.width = size.x + 'px'; 5359 img.style.height = size.y + 'px'; 5360 } 5361 }, 5362 5363 _createImg: function (src, el) { 5364 el = el || document.createElement('img'); 5365 el.src = src; 5366 return el; 5367 }, 5368 5369 _getIconUrl: function (name) { 5370 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 5371 } 5372}); 5373 5374 5375// @factory L.icon(options: Icon options) 5376// Creates an icon instance with the given options. 5377L.icon = function (options) { 5378 return new L.Icon(options); 5379}; 5380 5381 5382 5383/* 5384 * @miniclass Icon.Default (Icon) 5385 * @aka L.Icon.Default 5386 * @section 5387 * 5388 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 5389 * no icon is specified. Points to the blue marker image distributed with Leaflet 5390 * releases. 5391 * 5392 * In order to change the default icon, just change the properties of `L.Icon.Default.prototype.options` 5393 * (which is a set of `Icon options`). 5394 */ 5395 5396L.Icon.Default = L.Icon.extend({ 5397 5398 options: { 5399 iconUrl: 'marker-icon.png', 5400 iconRetinaUrl: 'marker-icon-2x.png', 5401 shadowUrl: 'marker-shadow.png', 5402 iconSize: [25, 41], 5403 iconAnchor: [12, 41], 5404 popupAnchor: [1, -34], 5405 tooltipAnchor: [16, -28], 5406 shadowSize: [41, 41] 5407 }, 5408 5409 _getIconUrl: function (name) { 5410 if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only 5411 L.Icon.Default.imagePath = this._detectIconPath(); 5412 } 5413 5414 // @option imagePath: String 5415 // `L.Icon.Default` will try to auto-detect the absolute location of the 5416 // blue icon images. If you are placing these images in a non-standard 5417 // way, set this option to point to the right absolute path. 5418 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name); 5419 }, 5420 5421 _detectIconPath: function () { 5422 var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body); 5423 var path = L.DomUtil.getStyle(el, 'background-image') || 5424 L.DomUtil.getStyle(el, 'backgroundImage'); // IE8 5425 5426 document.body.removeChild(el); 5427 5428 return path.indexOf('url') === 0 ? 5429 path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : ''; 5430 } 5431}); 5432 5433 5434 5435/* 5436 * @class Marker 5437 * @inherits Interactive layer 5438 * @aka L.Marker
5439 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 5440 * 5441 * @example 5442 * 5443 * ```js 5444 * L.marker([50.5, 30.5]).addTo(map); 5445 * ``` 5446 */ 5447 5448L.Marker = L.Layer.extend({ 5449 5450 // @section 5451 // @aka Marker options 5452 options: { 5453 // @option icon: Icon = * 5454 // 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. 5455 icon: new L.Icon.Default(), 5456 5457 // Option inherited from "Interactive layer" abstract class 5458 interactive: true, 5459 5460 // @option draggable: Boolean = false 5461 // Whether the marker is draggable with mouse/touch or not. 5462 draggable: false, 5463 5464 // @option keyboard: Boolean = true 5465 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 5466 keyboard: true, 5467 5468 // @option title: String = '' 5469 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 5470 title: '', 5471 5472 // @option alt: String = '' 5473 // Text for the `alt` attribute of the icon image (useful for accessibility). 5474 alt: '', 5475 5476 // @option zIndexOffset: Number = 0 5477 // 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). 5478 zIndexOffset: 0, 5479 5480 // @option opacity: Number = 1.0 5481 // The opacity of the marker. 5482 opacity: 1, 5483 5484 // @option riseOnHover: Boolean = false 5485 // If `true`, the marker will get on top of others when you hover the mouse over it. 5486 riseOnHover: false, 5487 5488 // @option riseOffset: Number = 250 5489 // The z-index offset used for the `riseOnHover` feature. 5490 riseOffset: 250, 5491 5492 // @option pane: String = 'markerPane' 5493 // `Map pane` where the markers icon will be added. 5494 pane: 'markerPane', 5495 5496 // FIXME: shadowPane is no longer a valid option 5497 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'] 5498 }, 5499 5500 /* @section 5501 * 5502 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 5503 */ 5504 5505 initialize: function (latlng, options) { 5506 L.setOptions(this, options); 5507 this._latlng = L.latLng(latlng); 5508 }, 5509 5510 onAdd: function (map) { 5511 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 5512 5513 if (this._zoomAnimated) { 5514 map.on('zoomanim', this._animateZoom, this); 5515 } 5516 5517 this._initIcon(); 5518 this.update(); 5519 }, 5520 5521 onRemove: function (map) { 5522 if (this.dragging && this.dragging.enabled()) { 5523 this.options.draggable = true; 5524 this.dragging.removeHooks(); 5525 } 5526 5527 if (this._zoomAnimated) { 5528 map.off('zoomanim', this._animateZoom, this); 5529 } 5530 5531 this._removeIcon(); 5532 this._removeShadow(); 5533 }, 5534 5535 getEvents: function () { 5536 return { 5537 zoom: this.update, 5538 viewreset: this.update 5539 }; 5540 }, 5541 5542 // @method getLatLng: LatLng 5543 // Returns the current geographical position of the marker. 5544 getLatLng: function () { 5545 return this._latlng; 5546 }, 5547 5548 // @method setLatLng(latlng: LatLng): this 5549 // Changes the marker position to the given point. 5550 setLatLng: function (latlng) { 5551 var oldLatLng = this._latlng; 5552 this._latlng = L.latLng(latlng); 5553 this.update(); 5554 5555 // @event move: Event 5556 // 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`. 5557 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 5558 }, 5559 5560 // @method setZIndexOffset(offset: Number): this 5561 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 5562 setZIndexOffset: function (offset) { 5563 this.options.zIndexOffset = offset; 5564 return this.update(); 5565 }, 5566 5567 // @method setIcon(icon: Icon): this 5568 // Changes the marker icon. 5569 setIcon: function (icon) { 5570 5571 this.options.icon = icon; 5572 5573 if (this._map) { 5574 this._initIcon(); 5575 this.update(); 5576 } 5577 5578 if (this._popup) { 5579 this.bindPopup(this._popup, this._popup.options); 5580 } 5581 5582 return this; 5583 }, 5584 5585 getElement: function () { 5586 return this._icon; 5587 }, 5588 5589 update: function () { 5590 5591 if (this._icon) { 5592 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 5593 this._setPos(pos); 5594 } 5595 5596 return this; 5597 }, 5598 5599 _initIcon: function () { 5600 var options = this.options, 5601 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 5602 5603 var icon = options.icon.createIcon(this._icon), 5604 addIcon = false;
5605 5606 // if we're not reusing the icon, remove the old one and init new one 5607 if (icon !== this._icon) { 5608 if (this._icon) { 5609 this._removeIcon(); 5610 } 5611 addIcon = true; 5612 5613 if (options.title) { 5614 icon.title = options.title; 5615 } 5616 if (options.alt) { 5617 icon.alt = options.alt; 5618 } 5619 } 5620 5621 L.DomUtil.addClass(icon, classToAdd); 5622 5623 if (options.keyboard) { 5624 icon.tabIndex = '0'; 5625 } 5626 5627 this._icon = icon; 5628 5629 if (options.riseOnHover) { 5630 this.on({ 5631 mouseover: this._bringToFront, 5632 mouseout: this._resetZIndex 5633 }); 5634 } 5635 5636 var newShadow = options.icon.createShadow(this._shadow), 5637 addShadow = false; 5638 5639 if (newShadow !== this._shadow) { 5640 this._removeShadow(); 5641 addShadow = true; 5642 } 5643 5644 if (newShadow) { 5645 L.DomUtil.addClass(newShadow, classToAdd); 5646 } 5647 this._shadow = newShadow; 5648 5649 5650 if (options.opacity < 1) { 5651 this._updateOpacity(); 5652 } 5653 5654 5655 if (addIcon) { 5656 this.getPane().appendChild(this._icon); 5657 } 5658 this._initInteraction(); 5659 if (newShadow && addShadow) { 5660 this.getPane('shadowPane').appendChild(this._shadow); 5661 } 5662 }, 5663 5664 _removeIcon: function () { 5665 if (this.options.riseOnHover) { 5666 this.off({ 5667 mouseover: this._bringToFront, 5668 mouseout: this._resetZIndex 5669 }); 5670 } 5671 5672 L.DomUtil.remove(this._icon); 5673 this.removeInteractiveTarget(this._icon); 5674 5675 this._icon = null; 5676 }, 5677 5678 _removeShadow: function () { 5679 if (this._shadow) { 5680 L.DomUtil.remove(this._shadow); 5681 } 5682 this._shadow = null; 5683 }, 5684 5685 _setPos: function (pos) { 5686 L.DomUtil.setPosition(this._icon, pos); 5687 5688 if (this._shadow) { 5689 L.DomUtil.setPosition(this._shadow, pos); 5690 } 5691 5692 this._zIndex = pos.y + this.options.zIndexOffset; 5693 5694 this._resetZIndex(); 5695 }, 5696 5697 _updateZIndex: function (offset) { 5698 this._icon.style.zIndex = this._zIndex + offset; 5699 }, 5700 5701 _animateZoom: function (opt) { 5702 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 5703 5704 this._setPos(pos); 5705 }, 5706 5707 _initInteraction: function () { 5708 5709 if (!this.options.interactive) { return; } 5710 5711 L.DomUtil.addClass(this._icon, 'leaflet-interactive'); 5712 5713 this.addInteractiveTarget(this._icon); 5714 5715 if (L.Handler.MarkerDrag) { 5716 var draggable = this.options.draggable; 5717 if (this.dragging) { 5718 draggable = this.dragging.enabled(); 5719 this.dragging.disable(); 5720 } 5721 5722 this.dragging = new L.Handler.MarkerDrag(this); 5723 5724 if (draggable) { 5725 this.dragging.enable(); 5726 } 5727 } 5728 }, 5729 5730 // @method setOpacity(opacity: Number): this 5731 // Changes the opacity of the marker. 5732 setOpacity: function (opacity) { 5733 this.options.opacity = opacity; 5734 if (this._map) { 5735 this._updateOpacity(); 5736 } 5737 5738 return this; 5739 }, 5740 5741 _updateOpacity: function () { 5742 var opacity = this.options.opacity; 5743 5744 L.DomUtil.setOpacity(this._icon, opacity); 5745 5746 if (this._shadow) { 5747 L.DomUtil.setOpacity(this._shadow, opacity); 5748 } 5749 }, 5750 5751 _bringToFront: function () { 5752 this._updateZIndex(this.options.riseOffset); 5753 }, 5754 5755 _resetZIndex: function () { 5756 this._updateZIndex(0); 5757 } 5758}); 5759 5760 5761// factory L.marker(latlng: LatLng, options? : Marker options) 5762 5763// @factory L.marker(latlng: LatLng, options? : Marker options) 5764// Instantiates a Marker object given a geographical point and optionally an options object. 5765L.marker = function (latlng, options) { 5766 return new L.Marker(latlng, options); 5767}; 5768 5769 5770 5771/* 5772 * @class DivIcon 5773 * @aka L.DivIcon 5774 * @inherits Icon 5775 * 5776 * Represents a lightweight icon for markers that uses a simple `<div>` 5777 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 5778 * 5779 * @example 5780 * ```js 5781 * var myIcon = L.divIcon({className: 'my-div-icon'}); 5782 * // you can set .my-div-icon styles in CSS 5783 * 5784 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 5785 * ``` 5786 * 5787 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 5788 */ 5789 5790L.DivIcon = L.Icon.extend({ 5791 options: { 5792 // @section 5793 // @aka DivIcon options 5794 iconSize: [12, 12], // also can be set through CSS 5795 5796 // iconAnchor: (Point), 5797 // popupAnchor: (Point), 5798 5799 // @option html: String = '' 5800 // Custom HTML code to put inside the div element, empty by default. 5801 html: false, 5802 5803 // @option bgPos: Point = [0, 0] 5804 // Optional relative position of the background, in pixels 5805 bgPos: null, 5806 5807 className: 'leaflet-div-icon' 5808 }, 5809 5810 createIcon: function (oldIcon) { 5811 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 5812 options = this.options; 5813 5814 div.innerHTML = options.html !== false ? options.html : ''; 5815 5816 if (options.bgPos) { 5817 var bgPos = L.point(options.bgPos); 5818 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 5819 } 5820 this._setIconStyles(div, 'icon'); 5821 5822 return div; 5823 }, 5824 5825 createShadow: function () { 5826 return null; 5827 } 5828}); 5829 5830// @factory L.divIcon(options: DivIcon options) 5831// Creates a `DivIcon` instance with the given options. 5832L.divIcon = function (options) { 5833 return new L.DivIcon(options); 5834}; 5835 5836 5837 5838/* 5839 * @class DivOverlay 5840 * @inherits Layer 5841 * @aka L.DivOverlay 5842 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins. 5843 */ 5844 5845// @namespace DivOverlay 5846L.DivOverlay = L.Layer.extend({ 5847 5848 // @section 5849 // @aka DivOverlay options 5850 options: {
vendor: 5,258 bytes, lines 5851-6058
5851 // @option offset: Point = Point(0, 7) 5852 // The offset of the popup position. Useful to control the anchor 5853 // of the popup when opening it on some overlays. 5854 offset: [0, 7], 5855 5856 // @option className: String = '' 5857 // A custom CSS class name to assign to the popup. 5858 className: '', 5859 5860 // @option pane: String = 'popupPane' 5861 // `Map pane` where the popup will be added. 5862 pane: 'popupPane' 5863 }, 5864 5865 initialize: function (options, source) { 5866 L.setOptions(this, options); 5867 5868 this._source = source; 5869 }, 5870 5871 onAdd: function (map) { 5872 this._zoomAnimated = map._zoomAnimated; 5873 5874 if (!this._container) { 5875 this._initLayout(); 5876 } 5877 5878 if (map._fadeAnimated) { 5879 L.DomUtil.setOpacity(this._container, 0); 5880 } 5881 5882 clearTimeout(this._removeTimeout); 5883 this.getPane().appendChild(this._container); 5884 this.update(); 5885 5886 if (map._fadeAnimated) { 5887 L.DomUtil.setOpacity(this._container, 1); 5888 } 5889 5890 this.bringToFront(); 5891 }, 5892 5893 onRemove: function (map) { 5894 if (map._fadeAnimated) { 5895 L.DomUtil.setOpacity(this._container, 0); 5896 this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200); 5897 } else { 5898 L.DomUtil.remove(this._container); 5899 } 5900 }, 5901 5902 // @namespace Popup 5903 // @method getLatLng: LatLng 5904 // Returns the geographical point of popup. 5905 getLatLng: function () { 5906 return this._latlng; 5907 }, 5908 5909 // @method setLatLng(latlng: LatLng): this 5910 // Sets the geographical point where the popup will open. 5911 setLatLng: function (latlng) { 5912 this._latlng = L.latLng(latlng); 5913 if (this._map) { 5914 this._updatePosition(); 5915 this._adjustPan(); 5916 } 5917 return this; 5918 }, 5919 5920 // @method getContent: String|HTMLElement 5921 // Returns the content of the popup. 5922 getContent: function () { 5923 return this._content; 5924 }, 5925 5926 // @method setContent(htmlContent: String|HTMLElement|Function): this 5927 // 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. 5928 setContent: function (content) { 5929 this._content = content; 5930 this.update(); 5931 return this; 5932 }, 5933 5934 // @method getElement: String|HTMLElement 5935 // Alias for [getContent()](#popup-getcontent) 5936 getElement: function () { 5937 return this._container; 5938 }, 5939 5940 // @method update: null 5941 // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded. 5942 update: function () { 5943 if (!this._map) { return; } 5944 5945 this._container.style.visibility = 'hidden'; 5946 5947 this._updateContent(); 5948 this._updateLayout(); 5949 this._updatePosition(); 5950 5951 this._container.style.visibility = ''; 5952 5953 this._adjustPan(); 5954 }, 5955 5956 getEvents: function () { 5957 var events = { 5958 zoom: this._updatePosition, 5959 viewreset: this._updatePosition 5960 }; 5961 5962 if (this._zoomAnimated) { 5963 events.zoomanim = this._animateZoom; 5964 } 5965 return events; 5966 }, 5967 5968 // @method isOpen: Boolean 5969 // Returns `true` when the popup is visible on the map. 5970 isOpen: function () { 5971 return !!this._map && this._map.hasLayer(this); 5972 }, 5973 5974 // @method bringToFront: this 5975 // Brings this popup in front of other popups (in the same map pane). 5976 bringToFront: function () { 5977 if (this._map) { 5978 L.DomUtil.toFront(this._container); 5979 } 5980 return this; 5981 }, 5982 5983 // @method bringToBack: this 5984 // Brings this popup to the back of other popups (in the same map pane). 5985 bringToBack: function () { 5986 if (this._map) { 5987 L.DomUtil.toBack(this._container); 5988 } 5989 return this; 5990 }, 5991 5992 _updateContent: function () { 5993 if (!this._content) { return; } 5994 5995 var node = this._contentNode; 5996 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 5997 5998 if (typeof content === 'string') { 5999 node.innerHTML = content; 6000 } else { 6001 while (node.hasChildNodes()) { 6002 node.removeChild(node.firstChild); 6003 } 6004 node.appendChild(content); 6005 } 6006 this.fire('contentupdate'); 6007 }, 6008 6009 _updatePosition: function () { 6010 if (!this._map) { return; } 6011 6012 var pos = this._map.latLngToLayerPoint(this._latlng), 6013 offset = L.point(this.options.offset), 6014 anchor = this._getAnchor(); 6015 6016 if (this._zoomAnimated) { 6017 L.DomUtil.setPosition(this._container, pos.add(anchor)); 6018 } else { 6019 offset = offset.add(pos).add(anchor); 6020 } 6021 6022 var bottom = this._containerBottom = -offset.y, 6023 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 6024 6025 // bottom position the popup in case the height of the popup changes (images loading etc) 6026 this._container.style.bottom = bottom + 'px'; 6027 this._container.style.left = left + 'px'; 6028 }, 6029 6030 _getAnchor: function () { 6031 return [0, 0]; 6032 } 6033 6034}); 6035 6036 6037 6038/* 6039 * @class Popup 6040 * @inherits DivOverlay 6041 * @aka L.Popup 6042 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 6043 * open popups while making sure that only one popup is open at one time 6044 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 6045 * 6046 * @example 6047 * 6048 * If you want to just bind a popup to marker click and then open it, it's really easy: 6049 * 6050 * ```js 6051 * marker.bindPopup(popupContent).openPopup(); 6052 * ``` 6053 * Path overlays like polylines also have a `bindPopup` method. 6054 * Here's a more complicated way to open a popup on a map: 6055 * 6056 * ```js 6057 * var popup = L.popup() 6058 * .setLatLng(latlng)
6059 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 6060 * .openOn(map); 6061 * ``` 6062 */ 6063 6064 6065// @namespace Popup 6066L.Popup = L.DivOverlay.extend({ 6067 6068 // @section 6069 // @aka Popup options 6070 options: { 6071 // @option maxWidth: Number = 300 6072 // Max width of the popup, in pixels. 6073 maxWidth: 300, 6074 6075 // @option minWidth: Number = 50 6076 // Min width of the popup, in pixels. 6077 minWidth: 50, 6078 6079 // @option maxHeight: Number = null 6080 // If set, creates a scrollable container of the given height 6081 // inside a popup if its content exceeds it. 6082 maxHeight: null, 6083 6084 // @option autoPan: Boolean = true 6085 // Set it to `false` if you don't want the map to do panning animation 6086 // to fit the opened popup. 6087 autoPan: true, 6088 6089 // @option autoPanPaddingTopLeft: Point = null 6090 // The margin between the popup and the top left corner of the map 6091 // view after autopanning was performed. 6092 autoPanPaddingTopLeft: null, 6093 6094 // @option autoPanPaddingBottomRight: Point = null 6095 // The margin between the popup and the bottom right corner of the map 6096 // view after autopanning was performed. 6097 autoPanPaddingBottomRight: null, 6098 6099 // @option autoPanPadding: Point = Point(5, 5) 6100 // Equivalent of setting both top left and bottom right autopan padding to the same value. 6101 autoPanPadding: [5, 5], 6102 6103 // @option keepInView: Boolean = false 6104 // Set it to `true` if you want to prevent users from panning the popup 6105 // off of the screen while it is open. 6106 keepInView: false, 6107 6108 // @option closeButton: Boolean = true 6109 // Controls the presence of a close button in the popup. 6110 closeButton: true, 6111 6112 // @option autoClose: Boolean = true 6113 // Set it to `false` if you want to override the default behavior of 6114 // the popup closing when user clicks the map (set globally by 6115 // the Map's [closePopupOnClick](#map-closepopuponclick) option). 6116 autoClose: true, 6117 6118 // @option className: String = '' 6119 // A custom CSS class name to assign to the popup. 6120 className: '' 6121 }, 6122 6123 // @namespace Popup 6124 // @method openOn(map: Map): this 6125 // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`. 6126 openOn: function (map) { 6127 map.openPopup(this); 6128 return this; 6129 }, 6130 6131 onAdd: function (map) { 6132 L.DivOverlay.prototype.onAdd.call(this, map); 6133 6134 // @namespace Map 6135 // @section Popup events 6136 // @event popupopen: PopupEvent 6137 // Fired when a popup is opened in the map 6138 map.fire('popupopen', {popup: this}); 6139 6140 if (this._source) { 6141 // @namespace Layer 6142 // @section Popup events 6143 // @event popupopen: PopupEvent 6144 // Fired when a popup bound to this layer is opened 6145 this._source.fire('popupopen', {popup: this}, true); 6146 // For non-path layers, we toggle the popup when clicking 6147 // again the layer, so prevent the map to reopen it. 6148 if (!(this._source instanceof L.Path)) { 6149 this._source.on('preclick', L.DomEvent.stopPropagation); 6150 } 6151 } 6152 }, 6153 6154 onRemove: function (map) { 6155 L.DivOverlay.prototype.onRemove.call(this, map); 6156 6157 // @namespace Map 6158 // @section Popup events 6159 // @event popupclose: PopupEvent 6160 // Fired when a popup in the map is closed 6161 map.fire('popupclose', {popup: this}); 6162 6163 if (this._source) { 6164 // @namespace Layer 6165 // @section Popup events 6166 // @event popupclose: PopupEvent 6167 // Fired when a popup bound to this layer is closed 6168 this._source.fire('popupclose', {popup: this}, true); 6169 if (!(this._source instanceof L.Path)) { 6170 this._source.off('preclick', L.DomEvent.stopPropagation); 6171 } 6172 } 6173 }, 6174 6175 getEvents: function () { 6176 var events = L.DivOverlay.prototype.getEvents.call(this); 6177 6178 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 6179 events.preclick = this._close; 6180 } 6181 6182 if (this.options.keepInView) { 6183 events.moveend = this._adjustPan; 6184 } 6185 6186 return events; 6187 }, 6188 6189 _close: function () { 6190 if (this._map) { 6191 this._map.closePopup(this); 6192 } 6193 }, 6194 6195 _initLayout: function () { 6196 var prefix = 'leaflet-popup', 6197 container = this._container = L.DomUtil.create('div', 6198 prefix + ' ' + (this.options.className || '') + 6199 ' leaflet-zoom-animated'); 6200 6201 if (this.options.closeButton) { 6202 var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container); 6203 closeButton.href = '#close'; 6204 closeButton.innerHTML = '×'; 6205 6206 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); 6207 } 6208 6209 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container); 6210 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); 6211 6212 L.DomEvent 6213 .disableClickPropagation(wrapper) 6214 .disableScrollPropagation(this._contentNode) 6215 .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); 6216 6217 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); 6218 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); 6219 }, 6220 6221 _updateLayout: function () { 6222 var container = this._contentNode, 6223 style = container.style; 6224 6225 style.width = ''; 6226 style.whiteSpace = 'nowrap'; 6227 6228 var width = container.offsetWidth; 6229 width = Math.min(width, this.options.maxWidth); 6230 width = Math.max(width, this.options.minWidth); 6231 6232 style.width = (width + 1) + 'px'; 6233 style.whiteSpace = ''; 6234 6235 style.height = ''; 6236 6237 var height = container.offsetHeight, 6238 maxHeight = this.options.maxHeight, 6239 scrolledClass = 'leaflet-popup-scrolled'; 6240 6241 if (maxHeight && height > maxHeight) { 6242 style.height = maxHeight + 'px'; 6243 L.DomUtil.addClass(container, scrolledClass); 6244 } else { 6245 L.DomUtil.removeClass(container, scrolledClass); 6246 } 6247 6248 this._containerWidth = this._container.offsetWidth; 6249 }, 6250 6251 _animateZoom: function (e) { 6252 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 6253 anchor = this._getAnchor(); 6254 L.DomUtil.setPosition(this._container, pos.add(anchor)); 6255 }, 6256 6257 _adjustPan: function () { 6258 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } 6259 6260 var map = this._map, 6261 marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0, 6262 containerHeight = this._container.offsetHeight + marginBottom, 6263 containerWidth = this._containerWidth, 6264 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); 6265 6266 layerPos._add(L.DomUtil.getPosition(this._container)); 6267 6268 var containerPos = map.layerPointToContainerPoint(layerPos), 6269 padding = L.point(this.options.autoPanPadding), 6270 paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), 6271 paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), 6272 size = map.getSize(), 6273 dx = 0, 6274 dy = 0; 6275 6276 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 6277 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 6278 } 6279 if (containerPos.x - dx - paddingTL.x < 0) { // left 6280 dx = containerPos.x - paddingTL.x; 6281 } 6282 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 6283 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 6284 } 6285 if (containerPos.y - dy - paddingTL.y < 0) { // top 6286 dy = containerPos.y - paddingTL.y; 6287 } 6288 6289 // @namespace Map 6290 // @section Popup events 6291 // @event autopanstart: Event 6292 // Fired when the map starts autopanning when opening a popup. 6293 if (dx || dy) { 6294 map 6295 .fire('autopanstart') 6296 .panBy([dx, dy]); 6297 } 6298 }, 6299 6300 _onCloseButtonClick: function (e) { 6301 this._close(); 6302 L.DomEvent.stop(e); 6303 }, 6304 6305 _getAnchor: function () { 6306 // Where should we anchor the popup on the source layer? 6307 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 6308 } 6309 6310}); 6311 6312// @namespace Popup 6313// @factory L.popup(options?: Popup options, source?: Layer) 6314// 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. 6315L.popup = function (options, source) { 6316 return new L.Popup(options, source); 6317}; 6318 6319 6320/* @namespace Map 6321 * @section Interaction Options 6322 * @option closePopupOnClick: Boolean = true 6323 * Set it to `false` if you don't want popups to close when user clicks the map. 6324 */ 6325L.Map.mergeOptions({ 6326 closePopupOnClick: true 6327}); 6328 6329 6330// @namespace Map 6331// @section Methods for Layers and Controls 6332L.Map.include({ 6333 // @method openPopup(popup: Popup): this 6334 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 6335 // @alternative 6336 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 6337 // Creates a popup with the specified content and options and opens it in the given point on a map. 6338 openPopup: function (popup, latlng, options) { 6339 if (!(popup instanceof L.Popup)) { 6340 popup = new L.Popup(options).setContent(popup); 6341 } 6342 6343 if (latlng) { 6344 popup.setLatLng(latlng); 6345 } 6346 6347 if (this.hasLayer(popup)) { 6348 return this; 6349 } 6350 6351 if (this._popup && this._popup.options.autoClose) { 6352 this.closePopup(); 6353 } 6354 6355 this._popup = popup; 6356 return this.addLayer(popup); 6357 }, 6358 6359 // @method closePopup(popup?: Popup): this 6360 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 6361 closePopup: function (popup) { 6362 if (!popup || popup === this._popup) { 6363 popup = this._popup; 6364 this._popup = null; 6365 } 6366 if (popup) { 6367 this.removeLayer(popup); 6368 } 6369 return this; 6370 } 6371}); 6372 6373 6374 6375/* 6376 * @namespace Layer 6377 * @section Popup methods example 6378 * 6379 * All layers share a set of methods convenient for binding popups to it. 6380 * 6381 * ```js 6382 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 6383 * layer.openPopup(); 6384 * layer.closePopup(); 6385 * ``` 6386 * 6387 * 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. 6388 */ 6389 6390// @section Popup methods 6391L.Layer.include({ 6392 6393 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 6394 // Binds a popup to the layer with the passed `content` and sets up the 6395 // neccessary event listeners. If a `Function` is passed it will receive 6396 // the layer as the first argument and should return a `String` or `HTMLElement`. 6397 bindPopup: function (content, options) { 6398 6399 if (content instanceof L.Popup) { 6400 L.setOptions(content, options); 6401 this._popup = content; 6402 content._source = this; 6403 } else { 6404 if (!this._popup || options) { 6405 this._popup = new L.Popup(options, this); 6406 } 6407 this._popup.setContent(content); 6408 } 6409 6410 if (!this._popupHandlersAdded) { 6411 this.on({ 6412 click: this._openPopup, 6413 remove: this.closePopup,
6414 move: this._movePopup 6415 }); 6416 this._popupHandlersAdded = true; 6417 } 6418 6419 return this; 6420 }, 6421 6422 // @method unbindPopup(): this 6423 // Removes the popup previously bound with `bindPopup`. 6424 unbindPopup: function () { 6425 if (this._popup) { 6426 this.off({ 6427 click: this._openPopup, 6428 remove: this.closePopup, 6429 move: this._movePopup 6430 }); 6431 this._popupHandlersAdded = false; 6432 this._popup = null; 6433 } 6434 return this; 6435 }, 6436 6437 // @method openPopup(latlng?: LatLng): this 6438 // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed. 6439 openPopup: function (layer, latlng) { 6440 if (!(layer instanceof L.Layer)) { 6441 latlng = layer; 6442 layer = this; 6443 } 6444 6445 if (layer instanceof L.FeatureGroup) { 6446 for (var id in this._layers) { 6447 layer = this._layers[id]; 6448 break; 6449 } 6450 } 6451 6452 if (!latlng) { 6453 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng(); 6454 } 6455 6456 if (this._popup && this._map) { 6457 // set popup source to this layer 6458 this._popup._source = layer; 6459 6460 // update the popup (content, layout, ect...) 6461 this._popup.update(); 6462 6463 // open the popup on the map 6464 this._map.openPopup(this._popup, latlng); 6465 } 6466 6467 return this; 6468 }, 6469 6470 // @method closePopup(): this 6471 // Closes the popup bound to this layer if it is open. 6472 closePopup: function () { 6473 if (this._popup) { 6474 this._popup._close(); 6475 } 6476 return this; 6477 }, 6478 6479 // @method togglePopup(): this 6480 // Opens or closes the popup bound to this layer depending on its current state. 6481 togglePopup: function (target) { 6482 if (this._popup) { 6483 if (this._popup._map) { 6484 this.closePopup(); 6485 } else { 6486 this.openPopup(target); 6487 } 6488 } 6489 return this; 6490 }, 6491 6492 // @method isPopupOpen(): boolean 6493 // Returns `true` if the popup bound to this layer is currently open. 6494 isPopupOpen: function () { 6495 return this._popup.isOpen(); 6496 }, 6497 6498 // @method setPopupContent(content: String|HTMLElement|Popup): this 6499 // Sets the content of the popup bound to this layer. 6500 setPopupContent: function (content) { 6501 if (this._popup) { 6502 this._popup.setContent(content); 6503 } 6504 return this; 6505 }, 6506 6507 // @method getPopup(): Popup 6508 // Returns the popup bound to this layer. 6509 getPopup: function () { 6510 return this._popup; 6511 }, 6512 6513 _openPopup: function (e) { 6514 var layer = e.layer || e.target; 6515 6516 if (!this._popup) { 6517 return; 6518 } 6519 6520 if (!this._map) { 6521 return; 6522 } 6523 6524 // prevent map click 6525 L.DomEvent.stop(e); 6526 6527 // if this inherits from Path its a vector and we can just 6528 // open the popup at the new location 6529 if (layer instanceof L.Path) { 6530 this.openPopup(e.layer || e.target, e.latlng); 6531 return; 6532 } 6533 6534 // otherwise treat it like a marker and figure out 6535 // if we should toggle it open/closed 6536 if (this._map.hasLayer(this._popup) && this._popup._source === layer) { 6537 this.closePopup(); 6538 } else { 6539 this.openPopup(layer, e.latlng); 6540 } 6541 }, 6542 6543 _movePopup: function (e) { 6544 this._popup.setLatLng(e.latlng); 6545 } 6546}); 6547 6548 6549 6550/* 6551 * Popup extension to L.Marker, adding popup-related methods. 6552 */ 6553 6554L.Marker.include({ 6555 _getPopupAnchor: function () { 6556 return this.options.icon.options.popupAnchor || [0, 0]; 6557 } 6558}); 6559 6560 6561 6562/* 6563 * @class Tooltip 6564 * @inherits DivOverlay 6565 * @aka L.Tooltip 6566 * Used to display small texts on top of map layers. 6567 * 6568 * @example 6569 * 6570 * ```js 6571 * marker.bindTooltip("my tooltip text").openTooltip(); 6572 * ``` 6573 * Note about tooltip offset. Leaflet takes two options in consideration 6574 * for computing tooltip offseting: 6575 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 6576 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 6577 * move it to the bottom. Negatives will move to the left and top. 6578 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 6579 * should adapt this value if you use a custom icon. 6580 */ 6581 6582 6583// @namespace Tooltip 6584L.Tooltip = L.DivOverlay.extend({ 6585 6586 // @section 6587 // @aka Tooltip options 6588 options: { 6589 // @option pane: String = 'tooltipPane' 6590 // `Map pane` where the tooltip will be added. 6591 pane: 'tooltipPane', 6592 6593 // @option offset: Point = Point(0, 0) 6594 // Optional offset of the tooltip position. 6595 offset: [0, 0], 6596 6597 // @option direction: String = 'auto' 6598 // Direction where to open the tooltip. Possible values are: `right`, `left`, 6599 // `top`, `bottom`, `center`, `auto`. 6600 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip 6601 // position on the map. 6602 direction: 'auto', 6603 6604 // @option permanent: Boolean = false 6605 // Whether to open the tooltip permanently or only on mouseover. 6606 permanent: false, 6607 6608 // @option sticky: Boolean = false 6609 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
6610 sticky: false, 6611 6612 // @option interactive: Boolean = false 6613 // If true, the tooltip will listen to the feature events. 6614 interactive: false, 6615 6616 // @option opacity: Number = 0.9 6617 // Tooltip container opacity. 6618 opacity: 0.9 6619 }, 6620 6621 onAdd: function (map) { 6622 L.DivOverlay.prototype.onAdd.call(this, map); 6623 this.setOpacity(this.options.opacity); 6624 6625 // @namespace Map 6626 // @section Tooltip events 6627 // @event tooltipopen: TooltipEvent 6628 // Fired when a tooltip is opened in the map. 6629 map.fire('tooltipopen', {tooltip: this}); 6630 6631 if (this._source) { 6632 // @namespace Layer 6633 // @section Tooltip events 6634 // @event tooltipopen: TooltipEvent 6635 // Fired when a tooltip bound to this layer is opened. 6636 this._source.fire('tooltipopen', {tooltip: this}, true); 6637 } 6638 }, 6639 6640 onRemove: function (map) { 6641 L.DivOverlay.prototype.onRemove.call(this, map); 6642 6643 // @namespace Map 6644 // @section Tooltip events 6645 // @event tooltipclose: TooltipEvent 6646 // Fired when a tooltip in the map is closed. 6647 map.fire('tooltipclose', {tooltip: this}); 6648 6649 if (this._source) { 6650 // @namespace Layer 6651 // @section Tooltip events 6652 // @event tooltipclose: TooltipEvent 6653 // Fired when a tooltip bound to this layer is closed. 6654 this._source.fire('tooltipclose', {tooltip: this}, true); 6655 } 6656 }, 6657 6658 getEvents: function () { 6659 var events = L.DivOverlay.prototype.getEvents.call(this); 6660 6661 if (L.Browser.touch && !this.options.permanent) { 6662 events.preclick = this._close; 6663 } 6664 6665 return events; 6666 }, 6667 6668 _close: function () { 6669 if (this._map) { 6670 this._map.closeTooltip(this); 6671 } 6672 }, 6673 6674 _initLayout: function () { 6675 var prefix = 'leaflet-tooltip', 6676 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 6677 6678 this._contentNode = this._container = L.DomUtil.create('div', className); 6679 }, 6680 6681 _updateLayout: function () {}, 6682 6683 _adjustPan: function () {}, 6684 6685 _setPosition: function (pos) { 6686 var map = this._map, 6687 container = this._container, 6688 centerPoint = map.latLngToContainerPoint(map.getCenter()), 6689 tooltipPoint = map.layerPointToContainerPoint(pos), 6690 direction = this.options.direction, 6691 tooltipWidth = container.offsetWidth, 6692 tooltipHeight = container.offsetHeight, 6693 offset = L.point(this.options.offset), 6694 anchor = this._getAnchor(); 6695 6696 if (direction === 'top') { 6697 pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y)); 6698 } else if (direction === 'bottom') { 6699 pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y)); 6700 } else if (direction === 'center') { 6701 pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y)); 6702 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) { 6703 direction = 'right'; 6704 pos = pos.add([offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y]); 6705 } else { 6706 direction = 'left'; 6707 pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y)); 6708 } 6709 6710 L.DomUtil.removeClass(container, 'leaflet-tooltip-right'); 6711 L.DomUtil.removeClass(container, 'leaflet-tooltip-left'); 6712 L.DomUtil.removeClass(container, 'leaflet-tooltip-top'); 6713 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom'); 6714 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction); 6715 L.DomUtil.setPosition(container, pos); 6716 }, 6717 6718 _updatePosition: function () { 6719 var pos = this._map.latLngToLayerPoint(this._latlng); 6720 this._setPosition(pos); 6721 }, 6722 6723 setOpacity: function (opacity) { 6724 this.options.opacity = opacity; 6725 6726 if (this._container) { 6727 L.DomUtil.setOpacity(this._container, opacity); 6728 } 6729 }, 6730 6731 _animateZoom: function (e) { 6732 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 6733 this._setPosition(pos); 6734 }, 6735 6736 _getAnchor: function () { 6737 // Where should we anchor the tooltip on the source layer? 6738 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 6739 } 6740 6741}); 6742 6743// @namespace Tooltip 6744// @factory L.tooltip(options?: Tooltip options, source?: Layer) 6745// 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. 6746L.tooltip = function (options, source) { 6747 return new L.Tooltip(options, source); 6748}; 6749 6750// @namespace Map 6751// @section Methods for Layers and Controls 6752L.Map.include({ 6753 6754 // @method openTooltip(tooltip: Tooltip): this 6755 // Opens the specified tooltip. 6756 // @alternative 6757 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 6758 // Creates a tooltip with the specified content and options and open it. 6759 openTooltip: function (tooltip, latlng, options) { 6760 if (!(tooltip instanceof L.Tooltip)) { 6761 tooltip = new L.Tooltip(options).setContent(tooltip); 6762 } 6763 6764 if (latlng) { 6765 tooltip.setLatLng(latlng); 6766 } 6767 6768 if (this.hasLayer(tooltip)) { 6769 return this; 6770 } 6771 6772 return this.addLayer(tooltip); 6773 }, 6774 6775 // @method closeTooltip(tooltip?: Tooltip): this 6776 // Closes the tooltip given as parameter. 6777 closeTooltip: function (tooltip) { 6778 if (tooltip) { 6779 this.removeLayer(tooltip); 6780 } 6781 return this; 6782 } 6783 6784}); 6785 6786 6787 6788/* 6789 * @namespace Layer 6790 * @section Tooltip methods example 6791 * 6792 * All layers share a set of methods convenient for binding tooltips to it. 6793 * 6794 * ```js 6795 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 6796 * layer.openTooltip(); 6797 * layer.closeTooltip(); 6798 * ``` 6799 */ 6800 6801// @section Tooltip methods 6802L.Layer.include({ 6803 6804 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 6805 // Binds a tooltip to the layer with the passed `content` and sets up the 6806 // neccessary event listeners. If a `Function` is passed it will receive 6807 // the layer as the first argument and should return a `String` or `HTMLElement`. 6808 bindTooltip: function (content, options) { 6809 6810 if (content instanceof L.Tooltip) { 6811 L.setOptions(content, options); 6812 this._tooltip = content; 6813 content._source = this; 6814 } else { 6815 if (!this._tooltip || options) { 6816 this._tooltip = L.tooltip(options, this); 6817 } 6818 this._tooltip.setContent(content); 6819 6820 } 6821 6822 this._initTooltipInteractions(); 6823 6824 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 6825 this.openTooltip(); 6826 } 6827 6828 return this; 6829 }, 6830 6831 // @method unbindTooltip(): this 6832 // Removes the tooltip previously bound with `bindTooltip`. 6833 unbindTooltip: function () { 6834 if (this._tooltip) { 6835 this._initTooltipInteractions(true); 6836 this.closeTooltip(); 6837 this._tooltip = null; 6838 } 6839 return this; 6840 }, 6841 6842 _initTooltipInteractions: function (remove) { 6843 if (!remove && this._tooltipHandlersAdded) { return; } 6844 var onOff = remove ? 'off' : 'on', 6845 events = { 6846 remove: this.closeTooltip, 6847 move: this._moveTooltip 6848 }; 6849 if (!this._tooltip.options.permanent) { 6850 events.mouseover = this._openTooltip; 6851 events.mouseout = this.closeTooltip; 6852 if (this._tooltip.options.sticky) { 6853 events.mousemove = this._moveTooltip; 6854 } 6855 if (L.Browser.touch) { 6856 events.click = this._openTooltip; 6857 } 6858 } else { 6859 events.add = this._openTooltip; 6860 } 6861 this[onOff](events); 6862 this._tooltipHandlersAdded = !remove; 6863 }, 6864 6865 // @method openTooltip(latlng?: LatLng): this 6866 // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed. 6867 openTooltip: function (layer, latlng) { 6868 if (!(layer instanceof L.Layer)) { 6869 latlng = layer; 6870 layer = this; 6871 } 6872 6873 if (layer instanceof L.FeatureGroup) { 6874 for (var id in this._layers) { 6875 layer = this._layers[id]; 6876 break; 6877 } 6878 } 6879 6880 if (!latlng) { 6881 latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng(); 6882 } 6883 6884 if (this._tooltip && this._map) { 6885 6886 // set tooltip source to this layer 6887 this._tooltip._source = layer; 6888 6889 // update the tooltip (content, layout, ect...) 6890 this._tooltip.update(); 6891 6892 // open the tooltip on the map 6893 this._map.openTooltip(this._tooltip, latlng); 6894 6895 // Tooltip container may not be defined if not permanent and never 6896 // opened. 6897 if (this._tooltip.options.interactive && this._tooltip._container) { 6898 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable'); 6899 this.addInteractiveTarget(this._tooltip._container); 6900 } 6901 } 6902 6903 return this; 6904 }, 6905 6906 // @method closeTooltip(): this 6907 // Closes the tooltip bound to this layer if it is open. 6908 closeTooltip: function () { 6909 if (this._tooltip) { 6910 this._tooltip._close(); 6911 if (this._tooltip.options.interactive && this._tooltip._container) { 6912 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable'); 6913 this.removeInteractiveTarget(this._tooltip._container); 6914 } 6915 } 6916 return this; 6917 }, 6918 6919 // @method toggleTooltip(): this 6920 // Opens or closes the tooltip bound to this layer depending on its current state. 6921 toggleTooltip: function (target) { 6922 if (this._tooltip) { 6923 if (this._tooltip._map) { 6924 this.closeTooltip(); 6925 } else { 6926 this.openTooltip(target); 6927 } 6928 } 6929 return this; 6930 }, 6931 6932 // @method isTooltipOpen(): boolean 6933 // Returns `true` if the tooltip bound to this layer is currently open. 6934 isTooltipOpen: function () { 6935 return this._tooltip.isOpen(); 6936 }, 6937 6938 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
6939 // Sets the content of the tooltip bound to this layer. 6940 setTooltipContent: function (content) { 6941 if (this._tooltip) { 6942 this._tooltip.setContent(content); 6943 } 6944 return this; 6945 }, 6946 6947 // @method getTooltip(): Tooltip 6948 // Returns the tooltip bound to this layer. 6949 getTooltip: function () { 6950 return this._tooltip; 6951 }, 6952 6953 _openTooltip: function (e) { 6954 var layer = e.layer || e.target; 6955 6956 if (!this._tooltip || !this._map) { 6957 return; 6958 } 6959 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined); 6960 }, 6961 6962 _moveTooltip: function (e) { 6963 var latlng = e.latlng, containerPoint, layerPoint; 6964 if (this._tooltip.options.sticky && e.originalEvent) { 6965 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 6966 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 6967 latlng = this._map.layerPointToLatLng(layerPoint); 6968 } 6969 this._tooltip.setLatLng(latlng); 6970 } 6971}); 6972 6973 6974 6975/* 6976 * Tooltip extension to L.Marker, adding tooltip-related methods. 6977 */ 6978 6979L.Marker.include({ 6980 _getTooltipAnchor: function () { 6981 return this.options.icon.options.tooltipAnchor || [0, 0]; 6982 } 6983}); 6984 6985 6986 6987/* 6988 * @class LayerGroup 6989 * @aka L.LayerGroup 6990 * @inherits Layer 6991 * 6992 * Used to group several layers and handle them as one. If you add it to the map, 6993 * any layers added or removed from the group will be added/removed on the map as 6994 * well. Extends `Layer`. 6995 * 6996 * @example 6997 * 6998 * ```js 6999 * L.layerGroup([marker1, marker2]) 7000 * .addLayer(polyline) 7001 * .addTo(map); 7002 * ``` 7003 */ 7004 7005L.LayerGroup = L.Layer.extend({ 7006 7007 initialize: function (layers) { 7008 this._layers = {}; 7009 7010 var i, len; 7011 7012 if (layers) { 7013 for (i = 0, len = layers.length; i < len; i++) { 7014 this.addLayer(layers[i]); 7015 } 7016 } 7017 }, 7018 7019 // @method addLayer(layer: Layer): this 7020 // Adds the given layer to the group. 7021 addLayer: function (layer) { 7022 var id = this.getLayerId(layer); 7023 7024 this._layers[id] = layer; 7025 7026 if (this._map) { 7027 this._map.addLayer(layer); 7028 } 7029 7030 return this; 7031 }, 7032 7033 // @method removeLayer(layer: Layer): this 7034 // Removes the given layer from the group. 7035 // @alternative 7036 // @method removeLayer(id: Number): this 7037 // Removes the layer with the given internal ID from the group. 7038 removeLayer: function (layer) { 7039 var id = layer in this._layers ? layer : this.getLayerId(layer); 7040 7041 if (this._map && this._layers[id]) { 7042 this._map.removeLayer(this._layers[id]); 7043 } 7044 7045 delete this._layers[id]; 7046 7047 return this; 7048 }, 7049 7050 // @method hasLayer(layer: Layer): Boolean 7051 // Returns `true` if the given layer is currently added to the group. 7052 hasLayer: function (layer) { 7053 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers); 7054 }, 7055 7056 // @method clearLayers(): this 7057 // Removes all the layers from the group. 7058 clearLayers: function () { 7059 for (var i in this._layers) { 7060 this.removeLayer(this._layers[i]); 7061 } 7062 return this; 7063 }, 7064 7065 // @method invoke(methodName: String, ââ¬Â¦): this 7066 // Calls `methodName` on every layer contained in this group, passing any 7067 // additional parameters. Has no effect if the layers contained do not 7068 // implement `methodName`. 7069 invoke: function (methodName) { 7070 var args = Array.prototype.slice.call(arguments, 1), 7071 i, layer; 7072 7073 for (i in this._layers) { 7074 layer = this._layers[i]; 7075 7076 if (layer[methodName]) { 7077 layer[methodName].apply(layer, args); 7078 } 7079 } 7080 7081 return this; 7082 }, 7083 7084 onAdd: function (map) { 7085 for (var i in this._layers) { 7086 map.addLayer(this._layers[i]); 7087 } 7088 }, 7089 7090 onRemove: function (map) { 7091 for (var i in this._layers) { 7092 map.removeLayer(this._layers[i]); 7093 } 7094 }, 7095 7096 // @method eachLayer(fn: Function, context?: Object): this 7097 // Iterates over the layers of the group, optionally specifying context of the iterator function. 7098 // ```js 7099 // group.eachLayer(function (layer) { 7100 // layer.bindPopup('Hello'); 7101 // }); 7102 // ``` 7103 eachLayer: function (method, context) { 7104 for (var i in this._layers) { 7105 method.call(context, this._layers[i]); 7106 } 7107 return this; 7108 }, 7109 7110 // @method getLayer(id: Number): Layer 7111 // Returns the layer with the given internal ID. 7112 getLayer: function (id) { 7113 return this._layers[id]; 7114 }, 7115 7116 // @method getLayers(): Layer[] 7117 // Returns an array of all the layers added to the group. 7118 getLayers: function () { 7119 var layers = []; 7120 7121 for (var i in this._layers) { 7122 layers.push(this._layers[i]); 7123 } 7124 return layers; 7125 }, 7126 7127 // @method setZIndex(zIndex: Number): this 7128 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 7129 setZIndex: function (zIndex) { 7130 return this.invoke('setZIndex', zIndex); 7131 }, 7132 7133 // @method getLayerId(layer: Layer): Number 7134 // Returns the internal ID for a layer 7135 getLayerId: function (layer) { 7136 return L.stamp(layer); 7137 } 7138}); 7139 7140 7141// @factory L.layerGroup(layers: Layer[]) 7142// Create a layer group, optionally given an initial set of layers. 7143L.layerGroup = function (layers) { 7144 return new L.LayerGroup(layers); 7145}; 7146 7147 7148 7149/* 7150 * @class FeatureGroup 7151 * @aka L.FeatureGroup 7152 * @inherits LayerGroup 7153 * 7154 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 7155 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 7156 * * Events are propagated to the `FeatureGroup`, so if the group has an event 7157 * handler, it will handle events from any of the layers. This includes mouse events 7158 * and custom events. 7159 * * Has `layeradd` and `layerremove` events 7160 * 7161 * @example 7162 * 7163 * ```js 7164 * L.featureGroup([marker1, marker2, polyline]) 7165 * .bindPopup('Hello world!') 7166 * .on('click', function() { alert('Clicked on a member of the group!'); }) 7167 * .addTo(map); 7168 * ``` 7169 */ 7170 7171L.FeatureGroup = L.LayerGroup.extend({ 7172 7173 addLayer: function (layer) { 7174 if (this.hasLayer(layer)) { 7175 return this; 7176 } 7177 7178 layer.addEventParent(this); 7179 7180 L.LayerGroup.prototype.addLayer.call(this, layer); 7181 7182 // @event layeradd: LayerEvent 7183 // Fired when a layer is added to this `FeatureGroup` 7184 return this.fire('layeradd', {layer: layer}); 7185 }, 7186 7187 removeLayer: function (layer) { 7188 if (!this.hasLayer(layer)) { 7189 return this; 7190 } 7191 if (layer in this._layers) { 7192 layer = this._layers[layer]; 7193 } 7194 7195 layer.removeEventParent(this); 7196 7197 L.LayerGroup.prototype.removeLayer.call(this, layer); 7198 7199 // @event layerremove: LayerEvent 7200 // Fired when a layer is removed from this `FeatureGroup` 7201 return this.fire('layerremove', {layer: layer}); 7202 }, 7203 7204 // @method setStyle(style: Path options): this
7205 // Sets the given path options to each layer of the group that has a `setStyle` method. 7206 setStyle: function (style) { 7207 return this.invoke('setStyle', style); 7208 }, 7209 7210 // @method bringToFront(): this 7211 // Brings the layer group to the top of all other layers 7212 bringToFront: function () { 7213 return this.invoke('bringToFront'); 7214 }, 7215 7216 // @method bringToBack(): this 7217 // Brings the layer group to the top of all other layers 7218 bringToBack: function () { 7219 return this.invoke('bringToBack'); 7220 }, 7221 7222 // @method getBounds(): LatLngBounds 7223 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 7224 getBounds: function () { 7225 var bounds = new L.LatLngBounds(); 7226 7227 for (var id in this._layers) { 7228 var layer = this._layers[id]; 7229 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 7230 } 7231 return bounds; 7232 } 7233}); 7234 7235// @factory L.featureGroup(layers: Layer[]) 7236// Create a feature group, optionally given an initial set of layers. 7237L.featureGroup = function (layers) { 7238 return new L.FeatureGroup(layers); 7239}; 7240 7241 7242 7243/* 7244 * @class Renderer 7245 * @inherits Layer 7246 * @aka L.Renderer 7247 * 7248 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 7249 * DOM container of the renderer, its bounds, and its zoom animation. 7250 * 7251 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 7252 * itself can be added or removed to the map. All paths use a renderer, which can 7253 * be implicit (the map will decide the type of renderer and use it automatically) 7254 * or explicit (using the [`renderer`](#path-renderer) option of the path). 7255 * 7256 * Do not use this class directly, use `SVG` and `Canvas` instead. 7257 * 7258 * @event update: Event 7259 * Fired when the renderer updates its bounds, center and zoom, for example when 7260 * its map has moved 7261 */ 7262 7263L.Renderer = L.Layer.extend({ 7264 7265 // @section 7266 // @aka Renderer options 7267 options: { 7268 // @option padding: Number = 0.1 7269 // How much to extend the clip area around the map view (relative to its size) 7270 // e.g. 0.1 would be 10% of map view in each direction 7271 padding: 0.1 7272 }, 7273 7274 initialize: function (options) { 7275 L.setOptions(this, options); 7276 L.stamp(this); 7277 }, 7278 7279 onAdd: function () { 7280 if (!this._container) { 7281 this._initContainer(); // defined by renderer implementations 7282 7283 if (this._zoomAnimated) { 7284 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated'); 7285 } 7286 } 7287 7288 this.getPane().appendChild(this._container); 7289 this._update(); 7290 }, 7291 7292 onRemove: function () { 7293 L.DomUtil.remove(this._container); 7294 }, 7295 7296 getEvents: function () { 7297 var events = { 7298 viewreset: this._reset, 7299 zoom: this._onZoom, 7300 moveend: this._update 7301 }; 7302 if (this._zoomAnimated) { 7303 events.zoomanim = this._onAnimZoom; 7304 } 7305 return events; 7306 }, 7307 7308 _onAnimZoom: function (ev) { 7309 this._updateTransform(ev.center, ev.zoom); 7310 }, 7311 7312 _onZoom: function () { 7313 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 7314 }, 7315 7316 _updateTransform: function (center, zoom) { 7317 var scale = this._map.getZoomScale(zoom, this._zoom), 7318 position = L.DomUtil.getPosition(this._container), 7319 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 7320 currentCenterPoint = this._map.project(this._center, zoom), 7321 destCenterPoint = this._map.project(center, zoom), 7322 centerOffset = destCenterPoint.subtract(currentCenterPoint), 7323 7324 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset); 7325 7326 if (L.Browser.any3d) { 7327 L.DomUtil.setTransform(this._container, topLeftOffset, scale); 7328 } else { 7329 L.DomUtil.setPosition(this._container, topLeftOffset); 7330 } 7331 }, 7332 7333 _reset: function () { 7334 this._update(); 7335 this._updateTransform(this._center, this._zoom); 7336 }, 7337 7338 _update: function () { 7339 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 7340 // Subclasses are responsible of firing the 'update' event. 7341 var p = this.options.padding, 7342 size = this._map.getSize(), 7343 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 7344 7345 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 7346 7347 this._center = this._map.getCenter(); 7348 this._zoom = this._map.getZoom(); 7349 } 7350}); 7351 7352 7353L.Map.include({ 7354 // @namespace Map; @method getRenderer(layer: Path): Renderer 7355 // Returns the instance of `Renderer` that should be used to render the given 7356 // `Path`. It will ensure that the `renderer` options of the map and paths 7357 // are respected, and that the renderers do exist on the map. 7358 getRenderer: function (layer) { 7359 // @namespace Path; @option renderer: Renderer 7360 // Use this specific instance of `Renderer` for this path. Takes 7361 // precedence over the map's [default renderer](#map-renderer). 7362 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 7363 7364 if (!renderer) { 7365 // @namespace Map; @option preferCanvas: Boolean = false 7366 // Whether `Path`s should be rendered on a `Canvas` renderer. 7367 // By default, all `Path`s are rendered in a `SVG` renderer. 7368 renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg(); 7369 } 7370 7371 if (!this.hasLayer(renderer)) { 7372 this.addLayer(renderer); 7373 } 7374 return renderer; 7375 }, 7376 7377 _getPaneRenderer: function (name) { 7378 if (name === 'overlayPane' || name === undefined) { 7379 return false;
7380 } 7381 7382 var renderer = this._paneRenderers[name]; 7383 if (renderer === undefined) { 7384 renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name})); 7385 this._paneRenderers[name] = renderer; 7386 } 7387 return renderer; 7388 } 7389}); 7390 7391 7392 7393/* 7394 * @class Path 7395 * @aka L.Path 7396 * @inherits Interactive layer 7397 * 7398 * An abstract class that contains options and constants shared between vector 7399 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 7400 */ 7401 7402L.Path = L.Layer.extend({ 7403 7404 // @section 7405 // @aka Path options 7406 options: { 7407 // @option stroke: Boolean = true 7408 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 7409 stroke: true, 7410 7411 // @option color: String = '#3388ff' 7412 // Stroke color 7413 color: '#3388ff', 7414 7415 // @option weight: Number = 3 7416 // Stroke width in pixels 7417 weight: 3, 7418 7419 // @option opacity: Number = 1.0 7420 // Stroke opacity 7421 opacity: 1, 7422 7423 // @option lineCap: String= 'round' 7424 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 7425 lineCap: 'round', 7426 7427 // @option lineJoin: String = 'round' 7428 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 7429 lineJoin: 'round', 7430 7431 // @option dashArray: String = null 7432 // 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). 7433 dashArray: null, 7434 7435 // @option dashOffset: String = null 7436 // 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). 7437 dashOffset: null, 7438 7439 // @option fill: Boolean = depends 7440 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 7441 fill: false, 7442 7443 // @option fillColor: String = * 7444 // Fill color. Defaults to the value of the [`color`](#path-color) option 7445 fillColor: null, 7446 7447 // @option fillOpacity: Number = 0.2 7448 // Fill opacity. 7449 fillOpacity: 0.2, 7450 7451 // @option fillRule: String = 'evenodd' 7452 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 7453 fillRule: 'evenodd', 7454 7455 // className: '', 7456 7457 // Option inherited from "Interactive layer" abstract class 7458 interactive: true 7459 }, 7460 7461 beforeAdd: function (map) { 7462 // Renderer is set here because we need to call renderer.getEvents 7463 // before this.getEvents. 7464 this._renderer = map.getRenderer(this); 7465 }, 7466 7467 onAdd: function () { 7468 this._renderer._initPath(this); 7469 this._reset(); 7470 this._renderer._addPath(this); 7471 this._renderer.on('update', this._update, this); 7472 }, 7473 7474 onRemove: function () { 7475 this._renderer._removePath(this); 7476 this._renderer.off('update', this._update, this); 7477 }, 7478 7479 getEvents: function () { 7480 return { 7481 zoomend: this._project, 7482 viewreset: this._reset 7483 }; 7484 }, 7485 7486 // @method redraw(): this 7487 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 7488 redraw: function () { 7489 if (this._map) { 7490 this._renderer._updatePath(this); 7491 } 7492 return this; 7493 }, 7494 7495 // @method setStyle(style: Path options): this 7496 // Changes the appearance of a Path based on the options in the `Path options` object. 7497 setStyle: function (style) { 7498 L.setOptions(this, style); 7499 if (this._renderer) { 7500 this._renderer._updateStyle(this); 7501 } 7502 return this; 7503 }, 7504 7505 // @method bringToFront(): this 7506 // Brings the layer to the top of all path layers. 7507 bringToFront: function () { 7508 if (this._renderer) { 7509 this._renderer._bringToFront(this); 7510 } 7511 return this; 7512 }, 7513 7514 // @method bringToBack(): this 7515 // Brings the layer to the bottom of all path layers. 7516 bringToBack: function () { 7517 if (this._renderer) { 7518 this._renderer._bringToBack(this); 7519 } 7520 return this; 7521 }, 7522 7523 getElement: function () { 7524 return this._path; 7525 }, 7526 7527 _reset: function () { 7528 // defined in children classes 7529 this._project(); 7530 this._update(); 7531 }, 7532 7533 _clickTolerance: function () { 7534 // used when doing hit detection for Canvas layers 7535 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0); 7536 } 7537}); 7538 7539 7540 7541/* 7542 * @namespace LineUtil 7543 * 7544 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast. 7545 */ 7546 7547L.LineUtil = { 7548 7549 // Simplify polyline with vertex reduction and Douglas-Peucker simplification. 7550 // Improves rendering performance dramatically by lessening the number of points to draw. 7551 7552 // @function simplify(points: Point[], tolerance: Number): Point[] 7553 // Dramatically reduces the number of points in a polyline while retaining 7554 // its shape and returns a new array of simplified points, using the 7555 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). 7556 // Used for a huge performance boost when processing/displaying Leaflet polylines for 7557 // each zoom level and also reducing visual noise. tolerance affects the amount of 7558 // simplification (lesser value means higher quality but slower and with more points). 7559 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). 7560 simplify: function (points, tolerance) { 7561 if (!tolerance || !points.length) { 7562 return points.slice(); 7563 } 7564 7565 var sqTolerance = tolerance * tolerance; 7566 7567 // stage 1: vertex reduction 7568 points = this._reducePoints(points, sqTolerance); 7569 7570 // stage 2: Douglas-Peucker simplification 7571 points = this._simplifyDP(points, sqTolerance); 7572 7573 return points; 7574 }, 7575 7576 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 7577 // Returns the distance between point `p` and segment `p1` to `p2`. 7578 pointToSegmentDistance: function (p, p1, p2) { 7579 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); 7580 }, 7581 7582 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 7583 // Returns the closest point from a point `p` on a segment `p1` to `p2`. 7584 closestPointOnSegment: function (p, p1, p2) { 7585 return this._sqClosestPointOnSegment(p, p1, p2); 7586 }, 7587 7588 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm 7589 _simplifyDP: function (points, sqTolerance) { 7590 7591 var len = points.length, 7592 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 7593 markers = new ArrayConstructor(len); 7594 7595 markers[0] = markers[len - 1] = 1; 7596 7597 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 7598 7599 var i, 7600 newPoints = []; 7601 7602 for (i = 0; i < len; i++) { 7603 if (markers[i]) { 7604 newPoints.push(points[i]); 7605 } 7606 } 7607 7608 return newPoints; 7609 }, 7610 7611 _simplifyDPStep: function (points, markers, sqTolerance, first, last) { 7612 7613 var maxSqDist = 0, 7614 index, i, sqDist; 7615 7616 for (i = first + 1; i <= last - 1; i++) { 7617 sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); 7618 7619 if (sqDist > maxSqDist) { 7620 index = i; 7621 maxSqDist = sqDist; 7622 } 7623 } 7624 7625 if (maxSqDist > sqTolerance) { 7626 markers[index] = 1; 7627 7628 this._simplifyDPStep(points, markers, sqTolerance, first, index); 7629 this._simplifyDPStep(points, markers, sqTolerance, index, last); 7630 } 7631 }, 7632 7633 // reduce points that are too close to each other to a single point 7634 _reducePoints: function (points, sqTolerance) { 7635 var reducedPoints = [points[0]]; 7636 7637 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 7638 if (this._sqDist(points[i], points[prev]) > sqTolerance) { 7639 reducedPoints.push(points[i]); 7640 prev = i; 7641 } 7642 } 7643 if (prev < len - 1) { 7644 reducedPoints.push(points[len - 1]); 7645 } 7646 return reducedPoints; 7647 }, 7648 7649 7650 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, roun
7650d?: Boolean): Point[]|Boolean 7651 // Clips the segment a to b by rectangular bounds with the 7652 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 7653 // (modifying the segment points directly!). Used by Leaflet to only show polyline 7654 // points that are on the screen or near, increasing performance. 7655 clipSegment: function (a, b, bounds, useLastCode, round) { 7656 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), 7657 codeB = this._getBitCode(b, bounds), 7658 7659 codeOut, p, newCode; 7660 7661 // save 2nd code to avoid calculating it on the next segment 7662 this._lastCode = codeB; 7663 7664 while (true) { 7665 // if a,b is inside the clip window (trivial accept) 7666 if (!(codeA | codeB)) { 7667 return [a, b]; 7668 } 7669 7670 // if a,b is outside the clip window (trivial reject) 7671 if (codeA & codeB) { 7672 return false; 7673 } 7674 7675 // other cases 7676 codeOut = codeA || codeB; 7677 p = this._getEdgeIntersection(a, b, codeOut, bounds, round); 7678 newCode = this._getBitCode(p, bounds); 7679 7680 if (codeOut === codeA) { 7681 a = p; 7682 codeA = newCode; 7683 } else { 7684 b = p; 7685 codeB = newCode; 7686 } 7687 } 7688 }, 7689
vendor: 4,999 bytes, lines 7690-7907
7690 _getEdgeIntersection: function (a, b, code, bounds, round) { 7691 var dx = b.x - a.x, 7692 dy = b.y - a.y, 7693 min = bounds.min, 7694 max = bounds.max, 7695 x, y; 7696 7697 if (code & 8) { // top 7698 x = a.x + dx * (max.y - a.y) / dy; 7699 y = max.y; 7700 7701 } else if (code & 4) { // bottom 7702 x = a.x + dx * (min.y - a.y) / dy; 7703 y = min.y; 7704 7705 } else if (code & 2) { // right 7706 x = max.x; 7707 y = a.y + dy * (max.x - a.x) / dx; 7708 7709 } else if (code & 1) { // left 7710 x = min.x; 7711 y = a.y + dy * (min.x - a.x) / dx; 7712 } 7713 7714 return new L.Point(x, y, round); 7715 }, 7716 7717 _getBitCode: function (p, bounds) { 7718 var code = 0; 7719 7720 if (p.x < bounds.min.x) { // left 7721 code |= 1; 7722 } else if (p.x > bounds.max.x) { // right 7723 code |= 2; 7724 } 7725 7726 if (p.y < bounds.min.y) { // bottom 7727 code |= 4; 7728 } else if (p.y > bounds.max.y) { // top 7729 code |= 8; 7730 } 7731 7732 return code; 7733 }, 7734 7735 // square distance (to avoid unnecessary Math.sqrt calls) 7736 _sqDist: function (p1, p2) { 7737 var dx = p2.x - p1.x, 7738 dy = p2.y - p1.y; 7739 return dx * dx + dy * dy; 7740 }, 7741 7742 // return closest point on segment or distance to that point 7743 _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { 7744 var x = p1.x, 7745 y = p1.y, 7746 dx = p2.x - x, 7747 dy = p2.y - y, 7748 dot = dx * dx + dy * dy, 7749 t; 7750 7751 if (dot > 0) { 7752 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 7753 7754 if (t > 1) { 7755 x = p2.x; 7756 y = p2.y; 7757 } else if (t > 0) { 7758 x += dx * t; 7759 y += dy * t; 7760 } 7761 } 7762 7763 dx = p.x - x; 7764 dy = p.y - y; 7765 7766 return sqDist ? dx * dx + dy * dy : new L.Point(x, y); 7767 } 7768}; 7769 7770 7771 7772/* 7773 * @class Polyline 7774 * @aka L.Polyline 7775 * @inherits Path 7776 * 7777 * A class for drawing polyline overlays on a map. Extends `Path`. 7778 * 7779 * @example 7780 * 7781 * ```js 7782 * // create a red polyline from an array of LatLng points 7783 * var latlngs = [ 7784 * [-122.68, 45.51], 7785 * [-122.43, 37.77], 7786 * [-118.2, 34.04] 7787 * ]; 7788 * 7789 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 7790 * 7791 * // zoom the map to the polyline 7792 * map.fitBounds(polyline.getBounds()); 7793 * ``` 7794 * 7795 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 7796 * 7797 * ```js 7798 * // create a red polyline from an array of arrays of LatLng points 7799 * var latlngs = [ 7800 * [[-122.68, 45.51], 7801 * [-122.43, 37.77], 7802 * [-118.2, 34.04]], 7803 * [[-73.91, 40.78], 7804 * [-87.62, 41.83], 7805 * [-96.72, 32.76]] 7806 * ]; 7807 * ``` 7808 */ 7809 7810L.Polyline = L.Path.extend({ 7811 7812 // @section 7813 // @aka Polyline options 7814 options: { 7815 // @option smoothFactor: Number = 1.0 7816 // How much to simplify the polyline on each zoom level. More means 7817 // better performance and smoother look, and less means more accurate representation. 7818 smoothFactor: 1.0, 7819 7820 // @option noClip: Boolean = false 7821 // Disable polyline clipping. 7822 noClip: false 7823 }, 7824 7825 initialize: function (latlngs, options) { 7826 L.setOptions(this, options); 7827 this._setLatLngs(latlngs); 7828 }, 7829 7830 // @method getLatLngs(): LatLng[] 7831 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 7832 getLatLngs: function () { 7833 return this._latlngs; 7834 }, 7835 7836 // @method setLatLngs(latlngs: LatLng[]): this 7837 // Replaces all the points in the polyline with the given array of geographical points. 7838 setLatLngs: function (latlngs) { 7839 this._setLatLngs(latlngs); 7840 return this.redraw(); 7841 }, 7842 7843 // @method isEmpty(): Boolean 7844 // Returns `true` if the Polyline has no LatLngs. 7845 isEmpty: function () { 7846 return !this._latlngs.length; 7847 }, 7848 7849 closestLayerPoint: function (p) { 7850 var minDistance = Infinity, 7851 minPoint = null, 7852 closest = L.LineUtil._sqClosestPointOnSegment, 7853 p1, p2; 7854 7855 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 7856 var points = this._parts[j]; 7857 7858 for (var i = 1, len = points.length; i < len; i++) { 7859 p1 = points[i - 1]; 7860 p2 = points[i]; 7861 7862 var sqDist = closest(p, p1, p2, true); 7863 7864 if (sqDist < minDistance) { 7865 minDistance = sqDist; 7866 minPoint = closest(p, p1, p2); 7867 } 7868 } 7869 } 7870 if (minPoint) { 7871 minPoint.distance = Math.sqrt(minDistance); 7872 } 7873 return minPoint; 7874 }, 7875 7876 // @method getCenter(): LatLng 7877 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline. 7878 getCenter: function () { 7879 // throws error when not yet added to map as this center calculation requires projected coordinates 7880 if (!this._map) { 7881 throw new Error('Must add layer to map before using getCenter()'); 7882 } 7883 7884 var i, halfDist, segDist, dist, p1, p2, ratio, 7885 points = this._rings[0], 7886 len = points.length; 7887 7888 if (!len) { return null; } 7889 7890 // polyline centroid algorithm; only uses the first ring if there are multiple 7891 7892 for (i = 0, halfDist = 0; i < len - 1; i++) { 7893 halfDist += points[i].distanceTo(points[i + 1]) / 2; 7894 } 7895 7896 // The line is so small in the current view that all points are on the same pixel. 7897 if (halfDist === 0) { 7898 return this._map.layerPointToLatLng(points[0]); 7899 } 7900 7901 for (i = 0, dist = 0; i < len - 1; i++) { 7902 p1 = points[i]; 7903 p2 = points[i + 1]; 7904 segDist = p1.distanceTo(p2); 7905 dist += segDist; 7906 7907 if (dist > halfDist) {
7908 ratio = (dist - halfDist) / segDist; 7909 return this._map.layerPointToLatLng([ 7910 p2.x - ratio * (p2.x - p1.x), 7911 p2.y - ratio * (p2.y - p1.y) 7912 ]); 7913 } 7914 } 7915 }, 7916 7917 // @method getBounds(): LatLngBounds 7918 // Returns the `LatLngBounds` of the path. 7919 getBounds: function () { 7920 return this._bounds; 7921 }, 7922 7923 // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this 7924 // Adds a given point to the polyline. By default, adds to the first ring of 7925 // the polyline in case of a multi-polyline, but can be overridden by passing 7926 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 7927 addLatLng: function (latlng, latlngs) { 7928 latlngs = latlngs || this._defaultShape(); 7929 latlng = L.latLng(latlng); 7930 latlngs.push(latlng); 7931 this._bounds.extend(latlng); 7932 return this.redraw(); 7933 }, 7934 7935 _setLatLngs: function (latlngs) { 7936 this._bounds = new L.LatLngBounds(); 7937 this._latlngs = this._convertLatLngs(latlngs); 7938 }, 7939 7940 _defaultShape: function () { 7941 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0]; 7942 }, 7943 7944 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 7945 _convertLatLngs: function (latlngs) { 7946 var result = [], 7947 flat = L.Polyline._flat(latlngs); 7948 7949 for (var i = 0, len = latlngs.length; i < len; i++) { 7950 if (flat) { 7951 result[i] = L.latLng(latlngs[i]); 7952 this._bounds.extend(result[i]); 7953 } else { 7954 result[i] = this._convertLatLngs(latlngs[i]); 7955 } 7956 } 7957 7958 return result; 7959 }, 7960 7961 _project: function () { 7962 var pxBounds = new L.Bounds(); 7963 this._rings = []; 7964 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 7965 7966 var w = this._clickTolerance(), 7967 p = new L.Point(w, w); 7968 7969 if (this._bounds.isValid() && pxBounds.isValid()) { 7970 pxBounds.min._subtract(p); 7971 pxBounds.max._add(p); 7972 this._pxBounds = pxBounds; 7973 } 7974 }, 7975 7976 // recursively turns latlngs into a set of rings with projected coordinates 7977 _projectLatlngs: function (latlngs, result, projectedBounds) { 7978 var flat = latlngs[0] instanceof L.LatLng, 7979 len = latlngs.length, 7980 i, ring; 7981 7982 if (flat) { 7983 ring = []; 7984 for (i = 0; i < len; i++) { 7985 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 7986 projectedBounds.extend(ring[i]); 7987 } 7988 result.push(ring); 7989 } else { 7990 for (i = 0; i < len; i++) { 7991 this._projectLatlngs(latlngs[i], result, projectedBounds); 7992 } 7993 } 7994 }, 7995 7996 // clip polyline by renderer bounds so that we have less to render for performance 7997 _clipPoints: function () { 7998 var bounds = this._renderer._bounds; 7999 8000 this._parts = []; 8001 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8002 return; 8003 } 8004 8005 if (this.options.noClip) { 8006 this._parts = this._rings; 8007 return; 8008 } 8009 8010 var parts = this._parts, 8011 i, j, k, len, len2, segment, points; 8012 8013 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8014 points = this._rings[i]; 8015 8016 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8017 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true); 8018 8019 if (!segment) { continue; } 8020 8021 parts[k] = parts[k] || []; 8022 parts[k].push(segment[0]); 8023 8024 // if segment goes out of screen, or it's the last one, it's the end of the line part 8025 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8026 parts[k].push(segment[1]); 8027 k++; 8028 } 8029 } 8030 } 8031 }, 8032 8033 // simplify each clipped part of the polyline for performance 8034 _simplifyPoints: function () { 8035 var parts = this._parts, 8036 tolerance = this.options.smoothFactor; 8037 8038 for (var i = 0, len = parts.length; i < len; i++) { 8039 parts[i] = L.LineUtil.simplify(parts[i], tolerance); 8040 } 8041 }, 8042 8043 _update: function () { 8044 if (!this._map) { return; } 8045 8046 this._clipPoints(); 8047 this._simplifyPoints(); 8048 this._updatePath(); 8049 }, 8050 8051 _updatePath: function () { 8052 this._renderer._updatePoly(this); 8053 } 8054}); 8055 8056// @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8057// Instantiates a polyline object given an array of geographical points and 8058// optionally an options object. You can create a `Polyline` object with 8059// multiple separate lines (`MultiPolyline`) by passing an array of arrays 8060// of geographic points. 8061L.polyline = function (latlngs, options) { 8062 return new L.Polyline(latlngs, options); 8063}; 8064 8065L.Polyline._flat = function (latlngs) { 8066 // true if it's a flat array of latlngs; false if nested 8067 return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 8068}; 8069 8070 8071 8072/* 8073 * @namespace PolyUtil 8074 * Various utility functions for polygon geometries. 8075 */ 8076 8077L.PolyUtil = {}; 8078 8079/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 8080 * 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)). 8081 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 8082 * performance. Note that polygon points needs different algorithm for clipping 8083 * than polyline, so there's a seperate method for it. 8084 */ 8085L.PolyUtil.clipPolygon = function (points, bounds, round) { 8086 var clippedPoints, 8087 edges = [1, 4, 2, 8], 8088 i, j, k, 8089 a, b, 8090 len, edge, p, 8091 lu = L.LineUtil; 8092 8093 for (i = 0, len = points.length; i < len; i++) { 8094 points[i]._code = lu._getBitCode(points[i], bounds); 8095 } 8096 8097 // for each edge (left, bottom, right, top) 8098 for (k = 0; k < 4; k++) { 8099 edge = edges[k]; 8100 clippedPoints = []; 8101 8102 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 8103 a = points[i]; 8104 b = points[j]; 8105 8106 // if a is inside the clip window 8107 if (!(a._code & edge)) { 8108 // if b is outside the clip window (a->b goes out of screen) 8109 if (b._code & edge) { 8110 p = lu._getEdgeIntersection(b, a, edge, bounds, round); 8111 p._code = lu._getBitCode(p, bounds); 8112 clippedPoints.push(p); 8113 } 8114 clippedPoints.push(a); 8115 8116 // else if b is inside the clip window (a->b enters the screen) 8117 } else if (!(b._code & edge)) { 8118 p = lu._getEdgeIntersection(b, a, edge, bounds, round); 8119 p._code = lu._getBitCode(p, bounds); 8120 clippedPoints.push(p); 8121 } 8122 } 8123 points = clippedPoints; 8124 } 8125 8126 return points; 8127}; 8128 8129 8130 8131/* 8132 * @class Polygon 8133 * @aka L.Polygon 8134 * @inherits Polyline 8135 * 8136 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8137 *
8138 * 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. 8139 * 8140 * 8141 * @example 8142 * 8143 * ```js 8144 * // create a red polygon from an array of LatLng points 8145 * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]]; 8146 * 8147 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8148 * 8149 * // zoom the map to the polygon 8150 * map.fitBounds(polygon.getBounds()); 8151 * ``` 8152 * 8153 * 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: 8154 * 8155 * ```js 8156 * var latlngs = [ 8157 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring 8158 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole 8159 * ]; 8160 * ``` 8161 * 8162 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8163 * 8164 * ```js 8165 * var latlngs = [ 8166 * [ // first polygon 8167 * [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring 8168 * [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole 8169 * ], 8170 * [ // second polygon 8171 * [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]] 8172 * ] 8173 * ]; 8174 * ``` 8175 */ 8176 8177L.Polygon = L.Polyline.extend({ 8178 8179 options: { 8180 fill: true 8181 }, 8182 8183 isEmpty: function () { 8184 return !this._latlngs.length || !this._latlngs[0].length; 8185 }, 8186 8187 getCenter: function () { 8188 // throws error when not yet added to map as this center calculation requires projected coordinates 8189 if (!this._map) { 8190 throw new Error('Must add layer to map before using getCenter()'); 8191 } 8192 8193 var i, j, p1, p2, f, area, x, y, center, 8194 points = this._rings[0], 8195 len = points.length; 8196 8197 if (!len) { return null; } 8198 8199 // polygon centroid algorithm; only uses the first ring if there are multiple 8200 8201 area = x = y = 0; 8202 8203 for (i = 0, j = len - 1; i < len; j = i++) { 8204 p1 = points[i]; 8205 p2 = points[j]; 8206 8207 f = p1.y * p2.x - p2.y * p1.x; 8208 x += (p1.x + p2.x) * f; 8209 y += (p1.y + p2.y) * f; 8210 area += f * 3; 8211 } 8212 8213 if (area === 0) { 8214 // Polygon is so small that all points are on same pixel. 8215 center = points[0]; 8216 } else { 8217 center = [x / area, y / area]; 8218 } 8219 return this._map.layerPointToLatLng(center); 8220 }, 8221 8222 _convertLatLngs: function (latlngs) { 8223 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs), 8224 len = result.length; 8225 8226 // remove last point if it equals first one 8227 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) { 8228 result.pop(); 8229 } 8230 return result; 8231 }, 8232 8233 _setLatLngs: function (latlngs) { 8234 L.Polyline.prototype._setLatLngs.call(this, latlngs); 8235 if (L.Polyline._flat(this._latlngs)) { 8236 this._latlngs = [this._latlngs]; 8237 } 8238 }, 8239 8240 _defaultShape: function () { 8241 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8242 }, 8243 8244 _clipPoints: function () { 8245 // polygons need a different clipping algorithm so we redefine that 8246 8247 var bounds = this._renderer._bounds, 8248 w = this.options.weight, 8249 p = new L.Point(w, w); 8250 8251 // increase clip padding by stroke width to avoid stroke on clip edges 8252 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8253 8254 this._parts = []; 8255 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8256 return; 8257 } 8258 8259 if (this.options.noClip) { 8260 this._parts = this._rings; 8261 return; 8262 } 8263 8264 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8265 clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true); 8266 if (clipped.length) { 8267 this._parts.push(clipped); 8268 } 8269 } 8270 }, 8271 8272 _updatePath: function () { 8273 this._renderer._updatePoly(this, true); 8274 } 8275}); 8276 8277 8278// @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8279L.polygon = function (latlngs, options) { 8280 return new L.Polygon(latlngs, options); 8281}; 8282 8283 8284 8285/* 8286 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. 8287 */ 8288 8289/* 8290 * @class Rectangle 8291 * @aka L.Retangle 8292 * @inherits Polygon 8293 * 8294 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 8295 * 8296 * @example 8297 * 8298 * ```js 8299 * // define rectangle geographical bounds 8300 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 8301 * 8302 * // create an orange rectangle 8303 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 8304 * 8305 * // zoom the map to the rectangle bounds 8306 * map.fitBounds(bounds); 8307 * ``` 8308 * 8309 */ 8310 8311 8312L.Rectangle = L.Polygon.extend({ 8313 initialize: function (latLngBounds, options) { 8314 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 8315 }, 8316 8317 // @method setBounds(latLngBounds: LatLngBounds): this 8318 // Redraws the rectangle with the passed bounds. 8319 setBounds: function (latLngBounds) { 8320 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 8321 }, 8322 8323 _boundsToLatLngs: function (latLngBounds) { 8324 latLngBounds = L.latLngBounds(latLngBounds); 8325 return [
8326 latLngBounds.getSouthWest(), 8327 latLngBounds.getNorthWest(), 8328 latLngBounds.getNorthEast(), 8329 latLngBounds.getSouthEast() 8330 ]; 8331 } 8332}); 8333 8334 8335// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 8336L.rectangle = function (latLngBounds, options) { 8337 return new L.Rectangle(latLngBounds, options); 8338}; 8339 8340 8341 8342/* 8343 * @class CircleMarker 8344 * @aka L.CircleMarker 8345 * @inherits Path 8346 * 8347 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 8348 */ 8349 8350L.CircleMarker = L.Path.extend({ 8351 8352 // @section 8353 // @aka CircleMarker options 8354 options: { 8355 fill: true, 8356 8357 // @option radius: Number = 10 8358 // Radius of the circle marker, in pixels 8359 radius: 10 8360 }, 8361 8362 initialize: function (latlng, options) { 8363 L.setOptions(this, options); 8364 this._latlng = L.latLng(latlng); 8365 this._radius = this.options.radius; 8366 }, 8367 8368 // @method setLatLng(latLng: LatLng): this 8369 // Sets the position of a circle marker to a new location. 8370 setLatLng: function (latlng) { 8371 this._latlng = L.latLng(latlng); 8372 this.redraw(); 8373 return this.fire('move', {latlng: this._latlng}); 8374 }, 8375 8376 // @method getLatLng(): LatLng 8377 // Returns the current geographical position of the circle marker 8378 getLatLng: function () { 8379 return this._latlng; 8380 }, 8381 8382 // @method setRadius(radius: Number): this 8383 // Sets the radius of a circle marker. Units are in pixels. 8384 setRadius: function (radius) { 8385 this.options.radius = this._radius = radius; 8386 return this.redraw(); 8387 }, 8388 8389 // @method getRadius(): Number 8390 // Returns the current radius of the circle 8391 getRadius: function () { 8392 return this._radius; 8393 }, 8394 8395 setStyle : function (options) { 8396 var radius = options && options.radius || this._radius; 8397 L.Path.prototype.setStyle.call(this, options); 8398 this.setRadius(radius); 8399 return this; 8400 }, 8401 8402 _project: function () { 8403 this._point = this._map.latLngToLayerPoint(this._latlng); 8404 this._updateBounds(); 8405 }, 8406 8407 _updateBounds: function () { 8408 var r = this._radius, 8409 r2 = this._radiusY || r, 8410 w = this._clickTolerance(), 8411 p = [r + w, r2 + w]; 8412 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p)); 8413 }, 8414 8415 _update: function () { 8416 if (this._map) { 8417 this._updatePath(); 8418 } 8419 }, 8420 8421 _updatePath: function () { 8422 this._renderer._updateCircle(this); 8423 }, 8424 8425 _empty: function () { 8426 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 8427 } 8428}); 8429 8430 8431// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 8432// Instantiates a circle marker object given a geographical point, and an optional options object. 8433L.circleMarker = function (latlng, options) { 8434 return new L.CircleMarker(latlng, options); 8435}; 8436 8437 8438 8439/* 8440 * @class Circle 8441 * @aka L.Circle 8442 * @inherits CircleMarker 8443 * 8444 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 8445 * 8446 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 8447 * 8448 * @example 8449 * 8450 * ```js 8451 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 8452 * ``` 8453 */ 8454 8455L.Circle = L.CircleMarker.extend({ 8456 8457 initialize: function (latlng, options, legacyOptions) { 8458 if (typeof options === 'number') { 8459 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 8460 options = L.extend({}, legacyOptions, {radius: options}); 8461 } 8462 L.setOptions(this, options); 8463 this._latlng = L.latLng(latlng); 8464 8465 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 8466 8467 // @section 8468 // @aka Circle options 8469 // @option radius: Number; Radius of the circle, in meters. 8470 this._mRadius = this.options.radius; 8471 }, 8472 8473 // @method setRadius(radius: Number): this 8474 // Sets the radius of a circle. Units are in meters. 8475 setRadius: function (radius) { 8476 this._mRadius = radius; 8477 return this.redraw(); 8478 }, 8479 8480 // @method getRadius(): Number 8481 // Returns the current radius of a circle. Units are in meters. 8482 getRadius: function () { 8483 return this._mRadius; 8484 }, 8485 8486 // @method getBounds(): LatLngBounds 8487 // Returns the `LatLngBounds` of the path. 8488 getBounds: function () { 8489 var half = [this._radius, this._radiusY || this._radius]; 8490 8491 return new L.LatLngBounds( 8492 this._map.layerPointToLatLng(this._point.subtract(half)), 8493 this._map.layerPointToLatLng(this._point.add(half))); 8494 }, 8495 8496 setStyle: L.Path.prototype.setStyle, 8497 8498 _project: function () { 8499 8500 var lng = this._latlng.lng, 8501 lat = this._latlng.lat, 8502 map = this._map, 8503 crs = map.options.crs; 8504 8505 if (crs.distance === L.CRS.Earth.distance) { 8506 var d = Math.PI / 180, 8507 latR = (this._mRadius / L.CRS.Earth.R) / d, 8508 top = map.project([lat + latR, lng]), 8509 bottom = map.project([lat - latR, lng]), 8510 p = top.add(bottom).divideBy(2), 8511 lat2 = map.unproject(p).lat, 8512 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8513 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8514 8515 if (isNaN(lngR) || lngR === 0) { 8516 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8517 } 8518 8519 this._point = p.subtract(map.getPixelOrigin()); 8520 this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1); 8521 this._radiusY = Math.max(Math.round(p.y - top.y), 1); 8522 8523 } else { 8524 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8525 8526 this._point = map.latLngToLayerPoint(this._latlng); 8527 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8528 } 8529 8530 this._updateBounds(); 8531 } 8532}); 8533 8534// @factory L.circle(latlng: LatLng, options?: Circle options) 8535// Instantiates a circle object given a geographical point, and an options object 8536// which contains the circle radius. 8537// @alternative 8538// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8539// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8540// Do not use in new applications or plugins. 8541L.circle = function (latlng, options, legacyOptions) { 8542 return new L.Circle(latlng, options, legacyOptions); 8543}; 8544 8545 8546 8547/* 8548 * @class SVG 8549 * @inherits Renderer 8550 * @aka L.SVG 8551 * 8552 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 8553 * Inherits `Renderer`. 8554 * 8555 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not 8556 * available in all web browsers, notably Android 2.x and 3.x. 8557 * 8558 * Although SVG is not available on IE7 and IE8, these browsers support 8559 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 8560 * (a now deprecated technology), and the SVG renderer will fall back to VML in 8561 * this case. 8562 * 8563 * @example 8564 * 8565 * Use SVG by default for all paths in the map: 8566 * 8567 * ```js 8568 * var map = L.map('map', { 8569 * renderer: L.svg() 8570 * }); 8571 * ``` 8572 * 8573 * Use a SVG renderer with extra padding for specific vector geometries: 8574 * 8575 * ```js 8576 * var map = L.map('map'); 8577 * var myRenderer = L.svg({ padding: 0.5 }); 8578 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 8579 * var circle = L.circle( center, { renderer: myRenderer } ); 8580 * ``` 8581 */ 8582 8583L.SVG = L.Renderer.extend({ 8584 8585 getEvents: function () { 8586 var events = L.Renderer.prototype.getEvents.call(this); 8587 events.zoomstart = this._onZoomStart; 8588 return events; 8589 }, 8590 8591 _initContainer: function () { 8592 this._container = L.SVG.create('svg'); 8593 8594 // makes it possible to click through svg root; we'll reset it back in individual paths
8595 this._container.setAttribute('pointer-events', 'none'); 8596 8597 this._rootGroup = L.SVG.create('g'); 8598 this._container.appendChild(this._rootGroup); 8599 }, 8600 8601 _onZoomStart: function () { 8602 // Drag-then-pinch interactions might mess up the center and zoom. 8603 // In this case, the easiest way to prevent this is re-do the renderer 8604 // bounds and padding when the zooming starts. 8605 this._update(); 8606 }, 8607 8608 _update: function () { 8609 if (this._map._animatingZoom && this._bounds) { return; } 8610 8611 L.Renderer.prototype._update.call(this); 8612 8613 var b = this._bounds, 8614 size = b.getSize(), 8615 container = this._container; 8616 8617 // set size of svg-container if changed 8618 if (!this._svgSize || !this._svgSize.equals(size)) { 8619 this._svgSize = size; 8620 container.setAttribute('width', size.x); 8621 container.setAttribute('height', size.y); 8622 } 8623 8624 // movement: update container viewBox so that we don't have to change coordinates of individual layers 8625 L.DomUtil.setPosition(container, b.min); 8626 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 8627 8628 this.fire('update'); 8629 }, 8630 8631 // methods below are called by vector layers implementations 8632 8633 _initPath: function (layer) { 8634 var path = layer._path = L.SVG.create('path'); 8635 8636 // @namespace Path 8637 // @option className: String = null 8638 // Custom class name set on an element. Only for SVG renderer. 8639 if (layer.options.className) { 8640 L.DomUtil.addClass(path, layer.options.className); 8641 } 8642 8643 if (layer.options.interactive) { 8644 L.DomUtil.addClass(path, 'leaflet-interactive'); 8645 } 8646 8647 this._updateStyle(layer); 8648 }, 8649 8650 _addPath: function (layer) { 8651 this._rootGroup.appendChild(layer._path); 8652 layer.addInteractiveTarget(layer._path); 8653 }, 8654 8655 _removePath: function (layer) { 8656 L.DomUtil.remove(layer._path); 8657 layer.removeInteractiveTarget(layer._path); 8658 }, 8659 8660 _updatePath: function (layer) { 8661 layer._project(); 8662 layer._update(); 8663 }, 8664 8665 _updateStyle: function (layer) { 8666 var path = layer._path, 8667 options = layer.options; 8668 8669 if (!path) { return; } 8670 8671 if (options.stroke) { 8672 path.setAttribute('stroke', options.color); 8673 path.setAttribute('stroke-opacity', options.opacity); 8674 path.setAttribute('stroke-width', options.weight); 8675 path.setAttribute('stroke-linecap', options.lineCap); 8676 path.setAttribute('stroke-linejoin', options.lineJoin); 8677 8678 if (options.dashArray) { 8679 path.setAttribute('stroke-dasharray', options.dashArray); 8680 } else { 8681 path.removeAttribute('stroke-dasharray'); 8682 } 8683 8684 if (options.dashOffset) { 8685 path.setAttribute('stroke-dashoffset', options.dashOffset); 8686 } else { 8687 path.removeAttribute('stroke-dashoffset'); 8688 } 8689 } else { 8690 path.setAttribute('stroke', 'none'); 8691 } 8692 8693 if (options.fill) { 8694 path.setAttribute('fill', options.fillColor || options.color); 8695 path.setAttribute('fill-opacity', options.fillOpacity); 8696 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 8697 } else { 8698 path.setAttribute('fill', 'none'); 8699 } 8700 }, 8701 8702 _updatePoly: function (layer, closed) { 8703 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed)); 8704 }, 8705 8706 _updateCircle: function (layer) { 8707 var p = layer._point, 8708 r = layer._radius, 8709 r2 = layer._radiusY || r, 8710 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 8711 8712 // drawing a circle with two half-arcs 8713 var d = layer._empty() ? 'M0 0' : 8714 'M' + (p.x - r) + ',' + p.y + 8715 arc + (r * 2) + ',0 ' + 8716 arc + (-r * 2) + ',0 '; 8717 8718 this._setPath(layer, d); 8719 }, 8720 8721 _setPath: function (layer, path) { 8722 layer._path.setAttribute('d', path); 8723 }, 8724 8725 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 8726 _bringToFront: function (layer) { 8727 L.DomUtil.toFront(layer._path); 8728 }, 8729 8730 _bringToBack: function (layer) { 8731 L.DomUtil.toBack(layer._path); 8732 } 8733}); 8734 8735 8736// @namespace SVG; @section 8737// There are several static functions which can be called without instantiating L.SVG: 8738L.extend(L.SVG, { 8739 // @function create(name: String): SVGElement 8740 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 8741 // corresponding to the class name passed. For example, using 'line' will return 8742 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 8743 create: function (name) { 8744 return document.createElementNS('http://www.w3.org/2000/svg', name); 8745 }, 8746 8747 // @function pointsToPath(rings: Point[], closed: Boolean): String 8748 // Generates a SVG path string for multiple rings, with each ring turning 8749 // into "M..L..L.." instructions 8750 pointsToPath: function (rings, closed) { 8751 var str = '', 8752 i, j, len, len2, points, p; 8753 8754 for (i = 0, len = rings.length; i < len; i++) { 8755 points = rings[i]; 8756 8757 for (j = 0, len2 = points.length; j < len2; j++) { 8758 p = points[j]; 8759 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 8760 } 8761 8762 // closes the ring for polygons; "x" is VML syntax 8763 str += closed ? (L.Browser.svg ? 'z' : 'x') : ''; 8764 } 8765 8766 // SVG complains about empty path strings 8767 return str || 'M0 0'; 8768 } 8769}); 8770 8771// @namespace Browser; @property svg: Boolean 8772// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 8773L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect); 8774 8775 8776// @namespace SVG 8777// @factory L.svg(options?: Renderer options) 8778// Creates a SVG renderer with the given options. 8779L.svg = function (options) { 8780 return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null; 8781}; 8782 8783 8784 8785/* 8786 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 8787 */ 8788 8789/* 8790 * @class SVG 8791 * 8792 * 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. 8793 *
8794 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 8795 * with old versions of Internet Explorer. 8796 */ 8797 8798// @namespace Browser; @property vml: Boolean 8799// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 8800L.Browser.vml = !L.Browser.svg && (function () { 8801 try { 8802 var div = document.createElement('div'); 8803 div.innerHTML = '<v:shape adj="1"/>'; 8804 8805 var shape = div.firstChild; 8806 shape.style.behavior = 'url(#default#VML)'; 8807 8808 return shape && (typeof shape.adj === 'object'); 8809 8810 } catch (e) { 8811 return false; 8812 } 8813}()); 8814 8815// redefine some SVG methods to handle VML syntax which is similar but with some differences 8816L.SVG.include(!L.Browser.vml ? {} : { 8817 8818 _initContainer: function () { 8819 this._container = L.DomUtil.create('div', 'leaflet-vml-container'); 8820 }, 8821 8822 _update: function () { 8823 if (this._map._animatingZoom) { return; } 8824 L.Renderer.prototype._update.call(this); 8825 this.fire('update'); 8826 }, 8827 8828 _initPath: function (layer) { 8829 var container = layer._container = L.SVG.create('shape'); 8830 8831 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 8832 8833 container.coordsize = '1 1'; 8834 8835 layer._path = L.SVG.create('path'); 8836 container.appendChild(layer._path); 8837 8838 this._updateStyle(layer); 8839 }, 8840 8841 _addPath: function (layer) { 8842 var container = layer._container; 8843 this._container.appendChild(container); 8844 8845 if (layer.options.interactive) { 8846 layer.addInteractiveTarget(container); 8847 } 8848 }, 8849 8850 _removePath: function (layer) { 8851 var container = layer._container; 8852 L.DomUtil.remove(container); 8853 layer.removeInteractiveTarget(container); 8854 }, 8855 8856 _updateStyle: function (layer) { 8857 var stroke = layer._stroke, 8858 fill = layer._fill, 8859 options = layer.options, 8860 container = layer._container; 8861 8862 container.stroked = !!options.stroke; 8863 container.filled = !!options.fill; 8864 8865 if (options.stroke) { 8866 if (!stroke) { 8867 stroke = layer._stroke = L.SVG.create('stroke'); 8868 } 8869 container.appendChild(stroke); 8870 stroke.weight = options.weight + 'px'; 8871 stroke.color = options.color; 8872 stroke.opacity = options.opacity; 8873 8874 if (options.dashArray) { 8875 stroke.dashStyle = L.Util.isArray(options.dashArray) ? 8876 options.dashArray.join(' ') : 8877 options.dashArray.replace(/( *, *)/g, ' '); 8878 } else { 8879 stroke.dashStyle = ''; 8880 } 8881 stroke.endcap = options.lineCap.replace('butt', 'flat'); 8882 stroke.joinstyle = options.lineJoin; 8883 8884 } else if (stroke) { 8885 container.removeChild(stroke); 8886 layer._stroke = null; 8887 } 8888 8889 if (options.fill) { 8890 if (!fill) { 8891 fill = layer._fill = L.SVG.create('fill'); 8892 } 8893 container.appendChild(fill); 8894 fill.color = options.fillColor || options.color; 8895 fill.opacity = options.fillOpacity; 8896 8897 } else if (fill) { 8898 container.removeChild(fill); 8899 layer._fill = null; 8900 } 8901 }, 8902 8903 _updateCircle: function (layer) { 8904 var p = layer._point.round(), 8905 r = Math.round(layer._radius), 8906 r2 = Math.round(layer._radiusY || r); 8907 8908 this._setPath(layer, layer._empty() ? 'M0 0' : 8909 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 8910 }, 8911 8912 _setPath: function (layer, path) { 8913 layer._path.v = path; 8914 }, 8915 8916 _bringToFront: function (layer) { 8917 L.DomUtil.toFront(layer._container); 8918 }, 8919 8920 _bringToBack: function (layer) { 8921 L.DomUtil.toBack(layer._container); 8922 } 8923}); 8924 8925if (L.Browser.vml) { 8926 L.SVG.create = (function () { 8927 try { 8928 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 8929 return function (name) { 8930 return document.createElement('<lvml:' + name + ' class="lvml">'); 8931 }; 8932 } catch (e) { 8933 return function (name) { 8934 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 8935 }; 8936 } 8937 })(); 8938} 8939 8940 8941 8942/* 8943 * @class Canvas 8944 * @inherits Renderer 8945 * @aka L.Canvas 8946 * 8947 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 8948 * Inherits `Renderer`. 8949 * 8950 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not 8951 * available in all web browsers, notably IE8, and overlapping geometries might 8952 * not display properly in some edge cases. 8953 * 8954 * @example 8955 * 8956 * Use Canvas by default for all paths in the map: 8957 * 8958 * ```js 8959 * var map = L.map('map', { 8960 * renderer: L.canvas() 8961 * }); 8962 * ``` 8963 * 8964 * Use a Canvas renderer with extra padding for specific vector geometries: 8965 * 8966 * ```js 8967 * var map = L.map('map'); 8968 * var myRenderer = L.canvas({ padding: 0.5 }); 8969 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 8970 * var circle = L.circle( center, { renderer: myRenderer } ); 8971 * ``` 8972 */ 8973 8974L.Canvas = L.Renderer.extend({ 8975 8976 onAdd: function () { 8977 L.Renderer.prototype.onAdd.call(this); 8978 8979 this._layers = this._layers || {}; 8980 8981 // Redraw vectors since canvas is cleared upon removal, 8982 // in case of removing the renderer itself from the map. 8983 this._draw(); 8984 }, 8985 8986 _initContainer: function () { 8987 var container = this._container = document.createElement('canvas'); 8988 8989 L.DomEvent 8990 .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this) 8991 .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this) 8992 .on(container, 'mouseout', this._handleMouseOut, this); 8993 8994 this._ctx = container.getContext('2d'); 8995 }, 8996 8997 _update: function () { 8998 if (this._map._animatingZoom && this._bounds) { return; } 8999 9000 this._drawnLayers = {}; 9001 9002 L.Renderer.prototype._update.call(this); 9003 9004 var b = this._bounds, 9005 container = this._container, 9006 size = b.getSize(), 9007 m = L.Browser.retina ? 2 : 1; 9008 9009 L.DomUtil.setPosition(container, b.min); 9010 9011 // set canvas size (also clearing it); use double size on retina 9012 container.width = m * size.x; 9013 container.height = m * size.y; 9014 container.style.width = size.x + 'px'; 9015 container.style.height = size.y + 'px'; 9016 9017 if (L.Browser.retina) { 9018 this._ctx.scale(2, 2); 9019 } 9020 9021 // translate so we use the same path coordinates after canvas element moves 9022 this._ctx.translate(-b.min.x, -b.min.y); 9023 9024 // Tell paths to redraw themselves 9025 this.fire('update'); 9026 }, 9027 9028 _initPath: function (layer) { 9029 this._updateDashArray(layer); 9030 this._layers[L.stamp(layer)] = layer; 9031 }, 9032 9033 _addPath: L.Util.falseFn, 9034 9035 _removePath: function (layer) { 9036 layer._removed = true; 9037 this._requestRedraw(layer); 9038 }, 9039 9040 _updatePath: function (layer) { 9041 this._redrawBounds = layer._pxBounds; 9042 this._draw(true); 9043 layer._project(); 9044 layer._update(); 9045 this._draw(); 9046 this._redrawBounds = null; 9047 }, 9048 9049 _updateStyle: function (layer) { 9050 this._updateDashArray(layer); 9051 this._requestRedraw(layer); 9052 }, 9053 9054 _updateDashArray: function (layer) { 9055 if (layer.options.dashArray) { 9056 var parts = layer.options.dashArray.split(','), 9057 dashArray = [], 9058 i; 9059 for (i = 0; i < parts.length; i++) {
9060 dashArray.push(Number(parts[i])); 9061 } 9062 layer.options._dashArray = dashArray; 9063 } 9064 }, 9065 9066 _requestRedraw: function (layer) { 9067 if (!this._map) { return; } 9068 9069 var padding = (layer.options.weight || 0) + 1; 9070 this._redrawBounds = this._redrawBounds || new L.Bounds(); 9071 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 9072 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 9073 9074 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this); 9075 }, 9076 9077 _redraw: function () { 9078 this._redrawRequest = null; 9079 9080 this._draw(true); // clear layers in redraw bounds 9081 this._draw(); // draw layers 9082 9083 this._redrawBounds = null; 9084 }, 9085 9086 _draw: function (clear) { 9087 this._clear = clear; 9088 var layer, bounds = this._redrawBounds; 9089 this._ctx.save(); 9090 if (bounds) { 9091 this._ctx.beginPath(); 9092 this._ctx.rect(bounds.min.x, bounds.min.y, bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y); 9093 this._ctx.clip(); 9094 } 9095 9096 for (var id in this._layers) { 9097 layer = this._layers[id]; 9098 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 9099 layer._updatePath(); 9100 } 9101 if (clear && layer._removed) { 9102 delete layer._removed; 9103 delete this._layers[id]; 9104 } 9105 } 9106 this._ctx.restore(); // Restore state before clipping. 9107 }, 9108 9109 _updatePoly: function (layer, closed) { 9110 9111 var i, j, len2, p, 9112 parts = layer._parts, 9113 len = parts.length, 9114 ctx = this._ctx; 9115 9116 if (!len) { return; } 9117 9118 this._drawnLayers[layer._leaflet_id] = layer; 9119 9120 ctx.beginPath(); 9121 9122 if (ctx.setLineDash) { 9123 ctx.setLineDash(layer.options && layer.options._dashArray || []); 9124 } 9125 9126 for (i = 0; i < len; i++) { 9127 for (j = 0, len2 = parts[i].length; j < len2; j++) { 9128 p = parts[i][j]; 9129 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 9130 } 9131 if (closed) { 9132 ctx.closePath(); 9133 } 9134 } 9135 9136 this._fillStroke(ctx, layer); 9137 9138 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 9139 }, 9140 9141 _updateCircle: function (layer) { 9142 9143 if (layer._empty()) { return; } 9144 9145 var p = layer._point, 9146 ctx = this._ctx, 9147 r = layer._radius, 9148 s = (layer._radiusY || r) / r; 9149 9150 this._drawnLayers[layer._leaflet_id] = layer; 9151 9152 if (s !== 1) { 9153 ctx.save(); 9154 ctx.scale(1, s); 9155 } 9156 9157 ctx.beginPath(); 9158 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 9159 9160 if (s !== 1) { 9161 ctx.restore(); 9162 } 9163 9164 this._fillStroke(ctx, layer); 9165 }, 9166 9167 _fillStroke: function (ctx, layer) { 9168 var clear = this._clear, 9169 options = layer.options; 9170 9171 ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over'; 9172 9173 if (options.fill) { 9174 ctx.globalAlpha = clear ? 1 : options.fillOpacity; 9175 ctx.fillStyle = options.fillColor || options.color; 9176 ctx.fill(options.fillRule || 'evenodd'); 9177 } 9178 9179 if (options.stroke && options.weight !== 0) { 9180 ctx.globalAlpha = clear ? 1 : options.opacity; 9181 9182 // if clearing shape, do it with the previously drawn line width 9183 layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight; 9184 9185 ctx.strokeStyle = options.color; 9186 ctx.lineCap = options.lineCap; 9187 ctx.lineJoin = options.lineJoin; 9188 ctx.stroke(); 9189 } 9190 }, 9191 9192 // Canvas obviously doesn't have mouse events for individual drawn objects, 9193 // so we emulate that by calculating what's under the mouse on mousemove/click manually 9194 9195 _onClick: function (e) { 9196 var point = this._map.mouseEventToLayerPoint(e), layers = [], layer; 9197 9198 for (var id in this._layers) { 9199 layer = this._layers[id]; 9200 if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) { 9201 L.DomEvent._fakeStop(e); 9202 layers.push(layer); 9203 } 9204 } 9205 if (layers.length) { 9206 this._fireEvent(layers, e); 9207 } 9208 }, 9209 9210 _onMouseMove: function (e) { 9211 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 9212 9213 var point = this._map.mouseEventToLayerPoint(e); 9214 this._handleMouseOut(e, point); 9215 this._handleMouseHover(e, point); 9216 }, 9217 9218 9219 _handleMouseOut: function (e, point) { 9220 var layer = this._hoveredLayer; 9221 if (layer && (e.type === 'mouseout' || !layer._containsPoint(point))) { 9222 // if we're leaving the layer, fire mouseout 9223 L.DomUtil.removeClass(this._container, 'leaflet-interactive'); 9224 this._fireEvent([layer], e, 'mouseout'); 9225 this._hoveredLayer = null; 9226 } 9227 }, 9228 9229 _handleMouseHover: function (e, point) { 9230 var id, layer; 9231 9232 for (id in this._drawnLayers) { 9233 layer = this._drawnLayers[id]; 9234 if (layer.options.interactive && layer._containsPoint(point)) { 9235 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor 9236 this._fireEvent([layer], e, 'mouseover'); 9237 this._hoveredLayer = layer; 9238 } 9239 } 9240 9241 if (this._hoveredLayer) { 9242 this._fireEvent([this._hoveredLayer], e); 9243 } 9244 }, 9245 9246 _fireEvent: function (layers, e, type) {
9247 this._map._fireDOMEvent(e, type || e.type, layers); 9248 }, 9249 9250 // TODO _bringToFront & _bringToBack, pretty tricky 9251 9252 _bringToFront: L.Util.falseFn, 9253 _bringToBack: L.Util.falseFn 9254}); 9255 9256// @namespace Browser; @property canvas: Boolean 9257// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 9258L.Browser.canvas = (function () { 9259 return !!document.createElement('canvas').getContext; 9260}()); 9261 9262// @namespace Canvas 9263// @factory L.canvas(options?: Renderer options) 9264// Creates a Canvas renderer with the given options. 9265L.canvas = function (options) { 9266 return L.Browser.canvas ? new L.Canvas(options) : null; 9267}; 9268 9269L.Polyline.prototype._containsPoint = function (p, closed) { 9270 var i, j, k, len, len2, part, 9271 w = this._clickTolerance(); 9272 9273 if (!this._pxBounds.contains(p)) { return false; } 9274 9275 // hit detection for polylines 9276 for (i = 0, len = this._parts.length; i < len; i++) { 9277 part = this._parts[i]; 9278 9279 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 9280 if (!closed && (j === 0)) { continue; } 9281 9282 if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) { 9283 return true; 9284 } 9285 } 9286 } 9287 return false; 9288}; 9289 9290L.Polygon.prototype._containsPoint = function (p) { 9291 var inside = false, 9292 part, p1, p2, i, j, k, len, len2; 9293 9294 if (!this._pxBounds.contains(p)) { return false; } 9295 9296 // ray casting algorithm for detecting if point is in polygon 9297 for (i = 0, len = this._parts.length; i < len; i++) { 9298 part = this._parts[i]; 9299 9300 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 9301 p1 = part[j]; 9302 p2 = part[k]; 9303 9304 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)) { 9305 inside = !inside; 9306 } 9307 } 9308 } 9309 9310 // also check if it's on polygon stroke 9311 return inside || L.Polyline.prototype._containsPoint.call(this, p, true); 9312}; 9313 9314L.CircleMarker.prototype._containsPoint = function (p) { 9315 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 9316}; 9317 9318 9319 9320/* 9321 * @class GeoJSON 9322 * @aka L.GeoJSON 9323 * @inherits FeatureGroup 9324 * 9325 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 9326 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 9327 * 9328 * @example 9329 * 9330 * ```js 9331 * L.geoJSON(data, { 9332 * style: function (feature) { 9333 * return {color: feature.properties.color}; 9334 * } 9335 * }).bindPopup(function (layer) { 9336 * return layer.feature.properties.description; 9337 * }).addTo(map); 9338 * ``` 9339 */ 9340 9341L.GeoJSON = L.FeatureGroup.extend({ 9342 9343 /* @section 9344 * @aka GeoJSON options 9345 * 9346 * @option pointToLayer: Function = * 9347 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 9348 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 9349 * The default is to spawn a default `Marker`: 9350 * ```js 9351 * function(geoJsonPoint, latlng) { 9352 * return L.marker(latlng); 9353 * } 9354 * ``` 9355 * 9356 * @option style: Function = * 9357 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, 9358 * called internally when data is added. 9359 * The default value is to not override any defaults: 9360 * ```js 9361 * function (geoJsonFeature) { 9362 * return {} 9363 * } 9364 * ``` 9365 * 9366 * @option onEachFeature: Function = * 9367 * A `Function` that will be called once for each created `Feature`, after it has 9368 * been created and styled. Useful for attaching events and popups to features. 9369 * The default is to do nothing with the newly created layers: 9370 * ```js 9371 * function (feature, layer) {} 9372 * ``` 9373 * 9374 * @option filter: Function = * 9375 * A `Function` that will be used to decide whether to include a feature or not. 9376 * The default is to include all features: 9377 * ```js 9378 * function (geoJsonFeature) { 9379 * return true; 9380 * } 9381 * ``` 9382 * Note: dynamically changing the `filter` option will have effect only on newly 9383 * added data. It will _not_ re-evaluate already included features. 9384 * 9385 * @option coordsToLatLng: Function = * 9386 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 9387 * The default is the `coordsToLatLng` static method. 9388 */ 9389 9390 initialize: function (geojson, options) { 9391 L.setOptions(this, options); 9392 9393 this._layers = {}; 9394 9395 if (geojson) { 9396 this.addData(geojson); 9397 } 9398 }, 9399 9400 // @method addData( <GeoJSON> data ): Layer 9401 // Adds a GeoJSON object to the layer. 9402 addData: function (geojson) { 9403 var features = L.Util.isArray(geojson) ? geojson : geojson.features, 9404 i, len, feature; 9405 9406 if (features) { 9407 for (i = 0, len = features.length; i < len; i++) { 9408 // only add this if geometry or geometries are set and not null 9409 feature = features[i]; 9410 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 9411 this.addData(feature); 9412 } 9413 } 9414 return this; 9415 } 9416 9417 var options = this.options; 9418 9419 if (options.filter && !options.filter(geojson)) { return this; } 9420 9421 var layer = L.GeoJSON.geometryToLayer(geojson, options); 9422 if (!layer) { 9423 return this; 9424 } 9425 layer.feature = L.GeoJSON.asFeature(geojson); 9426 9427 layer.defaultOptions = layer.options; 9428 this.resetStyle(layer); 9429 9430 if (options.onEachFeature) { 9431 options.onEachFeature(geojson, layer); 9432 } 9433 9434 return this.addLayer(layer); 9435 }, 9436 9437 // @method resetStyle( <Path>
9437 layer ): Layer 9438 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 9439 resetStyle: function (layer) { 9440 // reset any custom styles 9441 layer.options = L.Util.extend({}, layer.defaultOptions); 9442 this._setLayerStyle(layer, this.options.style); 9443 return this; 9444 }, 9445 9446 // @method setStyle( <Function> style ): Layer 9447 // Changes styles of GeoJSON vector layers with the given style function. 9448 setStyle: function (style) { 9449 return this.eachLayer(function (layer) { 9450 this._setLayerStyle(layer, style); 9451 }, this); 9452 }, 9453 9454 _setLayerStyle: function (layer, style) { 9455 if (typeof style === 'function') { 9456 style = style(layer.feature); 9457 } 9458 if (layer.setStyle) { 9459 layer.setStyle(style); 9460 } 9461 } 9462}); 9463 9464// @section 9465// There are several static functions which can be called without instantiating L.GeoJSON: 9466L.extend(L.GeoJSON, { 9467 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 9468 // Creates a `Layer` from a given GeoJSON feature. Can use a custom 9469 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 9470 // functions if provided as options. 9471 geometryToLayer: function (geojson, options) { 9472 9473 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 9474 coords = geometry ? geometry.coordinates : null, 9475 layers = [], 9476 pointToLayer = options && options.pointToLayer, 9477 coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng, 9478 latlng, latlngs, i, len; 9479 9480 if (!coords && !geometry) { 9481 return null; 9482 } 9483 9484 switch (geometry.type) { 9485 case 'Point': 9486 latlng = coordsToLatLng(coords); 9487 return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); 9488 9489 case 'MultiPoint': 9490 for (i = 0, len = coords.length; i < len; i++) { 9491 latlng = coordsToLatLng(coords[i]); 9492 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng)); 9493 } 9494 return new L.FeatureGroup(layers); 9495 9496 case 'LineString': 9497 case 'MultiLineString': 9498 latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng); 9499 return new L.Polyline(latlngs, options); 9500 9501 case 'Polygon': 9502 case 'MultiPolygon': 9503 latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng); 9504 return new L.Polygon(latlngs, options); 9505 9506 case 'GeometryCollection': 9507 for (i = 0, len = geometry.geometries.length; i < len; i++) { 9508 var layer = this.geometryToLayer({ 9509 geometry: geometry.geometries[i], 9510 type: 'Feature', 9511 properties: geojson.properties 9512 }, options); 9513 9514 if (layer) { 9515 layers.push(layer); 9516 } 9517 } 9518 return new L.FeatureGroup(layers); 9519 9520 default: 9521 throw new Error('Invalid GeoJSON object.'); 9522 } 9523 }, 9524 9525 // @function coordsToLatLng(coords: Array): LatLng 9526 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 9527 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 9528 coordsToLatLng: function (coords) { 9529 return new L.LatLng(coords[1], coords[0], coords[2]); 9530 }, 9531 9532 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 9533 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 9534 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 9535 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 9536 coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { 9537 var latlngs = []; 9538 9539 for (var i = 0, len = coords.length, latlng; i < len; i++) { 9540 latlng = levelsDeep ? 9541 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) : 9542 (coordsToLatLng || this.coordsToLatLng)(coords[i]); 9543 9544 latlngs.push(latlng); 9545 } 9546 9547 return latlngs; 9548 }, 9549 9550 // @function latLngToCoords(latlng: LatLng): Array 9551 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 9552 latLngToCoords: function (latlng) { 9553 return latlng.alt !== undefined ? 9554 [latlng.lng, latlng.lat, latlng.alt] : 9555 [latlng.lng, latlng.lat]; 9556 }, 9557 9558 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number,
9558closed?: Boolean): Array 9559 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 9560 // `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. 9561 latLngsToCoords: function (latlngs, levelsDeep, closed) { 9562 var coords = []; 9563 9564 for (var i = 0, len = latlngs.length; i < len; i++) { 9565 coords.push(levelsDeep ? 9566 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) : 9567 L.GeoJSON.latLngToCoords(latlngs[i])); 9568 } 9569 9570 if (!levelsDeep && closed) { 9571 coords.push(coords[0]); 9572 } 9573 9574 return coords; 9575 }, 9576 9577 getFeature: function (layer, newGeometry) { 9578 return layer.feature ? 9579 L.extend({}, layer.feature, {geometry: newGeometry}) : 9580 L.GeoJSON.asFeature(newGeometry); 9581 }, 9582 9583 // @function asFeature(geojson: Object): Object 9584 // Normalize GeoJSON geometries/features into GeoJSON features. 9585 asFeature: function (geojson) { 9586 if (geojson.type === 'Feature') { 9587 return geojson; 9588 } 9589 9590 return { 9591 type: 'Feature', 9592 properties: {}, 9593 geometry: geojson 9594 }; 9595 } 9596}); 9597 9598var PointToGeoJSON = { 9599 toGeoJSON: function () { 9600 return L.GeoJSON.getFeature(this, { 9601 type: 'Point', 9602 coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) 9603 }); 9604 } 9605}; 9606 9607L.Marker.include(PointToGeoJSON); 9608 9609// @namespace CircleMarker 9610// @method toGeoJSON(): Object 9611// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 9612L.Circle.include(PointToGeoJSON); 9613L.CircleMarker.include(PointToGeoJSON); 9614 9615 9616// @namespace Polyline 9617// @method toGeoJSON(): Object 9618// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 9619L.Polyline.prototype.toGeoJSON = function () { 9620 var multi = !L.Polyline._flat(this._latlngs); 9621 9622 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0); 9623 9624 return L.GeoJSON.getFeature(this, { 9625 type: (multi ? 'Multi' : '') + 'LineString', 9626 coordinates: coords 9627 }); 9628}; 9629 9630// @namespace Polygon 9631// @method toGeoJSON(): Object 9632// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 9633L.Polygon.prototype.toGeoJSON = function () { 9634 var holes = !L.Polyline._flat(this._latlngs), 9635 multi = holes && !L.Polyline._flat(this._latlngs[0]); 9636 9637 var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true); 9638 9639 if (!holes) { 9640 coords = [coords]; 9641 } 9642 9643 return L.GeoJSON.getFeature(this, { 9644 type: (multi ? 'Multi' : '') + 'Polygon', 9645 coordinates: coords 9646 }); 9647}; 9648 9649 9650// @namespace LayerGroup 9651L.LayerGroup.include({ 9652 toMultiPoint: function () { 9653 var coords = []; 9654 9655 this.eachLayer(function (layer) { 9656 coords.push(layer.toGeoJSON().geometry.coordinates); 9657 }); 9658 9659 return L.GeoJSON.getFeature(this, { 9660 type: 'MultiPoint', 9661 coordinates: coords 9662 }); 9663 }, 9664 9665 // @method toGeoJSON(): Object 9666 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`). 9667 toGeoJSON: function () { 9668 9669 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 9670 9671 if (type === 'MultiPoint') { 9672 return this.toMultiPoint(); 9673 } 9674 9675 var isGeometryCollection = type === 'GeometryCollection', 9676 jsons = []; 9677 9678 this.eachLayer(function (layer) { 9679 if (layer.toGeoJSON) { 9680 var json = layer.toGeoJSON(); 9681 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); 9682 } 9683 }); 9684 9685 if (isGeometryCollection) { 9686 return L.GeoJSON.getFeature(this, { 9687 geometries: jsons, 9688 type: 'GeometryCollection' 9689 }); 9690 } 9691 9692 return { 9693 type: 'FeatureCollection', 9694 features: jsons 9695 }; 9696 } 9697}); 9698 9699// @namespace GeoJSON 9700// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9701// Creates a GeoJSON layer. Optionally accepts an object in 9702// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map 9703// (you can alternatively add it later with `addData` method) and an `options` object. 9704L.geoJSON = function (geojson, options) { 9705 return new L.GeoJSON(geojson, options); 9706}; 9707// Backward compatibility. 9708L.geoJson = L.geoJSON; 9709 9710 9711 9712/* 9713 * @namespace DomEvent 9714 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 9715 */ 9716
9717// Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 9718 9719 9720 9721var eventsKey = '_leaflet_events'; 9722 9723L.DomEvent = { 9724 9725 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 9726 // Adds a listener function (`fn`) to a particular DOM event type of the 9727 // element `el`. You can optionally specify the context of the listener 9728 // (object the `this` keyword will point to). You can also pass several 9729 // space-separated types (e.g. `'click dblclick'`). 9730 9731 // @alternative 9732 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this 9733 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 9734 on: function (obj, types, fn, context) { 9735 9736 if (typeof types === 'object') { 9737 for (var type in types) { 9738 this._on(obj, type, types[type], fn); 9739 } 9740 } else { 9741 types = L.Util.splitWords(types); 9742 9743 for (var i = 0, len = types.length; i < len; i++) { 9744 this._on(obj, types[i], fn, context); 9745 } 9746 } 9747 9748 return this; 9749 }, 9750 9751 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 9752 // Removes a previously added listener function. If no function is specified, 9753 // it will remove all the listeners of that particular DOM event from the element. 9754 // Note that if you passed a custom context to on, you must pass the same 9755 // context to `off` in order to remove the listener. 9756 9757 // @alternative 9758 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this 9759 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 9760 off: function (obj, types, fn, context) { 9761 9762 if (typeof types === 'object') { 9763 for (var type in types) { 9764 this._off(obj, type, types[type], fn); 9765 } 9766 } else { 9767 types = L.Util.splitWords(types); 9768 9769 for (var i = 0, len = types.length; i < len; i++) { 9770 this._off(obj, types[i], fn, context); 9771 } 9772 } 9773 9774 return this; 9775 }, 9776 9777 _on: function (obj, type, fn, context) { 9778 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''); 9779 9780 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 9781 9782 var handler = function (e) { 9783 return fn.call(context || obj, e || window.event); 9784 }; 9785 9786 var originalHandler = handler; 9787 9788 if (L.Browser.pointer && type.indexOf('touch') === 0) { 9789 this.addPointerListener(obj, type, handler, id); 9790 9791 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { 9792 this.addDoubleTapListener(obj, handler, id); 9793 9794 } else if ('addEventListener' in obj) { 9795 9796 if (type === 'mousewheel') { 9797 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); 9798 9799 } else if ((type === 'mouseenter') || (type === 'mouseleave')) { 9800 handler = function (e) { 9801 e = e || window.event; 9802 if (L.DomEvent._isExternalTarget(obj, e)) { 9803 originalHandler(e); 9804 } 9805 }; 9806 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); 9807 9808 } else { 9809 if (type === 'click' && L.Browser.android) { 9810 handler = function (e) { 9811 return L.DomEvent._filterClick(e, originalHandler); 9812 }; 9813 } 9814 obj.addEventListener(type, handler, false); 9815 } 9816 9817 } else if ('attachEvent' in obj) { 9818 obj.attachEvent('on' + type, handler); 9819 } 9820 9821 obj[eventsKey] = obj[eventsKey] || {}; 9822 obj[eventsKey][id] = handler; 9823 9824 return this; 9825 }, 9826 9827 _off: function (obj, type, fn, context) { 9828 9829 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''), 9830 handler = obj[eventsKey] && obj[eventsKey][id]; 9831 9832 if (!handler) { return this; } 9833 9834 if (L.Browser.pointer && type.indexOf('touch') === 0) { 9835 this.removePointerListener(obj, type, id); 9836 9837 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { 9838 this.removeDoubleTapListener(obj, id); 9839 9840 } else if ('removeEventListener' in obj) { 9841 9842 if (type === 'mousewheel') { 9843 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); 9844 9845 } else { 9846 obj.removeEventListener( 9847 type === 'mouseenter' ? 'mouseover' : 9848 type === 'mouseleave' ? 'mouseout' : type, handler, false); 9849 } 9850 9851 } else if ('detachEvent' in obj) { 9852 obj.detachEvent('on' + type, handler); 9853 } 9854 9855 obj[eventsKey][id] = null; 9856 9857 return this; 9858 }, 9859 9860 // @function stopPropagation(ev: DOMEvent): this
9861 // Stop the given event from propagation to parent elements. Used inside the listener functions: 9862 // ```js 9863 // L.DomEvent.on(div, 'click', function (ev) { 9864 // L.DomEvent.stopPropagation(ev); 9865 // }); 9866 // ``` 9867 stopPropagation: function (e) { 9868 9869 if (e.stopPropagation) { 9870 e.stopPropagation(); 9871 } else if (e.originalEvent) { // In case of Leaflet event. 9872 e.originalEvent._stopped = true; 9873 } else { 9874 e.cancelBubble = true; 9875 } 9876 L.DomEvent._skipped(e); 9877 9878 return this; 9879 }, 9880 9881 // @function disableScrollPropagation(el: HTMLElement): this 9882 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). 9883 disableScrollPropagation: function (el) { 9884 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation); 9885 }, 9886 9887 // @function disableClickPropagation(el: HTMLElement): this 9888 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, 9889 // `'mousedown'` and `'touchstart'` events (plus browser variants). 9890 disableClickPropagation: function (el) { 9891 var stop = L.DomEvent.stopPropagation; 9892 9893 L.DomEvent.on(el, L.Draggable.START.join(' '), stop); 9894 9895 return L.DomEvent.on(el, { 9896 click: L.DomEvent._fakeStop, 9897 dblclick: stop 9898 }); 9899 }, 9900 9901 // @function preventDefault(ev: DOMEvent): this 9902 // Prevents the default action of the DOM Event `ev` from happening (such as 9903 // following a link in the href of the a element, or doing a POST request 9904 // with page reload when a `<form>` is submitted). 9905 // Use it inside listener functions. 9906 preventDefault: function (e) { 9907 9908 if (e.preventDefault) { 9909 e.preventDefault(); 9910 } else { 9911 e.returnValue = false; 9912 } 9913 return this; 9914 }, 9915 9916 // @function stop(ev): this 9917 // Does `stopPropagation` and `preventDefault` at the same time. 9918 stop: function (e) { 9919 return L.DomEvent 9920 .preventDefault(e) 9921 .stopPropagation(e); 9922 }, 9923 9924 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 9925 // Gets normalized mouse position from a DOM event relative to the 9926 // `container` or to the whole page if not specified. 9927 getMousePosition: function (e, container) { 9928 if (!container) { 9929 return new L.Point(e.clientX, e.clientY); 9930 } 9931 9932 var rect = container.getBoundingClientRect(); 9933 9934 return new L.Point( 9935 e.clientX - rect.left - container.clientLeft, 9936 e.clientY - rect.top - container.clientTop); 9937 }, 9938 9939 // Chrome on Win scrolls double the pixels as in other platforms (see #4538), 9940 // and Firefox scrolls device pixels, not CSS pixels 9941 _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 : 9942 L.Browser.gecko ? window.devicePixelRatio : 9943 1, 9944 9945 // @function getWheelDelta(ev: DOMEvent): Number 9946 // Gets normalized wheel delta from a mousewheel DOM event, in vertical 9947 // pixels scrolled (negative if scrolling down). 9948 // Events from pointing devices without precise scrolling are mapped to 9949 // a best guess of 60 pixels. 9950 getWheelDelta: function (e) { 9951 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 9952 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels 9953 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 9954 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 9955 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 9956 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 9957 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 9958 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 9959 0; 9960 }, 9961 9962 _skipEvents: {}, 9963 9964 _fakeStop: function (e) { 9965 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e) 9966 L.DomEvent._skipEvents[e.type] = true; 9967 }, 9968 9969 _skipped: function (e) { 9970 var skipped = this._skipEvents[e.type]; 9971 // reset when checking, as it's only used in map container and propagates outside of the map 9972 this._skipEvents[e.type] = false; 9973 return skipped; 9974 }, 9975 9976 // check if element really left/entered the event target (for mouseenter/mouseleave) 9977 _isExternalTarget: function (el, e) { 9978 9979 var related = e.relatedTarget; 9980 9981 if (!related) { return true; } 9982 9983 try { 9984 while (related && (related !== el)) { 9985 related = related.parentNode; 9986 } 9987 } catch (err) { 9988 return false;
9989 } 9990 return (related !== el); 9991 }, 9992 9993 // this is a horrible workaround for a bug in Android where a single touch triggers two click events 9994 _filterClick: function (e, handler) { 9995 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), 9996 elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); 9997 9998 // are they closer together than 500ms yet more than 100ms? 9999 // Android typically triggers them ~300ms apart while multiple listeners 10000 // on the same event should be triggered far faster; 10001 // or check if click is simulated on the element, and if it is, reject any non-simulated events 10002 10003 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { 10004 L.DomEvent.stop(e); 10005 return; 10006 } 10007 L.DomEvent._lastClick = timeStamp; 10008 10009 handler(e); 10010 } 10011}; 10012 10013// @function addListener(ââ¬Â¦): this 10014// Alias to [`L.DomEvent.on`](#domevent-on) 10015L.DomEvent.addListener = L.DomEvent.on; 10016 10017// @function removeListener(ââ¬Â¦): this 10018// Alias to [`L.DomEvent.off`](#domevent-off) 10019L.DomEvent.removeListener = L.DomEvent.off; 10020 10021 10022 10023/* 10024 * @class Draggable 10025 * @aka L.Draggable 10026 * @inherits Evented 10027 * 10028 * A class for making DOM elements draggable (including touch support). 10029 * Used internally for map and marker dragging. Only works for elements 10030 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 10031 * 10032 * @example 10033 * ```js 10034 * var draggable = new L.Draggable(elementToDrag); 10035 * draggable.enable(); 10036 * ``` 10037 */ 10038 10039L.Draggable = L.Evented.extend({ 10040 10041 options: { 10042 // @option clickTolerance: Number = 3 10043 // The max number of pixels a user can shift the mouse pointer during a click 10044 // for it to be considered a valid click (as opposed to a mouse drag). 10045 clickTolerance: 3 10046 }, 10047 10048 statics: { 10049 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], 10050 END: { 10051 mousedown: 'mouseup', 10052 touchstart: 'touchend', 10053 pointerdown: 'touchend', 10054 MSPointerDown: 'touchend' 10055 }, 10056 MOVE: { 10057 mousedown: 'mousemove', 10058 touchstart: 'touchmove', 10059 pointerdown: 'touchmove', 10060 MSPointerDown: 'touchmove' 10061 } 10062 }, 10063 10064 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean) 10065 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 10066 initialize: function (element, dragStartTarget, preventOutline) { 10067 this._element = element; 10068 this._dragStartTarget = dragStartTarget || element; 10069 this._preventOutline = preventOutline; 10070 }, 10071 10072 // @method enable() 10073 // Enables the dragging ability 10074 enable: function () { 10075 if (this._enabled) { return; } 10076 10077 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); 10078 10079 this._enabled = true; 10080 }, 10081 10082 // @method disable() 10083 // Disables the dragging ability 10084 disable: function () { 10085 if (!this._enabled) { return; } 10086 10087 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); 10088 10089 this._enabled = false; 10090 this._moved = false; 10091 }, 10092 10093 _onDown: function (e) { 10094 // Ignore simulated events, since we handle both touch and 10095 // mouse explicitly; otherwise we risk getting duplicates of 10096 // touch events, see #4315. 10097 // Also ignore the event if disabled; this happens in IE11 10098 // under some circumstances, see #3666. 10099 if (e._simulated || !this._enabled) { return; } 10100 10101 this._moved = false; 10102 10103 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; } 10104 10105 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches) || !this._enabled) { return; } 10106 L.Draggable._dragging = true; // Prevent dragging multiple objects at once. 10107 10108 if (this._preventOutline) { 10109 L.DomUtil.preventOutline(this._element); 10110 } 10111 10112 L.DomUtil.disableImageDrag(); 10113 L.DomUtil.disableTextSelection(); 10114 10115 if (this._moving) { return; } 10116 10117 // @event down: Event 10118 // Fired when a drag is about to start. 10119 this.fire('down'); 10120 10121 var first = e.touches ? e.touches[0] : e; 10122 10123 this._startPoint = new L.Point(first.clientX, first.clientY); 10124 10125 L.DomEvent 10126 .on(document, L.Draggable.MOVE[e.type], this._onMove, this) 10127 .on(document, L.Draggable.END[e.type], this._onUp, this); 10128 }, 10129 10130 _onMove: function (e) { 10131 // Ignore simulated events, since we handle both touch and 10132 // mouse explicitly; otherwise we risk getting duplicates of 10133 // touch events, see #4315. 10134 // Also ignore the event if disabled; this happens in IE11 10135 // under some circumstances, see #3666. 10136 if (e._simulated || !this._enabled) { return; } 10137 10138 if (e.touches && e.touches.length > 1) { 10139 this._moved = true; 10140 return; 10141 } 10142 10143 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 10144 newPoint = new L.Point(first.clientX, first.clientY), 10145 offset = newPoint.subtract(this._startPoint); 10146 10147 if (!offset.x && !offset.y) { return; } 10148 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 10149 10150 L.DomEvent.preventDefault(e); 10151 10152 if (!this._moved) { 10153 // @event dragstart: Event 10154 // Fired when a drag starts 10155 this.fire('dragstart'); 10156 10157 this._moved = true; 10158 this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); 10159 10160 L.DomUtil.addClass(document.body, 'leaflet-dragging'); 10161 10162 this._lastTarget = e.target || e.srcElement; 10163 // IE and Edge do not give the <use> element, so fetch it 10164 // if necessary 10165 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { 10166 this._lastTarget = this._lastTarget.correspondingUseElement; 10167 } 10168 L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); 10169 } 10170 10171 this._newPos = this._startPos.add(offset); 10172 this._moving = true; 10173
10174 L.Util.cancelAnimFrame(this._animRequest); 10175 this._lastEvent = e; 10176 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true); 10177 }, 10178 10179 _updatePosition: function () { 10180 var e = {originalEvent: this._lastEvent}; 10181 10182 // @event predrag: Event 10183 // Fired continuously during dragging *before* each corresponding 10184 // update of the element's position. 10185 this.fire('predrag', e); 10186 L.DomUtil.setPosition(this._element, this._newPos); 10187 10188 // @event drag: Event 10189 // Fired continuously during dragging. 10190 this.fire('drag', e); 10191 }, 10192 10193 _onUp: function (e) { 10194 // Ignore simulated events, since we handle both touch and 10195 // mouse explicitly; otherwise we risk getting duplicates of 10196 // touch events, see #4315. 10197 // Also ignore the event if disabled; this happens in IE11 10198 // under some circumstances, see #3666. 10199 if (e._simulated || !this._enabled) { return; } 10200 10201 L.DomUtil.removeClass(document.body, 'leaflet-dragging'); 10202 10203 if (this._lastTarget) { 10204 L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); 10205 this._lastTarget = null; 10206 } 10207 10208 for (var i in L.Draggable.MOVE) { 10209 L.DomEvent 10210 .off(document, L.Draggable.MOVE[i], this._onMove, this) 10211 .off(document, L.Draggable.END[i], this._onUp, this); 10212 } 10213 10214 L.DomUtil.enableImageDrag(); 10215 L.DomUtil.enableTextSelection(); 10216 10217 if (this._moved && this._moving) { 10218 // ensure drag is not fired after dragend 10219 L.Util.cancelAnimFrame(this._animRequest); 10220 10221 // @event dragend: DragEndEvent 10222 // Fired when the drag ends. 10223 this.fire('dragend', { 10224 distance: this._newPos.distanceTo(this._startPos) 10225 }); 10226 } 10227 10228 this._moving = false; 10229 L.Draggable._dragging = false; 10230 } 10231}); 10232 10233 10234 10235/* 10236 L.Handler is a base class for handler classes that are used internally to inject 10237 interaction features like dragging to classes like Map and Marker. 10238*/ 10239 10240// @class Handler 10241// @aka L.Handler 10242// Abstract class for map interaction handlers 10243 10244L.Handler = L.Class.extend({ 10245 initialize: function (map) { 10246 this._map = map; 10247 }, 10248 10249 // @method enable(): this 10250 // Enables the handler 10251 enable: function () { 10252 if (this._enabled) { return this; } 10253 10254 this._enabled = true; 10255 this.addHooks(); 10256 return this; 10257 }, 10258 10259 // @method disable(): this 10260 // Disables the handler 10261 disable: function () { 10262 if (!this._enabled) { return this; } 10263 10264 this._enabled = false; 10265 this.removeHooks(); 10266 return this; 10267 }, 10268 10269 // @method enabled(): Boolean 10270 // Returns `true` if the handler is enabled 10271 enabled: function () { 10272 return !!this._enabled; 10273 } 10274 10275 // @section Extension methods 10276 // Classes inheriting from `Handler` must implement the two following methods: 10277 // @method addHooks() 10278 // Called when the handler is enabled, should add event hooks. 10279 // @method removeHooks() 10280 // Called when the handler is disabled, should remove the event hooks added previously. 10281}); 10282 10283 10284 10285/* 10286 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 10287 */ 10288 10289// @namespace Map 10290// @section Interaction Options 10291L.Map.mergeOptions({ 10292 // @option dragging: Boolean = true 10293 // Whether the map be draggable with mouse/touch or not. 10294 dragging: true, 10295 10296 // @section Panning Inertia Options 10297 // @option inertia: Boolean = * 10298 // If enabled, panning of the map will have an inertia effect where 10299 // the map builds momentum while dragging and continues moving in 10300 // the same direction for some time. Feels especially nice on touch 10301 // devices. Enabled by default unless running on old Android devices. 10302 inertia: !L.Browser.android23, 10303 10304 // @option inertiaDeceleration: Number = 3000 10305 // The rate with which the inertial movement slows down, in pixels/secondò. 10306 inertiaDeceleration: 3400, // px/s^2 10307 10308 // @option inertiaMaxSpeed: Number = Infinity 10309 // Max speed of the inertial movement, in pixels/second. 10310 inertiaMaxSpeed: Infinity, // px/s 10311 10312 // @option easeLinearity: Number = 0.2 10313 easeLinearity: 0.2, 10314 10315 // TODO refactor, move to CRS 10316 // @option worldCopyJump: Boolean = false 10317 // With this option enabled, the map tracks when you pan to another "copy" 10318 // of the world and seamlessly jumps to the original one so that all overlays 10319 // like markers and vector layers are still visible.
vendor: 5,208 bytes, lines 10320-10493
10320 worldCopyJump: false, 10321 10322 // @option maxBoundsViscosity: Number = 0.0 10323 // If `maxBounds` is set, this option will control how solid the bounds 10324 // are when dragging the map around. The default value of `0.0` allows the 10325 // user to drag outside the bounds at normal speed, higher values will 10326 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 10327 // solid, preventing the user from dragging outside the bounds. 10328 maxBoundsViscosity: 0.0 10329}); 10330 10331L.Map.Drag = L.Handler.extend({ 10332 addHooks: function () { 10333 if (!this._draggable) { 10334 var map = this._map; 10335 10336 this._draggable = new L.Draggable(map._mapPane, map._container); 10337 10338 this._draggable.on({ 10339 down: this._onDown, 10340 dragstart: this._onDragStart, 10341 drag: this._onDrag, 10342 dragend: this._onDragEnd 10343 }, this); 10344 10345 this._draggable.on('predrag', this._onPreDragLimit, this); 10346 if (map.options.worldCopyJump) { 10347 this._draggable.on('predrag', this._onPreDragWrap, this); 10348 map.on('zoomend', this._onZoomEnd, this); 10349 10350 map.whenReady(this._onZoomEnd, this); 10351 } 10352 } 10353 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 10354 this._draggable.enable(); 10355 this._positions = []; 10356 this._times = []; 10357 }, 10358 10359 removeHooks: function () { 10360 L.DomUtil.removeClass(this._map._container, 'leaflet-grab'); 10361 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag'); 10362 this._draggable.disable(); 10363 }, 10364 10365 moved: function () { 10366 return this._draggable && this._draggable._moved; 10367 }, 10368 10369 moving: function () { 10370 return this._draggable && this._draggable._moving; 10371 }, 10372 10373 _onDown: function () { 10374 this._map._stop(); 10375 }, 10376 10377 _onDragStart: function () { 10378 var map = this._map; 10379 10380 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 10381 var bounds = L.latLngBounds(this._map.options.maxBounds); 10382 10383 this._offsetLimit = L.bounds( 10384 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 10385 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 10386 .add(this._map.getSize())); 10387 10388 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 10389 } else { 10390 this._offsetLimit = null; 10391 } 10392 10393 map 10394 .fire('movestart') 10395 .fire('dragstart'); 10396 10397 if (map.options.inertia) { 10398 this._positions = []; 10399 this._times = []; 10400 } 10401 }, 10402 10403 _onDrag: function (e) { 10404 if (this._map.options.inertia) { 10405 var time = this._lastTime = +new Date(), 10406 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 10407 10408 this._positions.push(pos); 10409 this._times.push(time); 10410 10411 if (time - this._times[0] > 50) { 10412 this._positions.shift(); 10413 this._times.shift(); 10414 } 10415 } 10416 10417 this._map 10418 .fire('move', e) 10419 .fire('drag', e); 10420 }, 10421 10422 _onZoomEnd: function () { 10423 var pxCenter = this._map.getSize().divideBy(2), 10424 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 10425 10426 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 10427 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 10428 }, 10429 10430 _viscousLimit: function (value, threshold) { 10431 return value - (value - threshold) * this._viscosity; 10432 }, 10433 10434 _onPreDragLimit: function () { 10435 if (!this._viscosity || !this._offsetLimit) { return; } 10436 10437 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 10438 10439 var limit = this._offsetLimit; 10440 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 10441 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 10442 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 10443 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 10444 10445 this._draggable._newPos = this._draggable._startPos.add(offset); 10446 }, 10447 10448 _onPreDragWrap: function () { 10449 // TODO refactor to be able to adjust map pane position after zoom 10450 var worldWidth = this._worldWidth, 10451 halfWidth = Math.round(worldWidth / 2), 10452 dx = this._initialWorldOffset, 10453 x = this._draggable._newPos.x, 10454 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 10455 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 10456 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 10457 10458 this._draggable._absPos = this._draggable._newPos.clone(); 10459 this._draggable._newPos.x = newX; 10460 }, 10461 10462 _onDragEnd: function (e) { 10463 var map = this._map, 10464 options = map.options, 10465 10466 noInertia = !options.inertia || this._times.length < 2; 10467 10468 map.fire('dragend', e); 10469 10470 if (noInertia) { 10471 map.fire('moveend'); 10472 10473 } else { 10474 10475 var direction = this._lastPos.subtract(this._positions[0]), 10476 duration = (this._lastTime - this._times[0]) / 1000, 10477 ease = options.easeLinearity, 10478 10479 speedVector = direction.multiplyBy(ease / duration), 10480 speed = speedVector.distanceTo([0, 0]), 10481 10482 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 10483 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 10484 10485 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 10486 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 10487 10488 if (!offset.x && !offset.y) { 10489 map.fire('moveend'); 10490 10491 } else { 10492 offset = map._limitOffset(offset, map.options.maxBounds); 10493
10494 L.Util.requestAnimFrame(function () { 10495 map.panBy(offset, { 10496 duration: decelerationDuration, 10497 easeLinearity: ease, 10498 noMoveStart: true, 10499 animate: true 10500 }); 10501 }); 10502 } 10503 } 10504 } 10505}); 10506 10507// @section Handlers 10508// @property dragging: Handler 10509// Map dragging handler (by both mouse and touch). 10510L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); 10511 10512 10513 10514/* 10515 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 10516 */ 10517 10518// @namespace Map 10519// @section Interaction Options 10520 10521L.Map.mergeOptions({ 10522 // @option doubleClickZoom: Boolean|String = true 10523 // Whether the map can be zoomed in by double clicking on it and 10524 // zoomed out by double clicking while holding shift. If passed 10525 // `'center'`, double-click zoom will zoom to the center of the 10526 // view regardless of where the mouse was. 10527 doubleClickZoom: true 10528}); 10529 10530L.Map.DoubleClickZoom = L.Handler.extend({ 10531 addHooks: function () { 10532 this._map.on('dblclick', this._onDoubleClick, this); 10533 }, 10534 10535 removeHooks: function () { 10536 this._map.off('dblclick', this._onDoubleClick, this); 10537 }, 10538 10539 _onDoubleClick: function (e) { 10540 var map = this._map, 10541 oldZoom = map.getZoom(), 10542 delta = map.options.zoomDelta, 10543 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 10544 10545 if (map.options.doubleClickZoom === 'center') { 10546 map.setZoom(zoom); 10547 } else { 10548 map.setZoomAround(e.containerPoint, zoom); 10549 } 10550 } 10551}); 10552 10553// @section Handlers 10554// 10555// Map properties include interaction handlers that allow you to control 10556// interaction behavior in runtime, enabling or disabling certain features such 10557// as dragging or touch zoom (see `Handler` methods). For example: 10558// 10559// ```js 10560// map.doubleClickZoom.disable(); 10561// ``` 10562// 10563// @property doubleClickZoom: Handler 10564// Double click zoom handler. 10565L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); 10566 10567 10568 10569/* 10570 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 10571 */ 10572 10573// @namespace Map 10574// @section Interaction Options 10575L.Map.mergeOptions({ 10576 // @section Mousewheel options 10577 // @option scrollWheelZoom: Boolean|String = true 10578 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 10579 // it will zoom to the center of the view regardless of where the mouse was. 10580 scrollWheelZoom: true, 10581 10582 // @option wheelDebounceTime: Number = 40 10583 // Limits the rate at which a wheel can fire (in milliseconds). By default 10584 // user can't zoom via wheel more often than once per 40 ms. 10585 wheelDebounceTime: 40, 10586 10587 // @option wheelPxPerZoomLevel: Number = 60 10588 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 10589 // mean a change of one full zoom level. Smaller values will make wheel-zooming 10590 // faster (and vice versa). 10591 wheelPxPerZoomLevel: 60 10592}); 10593 10594L.Map.ScrollWheelZoom = L.Handler.extend({ 10595 addHooks: function () { 10596 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); 10597 10598 this._delta = 0; 10599 }, 10600 10601 removeHooks: function () { 10602 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this); 10603 }, 10604 10605 _onWheelScroll: function (e) { 10606 var delta = L.DomEvent.getWheelDelta(e); 10607 10608 var debounce = this._map.options.wheelDebounceTime; 10609 10610 this._delta += delta; 10611 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 10612 10613 if (!this._startTime) { 10614 this._startTime = +new Date(); 10615 } 10616 10617 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 10618 10619 clearTimeout(this._timer); 10620 this._timer = setTimeout(L.bind(this._performZoom, this), left); 10621 10622 L.DomEvent.stop(e); 10623 }, 10624 10625 _performZoom: function () { 10626 var map = this._map, 10627 zoom = map.getZoom(), 10628 snap = this._map.options.zoomSnap || 0; 10629 10630 map._stop(); // stop panning and fly animations if any 10631 10632 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 10633 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 10634 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 10635 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 10636 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 10637 10638 this._delta = 0; 10639 this._startTime = null; 10640 10641 if (!delta) { return; } 10642 10643 if (map.options.scrollWheelZoom === 'center') { 10644 map.setZoom(zoom + delta); 10645 } else { 10646 map.setZoomAround(this._lastMousePos, zoom + delta); 10647 } 10648 } 10649}); 10650 10651// @section Handlers 10652// @property scrollWheelZoom: Handler 10653// Scroll wheel zoom handler. 10654L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); 10655 10656 10657 10658/* 10659 * Extends the event handling code with double tap support for mobile browsers. 10660 */ 10661 10662L.extend(L.DomEvent, { 10663 10664 _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', 10665 _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', 10666 10667 // inspired by Zepto touch code by Thomas Fuchs 10668 addDoubleTapListener: function (obj, handler, id) { 10669 var last, touch, 10670 doubleTap = false,
10671 delay = 250; 10672 10673 function onTouchStart(e) { 10674 var count; 10675 10676 if (L.Browser.pointer) { 10677 count = L.DomEvent._pointersCount; 10678 } else { 10679 count = e.touches.length; 10680 } 10681 10682 if (count > 1) { return; } 10683 10684 var now = Date.now(), 10685 delta = now - (last || now); 10686 10687 touch = e.touches ? e.touches[0] : e; 10688 doubleTap = (delta > 0 && delta <= delay); 10689 last = now; 10690 } 10691 10692 function onTouchEnd() { 10693 if (doubleTap && !touch.cancelBubble) { 10694 if (L.Browser.pointer) { 10695 // work around .type being readonly with MSPointer* events 10696 var newTouch = {}, 10697 prop, i; 10698 10699 for (i in touch) { 10700 prop = touch[i]; 10701 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop; 10702 } 10703 touch = newTouch; 10704 } 10705 touch.type = 'dblclick'; 10706 handler(touch); 10707 last = null; 10708 } 10709 } 10710 10711 var pre = '_leaflet_', 10712 touchstart = this._touchstart, 10713 touchend = this._touchend; 10714 10715 obj[pre + touchstart + id] = onTouchStart; 10716 obj[pre + touchend + id] = onTouchEnd; 10717 obj[pre + 'dblclick' + id] = handler; 10718 10719 obj.addEventListener(touchstart, onTouchStart, false); 10720 obj.addEventListener(touchend, onTouchEnd, false); 10721 10722 // On some platforms (notably, chrome on win10 + touchscreen + mouse), 10723 // the browser doesn't fire touchend/pointerup events but does fire 10724 // native dblclicks. See #4127. 10725 if (!L.Browser.edge) { 10726 obj.addEventListener('dblclick', handler, false); 10727 } 10728 10729 return this; 10730 }, 10731 10732 removeDoubleTapListener: function (obj, id) { 10733 var pre = '_leaflet_', 10734 touchstart = obj[pre + this._touchstart + id], 10735 touchend = obj[pre + this._touchend + id], 10736 dblclick = obj[pre + 'dblclick' + id]; 10737 10738 obj.removeEventListener(this._touchstart, touchstart, false); 10739 obj.removeEventListener(this._touchend, touchend, false); 10740 if (!L.Browser.edge) { 10741 obj.removeEventListener('dblclick', dblclick, false); 10742 } 10743 10744 return this; 10745 } 10746}); 10747 10748 10749 10750/* 10751 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 10752 */ 10753 10754L.extend(L.DomEvent, { 10755 10756 POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown', 10757 POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove', 10758 POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup', 10759 POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', 10760 TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'], 10761 10762 _pointers: {}, 10763 _pointersCount: 0, 10764 10765 // Provides a touch events wrapper for (ms)pointer events. 10766 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 10767 10768 addPointerListener: function (obj, type, handler, id) { 10769 10770 if (type === 'touchstart') { 10771 this._addPointerStart(obj, handler, id); 10772 10773 } else if (type === 'touchmove') { 10774 this._addPointerMove(obj, handler, id); 10775 10776 } else if (type === 'touchend') { 10777 this._addPointerEnd(obj, handler, id); 10778 } 10779 10780 return this; 10781 }, 10782 10783 removePointerListener: function (obj, type, id) { 10784 var handler = obj['_leaflet_' + type + id]; 10785 10786 if (type === 'touchstart') { 10787 obj.removeEventListener(this.POINTER_DOWN, handler, false); 10788 10789 } else if (type === 'touchmove') { 10790 obj.removeEventListener(this.POINTER_MOVE, handler, false); 10791 10792 } else if (type === 'touchend') { 10793 obj.removeEventListener(this.POINTER_UP, handler, false); 10794 obj.removeEventListener(this.POINTER_CANCEL, handler, false); 10795 } 10796 10797 return this; 10798 }, 10799 10800 _addPointerStart: function (obj, handler, id) { 10801 var onDown = L.bind(function (e) { 10802 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { 10803 // In IE11, some touch events needs to fire for form controls, or 10804 // the controls will stop working. We keep a whitelist of tag names that 10805 // need these events. For other target tags, we prevent default on the event. 10806 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { 10807 L.DomEvent.preventDefault(e); 10808 } else { 10809 return; 10810 } 10811 } 10812 10813 this._handlePointer(e, handler); 10814 }, this); 10815 10816 obj['_leaflet_touchstart' + id] = onDown; 10817 obj.addEventListener(this.POINTER_DOWN, onDown, false); 10818 10819 // need to keep track of what pointers and how many are active to provide e.touches emulation 10820 if (!this._pointerDocListener) { 10821 var pointerUp = L.bind(this._globalPointerUp, this); 10822 10823 // we listen documentElement as any drags that end by moving the touch off the screen get fired there 10824 document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
10825 document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true); 10826 document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true); 10827 document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true); 10828 10829 this._pointerDocListener = true; 10830 } 10831 }, 10832 10833 _globalPointerDown: function (e) { 10834 this._pointers[e.pointerId] = e; 10835 this._pointersCount++; 10836 }, 10837 10838 _globalPointerMove: function (e) { 10839 if (this._pointers[e.pointerId]) { 10840 this._pointers[e.pointerId] = e; 10841 } 10842 }, 10843 10844 _globalPointerUp: function (e) { 10845 delete this._pointers[e.pointerId]; 10846 this._pointersCount--; 10847 }, 10848 10849 _handlePointer: function (e, handler) { 10850 e.touches = []; 10851 for (var i in this._pointers) { 10852 e.touches.push(this._pointers[i]); 10853 } 10854 e.changedTouches = [e]; 10855 10856 handler(e); 10857 }, 10858 10859 _addPointerMove: function (obj, handler, id) { 10860 var onMove = L.bind(function (e) { 10861 // don't fire touch moves when mouse isn't down 10862 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } 10863 10864 this._handlePointer(e, handler); 10865 }, this); 10866 10867 obj['_leaflet_touchmove' + id] = onMove; 10868 obj.addEventListener(this.POINTER_MOVE, onMove, false); 10869 }, 10870 10871 _addPointerEnd: function (obj, handler, id) { 10872 var onUp = L.bind(function (e) { 10873 this._handlePointer(e, handler); 10874 }, this); 10875 10876 obj['_leaflet_touchend' + id] = onUp; 10877 obj.addEventListener(this.POINTER_UP, onUp, false); 10878 obj.addEventListener(this.POINTER_CANCEL, onUp, false); 10879 } 10880}); 10881 10882 10883 10884/* 10885 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. 10886 */ 10887 10888// @namespace Map 10889// @section Interaction Options 10890L.Map.mergeOptions({ 10891 // @section Touch interaction options 10892 // @option touchZoom: Boolean|String = * 10893 // Whether the map can be zoomed by touch-dragging with two fingers. If 10894 // passed `'center'`, it will zoom to the center of the view regardless of 10895 // where the touch events (fingers) were. Enabled for touch-capable web 10896 // browsers except for old Androids. 10897 touchZoom: L.Browser.touch && !L.Browser.android23, 10898 10899 // @option bounceAtZoomLimits: Boolean = true 10900 // Set it to false if you don't want the map to zoom beyond min/max zoom 10901 // and then bounce back when pinch-zooming. 10902 bounceAtZoomLimits: true 10903}); 10904 10905L.Map.TouchZoom = L.Handler.extend({ 10906 addHooks: function () { 10907 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom'); 10908 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); 10909 }, 10910 10911 removeHooks: function () { 10912 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom'); 10913 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); 10914 }, 10915 10916 _onTouchStart: function (e) { 10917 var map = this._map; 10918 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 10919 10920 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 10921 p2 = map.mouseEventToContainerPoint(e.touches[1]); 10922 10923 this._centerPoint = map.getSize()._divideBy(2); 10924 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 10925 if (map.options.touchZoom !== 'center') { 10926 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 10927 } 10928 10929 this._startDist = p1.distanceTo(p2); 10930 this._startZoom = map.getZoom(); 10931 10932 this._moved = false; 10933 this._zooming = true; 10934 10935 map._stop(); 10936 10937 L.DomEvent 10938 .on(document, 'touchmove', this._onTouchMove, this) 10939 .on(document, 'touchend', this._onTouchEnd, this); 10940 10941 L.DomEvent.preventDefault(e); 10942 }, 10943 10944 _onTouchMove: function (e) { 10945 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 10946 10947 var map = this._map, 10948 p1 = map.mouseEventToContainerPoint(e.touches[0]), 10949 p2 = map.mouseEventToContainerPoint(e.touches[1]), 10950 scale = p1.distanceTo(p2) / this._startDist; 10951 10952 10953 this._zoom = map.getScaleZoom(scale, this._startZoom); 10954 10955 if (!map.options.bounceAtZoomLimits && ( 10956 (this._zoom < map.getMinZoom() && scale < 1) || 10957 (this._zoom > map.getMaxZoom() && scale > 1))) { 10958 this._zoom = map._limitZoom(this._zoom); 10959 } 10960 10961 if (map.options.touchZoom === 'center') { 10962 this._center = this._startLatLng; 10963 if (scale === 1) { return; } 10964 } else { 10965 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 10966 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 10967 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 10968 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 10969 } 10970 10971 if (!this._moved) { 10972 map._moveStart(true); 10973 this._moved = true; 10974 } 10975
10976 L.Util.cancelAnimFrame(this._animRequest); 10977 10978 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); 10979 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true); 10980 10981 L.DomEvent.preventDefault(e); 10982 }, 10983 10984 _onTouchEnd: function () { 10985 if (!this._moved || !this._zooming) { 10986 this._zooming = false; 10987 return; 10988 } 10989 10990 this._zooming = false; 10991 L.Util.cancelAnimFrame(this._animRequest); 10992 10993 L.DomEvent 10994 .off(document, 'touchmove', this._onTouchMove) 10995 .off(document, 'touchend', this._onTouchEnd); 10996 10997 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 10998 if (this._map.options.zoomAnimation) { 10999 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 11000 } else { 11001 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 11002 } 11003 } 11004}); 11005 11006// @section Handlers 11007// @property touchZoom: Handler 11008// Touch zoom handler. 11009L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); 11010 11011 11012 11013/* 11014 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. 11015 */ 11016 11017// @namespace Map 11018// @section Interaction Options 11019L.Map.mergeOptions({ 11020 // @section Touch interaction options 11021 // @option tap: Boolean = true 11022 // Enables mobile hacks for supporting instant taps (fixing 200ms click 11023 // delay on iOS/Android) and touch holds (fired as `contextmenu` events). 11024 tap: true, 11025 11026 // @option tapTolerance: Number = 15 11027 // The max number of pixels a user can shift his finger during touch 11028 // for it to be considered a valid tap. 11029 tapTolerance: 15 11030}); 11031 11032L.Map.Tap = L.Handler.extend({ 11033 addHooks: function () { 11034 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); 11035 }, 11036 11037 removeHooks: function () { 11038 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); 11039 }, 11040 11041 _onDown: function (e) { 11042 if (!e.touches) { return; } 11043 11044 L.DomEvent.preventDefault(e); 11045 11046 this._fireClick = true; 11047 11048 // don't simulate click or track longpress if more than 1 touch 11049 if (e.touches.length > 1) { 11050 this._fireClick = false; 11051 clearTimeout(this._holdTimeout); 11052 return; 11053 } 11054 11055 var first = e.touches[0], 11056 el = first.target; 11057 11058 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); 11059 11060 // if touching a link, highlight it 11061 if (el.tagName && el.tagName.toLowerCase() === 'a') { 11062 L.DomUtil.addClass(el, 'leaflet-active'); 11063 } 11064 11065 // simulate long hold but setting a timeout 11066 this._holdTimeout = setTimeout(L.bind(function () { 11067 if (this._isTapValid()) { 11068 this._fireClick = false; 11069 this._onUp(); 11070 this._simulateEvent('contextmenu', first); 11071 } 11072 }, this), 1000); 11073 11074 this._simulateEvent('mousedown', first); 11075 11076 L.DomEvent.on(document, { 11077 touchmove: this._onMove, 11078 touchend: this._onUp 11079 }, this); 11080 }, 11081 11082 _onUp: function (e) { 11083 clearTimeout(this._holdTimeout); 11084 11085 L.DomEvent.off(document, { 11086 touchmove: this._onMove, 11087 touchend: this._onUp 11088 }, this); 11089 11090 if (this._fireClick && e && e.changedTouches) { 11091 11092 var first = e.changedTouches[0], 11093 el = first.target; 11094 11095 if (el && el.tagName && el.tagName.toLowerCase() === 'a') { 11096 L.DomUtil.removeClass(el, 'leaflet-active'); 11097 } 11098 11099 this._simulateEvent('mouseup', first); 11100 11101 // simulate click if the touch didn't move too much 11102 if (this._isTapValid()) { 11103 this._simulateEvent('click', first); 11104 } 11105 } 11106 }, 11107 11108 _isTapValid: function () { 11109 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 11110 }, 11111 11112 _onMove: function (e) { 11113 var first = e.touches[0]; 11114 this._newPos = new L.Point(first.clientX, first.clientY); 11115 this._simulateEvent('mousemove', first); 11116 }, 11117 11118 _simulateEvent: function (type, e) { 11119 var simulatedEvent = document.createEvent('MouseEvents'); 11120 11121 simulatedEvent._simulated = true; 11122 e.target._simulatedClick = true; 11123 11124 simulatedEvent.initMouseEvent( 11125 type, true, true, window, 1, 11126 e.screenX, e.screenY, 11127 e.clientX, e.clientY, 11128 false, false, false, false, 0, null); 11129 11130 e.target.dispatchEvent(simulatedEvent); 11131 } 11132}); 11133 11134// @section Handlers 11135// @property tap: Handler 11136// Mobile touch hacks (quick tap and touch hold) handler. 11137if (L.Browser.touch && !L.Browser.pointer) { 11138 L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); 11139} 11140 11141 11142 11143/* 11144 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 11145 * (zoom to a selected bounding box), enabled by default. 11146 */ 11147 11148// @namespace Map 11149// @section Interaction Options 11150L.Map.mergeOptions({ 11151 // @option boxZoom: Boolean = true 11152 // Whether the map can be zoomed to a rectangular area specified by 11153 // dragging the mouse while pressing the shift key. 11154 boxZoom: true 11155}); 11156 11157L.Map.BoxZoom = L.Handler.extend({ 11158 initialize: function (map) { 11159 this._map = map; 11160 this._container = map._container; 11161 this._pane = map._panes.overlayPane; 11162 }, 11163 11164 addHooks: function () { 11165 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); 11166 }, 11167 11168 removeHooks: function () { 11169 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); 11170 }, 11171 11172 moved: function () { 11173 return this._moved; 11174 }, 11175 11176 _resetState: function () { 11177 this._moved = false;
11178 }, 11179 11180 _onMouseDown: function (e) { 11181 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 11182 11183 this._resetState(); 11184 11185 L.DomUtil.disableTextSelection(); 11186 L.DomUtil.disableImageDrag(); 11187 11188 this._startPoint = this._map.mouseEventToContainerPoint(e); 11189 11190 L.DomEvent.on(document, { 11191 contextmenu: L.DomEvent.stop, 11192 mousemove: this._onMouseMove, 11193 mouseup: this._onMouseUp, 11194 keydown: this._onKeyDown 11195 }, this); 11196 }, 11197 11198 _onMouseMove: function (e) { 11199 if (!this._moved) { 11200 this._moved = true; 11201 11202 this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container); 11203 L.DomUtil.addClass(this._container, 'leaflet-crosshair'); 11204 11205 this._map.fire('boxzoomstart'); 11206 } 11207 11208 this._point = this._map.mouseEventToContainerPoint(e); 11209 11210 var bounds = new L.Bounds(this._point, this._startPoint), 11211 size = bounds.getSize(); 11212 11213 L.DomUtil.setPosition(this._box, bounds.min); 11214 11215 this._box.style.width = size.x + 'px'; 11216 this._box.style.height = size.y + 'px'; 11217 }, 11218 11219 _finish: function () { 11220 if (this._moved) { 11221 L.DomUtil.remove(this._box); 11222 L.DomUtil.removeClass(this._container, 'leaflet-crosshair'); 11223 } 11224 11225 L.DomUtil.enableTextSelection(); 11226 L.DomUtil.enableImageDrag(); 11227 11228 L.DomEvent.off(document, { 11229 contextmenu: L.DomEvent.stop, 11230 mousemove: this._onMouseMove, 11231 mouseup: this._onMouseUp, 11232 keydown: this._onKeyDown 11233 }, this); 11234 }, 11235 11236 _onMouseUp: function (e) { 11237 if ((e.which !== 1) && (e.button !== 1)) { return; } 11238 11239 this._finish(); 11240 11241 if (!this._moved) { return; } 11242 // Postpone to next JS tick so internal click event handling 11243 // still see it as "moved". 11244 setTimeout(L.bind(this._resetState, this), 0); 11245 11246 var bounds = new L.LatLngBounds( 11247 this._map.containerPointToLatLng(this._startPoint), 11248 this._map.containerPointToLatLng(this._point)); 11249 11250 this._map 11251 .fitBounds(bounds) 11252 .fire('boxzoomend', {boxZoomBounds: bounds}); 11253 }, 11254 11255 _onKeyDown: function (e) { 11256 if (e.keyCode === 27) { 11257 this._finish(); 11258 } 11259 } 11260}); 11261 11262// @section Handlers 11263// @property boxZoom: Handler 11264// Box (shift-drag with mouse) zoom handler. 11265L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); 11266 11267 11268 11269/* 11270 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 11271 */ 11272 11273// @namespace Map 11274// @section Keyboard Navigation Options 11275L.Map.mergeOptions({ 11276 // @option keyboard: Boolean = true 11277 // Makes the map focusable and allows users to navigate the map with keyboard 11278 // arrows and `+`/`-` keys. 11279 keyboard: true, 11280 11281 // @option keyboardPanDelta: Number = 80 11282 // Amount of pixels to pan when pressing an arrow key. 11283 keyboardPanDelta: 80 11284}); 11285 11286L.Map.Keyboard = L.Handler.extend({ 11287 11288 keyCodes: { 11289 left: [37], 11290 right: [39], 11291 down: [40], 11292 up: [38], 11293 zoomIn: [187, 107, 61, 171], 11294 zoomOut: [189, 109, 54, 173] 11295 }, 11296 11297 initialize: function (map) { 11298 this._map = map; 11299 11300 this._setPanDelta(map.options.keyboardPanDelta); 11301 this._setZoomDelta(map.options.zoomDelta); 11302 }, 11303 11304 addHooks: function () { 11305 var container = this._map._container; 11306 11307 // make the container focusable by tabbing 11308 if (container.tabIndex <= 0) { 11309 container.tabIndex = '0'; 11310 } 11311 11312 L.DomEvent.on(container, { 11313 focus: this._onFocus, 11314 blur: this._onBlur, 11315 mousedown: this._onMouseDown 11316 }, this); 11317 11318 this._map.on({ 11319 focus: this._addHooks, 11320 blur: this._removeHooks 11321 }, this); 11322 }, 11323 11324 removeHooks: function () { 11325 this._removeHooks(); 11326 11327 L.DomEvent.off(this._map._container, { 11328 focus: this._onFocus, 11329 blur: this._onBlur, 11330 mousedown: this._onMouseDown 11331 }, this); 11332 11333 this._map.off({ 11334 focus: this._addHooks, 11335 blur: this._removeHooks 11336 }, this); 11337 }, 11338 11339 _onMouseDown: function () { 11340 if (this._focused) { return; } 11341 11342 var body = document.body, 11343 docEl = document.documentElement, 11344 top = body.scrollTop || docEl.scrollTop, 11345 left = body.scrollLeft || docEl.scrollLeft; 11346 11347 this._map._container.focus(); 11348 11349 window.scrollTo(left, top); 11350 }, 11351 11352 _onFocus: function () { 11353 this._focused = true; 11354 this._map.fire('focus'); 11355 }, 11356 11357 _onBlur: function () { 11358 this._focused = false; 11359 this._map.fire('blur'); 11360 }, 11361 11362 _setPanDelta: function (panDelta) { 11363 var keys = this._panKeys = {}, 11364 codes = this.keyCodes, 11365 i, len; 11366 11367 for (i = 0, len = codes.left.length; i < len; i++) { 11368 keys[codes.left[i]] = [-1 * panDelta, 0]; 11369 } 11370 for (i = 0, len = codes.right.length; i < len; i++) { 11371 keys[codes.right[i]] = [panDelta, 0]; 11372 } 11373 for (i = 0, len = codes.down.length; i < len; i++) { 11374 keys[codes.down[i]] = [0, panDelta]; 11375 } 11376 for (i = 0, len = codes.up.length; i < len; i++) { 11377 keys[codes.up[i]] = [0, -1 * panDelta]; 11378 } 11379 }, 11380 11381 _setZoomDelta: function (zoomDelta) { 11382 var keys = this._zoomKeys = {}, 11383 codes = this.keyCodes, 11384 i, len; 11385 11386 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 11387 keys[codes.zoomIn[i]] = zoomDelta; 11388 } 11389 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 11390 keys[codes.zoomOut[i]] = -zoomDelta; 11391 } 11392 }, 11393 11394 _addHooks: function () { 11395 L.DomEvent.on(document, 'keydown', this._onKeyDown, this); 11396 }, 11397 11398 _removeHooks: function () { 11399 L.DomEvent.off(document, 'keydown', this._onKeyDown, this); 11400 }, 11401 11402 _onKeyDown: function (e) { 11403 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 11404 11405 var key = e.keyCode, 11406 map = this._map, 11407 offset; 11408 11409 if (key in this._panKeys) { 11410 11411 if (map._panAnim && map._panAnim._inProgress) { return; } 11412 11413 offset = this._panKeys[key]; 11414 if (e.shiftKey) { 11415 offset = L.point(offset).multiplyBy(3); 11416 } 11417 11418 map.panBy(offset); 11419 11420 if (map.options.maxBounds) { 11421 map.panInsideBounds(map.options.maxBounds); 11422 } 11423 11424 } else if (key in this._zoomKeys) { 11425 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 11426 11427 } else if (key === 27) { 11428 map.closePopup(); 11429 11430 } else { 11431 return; 11432 } 11433 11434 L.DomEvent.stop(e); 11435 } 11436}); 11437 11438// @section Handlers 11439// @section Handlers 11440// @property keyboard: Handler 11441// Keyboard navigation handler. 11442L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); 11443 11444 11445 11446/* 11447 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 11448 */ 11449 11450 11451/* @namespace Marker 11452 * @section Interaction handlers 11453 * 11454 * 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: 11455 * 11456 * ```js 11457 * marker.dragging.disable(); 11458 * ``` 11459 * 11460 * @property dragging: Handler 11461 * Marker dragging handler (by both mouse and touch). 11462 */ 11463 11464L.Handler.MarkerDrag = L.Handler.extend({ 11465 initialize: function (marker) { 11466 this._marker = marker; 11467 }, 11468 11469 addHooks: function () { 11470 var icon = this._marker._icon; 11471 11472 if (!this._draggable) { 11473 this._draggable = new L.Draggable(icon, icon, true); 11474 } 11475 11476 this._draggable.on({ 11477 dragstart: this._onDragStart, 11478 drag: this._onDrag, 11479 dragend: this._onDragEnd 11480 }, this).enable(); 11481 11482 L.DomUtil.addClass(icon, 'leaflet-marker-draggable'); 11483 }, 11484 11485 removeHooks: function () { 11486 this._draggable.off({ 11487 dragstart: this._onDragStart, 11488 drag: this._onDrag, 11489 dragend: this._onDragEnd 11490 }, this).disable(); 11491 11492 if (this._marker._icon) { 11493 L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); 11494 } 11495 }, 11496 11497 moved: function () { 11498 return this._draggable && this._draggable._moved; 11499 }, 11500 11501 _onDragStart: function () { 11502 // @section Dragging events 11503 // @event dragstart: Event 11504 // Fired when the user starts dragging the marker. 11505 11506 // @event movestart: Event 11507 // Fired when the marker starts moving (because of dragging). 11508 11509 this._oldLatLng = this._marker.getLatLng(); 11510 this._marker 11511 .closePopup() 11512 .fire('movestart') 11513 .fire('dragstart'); 11514 }, 11515 11516 _onDrag: function (e) { 11517 var marker = this._marker, 11518 shadow = marker._shadow, 11519 iconPos = L.DomUtil.getPosition(marker._icon), 11520 latlng = marker._map.layerPointToLatLng(iconPos); 11521 11522 // update shadow position 11523 if (shadow) { 11524 L.DomUtil.setPosition(shadow, iconPos); 11525 } 11526 11527 marker._latlng = latlng; 11528 e.latlng = latlng; 11529 e.oldLatLng = this._oldLatLng; 11530 11531 // @event drag: Event 11532 // Fired repeatedly while the user drags the marker. 11533 marker 11534 .fire('move', e) 11535 .fire('drag', e); 11536 }, 11537 11538 _onDragEnd: function (e) { 11539 // @event dragend: DragEndEvent 11540 // Fired when the user stops dragging the marker. 11541 11542 // @event moveend: Event 11543 // Fired when the marker stops moving (because of dragging). 11544 delete this._oldLatLng; 11545 this._marker 11546 .fire('moveend') 11547 .fire('dragend', e); 11548 } 11549}); 11550 11551 11552 11553/* 11554 * @class Control 11555 * @aka L.Control 11556 * 11557 * L.Control is a base class for implementing map controls. Handles positioning. 11558 * All other controls extend from this class. 11559 */ 11560 11561L.Control = L.Class.extend({ 11562 // @section 11563 // @aka Control options 11564 options: {
11565 // @option position: String = 'topright' 11566 // The position of the control (one of the map corners). Possible values are `'topleft'`, 11567 // `'topright'`, `'bottomleft'` or `'bottomright'` 11568 position: 'topright' 11569 }, 11570 11571 initialize: function (options) { 11572 L.setOptions(this, options); 11573 }, 11574 11575 /* @section 11576 * Classes extending L.Control will inherit the following methods: 11577 * 11578 * @method getPosition: string 11579 * Returns the position of the control. 11580 */ 11581 getPosition: function () { 11582 return this.options.position; 11583 }, 11584 11585 // @method setPosition(position: string): this 11586 // Sets the position of the control. 11587 setPosition: function (position) { 11588 var map = this._map; 11589 11590 if (map) { 11591 map.removeControl(this); 11592 } 11593 11594 this.options.position = position; 11595 11596 if (map) { 11597 map.addControl(this); 11598 } 11599 11600 return this; 11601 }, 11602 11603 // @method getContainer: HTMLElement 11604 // Returns the HTMLElement that contains the control. 11605 getContainer: function () { 11606 return this._container; 11607 }, 11608 11609 // @method addTo(map: Map): this 11610 // Adds the control to the given map. 11611 addTo: function (map) { 11612 this.remove(); 11613 this._map = map; 11614 11615 var container = this._container = this.onAdd(map), 11616 pos = this.getPosition(), 11617 corner = map._controlCorners[pos]; 11618 11619 L.DomUtil.addClass(container, 'leaflet-control'); 11620 11621 if (pos.indexOf('bottom') !== -1) { 11622 corner.insertBefore(container, corner.firstChild); 11623 } else { 11624 corner.appendChild(container); 11625 } 11626 11627 return this; 11628 }, 11629 11630 // @method remove: this 11631 // Removes the control from the map it is currently active on. 11632 remove: function () { 11633 if (!this._map) { 11634 return this; 11635 } 11636 11637 L.DomUtil.remove(this._container); 11638 11639 if (this.onRemove) { 11640 this.onRemove(this._map); 11641 } 11642 11643 this._map = null; 11644 11645 return this; 11646 }, 11647 11648 _refocusOnMap: function (e) { 11649 // if map exists and event is not a keyboard event 11650 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 11651 this._map.getContainer().focus(); 11652 } 11653 } 11654}); 11655 11656L.control = function (options) { 11657 return new L.Control(options); 11658}; 11659 11660/* @section Extension methods 11661 * @uninheritable 11662 * 11663 * Every control should extend from `L.Control` and (re-)implement the following methods. 11664 * 11665 * @method onAdd(map: Map): HTMLElement 11666 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 11667 * 11668 * @method onRemove(map: Map) 11669 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 11670 */ 11671 11672/* @namespace Map 11673 * @section Methods for Layers and Controls 11674 */ 11675L.Map.include({ 11676 // @method addControl(control: Control): this 11677 // Adds the given control to the map 11678 addControl: function (control) { 11679 control.addTo(this); 11680 return this; 11681 }, 11682 11683 // @method removeControl(control: Control): this 11684 // Removes the given control from the map 11685 removeControl: function (control) { 11686 control.remove(); 11687 return this; 11688 }, 11689 11690 _initControlPos: function () { 11691 var corners = this._controlCorners = {}, 11692 l = 'leaflet-', 11693 container = this._controlContainer = 11694 L.DomUtil.create('div', l + 'control-container', this._container); 11695 11696 function createCorner(vSide, hSide) { 11697 var className = l + vSide + ' ' + l + hSide; 11698 11699 corners[vSide + hSide] = L.DomUtil.create('div', className, container); 11700 } 11701 11702 createCorner('top', 'left'); 11703 createCorner('top', 'right'); 11704 createCorner('bottom', 'left'); 11705 createCorner('bottom', 'right'); 11706 }, 11707 11708 _clearControlPos: function () { 11709 L.DomUtil.remove(this._controlContainer); 11710 } 11711}); 11712 11713 11714 11715/* 11716 * @class Control.Zoom 11717 * @aka L.Control.Zoom 11718 * @inherits Control 11719 * 11720 * 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`. 11721 */ 11722 11723L.Control.Zoom = L.Control.extend({ 11724 // @section 11725 // @aka Control.Zoom options 11726 options: { 11727 position: 'topleft', 11728 11729 // @option zoomInText: String = '+' 11730 // The text set on the 'zoom in' button. 11731 zoomInText: '+', 11732 11733 // @option zoomInTitle: String = 'Zoom in' 11734 // The title set on the 'zoom in' button. 11735 zoomInTitle: 'Zoom in', 11736 11737 // @option zoomOutText: String = '-' 11738 // The text set on the 'zoom out' button. 11739 zoomOutText: '-', 11740 11741 // @option zoomOutTitle: String = 'Zoom out' 11742 // The title set on the 'zoom out' button. 11743 zoomOutTitle: 'Zoom out' 11744 }, 11745 11746 onAdd: function (map) { 11747 var zoomName = 'leaflet-control-zoom', 11748 container = L.DomUtil.create('div', zoomName + ' leaflet-bar'), 11749 options = this.options; 11750 11751 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 11752 zoomName + '-in', container, this._zoomIn); 11753 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 11754 zoomName + '-out', container, this._zoomOut); 11755 11756 this._updateDisabled(); 11757 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 11758 11759 return container; 11760 }, 11761 11762 onRemove: function (map) { 11763 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 11764 }, 11765 11766 disable: function () { 11767 this._disabled = true; 11768 this._updateDisabled(); 11769 return this; 11770 }, 11771 11772 enable: function () { 11773 this._disabled = false;
11774 this._updateDisabled(); 11775 return this; 11776 }, 11777 11778 _zoomIn: function (e) { 11779 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 11780 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 11781 } 11782 }, 11783 11784 _zoomOut: function (e) { 11785 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 11786 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 11787 } 11788 }, 11789 11790 _createButton: function (html, title, className, container, fn) { 11791 var link = L.DomUtil.create('a', className, container); 11792 link.innerHTML = html; 11793 link.href = '#'; 11794 link.title = title; 11795 11796 L.DomEvent 11797 .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation) 11798 .on(link, 'click', L.DomEvent.stop) 11799 .on(link, 'click', fn, this) 11800 .on(link, 'click', this._refocusOnMap, this); 11801 11802 return link; 11803 }, 11804 11805 _updateDisabled: function () { 11806 var map = this._map, 11807 className = 'leaflet-disabled'; 11808 11809 L.DomUtil.removeClass(this._zoomInButton, className); 11810 L.DomUtil.removeClass(this._zoomOutButton, className); 11811 11812 if (this._disabled || map._zoom === map.getMinZoom()) { 11813 L.DomUtil.addClass(this._zoomOutButton, className); 11814 } 11815 if (this._disabled || map._zoom === map.getMaxZoom()) { 11816 L.DomUtil.addClass(this._zoomInButton, className); 11817 } 11818 } 11819}); 11820 11821// @namespace Map 11822// @section Control options 11823// @option zoomControl: Boolean = true 11824// Whether a [zoom control](#control-zoom) is added to the map by default. 11825L.Map.mergeOptions({ 11826 zoomControl: true 11827}); 11828 11829L.Map.addInitHook(function () { 11830 if (this.options.zoomControl) { 11831 this.zoomControl = new L.Control.Zoom(); 11832 this.addControl(this.zoomControl); 11833 } 11834}); 11835 11836// @namespace Control.Zoom 11837// @factory L.control.zoom(options: Control.Zoom options) 11838// Creates a zoom control 11839L.control.zoom = function (options) { 11840 return new L.Control.Zoom(options); 11841}; 11842 11843 11844 11845/* 11846 * @class Control.Attribution 11847 * @aka L.Control.Attribution 11848 * @inherits Control 11849 * 11850 * 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. 11851 */ 11852 11853L.Control.Attribution = L.Control.extend({ 11854 // @section 11855 // @aka Control.Attribution options 11856 options: { 11857 position: 'bottomright', 11858 11859 // @option prefix: String = 'Leaflet' 11860 // The HTML text shown before the attributions. Pass `false` to disable. 11861 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>' 11862 }, 11863 11864 initialize: function (options) { 11865 L.setOptions(this, options); 11866 11867 this._attributions = {}; 11868 }, 11869 11870 onAdd: function (map) { 11871 map.attributionControl = this; 11872 this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); 11873 if (L.DomEvent) { 11874 L.DomEvent.disableClickPropagation(this._container); 11875 } 11876 11877 // TODO ugly, refactor 11878 for (var i in map._layers) { 11879 if (map._layers[i].getAttribution) { 11880 this.addAttribution(map._layers[i].getAttribution()); 11881 } 11882 } 11883 11884 this._update(); 11885 11886 return this._container; 11887 }, 11888 11889 // @method setPrefix(prefix: String): this 11890 // Sets the text before the attributions. 11891 setPrefix: function (prefix) { 11892 this.options.prefix = prefix; 11893 this._update(); 11894 return this; 11895 }, 11896 11897 // @method addAttribution(text: String): this 11898 // Adds an attribution text (e.g. `'Vector data © Mapbox'`). 11899 addAttribution: function (text) { 11900 if (!text) { return this; } 11901 11902 if (!this._attributions[text]) { 11903 this._attributions[text] = 0; 11904 } 11905 this._attributions[text]++; 11906 11907 this._update(); 11908 11909 return this; 11910 }, 11911 11912 // @method removeAttribution(text: String): this 11913 // Removes an attribution text. 11914 removeAttribution: function (text) { 11915 if (!text) { return this; } 11916 11917 if (this._attributions[text]) { 11918 this._attributions[text]--; 11919 this._update(); 11920 } 11921 11922 return this; 11923 }, 11924 11925 _update: function () { 11926 if (!this._map) { return; } 11927 11928 var attribs = []; 11929 11930 for (var i in this._attributions) { 11931 if (this._attributions[i]) { 11932 attribs.push(i); 11933 } 11934 } 11935 11936 var prefixAndAttribs = []; 11937 11938 if (this.options.prefix) { 11939 prefixAndAttribs.push(this.options.prefix); 11940 } 11941 if (attribs.length) { 11942 prefixAndAttribs.push(attribs.join(', ')); 11943 } 11944 11945 this._container.innerHTML = prefixAndAttribs.join(' | '); 11946 } 11947}); 11948 11949// @namespace Map 11950// @section Control options 11951// @option attributionControl: Boolean = true 11952// Whether a [attribution control](#control-attribution) is added to the map by default. 11953L.Map.mergeOptions({ 11954 attributionControl: true 11955}); 11956 11957L.Map.addInitHook(function () { 11958 if (this.options.attributionControl) { 11959 new L.Control.Attribution().addTo(this); 11960 } 11961}); 11962 11963// @namespace Control.Attribution 11964// @factory L.control.attribution(options: Control.Attribution options) 11965// Creates an attribution control. 11966L.control.attribution = function (options) { 11967 return new L.Control.Attribution(options); 11968}; 11969 11970 11971 11972/* 11973 * @class Control.Scale 11974 * @aka L.Control.Scale 11975 * @inherits Control 11976 * 11977 * 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`. 11978 * 11979 * @example 11980 * 11981 * ```js 11982 * L.control.scale().addTo(map); 11983 * ``` 11984 */ 11985 11986L.Control.Scale = L.Control.extend({ 11987 // @section 11988 // @aka Control.Scale options 11989 options: { 11990 position: 'bottomleft', 11991 11992 // @option maxWidth: Number = 100 11993 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 11994 maxWidth: 100, 11995 11996 // @option metric: Boolean = True 11997 // Whether to show the metric scale line (m/km). 11998 metric: true, 11999 12000 // @option imperial: Boolean = True 12001 // Whether to show the imperial scale line (mi/ft).
vendor: 4,344 bytes, lines 12002-12166
12002 imperial: true 12003 12004 // @option updateWhenIdle: Boolean = false 12005 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 12006 }, 12007 12008 onAdd: function (map) { 12009 var className = 'leaflet-control-scale', 12010 container = L.DomUtil.create('div', className), 12011 options = this.options; 12012 12013 this._addScales(options, className + '-line', container); 12014 12015 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 12016 map.whenReady(this._update, this); 12017 12018 return container; 12019 }, 12020 12021 onRemove: function (map) { 12022 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 12023 }, 12024 12025 _addScales: function (options, className, container) { 12026 if (options.metric) { 12027 this._mScale = L.DomUtil.create('div', className, container); 12028 } 12029 if (options.imperial) { 12030 this._iScale = L.DomUtil.create('div', className, container); 12031 } 12032 }, 12033 12034 _update: function () { 12035 var map = this._map, 12036 y = map.getSize().y / 2; 12037 12038 var maxMeters = map.distance( 12039 map.containerPointToLatLng([0, y]), 12040 map.containerPointToLatLng([this.options.maxWidth, y])); 12041 12042 this._updateScales(maxMeters); 12043 }, 12044 12045 _updateScales: function (maxMeters) { 12046 if (this.options.metric && maxMeters) { 12047 this._updateMetric(maxMeters); 12048 } 12049 if (this.options.imperial && maxMeters) { 12050 this._updateImperial(maxMeters); 12051 } 12052 }, 12053 12054 _updateMetric: function (maxMeters) { 12055 var meters = this._getRoundNum(maxMeters), 12056 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 12057 12058 this._updateScale(this._mScale, label, meters / maxMeters); 12059 }, 12060 12061 _updateImperial: function (maxMeters) { 12062 var maxFeet = maxMeters * 3.2808399, 12063 maxMiles, miles, feet; 12064 12065 if (maxFeet > 5280) { 12066 maxMiles = maxFeet / 5280; 12067 miles = this._getRoundNum(maxMiles); 12068 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 12069 12070 } else { 12071 feet = this._getRoundNum(maxFeet); 12072 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 12073 } 12074 }, 12075 12076 _updateScale: function (scale, text, ratio) { 12077 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 12078 scale.innerHTML = text; 12079 }, 12080 12081 _getRoundNum: function (num) { 12082 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 12083 d = num / pow10; 12084 12085 d = d >= 10 ? 10 : 12086 d >= 5 ? 5 : 12087 d >= 3 ? 3 : 12088 d >= 2 ? 2 : 1; 12089 12090 return pow10 * d; 12091 } 12092}); 12093 12094 12095// @factory L.control.scale(options?: Control.Scale options) 12096// Creates an scale control with the given options. 12097L.control.scale = function (options) { 12098 return new L.Control.Scale(options); 12099}; 12100 12101 12102 12103/* 12104 * @class Control.Layers 12105 * @aka L.Control.Layers 12106 * @inherits Control 12107 * 12108 * 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`. 12109 * 12110 * @example 12111 * 12112 * ```js 12113 * var baseLayers = { 12114 * "Mapbox": mapbox, 12115 * "OpenStreetMap": osm 12116 * }; 12117 * 12118 * var overlays = { 12119 * "Marker": marker, 12120 * "Roads": roadsLayer 12121 * }; 12122 * 12123 * L.control.layers(baseLayers, overlays).addTo(map); 12124 * ``` 12125 * 12126 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 12127 * 12128 * ```js 12129 * { 12130 * "<someName1>": layer1, 12131 * "<someName2>": layer2 12132 * } 12133 * ``` 12134 * 12135 * The layer names can contain HTML, which allows you to add additional styling to the items: 12136 * 12137 * ```js 12138 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 12139 * ``` 12140 */ 12141 12142 12143L.Control.Layers = L.Control.extend({ 12144 // @section 12145 // @aka Control.Layers options 12146 options: { 12147 // @option collapsed: Boolean = true 12148 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. 12149 collapsed: true, 12150 position: 'topright', 12151 12152 // @option autoZIndex: Boolean = true 12153 // 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. 12154 autoZIndex: true, 12155 12156 // @option hideSingleBase: Boolean = false 12157 // If `true`, the base layers in the control will be hidden when there is only one. 12158 hideSingleBase: false 12159 }, 12160 12161 initialize: function (baseLayers, overlays, options) { 12162 L.setOptions(this, options); 12163 12164 this._layers = []; 12165 this._lastZIndex = 0; 12166 this._handlingClick = false;
12167 12168 for (var i in baseLayers) { 12169 this._addLayer(baseLayers[i], i); 12170 } 12171 12172 for (i in overlays) { 12173 this._addLayer(overlays[i], i, true); 12174 } 12175 }, 12176 12177 onAdd: function (map) { 12178 this._initLayout(); 12179 this._update(); 12180 12181 this._map = map; 12182 map.on('zoomend', this._checkDisabledLayers, this); 12183 12184 return this._container; 12185 }, 12186 12187 onRemove: function () { 12188 this._map.off('zoomend', this._checkDisabledLayers, this); 12189 12190 for (var i = 0; i < this._layers.length; i++) { 12191 this._layers[i].layer.off('add remove', this._onLayerChange, this); 12192 } 12193 }, 12194 12195 // @method addBaseLayer(layer: Layer, name: String): this 12196 // Adds a base layer (radio button entry) with the given name to the control. 12197 addBaseLayer: function (layer, name) { 12198 this._addLayer(layer, name); 12199 return (this._map) ? this._update() : this; 12200 }, 12201 12202 // @method addOverlay(layer: Layer, name: String): this 12203 // Adds an overlay (checkbox entry) with the given name to the control. 12204 addOverlay: function (layer, name) { 12205 this._addLayer(layer, name, true); 12206 return (this._map) ? this._update() : this; 12207 }, 12208 12209 // @method removeLayer(layer: Layer): this 12210 // Remove the given layer from the control. 12211 removeLayer: function (layer) { 12212 layer.off('add remove', this._onLayerChange, this); 12213 12214 var obj = this._getLayer(L.stamp(layer)); 12215 if (obj) { 12216 this._layers.splice(this._layers.indexOf(obj), 1); 12217 } 12218 return (this._map) ? this._update() : this; 12219 }, 12220 12221 // @method expand(): this 12222 // Expand the control container if collapsed. 12223 expand: function () { 12224 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded'); 12225 this._form.style.height = null; 12226 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 12227 if (acceptableHeight < this._form.clientHeight) { 12228 L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar'); 12229 this._form.style.height = acceptableHeight + 'px'; 12230 } else { 12231 L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar'); 12232 } 12233 this._checkDisabledLayers(); 12234 return this; 12235 }, 12236 12237 // @method collapse(): this 12238 // Collapse the control container if expanded. 12239 collapse: function () { 12240 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); 12241 return this; 12242 }, 12243 12244 _initLayout: function () { 12245 var className = 'leaflet-control-layers', 12246 container = this._container = L.DomUtil.create('div', className); 12247 12248 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 12249 container.setAttribute('aria-haspopup', true); 12250 12251 L.DomEvent.disableClickPropagation(container); 12252 if (!L.Browser.touch) { 12253 L.DomEvent.disableScrollPropagation(container); 12254 } 12255 12256 var form = this._form = L.DomUtil.create('form', className + '-list'); 12257 12258 if (this.options.collapsed) { 12259 if (!L.Browser.android) { 12260 L.DomEvent.on(container, { 12261 mouseenter: this.expand, 12262 mouseleave: this.collapse 12263 }, this); 12264 } 12265 12266 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); 12267 link.href = '#'; 12268 link.title = 'Layers'; 12269 12270 if (L.Browser.touch) { 12271 L.DomEvent 12272 .on(link, 'click', L.DomEvent.stop) 12273 .on(link, 'click', this.expand, this); 12274 } else { 12275 L.DomEvent.on(link, 'focus', this.expand, this); 12276 } 12277 12278 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033 12279 L.DomEvent.on(form, 'click', function () { 12280 setTimeout(L.bind(this._onInputClick, this), 0); 12281 }, this); 12282 12283 this._map.on('click', this.collapse, this); 12284 // TODO keyboard accessibility 12285 } else { 12286 this.expand(); 12287 } 12288 12289 this._baseLayersList = L.DomUtil.create('div', className + '-base', form); 12290 this._separator = L.DomUtil.create('div', className + '-separator', form); 12291 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); 12292 12293 container.appendChild(form); 12294 }, 12295 12296 _getLayer: function (id) { 12297 for (var i = 0; i < this._layers.length; i++) { 12298 12299 if (this._layers[i] && L.stamp(this._layers[i].layer) === id) { 12300 return this._layers[i]; 12301 } 12302 } 12303 }, 12304 12305 _addLayer: function (layer, name, overlay) { 12306 layer.on('add remove', this._onLayerChange, this); 12307 12308 this._layers.push({ 12309 layer: layer, 12310 name: name, 12311 overlay: overlay 12312 }); 12313
12314 if (this.options.autoZIndex && layer.setZIndex) { 12315 this._lastZIndex++; 12316 layer.setZIndex(this._lastZIndex); 12317 } 12318 }, 12319 12320 _update: function () { 12321 if (!this._container) { return this; } 12322 12323 L.DomUtil.empty(this._baseLayersList); 12324 L.DomUtil.empty(this._overlaysList); 12325 12326 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 12327 12328 for (i = 0; i < this._layers.length; i++) { 12329 obj = this._layers[i]; 12330 this._addItem(obj); 12331 overlaysPresent = overlaysPresent || obj.overlay; 12332 baseLayersPresent = baseLayersPresent || !obj.overlay; 12333 baseLayersCount += !obj.overlay ? 1 : 0; 12334 } 12335 12336 // Hide base layers section if there's only one layer. 12337 if (this.options.hideSingleBase) { 12338 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 12339 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 12340 } 12341 12342 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 12343 12344 return this; 12345 }, 12346 12347 _onLayerChange: function (e) { 12348 if (!this._handlingClick) { 12349 this._update(); 12350 } 12351 12352 var obj = this._getLayer(L.stamp(e.target)); 12353 12354 // @namespace Map 12355 // @section Layer events 12356 // @event baselayerchange: LayersControlEvent 12357 // Fired when the base layer is changed through the [layer control](#control-layers). 12358 // @event overlayadd: LayersControlEvent 12359 // Fired when an overlay is selected through the [layer control](#control-layers). 12360 // @event overlayremove: LayersControlEvent 12361 // Fired when an overlay is deselected through the [layer control](#control-layers). 12362 // @namespace Control.Layers 12363 var type = obj.overlay ? 12364 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 12365 (e.type === 'add' ? 'baselayerchange' : null); 12366 12367 if (type) { 12368 this._map.fire(type, obj); 12369 } 12370 }, 12371 12372 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) 12373 _createRadioElement: function (name, checked) { 12374 12375 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 12376 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 12377 12378 var radioFragment = document.createElement('div'); 12379 radioFragment.innerHTML = radioHtml; 12380 12381 return radioFragment.firstChild; 12382 }, 12383 12384 _addItem: function (obj) { 12385 var label = document.createElement('label'), 12386 checked = this._map.hasLayer(obj.layer), 12387 input; 12388 12389 if (obj.overlay) { 12390 input = document.createElement('input'); 12391 input.type = 'checkbox'; 12392 input.className = 'leaflet-control-layers-selector'; 12393 input.defaultChecked = checked; 12394 } else { 12395 input = this._createRadioElement('leaflet-base-layers', checked); 12396 } 12397 12398 input.layerId = L.stamp(obj.layer); 12399 12400 L.DomEvent.on(input, 'click', this._onInputClick, this); 12401 12402 var name = document.createElement('span'); 12403 name.innerHTML = ' ' + obj.name; 12404 12405 // Helps from preventing layer control flicker when checkboxes are disabled 12406 // https://github.com/Leaflet/Leaflet/issues/2771 12407 var holder = document.createElement('div'); 12408 12409 label.appendChild(holder); 12410 holder.appendChild(input); 12411 holder.appendChild(name); 12412 12413 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 12414 container.appendChild(label); 12415 12416 this._checkDisabledLayers(); 12417 return label; 12418 }, 12419 12420 _onInputClick: function () { 12421 var inputs = this._form.getElementsByTagName('input'), 12422 input, layer, hasLayer; 12423 var addedLayers = [], 12424 removedLayers = []; 12425 12426 this._handlingClick = true; 12427 12428 for (var i = inputs.length - 1; i >= 0; i--) { 12429 input = inputs[i]; 12430 layer = this._getLayer(input.layerId).layer; 12431 hasLayer = this._map.hasLayer(layer); 12432 12433 if (input.checked && !hasLayer) { 12434 addedLayers.push(layer); 12435 12436 } else if (!input.checked && hasLayer) { 12437 removedLayers.push(layer); 12438 } 12439 } 12440 12441 // Bugfix issue 2318: Should remove all old layers before readding new ones 12442 for (i = 0; i < removedLayers.length; i++) { 12443 this._map.removeLayer(removedLayers[i]); 12444 } 12445 for (i = 0; i < addedLayers.length; i++) { 12446 this._map.addLayer(addedLayers[i]); 12447 } 12448 12449 this._handlingClick = false; 12450 12451 this._refocusOnMap(); 12452 }, 12453 12454 _checkDisabledLayers: function () { 12455 var inputs = this._form.getElementsByTagName('input'), 12456 input, 12457 layer, 12458 zoom = this._map.getZoom(); 12459
12460 for (var i = inputs.length - 1; i >= 0; i--) { 12461 input = inputs[i]; 12462 layer = this._getLayer(input.layerId).layer; 12463 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 12464 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 12465 12466 } 12467 }, 12468 12469 _expand: function () { 12470 // Backward compatibility, remove me in 1.1. 12471 return this.expand(); 12472 }, 12473 12474 _collapse: function () { 12475 // Backward compatibility, remove me in 1.1. 12476 return this.collapse(); 12477 } 12478 12479}); 12480 12481 12482// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 12483// 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. 12484L.control.layers = function (baseLayers, overlays, options) { 12485 return new L.Control.Layers(baseLayers, overlays, options); 12486}; 12487 12488 12489 12490/* 12491 * @class PosAnimation 12492 * @aka L.PosAnimation 12493 * @inherits Evented 12494 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 12495 * 12496 * @example 12497 * ```js 12498 * var fx = new L.PosAnimation(); 12499 * fx.run(el, [300, 500], 0.5); 12500 * ``` 12501 * 12502 * @constructor L.PosAnimation() 12503 * Creates a `PosAnimation` object. 12504 * 12505 */ 12506 12507L.PosAnimation = L.Evented.extend({ 12508 12509 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 12510 // Run an animation of a given element to a new position, optionally setting 12511 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 12512 // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1), 12513 // `0.5` by default). 12514 run: function (el, newPos, duration, easeLinearity) { 12515 this.stop(); 12516 12517 this._el = el; 12518 this._inProgress = true; 12519 this._duration = duration || 0.25; 12520 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 12521 12522 this._startPos = L.DomUtil.getPosition(el); 12523 this._offset = newPos.subtract(this._startPos); 12524 this._startTime = +new Date(); 12525 12526 // @event start: Event 12527 // Fired when the animation starts 12528 this.fire('start'); 12529 12530 this._animate(); 12531 }, 12532 12533 // @method stop() 12534 // Stops the animation (if currently running). 12535 stop: function () { 12536 if (!this._inProgress) { return; } 12537 12538 this._step(true); 12539 this._complete(); 12540 }, 12541 12542 _animate: function () { 12543 // animation loop 12544 this._animId = L.Util.requestAnimFrame(this._animate, this); 12545 this._step(); 12546 }, 12547 12548 _step: function (round) { 12549 var elapsed = (+new Date()) - this._startTime, 12550 duration = this._duration * 1000; 12551 12552 if (elapsed < duration) { 12553 this._runFrame(this._easeOut(elapsed / duration), round); 12554 } else { 12555 this._runFrame(1); 12556 this._complete(); 12557 } 12558 }, 12559 12560 _runFrame: function (progress, round) { 12561 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 12562 if (round) { 12563 pos._round(); 12564 } 12565 L.DomUtil.setPosition(this._el, pos); 12566 12567 // @event step: Event 12568 // Fired continuously during the animation. 12569 this.fire('step'); 12570 }, 12571 12572 _complete: function () { 12573 L.Util.cancelAnimFrame(this._animId); 12574 12575 this._inProgress = false;
12576 // @event end: Event 12577 // Fired when the animation ends. 12578 this.fire('end'); 12579 }, 12580 12581 _easeOut: function (t) { 12582 return 1 - Math.pow(1 - t, this._easeOutPower); 12583 } 12584}); 12585 12586 12587 12588/* 12589 * Extends L.Map to handle panning animations. 12590 */ 12591 12592L.Map.include({ 12593 12594 setView: function (center, zoom, options) { 12595 12596 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 12597 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds); 12598 options = options || {}; 12599 12600 this._stop(); 12601 12602 if (this._loaded && !options.reset && options !== true) { 12603 12604 if (options.animate !== undefined) { 12605 options.zoom = L.extend({animate: options.animate}, options.zoom); 12606 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan); 12607 } 12608 12609 // try animating pan or zoom 12610 var moved = (this._zoom !== zoom) ? 12611 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 12612 this._tryAnimatedPan(center, options.pan); 12613 12614 if (moved) { 12615 // prevent resize handler call, the view will refresh after animation anyway 12616 clearTimeout(this._sizeTimer); 12617 return this; 12618 } 12619 } 12620 12621 // animation didn't start, just reset the map view 12622 this._resetView(center, zoom); 12623 12624 return this; 12625 }, 12626 12627 panBy: function (offset, options) { 12628 offset = L.point(offset).round(); 12629 options = options || {}; 12630 12631 if (!offset.x && !offset.y) { 12632 return this.fire('moveend'); 12633 } 12634 // If we pan too far, Chrome gets issues with tiles 12635 // and makes them disappear or appear in the wrong place (slightly offset) #2602 12636 if (options.animate !== true && !this.getSize().contains(offset)) { 12637 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 12638 return this; 12639 } 12640 12641 if (!this._panAnim) { 12642 this._panAnim = new L.PosAnimation(); 12643 12644 this._panAnim.on({ 12645 'step': this._onPanTransitionStep, 12646 'end': this._onPanTransitionEnd 12647 }, this); 12648 } 12649 12650 // don't fire movestart if animating inertia 12651 if (!options.noMoveStart) { 12652 this.fire('movestart'); 12653 } 12654 12655 // animate pan unless animate: false specified 12656 if (options.animate !== false) { 12657 L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); 12658 12659 var newPos = this._getMapPanePos().subtract(offset).round(); 12660 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 12661 } else { 12662 this._rawPanBy(offset); 12663 this.fire('move').fire('moveend'); 12664 } 12665 12666 return this; 12667 }, 12668 12669 _onPanTransitionStep: function () { 12670 this.fire('move'); 12671 }, 12672 12673 _onPanTransitionEnd: function () { 12674 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim'); 12675 this.fire('moveend'); 12676 }, 12677 12678 _tryAnimatedPan: function (center, options) { 12679 // difference between the new and current centers in pixels 12680 var offset = this._getCenterOffset(center)._floor(); 12681 12682 // don't animate too far unless animate: true specified in options 12683 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 12684 12685 this.panBy(offset, options); 12686 12687 return true; 12688 } 12689}); 12690 12691 12692 12693/* 12694 * Extends L.Map to handle zoom animations. 12695 */ 12696 12697// @namespace Map 12698// @section Animation Options 12699L.Map.mergeOptions({ 12700 // @option zoomAnimation: Boolean = true 12701 // Whether the map zoom animation is enabled. By default it's enabled 12702 // in all browsers that support CSS3 Transitions except Android. 12703 zoomAnimation: true, 12704 12705 // @option zoomAnimationThreshold: Number = 4 12706 // Won't animate zoom if the zoom difference exceeds this value. 12707 zoomAnimationThreshold: 4 12708}); 12709 12710var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera; 12711 12712if (zoomAnimated) { 12713 12714 L.Map.addInitHook(function () { 12715 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 12716 this._zoomAnimated = this.options.zoomAnimation; 12717 12718 // zoom transitions run with the same duration for all layers, so if one of transitionend events 12719 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 12720 if (this._zoomAnimated) { 12721 12722 this._createAnimProxy(); 12723 12724 L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); 12725 } 12726 }); 12727} 12728 12729L.Map.include(!zoomAnimated ? {} : { 12730 12731 _createAnimProxy: function () { 12732 12733 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated'); 12734 this._panes.mapPane.appendChild(proxy); 12735 12736 this.on('zoomanim', function (e) { 12737 var prop = L.DomUtil.TRANSFORM, 12738 transform = proxy.style[prop]; 12739 12740 L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 12741 12742 // workaround for case when transform is the same and so transitionend event is not fired 12743 if (transform === proxy.style[prop] && this._animatingZoom) { 12744 this._onZoomTransitionEnd(); 12745 } 12746 }, this); 12747 12748 this.on('load moveend', function () { 12749 var c = this.getCenter(), 12750 z = this.getZoom(); 12751 L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1)); 12752 }, this); 12753 }, 12754 12755 _catchTransitionEnd: function (e) { 12756 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 12757 this._onZoomTransitionEnd(); 12758 } 12759 }, 12760 12761 _nothingToAnimate: function () { 12762 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 12763 }, 12764
12765 _tryAnimatedZoom: function (center, zoom, options) { 12766 12767 if (this._animatingZoom) { return true; } 12768 12769 options = options || {}; 12770 12771 // don't animate if disabled, not supported or zoom difference is too large 12772 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 12773 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 12774 12775 // offset is the pixel coords of the zoom origin relative to the current center 12776 var scale = this.getZoomScale(zoom), 12777 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 12778 12779 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 12780 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 12781 12782 L.Util.requestAnimFrame(function () { 12783 this 12784 ._moveStart(true) 12785 ._animateZoom(center, zoom, true); 12786 }, this); 12787 12788 return true; 12789 }, 12790 12791 _animateZoom: function (center, zoom, startAnim, noUpdate) { 12792 if (startAnim) { 12793 this._animatingZoom = true; 12794 12795 // remember what center/zoom to set after animation 12796 this._animateToCenter = center; 12797 this._animateToZoom = zoom; 12798 12799 L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); 12800 } 12801 12802 // @event zoomanim: ZoomAnimEvent 12803 // Fired on every frame of a zoom animation 12804 this.fire('zoomanim', { 12805 center: center, 12806 zoom: zoom, 12807 noUpdate: noUpdate 12808 }); 12809 12810 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 12811 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250); 12812 }, 12813 12814 _onZoomTransitionEnd: function () { 12815 if (!this._animatingZoom) { return; } 12816 12817 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); 12818 12819 this._animatingZoom = false;
vendor: 3,295 bytes, lines 12820-12941
12820 12821 this._move(this._animateToCenter, this._animateToZoom); 12822 12823 // This anim frame should prevent an obscure iOS webkit tile loading race condition. 12824 L.Util.requestAnimFrame(function () { 12825 this._moveEnd(true); 12826 }, this); 12827 } 12828}); 12829 12830 12831 12832// @namespace Map 12833// @section Methods for modifying map state 12834L.Map.include({ 12835 12836 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 12837 // Sets the view of the map (geographical center and zoom) performing a smooth 12838 // pan-zoom animation. 12839 flyTo: function (targetCenter, targetZoom, options) { 12840 12841 options = options || {}; 12842 if (options.animate === false || !L.Browser.any3d) { 12843 return this.setView(targetCenter, targetZoom, options); 12844 } 12845 12846 this._stop(); 12847 12848 var from = this.project(this.getCenter()), 12849 to = this.project(targetCenter), 12850 size = this.getSize(), 12851 startZoom = this._zoom; 12852 12853 targetCenter = L.latLng(targetCenter); 12854 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 12855 12856 var w0 = Math.max(size.x, size.y), 12857 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 12858 u1 = (to.distanceTo(from)) || 1, 12859 rho = 1.42, 12860 rho2 = rho * rho; 12861 12862 function r(i) { 12863 var s1 = i ? -1 : 1, 12864 s2 = i ? w1 : w0, 12865 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 12866 b1 = 2 * s2 * rho2 * u1, 12867 b = t1 / b1, 12868 sq = Math.sqrt(b * b + 1) - b; 12869 12870 // workaround for floating point precision bug when sq = 0, log = -Infinite, 12871 // thus triggering an infinite loop in flyTo 12872 var log = sq < 0.000000001 ? -18 : Math.log(sq); 12873 12874 return log; 12875 } 12876 12877 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 12878 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 12879 function tanh(n) { return sinh(n) / cosh(n); } 12880 12881 var r0 = r(0); 12882 12883 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 12884 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 12885 12886 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 12887 12888 var start = Date.now(), 12889 S = (r(1) - r0) / rho, 12890 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 12891 12892 function frame() { 12893 var t = (Date.now() - start) / duration, 12894 s = easeOut(t) * S; 12895 12896 if (t <= 1) { 12897 this._flyToFrame = L.Util.requestAnimFrame(frame, this); 12898 12899 this._move( 12900 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 12901 this.getScaleZoom(w0 / w(s), startZoom), 12902 {flyTo: true}); 12903 12904 } else { 12905 this 12906 ._move(targetCenter, targetZoom) 12907 ._moveEnd(true); 12908 } 12909 } 12910 12911 this._moveStart(true); 12912 12913 frame.call(this); 12914 return this; 12915 }, 12916 12917 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 12918 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 12919 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 12920 flyToBounds: function (bounds, options) { 12921 var target = this._getBoundsCenterZoom(bounds, options); 12922 return this.flyTo(target.center, target.zoom, options); 12923 } 12924}); 12925 12926 12927 12928/* 12929 * Provides L.Map with convenient shortcuts for using browser geolocation features. 12930 */ 12931 12932// @namespace Map 12933 12934L.Map.include({ 12935 // @section Geolocation methods 12936 _defaultLocateOptions: { 12937 timeout: 10000, 12938 watch: false 12939 // setView: false 12940 // maxZoom: <Number> 12941 // maximumAge: 0
12942 // enableHighAccuracy: false 12943 }, 12944 12945 // @method locate(options?: Locate options): this 12946 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 12947 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 12948 // and optionally sets the map view to the user's location with respect to 12949 // detection accuracy (or to the world view if geolocation failed). 12950 // Note that, if your page doesn't use HTTPS, this method will fail in 12951 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 12952 // See `Locate options` for more details. 12953 locate: function (options) { 12954 12955 options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options); 12956 12957 if (!('geolocation' in navigator)) { 12958 this._handleGeolocationError({ 12959 code: 0, 12960 message: 'Geolocation not supported.' 12961 }); 12962 return this; 12963 } 12964 12965 var onResponse = L.bind(this._handleGeolocationResponse, this), 12966 onError = L.bind(this._handleGeolocationError, this); 12967 12968 if (options.watch) { 12969 this._locationWatchId = 12970 navigator.geolocation.watchPosition(onResponse, onError, options); 12971 } else { 12972 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 12973 } 12974 return this; 12975 }, 12976 12977 // @method stopLocate(): this 12978 // Stops watching location previously initiated by `map.locate({watch: true})` 12979 // and aborts resetting the map view if map.locate was called with 12980 // `{setView: true}`. 12981 stopLocate: function () { 12982 if (navigator.geolocation && navigator.geolocation.clearWatch) { 12983 navigator.geolocation.clearWatch(this._locationWatchId); 12984 } 12985 if (this._locateOptions) { 12986 this._locateOptions.setView = false; 12987 } 12988 return this; 12989 }, 12990 12991 _handleGeolocationError: function (error) { 12992 var c = error.code, 12993 message = error.message || 12994 (c === 1 ? 'permission denied' : 12995 (c === 2 ? 'position unavailable' : 'timeout')); 12996 12997 if (this._locateOptions.setView && !this._loaded) { 12998 this.fitWorld(); 12999 } 13000 13001 // @section Location events 13002 // @event locationerror: ErrorEvent 13003 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 13004 this.fire('locationerror', { 13005 code: c, 13006 message: 'Geolocation error: ' + message + '.' 13007 }); 13008 }, 13009 13010 _handleGeolocationResponse: function (pos) { 13011 var lat = pos.coords.latitude, 13012 lng = pos.coords.longitude, 13013 latlng = new L.LatLng(lat, lng), 13014 bounds = latlng.toBounds(pos.coords.accuracy), 13015 options = this._locateOptions; 13016 13017 if (options.setView) { 13018 var zoom = this.getBoundsZoom(bounds); 13019 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 13020 } 13021 13022 var data = { 13023 latlng: latlng, 13024 bounds: bounds, 13025 timestamp: pos.timestamp 13026 }; 13027 13028 for (var i in pos.coords) { 13029 if (typeof pos.coords[i] === 'number') { 13030 data[i] = pos.coords[i]; 13031 } 13032 } 13033 13034 // @event locationfound: LocationEvent 13035 // Fired when geolocation (using the [`locate`](#map-locate) method) 13036 // went successfully. 13037 this.fire('locationfound', data); 13038 } 13039}); 13040 13041 13042 13043}(window, document)); 13044//# sourceMappingURL=leaflet-src.map 13045/* 13046 Leaflet.markercluster, Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps. 13047 https://github.com/Leaflet/Leaflet.markercluster 13048 (c) 2012-2013, Dave Leaver, smartrak 13049*/ 13050(function (window, document, undefined) {/* 13051 * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within 13052 */ 13053 13054L.MarkerClusterGroup = L.FeatureGroup.extend({ 13055 13056 options: { 13057 maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center 13058 iconCreateFunction: null, 13059 13060 spiderfyOnMaxZoom: true, 13061 showCoverageOnHover: true, 13062 zoomToBoundsOnClick: true, 13063 singleMarkerMode: false, 13064 13065 disableClusteringAtZoom: null, 13066 13067 // Setting this to false prevents the removal of any clusters outside of the viewpoint, which 13068 // is the default behaviour for performance reasons. 13069 removeOutsideVisibleBounds: true, 13070 13071 // Set to false to disable all animations (zoom and spiderfy). 13072 // If false, option animateAddingMarkers below has no effect. 13073 // If L.DomUtil.TRANSITION is falsy, this option has no effect. 13074 animate: true, 13075 13076 //Whether to animate adding markers after adding the MarkerClusterGroup to the map 13077 // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains. 13078 animateAddingMarkers: false, 13079 13080 //Increase to increase the distance away that spiderfied markers appear from the center 13081 spiderfyDistanceMultiplier: 1, 13082 13083 // Make it possible to specify a polyline options on a spider leg 13084 spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 },
vendor: 52,797 bytes, lines 13085-14968
13085 13086 // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts 13087 chunkedLoading: false, 13088 chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback) 13089 chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser 13090 chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator) 13091 13092 //Options to pass to the L.Polygon constructor 13093 polygonOptions: {} 13094 }, 13095 13096 initialize: function (options) { 13097 L.Util.setOptions(this, options); 13098 if (!this.options.iconCreateFunction) { 13099 this.options.iconCreateFunction = this._defaultIconCreateFunction; 13100 } 13101 13102 this._featureGroup = L.featureGroup(); 13103 this._featureGroup.addEventParent(this); 13104 13105 this._nonPointGroup = L.featureGroup(); 13106 this._nonPointGroup.addEventParent(this); 13107 13108 this._inZoomAnimation = 0; 13109 this._needsClustering = []; 13110 this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of 13111 //The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move 13112 this._currentShownBounds = null; 13113 13114 this._queue = []; 13115 13116 this._childMarkerEventHandlers = { 13117 'dragstart': this._childMarkerDragStart, 13118 'move': this._childMarkerMoved, 13119 'dragend': this._childMarkerDragEnd, 13120 }; 13121 13122 // Hook the appropriate animation methods. 13123 var animate = L.DomUtil.TRANSITION && this.options.animate; 13124 L.extend(this, animate ? this._withAnimation : this._noAnimation); 13125 // Remember which MarkerCluster class to instantiate (animated or not). 13126 this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated; 13127 }, 13128 13129 addLayer: function (layer) { 13130 13131 if (layer instanceof L.LayerGroup) { 13132 return this.addLayers([layer]); 13133 } 13134 13135 //Don't cluster non point data 13136 if (!layer.getLatLng) { 13137 this._nonPointGroup.addLayer(layer); 13138 this.fire('layeradd', { layer: layer }); 13139 return this; 13140 } 13141 13142 if (!this._map) { 13143 this._needsClustering.push(layer); 13144 this.fire('layeradd', { layer: layer }); 13145 return this; 13146 } 13147 13148 if (this.hasLayer(layer)) { 13149 return this; 13150 } 13151 13152 13153 //If we have already clustered we'll need to add this one to a cluster 13154 13155 if (this._unspiderfy) { 13156 this._unspiderfy(); 13157 } 13158 13159 this._addLayer(layer, this._maxZoom); 13160 this.fire('layeradd', { layer: layer }); 13161 13162 // Refresh bounds and weighted positions. 13163 this._topClusterLevel._recalculateBounds(); 13164 13165 this._refreshClustersIcons(); 13166 13167 //Work out what is visible 13168 var visibleLayer = layer, 13169 currentZoom = this._zoom; 13170 if (layer.__parent) { 13171 while (visibleLayer.__parent._zoom >= currentZoom) { 13172 visibleLayer = visibleLayer.__parent; 13173 } 13174 } 13175 13176 if (this._currentShownBounds.contains(visibleLayer.getLatLng())) { 13177 if (this.options.animateAddingMarkers) { 13178 this._animationAddLayer(layer, visibleLayer); 13179 } else { 13180 this._animationAddLayerNonAnimated(layer, visibleLayer); 13181 } 13182 } 13183 return this; 13184 }, 13185 13186 removeLayer: function (layer) { 13187 13188 if (layer instanceof L.LayerGroup) { 13189 return this.removeLayers([layer]); 13190 } 13191 13192 //Non point layers 13193 if (!layer.getLatLng) { 13194 this._nonPointGroup.removeLayer(layer); 13195 this.fire('layerremove', { layer: layer }); 13196 return this; 13197 } 13198 13199 if (!this._map) { 13200 if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) { 13201 this._needsRemoving.push({ layer: layer, latlng: layer._latlng }); 13202 } 13203 this.fire('layerremove', { layer: layer }); 13204 return this; 13205 } 13206 13207 if (!layer.__parent) { 13208 return this; 13209 } 13210 13211 if (this._unspiderfy) { 13212 this._unspiderfy(); 13213 this._unspiderfyLayer(layer); 13214 } 13215 13216 //Remove the marker from clusters 13217 this._removeLayer(layer, true); 13218 this.fire('layerremove', { layer: layer }); 13219 13220 // Refresh bounds and weighted positions. 13221 this._topClusterLevel._recalculateBounds(); 13222 13223 this._refreshClustersIcons(); 13224 13225 layer.off(this._childMarkerEventHandlers, this); 13226 13227 if (this._featureGroup.hasLayer(layer)) { 13228 this._featureGroup.removeLayer(layer); 13229 if (layer.clusterShow) { 13230 layer.clusterShow(); 13231 } 13232 } 13233 13234 return this; 13235 }, 13236 13237 //Takes an array of markers and adds them in bulk 13238 addLayers: function (layersArray, skipLayerAddEvent) { 13239 if (!L.Util.isArray(layersArray)) { 13240 return this.addLayer(layersArray); 13241 } 13242 13243 var fg = this._featureGroup, 13244 npg = this._nonPointGroup, 13245 chunked = this.options.chunkedLoading, 13246 chunkInterval = this.options.chunkInterval, 13247 chunkProgress = this.options.chunkProgress, 13248 l = layersArray.length, 13249 offset = 0, 13250 originalArray = true, 13251 m; 13252 13253 if (this._map) { 13254 var started = (new Date()).getTime(); 13255 var process = L.bind(function () { 13256 var start = (new Date()).getTime(); 13257 for (; offset < l; offset++) { 13258 if (chunked && offset % 200 === 0) { 13259 // every couple hundred markers, instrument the time elapsed since processing started: 13260 var elapsed = (new Date()).getTime() - start; 13261 if (elapsed > chunkInterval) { 13262 break; // been working too hard, time to take a break :-) 13263 } 13264 } 13265 13266 m = layersArray[offset]; 13267 13268 // Group of layers, append children to layersArray and skip. 13269 // Side effects: 13270 // - Total increases, so chunkProgress ratio jumps backward. 13271 // - Groups are not included in this group, only their non-group child layers (hasLayer). 13272 // Changing array length while looping does not affect performance in current browsers: 13273 // http://jsperf.com/for-loop-changing-length/6 13274 if (m instanceof L.LayerGroup) { 13275 if (originalArray) { 13276 layersArray = layersArray.slice(); 13277 originalArray = false; 13278 } 13279 this._extractNonGroupLayers(m, layersArray); 13280 l = layersArray.length; 13281 continue; 13282 } 13283 13284 //Not point data, can't be clustered 13285 if (!m.getLatLng) { 13286 npg.addLayer(m); 13287 if (!skipLayerAddEvent) { 13288 this.fire('layeradd', { layer: m }); 13289 } 13290 continue; 13291 } 13292 13293 if (this.hasLayer(m)) { 13294 continue; 13295 } 13296 13297 this._addLayer(m, this._maxZoom); 13298 if (!skipLayerAddEvent) { 13299 this.fire('layeradd', { layer: m }); 13300 } 13301 13302 //If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will 13303 if (m.__parent) { 13304 if (m.__parent.getChildCount() === 2) { 13305 var markers = m.__parent.getAllChildMarkers(), 13306 otherMarker = markers[0] === m ? markers[1] : markers[0]; 13307 fg.removeLayer(otherMarker); 13308 } 13309 } 13310 } 13311 13312 if (chunkProgress) { 13313 // report progress and time elapsed: 13314 chunkProgress(offset, l, (new Date()).getTime() - started); 13315 } 13316 13317 // Completed processing all markers. 13318 if (offset === l) { 13319 13320 // Refresh bounds and weighted positions. 13321 this._topClusterLevel._recalculateBounds(); 13322 13323 this._refreshClustersIcons(); 13324 13325 this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds); 13326 } else { 13327 setTimeout(process, this.options.chunkDelay); 13328 } 13329 }, this); 13330 13331 process(); 13332 } else { 13333 var needsClustering = this._needsClustering; 13334 13335 for (; offset < l; offset++) { 13336 m = layersArray[offset]; 13337 13338 // Group of layers, append children to layersArray and skip. 13339 if (m instanceof L.LayerGroup) { 13340 if (originalArray) { 13341 layersArray = layersArray.slice(); 13342 originalArray = false; 13343 } 13344 this._extractNonGroupLayers(m, layersArray); 13345 l = layersArray.length; 13346 continue; 13347 } 13348 13349 //Not point data, can't be clustered 13350 if (!m.getLatLng) { 13351 npg.addLayer(m); 13352 continue; 13353 } 13354 13355 if (this.hasLayer(m)) { 13356 continue; 13357 } 13358 13359 needsClustering.push(m); 13360 } 13361 } 13362 return this; 13363 }, 13364 13365 //Takes an array of markers and removes them in bulk 13366 removeLayers: function (layersArray) { 13367 var i, m, 13368 l = layersArray.length, 13369 fg = this._featureGroup, 13370 npg = this._nonPointGroup, 13371 originalArray = true; 13372 13373 if (!this._map) { 13374 for (i = 0; i < l; i++) { 13375 m = layersArray[i]; 13376 13377 // Group of layers, append children to layersArray and skip. 13378 if (m instanceof L.LayerGroup) { 13379 if (originalArray) { 13380 layersArray = layersArray.slice(); 13381 originalArray = false; 13382 } 13383 this._extractNonGroupLayers(m, layersArray); 13384 l = layersArray.length; 13385 continue; 13386 } 13387 13388 this._arraySplice(this._needsClustering, m); 13389 npg.removeLayer(m); 13390 if (this.hasLayer(m)) { 13391 this._needsRemoving.push({ layer: m, latlng: m._latlng }); 13392 } 13393 this.fire('layerremove', { layer: m }); 13394 } 13395 return this; 13396 } 13397 13398 if (this._unspiderfy) { 13399 this._unspiderfy(); 13400 13401 // Work on a copy of the array, so that next loop is not affected. 13402 var layersArray2 = layersArray.slice(), 13403 l2 = l; 13404 for (i = 0; i < l2; i++) { 13405 m = layersArray2[i]; 13406 13407 // Group of layers, append children to layersArray and skip. 13408 if (m instanceof L.LayerGroup) { 13409 this._extractNonGroupLayers(m, layersArray2); 13410 l2 = layersArray2.length; 13411 continue; 13412 } 13413 13414 this._unspiderfyLayer(m); 13415 } 13416 } 13417 13418 for (i = 0; i < l; i++) { 13419 m = layersArray[i]; 13420 13421 // Group of layers, append children to layersArray and skip. 13422 if (m instanceof L.LayerGroup) { 13423 if (originalArray) { 13424 layersArray = layersArray.slice(); 13425 originalArray = false; 13426 } 13427 this._extractNonGroupLayers(m, layersArray); 13428 l = layersArray.length; 13429 continue; 13430 } 13431 13432 if (!m.__parent) { 13433 npg.removeLayer(m); 13434 this.fire('layerremove', { layer: m }); 13435 continue; 13436 } 13437 13438 this._removeLayer(m, true, true); 13439 this.fire('layerremove', { layer: m }); 13440 13441 if (fg.hasLayer(m)) { 13442 fg.removeLayer(m); 13443 if (m.clusterShow) { 13444 m.clusterShow(); 13445 } 13446 } 13447 } 13448 13449 // Refresh bounds and weighted positions. 13450 this._topClusterLevel._recalculateBounds(); 13451 13452 this._refreshClustersIcons(); 13453 13454 //Fix up the clusters and markers on the map 13455 this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds); 13456 13457 return this; 13458 }, 13459 13460 //Removes all layers from the MarkerClusterGroup 13461 clearLayers: function () { 13462 //Need our own special implementation as the LayerGroup one doesn't work for us 13463 13464 //If we aren't on the map (yet), blow away the markers we know of 13465 if (!this._map) { 13466 this._needsClustering = []; 13467 delete this._gridClusters; 13468 delete this._gridUnclustered; 13469 } 13470 13471 if (this._noanimationUnspiderfy) { 13472 this._noanimationUnspiderfy(); 13473 } 13474 13475 //Remove all the visible layers 13476 this._featureGroup.clearLayers(); 13477 this._nonPointGroup.clearLayers(); 13478 13479 this.eachLayer(function (marker) { 13480 marker.off(this._childMarkerEventHandlers, this); 13481 delete marker.__parent; 13482 }, this); 13483 13484 if (this._map) { 13485 //Reset _topClusterLevel and the DistanceGrids 13486 this._generateInitialClusters(); 13487 } 13488 13489 return this; 13490 }, 13491 13492 //Override FeatureGroup.getBounds as it doesn't work 13493 getBounds: function () { 13494 var bounds = new L.LatLngBounds(); 13495 13496 if (this._topClusterLevel) { 13497 bounds.extend(this._topClusterLevel._bounds); 13498 } 13499 13500 for (var i = this._needsClustering.length - 1; i >= 0; i--) { 13501 bounds.extend(this._needsClustering[i].getLatLng()); 13502 } 13503 13504 bounds.extend(this._nonPointGroup.getBounds()); 13505 13506 return bounds; 13507 }, 13508 13509 //Overrides LayerGroup.eachLayer 13510 eachLayer: function (method, context) { 13511 var markers = this._needsClustering.slice(), 13512 needsRemoving = this._needsRemoving, 13513 thisNeedsRemoving, i, j; 13514 13515 if (this._topClusterLevel) { 13516 this._topClusterLevel.getAllChildMarkers(markers); 13517 } 13518 13519 for (i = markers.length - 1; i >= 0; i--) { 13520 thisNeedsRemoving = true; 13521 13522 for (j = needsRemoving.length - 1; j >= 0; j--) { 13523 if (needsRemoving[j].layer === markers[i]) { 13524 thisNeedsRemoving = false; 13525 break; 13526 } 13527 } 13528 13529 if (thisNeedsRemoving) { 13530 method.call(context, markers[i]); 13531 } 13532 } 13533 13534 this._nonPointGroup.eachLayer(method, context); 13535 }, 13536 13537 //Overrides LayerGroup.getLayers 13538 getLayers: function () { 13539 var layers = []; 13540 this.eachLayer(function (l) { 13541 layers.push(l); 13542 }); 13543 return layers; 13544 }, 13545 13546 //Overrides LayerGroup.getLayer, WARNING: Really bad performance 13547 getLayer: function (id) { 13548 var result = null; 13549 13550 id = parseInt(id, 10); 13551 13552 this.eachLayer(function (l) { 13553 if (L.stamp(l) === id) { 13554 result = l; 13555 } 13556 }); 13557 13558 return result; 13559 }, 13560 13561 //Returns true if the given layer is in this MarkerClusterGroup 13562 hasLayer: function (layer) { 13563 if (!layer) { 13564 return false; 13565 } 13566 13567 var i, anArray = this._needsClustering; 13568 13569 for (i = anArray.length - 1; i >= 0; i--) { 13570 if (anArray[i] === layer) { 13571 return true; 13572 } 13573 } 13574 13575 anArray = this._needsRemoving; 13576 for (i = anArray.length - 1; i >= 0; i--) { 13577 if (anArray[i].layer === layer) { 13578 return false; 13579 } 13580 } 13581 13582 return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer); 13583 }, 13584 13585 //Zoom down to show the given layer (spiderfying if necessary) then calls the callback 13586 zoomToShowLayer: function (layer, callback) { 13587 13588 if (typeof callback !== 'function') { 13589 callback = function () {}; 13590 } 13591 13592 var showMarker = function () { 13593 if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) { 13594 this._map.off('moveend', showMarker, this); 13595 this.off('animationend', showMarker, this); 13596 13597 if (layer._icon) { 13598 callback(); 13599 } else if (layer.__parent._icon) { 13600 this.once('spiderfied', callback, this); 13601 layer.__parent.spiderfy(); 13602 } 13603 } 13604 }; 13605 13606 if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) { 13607 //Layer is visible ond on screen, immediate return 13608 callback(); 13609 } else if (layer.__parent._zoom < Math.round(this._map._zoom)) { 13610 //Layer should be visible at this zoom level. It must not be on screen so just pan over to it 13611 this._map.on('moveend', showMarker, this); 13612 this._map.panTo(layer.getLatLng()); 13613 } else { 13614 this._map.on('moveend', showMarker, this); 13615 this.on('animationend', showMarker, this); 13616 layer.__parent.zoomToBounds(); 13617 } 13618 }, 13619 13620 //Overrides FeatureGroup.onAdd 13621 onAdd: function (map) { 13622 this._map = map; 13623 var i, l, layer; 13624 13625 if (!isFinite(this._map.getMaxZoom())) { 13626 throw "Map has no maxZoom specified"; 13627 } 13628 13629 this._featureGroup.addTo(map); 13630 this._nonPointGroup.addTo(map); 13631 13632 if (!this._gridClusters) { 13633 this._generateInitialClusters(); 13634 } 13635 13636 this._maxLat = map.options.crs.projection.MAX_LATITUDE; 13637 13638 //Restore all the positions as they are in the MCG before removing them 13639 for (i = 0, l = this._needsRemoving.length; i < l; i++) { 13640 layer = this._needsRemoving[i]; 13641 layer.newlatlng = layer.layer._latlng; 13642 layer.layer._latlng = layer.latlng; 13643 } 13644 //Remove them, then restore their new positions 13645 for (i = 0, l = this._needsRemoving.length; i < l; i++) { 13646 layer = this._needsRemoving[i]; 13647 this._removeLayer(layer.layer, true); 13648 layer.layer._latlng = layer.newlatlng; 13649 } 13650 this._needsRemoving = []; 13651 13652 //Remember the current zoom level and bounds 13653 this._zoom = Math.round(this._map._zoom); 13654 this._currentShownBounds = this._getExpandedVisibleBounds(); 13655 13656 this._map.on('zoomend', this._zoomEnd, this); 13657 this._map.on('moveend', this._moveEnd, this); 13658 13659 if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely 13660 this._spiderfierOnAdd(); 13661 } 13662 13663 this._bindEvents(); 13664 13665 //Actually add our markers to the map: 13666 l = this._needsClustering; 13667 this._needsClustering = []; 13668 this.addLayers(l, true); 13669 }, 13670 13671 //Overrides FeatureGroup.onRemove 13672 onRemove: function (map) { 13673 map.off('zoomend', this._zoomEnd, this); 13674 map.off('moveend', this._moveEnd, this); 13675 13676 this._unbindEvents(); 13677 13678 //In case we are in a cluster animation 13679 this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', ''); 13680 13681 if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely 13682 this._spiderfierOnRemove(); 13683 } 13684 13685 delete this._maxLat; 13686 13687 //Clean up all the layers we added to the map 13688 this._hideCoverage(); 13689 this._featureGroup.remove(); 13690 this._nonPointGroup.remove(); 13691 13692 this._featureGroup.clearLayers(); 13693 13694 this._map = null; 13695 }, 13696 13697 getVisibleParent: function (marker) { 13698 var vMarker = marker; 13699 while (vMarker && !vMarker._icon) { 13700 vMarker = vMarker.__parent; 13701 } 13702 return vMarker || null; 13703 }, 13704 13705 //Remove the given object from the given array 13706 _arraySplice: function (anArray, obj) { 13707 for (var i = anArray.length - 1; i >= 0; i--) { 13708 if (anArray[i] === obj) { 13709 anArray.splice(i, 1); 13710 return true; 13711 } 13712 } 13713 }, 13714 13715 /** 13716 * Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom. 13717 * @param marker to be removed from _gridUnclustered. 13718 * @param z integer bottom start zoom level (included) 13719 * @private 13720 */ 13721 _removeFromGridUnclustered: function (marker, z) { 13722 var map = this._map, 13723 gridUnclustered = this._gridUnclustered, 13724 minZoom = this._map.getMinZoom(); 13725 13726 for (; z >= minZoom; z--) { 13727 if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) { 13728 break; 13729 } 13730 } 13731 }, 13732 13733 _childMarkerDragStart: function (e) { 13734 e.target.__dragStart = e.target._latlng; 13735 }, 13736 13737 _childMarkerMoved: function (e) { 13738 if (!this._ignoreMove && !e.target.__dragStart) { 13739 var isPopupOpen = e.target._popup && e.target._popup.isOpen(); 13740 13741 this._moveChild(e.target, e.oldLatLng, e.latlng); 13742 13743 if (isPopupOpen) { 13744 e.target.openPopup(); 13745 } 13746 } 13747 }, 13748 13749 _moveChild: function (layer, from, to) { 13750 layer._latlng = from; 13751 this.removeLayer(layer); 13752 13753 layer._latlng = to; 13754 this.addLayer(layer); 13755 }, 13756 13757 _childMarkerDragEnd: function (e) { 13758 if (e.target.__dragStart) { 13759 this._moveChild(e.target, e.target.__dragStart, e.target._latlng); 13760 } 13761 delete e.target.__dragStart; 13762 }, 13763 13764 13765 //Internal function for removing a marker from everything. 13766 //dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions) 13767 _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) { 13768 var gridClusters = this._gridClusters, 13769 gridUnclustered = this._gridUnclustered, 13770 fg = this._featureGroup, 13771 map = this._map, 13772 minZoom = this._map.getMinZoom(); 13773 13774 //Remove the marker from distance clusters it might be in 13775 if (removeFromDistanceGrid) { 13776 this._removeFromGridUnclustered(marker, this._maxZoom); 13777 } 13778 13779 //Work our way up the clusters removing them as we go if required 13780 var cluster = marker.__parent, 13781 markers = cluster._markers, 13782 otherMarker; 13783 13784 //Remove the marker from the immediate parents marker list 13785 this._arraySplice(markers, marker); 13786 13787 while (cluster) { 13788 cluster._childCount--; 13789 cluster._boundsNeedUpdate = true; 13790 13791 if (cluster._zoom < minZoom) { 13792 //Top level, do nothing 13793 break; 13794 } else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required 13795 //We need to push the other marker up to the parent 13796 otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0]; 13797 13798 //Update distance grid 13799 gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom)); 13800 gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom)); 13801 13802 //Move otherMarker up to parent 13803 this._arraySplice(cluster.__parent._childClusters, cluster); 13804 cluster.__parent._markers.push(otherMarker); 13805 otherMarker.__parent = cluster.__parent; 13806 13807 if (cluster._icon) { 13808 //Cluster is currently on the map, need to put the marker on the map instead 13809 fg.removeLayer(cluster); 13810 if (!dontUpdateMap) { 13811 fg.addLayer(otherMarker); 13812 } 13813 } 13814 } else { 13815 cluster._iconNeedsUpdate = true; 13816 } 13817 13818 cluster = cluster.__parent; 13819 } 13820 13821 delete marker.__parent; 13822 }, 13823 13824 _isOrIsParent: function (el, oel) { 13825 while (oel) { 13826 if (el === oel) { 13827 return true; 13828 } 13829 oel = oel.parentNode; 13830 } 13831 return false; 13832 }, 13833 13834 //Override L.Evented.fire 13835 fire: function (type, data, propagate) { 13836 if (data && data.layer instanceof L.MarkerCluster) { 13837 //Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget) 13838 if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) { 13839 return; 13840 } 13841 type = 'cluster' + type; 13842 } 13843 13844 L.FeatureGroup.prototype.fire.call(this, type, data, propagate); 13845 }, 13846 13847 //Override L.Evented.listens 13848 listens: function (type, propagate) { 13849 return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate); 13850 }, 13851 13852 //Default functionality 13853 _defaultIconCreateFunction: function (cluster) { 13854 var childCount = cluster.getChildCount(); 13855 13856 var c = ' marker-cluster-'; 13857 if (childCount < 10) { 13858 c += 'small'; 13859 } else if (childCount < 100) { 13860 c += 'medium'; 13861 } else { 13862 c += 'large'; 13863 } 13864 13865 return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) }); 13866 }, 13867 13868 _bindEvents: function () { 13869 var map = this._map, 13870 spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom, 13871 showCoverageOnHover = this.options.showCoverageOnHover, 13872 zoomToBoundsOnClick = this.options.zoomToBoundsOnClick; 13873 13874 //Zoom on cluster click or spiderfy if we are at the lowest level 13875 if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { 13876 this.on('clusterclick', this._zoomOrSpiderfy, this); 13877 } 13878 13879 //Show convex hull (boundary) polygon on mouse over 13880 if (showCoverageOnHover) { 13881 this.on('clustermouseover', this._showCoverage, this); 13882 this.on('clustermouseout', this._hideCoverage, this); 13883 map.on('zoomend', this._hideCoverage, this); 13884 } 13885 }, 13886 13887 _zoomOrSpiderfy: function (e) { 13888 var cluster = e.layer, 13889 bottomCluster = cluster; 13890 13891 while (bottomCluster._childClusters.length === 1) { 13892 bottomCluster = bottomCluster._childClusters[0]; 13893 } 13894 13895 if (bottomCluster._zoom === this._maxZoom && 13896 bottomCluster._childCount === cluster._childCount && 13897 this.options.spiderfyOnMaxZoom) { 13898 13899 // All child markers are contained in a single cluster from this._maxZoom to this cluster. 13900 cluster.spiderfy(); 13901 } else if (this.options.zoomToBoundsOnClick) { 13902 cluster.zoomToBounds(); 13903 } 13904 13905 // Focus the map again for keyboard users. 13906 if (e.originalEvent && e.originalEvent.keyCode === 13) { 13907 this._map._container.focus(); 13908 } 13909 }, 13910 13911 _showCoverage: function (e) { 13912 var map = this._map; 13913 if (this._inZoomAnimation) { 13914 return; 13915 } 13916 if (this._shownPolygon) { 13917 map.removeLayer(this._shownPolygon); 13918 } 13919 if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) { 13920 this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions); 13921 map.addLayer(this._shownPolygon); 13922 } 13923 }, 13924 13925 _hideCoverage: function () { 13926 if (this._shownPolygon) { 13927 this._map.removeLayer(this._shownPolygon); 13928 this._shownPolygon = null; 13929 } 13930 }, 13931 13932 _unbindEvents: function () { 13933 var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom, 13934 showCoverageOnHover = this.options.showCoverageOnHover, 13935 zoomToBoundsOnClick = this.options.zoomToBoundsOnClick, 13936 map = this._map; 13937 13938 if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { 13939 this.off('clusterclick', this._zoomOrSpiderfy, this); 13940 } 13941 if (showCoverageOnHover) { 13942 this.off('clustermouseover', this._showCoverage, this); 13943 this.off('clustermouseout', this._hideCoverage, this); 13944 map.off('zoomend', this._hideCoverage, this); 13945 } 13946 }, 13947 13948 _zoomEnd: function () { 13949 if (!this._map) { //May have been removed from the map by a zoomEnd handler 13950 return; 13951 } 13952 this._mergeSplitClusters(); 13953 13954 this._zoom = Math.round(this._map._zoom); 13955 this._currentShownBounds = this._getExpandedVisibleBounds(); 13956 }, 13957 13958 _moveEnd: function () { 13959 if (this._inZoomAnimation) { 13960 return; 13961 } 13962 13963 var newBounds = this._getExpandedVisibleBounds(); 13964 13965 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), this._zoom, newBounds); 13966 this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds); 13967 13968 this._currentShownBounds = newBounds; 13969 return; 13970 }, 13971 13972 _generateInitialClusters: function () { 13973 var maxZoom = this._map.getMaxZoom(), 13974 minZoom = this._map.getMinZoom(), 13975 radius = this.options.maxClusterRadius, 13976 radiusFn = radius; 13977 13978 //If we just set maxClusterRadius to a single number, we need to create 13979 //a simple function to return that number. Otherwise, we just have to 13980 //use the function we've passed in. 13981 if (typeof radius !== "function") { 13982 radiusFn = function () { return radius; }; 13983 } 13984 13985 if (this.options.disableClusteringAtZoom !== null) { 13986 maxZoom = this.options.disableClusteringAtZoom - 1; 13987 } 13988 this._maxZoom = maxZoom; 13989 this._gridClusters = {}; 13990 this._gridUnclustered = {}; 13991 13992 //Set up DistanceGrids for each zoom 13993 for (var zoom = maxZoom; zoom >= minZoom; zoom--) { 13994 this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom)); 13995 this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom)); 13996 } 13997 13998 // Instantiate the appropriate L.MarkerCluster class (animated or not). 13999 this._topClusterLevel = new this._markerCluster(this, minZoom - 1); 14000 }, 14001 14002 //Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom) 14003 _addLayer: function (layer, zoom) { 14004 var gridClusters = this._gridClusters, 14005 gridUnclustered = this._gridUnclustered, 14006 minZoom = this._map.getMinZoom(), 14007 markerPoint, z; 14008 14009 if (this.options.singleMarkerMode) { 14010 this._overrideMarkerIcon(layer); 14011 } 14012 14013 layer.on(this._childMarkerEventHandlers, this); 14014 14015 //Find the lowest zoom level to slot this one in 14016 for (; zoom >= minZoom; zoom--) { 14017 markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position 14018 14019 //Try find a cluster close by 14020 var closest = gridClusters[zoom].getNearObject(markerPoint); 14021 if (closest) { 14022 closest._addChild(layer); 14023 layer.__parent = closest; 14024 return; 14025 } 14026 14027 //Try find a marker close by to form a new cluster with 14028 closest = gridUnclustered[zoom].getNearObject(markerPoint); 14029 if (closest) { 14030 var parent = closest.__parent; 14031 if (parent) { 14032 this._removeLayer(closest, false); 14033 } 14034 14035 //Create new cluster with these 2 in it 14036 14037 var newCluster = new this._markerCluster(this, zoom, closest, layer); 14038 gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom)); 14039 closest.__parent = newCluster; 14040 layer.__parent = newCluster; 14041 14042 //First create any new intermediate parent clusters that don't exist 14043 var lastParent = newCluster; 14044 for (z = zoom - 1; z > parent._zoom; z--) { 14045 lastParent = new this._markerCluster(this, z, lastParent); 14046 gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z)); 14047 } 14048 parent._addChild(lastParent); 14049 14050 //Remove closest from this zoom level and any above that it is in, replace with newCluster 14051 this._removeFromGridUnclustered(closest, zoom); 14052 14053 return; 14054 } 14055 14056 //Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards 14057 gridUnclustered[zoom].addObject(layer, markerPoint); 14058 } 14059 14060 //Didn't get in anything, add us to the top 14061 this._topClusterLevel._addChild(layer); 14062 layer.__parent = this._topClusterLevel; 14063 return; 14064 }, 14065 14066 /** 14067 * Refreshes the icon of all "dirty" visible clusters. 14068 * Non-visible "dirty" clusters will be updated when they are added to the map. 14069 * @private 14070 */ 14071 _refreshClustersIcons: function () { 14072 this._featureGroup.eachLayer(function (c) { 14073 if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) { 14074 c._updateIcon(); 14075 } 14076 }); 14077 }, 14078 14079 //Enqueue code to fire after the marker expand/contract has happened 14080 _enqueue: function (fn) { 14081 this._queue.push(fn); 14082 if (!this._queueTimeout) { 14083 this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300); 14084 } 14085 }, 14086 _processQueue: function () { 14087 for (var i = 0; i < this._queue.length; i++) { 14088 this._queue[i].call(this); 14089 } 14090 this._queue.length = 0; 14091 clearTimeout(this._queueTimeout); 14092 this._queueTimeout = null; 14093 }, 14094 14095 //Merge and split any existing clusters that are too big or small 14096 _mergeSplitClusters: function () { 14097 var mapZoom = Math.round(this._map._zoom); 14098 14099 //In case we are starting to split before the animation finished 14100 this._processQueue(); 14101 14102 if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split 14103 this._animationStart(); 14104 //Remove clusters now off screen 14105 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), this._zoom, this._getExpandedVisibleBounds()); 14106 14107 this._animationZoomIn(this._zoom, mapZoom); 14108 14109 } else if (this._zoom > mapZoom) { //Zoom out, merge 14110 this._animationStart(); 14111 14112 this._animationZoomOut(this._zoom, mapZoom); 14113 } else { 14114 this._moveEnd(); 14115 } 14116 }, 14117 14118 //Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan) 14119 _getExpandedVisibleBounds: function () { 14120 if (!this.options.removeOutsideVisibleBounds) { 14121 return this._mapBoundsInfinite; 14122 } else if (L.Browser.mobile) { 14123 return this._checkBoundsMaxLat(this._map.getBounds()); 14124 } 14125 14126 return this._checkBoundsMaxLat(this._map.getBounds().pad(1)); // Padding expands the bounds by its own dimensions but scaled with the given factor. 14127 }, 14128 14129 /** 14130 * Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude 14131 * (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https://en.wikipedia.org/wiki/Web_Mercator#Formulas). 14132 * Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without 14133 * this option (or outside MCG) will have their position floored (ceiled) by the projection and rendered at that limit, 14134 * making the user think that MCG "eats" them and never displays them again. 14135 * @param bounds L.LatLngBounds 14136 * @returns {L.LatLngBounds} 14137 * @private 14138 */ 14139 _checkBoundsMaxLat: function (bounds) { 14140 var maxLat = this._maxLat; 14141 14142 if (maxLat !== undefined) { 14143 if (bounds.getNorth() >= maxLat) { 14144 bounds._northEast.lat = Infinity; 14145 } 14146 if (bounds.getSouth() <= -maxLat) { 14147 bounds._southWest.lat = -Infinity; 14148 } 14149 } 14150 14151 return bounds; 14152 }, 14153 14154 //Shared animation code 14155 _animationAddLayerNonAnimated: function (layer, newCluster) { 14156 if (newCluster === layer) { 14157 this._featureGroup.addLayer(layer); 14158 } else if (newCluster._childCount === 2) { 14159 newCluster._addToMap(); 14160 14161 var markers = newCluster.getAllChildMarkers(); 14162 this._featureGroup.removeLayer(markers[0]); 14163 this._featureGroup.removeLayer(markers[1]); 14164 } else { 14165 newCluster._updateIcon(); 14166 } 14167 }, 14168 14169 /** 14170 * Extracts individual (i.e. non-group) layers from a Layer Group. 14171 * @param group to extract layers from. 14172 * @param output {Array} in which to store the extracted layers. 14173 * @returns {*|Array} 14174 * @private 14175 */ 14176 _extractNonGroupLayers: function (group, output) { 14177 var layers = group.getLayers(), 14178 i = 0, 14179 layer; 14180 14181 output = output || []; 14182 14183 for (; i < layers.length; i++) { 14184 layer = layers[i]; 14185 14186 if (layer instanceof L.LayerGroup) { 14187 this._extractNonGroupLayers(layer, output); 14188 continue; 14189 } 14190 14191 output.push(layer); 14192 } 14193 14194 return output; 14195 }, 14196 14197 /** 14198 * Implements the singleMarkerMode option. 14199 * @param layer Marker to re-style using the Clusters iconCreateFunction. 14200 * @returns {L.Icon} The newly created icon. 14201 * @private 14202 */ 14203 _overrideMarkerIcon: function (layer) { 14204 var icon = layer.options.icon = this.options.iconCreateFunction({ 14205 getChildCount: function () { 14206 return 1; 14207 }, 14208 getAllChildMarkers: function () { 14209 return [layer]; 14210 } 14211 }); 14212 14213 return icon; 14214 } 14215}); 14216 14217// Constant bounds used in case option "removeOutsideVisibleBounds" is set to false. 14218L.MarkerClusterGroup.include({ 14219 _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity)) 14220}); 14221 14222L.MarkerClusterGroup.include({ 14223 _noAnimation: { 14224 //Non Animated versions of everything 14225 _animationStart: function () { 14226 //Do nothing... 14227 }, 14228 _animationZoomIn: function (previousZoomLevel, newZoomLevel) { 14229 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), previousZoomLevel); 14230 this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()); 14231 14232 //We didn't actually animate, but we use this event to mean "clustering animations have finished" 14233 this.fire('animationend'); 14234 }, 14235 _animationZoomOut: function (previousZoomLevel, newZoomLevel) { 14236 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), previousZoomLevel); 14237 this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()); 14238 14239 //We didn't actually animate, but we use this event to mean "clustering animations have finished" 14240 this.fire('animationend'); 14241 }, 14242 _animationAddLayer: function (layer, newCluster) { 14243 this._animationAddLayerNonAnimated(layer, newCluster); 14244 } 14245 }, 14246 14247 _withAnimation: { 14248 //Animated versions here 14249 _animationStart: function () { 14250 this._map._mapPane.className += ' leaflet-cluster-anim'; 14251 this._inZoomAnimation++; 14252 }, 14253 14254 _animationZoomIn: function (previousZoomLevel, newZoomLevel) { 14255 var bounds = this._getExpandedVisibleBounds(), 14256 fg = this._featureGroup, 14257 minZoom = this._map.getMinZoom(), 14258 i; 14259 14260 this._ignoreMove = true; 14261 14262 //Add all children of current clusters to map and remove those clusters from map 14263 this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) { 14264 var startPos = c._latlng, 14265 markers = c._markers, 14266 m; 14267 14268 if (!bounds.contains(startPos)) { 14269 startPos = null; 14270 } 14271 14272 if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us 14273 fg.removeLayer(c); 14274 c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds); 14275 } else { 14276 //Fade out old cluster 14277 c.clusterHide(); 14278 c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds); 14279 } 14280 14281 //Remove all markers that aren't visible any more 14282 //TODO: Do we actually need to do this on the higher levels too? 14283 for (i = markers.length - 1; i >= 0; i--) { 14284 m = markers[i]; 14285 if (!bounds.contains(m._latlng)) { 14286 fg.removeLayer(m); 14287 } 14288 } 14289 14290 }); 14291 14292 this._forceLayout(); 14293 14294 //Update opacities 14295 this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel); 14296 //TODO Maybe? Update markers in _recursivelyBecomeVisible 14297 fg.eachLayer(function (n) { 14298 if (!(n instanceof L.MarkerCluster) && n._icon) { 14299 n.clusterShow(); 14300 } 14301 }); 14302 14303 //update the positions of the just added clusters/markers 14304 this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) { 14305 c._recursivelyRestoreChildPositions(newZoomLevel); 14306 }); 14307 14308 this._ignoreMove = false; 14309 14310 //Remove the old clusters and close the zoom animation 14311 this._enqueue(function () { 14312 //update the positions of the just added clusters/markers 14313 this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) { 14314 fg.removeLayer(c); 14315 c.clusterShow(); 14316 }); 14317 14318 this._animationEnd(); 14319 }); 14320 }, 14321 14322 _animationZoomOut: function (previousZoomLevel, newZoomLevel) { 14323 this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel); 14324 14325 //Need to add markers for those that weren't on the map before but are now 14326 this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()); 14327 //Remove markers that were on the map before but won't be now 14328 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), previousZoomLevel, this._getExpandedVisibleBounds()); 14329 }, 14330 14331 _animationAddLayer: function (layer, newCluster) { 14332 var me = this, 14333 fg = this._featureGroup; 14334 14335 fg.addLayer(layer); 14336 if (newCluster !== layer) { 14337 if (newCluster._childCount > 2) { //Was already a cluster 14338 14339 newCluster._updateIcon(); 14340 this._forceLayout(); 14341 this._animationStart(); 14342 14343 layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng())); 14344 layer.clusterHide(); 14345 14346 this._enqueue(function () { 14347 fg.removeLayer(layer); 14348 layer.clusterShow(); 14349 14350 me._animationEnd(); 14351 }); 14352 14353 } else { //Just became a cluster 14354 this._forceLayout(); 14355 14356 me._animationStart(); 14357 me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom); 14358 } 14359 } 14360 } 14361 }, 14362 14363 // Private methods for animated versions. 14364 _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) { 14365 var bounds = this._getExpandedVisibleBounds(), 14366 minZoom = this._map.getMinZoom(); 14367 14368 //Animate all of the markers in the clusters to move to their cluster center point 14369 cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel); 14370 14371 var me = this; 14372 14373 //Update the opacity (If we immediately set it they won't animate) 14374 this._forceLayout(); 14375 cluster._recursivelyBecomeVisible(bounds, newZoomLevel); 14376 14377 //TODO: Maybe use the transition timing stuff to make this more reliable 14378 //When the animations are done, tidy up 14379 this._enqueue(function () { 14380 14381 //This cluster stopped being a cluster before the timeout fired 14382 if (cluster._childCount === 1) { 14383 var m = cluster._markers[0]; 14384 //If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it 14385 this._ignoreMove = true; 14386 m.setLatLng(m.getLatLng()); 14387 this._ignoreMove = false; 14388 if (m.clusterShow) { 14389 m.clusterShow(); 14390 } 14391 } else { 14392 cluster._recursively(bounds, newZoomLevel, minZoom, function (c) { 14393 c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1); 14394 }); 14395 } 14396 me._animationEnd(); 14397 }); 14398 }, 14399 14400 _animationEnd: function () { 14401 if (this._map) { 14402 this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', ''); 14403 } 14404 this._inZoomAnimation--; 14405 this.fire('animationend'); 14406 }, 14407 14408 //Force a browser layout of stuff in the map 14409 // Should apply the current opacity and location to all elements so we can update them again for an animation 14410 _forceLayout: function () { 14411 //In my testing this works, infact offsetWidth of any element seems to work. 14412 //Could loop all this._layers and do this for each _icon if it stops working 14413 14414 L.Util.falseFn(document.body.offsetWidth); 14415 } 14416}); 14417 14418L.markerClusterGroup = function (options) { 14419 return new L.MarkerClusterGroup(options); 14420}; 14421 14422 14423L.MarkerCluster = L.Marker.extend({ 14424 initialize: function (group, zoom, a, b) { 14425 14426 L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), { icon: this }); 14427 14428 14429 this._group = group; 14430 this._zoom = zoom; 14431 14432 this._markers = []; 14433 this._childClusters = []; 14434 this._childCount = 0; 14435 this._iconNeedsUpdate = true; 14436 this._boundsNeedUpdate = true; 14437 14438 this._bounds = new L.LatLngBounds(); 14439 14440 if (a) { 14441 this._addChild(a); 14442 } 14443 if (b) { 14444 this._addChild(b); 14445 } 14446 }, 14447 14448 //Recursively retrieve all child markers of this cluster 14449 getAllChildMarkers: function (storageArray) { 14450 storageArray = storageArray || []; 14451 14452 for (var i = this._childClusters.length - 1; i >= 0; i--) { 14453 this._childClusters[i].getAllChildMarkers(storageArray); 14454 } 14455 14456 for (var j = this._markers.length - 1; j >= 0; j--) { 14457 storageArray.push(this._markers[j]); 14458 } 14459 14460 return storageArray; 14461 }, 14462 14463 //Returns the count of how many child markers we have 14464 getChildCount: function () { 14465 return this._childCount; 14466 }, 14467 14468 //Zoom to the minimum of showing all of the child markers, or the extents of this cluster 14469 zoomToBounds: function (fitBoundsOptions) { 14470 var childClusters = this._childClusters.slice(), 14471 map = this._group._map, 14472 boundsZoom = map.getBoundsZoom(this._bounds), 14473 zoom = this._zoom + 1, 14474 mapZoom = map.getZoom(), 14475 i; 14476 14477 //calculate how far we need to zoom down to see all of the markers 14478 while (childClusters.length > 0 && boundsZoom > zoom) { 14479 zoom++; 14480 var newClusters = []; 14481 for (i = 0; i < childClusters.length; i++) { 14482 newClusters = newClusters.concat(childClusters[i]._childClusters); 14483 } 14484 childClusters = newClusters; 14485 } 14486 14487 if (boundsZoom > zoom) { 14488 this._group._map.setView(this._latlng, zoom); 14489 } else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead 14490 this._group._map.setView(this._latlng, mapZoom + 1); 14491 } else { 14492 this._group._map.fitBounds(this._bounds, fitBoundsOptions); 14493 } 14494 }, 14495 14496 getBounds: function () { 14497 var bounds = new L.LatLngBounds(); 14498 bounds.extend(this._bounds); 14499 return bounds; 14500 }, 14501 14502 _updateIcon: function () { 14503 this._iconNeedsUpdate = true; 14504 if (this._icon) { 14505 this.setIcon(this); 14506 } 14507 }, 14508 14509 //Cludge for Icon, we pretend to be an icon for performance 14510 createIcon: function () { 14511 if (this._iconNeedsUpdate) { 14512 this._iconObj = this._group.options.iconCreateFunction(this); 14513 this._iconNeedsUpdate = false; 14514 } 14515 return this._iconObj.createIcon(); 14516 }, 14517 createShadow: function () { 14518 return this._iconObj.createShadow(); 14519 }, 14520 14521 14522 _addChild: function (new1, isNotificationFromChild) { 14523 14524 this._iconNeedsUpdate = true; 14525 14526 this._boundsNeedUpdate = true; 14527 this._setClusterCenter(new1); 14528 14529 if (new1 instanceof L.MarkerCluster) { 14530 if (!isNotificationFromChild) { 14531 this._childClusters.push(new1); 14532 new1.__parent = this; 14533 } 14534 this._childCount += new1._childCount; 14535 } else { 14536 if (!isNotificationFromChild) { 14537 this._markers.push(new1); 14538 } 14539 this._childCount++; 14540 } 14541 14542 if (this.__parent) { 14543 this.__parent._addChild(new1, true); 14544 } 14545 }, 14546 14547 /** 14548 * Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position. 14549 * @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet. 14550 * @private 14551 */ 14552 _setClusterCenter: function (child) { 14553 if (!this._cLatLng) { 14554 // when clustering, take position of the first point as the cluster center 14555 this._cLatLng = child._cLatLng || child._latlng; 14556 } 14557 }, 14558 14559 /** 14560 * Assigns impossible bounding values so that the next extend entirely determines the new bounds. 14561 * This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class. 14562 * As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended. 14563 * @private 14564 */ 14565 _resetBounds: function () { 14566 var bounds = this._bounds; 14567 14568 if (bounds._southWest) { 14569 bounds._southWest.lat = Infinity; 14570 bounds._southWest.lng = Infinity; 14571 } 14572 if (bounds._northEast) { 14573 bounds._northEast.lat = -Infinity; 14574 bounds._northEast.lng = -Infinity; 14575 } 14576 }, 14577 14578 _recalculateBounds: function () { 14579 var markers = this._markers, 14580 childClusters = this._childClusters, 14581 latSum = 0, 14582 lngSum = 0, 14583 totalCount = this._childCount, 14584 i, child, childLatLng, childCount; 14585 14586 // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel. 14587 if (totalCount === 0) { 14588 return; 14589 } 14590 14591 // Reset rather than creating a new object, for performance. 14592 this._resetBounds(); 14593 14594 // Child markers. 14595 for (i = 0; i < markers.length; i++) { 14596 childLatLng = markers[i]._latlng; 14597 14598 this._bounds.extend(childLatLng); 14599 14600 latSum += childLatLng.lat; 14601 lngSum += childLatLng.lng; 14602 } 14603 14604 // Child clusters. 14605 for (i = 0; i < childClusters.length; i++) { 14606 child = childClusters[i]; 14607 14608 // Re-compute child bounds and weighted position first if necessary. 14609 if (child._boundsNeedUpdate) { 14610 child._recalculateBounds(); 14611 } 14612 14613 this._bounds.extend(child._bounds); 14614 14615 childLatLng = child._wLatLng; 14616 childCount = child._childCount; 14617 14618 latSum += childLatLng.lat * childCount; 14619 lngSum += childLatLng.lng * childCount; 14620 } 14621 14622 this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount); 14623 14624 // Reset dirty flag. 14625 this._boundsNeedUpdate = false; 14626 }, 14627 14628 //Set our markers position as given and add it to the map 14629 _addToMap: function (startPos) { 14630 if (startPos) { 14631 this._backupLatlng = this._latlng; 14632 this.setLatLng(startPos); 14633 } 14634 this._group._featureGroup.addLayer(this); 14635 }, 14636 14637 _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) { 14638 this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1, 14639 function (c) { 14640 var markers = c._markers, 14641 i, m; 14642 for (i = markers.length - 1; i >= 0; i--) { 14643 m = markers[i]; 14644 14645 //Only do it if the icon is still on the map 14646 if (m._icon) { 14647 m._setPos(center); 14648 m.clusterHide(); 14649 } 14650 } 14651 }, 14652 function (c) { 14653 var childClusters = c._childClusters, 14654 j, cm; 14655 for (j = childClusters.length - 1; j >= 0; j--) { 14656 cm = childClusters[j]; 14657 if (cm._icon) { 14658 cm._setPos(center); 14659 cm.clusterHide(); 14660 } 14661 } 14662 } 14663 ); 14664 }, 14665 14666 _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) { 14667 this._recursively(bounds, newZoomLevel, mapMinZoom, 14668 function (c) { 14669 c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel); 14670 14671 //TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be. 14672 //As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate 14673 if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) { 14674 c.clusterShow(); 14675 c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds 14676 } else { 14677 c.clusterHide(); 14678 } 14679 14680 c._addToMap(); 14681 } 14682 ); 14683 }, 14684 14685 _recursivelyBecomeVisible: function (bounds, zoomLevel) { 14686 this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) { 14687 c.clusterShow(); 14688 }); 14689 }, 14690 14691 _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) { 14692 this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel, 14693 function (c) { 14694 if (zoomLevel === c._zoom) { 14695 return; 14696 } 14697 14698 //Add our child markers at startPos (so they can be animated out) 14699 for (var i = c._markers.length - 1; i >= 0; i--) { 14700 var nm = c._markers[i]; 14701 14702 if (!bounds.contains(nm._latlng)) { 14703 continue; 14704 } 14705 14706 if (startPos) { 14707 nm._backupLatlng = nm.getLatLng(); 14708 14709 nm.setLatLng(startPos); 14710 if (nm.clusterHide) { 14711 nm.clusterHide(); 14712 } 14713 } 14714 14715 c._group._featureGroup.addLayer(nm); 14716 } 14717 }, 14718 function (c) { 14719 c._addToMap(startPos); 14720 } 14721 ); 14722 }, 14723 14724 _recursivelyRestoreChildPositions: function (zoomLevel) { 14725 //Fix positions of child markers 14726 for (var i = this._markers.length - 1; i >= 0; i--) { 14727 var nm = this._markers[i]; 14728 if (nm._backupLatlng) { 14729 nm.setLatLng(nm._backupLatlng); 14730 delete nm._backupLatlng; 14731 } 14732 } 14733 14734 if (zoomLevel - 1 === this._zoom) { 14735 //Reposition child clusters 14736 for (var j = this._childClusters.length - 1; j >= 0; j--) { 14737 this._childClusters[j]._restorePosition(); 14738 } 14739 } else { 14740 for (var k = this._childClusters.length - 1; k >= 0; k--) { 14741 this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel); 14742 } 14743 } 14744 }, 14745 14746 _restorePosition: function () { 14747 if (this._backupLatlng) { 14748 this.setLatLng(this._backupLatlng); 14749 delete this._backupLatlng; 14750 } 14751 }, 14752 14753 //exceptBounds: If set, don't remove any markers/clusters in it 14754 _recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) { 14755 var m, i; 14756 this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1, 14757 function (c) { 14758 //Remove markers at every level 14759 for (i = c._markers.length - 1; i >= 0; i--) { 14760 m = c._markers[i]; 14761 if (!exceptBounds || !exceptBounds.contains(m._latlng)) { 14762 c._group._featureGroup.removeLayer(m); 14763 if (m.clusterShow) { 14764 m.clusterShow(); 14765 } 14766 } 14767 } 14768 }, 14769 function (c) { 14770 //Remove child clusters at just the bottom level 14771 for (i = c._childClusters.length - 1; i >= 0; i--) { 14772 m = c._childClusters[i]; 14773 if (!exceptBounds || !exceptBounds.contains(m._latlng)) { 14774 c._group._featureGroup.removeLayer(m); 14775 if (m.clusterShow) { 14776 m.clusterShow(); 14777 } 14778 } 14779 } 14780 } 14781 ); 14782 }, 14783 14784 //Run the given functions recursively to this and child clusters 14785 // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to 14786 // zoomLevelToStart: zoom level to start running functions (inclusive) 14787 // zoomLevelToStop: zoom level to stop running functions (inclusive) 14788 // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level 14789 // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level 14790 _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) { 14791 var childClusters = this._childClusters, 14792 zoom = this._zoom, 14793 i, c; 14794 14795 if (zoomLevelToStart <= zoom) { 14796 if (runAtEveryLevel) { 14797 runAtEveryLevel(this); 14798 } 14799 if (runAtBottomLevel && zoom === zoomLevelToStop) { 14800 runAtBottomLevel(this); 14801 } 14802 } 14803 14804 if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) { 14805 for (i = childClusters.length - 1; i >= 0; i--) { 14806 c = childClusters[i]; 14807 if (boundsToApplyTo.intersects(c._bounds)) { 14808 c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel); 14809 } 14810 } 14811 } 14812 }, 14813 14814 //Returns true if we are the parent of only one cluster and that cluster is the same as us 14815 _isSingleParent: function () { 14816 //Don't need to check this._markers as the rest won't work if there are any 14817 return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount; 14818 } 14819}); 14820 14821 14822 14823/* 14824* Extends L.Marker to include two extra methods: clusterHide and clusterShow. 14825* 14826* They work as setOpacity(0) and setOpacity(1) respectively, but 14827* they will remember the marker's opacity when hiding and showing it again. 14828* 14829*/ 14830 14831 14832L.Marker.include({ 14833 14834 clusterHide: function () { 14835 this.options.opacityWhenUnclustered = this.options.opacity || 1; 14836 return this.setOpacity(0); 14837 }, 14838 14839 clusterShow: function () { 14840 var ret = this.setOpacity(this.options.opacity || this.options.opacityWhenUnclustered); 14841 delete this.options.opacityWhenUnclustered; 14842 return ret; 14843 } 14844 14845}); 14846 14847 14848 14849 14850 14851L.DistanceGrid = function (cellSize) { 14852 this._cellSize = cellSize; 14853 this._sqCellSize = cellSize * cellSize; 14854 this._grid = {}; 14855 this._objectPoint = { }; 14856}; 14857 14858L.DistanceGrid.prototype = { 14859 14860 addObject: function (obj, point) { 14861 var x = this._getCoord(point.x), 14862 y = this._getCoord(point.y), 14863 grid = this._grid, 14864 row = grid[y] = grid[y] || {}, 14865 cell = row[x] = row[x] || [], 14866 stamp = L.Util.stamp(obj); 14867 14868 this._objectPoint[stamp] = point; 14869 14870 cell.push(obj); 14871 }, 14872 14873 updateObject: function (obj, point) { 14874 this.removeObject(obj); 14875 this.addObject(obj, point); 14876 }, 14877 14878 //Returns true if the object was found 14879 removeObject: function (obj, point) { 14880 var x = this._getCoord(point.x), 14881 y = this._getCoord(point.y), 14882 grid = this._grid, 14883 row = grid[y] = grid[y] || {}, 14884 cell = row[x] = row[x] || [], 14885 i, len; 14886 14887 delete this._objectPoint[L.Util.stamp(obj)]; 14888 14889 for (i = 0, len = cell.length; i < len; i++) { 14890 if (cell[i] === obj) { 14891 14892 cell.splice(i, 1); 14893 14894 if (len === 1) { 14895 delete row[x]; 14896 } 14897 14898 return true; 14899 } 14900 } 14901 14902 }, 14903 14904 eachObject: function (fn, context) { 14905 var i, j, k, len, row, cell, removed, 14906 grid = this._grid; 14907 14908 for (i in grid) { 14909 row = grid[i]; 14910 14911 for (j in row) { 14912 cell = row[j]; 14913 14914 for (k = 0, len = cell.length; k < len; k++) { 14915 removed = fn.call(context, cell[k]); 14916 if (removed) { 14917 k--; 14918 len--; 14919 } 14920 } 14921 } 14922 } 14923 }, 14924 14925 getNearObject: function (point) { 14926 var x = this._getCoord(point.x), 14927 y = this._getCoord(point.y), 14928 i, j, k, row, cell, len, obj, dist, 14929 objectPoint = this._objectPoint, 14930 closestDistSq = this._sqCellSize, 14931 closest = null; 14932 14933 for (i = y - 1; i <= y + 1; i++) { 14934 row = this._grid[i]; 14935 if (row) { 14936 14937 for (j = x - 1; j <= x + 1; j++) { 14938 cell = row[j]; 14939 if (cell) { 14940 14941 for (k = 0, len = cell.length; k < len; k++) { 14942 obj = cell[k]; 14943 dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point); 14944 if (dist < closestDistSq) { 14945 closestDistSq = dist; 14946 closest = obj; 14947 } 14948 } 14949 } 14950 } 14951 } 14952 } 14953 return closest; 14954 }, 14955 14956 _getCoord: function (x) { 14957 return Math.floor(x / this._cellSize); 14958 }, 14959 14960 _sqDist: function (p, p2) { 14961 var dx = p2.x - p.x, 14962 dy = p2.y - p.y; 14963 return dx * dx + dy * dy; 14964 } 14965}; 14966 14967 14968/* Copyright (c) 2012 the authors listed at the following URL, and/or
14969the authors of referenced articles or incorporated external code: 14970http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256 14971 14972Permission is hereby granted, free of charge, to any person obtaining 14973a copy of this software and associated documentation files (the 14974"Software"), to deal in the Software without restriction, including 14975without limitation the rights to use, copy, modify, merge, publish, 14976distribute, sublicense, and/or sell copies of the Software, and to 14977permit persons to whom the Software is furnished to do so, subject to 14978the following conditions: 14979 14980The above copyright notice and this permission notice shall be 14981included in all copies or substantial portions of the Software. 14982 14983THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14984EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14985MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14986IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 14987CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 14988TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14989SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14990 14991Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434 14992*/ 14993 14994(function () { 14995 L.QuickHull = { 14996 14997 /* 14998 * @param {Object} cpt a point to be measured from the baseline 14999 * @param {Array} bl the baseline, as represented by a two-element 15000 * array of latlng objects. 15001 * @returns {Number} an approximate distance measure 15002 */ 15003 getDistant: function (cpt, bl) { 15004 var vY = bl[1].lat - bl[0].lat, 15005 vX = bl[0].lng - bl[1].lng; 15006 return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng)); 15007 }, 15008 15009 /* 15010 * @param {Array} baseLine a two-element array of latlng objects 15011 * representing the baseline to project from 15012 * @param {Array} latLngs an array of latlng objects 15013 * @returns {Object} the maximum point and all new points to stay 15014 * in consideration for the hull. 15015 */ 15016 findMostDistantPointFromBaseLine: function (baseLine, latLngs) { 15017 var maxD = 0, 15018 maxPt = null, 15019 newPoints = [], 15020 i, pt, d; 15021 15022 for (i = latLngs.length - 1; i >= 0; i--) { 15023 pt = latLngs[i]; 15024 d = this.getDistant(pt, baseLine); 15025 15026 if (d > 0) { 15027 newPoints.push(pt); 15028 } else { 15029 continue; 15030 } 15031 15032 if (d > maxD) { 15033 maxD = d; 15034 maxPt = pt; 15035 } 15036 } 15037 15038 return { maxPoint: maxPt, newPoints: newPoints }; 15039 }, 15040 15041 15042 /* 15043 * Given a baseline, compute the convex hull of latLngs as an array 15044 * of latLngs. 15045 * 15046 * @param {Array} latLngs 15047 * @returns {Array} 15048 */ 15049 buildConvexHull: function (baseLine, latLngs) { 15050 var convexHullBaseLines = [], 15051 t = this.findMostDistantPointFromBaseLine(baseLine, latLngs); 15052 15053 if (t.maxPoint) { // if there is still a point "outside" the base line 15054 convexHullBaseLines = 15055 convexHullBaseLines.concat( 15056 this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints) 15057 ); 15058 convexHullBaseLines = 15059 convexHullBaseLines.concat( 15060 this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints) 15061 ); 15062 return convexHullBaseLines; 15063 } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull 15064 return [baseLine[0]]; 15065 } 15066 }, 15067 15068 /* 15069 * Given an array of latlngs, compute a convex hull as an array 15070 * of latlngs 15071 * 15072 * @param {Array} latLngs 15073 * @returns {Array} 15074 */ 15075 getConvexHull: function (latLngs) { 15076 // find first baseline 15077 var maxLat = false, minLat = false, 15078 maxLng = false, minLng = false, 15079 maxLatPt = null, minLatPt = null, 15080 maxLngPt = null, minLngPt = null, 15081 maxPt = null, minPt = null, 15082 i; 15083 15084 for (i = latLngs.length - 1; i >= 0; i--) { 15085 var pt = latLngs[i]; 15086 if (maxLat === false || pt.lat > maxLat) { 15087 maxLatPt = pt; 15088 maxLat = pt.lat; 15089 } 15090 if (minLat === false || pt.lat < minLat) { 15091 minLatPt = pt; 15092 minLat = pt.lat; 15093 } 15094 if (maxLng === false || pt.lng > maxLng) { 15095 maxLngPt = pt; 15096 maxLng = pt.lng; 15097 } 15098 if (minLng === false || pt.lng < minLng) { 15099 minLngPt = pt; 15100 minLng = pt.lng; 15101 } 15102 } 15103 15104 if (minLat !== maxLat) { 15105 minPt = minLatPt; 15106 maxPt = maxLatPt; 15107 } else { 15108 minPt = minLngPt; 15109 maxPt = maxLngPt; 15110 } 15111 15112 var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs), 15113 this.buildConvexHull([maxPt, minPt], latLngs)); 15114 return ch; 15115 } 15116 }; 15117}()); 15118 15119L.MarkerCluster.include({ 15120 getConvexHull: function () { 15121 var childMarkers = this.getAllChildMarkers(), 15122 points = [], 15123 p, i; 15124 15125 for (i = childMarkers.length - 1; i >= 0; i--) { 15126 p = childMarkers[i].getLatLng(); 15127 points.push(p); 15128 } 15129 15130 return L.QuickHull.getConvexHull(points); 15131 } 15132}); 15133 15134 15135//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet 15136//Huge thanks to jawj for implementing it first to make my job easy :-) 15137 15138L.MarkerCluster.include({ 15139 15140 _2PI: Math.PI * 2, 15141 _circleFootSeparation: 25, //related to circumference of circle 15142 _circleStartAngle: Math.PI / 6, 15143 15144 _spiralFootSeparation: 28, //related to size of spiral (experiment!) 15145 _spiralLengthStart: 11, 15146 _spiralLengthFactor: 5, 15147 15148 _circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards. 15149 // 0 -> always spiral; Infinity -> always circle 15150 15151 spiderfy: function () { 15152 if (this._group._spiderfied === this || this._group._inZoomAnimation) { 15153 return; 15154 } 15155 15156 var childMarkers = this.getAllChildMarkers(), 15157 group = this._group, 15158 map = group._map, 15159 center = map.latLngToLayerPoint(this._latlng), 15160 positions; 15161 15162 this._group._unspiderfy(); 15163 this._group._spiderfied = this; 15164 15165 //TODO Maybe: childMarkers order by distance to center 15166 15167 if (childMarkers.length >= this._circleSpiralSwitchover) { 15168 positions = this._generatePointsSpiral(childMarkers.length, center); 15169 } else { 15170 center.y += 10; // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons. 15171 positions = this._generatePointsCircle(childMarkers.length, center); 15172 } 15173 15174 this._animationSpiderfy(childMarkers, positions); 15175 }, 15176 15177 unspiderfy: function (zoomDetails) { 15178 /// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param> 15179 if (this._group._inZoomAnimation) { 15180 return; 15181 } 15182 this._animationUnspiderfy(zoomDetails); 15183 15184 this._group._spiderfied = null; 15185 }, 15186 15187 _generatePointsCircle: function (count, centerPt) { 15188 var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
vendor: 11,941 bytes, lines 15189-15607
15189 legLength = circumference / this._2PI, //radius from circumference 15190 angleStep = this._2PI / count, 15191 res = [], 15192 i, angle; 15193 15194 res.length = count; 15195 15196 for (i = count - 1; i >= 0; i--) { 15197 angle = this._circleStartAngle + i * angleStep; 15198 res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round(); 15199 } 15200 15201 return res; 15202 }, 15203 15204 _generatePointsSpiral: function (count, centerPt) { 15205 var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier, 15206 legLength = spiderfyDistanceMultiplier * this._spiralLengthStart, 15207 separation = spiderfyDistanceMultiplier * this._spiralFootSeparation, 15208 lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI, 15209 angle = 0, 15210 res = [], 15211 i; 15212 15213 res.length = count; 15214 15215 // Higher index, closer position to cluster center. 15216 for (i = count - 1; i >= 0; i--) { 15217 angle += separation / legLength + i * 0.0005; 15218 res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round(); 15219 legLength += lengthFactor / angle; 15220 } 15221 return res; 15222 }, 15223 15224 _noanimationUnspiderfy: function () { 15225 var group = this._group, 15226 map = group._map, 15227 fg = group._featureGroup, 15228 childMarkers = this.getAllChildMarkers(), 15229 m, i; 15230 15231 group._ignoreMove = true; 15232 15233 this.setOpacity(1); 15234 for (i = childMarkers.length - 1; i >= 0; i--) { 15235 m = childMarkers[i]; 15236 15237 fg.removeLayer(m); 15238 15239 if (m._preSpiderfyLatlng) { 15240 m.setLatLng(m._preSpiderfyLatlng); 15241 delete m._preSpiderfyLatlng; 15242 } 15243 if (m.setZIndexOffset) { 15244 m.setZIndexOffset(0); 15245 } 15246 15247 if (m._spiderLeg) { 15248 map.removeLayer(m._spiderLeg); 15249 delete m._spiderLeg; 15250 } 15251 } 15252 15253 group.fire('unspiderfied', { 15254 cluster: this, 15255 markers: childMarkers 15256 }); 15257 group._ignoreMove = false; 15258 group._spiderfied = null; 15259 } 15260}); 15261 15262//Non Animated versions of everything 15263L.MarkerClusterNonAnimated = L.MarkerCluster.extend({ 15264 _animationSpiderfy: function (childMarkers, positions) { 15265 var group = this._group, 15266 map = group._map, 15267 fg = group._featureGroup, 15268 legOptions = this._group.options.spiderLegPolylineOptions, 15269 i, m, leg, newPos; 15270 15271 group._ignoreMove = true; 15272 15273 // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition. 15274 // The reverse order trick no longer improves performance on modern browsers. 15275 for (i = 0; i < childMarkers.length; i++) { 15276 newPos = map.layerPointToLatLng(positions[i]); 15277 m = childMarkers[i]; 15278 15279 // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it. 15280 leg = new L.Polyline([this._latlng, newPos], legOptions); 15281 map.addLayer(leg); 15282 m._spiderLeg = leg; 15283 15284 // Now add the marker. 15285 m._preSpiderfyLatlng = m._latlng; 15286 m.setLatLng(newPos); 15287 if (m.setZIndexOffset) { 15288 m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING 15289 } 15290 15291 fg.addLayer(m); 15292 } 15293 this.setOpacity(0.3); 15294 15295 group._ignoreMove = false; 15296 group.fire('spiderfied', { 15297 cluster: this, 15298 markers: childMarkers 15299 }); 15300 }, 15301 15302 _animationUnspiderfy: function () { 15303 this._noanimationUnspiderfy(); 15304 } 15305}); 15306 15307//Animated versions here 15308L.MarkerCluster.include({ 15309 15310 _animationSpiderfy: function (childMarkers, positions) { 15311 var me = this, 15312 group = this._group, 15313 map = group._map, 15314 fg = group._featureGroup, 15315 thisLayerLatLng = this._latlng, 15316 thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng), 15317 svg = L.Path.SVG, 15318 legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation. 15319 finalLegOpacity = legOptions.opacity, 15320 i, m, leg, legPath, legLength, newPos; 15321 15322 if (finalLegOpacity === undefined) { 15323 finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity; 15324 } 15325 15326 if (svg) { 15327 // If the initial opacity of the spider leg is not 0 then it appears before the animation starts. 15328 legOptions.opacity = 0; 15329 15330 // Add the class for CSS transitions. 15331 legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg'; 15332 } else { 15333 // Make sure we have a defined opacity. 15334 legOptions.opacity = finalLegOpacity; 15335 } 15336 15337 group._ignoreMove = true; 15338 15339 // Add markers and spider legs to map, hidden at our center point. 15340 // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition. 15341 // The reverse order trick no longer improves performance on modern browsers. 15342 for (i = 0; i < childMarkers.length; i++) { 15343 m = childMarkers[i]; 15344 15345 newPos = map.layerPointToLatLng(positions[i]); 15346 15347 // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it. 15348 leg = new L.Polyline([thisLayerLatLng, newPos], legOptions); 15349 map.addLayer(leg); 15350 m._spiderLeg = leg; 15351 15352 // Explanations: https://jakearchibald.com/2013/animated-line-drawing-svg/ 15353 // In our case the transition property is declared in the CSS file. 15354 if (svg) { 15355 legPath = leg._path; 15356 legLength = legPath.getTotalLength() + 0.1; // Need a small extra length to avoid remaining dot in Firefox. 15357 legPath.style.strokeDasharray = legLength; // Just 1 length is enough, it will be duplicated. 15358 legPath.style.strokeDashoffset = legLength; 15359 } 15360 15361 // If it is a marker, add it now and we'll animate it out 15362 if (m.setZIndexOffset) { 15363 m.setZIndexOffset(1000000); // Make normal markers appear on top of EVERYTHING 15364 } 15365 if (m.clusterHide) { 15366 m.clusterHide(); 15367 } 15368 15369 // Vectors just get immediately added 15370 fg.addLayer(m); 15371 15372 if (m._setPos) { 15373 m._setPos(thisLayerPos); 15374 } 15375 } 15376 15377 group._forceLayout(); 15378 group._animationStart(); 15379 15380 // Reveal markers and spider legs. 15381 for (i = childMarkers.length - 1; i >= 0; i--) { 15382 newPos = map.layerPointToLatLng(positions[i]); 15383 m = childMarkers[i]; 15384 15385 //Move marker to new position 15386 m._preSpiderfyLatlng = m._latlng; 15387 m.setLatLng(newPos); 15388 15389 if (m.clusterShow) { 15390 m.clusterShow(); 15391 } 15392 15393 // Animate leg (animation is actually delegated to CSS transition). 15394 if (svg) { 15395 leg = m._spiderLeg; 15396 legPath = leg._path; 15397 legPath.style.strokeDashoffset = 0; 15398 //legPath.style.strokeOpacity = finalLegOpacity; 15399 leg.setStyle({opacity: finalLegOpacity}); 15400 } 15401 } 15402 this.setOpacity(0.3); 15403 15404 group._ignoreMove = false; 15405 15406 setTimeout(function () { 15407 group._animationEnd(); 15408 group.fire('spiderfied', { 15409 cluster: me, 15410 markers: childMarkers 15411 }); 15412 }, 200); 15413 }, 15414 15415 _animationUnspiderfy: function (zoomDetails) { 15416 var me = this, 15417 group = this._group, 15418 map = group._map, 15419 fg = group._featureGroup, 15420 thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng), 15421 childMarkers = this.getAllChildMarkers(), 15422 svg = L.Path.SVG, 15423 m, i, leg, legPath, legLength, nonAnimatable; 15424 15425 group._ignoreMove = true; 15426 group._animationStart(); 15427 15428 //Make us visible and bring the child markers back in 15429 this.setOpacity(1); 15430 for (i = childMarkers.length - 1; i >= 0; i--) { 15431 m = childMarkers[i]; 15432 15433 //Marker was added to us after we were spiderfied 15434 if (!m._preSpiderfyLatlng) { 15435 continue; 15436 } 15437 15438 //Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll 15439 m.closePopup(); 15440 15441 //Fix up the location to the real one 15442 m.setLatLng(m._preSpiderfyLatlng); 15443 delete m._preSpiderfyLatlng; 15444 15445 //Hack override the location to be our center 15446 nonAnimatable = true; 15447 if (m._setPos) { 15448 m._setPos(thisLayerPos); 15449 nonAnimatable = false; 15450 } 15451 if (m.clusterHide) { 15452 m.clusterHide(); 15453 nonAnimatable = false; 15454 } 15455 if (nonAnimatable) { 15456 fg.removeLayer(m); 15457 } 15458 15459 // Animate the spider leg back in (animation is actually delegated to CSS transition). 15460 if (svg) { 15461 leg = m._spiderLeg; 15462 legPath = leg._path; 15463 legLength = legPath.getTotalLength() + 0.1; 15464 legPath.style.strokeDashoffset = legLength; 15465 leg.setStyle({opacity: 0}); 15466 } 15467 } 15468 15469 group._ignoreMove = false; 15470 15471 setTimeout(function () { 15472 //If we have only <= one child left then that marker will be shown on the map so don't remove it! 15473 var stillThereChildCount = 0; 15474 for (i = childMarkers.length - 1; i >= 0; i--) { 15475 m = childMarkers[i]; 15476 if (m._spiderLeg) { 15477 stillThereChildCount++; 15478 } 15479 } 15480 15481 15482 for (i = childMarkers.length - 1; i >= 0; i--) { 15483 m = childMarkers[i]; 15484 15485 if (!m._spiderLeg) { //Has already been unspiderfied 15486 continue; 15487 } 15488 15489 if (m.clusterShow) { 15490 m.clusterShow(); 15491 } 15492 if (m.setZIndexOffset) { 15493 m.setZIndexOffset(0); 15494 } 15495 15496 if (stillThereChildCount > 1) { 15497 fg.removeLayer(m); 15498 } 15499 15500 map.removeLayer(m._spiderLeg); 15501 delete m._spiderLeg; 15502 } 15503 group._animationEnd(); 15504 group.fire('unspiderfied', { 15505 cluster: me, 15506 markers: childMarkers 15507 }); 15508 }, 200); 15509 } 15510}); 15511 15512 15513L.MarkerClusterGroup.include({ 15514 //The MarkerCluster currently spiderfied (if any) 15515 _spiderfied: null, 15516 15517 unspiderfy: function () { 15518 this._unspiderfy.apply(this, arguments); 15519 }, 15520 15521 _spiderfierOnAdd: function () { 15522 this._map.on('click', this._unspiderfyWrapper, this); 15523 15524 if (this._map.options.zoomAnimation) { 15525 this._map.on('zoomstart', this._unspiderfyZoomStart, this); 15526 } 15527 //Browsers without zoomAnimation or a big zoom don't fire zoomstart 15528 this._map.on('zoomend', this._noanimationUnspiderfy, this); 15529 15530 if (!L.Browser.touch) { 15531 this._map.getRenderer(this); 15532 //Needs to happen in the pageload, not after, or animations don't work in webkit 15533 // http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements 15534 //Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable 15535 } 15536 }, 15537 15538 _spiderfierOnRemove: function () { 15539 this._map.off('click', this._unspiderfyWrapper, this); 15540 this._map.off('zoomstart', this._unspiderfyZoomStart, this); 15541 this._map.off('zoomanim', this._unspiderfyZoomAnim, this); 15542 this._map.off('zoomend', this._noanimationUnspiderfy, this); 15543 15544 //Ensure that markers are back where they should be 15545 // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane 15546 this._noanimationUnspiderfy(); 15547 }, 15548 15549 //On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated) 15550 //This means we can define the animation they do rather than Markers doing an animation to their actual location 15551 _unspiderfyZoomStart: function () { 15552 if (!this._map) { //May have been removed from the map by a zoomEnd handler 15553 return; 15554 } 15555 15556 this._map.on('zoomanim', this._unspiderfyZoomAnim, this); 15557 }, 15558 15559 _unspiderfyZoomAnim: function (zoomDetails) { 15560 //Wait until the first zoomanim after the user has finished touch-zooming before running the animation 15561 if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) { 15562 return; 15563 } 15564 15565 this._map.off('zoomanim', this._unspiderfyZoomAnim, this); 15566 this._unspiderfy(zoomDetails); 15567 }, 15568 15569 _unspiderfyWrapper: function () { 15570 /// <summary>_unspiderfy but passes no arguments</summary> 15571 this._unspiderfy(); 15572 }, 15573 15574 _unspiderfy: function (zoomDetails) { 15575 if (this._spiderfied) { 15576 this._spiderfied.unspiderfy(zoomDetails); 15577 } 15578 }, 15579 15580 _noanimationUnspiderfy: function () { 15581 if (this._spiderfied) { 15582 this._spiderfied._noanimationUnspiderfy(); 15583 } 15584 }, 15585 15586 //If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc 15587 _unspiderfyLayer: function (layer) { 15588 if (layer._spiderLeg) { 15589 this._featureGroup.removeLayer(layer); 15590 15591 if (layer.clusterShow) { 15592 layer.clusterShow(); 15593 } 15594 //Position will be fixed up immediately in _animationUnspiderfy 15595 if (layer.setZIndexOffset) { 15596 layer.setZIndexOffset(0); 15597 } 15598 15599 this._map.removeLayer(layer._spiderLeg); 15600 delete layer._spiderLeg; 15601 } 15602 } 15603}); 15604 15605 15606/** 15607 * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing
15608 * markers' icon options and refreshing their icon and their parent clusters 15609 * accordingly (case where their iconCreateFunction uses data of childMarkers 15610 * to make up the cluster icon). 15611 */ 15612 15613 15614L.MarkerClusterGroup.include({ 15615 /** 15616 * Updates the icon of all clusters which are parents of the given marker(s). 15617 * In singleMarkerMode, also updates the given marker(s) icon. 15618 * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)| 15619 * L.MarkerCluster|L.Marker (optional) list of markers (or single marker) whose parent 15620 * clusters need to be updated. If not provided, retrieves all child markers of this. 15621 * @returns {L.MarkerClusterGroup} 15622 */ 15623 refreshClusters: function (layers) { 15624 if (!layers) { 15625 layers = this._topClusterLevel.getAllChildMarkers(); 15626 } else if (layers instanceof L.MarkerClusterGroup) { 15627 layers = layers._topClusterLevel.getAllChildMarkers(); 15628 } else if (layers instanceof L.LayerGroup) { 15629 layers = layers._layers; 15630 } else if (layers instanceof L.MarkerCluster) { 15631 layers = layers.getAllChildMarkers(); 15632 } else if (layers instanceof L.Marker) { 15633 layers = [layers]; 15634 } // else: must be an Array(L.Marker)|Map(L.Marker) 15635 this._flagParentsIconsNeedUpdate(layers); 15636 this._refreshClustersIcons(); 15637 15638 // In case of singleMarkerMode, also re-draw the markers. 15639 if (this.options.singleMarkerMode) { 15640 this._refreshSingleMarkerModeMarkers(layers); 15641 } 15642 15643 return this; 15644 }, 15645 15646 /** 15647 * Simply flags all parent clusters of the given markers as having a "dirty" icon. 15648 * @param layers Array(L.Marker)|Map(L.Marker) list of markers. 15649 * @private 15650 */ 15651 _flagParentsIconsNeedUpdate: function (layers) { 15652 var id, parent; 15653 15654 // Assumes layers is an Array or an Object whose prototype is non-enumerable. 15655 for (id in layers) { 15656 // Flag parent clusters' icon as "dirty", all the way up. 15657 // Dumb process that flags multiple times upper parents, but still 15658 // much more efficient than trying to be smart and make short lists, 15659 // at least in the case of a hierarchy following a power law: 15660 // http://jsperf.com/flag-nodes-in-power-hierarchy/2 15661 parent = layers[id].__parent; 15662 while (parent) { 15663 parent._iconNeedsUpdate = true; 15664 parent = parent.__parent; 15665 } 15666 } 15667 }, 15668 15669 /** 15670 * Re-draws the icon of the supplied markers. 15671 * To be used in singleMarkerMode only. 15672 * @param layers Array(L.Marker)|Map(L.Marker) list of markers. 15673 * @private 15674 */ 15675 _refreshSingleMarkerModeMarkers: function (layers) { 15676 var id, layer; 15677 15678 for (id in layers) { 15679 layer = layers[id]; 15680 15681 // Make sure we do not override markers that do not belong to THIS group. 15682 if (this.hasLayer(layer)) { 15683 // Need to re-create the icon first, then re-draw the marker. 15684 layer.setIcon(this._overrideMarkerIcon(layer)); 15685 } 15686 } 15687 } 15688}); 15689 15690L.Marker.include({ 15691 /** 15692 * Updates the given options in the marker's icon and refreshes the marker. 15693 * @param options map object of icon options. 15694 * @param directlyRefreshClusters boolean (optional) true to trigger 15695 * MCG.refreshClustersOf() right away with this single marker. 15696 * @returns {L.Marker} 15697 */ 15698 refreshIconOptions: function (options, directlyRefreshClusters) { 15699 var icon = this.options.icon; 15700 15701 L.setOptions(icon, options); 15702 15703 this.setIcon(icon); 15704 15705 // Shortcut to refresh the associated MCG clusters right away. 15706 // To be used when refreshing a single marker. 15707 // Otherwise, better use MCG.refreshClusters() once at the end with 15708 // the list of modified markers. 15709 if (directlyRefreshClusters && this.__parent) { 15710 this.__parent._group.refreshClusters(this); 15711 } 15712 15713 return this; 15714 } 15715}); 15716 15717 15718}(window, document)); 15719// Based on https://github.com/shramov/leaflet-plugins 15720// GridLayer like https://avinmathew.com/leaflet-and-google-maps/ , but using MutationObserver instead of jQuery 15721 15722 15723// ðŸÂâclass GridLayer.GoogleMutant 15724// ðŸÂâextends GridLayer 15725L.GridLayer.GoogleMutant = L.GridLayer.extend({ 15726 options: { 15727 minZoom: 0, 15728 maxZoom: 23, 15729 tileSize: 256, 15730 subdomains: 'abc', 15731 errorTileUrl: '', 15732 attribution: '', // The mutant container will add its own attribution anyways. 15733 opacity: 1, 15734 continuousWorld: false, 15735 noWrap: false, 15736 // ðŸÂâoption type: String = 'roadmap' 15737 // Google's map type. Valid values are 'roadmap', 'satellite' or 'terrain'. 'hybrid' is not really supported. 15738 type: 'roadmap', 15739 maxNativeZoom: 21 15740 }, 15741 15742 initialize: function (options) { 15743 L.GridLayer.prototype.initialize.call(this, options); 15744 15745 this._ready = !!window.google && !!window.google.maps && !!window.google.maps.Map; 15746 15747 this._GAPIPromise = this._ready ? Promise.resolve(window.google) : new Promise(function (resolve, reject) { 15748 var checkCounter = 0; 15749 var intervalId = null; 15750 intervalId = setInterval(function () { 15751 if (checkCounter >= 10) { 15752 clearInterval(intervalId); 15753 return reject(new Error('window.google not found after 10 attempts')); 15754 } 15755 if (!!window.google && !!window.google.maps && !!window.google.maps.Map) { 15756 clearInterval(intervalId); 15757 return resolve(window.google); 15758 } 15759 checkCounter++; 15760 }, 500); 15761 }); 15762 15763 // Couple data structures indexed by tile key 15764 this._tileCallbacks = {}; // Callbacks for promises for tiles that are expected 15765 this._freshTiles = {}; // Tiles from the mutant which haven't been requested yet 15766 15767 this._imagesPerTile = (this.options.type === 'hybrid') ? 2 : 1; 15768 }, 15769 15770 onAdd: function (map) { 15771 L.GridLayer.prototype.onAdd.call(this, map); 15772 this._initMutantContainer(); 15773 15774 this._GAPIPromise.then(function () { 15775 this._ready = true; 15776 this._map = map; 15777 15778 this._initMutant(); 15779 15780 map.on('viewreset', this._reset, this); 15781 map.on('move', this._update, this); 15782 map.on('zoomend', this._handleZoomAnim, this); 15783 map.on('resize', this._resize, this); 15784 15785 //handle layer being added to a map for which there are no Google tiles at the given zoom 15786 google.maps.event.addListenerOnce(this._mutant, 'idle', function () { 15787 this._checkZoomLevels(); 15788 this._mutantIsReady = true; 15789 }.bind(this)); 15790 15791 //20px instead of 1em to avoid a slight overlap with google's attribution 15792 map._controlCorners.bottomright.style.marginBottom = '20px'; 15793 map._controlCorners.bottomleft.style.marginBottom = '20px'; 15794 15795 this._reset(); 15796 this._update(); 15797 15798 if (this._subLayers) { 15799 //restore previously added google layers 15800 for (var layerName in this._subLayers) { 15801 this._subLayers[layerName].setMap(this._mutant); 15802 } 15803 } 15804 }.bind(this)); 15805 }, 15806 15807 onRemove: function (map) { 15808 L.GridLayer.prototype.onRemove.call(this, map); 15809 map._container.removeChild(this._mutantContainer); 15810 this._mutantContainer = undefined; 15811 15812 google.maps.event.clearListeners(map, 'idle'); 15813 google.maps.event.clearListeners(this._mutant, 'idle'); 15814 map.off('viewreset', this._reset, this); 15815 map.off('move', this._update, this); 15816 map.off('zoomend', this._handleZoomAnim, this); 15817 map.off('resize', this._resize, this); 15818 15819 if (map._controlCorners) { 15820 map._controlCorners.bottomright.style.marginBottom = '0em'; 15821 map._controlCorners.bottomleft.style.marginBottom = '0em'; 15822 } 15823 }, 15824 15825 getAttribution: function () { 15826 return this.options.attribution; 15827 }, 15828 15829 setOpacity: function (opacity) { 15830 this.options.opacity = opacity; 15831 if (opacity < 1) { 15832 L.DomUtil.setOpacity(this._mutantContainer, opacity); 15833 } 15834 }, 15835 15836 setElementSize: function (e, size) { 15837 e.style.width = size.x + 'px'; 15838 e.style.height = size.y + 'px'; 15839 }, 15840 15841 15842 addGoogleLayer: function (googleLayerName, options) { 15843 if (!this._subLayers) this._subLayers = {}; 15844 return this._GAPIPromise.then(function () { 15845 var Constructor = google.maps[googleLayerName]; 15846 var googleLayer = new Constructor(options); 15847 googleLayer.setMap(this._mutant); 15848 this._subLayers[googleLayerName] = googleLayer; 15849 return googleLayer; 15850 }.bind(this)); 15851 }, 15852 15853 removeGoogleLayer: function (googleLayerName) { 15854 var googleLayer = this._subLayers && this._subLayers[googleLayerName]; 15855 if (!googleLayer) return; 15856 15857 googleLayer.setMap(null); 15858 delete this._subLayers[googleLayerName]; 15859 }, 15860 15861 15862 _initMutantContainer: function () { 15863 if (!this._mutantContainer) { 15864 this._mutantContainer = L.DomUtil.create('div', 'leaflet-google-mutant leaflet-top leaflet-left'); 15865 this._mutantContainer.id = '_MutantContainer_' + L.Util.stamp(this._mutantContainer); 15866 this._mutantContainer.style.zIndex = '800'; //leaflet map pane at 400, controls at 1000 15867 this._mutantContainer.style.pointerEvents = 'none'; 15868 15869 this._map.getContainer().appendChild(this._mutantContainer); 15870 } 15871 15872 this.setOpacity(this.options.opacity); 15873 this.setElementSize(this._mutantContainer, this._map.getSize()); 15874 15875 this._attachObserver(this._mutantContainer); 15876 }, 15877 15878 _initMutant: function () { 15879 if (!this._ready || !this._mutantContainer) return; 15880 this._mutantCenter = new google.maps.LatLng(0, 0); 15881 15882 var map = new google.maps.Map(this._mutantContainer, { 15883 center: this._mutantCenter, 15884 zoom: 0, 15885 tilt: 0, 15886 mapTypeId: this.options.type, 15887 disableDefaultUI: true, 15888 keyboardShortcuts: false, 15889 draggable: false, 15890 disableDoubleClickZoom: true, 15891 scrollwheel: false, 15892 streetViewControl: false, 15893 styles: this.options.styles || {}, 15894 backgroundColor: 'transparent' 15895 }); 15896 15897 this._mutant = map; 15898 15899 google.maps.event.addListenerOnce(map, 'idle', function () { 15900 var nodes = this._mutantContainer.querySelectorAll('a'); 15901 for (var i = 0; i < nodes.length; i++) { 15902 nodes[i].style.pointerEvents = 'auto'; 15903 } 15904 }.bind(this)); 15905 15906 // ðŸÂâevent spawned 15907 // Fired when the mutant has been created. 15908 this.fire('spawned', {mapObject: map}); 15909 }, 15910 15911 _attachObserver: function _attachObserver (node) { 15912// console.log('Gonna observe', node); 15913 15914 var observer = new MutationObserver(this._onMutations.bind(this)); 15915 15916 // pass in the target node, as well as the observer options 15917 observer.observe(node, { childList: true, subtree: true }); 15918 }, 15919 15920 _onMutations: function _onMutations (mutations) { 15921 for (var i = 0; i < mutations.length; ++i) { 15922 var mutation = mutations[i]; 15923 for (var j = 0; j < mutation.addedNodes.length; ++j) { 15924 var node = mutation.addedNodes[j]; 15925 15926 if (node instanceof HTMLImageElement) { 15927 this._onMutatedImage(node); 15928 } else if (node instanceof HTMLElement) { 15929 Array.prototype.forEach.call(node.querySelectorAll('img'), this._onMutatedImage.bind(this)); 15930 } 15931 } 15932 } 15933 }, 15934 15935 // Only images which 'src' attrib match this will be considered for moving around. 15936 // Looks like some kind of string-based protobuf, maybe?? 15937 // Only the roads (and terrain, and vector-based stuff) match this pattern 15938 _roadRegexp: /!1i(\d+)!2i(\d+)!3i(\d+)!/, 15939 15940 // On the other hand, raster imagery matches this other pattern 15941 _satRegexp: /x=(\d+)&y=(\d+)&z=(\d+)/, 15942 15943 // On small viewports, when zooming in/out, a static image is requested 15944 // This will not be moved around, just removed from the DOM. 15945 _staticRegExp: /StaticMapService\.GetMapImage/, 15946 15947 _onMutatedImage: function _onMutatedImage (imgNode) { 15948// if (imgNode.src) { 15949// console.log('caught mutated image: ', imgNode.src); 15950// } 15951 15952 var coords; 15953 var match = imgNode.src.match(this._roadRegexp); 15954 var sublayer = 0; 15955 15956 if (match) { 15957 coords = { 15958 z: match[1], 15959 x: match[2], 15960 y: match[3] 15961 }; 15962 if (this._imagesPerTile > 1) { 15963 imgNode.style.zIndex = 1; 15964 sublayer = 1; 15965 } 15966 } else { 15967 match = imgNode.src.match(this._satRegexp); 15968 if (match) { 15969 coords = { 15970 x: match[1], 15971 y: match[2], 15972 z: match[3] 15973 }; 15974 } 15975// imgNode.style.zIndex = 0; 15976 sublayer = 0; 15977 } 15978 15979 if (coords) { 15980 var tileKey = this._tileCoordsToKey(coords); 15981 imgNode.style.position = 'absolute'; 15982 imgNode.style.visibility = 'hidden'; 15983 15984 var key = tileKey + '/' + sublayer; 15985 // console.log('mutation for tile', key) 15986 //store img so it can also be used in subsequent tile requests 15987 this._freshTiles[key] = imgNode; 15988 15989 if (key in this._tileCallbacks && this._tileCallbacks[key]) { 15990// console.log('Fullfilling callback ', key); 15991 //fullfill most recent tileCallback because there maybe callbacks that will never get a 15992 //corresponding mutation (because map moved to quickly...) 15993 this._tileCallbacks[key].pop()(imgNode); 15994 if (!this._tileCallbacks[key].length) { delete this._tileCallbacks[key]; } 15995 } else { 15996 if (this._tiles[tileKey]) { 15997 //we already have a tile in this position (mutation is probably a google layer being added) 15998 //replace it 15999 var c = this._tiles[tileKey].el; 16000 var oldImg = (sublayer === 0) ? c.firstChild : c.firstChild.nextSibling; 16001 var cloneImgNode = this._clone(imgNode); 16002 c.replaceChild(cloneImgNode, oldImg); 16003 } 16004 } 16005 } else if (imgNode.src.match(this._staticRegExp)) { 16006 imgNode.style.visibility = 'hidden'; 16007 } 16008 }, 16009 16010 16011 createTile: function (coords, done) { 16012 var key = this._tileCoordsToKey(coords); 16013 16014 var tileContainer = L.DomUtil.create('div'); 16015 tileContainer.dataset.pending = this._imagesPerTile; 16016 done = done.bind(this, null, tileContainer); 16017 16018 for (var i = 0; i < this._imagesPerTile; i++) { 16019 var key2 = key + '/' + i; 16020 if (key2 in this._freshTiles) { 16021 var imgNode = this._freshTiles[key2]; 16022 tileContainer.appendChild(this._clone(imgNode)); 16023 tileContainer.dataset.pending--; 16024// console.log('Got ', key2, ' from _freshTiles'); 16025 } else { 16026 this._tileCallbacks[key2] = this._tileCallbacks[key2] || []; 16027 this._tileCallbacks[key2].push( (function (c/*, k2*/) { 16028 return function (imgNode) { 16029 c.appendChild(this._clone(imgNode)); 16030 c.dataset.pending--; 16031 if (!parseInt(c.dataset.pending)) { done(); } 16032// console.log('Sent ', k2, ' to _tileCallbacks, still ', c.dataset.pending, ' images to go'); 16033 }.bind(this); 16034 }.bind(this))(tileContainer/*, key2*/) ); 16035 } 16036 } 16037 16038 if (!parseInt(tileContainer.dataset.pending)) {
16039 L.Util.requestAnimFrame(done); 16040 } 16041 return tileContainer; 16042 }, 16043 16044 _clone: function (imgNode) { 16045 var clonedImgNode = imgNode.cloneNode(true); 16046 clonedImgNode.style.visibility = 'visible'; 16047 return clonedImgNode; 16048 }, 16049 16050 _checkZoomLevels: function () { 16051 //setting the zoom level on the Google map may result in a different zoom level than the one requested 16052 //(it won't go beyond the level for which they have data). 16053 var zoomLevel = this._map.getZoom(); 16054 var gMapZoomLevel = this._mutant.getZoom(); 16055 if (!zoomLevel || !gMapZoomLevel) return; 16056 16057 16058 if ((gMapZoomLevel !== zoomLevel) || //zoom levels are out of sync, Google doesn't have data 16059 (gMapZoomLevel > this.options.maxNativeZoom)) { //at current location, Google does have data (contrary to maxNativeZoom) 16060 //Update maxNativeZoom 16061 this._setMaxNativeZoom(gMapZoomLevel); 16062 } 16063 }, 16064 16065 _setMaxNativeZoom: function (zoomLevel) { 16066 if (zoomLevel != this.options.maxNativeZoom) { 16067 this.options.maxNativeZoom = zoomLevel; 16068 this._resetView(); 16069 } 16070 }, 16071 16072 _reset: function () { 16073 this._initContainer(); 16074 }, 16075 16076 _update: function () { 16077 // zoom level check needs to happen before super's implementation (tile addition/creation) 16078 // otherwise tiles may be missed if maxNativeZoom is not yet correctly determined 16079 if (this._mutant) { 16080 var center = this._map.getCenter(); 16081 var _center = new google.maps.LatLng(center.lat, center.lng); 16082 16083 this._mutant.setCenter(_center); 16084 var zoom = this._map.getZoom(); 16085 var fractionalLevel = zoom !== Math.round(zoom); 16086 var mutantZoom = this._mutant.getZoom(); 16087 16088 //ignore fractional zoom levels 16089 if (!fractionalLevel && (zoom != mutantZoom)) { 16090 this._mutant.setZoom(zoom); 16091 16092 if (this._mutantIsReady) this._checkZoomLevels(); 16093 //else zoom level check will be done later by 'idle' handler 16094 } 16095 } 16096 16097 L.GridLayer.prototype._update.call(this); 16098 }, 16099 16100 _resize: function () { 16101 var size = this._map.getSize(); 16102 if (this._mutantContainer.style.width === size.x && 16103 this._mutantContainer.style.height === size.y) 16104 return; 16105 this.setElementSize(this._mutantContainer, size); 16106 if (!this._mutant) return; 16107 google.maps.event.trigger(this._mutant, 'resize'); 16108 }, 16109 16110 _handleZoomAnim: function () { 16111 if (!this._mutant) return; 16112 var center = this._map.getCenter(); 16113 var _center = new google.maps.LatLng(center.lat, center.lng); 16114 16115 this._mutant.setCenter(_center); 16116 this._mutant.setZoom(Math.round(this._map.getZoom())); 16117 }, 16118 16119 // Agressively prune _freshtiles when a tile with the same key is removed, 16120 // this prevents a problem where Leaflet keeps a loaded tile longer than 16121 // GMaps, so that GMaps makes two requests but Leaflet only consumes one, 16122 // polluting _freshTiles with stale data. 16123 _removeTile: function (key) { 16124 if (!this._mutant) return; 16125 16126 //give time for animations to finish before checking it tile should be pruned 16127 setTimeout(this._pruneTile.bind(this, key), 1000); 16128 16129 16130 return L.GridLayer.prototype._removeTile.call(this, key); 16131 }, 16132 16133 _pruneTile: function (key) { 16134 var gZoom = this._mutant.getZoom(); 16135 var tileZoom = key.split(':')[2]; 16136 var googleBounds = this._mutant.getBounds();
16137 var sw = googleBounds.getSouthWest(); 16138 var ne = googleBounds.getNorthEast(); 16139 var gMapBounds = L.latLngBounds([[sw.lat(), sw.lng()], [ne.lat(), ne.lng()]]); 16140 16141 for (var i=0; i<this._imagesPerTile; i++) { 16142 var key2 = key + '/' + i; 16143 if (key2 in this._freshTiles) { 16144 var tileBounds = this._map && this._keyToBounds(key); 16145 var stillVisible = this._map && tileBounds.overlaps(gMapBounds) && (tileZoom == gZoom); 16146 16147 if (!stillVisible) delete this._freshTiles[key2]; 16148// console.log('Prunning of ', key, (!stillVisible)) 16149 } 16150 } 16151 } 16152}); 16153 16154 16155// ðŸÂâfactory gridLayer.googleMutant(options) 16156// Returns a new `GridLayer.GoogleMutant` given its options 16157L.gridLayer.googleMutant = function (options) { 16158 return new L.GridLayer.GoogleMutant(options); 16159};
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.