vendor: 45,705 bytes, lines 1-1578
1/* @preserve 2 * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com 3 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade 4 */ 5 6var version = "1.9.4"; 7 8/* 9 * @namespace Util 10 * 11 * Various utility functions, used by Leaflet internally. 12 */ 13 14// @function extend(dest: Object, src?: Object): Object 15// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 16function extend(dest) { 17 var i, j, len, src; 18 19 for (j = 1, len = arguments.length; j < len; j++) { 20 src = arguments[j]; 21 for (i in src) { 22 dest[i] = src[i]; 23 } 24 } 25 return dest; 26} 27 28// @function create(proto: Object, properties?: Object): Object 29// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 30var create$2 = Object.create || (function () { 31 function F() {} 32 return function (proto) { 33 F.prototype = proto; 34 return new F(); 35 }; 36})(); 37 38// @function bind(fn: Function, â¦): Function 39// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). 40// Has a `L.bind()` shortcut. 41function bind(fn, obj) { 42 var slice = Array.prototype.slice; 43 44 if (fn.bind) { 45 return fn.bind.apply(fn, slice.call(arguments, 1)); 46 } 47 48 var args = slice.call(arguments, 2); 49 50 return function () { 51 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 52 }; 53} 54 55// @property lastId: Number 56// Last unique ID used by [`stamp()`](#util-stamp) 57var lastId = 0; 58 59// @function stamp(obj: Object): Number 60// Returns the unique ID of an object, assigning it one if it doesn't have it. 61function stamp(obj) { 62 if (!('_leaflet_id' in obj)) { 63 obj['_leaflet_id'] = ++lastId; 64 } 65 return obj._leaflet_id; 66} 67 68// @function throttle(fn: Function, time: Number, context: Object): Function 69// Returns a function which executes function `fn` with the given scope `context` 70// (so that the `this` keyword refers to `context` inside `fn`'s code). The function 71// `fn` will be called no more than one time per given amount of `time`. The arguments 72// received by the bound function will be any arguments passed when binding the 73// function, followed by any arguments passed when invoking the bound function. 74// Has an `L.throttle` shortcut. 75function throttle(fn, time, context) { 76 var lock, args, wrapperFn, later; 77 78 later = function () { 79 // reset lock and call if queued 80 lock = false; 81 if (args) { 82 wrapperFn.apply(context, args); 83 args = false; 84 } 85 }; 86 87 wrapperFn = function () { 88 if (lock) { 89 // called too soon, queue to call later 90 args = arguments; 91 92 } else { 93 // call and lock until later 94 fn.apply(context, arguments); 95 setTimeout(later, time); 96 lock = true; 97 } 98 }; 99 100 return wrapperFn; 101} 102 103// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number 104// Returns the number `num` modulo `range` in such a way so it lies within 105// `range[0]` and `range[1]`. The returned value will be always smaller than 106// `range[1]` unless `includeMax` is set to `true`. 107function wrapNum(x, range, includeMax) { 108 var max = range[1], 109 min = range[0], 110 d = max - min; 111 return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 112} 113 114// @function falseFn(): Function 115// Returns a function which always returns `false`. 116function falseFn() { return false; } 117 118// @function formatNum(num: Number, precision?: Number|false): Number 119// Returns the number `num` rounded with specified `precision`. 120// The default `precision` value is 6 decimal places. 121// `false` can be passed to skip any processing (can be useful to avoid round-off errors). 122function formatNum(num, precision) { 123 if (precision === false) { return num; } 124 var pow = Math.pow(10, precision === undefined ? 6 : precision); 125 return Math.round(num * pow) / pow; 126} 127 128// @function trim(str: String): String 129// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) 130function trim(str) { 131 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); 132} 133 134// @function splitWords(str: String): String[] 135// Trims and splits the string on whitespace and returns the array of parts. 136function splitWords(str) { 137 return trim(str).split(/\s+/); 138} 139 140// @function setOptions(obj: Object, options: Object): Object 141// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. 142function setOptions(obj, options) { 143 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) { 144 obj.options = obj.options ? create$2(obj.options) : {}; 145 } 146 for (var i in options) { 147 obj.options[i] = options[i]; 148 } 149 return obj.options; 150} 151 152// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String 153// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` 154// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will 155// be appended at the end. If `uppercase` is `true`, the parameter names will 156// be uppercased (e.g. `'?A=foo&B=bar'`) 157function getParamString(obj, existingUrl, uppercase) { 158 var params = []; 159 for (var i in obj) { 160 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); 161 } 162 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); 163} 164 165var templateRe = /\{ *([\w_ -]+) *\}/g; 166 167// @function template(str: String, data: Object): String 168// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` 169// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string 170// `('Hello foo, bar')`. You can also specify functions instead of strings for 171// data values â they will be evaluated passing `data` as an argument. 172function template(str, data) { 173 return str.replace(templateRe, function (str, key) { 174 var value = data[key]; 175 176 if (value === undefined) { 177 throw new Error('No value provided for variable ' + str); 178 179 } else if (typeof value === 'function') { 180 value = value(data); 181 } 182 return value; 183 }); 184} 185 186// @function isArray(obj): Boolean 187// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) 188var isArray = Array.isArray || function (obj) { 189 return (Object.prototype.toString.call(obj) === '[object Array]'); 190}; 191 192// @function indexOf(array: Array, el: Object): Number 193// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) 194function indexOf(array, el) { 195 for (var i = 0; i < array.length; i++) { 196 if (array[i] === el) { return i; } 197 } 198 return -1; 199} 200 201// @property emptyImageUrl: String 202// Data URI string containing a base64-encoded empty GIF image. 203// Used as a hack to free memory from unused images on WebKit-powered 204// mobile devices (by setting image `src` to this string). 205var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; 206 207// inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/ 208 209function getPrefixed(name) { 210 return window['webkit' + name] || window['moz' + name] || window['ms' + name]; 211} 212 213var lastTime = 0; 214 215// fallback for IE 7-8 216function timeoutDefer(fn) { 217 var time = +new Date(), 218 timeToCall = Math.max(0, 16 - (time - lastTime)); 219 220 lastTime = time + timeToCall; 221 return window.setTimeout(fn, timeToCall); 222} 223 224var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; 225var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || 226 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; 227 228// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number 229// Schedules `fn` to be executed when the browser repaints. `fn` is bound to 230// `context` if given. When `immediate` is set, `fn` is called immediately if 231// the browser doesn't have native support for 232// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), 233// otherwise it's delayed. Returns a request ID that can be used to cancel the request. 234function requestAnimFrame(fn, context, immediate) { 235 if (immediate && requestFn === timeoutDefer) { 236 fn.call(context); 237 } else { 238 return requestFn.call(window, bind(fn, context)); 239 } 240} 241 242// @function cancelAnimFrame(id: Number): undefined 243// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). 244function cancelAnimFrame(id) { 245 if (id) { 246 cancelFn.call(window, id); 247 } 248} 249 250var Util = { 251 __proto__: null, 252 extend: extend, 253 create: create$2, 254 bind: bind, 255 get lastId () { return lastId; }, 256 stamp: stamp, 257 throttle: throttle, 258 wrapNum: wrapNum, 259 falseFn: falseFn, 260 formatNum: formatNum, 261 trim: trim, 262 splitWords: splitWords, 263 setOptions: setOptions, 264 getParamString: getParamString, 265 template: template, 266 isArray: isArray, 267 indexOf: indexOf, 268 emptyImageUrl: emptyImageUrl, 269 requestFn: requestFn, 270 cancelFn: cancelFn, 271 requestAnimFrame: requestAnimFrame, 272 cancelAnimFrame: cancelAnimFrame 273}; 274 275// @class Class 276// @aka L.Class 277 278// @section 279// @uninheritable 280 281// Thanks to John Resig and Dean Edwards for inspiration! 282 283function Class() {} 284 285Class.extend = function (props) { 286 287 // @function extend(props: Object): Function 288 // [Extends the current class](#class-inheritance) given the properties to be included. 289 // Returns a Javascript function that is a class constructor (to be called with `new`). 290 var NewClass = function () { 291 292 setOptions(this); 293 294 // call the constructor 295 if (this.initialize) { 296 this.initialize.apply(this, arguments); 297 } 298 299 // call all constructor hooks 300 this.callInitHooks(); 301 }; 302 303 var parentProto = NewClass.__super__ = this.prototype; 304 305 var proto = create$2(parentProto); 306 proto.constructor = NewClass; 307 308 NewClass.prototype = proto; 309 310 // inherit parent's statics 311 for (var i in this) { 312 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') { 313 NewClass[i] = this[i]; 314 } 315 } 316 317 // mix static properties into the class 318 if (props.statics) { 319 extend(NewClass, props.statics); 320 } 321 322 // mix includes into the prototype 323 if (props.includes) { 324 checkDeprecatedMixinEvents(props.includes); 325 extend.apply(null, [proto].concat(props.includes)); 326 } 327 328 // mix given properties into the prototype 329 extend(proto, props); 330 delete proto.statics; 331 delete proto.includes; 332 333 // merge options 334 if (proto.options) { 335 proto.options = parentProto.options ? create$2(parentProto.options) : {}; 336 extend(proto.options, props.options); 337 } 338 339 proto._initHooks = []; 340 341 // add method for calling all hooks 342 proto.callInitHooks = function () { 343 344 if (this._initHooksCalled) { return; } 345 346 if (parentProto.callInitHooks) { 347 parentProto.callInitHooks.call(this); 348 } 349 350 this._initHooksCalled = true; 351 352 for (var i = 0, len = proto._initHooks.length; i < len; i++) { 353 proto._initHooks[i].call(this); 354 } 355 }; 356 357 return NewClass; 358}; 359 360 361// @function include(properties: Object): this 362// [Includes a mixin](#class-includes) into the current class. 363Class.include = function (props) { 364 var parentOptions = this.prototype.options; 365 extend(this.prototype, props); 366 if (props.options) { 367 this.prototype.options = parentOptions; 368 this.mergeOptions(props.options); 369 } 370 return this; 371}; 372 373// @function mergeOptions(options: Object): this 374// [Merges `options`](#class-options) into the defaults of the class. 375Class.mergeOptions = function (options) { 376 extend(this.prototype.options, options); 377 return this; 378}; 379 380// @function addInitHook(fn: Function): this 381// Adds a [constructor hook](#class-constructor-hooks) to the class. 382Class.addInitHook = function (fn) { // (Function) || (String, args...) 383 var args = Array.prototype.slice.call(arguments, 1); 384 385 var init = typeof fn === 'function' ? fn : function () { 386 this[fn].apply(this, args); 387 }; 388 389 this.prototype._initHooks = this.prototype._initHooks || []; 390 this.prototype._initHooks.push(init); 391 return this; 392}; 393 394function checkDeprecatedMixinEvents(includes) { 395 /* global L: true */ 396 if (typeof L === 'undefined' || !L || !L.Mixin) { return; } 397 398 includes = isArray(includes) ? includes : [includes]; 399 400 for (var i = 0; i < includes.length; i++) { 401 if (includes[i] === L.Mixin.Events) { 402 console.warn('Deprecated include of L.Mixin.Events: ' + 403 'this property will be removed in future releases, ' + 404 'please inherit from L.Evented instead.', new Error().stack); 405 } 406 } 407} 408 409/* 410 * @class Evented 411 * @aka L.Evented 412 * @inherits Class 413 * 414 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event). 415 * 416 * @example 417 * 418 * ```js 419 * map.on('click', function(e) { 420 * alert(e.latlng); 421 * } ); 422 * ``` 423 * 424 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 425 * 426 * ```js 427 * function onClick(e) { ... } 428 * 429 * map.on('click', onClick); 430 * map.off('click', onClick); 431 * ``` 432 */ 433 434var Events = { 435 /* @method on(type: String, fn: Function, context?: Object): this 436 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). 437 * 438 * @alternative 439 * @method on(eventMap: Object): this 440 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 441 */ 442 on: function (types, fn, context) { 443 444 // types can be a map of types/handlers 445 if (typeof types === 'object') { 446 for (var type in types) { 447 // we don't process space-separated events here for performance; 448 // it's a hot path since Layer uses the on(obj) syntax 449 this._on(type, types[type], fn); 450 } 451 452 } else { 453 // types can be a string of space-separated words 454 types = splitWords(types); 455 456 for (var i = 0, len = types.length; i < len; i++) { 457 this._on(types[i], fn, context); 458 } 459 } 460 461 return this; 462 }, 463 464 /* @method off(type: String, fn?: Function, context?: Object): this 465 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener. 466 * 467 * @alternative 468 * @method off(eventMap: Object): this 469 * Removes a set of type/listener pairs. 470 * 471 * @alternative 472 * @method off: this 473 * Removes all listeners to all events on the object. This includes implicitly attached events. 474 */ 475 off: function (types, fn, context) { 476 477 if (!arguments.length) { 478 // clear all listeners if called without arguments 479 delete this._events; 480 481 } else if (typeof types === 'object') { 482 for (var type in types) { 483 this._off(type, types[type], fn); 484 } 485 486 } else { 487 types = splitWords(types); 488 489 var removeAll = arguments.length === 1; 490 for (var i = 0, len = types.length; i < len; i++) { 491 if (removeAll) { 492 this._off(types[i]); 493 } else { 494 this._off(types[i], fn, context); 495 } 496 } 497 } 498 499 return this; 500 }, 501 502 // attach listener (without syntactic sugar now) 503 _on: function (type, fn, context, _once) { 504 if (typeof fn !== 'function') { 505 console.warn('wrong listener type: ' + typeof fn); 506 return; 507 } 508 509 // check if fn already there 510 if (this._listens(type, fn, context) !== false) { 511 return; 512 } 513 514 if (context === this) { 515 // Less memory footprint. 516 context = undefined; 517 } 518 519 var newListener = {fn: fn, ctx: context}; 520 if (_once) { 521 newListener.once = true; 522 } 523 524 this._events = this._events || {}; 525 this._events[type] = this._events[type] || []; 526 this._events[type].push(newListener); 527 }, 528 529 _off: function (type, fn, context) { 530 var listeners, 531 i, 532 len; 533 534 if (!this._events) { 535 return; 536 } 537 538 listeners = this._events[type]; 539 if (!listeners) { 540 return; 541 } 542 543 if (arguments.length === 1) { // remove all 544 if (this._firingCount) { 545 // Set all removed listeners to noop 546 // so they are not called if remove happens in fire 547 for (i = 0, len = listeners.length; i < len; i++) { 548 listeners[i].fn = falseFn; 549 } 550 } 551 // clear all listeners for a type if function isn't specified 552 delete this._events[type]; 553 return; 554 } 555 556 if (typeof fn !== 'function') { 557 console.warn('wrong listener type: ' + typeof fn); 558 return; 559 } 560 561 // find fn and remove it 562 var index = this._listens(type, fn, context); 563 if (index !== false) { 564 var listener = listeners[index]; 565 if (this._firingCount) { 566 // set the removed listener to noop so that's not called if remove happens in fire 567 listener.fn = falseFn; 568 569 /* copy array in case events are being fired */ 570 this._events[type] = listeners = listeners.slice(); 571 } 572 listeners.splice(index, 1); 573 } 574 }, 575 576 // @method fire(type: String, data?: Object, propagate?: Boolean): this 577 // Fires an event of the specified type. You can optionally provide a data 578 // object â the first argument of the listener function will contain its 579 // properties. The event can optionally be propagated to event parents. 580 fire: function (type, data, propagate) { 581 if (!this.listens(type, propagate)) { return this; } 582 583 var event = extend({}, data, { 584 type: type, 585 target: this, 586 sourceTarget: data && data.sourceTarget || this 587 }); 588 589 if (this._events) { 590 var listeners = this._events[type]; 591 if (listeners) { 592 this._firingCount = (this._firingCount + 1) || 1; 593 for (var i = 0, len = listeners.length; i < len; i++) { 594 var l = listeners[i]; 595 // off overwrites l.fn, so we need to copy fn to a var 596 var fn = l.fn; 597 if (l.once) { 598 this.off(type, fn, l.ctx); 599 } 600 fn.call(l.ctx || this, event); 601 } 602 603 this._firingCount--; 604 } 605 } 606 607 if (propagate) { 608 // propagate the event to parents (set with addEventParent) 609 this._propagateEvent(event); 610 } 611 612 return this; 613 }, 614 615 // @method listens(type: String, propagate?: Boolean): Boolean 616 // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean 617 // Returns `true` if a particular event type has any listeners attached to it. 618 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it. 619 listens: function (type, fn, context, propagate) { 620 if (typeof type !== 'string') { 621 console.warn('"string" type argument expected'); 622 } 623 624 // we don't overwrite the input `fn` value, because we need to use it for propagation 625 var _fn = fn; 626 if (typeof fn !== 'function') { 627 propagate = !!fn; 628 _fn = undefined; 629 context = undefined; 630 } 631 632 var listeners = this._events && this._events[type]; 633 if (listeners && listeners.length) { 634 if (this._listens(type, _fn, context) !== false) { 635 return true; 636 } 637 } 638 639 if (propagate) { 640 // also check parents for listeners if event propagates 641 for (var id in this._eventParents) { 642 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; } 643 } 644 } 645 return false; 646 }, 647 648 // returns the index (number) or false 649 _listens: function (type, fn, context) { 650 if (!this._events) { 651 return false; 652 } 653 654 var listeners = this._events[type] || []; 655 if (!fn) { 656 return !!listeners.length; 657 } 658 659 if (context === this) { 660 // Less memory footprint. 661 context = undefined; 662 } 663 664 for (var i = 0, len = listeners.length; i < len; i++) { 665 if (listeners[i].fn === fn && listeners[i].ctx === context) { 666 return i; 667 } 668 } 669 return false; 670 671 }, 672 673 // @method once(â¦): this 674 // Behaves as [`on(â¦)`](#evented-on), except the listener will only get fired once and then removed. 675 once: function (types, fn, context) { 676 677 // types can be a map of types/handlers 678 if (typeof types === 'object') { 679 for (var type in types) { 680 // we don't process space-separated events here for performance; 681 // it's a hot path since Layer uses the on(obj) syntax 682 this._on(type, types[type], fn, true); 683 } 684 685 } else { 686 // types can be a string of space-separated words 687 types = splitWords(types); 688 689 for (var i = 0, len = types.length; i < len; i++) { 690 this._on(types[i], fn, context, true); 691 } 692 } 693 694 return this; 695 }, 696 697 // @method addEventParent(obj: Evented): this 698 // Adds an event parent - an `Evented` that will receive propagated events 699 addEventParent: function (obj) { 700 this._eventParents = this._eventParents || {}; 701 this._eventParents[stamp(obj)] = obj; 702 return this; 703 }, 704 705 // @method removeEventParent(obj: Evented): this 706 // Removes an event parent, so it will stop receiving propagated events 707 removeEventParent: function (obj) { 708 if (this._eventParents) { 709 delete this._eventParents[stamp(obj)]; 710 } 711 return this; 712 }, 713 714 _propagateEvent: function (e) { 715 for (var id in this._eventParents) { 716 this._eventParents[id].fire(e.type, extend({ 717 layer: e.target, 718 propagatedFrom: e.target 719 }, e), true); 720 } 721 } 722}; 723 724// aliases; we should ditch those eventually 725 726// @method addEventListener(â¦): this 727// Alias to [`on(â¦)`](#evented-on) 728Events.addEventListener = Events.on; 729 730// @method removeEventListener(â¦): this 731// Alias to [`off(â¦)`](#evented-off) 732 733// @method clearAllEventListeners(â¦): this 734// Alias to [`off()`](#evented-off) 735Events.removeEventListener = Events.clearAllEventListeners = Events.off; 736 737// @method addOneTimeEventListener(â¦): this 738// Alias to [`once(â¦)`](#evented-once) 739Events.addOneTimeEventListener = Events.once; 740 741// @method fireEvent(â¦): this 742// Alias to [`fire(â¦)`](#evented-fire) 743Events.fireEvent = Events.fire; 744 745// @method hasEventListeners(â¦): Boolean 746// Alias to [`listens(â¦)`](#evented-listens) 747Events.hasEventListeners = Events.listens; 748 749var Evented = Class.extend(Events); 750 751/* 752 * @class Point 753 * @aka L.Point 754 * 755 * Represents a point with `x` and `y` coordinates in pixels. 756 * 757 * @example 758 * 759 * ```js 760 * var point = L.point(200, 300); 761 * ``` 762 * 763 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent: 764 * 765 * ```js 766 * map.panBy([200, 300]); 767 * map.panBy(L.point(200, 300)); 768 * ``` 769 * 770 * Note that `Point` does not inherit from Leaflet's `Class` object, 771 * which means new classes can't inherit from it, and new methods 772 * can't be added to it with the `include` function. 773 */ 774 775function Point(x, y, round) { 776 // @property x: Number; The `x` coordinate of the point 777 this.x = (round ? Math.round(x) : x); 778 // @property y: Number; The `y` coordinate of the point 779 this.y = (round ? Math.round(y) : y); 780} 781 782var trunc = Math.trunc || function (v) { 783 return v > 0 ? Math.floor(v) : Math.ceil(v); 784}; 785 786Point.prototype = { 787 788 // @method clone(): Point 789 // Returns a copy of the current point. 790 clone: function () { 791 return new Point(this.x, this.y); 792 }, 793 794 // @method add(otherPoint: Point): Point 795 // Returns the result of addition of the current and the given points. 796 add: function (point) { 797 // non-destructive, returns a new point 798 return this.clone()._add(toPoint(point)); 799 }, 800 801 _add: function (point) { 802 // destructive, used directly for performance in situations where it's safe to modify existing point 803 this.x += point.x; 804 this.y += point.y; 805 return this; 806 }, 807 808 // @method subtract(otherPoint: Point): Point 809 // Returns the result of subtraction of the given point from the current. 810 subtract: function (point) { 811 return this.clone()._subtract(toPoint(point)); 812 }, 813 814 _subtract: function (point) { 815 this.x -= point.x; 816 this.y -= point.y; 817 return this; 818 }, 819 820 // @method divideBy(num: Number): Point 821 // Returns the result of division of the current point by the given number. 822 divideBy: function (num) { 823 return this.clone()._divideBy(num); 824 }, 825 826 _divideBy: function (num) { 827 this.x /= num; 828 this.y /= num; 829 return this; 830 }, 831 832 // @method multiplyBy(num: Number): Point 833 // Returns the result of multiplication of the current point by the given number. 834 multiplyBy: function (num) { 835 return this.clone()._multiplyBy(num); 836 }, 837 838 _multiplyBy: function (num) { 839 this.x *= num; 840 this.y *= num; 841 return this; 842 }, 843 844 // @method scaleBy(scale: Point): Point 845 // Multiply each coordinate of the current point by each coordinate of 846 // `scale`. In linear algebra terms, multiply the point by the 847 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 848 // defined by `scale`. 849 scaleBy: function (point) { 850 return new Point(this.x * point.x, this.y * point.y); 851 }, 852 853 // @method unscaleBy(scale: Point): Point 854 // Inverse of `scaleBy`. Divide each coordinate of the current point by 855 // each coordinate of `scale`. 856 unscaleBy: function (point) { 857 return new Point(this.x / point.x, this.y / point.y); 858 }, 859 860 // @method round(): Point 861 // Returns a copy of the current point with rounded coordinates. 862 round: function () { 863 return this.clone()._round(); 864 }, 865 866 _round: function () { 867 this.x = Math.round(this.x); 868 this.y = Math.round(this.y); 869 return this; 870 }, 871 872 // @method floor(): Point 873 // Returns a copy of the current point with floored coordinates (rounded down). 874 floor: function () { 875 return this.clone()._floor(); 876 }, 877 878 _floor: function () { 879 this.x = Math.floor(this.x); 880 this.y = Math.floor(this.y); 881 return this; 882 }, 883 884 // @method ceil(): Point 885 // Returns a copy of the current point with ceiled coordinates (rounded up). 886 ceil: function () { 887 return this.clone()._ceil(); 888 }, 889 890 _ceil: function () { 891 this.x = Math.ceil(this.x); 892 this.y = Math.ceil(this.y); 893 return this; 894 }, 895 896 // @method trunc(): Point 897 // Returns a copy of the current point with truncated coordinates (rounded towards zero). 898 trunc: function () { 899 return this.clone()._trunc(); 900 }, 901 902 _trunc: function () { 903 this.x = trunc(this.x); 904 this.y = trunc(this.y); 905 return this; 906 }, 907 908 // @method distanceTo(otherPoint: Point): Number 909 // Returns the cartesian distance between the current and the given points. 910 distanceTo: function (point) { 911 point = toPoint(point); 912 913 var x = point.x - this.x, 914 y = point.y - this.y; 915 916 return Math.sqrt(x * x + y * y); 917 }, 918 919 // @method equals(otherPoint: Point): Boolean 920 // Returns `true` if the given point has the same coordinates. 921 equals: function (point) { 922 point = toPoint(point); 923 924 return point.x === this.x && 925 point.y === this.y; 926 }, 927 928 // @method contains(otherPoint: Point): Boolean 929 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 930 contains: function (point) { 931 point = toPoint(point); 932 933 return Math.abs(point.x) <= Math.abs(this.x) && 934 Math.abs(point.y) <= Math.abs(this.y); 935 }, 936 937 // @method toString(): String 938 // Returns a string representation of the point for debugging purposes. 939 toString: function () { 940 return 'Point(' + 941 formatNum(this.x) + ', ' + 942 formatNum(this.y) + ')'; 943 } 944}; 945 946// @factory L.point(x: Number, y: Number, round?: Boolean) 947// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 948 949// @alternative 950// @factory L.point(coords: Number[]) 951// Expects an array of the form `[x, y]` instead. 952 953// @alternative 954// @factory L.point(coords: Object) 955// Expects a plain object of the form `{x: Number, y: Number}` instead. 956function toPoint(x, y, round) { 957 if (x instanceof Point) { 958 return x; 959 } 960 if (isArray(x)) { 961 return new Point(x[0], x[1]); 962 } 963 if (x === undefined || x === null) { 964 return x; 965 } 966 if (typeof x === 'object' && 'x' in x && 'y' in x) { 967 return new Point(x.x, x.y); 968 } 969 return new Point(x, y, round); 970} 971 972/* 973 * @class Bounds 974 * @aka L.Bounds 975 * 976 * Represents a rectangular area in pixel coordinates. 977 * 978 * @example 979 * 980 * ```js 981 * var p1 = L.point(10, 10), 982 * p2 = L.point(40, 60), 983 * bounds = L.bounds(p1, p2); 984 * ``` 985 * 986 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: 987 * 988 * ```js 989 * otherBounds.intersects([[10, 10], [40, 60]]); 990 * ``` 991 * 992 * Note that `Bounds` does not inherit from Leaflet's `Class` object, 993 * which means new classes can't inherit from it, and new methods 994 * can't be added to it with the `include` function. 995 */ 996 997function Bounds(a, b) { 998 if (!a) { return; } 999 1000 var points = b ? [a, b] : a; 1001 1002 for (var i = 0, len = points.length; i < len; i++) { 1003 this.extend(points[i]); 1004 } 1005} 1006 1007Bounds.prototype = { 1008 // @method extend(point: Point): this 1009 // Extends the bounds to contain the given point. 1010 1011 // @alternative 1012 // @method extend(otherBounds: Bounds): this 1013 // Extend the bounds to contain the given bounds 1014 extend: function (obj) { 1015 var min2, max2; 1016 if (!obj) { return this; } 1017 1018 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) { 1019 min2 = max2 = toPoint(obj); 1020 } else { 1021 obj = toBounds(obj); 1022 min2 = obj.min; 1023 max2 = obj.max; 1024 1025 if (!min2 || !max2) { return this; } 1026 } 1027 1028 // @property min: Point 1029 // The top left corner of the rectangle. 1030 // @property max: Point 1031 // The bottom right corner of the rectangle. 1032 if (!this.min && !this.max) { 1033 this.min = min2.clone(); 1034 this.max = max2.clone(); 1035 } else { 1036 this.min.x = Math.min(min2.x, this.min.x); 1037 this.max.x = Math.max(max2.x, this.max.x); 1038 this.min.y = Math.min(min2.y, this.min.y); 1039 this.max.y = Math.max(max2.y, this.max.y); 1040 } 1041 return this; 1042 }, 1043 1044 // @method getCenter(round?: Boolean): Point 1045 // Returns the center point of the bounds. 1046 getCenter: function (round) { 1047 return toPoint( 1048 (this.min.x + this.max.x) / 2, 1049 (this.min.y + this.max.y) / 2, round); 1050 }, 1051 1052 // @method getBottomLeft(): Point 1053 // Returns the bottom-left point of the bounds. 1054 getBottomLeft: function () { 1055 return toPoint(this.min.x, this.max.y); 1056 }, 1057 1058 // @method getTopRight(): Point 1059 // Returns the top-right point of the bounds. 1060 getTopRight: function () { // -> Point 1061 return toPoint(this.max.x, this.min.y); 1062 }, 1063 1064 // @method getTopLeft(): Point 1065 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 1066 getTopLeft: function () { 1067 return this.min; // left, top 1068 }, 1069 1070 // @method getBottomRight(): Point 1071 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 1072 getBottomRight: function () { 1073 return this.max; // right, bottom 1074 }, 1075 1076 // @method getSize(): Point 1077 // Returns the size of the given bounds 1078 getSize: function () { 1079 return this.max.subtract(this.min); 1080 }, 1081 1082 // @method contains(otherBounds: Bounds): Boolean 1083 // Returns `true` if the rectangle contains the given one. 1084 // @alternative 1085 // @method contains(point: Point): Boolean 1086 // Returns `true` if the rectangle contains the given point. 1087 contains: function (obj) { 1088 var min, max; 1089 1090 if (typeof obj[0] === 'number' || obj instanceof Point) { 1091 obj = toPoint(obj); 1092 } else { 1093 obj = toBounds(obj); 1094 } 1095 1096 if (obj instanceof Bounds) { 1097 min = obj.min; 1098 max = obj.max; 1099 } else { 1100 min = max = obj; 1101 } 1102 1103 return (min.x >= this.min.x) && 1104 (max.x <= this.max.x) && 1105 (min.y >= this.min.y) && 1106 (max.y <= this.max.y); 1107 }, 1108 1109 // @method intersects(otherBounds: Bounds): Boolean 1110 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1111 // intersect if they have at least one point in common. 1112 intersects: function (bounds) { // (Bounds) -> Boolean 1113 bounds = toBounds(bounds); 1114 1115 var min = this.min, 1116 max = this.max, 1117 min2 = bounds.min, 1118 max2 = bounds.max, 1119 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1120 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1121 1122 return xIntersects && yIntersects; 1123 }, 1124 1125 // @method overlaps(otherBounds: Bounds): Boolean 1126 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1127 // overlap if their intersection is an area. 1128 overlaps: function (bounds) { // (Bounds) -> Boolean 1129 bounds = toBounds(bounds); 1130 1131 var min = this.min, 1132 max = this.max, 1133 min2 = bounds.min, 1134 max2 = bounds.max, 1135 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1136 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1137 1138 return xOverlaps && yOverlaps; 1139 }, 1140 1141 // @method isValid(): Boolean 1142 // Returns `true` if the bounds are properly initialized. 1143 isValid: function () { 1144 return !!(this.min && this.max); 1145 }, 1146 1147 1148 // @method pad(bufferRatio: Number): Bounds 1149 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1150 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1151 // Negative values will retract the bounds. 1152 pad: function (bufferRatio) { 1153 var min = this.min, 1154 max = this.max, 1155 heightBuffer = Math.abs(min.x - max.x) * bufferRatio, 1156 widthBuffer = Math.abs(min.y - max.y) * bufferRatio; 1157 1158 1159 return toBounds( 1160 toPoint(min.x - heightBuffer, min.y - widthBuffer), 1161 toPoint(max.x + heightBuffer, max.y + widthBuffer)); 1162 }, 1163 1164 1165 // @method equals(otherBounds: Bounds): Boolean 1166 // Returns `true` if the rectangle is equivalent to the given bounds. 1167 equals: function (bounds) { 1168 if (!bounds) { return false; } 1169 1170 bounds = toBounds(bounds); 1171 1172 return this.min.equals(bounds.getTopLeft()) && 1173 this.max.equals(bounds.getBottomRight()); 1174 }, 1175}; 1176 1177 1178// @factory L.bounds(corner1: Point, corner2: Point) 1179// Creates a Bounds object from two corners coordinate pairs. 1180// @alternative 1181// @factory L.bounds(points: Point[]) 1182// Creates a Bounds object from the given array of points. 1183function toBounds(a, b) { 1184 if (!a || a instanceof Bounds) { 1185 return a; 1186 } 1187 return new Bounds(a, b); 1188} 1189 1190/* 1191 * @class LatLngBounds 1192 * @aka L.LatLngBounds 1193 * 1194 * Represents a rectangular geographical area on a map. 1195 * 1196 * @example 1197 * 1198 * ```js 1199 * var corner1 = L.latLng(40.712, -74.227), 1200 * corner2 = L.latLng(40.774, -74.125), 1201 * bounds = L.latLngBounds(corner1, corner2); 1202 * ``` 1203 * 1204 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: 1205 * 1206 * ```js 1207 * map.fitBounds([ 1208 * [40.712, -74.227], 1209 * [40.774, -74.125] 1210 * ]); 1211 * ``` 1212 * 1213 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. 1214 * 1215 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object, 1216 * which means new classes can't inherit from it, and new methods 1217 * can't be added to it with the `include` function. 1218 */ 1219 1220function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1221 if (!corner1) { return; } 1222 1223 var latlngs = corner2 ? [corner1, corner2] : corner1; 1224 1225 for (var i = 0, len = latlngs.length; i < len; i++) { 1226 this.extend(latlngs[i]); 1227 } 1228} 1229 1230LatLngBounds.prototype = { 1231 1232 // @method extend(latlng: LatLng): this 1233 // Extend the bounds to contain the given point 1234 1235 // @alternative 1236 // @method extend(otherBounds: LatLngBounds): this 1237 // Extend the bounds to contain the given bounds 1238 extend: function (obj) { 1239 var sw = this._southWest, 1240 ne = this._northEast, 1241 sw2, ne2; 1242 1243 if (obj instanceof LatLng) { 1244 sw2 = obj; 1245 ne2 = obj; 1246 1247 } else if (obj instanceof LatLngBounds) { 1248 sw2 = obj._southWest; 1249 ne2 = obj._northEast; 1250 1251 if (!sw2 || !ne2) { return this; } 1252 1253 } else { 1254 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 1255 } 1256 1257 if (!sw && !ne) { 1258 this._southWest = new LatLng(sw2.lat, sw2.lng); 1259 this._northEast = new LatLng(ne2.lat, ne2.lng); 1260 } else { 1261 sw.lat = Math.min(sw2.lat, sw.lat); 1262 sw.lng = Math.min(sw2.lng, sw.lng); 1263 ne.lat = Math.max(ne2.lat, ne.lat); 1264 ne.lng = Math.max(ne2.lng, ne.lng); 1265 } 1266 1267 return this; 1268 }, 1269 1270 // @method pad(bufferRatio: Number): LatLngBounds 1271 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1272 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1273 // Negative values will retract the bounds. 1274 pad: function (bufferRatio) { 1275 var sw = this._southWest, 1276 ne = this._northEast, 1277 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1278 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1279 1280 return new LatLngBounds( 1281 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1282 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1283 }, 1284 1285 // @method getCenter(): LatLng 1286 // Returns the center point of the bounds. 1287 getCenter: function () { 1288 return new LatLng( 1289 (this._southWest.lat + this._northEast.lat) / 2, 1290 (this._southWest.lng + this._northEast.lng) / 2); 1291 }, 1292 1293 // @method getSouthWest(): LatLng 1294 // Returns the south-west point of the bounds. 1295 getSouthWest: function () { 1296 return this._southWest; 1297 }, 1298 1299 // @method getNorthEast(): LatLng 1300 // Returns the north-east point of the bounds. 1301 getNorthEast: function () { 1302 return this._northEast; 1303 }, 1304 1305 // @method getNorthWest(): LatLng 1306 // Returns the north-west point of the bounds. 1307 getNorthWest: function () { 1308 return new LatLng(this.getNorth(), this.getWest()); 1309 }, 1310 1311 // @method getSouthEast(): LatLng 1312 // Returns the south-east point of the bounds. 1313 getSouthEast: function () { 1314 return new LatLng(this.getSouth(), this.getEast()); 1315 }, 1316 1317 // @method getWest(): Number 1318 // Returns the west longitude of the bounds 1319 getWest: function () { 1320 return this._southWest.lng; 1321 }, 1322 1323 // @method getSouth(): Number 1324 // Returns the south latitude of the bounds 1325 getSouth: function () { 1326 return this._southWest.lat; 1327 }, 1328 1329 // @method getEast(): Number 1330 // Returns the east longitude of the bounds 1331 getEast: function () { 1332 return this._northEast.lng; 1333 }, 1334 1335 // @method getNorth(): Number 1336 // Returns the north latitude of the bounds 1337 getNorth: function () { 1338 return this._northEast.lat; 1339 }, 1340 1341 // @method contains(otherBounds: LatLngBounds): Boolean 1342 // Returns `true` if the rectangle contains the given one. 1343 1344 // @alternative 1345 // @method contains (latlng: LatLng): Boolean 1346 // Returns `true` if the rectangle contains the given point. 1347 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1348 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { 1349 obj = toLatLng(obj); 1350 } else { 1351 obj = toLatLngBounds(obj); 1352 } 1353 1354 var sw = this._southWest, 1355 ne = this._northEast, 1356 sw2, ne2; 1357 1358 if (obj instanceof LatLngBounds) { 1359 sw2 = obj.getSouthWest(); 1360 ne2 = obj.getNorthEast(); 1361 } else { 1362 sw2 = ne2 = obj; 1363 } 1364 1365 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1366 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1367 }, 1368 1369 // @method intersects(otherBounds: LatLngBounds): Boolean 1370 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1371 intersects: function (bounds) { 1372 bounds = toLatLngBounds(bounds); 1373 1374 var sw = this._southWest, 1375 ne = this._northEast, 1376 sw2 = bounds.getSouthWest(), 1377 ne2 = bounds.getNorthEast(), 1378 1379 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1380 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1381 1382 return latIntersects && lngIntersects; 1383 }, 1384 1385 // @method overlaps(otherBounds: LatLngBounds): Boolean 1386 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1387 overlaps: function (bounds) { 1388 bounds = toLatLngBounds(bounds); 1389 1390 var sw = this._southWest, 1391 ne = this._northEast, 1392 sw2 = bounds.getSouthWest(), 1393 ne2 = bounds.getNorthEast(), 1394 1395 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1396 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1397 1398 return latOverlaps && lngOverlaps; 1399 }, 1400 1401 // @method toBBoxString(): String 1402 // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. 1403 toBBoxString: function () { 1404 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1405 }, 1406 1407 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 1408 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number. 1409 equals: function (bounds, maxMargin) { 1410 if (!bounds) { return false; } 1411 1412 bounds = toLatLngBounds(bounds); 1413 1414 return this._southWest.equals(bounds.getSouthWest(), maxMargin) && 1415 this._northEast.equals(bounds.getNorthEast(), maxMargin); 1416 }, 1417 1418 // @method isValid(): Boolean 1419 // Returns `true` if the bounds are properly initialized. 1420 isValid: function () { 1421 return !!(this._southWest && this._northEast); 1422 } 1423}; 1424 1425// TODO International date line? 1426 1427// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1428// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1429 1430// @alternative 1431// @factory L.latLngBounds(latlngs: LatLng[]) 1432// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). 1433function toLatLngBounds(a, b) { 1434 if (a instanceof LatLngBounds) { 1435 return a; 1436 } 1437 return new LatLngBounds(a, b); 1438} 1439 1440/* @class LatLng 1441 * @aka L.LatLng 1442 * 1443 * Represents a geographical point with a certain latitude and longitude. 1444 * 1445 * @example 1446 * 1447 * ``` 1448 * var latlng = L.latLng(50.5, 30.5); 1449 * ``` 1450 * 1451 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent: 1452 * 1453 * ``` 1454 * map.panTo([50, 30]); 1455 * map.panTo({lon: 30, lat: 50}); 1456 * map.panTo({lat: 50, lng: 30}); 1457 * map.panTo(L.latLng(50, 30)); 1458 * ``` 1459 * 1460 * Note that `LatLng` does not inherit from Leaflet's `Class` object, 1461 * which means new classes can't inherit from it, and new methods 1462 * can't be added to it with the `include` function. 1463 */ 1464 1465function LatLng(lat, lng, alt) { 1466 if (isNaN(lat) || isNaN(lng)) { 1467 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1468 } 1469 1470 // @property lat: Number 1471 // Latitude in degrees 1472 this.lat = +lat; 1473 1474 // @property lng: Number 1475 // Longitude in degrees 1476 this.lng = +lng; 1477 1478 // @property alt: Number 1479 // Altitude in meters (optional) 1480 if (alt !== undefined) { 1481 this.alt = +alt; 1482 } 1483} 1484 1485LatLng.prototype = { 1486 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1487 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number. 1488 equals: function (obj, maxMargin) { 1489 if (!obj) { return false; } 1490 1491 obj = toLatLng(obj); 1492 1493 var margin = Math.max( 1494 Math.abs(this.lat - obj.lat), 1495 Math.abs(this.lng - obj.lng)); 1496 1497 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1498 }, 1499 1500 // @method toString(): String 1501 // Returns a string representation of the point (for debugging purposes). 1502 toString: function (precision) { 1503 return 'LatLng(' + 1504 formatNum(this.lat, precision) + ', ' + 1505 formatNum(this.lng, precision) + ')'; 1506 }, 1507 1508 // @method distanceTo(otherLatLng: LatLng): Number 1509 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines). 1510 distanceTo: function (other) { 1511 return Earth.distance(this, toLatLng(other)); 1512 }, 1513 1514 // @method wrap(): LatLng 1515 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1516 wrap: function () { 1517 return Earth.wrapLatLng(this); 1518 }, 1519 1520 // @method toBounds(sizeInMeters: Number): LatLngBounds 1521 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1522 toBounds: function (sizeInMeters) { 1523 var latAccuracy = 180 * sizeInMeters / 40075017, 1524 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1525 1526 return toLatLngBounds( 1527 [this.lat - latAccuracy, this.lng - lngAccuracy], 1528 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1529 }, 1530 1531 clone: function () { 1532 return new LatLng(this.lat, this.lng, this.alt); 1533 } 1534}; 1535 1536 1537 1538// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1539// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1540 1541// @alternative 1542// @factory L.latLng(coords: Array): LatLng 1543// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1544 1545// @alternative 1546// @factory L.latLng(coords: Object): LatLng 1547// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1548 1549function toLatLng(a, b, c) { 1550 if (a instanceof LatLng) { 1551 return a; 1552 } 1553 if (isArray(a) && typeof a[0] !== 'object') { 1554 if (a.length === 3) { 1555 return new LatLng(a[0], a[1], a[2]); 1556 } 1557 if (a.length === 2) { 1558 return new LatLng(a[0], a[1]); 1559 } 1560 return null; 1561 } 1562 if (a === undefined || a === null) { 1563 return a; 1564 } 1565 if (typeof a === 'object' && 'lat' in a) { 1566 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1567 } 1568 if (b === undefined) { 1569 return null; 1570 } 1571 return new LatLng(a, b, c); 1572} 1573 1574/* 1575 * @namespace CRS 1576 * @crs L.CRS.Base 1577 * Object that defines coordinate reference systems for projecting 1578 * geographical points into pixel (screen) coordinates and back (and to
1579 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 1580 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system). 1581 * 1582 * Leaflet defines the most usual CRSs by default. If you want to use a 1583 * CRS not defined by default, take a look at the 1584 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 1585 * 1586 * Note that the CRS instances do not inherit from Leaflet's `Class` object, 1587 * and can't be instantiated. Also, new classes can't inherit from them, 1588 * and methods can't be added to them with the `include` function. 1589 */ 1590 1591var CRS = { 1592 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1593 // Projects geographical coordinates into pixel coordinates for a given zoom. 1594 latLngToPoint: function (latlng, zoom) { 1595 var projectedPoint = this.projection.project(latlng), 1596 scale = this.scale(zoom); 1597 1598 return this.transformation._transform(projectedPoint, scale); 1599 }, 1600 1601 // @method pointToLatLng(point: Point, zoom: Number): LatLng 1602 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1603 // zoom into geographical coordinates. 1604 pointToLatLng: function (point, zoom) { 1605 var scale = this.scale(zoom), 1606 untransformedPoint = this.transformation.untransform(point, scale); 1607 1608 return this.projection.unproject(untransformedPoint); 1609 }, 1610 1611 // @method project(latlng: LatLng): Point 1612 // Projects geographical coordinates into coordinates in units accepted for 1613 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1614 project: function (latlng) { 1615 return this.projection.project(latlng); 1616 }, 1617 1618 // @method unproject(point: Point): LatLng 1619 // Given a projected coordinate returns the corresponding LatLng. 1620 // The inverse of `project`. 1621 unproject: function (point) { 1622 return this.projection.unproject(point); 1623 }, 1624 1625 // @method scale(zoom: Number): Number 1626 // Returns the scale used when transforming projected coordinates into 1627 // pixel coordinates for a particular zoom. For example, it returns 1628 // `256 * 2^zoom` for Mercator-based CRS. 1629 scale: function (zoom) { 1630 return 256 * Math.pow(2, zoom); 1631 }, 1632 1633 // @method zoom(scale: Number): Number 1634 // Inverse of `scale()`, returns the zoom level corresponding to a scale 1635 // factor of `scale`. 1636 zoom: function (scale) { 1637 return Math.log(scale / 256) / Math.LN2; 1638 }, 1639 1640 // @method getProjectedBounds(zoom: Number): Bounds 1641 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1642 getProjectedBounds: function (zoom) { 1643 if (this.infinite) { return null; } 1644 1645 var b = this.projection.bounds, 1646 s = this.scale(zoom), 1647 min = this.transformation.transform(b.min, s), 1648 max = this.transformation.transform(b.max, s); 1649 1650 return new Bounds(min, max); 1651 }, 1652 1653 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1654 // Returns the distance between two geographical coordinates. 1655 1656 // @property code: String 1657 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1658 // 1659 // @property wrapLng: Number[] 1660 // An array of two numbers defining whether the longitude (horizontal) coordinate 1661 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1662 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1663 // 1664 // @property wrapLat: Number[] 1665 // Like `wrapLng`, but for the latitude (vertical) axis. 1666 1667 // wrapLng: [min, max], 1668 // wrapLat: [min, max], 1669 1670 // @property infinite: Boolean 1671 // If true, the coordinate space will be unbounded (infinite in both axes) 1672 infinite: false, 1673 1674 // @method wrapLatLng(latlng: LatLng): LatLng 1675 // Returns a `LatLng` where lat and lng has been wrapped according to the 1676 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1677 wrapLatLng: function (latlng) { 1678 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 1679 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 1680 alt = latlng.alt; 1681 1682 return new LatLng(lat, lng, alt); 1683 }, 1684 1685 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1686 // Returns a `LatLngBounds` with the same size as the given one, ensuring 1687 // that its center is within the CRS's bounds. 1688 // Only accepts actual `L.LatLngBounds` instances, not arrays. 1689 wrapLatLngBounds: function (bounds) { 1690 var center = bounds.getCenter(), 1691 newCenter = this.wrapLatLng(center), 1692 latShift = center.lat - newCenter.lat, 1693 lngShift = center.lng - newCenter.lng; 1694 1695 if (latShift === 0 && lngShift === 0) { 1696 return bounds; 1697 } 1698
vendor: 151,688 bytes, lines 1699-6733
1699 var sw = bounds.getSouthWest(), 1700 ne = bounds.getNorthEast(), 1701 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), 1702 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1703 1704 return new LatLngBounds(newSw, newNe); 1705 } 1706}; 1707 1708/* 1709 * @namespace CRS 1710 * @crs L.CRS.Earth 1711 * 1712 * Serves as the base for CRS that are global such that they cover the earth. 1713 * Can only be used as the base for other CRS and cannot be used directly, 1714 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 1715 * meters. 1716 */ 1717 1718var Earth = extend({}, CRS, { 1719 wrapLng: [-180, 180], 1720 1721 // Mean Earth Radius, as recommended for use by 1722 // the International Union of Geodesy and Geophysics, 1723 // see https://rosettacode.org/wiki/Haversine_formula 1724 R: 6371000, 1725 1726 // distance between two geographical points using spherical law of cosines approximation 1727 distance: function (latlng1, latlng2) { 1728 var rad = Math.PI / 180, 1729 lat1 = latlng1.lat * rad, 1730 lat2 = latlng2.lat * rad, 1731 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), 1732 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), 1733 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, 1734 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1735 return this.R * c; 1736 } 1737}); 1738 1739/* 1740 * @namespace Projection 1741 * @projection L.Projection.SphericalMercator 1742 * 1743 * Spherical Mercator projection â the most common projection for online maps, 1744 * used by almost all free and commercial tile providers. Assumes that Earth is 1745 * a sphere. Used by the `EPSG:3857` CRS. 1746 */ 1747 1748var earthRadius = 6378137; 1749 1750var SphericalMercator = { 1751 1752 R: earthRadius, 1753 MAX_LATITUDE: 85.0511287798, 1754 1755 project: function (latlng) { 1756 var d = Math.PI / 180, 1757 max = this.MAX_LATITUDE, 1758 lat = Math.max(Math.min(max, latlng.lat), -max), 1759 sin = Math.sin(lat * d); 1760 1761 return new Point( 1762 this.R * latlng.lng * d, 1763 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 1764 }, 1765 1766 unproject: function (point) { 1767 var d = 180 / Math.PI; 1768 1769 return new LatLng( 1770 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 1771 point.x * d / this.R); 1772 }, 1773 1774 bounds: (function () { 1775 var d = earthRadius * Math.PI; 1776 return new Bounds([-d, -d], [d, d]); 1777 })() 1778}; 1779 1780/* 1781 * @class Transformation 1782 * @aka L.Transformation 1783 * 1784 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1785 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1786 * the reverse. Used by Leaflet in its projections code. 1787 * 1788 * @example 1789 * 1790 * ```js 1791 * var transformation = L.transformation(2, 5, -1, 10), 1792 * p = L.point(1, 2), 1793 * p2 = transformation.transform(p), // L.point(7, 8) 1794 * p3 = transformation.untransform(p2); // L.point(1, 2) 1795 * ``` 1796 */ 1797 1798 1799// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1800// Creates a `Transformation` object with the given coefficients. 1801function Transformation(a, b, c, d) { 1802 if (isArray(a)) { 1803 // use array properties 1804 this._a = a[0]; 1805 this._b = a[1]; 1806 this._c = a[2]; 1807 this._d = a[3]; 1808 return; 1809 } 1810 this._a = a; 1811 this._b = b; 1812 this._c = c; 1813 this._d = d; 1814} 1815 1816Transformation.prototype = { 1817 // @method transform(point: Point, scale?: Number): Point 1818 // Returns a transformed point, optionally multiplied by the given scale. 1819 // Only accepts actual `L.Point` instances, not arrays. 1820 transform: function (point, scale) { // (Point, Number) -> Point 1821 return this._transform(point.clone(), scale); 1822 }, 1823 1824 // destructive transform (faster) 1825 _transform: function (point, scale) { 1826 scale = scale || 1; 1827 point.x = scale * (this._a * point.x + this._b); 1828 point.y = scale * (this._c * point.y + this._d); 1829 return point; 1830 }, 1831 1832 // @method untransform(point: Point, scale?: Number): Point 1833 // Returns the reverse transformation of the given point, optionally divided 1834 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1835 untransform: function (point, scale) { 1836 scale = scale || 1; 1837 return new Point( 1838 (point.x / scale - this._b) / this._a, 1839 (point.y / scale - this._d) / this._c); 1840 } 1841}; 1842 1843// factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1844 1845// @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1846// Instantiates a Transformation object with the given coefficients. 1847 1848// @alternative 1849// @factory L.transformation(coefficients: Array): Transformation 1850// Expects an coefficients array of the form 1851// `[a: Number, b: Number, c: Number, d: Number]`. 1852 1853function toTransformation(a, b, c, d) { 1854 return new Transformation(a, b, c, d); 1855} 1856 1857/* 1858 * @namespace CRS 1859 * @crs L.CRS.EPSG3857 1860 * 1861 * The most common CRS for online maps, used by almost all free and commercial 1862 * tile providers. Uses Spherical Mercator projection. Set in by default in 1863 * Map's `crs` option. 1864 */ 1865 1866var EPSG3857 = extend({}, Earth, { 1867 code: 'EPSG:3857', 1868 projection: SphericalMercator, 1869 1870 transformation: (function () { 1871 var scale = 0.5 / (Math.PI * SphericalMercator.R); 1872 return toTransformation(scale, 0.5, -scale, 0.5); 1873 }()) 1874}); 1875 1876var EPSG900913 = extend({}, EPSG3857, { 1877 code: 'EPSG:900913' 1878}); 1879 1880// @namespace SVG; @section 1881// There are several static functions which can be called without instantiating L.SVG: 1882 1883// @function create(name: String): SVGElement 1884// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 1885// corresponding to the class name passed. For example, using 'line' will return 1886// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 1887function svgCreate(name) { 1888 return document.createElementNS('http://www.w3.org/2000/svg', name); 1889} 1890 1891// @function pointsToPath(rings: Point[], closed: Boolean): String 1892// Generates a SVG path string for multiple rings, with each ring turning 1893// into "M..L..L.." instructions 1894function pointsToPath(rings, closed) { 1895 var str = '', 1896 i, j, len, len2, points, p; 1897 1898 for (i = 0, len = rings.length; i < len; i++) { 1899 points = rings[i]; 1900 1901 for (j = 0, len2 = points.length; j < len2; j++) { 1902 p = points[j]; 1903 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 1904 } 1905 1906 // closes the ring for polygons; "x" is VML syntax 1907 str += closed ? (Browser.svg ? 'z' : 'x') : ''; 1908 } 1909 1910 // SVG complains about empty path strings 1911 return str || 'M0 0'; 1912} 1913 1914/* 1915 * @namespace Browser 1916 * @aka L.Browser 1917 * 1918 * A namespace with static properties for browser/feature detection used by Leaflet internally. 1919 * 1920 * @example 1921 * 1922 * ```js 1923 * if (L.Browser.ielt9) { 1924 * alert('Upgrade your browser, dude!'); 1925 * } 1926 * ``` 1927 */ 1928 1929var style = document.documentElement.style; 1930 1931// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 1932var ie = 'ActiveXObject' in window; 1933 1934// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 1935var ielt9 = ie && !document.addEventListener; 1936 1937// @property edge: Boolean; `true` for the Edge web browser. 1938var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 1939 1940// @property webkit: Boolean; 1941// `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 1942var webkit = userAgentContains('webkit'); 1943 1944// @property android: Boolean 1945// **Deprecated.** `true` for any browser running on an Android platform. 1946var android = userAgentContains('android'); 1947 1948// @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3. 1949var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 1950 1951/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 1952var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 1953// @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome) 1954var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 1955 1956// @property opera: Boolean; `true` for the Opera browser 1957var opera = !!window.opera; 1958 1959// @property chrome: Boolean; `true` for the Chrome browser. 1960var chrome = !edge && userAgentContains('chrome'); 1961 1962// @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 1963var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 1964 1965// @property safari: Boolean; `true` for the Safari browser. 1966var safari = !chrome && userAgentContains('safari'); 1967 1968var phantom = userAgentContains('phantom'); 1969 1970// @property opera12: Boolean 1971// `true` for the Opera browser supporting CSS transforms (version 12 or later). 1972var opera12 = 'OTransition' in style; 1973 1974// @property win: Boolean; `true` when the browser is running in a Windows platform 1975var win = navigator.platform.indexOf('Win') === 0; 1976 1977// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. 1978var ie3d = ie && ('transition' in style); 1979 1980// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 1981var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 1982 1983// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 1984var gecko3d = 'MozPerspective' in style; 1985 1986// @property any3d: Boolean 1987// `true` for all browsers supporting CSS transforms. 1988var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1989 1990// @property mobile: Boolean; `true` for all browsers running in a mobile device. 1991var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 1992 1993// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 1994var mobileWebkit = mobile && webkit; 1995 1996// @property mobileWebkit3d: Boolean 1997// `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 1998var mobileWebkit3d = mobile && webkit3d; 1999 2000// @property msPointer: Boolean 2001// `true` for browsers implementing the Microsoft touch events model (notably IE10). 2002var msPointer = !window.PointerEvent && window.MSPointerEvent; 2003 2004// @property pointer: Boolean 2005// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 2006var pointer = !!(window.PointerEvent || msPointer); 2007 2008// @property touchNative: Boolean 2009// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 2010// **This does not necessarily mean** that the browser is running in a computer with 2011// a touchscreen, it only means that the browser is capable of understanding 2012// touch events. 2013var touchNative = 'ontouchstart' in window || !!window.TouchEvent; 2014 2015// @property touch: Boolean 2016// `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events. 2017// Note: pointer events will be preferred (if available), and processed for all `touch*` listeners. 2018var touch = !window.L_NO_TOUCH && (touchNative || pointer); 2019 2020// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 2021var mobileOpera = mobile && opera; 2022 2023// @property mobileGecko: Boolean 2024// `true` for gecko-based browsers running in a mobile device. 2025var mobileGecko = mobile && gecko; 2026 2027// @property retina: Boolean 2028// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 2029var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 2030 2031// @property passiveEvents: Boolean 2032// `true` for browsers that support passive events. 2033var passiveEvents = (function () { 2034 var supportsPassiveOption = false; 2035 try { 2036 var opts = Object.defineProperty({}, 'passive', { 2037 get: function () { // eslint-disable-line getter-return 2038 supportsPassiveOption = true; 2039 } 2040 }); 2041 window.addEventListener('testPassiveEventSupport', falseFn, opts); 2042 window.removeEventListener('testPassiveEventSupport', falseFn, opts); 2043 } catch (e) { 2044 // Errors can safely be ignored since this is only a browser support test. 2045 } 2046 return supportsPassiveOption; 2047}()); 2048 2049// @property canvas: Boolean 2050// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 2051var canvas$1 = (function () { 2052 return !!document.createElement('canvas').getContext; 2053}()); 2054 2055// @property svg: Boolean 2056// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 2057var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect); 2058 2059var inlineSvg = !!svg$1 && (function () { 2060 var div = document.createElement('div'); 2061 div.innerHTML = '<svg/>'; 2062 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg'; 2063})(); 2064 2065// @property vml: Boolean 2066// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 2067var vml = !svg$1 && (function () { 2068 try { 2069 var div = document.createElement('div'); 2070 div.innerHTML = '<v:shape adj="1"/>'; 2071 2072 var shape = div.firstChild; 2073 shape.style.behavior = 'url(#default#VML)'; 2074 2075 return shape && (typeof shape.adj === 'object'); 2076 2077 } catch (e) { 2078 return false; 2079 } 2080}()); 2081 2082 2083// @property mac: Boolean; `true` when the browser is running in a Mac platform 2084var mac = navigator.platform.indexOf('Mac') === 0; 2085 2086// @property mac: Boolean; `true` when the browser is running in a Linux platform 2087var linux = navigator.platform.indexOf('Linux') === 0; 2088 2089function userAgentContains(str) { 2090 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 2091} 2092 2093 2094var Browser = { 2095 ie: ie, 2096 ielt9: ielt9, 2097 edge: edge, 2098 webkit: webkit, 2099 android: android, 2100 android23: android23, 2101 androidStock: androidStock, 2102 opera: opera, 2103 chrome: chrome, 2104 gecko: gecko, 2105 safari: safari, 2106 phantom: phantom, 2107 opera12: opera12, 2108 win: win, 2109 ie3d: ie3d, 2110 webkit3d: webkit3d, 2111 gecko3d: gecko3d, 2112 any3d: any3d, 2113 mobile: mobile, 2114 mobileWebkit: mobileWebkit, 2115 mobileWebkit3d: mobileWebkit3d, 2116 msPointer: msPointer, 2117 pointer: pointer, 2118 touch: touch, 2119 touchNative: touchNative, 2120 mobileOpera: mobileOpera, 2121 mobileGecko: mobileGecko, 2122 retina: retina, 2123 passiveEvents: passiveEvents, 2124 canvas: canvas$1, 2125 svg: svg$1, 2126 vml: vml, 2127 inlineSvg: inlineSvg, 2128 mac: mac, 2129 linux: linux 2130}; 2131 2132/* 2133 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 2134 */ 2135 2136var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown'; 2137var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove'; 2138var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup'; 2139var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel'; 2140var pEvent = { 2141 touchstart : POINTER_DOWN, 2142 touchmove : POINTER_MOVE, 2143 touchend : POINTER_UP, 2144 touchcancel : POINTER_CANCEL 2145}; 2146var handle = { 2147 touchstart : _onPointerStart, 2148 touchmove : _handlePointer, 2149 touchend : _handlePointer, 2150 touchcancel : _handlePointer 2151}; 2152var _pointers = {}; 2153var _pointerDocListener = false; 2154 2155// Provides a touch events wrapper for (ms)pointer events. 2156// ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2157 2158function addPointerListener(obj, type, handler) { 2159 if (type === 'touchstart') { 2160 _addPointerDocListener(); 2161 } 2162 if (!handle[type]) { 2163 console.warn('wrong event specified:', type); 2164 return falseFn; 2165 } 2166 handler = handle[type].bind(this, handler); 2167 obj.addEventListener(pEvent[type], handler, false); 2168 return handler; 2169} 2170 2171function removePointerListener(obj, type, handler) { 2172 if (!pEvent[type]) { 2173 console.warn('wrong event specified:', type); 2174 return; 2175 } 2176 obj.removeEventListener(pEvent[type], handler, false); 2177} 2178 2179function _globalPointerDown(e) { 2180 _pointers[e.pointerId] = e; 2181} 2182 2183function _globalPointerMove(e) { 2184 if (_pointers[e.pointerId]) { 2185 _pointers[e.pointerId] = e; 2186 } 2187} 2188 2189function _globalPointerUp(e) { 2190 delete _pointers[e.pointerId]; 2191} 2192 2193function _addPointerDocListener() { 2194 // need to keep track of what pointers and how many are active to provide e.touches emulation 2195 if (!_pointerDocListener) { 2196 // we listen document as any drags that end by moving the touch off the screen get fired there 2197 document.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2198 document.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2199 document.addEventListener(POINTER_UP, _globalPointerUp, true); 2200 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2201 2202 _pointerDocListener = true; 2203 } 2204} 2205 2206function _handlePointer(handler, e) { 2207 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; } 2208 2209 e.touches = []; 2210 for (var i in _pointers) { 2211 e.touches.push(_pointers[i]); 2212 } 2213 e.changedTouches = [e]; 2214 2215 handler(e); 2216} 2217 2218function _onPointerStart(handler, e) { 2219 // IE10 specific: MsTouch needs preventDefault. See #2000 2220 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) { 2221 preventDefault(e); 2222 } 2223 _handlePointer(handler, e); 2224} 2225 2226/* 2227 * Extends the event handling code with double tap support for mobile browsers. 2228 * 2229 * Note: currently most browsers fire native dblclick, with only a few exceptions 2230 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386) 2231 */ 2232 2233function makeDblclick(event) { 2234 // in modern browsers `type` cannot be just overridden: 2235 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only 2236 var newEvent = {}, 2237 prop, i; 2238 for (i in event) { 2239 prop = event[i]; 2240 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop; 2241 } 2242 event = newEvent; 2243 newEvent.type = 'dblclick'; 2244 newEvent.detail = 2; 2245 newEvent.isTrusted = false; 2246 newEvent._simulated = true; // for debug purposes 2247 return newEvent; 2248} 2249 2250var delay = 200; 2251function addDoubleTapListener(obj, handler) { 2252 // Most browsers handle double tap natively 2253 obj.addEventListener('dblclick', handler); 2254 2255 // On some platforms the browser doesn't fire native dblclicks for touch events. 2256 // It seems that in all such cases `detail` property of `click` event is always `1`. 2257 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed. 2258 var last = 0, 2259 detail; 2260 function simDblclick(e) { 2261 if (e.detail !== 1) { 2262 detail = e.detail; // keep in sync to avoid false dblclick in some cases 2263 return; 2264 } 2265 2266 if (e.pointerType === 'mouse' || 2267 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) { 2268 2269 return; 2270 } 2271 2272 // When clicking on an <input>, the browser generates a click on its 2273 // <label> (and vice versa) triggering two clicks in quick succession. 2274 // This ignores clicks on elements which are a label with a 'for' 2275 // attribute (or children of such a label), but not children of 2276 // a <input>. 2277 var path = getPropagationPath(e); 2278 if (path.some(function (el) { 2279 return el instanceof HTMLLabelElement && el.attributes.for; 2280 }) && 2281 !path.some(function (el) { 2282 return ( 2283 el instanceof HTMLInputElement || 2284 el instanceof HTMLSelectElement 2285 ); 2286 }) 2287 ) { 2288 return; 2289 } 2290 2291 var now = Date.now(); 2292 if (now - last <= delay) { 2293 detail++; 2294 if (detail === 2) { 2295 handler(makeDblclick(e)); 2296 } 2297 } else { 2298 detail = 1; 2299 } 2300 last = now; 2301 } 2302 2303 obj.addEventListener('click', simDblclick); 2304 2305 return { 2306 dblclick: handler, 2307 simDblclick: simDblclick 2308 }; 2309} 2310 2311function removeDoubleTapListener(obj, handlers) { 2312 obj.removeEventListener('dblclick', handlers.dblclick); 2313 obj.removeEventListener('click', handlers.simDblclick); 2314} 2315 2316/* 2317 * @namespace DomUtil 2318 * 2319 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2320 * tree, used by Leaflet internally. 2321 * 2322 * Most functions expecting or returning a `HTMLElement` also work for 2323 * SVG elements. The only difference is that classes refer to CSS classes 2324 * in HTML and SVG classes in SVG. 2325 */ 2326 2327 2328// @property TRANSFORM: String 2329// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2330var TRANSFORM = testProp( 2331 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2332 2333// webkitTransition comes first because some browser versions that drop vendor prefix don't do 2334// the same for the transitionend event, in particular the Android 4.1 stock browser 2335 2336// @property TRANSITION: String 2337// Vendor-prefixed transition style name. 2338var TRANSITION = testProp( 2339 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2340 2341// @property TRANSITION_END: String 2342// Vendor-prefixed transitionend event name. 2343var TRANSITION_END = 2344 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2345 2346 2347// @function get(id: String|HTMLElement): HTMLElement 2348// Returns an element given its DOM id, or returns the element itself 2349// if it was passed directly. 2350function get(id) { 2351 return typeof id === 'string' ? document.getElementById(id) : id; 2352} 2353 2354// @function getStyle(el: HTMLElement, styleAttrib: String): String 2355// Returns the value for a certain style attribute on an element, 2356// including computed values or values set through CSS. 2357function getStyle(el, style) { 2358 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2359 2360 if ((!value || value === 'auto') && document.defaultView) { 2361 var css = document.defaultView.getComputedStyle(el, null); 2362 value = css ? css[style] : null; 2363 } 2364 return value === 'auto' ? null : value; 2365} 2366 2367// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2368// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2369function create$1(tagName, className, container) { 2370 var el = document.createElement(tagName); 2371 el.className = className || ''; 2372 2373 if (container) { 2374 container.appendChild(el); 2375 } 2376 return el; 2377} 2378 2379// @function remove(el: HTMLElement) 2380// Removes `el` from its parent element 2381function remove(el) { 2382 var parent = el.parentNode; 2383 if (parent) { 2384 parent.removeChild(el); 2385 } 2386} 2387 2388// @function empty(el: HTMLElement) 2389// Removes all of `el`'s children elements from `el` 2390function empty(el) { 2391 while (el.firstChild) { 2392 el.removeChild(el.firstChild); 2393 } 2394} 2395 2396// @function toFront(el: HTMLElement) 2397// Makes `el` the last child of its parent, so it renders in front of the other children. 2398function toFront(el) { 2399 var parent = el.parentNode; 2400 if (parent && parent.lastChild !== el) { 2401 parent.appendChild(el); 2402 } 2403} 2404 2405// @function toBack(el: HTMLElement) 2406// Makes `el` the first child of its parent, so it renders behind the other children. 2407function toBack(el) { 2408 var parent = el.parentNode; 2409 if (parent && parent.firstChild !== el) { 2410 parent.insertBefore(el, parent.firstChild); 2411 } 2412} 2413 2414// @function hasClass(el: HTMLElement, name: String): Boolean 2415// Returns `true` if the element's class attribute contains `name`. 2416function hasClass(el, name) { 2417 if (el.classList !== undefined) { 2418 return el.classList.contains(name); 2419 } 2420 var className = getClass(el); 2421 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2422} 2423 2424// @function addClass(el: HTMLElement, name: String) 2425// Adds `name` to the element's class attribute. 2426function addClass(el, name) { 2427 if (el.classList !== undefined) { 2428 var classes = splitWords(name); 2429 for (var i = 0, len = classes.length; i < len; i++) { 2430 el.classList.add(classes[i]); 2431 } 2432 } else if (!hasClass(el, name)) { 2433 var className = getClass(el); 2434 setClass(el, (className ? className + ' ' : '') + name); 2435 } 2436} 2437 2438// @function removeClass(el: HTMLElement, name: String) 2439// Removes `name` from the element's class attribute. 2440function removeClass(el, name) { 2441 if (el.classList !== undefined) { 2442 el.classList.remove(name); 2443 } else { 2444 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2445 } 2446} 2447 2448// @function setClass(el: HTMLElement, name: String) 2449// Sets the element's class. 2450function setClass(el, name) { 2451 if (el.className.baseVal === undefined) { 2452 el.className = name; 2453 } else { 2454 // in case of SVG element 2455 el.className.baseVal = name; 2456 } 2457} 2458 2459// @function getClass(el: HTMLElement): String 2460// Returns the element's class. 2461function getClass(el) { 2462 // Check if the element is an SVGElementInstance and use the correspondingElement instead 2463 // (Required for linked SVG elements in IE11.) 2464 if (el.correspondingElement) { 2465 el = el.correspondingElement; 2466 } 2467 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2468} 2469 2470// @function setOpacity(el: HTMLElement, opacity: Number) 2471// Set the opacity of an element (including old IE support). 2472// `opacity` must be a number from `0` to `1`. 2473function setOpacity(el, value) { 2474 if ('opacity' in el.style) { 2475 el.style.opacity = value; 2476 } else if ('filter' in el.style) { 2477 _setOpacityIE(el, value); 2478 } 2479} 2480 2481function _setOpacityIE(el, value) { 2482 var filter = false, 2483 filterName = 'DXImageTransform.Microsoft.Alpha'; 2484 2485 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2486 try { 2487 filter = el.filters.item(filterName); 2488 } catch (e) { 2489 // don't set opacity to 1 if we haven't already set an opacity, 2490 // it isn't needed and breaks transparent pngs. 2491 if (value === 1) { return; } 2492 } 2493 2494 value = Math.round(value * 100); 2495 2496 if (filter) { 2497 filter.Enabled = (value !== 100); 2498 filter.Opacity = value; 2499 } else { 2500 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2501 } 2502} 2503 2504// @function testProp(props: String[]): String|false 2505// Goes through the array of style names and returns the first name 2506// that is a valid style name for an element. If no such name is found, 2507// it returns false. Useful for vendor-prefixed styles like `transform`. 2508function testProp(props) { 2509 var style = document.documentElement.style; 2510 2511 for (var i = 0; i < props.length; i++) { 2512 if (props[i] in style) { 2513 return props[i]; 2514 } 2515 } 2516 return false; 2517} 2518 2519// @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2520// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2521// and optionally scaled by `scale`. Does not have an effect if the 2522// browser doesn't support 3D CSS transforms. 2523function setTransform(el, offset, scale) { 2524 var pos = offset || new Point(0, 0); 2525 2526 el.style[TRANSFORM] = 2527 (Browser.ie3d ? 2528 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2529 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2530 (scale ? ' scale(' + scale + ')' : ''); 2531} 2532 2533// @function setPosition(el: HTMLElement, position: Point) 2534// Sets the position of `el` to coordinates specified by `position`, 2535// using CSS translate or top/left positioning depending on the browser 2536// (used by Leaflet internally to position its layers). 2537function setPosition(el, point) { 2538 2539 /*eslint-disable */ 2540 el._leaflet_pos = point; 2541 /* eslint-enable */ 2542 2543 if (Browser.any3d) { 2544 setTransform(el, point); 2545 } else { 2546 el.style.left = point.x + 'px'; 2547 el.style.top = point.y + 'px'; 2548 } 2549} 2550 2551// @function getPosition(el: HTMLElement): Point 2552// Returns the coordinates of an element previously positioned with setPosition. 2553function getPosition(el) { 2554 // this method is only used for elements previously positioned using setPosition, 2555 // so it's safe to cache the position for performance 2556 2557 return el._leaflet_pos || new Point(0, 0); 2558} 2559 2560// @function disableTextSelection() 2561// Prevents the user from generating `selectstart` DOM events, usually generated 2562// when the user drags the mouse through a page with text. Used internally 2563// by Leaflet to override the behaviour of any click-and-drag interaction on 2564// the map. Affects drag interactions on the whole document. 2565 2566// @function enableTextSelection() 2567// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2568var disableTextSelection; 2569var enableTextSelection; 2570var _userSelect; 2571if ('onselectstart' in document) { 2572 disableTextSelection = function () { 2573 on(window, 'selectstart', preventDefault); 2574 }; 2575 enableTextSelection = function () { 2576 off(window, 'selectstart', preventDefault); 2577 }; 2578} else { 2579 var userSelectProperty = testProp( 2580 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2581 2582 disableTextSelection = function () { 2583 if (userSelectProperty) { 2584 var style = document.documentElement.style; 2585 _userSelect = style[userSelectProperty]; 2586 style[userSelectProperty] = 'none'; 2587 } 2588 }; 2589 enableTextSelection = function () { 2590 if (userSelectProperty) { 2591 document.documentElement.style[userSelectProperty] = _userSelect; 2592 _userSelect = undefined; 2593 } 2594 }; 2595} 2596 2597// @function disableImageDrag() 2598// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2599// for `dragstart` DOM events, usually generated when the user drags an image. 2600function disableImageDrag() { 2601 on(window, 'dragstart', preventDefault); 2602} 2603 2604// @function enableImageDrag() 2605// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2606function enableImageDrag() { 2607 off(window, 'dragstart', preventDefault); 2608} 2609 2610var _outlineElement, _outlineStyle; 2611// @function preventOutline(el: HTMLElement) 2612// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2613// of the element `el` invisible. Used internally by Leaflet to prevent 2614// focusable elements from displaying an outline when the user performs a 2615// drag interaction on them. 2616function preventOutline(element) { 2617 while (element.tabIndex === -1) { 2618 element = element.parentNode; 2619 } 2620 if (!element.style) { return; } 2621 restoreOutline(); 2622 _outlineElement = element; 2623 _outlineStyle = element.style.outlineStyle; 2624 element.style.outlineStyle = 'none'; 2625 on(window, 'keydown', restoreOutline); 2626} 2627 2628// @function restoreOutline() 2629// Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2630function restoreOutline() { 2631 if (!_outlineElement) { return; } 2632 _outlineElement.style.outlineStyle = _outlineStyle; 2633 _outlineElement = undefined; 2634 _outlineStyle = undefined; 2635 off(window, 'keydown', restoreOutline); 2636} 2637 2638// @function getSizedParentNode(el: HTMLElement): HTMLElement 2639// Finds the closest parent node which size (width and height) is not null. 2640function getSizedParentNode(element) { 2641 do { 2642 element = element.parentNode; 2643 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2644 return element; 2645} 2646 2647// @function getScale(el: HTMLElement): Object 2648// Computes the CSS scale currently applied on the element. 2649// Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2650// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2651function getScale(element) { 2652 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2653 2654 return { 2655 x: rect.width / element.offsetWidth || 1, 2656 y: rect.height / element.offsetHeight || 1, 2657 boundingClientRect: rect 2658 }; 2659} 2660 2661var DomUtil = { 2662 __proto__: null, 2663 TRANSFORM: TRANSFORM, 2664 TRANSITION: TRANSITION, 2665 TRANSITION_END: TRANSITION_END, 2666 get: get, 2667 getStyle: getStyle, 2668 create: create$1, 2669 remove: remove, 2670 empty: empty, 2671 toFront: toFront, 2672 toBack: toBack, 2673 hasClass: hasClass, 2674 addClass: addClass, 2675 removeClass: removeClass, 2676 setClass: setClass, 2677 getClass: getClass, 2678 setOpacity: setOpacity, 2679 testProp: testProp, 2680 setTransform: setTransform, 2681 setPosition: setPosition, 2682 getPosition: getPosition, 2683 get disableTextSelection () { return disableTextSelection; }, 2684 get enableTextSelection () { return enableTextSelection; }, 2685 disableImageDrag: disableImageDrag, 2686 enableImageDrag: enableImageDrag, 2687 preventOutline: preventOutline, 2688 restoreOutline: restoreOutline, 2689 getSizedParentNode: getSizedParentNode, 2690 getScale: getScale 2691}; 2692 2693/* 2694 * @namespace DomEvent 2695 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2696 */ 2697 2698// Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2699 2700// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2701// Adds a listener function (`fn`) to a particular DOM event type of the 2702// element `el`. You can optionally specify the context of the listener 2703// (object the `this` keyword will point to). You can also pass several 2704// space-separated types (e.g. `'click dblclick'`). 2705 2706// @alternative 2707// @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2708// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2709function on(obj, types, fn, context) { 2710 2711 if (types && typeof types === 'object') { 2712 for (var type in types) { 2713 addOne(obj, type, types[type], fn); 2714 } 2715 } else { 2716 types = splitWords(types); 2717 2718 for (var i = 0, len = types.length; i < len; i++) { 2719 addOne(obj, types[i], fn, context); 2720 } 2721 } 2722 2723 return this; 2724} 2725 2726var eventsKey = '_leaflet_events'; 2727 2728// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2729// Removes a previously added listener function. 2730// Note that if you passed a custom context to on, you must pass the same 2731// context to `off` in order to remove the listener. 2732 2733// @alternative 2734// @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2735// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2736 2737// @alternative 2738// @function off(el: HTMLElement, types: String): this 2739// Removes all previously added listeners of given types. 2740 2741// @alternative 2742// @function off(el: HTMLElement): this 2743// Removes all previously added listeners from given HTMLElement 2744function off(obj, types, fn, context) { 2745 2746 if (arguments.length === 1) { 2747 batchRemove(obj); 2748 delete obj[eventsKey]; 2749 2750 } else if (types && typeof types === 'object') { 2751 for (var type in types) { 2752 removeOne(obj, type, types[type], fn); 2753 } 2754 2755 } else { 2756 types = splitWords(types); 2757 2758 if (arguments.length === 2) { 2759 batchRemove(obj, function (type) { 2760 return indexOf(types, type) !== -1; 2761 }); 2762 } else { 2763 for (var i = 0, len = types.length; i < len; i++) { 2764 removeOne(obj, types[i], fn, context); 2765 } 2766 } 2767 } 2768 2769 return this; 2770} 2771 2772function batchRemove(obj, filterFn) { 2773 for (var id in obj[eventsKey]) { 2774 var type = id.split(/\d/)[0]; 2775 if (!filterFn || filterFn(type)) { 2776 removeOne(obj, type, null, null, id); 2777 } 2778 } 2779} 2780 2781var mouseSubst = { 2782 mouseenter: 'mouseover', 2783 mouseleave: 'mouseout', 2784 wheel: !('onwheel' in window) && 'mousewheel' 2785}; 2786 2787function addOne(obj, type, fn, context) { 2788 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2789 2790 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 2791 2792 var handler = function (e) { 2793 return fn.call(context || obj, e || window.event); 2794 }; 2795 2796 var originalHandler = handler; 2797 2798 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 2799 // Needs DomEvent.Pointer.js 2800 handler = addPointerListener(obj, type, handler); 2801 2802 } else if (Browser.touch && (type === 'dblclick')) { 2803 handler = addDoubleTapListener(obj, handler); 2804 2805 } else if ('addEventListener' in obj) { 2806 2807 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') { 2808 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false); 2809 2810 } else if (type === 'mouseenter' || type === 'mouseleave') { 2811 handler = function (e) { 2812 e = e || window.event; 2813 if (isExternalTarget(obj, e)) { 2814 originalHandler(e); 2815 } 2816 }; 2817 obj.addEventListener(mouseSubst[type], handler, false); 2818 2819 } else { 2820 obj.addEventListener(type, originalHandler, false); 2821 } 2822 2823 } else { 2824 obj.attachEvent('on' + type, handler); 2825 } 2826 2827 obj[eventsKey] = obj[eventsKey] || {}; 2828 obj[eventsKey][id] = handler; 2829} 2830 2831function removeOne(obj, type, fn, context, id) { 2832 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2833 var handler = obj[eventsKey] && obj[eventsKey][id]; 2834 2835 if (!handler) { return this; } 2836 2837 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 2838 removePointerListener(obj, type, handler); 2839 2840 } else if (Browser.touch && (type === 'dblclick')) { 2841 removeDoubleTapListener(obj, handler); 2842 2843 } else if ('removeEventListener' in obj) { 2844 2845 obj.removeEventListener(mouseSubst[type] || type, handler, false); 2846 2847 } else { 2848 obj.detachEvent('on' + type, handler); 2849 } 2850 2851 obj[eventsKey][id] = null; 2852} 2853 2854// @function stopPropagation(ev: DOMEvent): this 2855// Stop the given event from propagation to parent elements. Used inside the listener functions: 2856// ```js 2857// L.DomEvent.on(div, 'click', function (ev) { 2858// L.DomEvent.stopPropagation(ev); 2859// }); 2860// ``` 2861function stopPropagation(e) { 2862 2863 if (e.stopPropagation) { 2864 e.stopPropagation(); 2865 } else if (e.originalEvent) { // In case of Leaflet event. 2866 e.originalEvent._stopped = true; 2867 } else { 2868 e.cancelBubble = true; 2869 } 2870 2871 return this; 2872} 2873 2874// @function disableScrollPropagation(el: HTMLElement): this 2875// Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants). 2876function disableScrollPropagation(el) { 2877 addOne(el, 'wheel', stopPropagation); 2878 return this; 2879} 2880 2881// @function disableClickPropagation(el: HTMLElement): this 2882// Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`, 2883// `'mousedown'` and `'touchstart'` events (plus browser variants). 2884function disableClickPropagation(el) { 2885 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation); 2886 el['_leaflet_disable_click'] = true; 2887 return this; 2888} 2889 2890// @function preventDefault(ev: DOMEvent): this 2891// Prevents the default action of the DOM Event `ev` from happening (such as 2892// following a link in the href of the a element, or doing a POST request 2893// with page reload when a `<form>` is submitted). 2894// Use it inside listener functions. 2895function preventDefault(e) { 2896 if (e.preventDefault) { 2897 e.preventDefault(); 2898 } else { 2899 e.returnValue = false; 2900 } 2901 return this; 2902} 2903 2904// @function stop(ev: DOMEvent): this 2905// Does `stopPropagation` and `preventDefault` at the same time. 2906function stop(e) { 2907 preventDefault(e); 2908 stopPropagation(e); 2909 return this; 2910} 2911 2912// @function getPropagationPath(ev: DOMEvent): Array 2913// Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath). 2914// Returns an array containing the `HTMLElement`s that the given DOM event 2915// should propagate to (if not stopped). 2916function getPropagationPath(ev) { 2917 if (ev.composedPath) { 2918 return ev.composedPath(); 2919 } 2920 2921 var path = []; 2922 var el = ev.target; 2923 2924 while (el) { 2925 path.push(el); 2926 el = el.parentNode; 2927 } 2928 return path; 2929} 2930 2931 2932// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 2933// Gets normalized mouse position from a DOM event relative to the 2934// `container` (border excluded) or to the whole page if not specified. 2935function getMousePosition(e, container) { 2936 if (!container) { 2937 return new Point(e.clientX, e.clientY); 2938 } 2939 2940 var scale = getScale(container), 2941 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 2942 2943 return new Point( 2944 // offset.left/top values are in page scale (like clientX/Y), 2945 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 2946 (e.clientX - offset.left) / scale.x - container.clientLeft, 2947 (e.clientY - offset.top) / scale.y - container.clientTop 2948 ); 2949} 2950 2951 2952// except , Safari and 2953// We need double the scroll pixels (see #7403 and #4538) for all Browsers 2954// except OSX (Mac) -> 3x, Chrome running on Linux 1x 2955 2956var wheelPxFactor = 2957 (Browser.linux && Browser.chrome) ? window.devicePixelRatio : 2958 Browser.mac ? window.devicePixelRatio * 3 : 2959 window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1; 2960// @function getWheelDelta(ev: DOMEvent): Number 2961// Gets normalized wheel delta from a wheel DOM event, in vertical 2962// pixels scrolled (negative if scrolling down). 2963// Events from pointing devices without precise scrolling are mapped to 2964// a best guess of 60 pixels. 2965function getWheelDelta(e) { 2966 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 2967 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 2968 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 2969 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 2970 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 2971 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 2972 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 2973 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 2974 0; 2975} 2976 2977// check if element really left/entered the event target (for mouseenter/mouseleave) 2978function isExternalTarget(el, e) { 2979 2980 var related = e.relatedTarget; 2981 2982 if (!related) { return true; } 2983 2984 try { 2985 while (related && (related !== el)) { 2986 related = related.parentNode; 2987 } 2988 } catch (err) { 2989 return false; 2990 } 2991 return (related !== el); 2992} 2993 2994var DomEvent = { 2995 __proto__: null, 2996 on: on, 2997 off: off, 2998 stopPropagation: stopPropagation, 2999 disableScrollPropagation: disableScrollPropagation, 3000 disableClickPropagation: disableClickPropagation, 3001 preventDefault: preventDefault, 3002 stop: stop, 3003 getPropagationPath: getPropagationPath, 3004 getMousePosition: getMousePosition, 3005 getWheelDelta: getWheelDelta, 3006 isExternalTarget: isExternalTarget, 3007 addListener: on, 3008 removeListener: off 3009}; 3010 3011/* 3012 * @class PosAnimation 3013 * @aka L.PosAnimation 3014 * @inherits Evented 3015 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 3016 * 3017 * @example 3018 * ```js 3019 * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map); 3020 * 3021 * myPositionMarker.on("click", function() { 3022 * var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng()); 3023 * pos.y -= 25; 3024 * var fx = new L.PosAnimation(); 3025 * 3026 * fx.once('end',function() { 3027 * pos.y += 25; 3028 * fx.run(myPositionMarker._icon, pos, 0.8); 3029 * }); 3030 * 3031 * fx.run(myPositionMarker._icon, pos, 0.3); 3032 * }); 3033 * 3034 * ``` 3035 * 3036 * @constructor L.PosAnimation() 3037 * Creates a `PosAnimation` object. 3038 * 3039 */ 3040 3041var PosAnimation = Evented.extend({ 3042 3043 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 3044 // Run an animation of a given element to a new position, optionally setting 3045 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 3046 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1), 3047 // `0.5` by default). 3048 run: function (el, newPos, duration, easeLinearity) { 3049 this.stop(); 3050 3051 this._el = el; 3052 this._inProgress = true; 3053 this._duration = duration || 0.25; 3054 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 3055 3056 this._startPos = getPosition(el); 3057 this._offset = newPos.subtract(this._startPos); 3058 this._startTime = +new Date(); 3059 3060 // @event start: Event 3061 // Fired when the animation starts 3062 this.fire('start'); 3063 3064 this._animate(); 3065 }, 3066 3067 // @method stop() 3068 // Stops the animation (if currently running). 3069 stop: function () { 3070 if (!this._inProgress) { return; } 3071 3072 this._step(true); 3073 this._complete(); 3074 }, 3075 3076 _animate: function () { 3077 // animation loop 3078 this._animId = requestAnimFrame(this._animate, this); 3079 this._step(); 3080 }, 3081 3082 _step: function (round) { 3083 var elapsed = (+new Date()) - this._startTime, 3084 duration = this._duration * 1000; 3085 3086 if (elapsed < duration) { 3087 this._runFrame(this._easeOut(elapsed / duration), round); 3088 } else { 3089 this._runFrame(1); 3090 this._complete(); 3091 } 3092 }, 3093 3094 _runFrame: function (progress, round) { 3095 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 3096 if (round) { 3097 pos._round(); 3098 } 3099 setPosition(this._el, pos); 3100 3101 // @event step: Event 3102 // Fired continuously during the animation. 3103 this.fire('step'); 3104 }, 3105 3106 _complete: function () { 3107 cancelAnimFrame(this._animId); 3108 3109 this._inProgress = false; 3110 // @event end: Event 3111 // Fired when the animation ends. 3112 this.fire('end'); 3113 }, 3114 3115 _easeOut: function (t) { 3116 return 1 - Math.pow(1 - t, this._easeOutPower); 3117 } 3118}); 3119 3120/* 3121 * @class Map 3122 * @aka L.Map 3123 * @inherits Evented 3124 * 3125 * The central class of the API â it is used to create a map on a page and manipulate it. 3126 * 3127 * @example 3128 * 3129 * ```js 3130 * // initialize the map on the "map" div with a given center and zoom 3131 * var map = L.map('map', { 3132 * center: [51.505, -0.09], 3133 * zoom: 13 3134 * }); 3135 * ``` 3136 * 3137 */ 3138 3139var Map = Evented.extend({ 3140 3141 options: { 3142 // @section Map State Options 3143 // @option crs: CRS = L.CRS.EPSG3857 3144 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 3145 // sure what it means. 3146 crs: EPSG3857, 3147 3148 // @option center: LatLng = undefined 3149 // Initial geographic center of the map 3150 center: undefined, 3151 3152 // @option zoom: Number = undefined 3153 // Initial map zoom level 3154 zoom: undefined, 3155 3156 // @option minZoom: Number = * 3157 // Minimum zoom level of the map. 3158 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3159 // the lowest of their `minZoom` options will be used instead. 3160 minZoom: undefined, 3161 3162 // @option maxZoom: Number = * 3163 // Maximum zoom level of the map. 3164 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3165 // the highest of their `maxZoom` options will be used instead. 3166 maxZoom: undefined, 3167 3168 // @option layers: Layer[] = [] 3169 // Array of layers that will be added to the map initially 3170 layers: [], 3171 3172 // @option maxBounds: LatLngBounds = null 3173 // When this option is set, the map restricts the view to the given 3174 // geographical bounds, bouncing the user back if the user tries to pan 3175 // outside the view. To set the restriction dynamically, use 3176 // [`setMaxBounds`](#map-setmaxbounds) method. 3177 maxBounds: undefined, 3178 3179 // @option renderer: Renderer = * 3180 // The default method for drawing vector layers on the map. `L.SVG` 3181 // or `L.Canvas` by default depending on browser support. 3182 renderer: undefined, 3183 3184 3185 // @section Animation Options 3186 // @option zoomAnimation: Boolean = true 3187 // Whether the map zoom animation is enabled. By default it's enabled 3188 // in all browsers that support CSS3 Transitions except Android. 3189 zoomAnimation: true, 3190 3191 // @option zoomAnimationThreshold: Number = 4 3192 // Won't animate zoom if the zoom difference exceeds this value. 3193 zoomAnimationThreshold: 4, 3194 3195 // @option fadeAnimation: Boolean = true 3196 // Whether the tile fade animation is enabled. By default it's enabled 3197 // in all browsers that support CSS3 Transitions except Android. 3198 fadeAnimation: true, 3199 3200 // @option markerZoomAnimation: Boolean = true 3201 // Whether markers animate their zoom with the zoom animation, if disabled 3202 // they will disappear for the length of the animation. By default it's 3203 // enabled in all browsers that support CSS3 Transitions except Android. 3204 markerZoomAnimation: true, 3205 3206 // @option transform3DLimit: Number = 2^23 3207 // Defines the maximum size of a CSS translation transform. The default 3208 // value should not be changed unless a web browser positions layers in 3209 // the wrong place after doing a large `panBy`. 3210 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3211 3212 // @section Interaction Options 3213 // @option zoomSnap: Number = 1 3214 // Forces the map's zoom level to always be a multiple of this, particularly 3215 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3216 // By default, the zoom level snaps to the nearest integer; lower values 3217 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3218 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3219 zoomSnap: 1, 3220 3221 // @option zoomDelta: Number = 1 3222 // Controls how much the map's zoom level will change after a 3223 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3224 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3225 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3226 zoomDelta: 1, 3227 3228 // @option trackResize: Boolean = true 3229 // Whether the map automatically handles browser window resize to update itself. 3230 trackResize: true 3231 }, 3232 3233 initialize: function (id, options) { // (HTMLElement or String, Object) 3234 options = setOptions(this, options); 3235 3236 // Make sure to assign internal flags at the beginning, 3237 // to avoid inconsistent state in some edge cases. 3238 this._handlers = []; 3239 this._layers = {}; 3240 this._zoomBoundLayers = {}; 3241 this._sizeChanged = true; 3242 3243 this._initContainer(id); 3244 this._initLayout(); 3245 3246 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3247 this._onResize = bind(this._onResize, this); 3248 3249 this._initEvents(); 3250 3251 if (options.maxBounds) { 3252 this.setMaxBounds(options.maxBounds); 3253 } 3254 3255 if (options.zoom !== undefined) { 3256 this._zoom = this._limitZoom(options.zoom); 3257 } 3258 3259 if (options.center && options.zoom !== undefined) { 3260 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3261 } 3262 3263 this.callInitHooks(); 3264 3265 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3266 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera && 3267 this.options.zoomAnimation; 3268 3269 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3270 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3271 if (this._zoomAnimated) { 3272 this._createAnimProxy(); 3273 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3274 } 3275 3276 this._addLayers(this.options.layers); 3277 }, 3278 3279 3280 // @section Methods for modifying map state 3281 3282 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3283 // Sets the view of the map (geographical center and zoom) with the given 3284 // animation options. 3285 setView: function (center, zoom, options) { 3286 3287 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3288 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3289 options = options || {}; 3290 3291 this._stop(); 3292 3293 if (this._loaded && !options.reset && options !== true) { 3294 3295 if (options.animate !== undefined) { 3296 options.zoom = extend({animate: options.animate}, options.zoom); 3297 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3298 } 3299 3300 // try animating pan or zoom 3301 var moved = (this._zoom !== zoom) ? 3302 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3303 this._tryAnimatedPan(center, options.pan); 3304 3305 if (moved) { 3306 // prevent resize handler call, the view will refresh after animation anyway 3307 clearTimeout(this._sizeTimer); 3308 return this; 3309 } 3310 } 3311 3312 // animation didn't start, just reset the map view 3313 this._resetView(center, zoom, options.pan && options.pan.noMoveStart); 3314 3315 return this; 3316 }, 3317 3318 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3319 // Sets the zoom of the map. 3320 setZoom: function (zoom, options) { 3321 if (!this._loaded) { 3322 this._zoom = zoom; 3323 return this; 3324 } 3325 return this.setView(this.getCenter(), zoom, {zoom: options}); 3326 }, 3327 3328 // @method zoomIn(delta?: Number, options?: Zoom options): this 3329 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3330 zoomIn: function (delta, options) { 3331 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3332 return this.setZoom(this._zoom + delta, options); 3333 }, 3334 3335 // @method zoomOut(delta?: Number, options?: Zoom options): this 3336 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3337 zoomOut: function (delta, options) { 3338 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3339 return this.setZoom(this._zoom - delta, options); 3340 }, 3341 3342 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3343 // Zooms the map while keeping a specified geographical point on the map 3344 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3345 // @alternative 3346 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3347 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3348 setZoomAround: function (latlng, zoom, options) { 3349 var scale = this.getZoomScale(zoom), 3350 viewHalf = this.getSize().divideBy(2), 3351 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3352 3353 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3354 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3355 3356 return this.setView(newCenter, zoom, {zoom: options}); 3357 }, 3358 3359 _getBoundsCenterZoom: function (bounds, options) { 3360 3361 options = options || {}; 3362 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3363 3364 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3365 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3366 3367 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3368 3369 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3370 3371 if (zoom === Infinity) { 3372 return { 3373 center: bounds.getCenter(), 3374 zoom: zoom 3375 }; 3376 } 3377 3378 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3379 3380 swPoint = this.project(bounds.getSouthWest(), zoom), 3381 nePoint = this.project(bounds.getNorthEast(), zoom), 3382 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3383 3384 return { 3385 center: center, 3386 zoom: zoom 3387 }; 3388 }, 3389 3390 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3391 // Sets a map view that contains the given geographical bounds with the 3392 // maximum zoom level possible. 3393 fitBounds: function (bounds, options) { 3394 3395 bounds = toLatLngBounds(bounds); 3396 3397 if (!bounds.isValid()) { 3398 throw new Error('Bounds are not valid.'); 3399 } 3400 3401 var target = this._getBoundsCenterZoom(bounds, options); 3402 return this.setView(target.center, target.zoom, options); 3403 }, 3404 3405 // @method fitWorld(options?: fitBounds options): this 3406 // Sets a map view that mostly contains the whole world with the maximum 3407 // zoom level possible. 3408 fitWorld: function (options) { 3409 return this.fitBounds([[-90, -180], [90, 180]], options); 3410 }, 3411 3412 // @method panTo(latlng: LatLng, options?: Pan options): this 3413 // Pans the map to a given center. 3414 panTo: function (center, options) { // (LatLng) 3415 return this.setView(center, this._zoom, {pan: options}); 3416 }, 3417 3418 // @method panBy(offset: Point, options?: Pan options): this 3419 // Pans the map by a given number of pixels (animated). 3420 panBy: function (offset, options) { 3421 offset = toPoint(offset).round(); 3422 options = options || {}; 3423 3424 if (!offset.x && !offset.y) { 3425 return this.fire('moveend'); 3426 } 3427 // If we pan too far, Chrome gets issues with tiles 3428 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3429 if (options.animate !== true && !this.getSize().contains(offset)) { 3430 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3431 return this; 3432 } 3433 3434 if (!this._panAnim) { 3435 this._panAnim = new PosAnimation(); 3436 3437 this._panAnim.on({ 3438 'step': this._onPanTransitionStep, 3439 'end': this._onPanTransitionEnd 3440 }, this); 3441 } 3442 3443 // don't fire movestart if animating inertia 3444 if (!options.noMoveStart) { 3445 this.fire('movestart'); 3446 } 3447 3448 // animate pan unless animate: false specified 3449 if (options.animate !== false) { 3450 addClass(this._mapPane, 'leaflet-pan-anim'); 3451 3452 var newPos = this._getMapPanePos().subtract(offset).round(); 3453 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3454 } else { 3455 this._rawPanBy(offset); 3456 this.fire('move').fire('moveend'); 3457 } 3458 3459 return this; 3460 }, 3461 3462 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3463 // Sets the view of the map (geographical center and zoom) performing a smooth 3464 // pan-zoom animation. 3465 flyTo: function (targetCenter, targetZoom, options) { 3466 3467 options = options || {}; 3468 if (options.animate === false || !Browser.any3d) { 3469 return this.setView(targetCenter, targetZoom, options); 3470 } 3471 3472 this._stop(); 3473 3474 var from = this.project(this.getCenter()), 3475 to = this.project(targetCenter), 3476 size = this.getSize(), 3477 startZoom = this._zoom; 3478 3479 targetCenter = toLatLng(targetCenter); 3480 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3481 3482 var w0 = Math.max(size.x, size.y), 3483 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3484 u1 = (to.distanceTo(from)) || 1, 3485 rho = 1.42, 3486 rho2 = rho * rho; 3487 3488 function r(i) { 3489 var s1 = i ? -1 : 1, 3490 s2 = i ? w1 : w0, 3491 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3492 b1 = 2 * s2 * rho2 * u1, 3493 b = t1 / b1, 3494 sq = Math.sqrt(b * b + 1) - b; 3495 3496 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3497 // thus triggering an infinite loop in flyTo 3498 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3499 3500 return log; 3501 } 3502 3503 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3504 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3505 function tanh(n) { return sinh(n) / cosh(n); } 3506 3507 var r0 = r(0); 3508 3509 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3510 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3511 3512 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3513 3514 var start = Date.now(), 3515 S = (r(1) - r0) / rho, 3516 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3517 3518 function frame() { 3519 var t = (Date.now() - start) / duration, 3520 s = easeOut(t) * S; 3521 3522 if (t <= 1) { 3523 this._flyToFrame = requestAnimFrame(frame, this); 3524 3525 this._move( 3526 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3527 this.getScaleZoom(w0 / w(s), startZoom), 3528 {flyTo: true}); 3529 3530 } else { 3531 this 3532 ._move(targetCenter, targetZoom) 3533 ._moveEnd(true); 3534 } 3535 } 3536 3537 this._moveStart(true, options.noMoveStart); 3538 3539 frame.call(this); 3540 return this; 3541 }, 3542 3543 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3544 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3545 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3546 flyToBounds: function (bounds, options) { 3547 var target = this._getBoundsCenterZoom(bounds, options); 3548 return this.flyTo(target.center, target.zoom, options); 3549 }, 3550 3551 // @method setMaxBounds(bounds: LatLngBounds): this 3552 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3553 setMaxBounds: function (bounds) { 3554 bounds = toLatLngBounds(bounds); 3555 3556 if (this.listens('moveend', this._panInsideMaxBounds)) { 3557 this.off('moveend', this._panInsideMaxBounds); 3558 } 3559 3560 if (!bounds.isValid()) { 3561 this.options.maxBounds = null; 3562 return this; 3563 } 3564 3565 this.options.maxBounds = bounds; 3566 3567 if (this._loaded) { 3568 this._panInsideMaxBounds(); 3569 } 3570 3571 return this.on('moveend', this._panInsideMaxBounds); 3572 }, 3573 3574 // @method setMinZoom(zoom: Number): this 3575 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3576 setMinZoom: function (zoom) { 3577 var oldZoom = this.options.minZoom; 3578 this.options.minZoom = zoom; 3579 3580 if (this._loaded && oldZoom !== zoom) { 3581 this.fire('zoomlevelschange'); 3582 3583 if (this.getZoom() < this.options.minZoom) { 3584 return this.setZoom(zoom); 3585 } 3586 } 3587 3588 return this; 3589 }, 3590 3591 // @method setMaxZoom(zoom: Number): this 3592 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3593 setMaxZoom: function (zoom) { 3594 var oldZoom = this.options.maxZoom; 3595 this.options.maxZoom = zoom; 3596 3597 if (this._loaded && oldZoom !== zoom) { 3598 this.fire('zoomlevelschange'); 3599 3600 if (this.getZoom() > this.options.maxZoom) { 3601 return this.setZoom(zoom); 3602 } 3603 } 3604 3605 return this; 3606 }, 3607 3608 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3609 // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any. 3610 panInsideBounds: function (bounds, options) { 3611 this._enforcingBounds = true; 3612 var center = this.getCenter(), 3613 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3614 3615 if (!center.equals(newCenter)) { 3616 this.panTo(newCenter, options); 3617 } 3618 3619 this._enforcingBounds = false; 3620 return this; 3621 }, 3622 3623 // @method panInside(latlng: LatLng, options?: padding options): this 3624 // Pans the map the minimum amount to make the `latlng` visible. Use 3625 // padding options to fit the display to more restricted bounds. 3626 // If `latlng` is already within the (optionally padded) display bounds, 3627 // the map will not be panned. 3628 panInside: function (latlng, options) { 3629 options = options || {}; 3630 3631 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3632 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3633 pixelCenter = this.project(this.getCenter()), 3634 pixelPoint = this.project(latlng), 3635 pixelBounds = this.getPixelBounds(), 3636 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]), 3637 paddedSize = paddedBounds.getSize(); 3638 3639 if (!paddedBounds.contains(pixelPoint)) { 3640 this._enforcingBounds = true; 3641 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter()); 3642 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize); 3643 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x; 3644 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y; 3645 this.panTo(this.unproject(pixelCenter), options); 3646 this._enforcingBounds = false; 3647 } 3648 return this; 3649 }, 3650 3651 // @method invalidateSize(options: Zoom/pan options): this 3652 // Checks if the map container size changed and updates the map if so â 3653 // call it after you've changed the map size dynamically, also animating 3654 // pan by default. If `options.pan` is `false`, panning will not occur. 3655 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3656 // that it doesn't happen often even if the method is called many 3657 // times in a row. 3658 3659 // @alternative 3660 // @method invalidateSize(animate: Boolean): this 3661 // Checks if the map container size changed and updates the map if so â 3662 // call it after you've changed the map size dynamically, also animating 3663 // pan by default. 3664 invalidateSize: function (options) { 3665 if (!this._loaded) { return this; } 3666 3667 options = extend({ 3668 animate: false, 3669 pan: true 3670 }, options === true ? {animate: true} : options); 3671 3672 var oldSize = this.getSize(); 3673 this._sizeChanged = true; 3674 this._lastCenter = null; 3675 3676 var newSize = this.getSize(), 3677 oldCenter = oldSize.divideBy(2).round(), 3678 newCenter = newSize.divideBy(2).round(), 3679 offset = oldCenter.subtract(newCenter); 3680 3681 if (!offset.x && !offset.y) { return this; } 3682 3683 if (options.animate && options.pan) { 3684 this.panBy(offset); 3685 3686 } else { 3687 if (options.pan) { 3688 this._rawPanBy(offset); 3689 } 3690 3691 this.fire('move'); 3692 3693 if (options.debounceMoveend) { 3694 clearTimeout(this._sizeTimer); 3695 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3696 } else { 3697 this.fire('moveend'); 3698 } 3699 } 3700 3701 // @section Map state change events 3702 // @event resize: ResizeEvent 3703 // Fired when the map is resized. 3704 return this.fire('resize', { 3705 oldSize: oldSize, 3706 newSize: newSize 3707 }); 3708 }, 3709 3710 // @section Methods for modifying map state 3711 // @method stop(): this 3712 // Stops the currently running `panTo` or `flyTo` animation, if any. 3713 stop: function () { 3714 this.setZoom(this._limitZoom(this._zoom)); 3715 if (!this.options.zoomSnap) { 3716 this.fire('viewreset'); 3717 } 3718 return this._stop(); 3719 }, 3720 3721 // @section Geolocation methods 3722 // @method locate(options?: Locate options): this 3723 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3724 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3725 // and optionally sets the map view to the user's location with respect to 3726 // detection accuracy (or to the world view if geolocation failed). 3727 // Note that, if your page doesn't use HTTPS, this method will fail in 3728 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3729 // See `Locate options` for more details. 3730 locate: function (options) { 3731 3732 options = this._locateOptions = extend({ 3733 timeout: 10000, 3734 watch: false 3735 // setView: false 3736 // maxZoom: <Number> 3737 // maximumAge: 0 3738 // enableHighAccuracy: false 3739 }, options); 3740 3741 if (!('geolocation' in navigator)) { 3742 this._handleGeolocationError({ 3743 code: 0, 3744 message: 'Geolocation not supported.' 3745 }); 3746 return this; 3747 } 3748 3749 var onResponse = bind(this._handleGeolocationResponse, this), 3750 onError = bind(this._handleGeolocationError, this); 3751 3752 if (options.watch) { 3753 this._locationWatchId = 3754 navigator.geolocation.watchPosition(onResponse, onError, options); 3755 } else { 3756 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 3757 } 3758 return this; 3759 }, 3760 3761 // @method stopLocate(): this 3762 // Stops watching location previously initiated by `map.locate({watch: true})` 3763 // and aborts resetting the map view if map.locate was called with 3764 // `{setView: true}`. 3765 stopLocate: function () { 3766 if (navigator.geolocation && navigator.geolocation.clearWatch) { 3767 navigator.geolocation.clearWatch(this._locationWatchId); 3768 } 3769 if (this._locateOptions) { 3770 this._locateOptions.setView = false; 3771 } 3772 return this; 3773 }, 3774 3775 _handleGeolocationError: function (error) { 3776 if (!this._container._leaflet_id) { return; } 3777 3778 var c = error.code, 3779 message = error.message || 3780 (c === 1 ? 'permission denied' : 3781 (c === 2 ? 'position unavailable' : 'timeout')); 3782 3783 if (this._locateOptions.setView && !this._loaded) { 3784 this.fitWorld(); 3785 } 3786 3787 // @section Location events 3788 // @event locationerror: ErrorEvent 3789 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 3790 this.fire('locationerror', { 3791 code: c, 3792 message: 'Geolocation error: ' + message + '.' 3793 }); 3794 }, 3795 3796 _handleGeolocationResponse: function (pos) { 3797 if (!this._container._leaflet_id) { return; } 3798 3799 var lat = pos.coords.latitude, 3800 lng = pos.coords.longitude, 3801 latlng = new LatLng(lat, lng), 3802 bounds = latlng.toBounds(pos.coords.accuracy * 2), 3803 options = this._locateOptions; 3804 3805 if (options.setView) { 3806 var zoom = this.getBoundsZoom(bounds); 3807 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 3808 } 3809 3810 var data = { 3811 latlng: latlng, 3812 bounds: bounds, 3813 timestamp: pos.timestamp 3814 }; 3815 3816 for (var i in pos.coords) { 3817 if (typeof pos.coords[i] === 'number') { 3818 data[i] = pos.coords[i]; 3819 } 3820 } 3821 3822 // @event locationfound: LocationEvent 3823 // Fired when geolocation (using the [`locate`](#map-locate) method) 3824 // went successfully. 3825 this.fire('locationfound', data); 3826 }, 3827 3828 // TODO Appropriate docs section? 3829 // @section Other Methods 3830 // @method addHandler(name: String, HandlerClass: Function): this 3831 // Adds a new `Handler` to the map, given its name and constructor function. 3832 addHandler: function (name, HandlerClass) { 3833 if (!HandlerClass) { return this; } 3834 3835 var handler = this[name] = new HandlerClass(this); 3836 3837 this._handlers.push(handler); 3838 3839 if (this.options[name]) { 3840 handler.enable(); 3841 } 3842 3843 return this; 3844 }, 3845 3846 // @method remove(): this 3847 // Destroys the map and clears all related event listeners. 3848 remove: function () { 3849 3850 this._initEvents(true); 3851 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); } 3852 3853 if (this._containerId !== this._container._leaflet_id) { 3854 throw new Error('Map container is being reused by another instance'); 3855 } 3856 3857 try { 3858 // throws error in IE6-8 3859 delete this._container._leaflet_id; 3860 delete this._containerId; 3861 } catch (e) { 3862 /*eslint-disable */ 3863 this._container._leaflet_id = undefined; 3864 /* eslint-enable */ 3865 this._containerId = undefined; 3866 } 3867 3868 if (this._locationWatchId !== undefined) { 3869 this.stopLocate(); 3870 } 3871 3872 this._stop(); 3873 3874 remove(this._mapPane); 3875 3876 if (this._clearControlPos) { 3877 this._clearControlPos(); 3878 } 3879 if (this._resizeRequest) { 3880 cancelAnimFrame(this._resizeRequest); 3881 this._resizeRequest = null; 3882 } 3883 3884 this._clearHandlers(); 3885 3886 if (this._loaded) { 3887 // @section Map state change events 3888 // @event unload: Event 3889 // Fired when the map is destroyed with [remove](#map-remove) method. 3890 this.fire('unload'); 3891 } 3892 3893 var i; 3894 for (i in this._layers) { 3895 this._layers[i].remove(); 3896 } 3897 for (i in this._panes) { 3898 remove(this._panes[i]); 3899 } 3900 3901 this._layers = []; 3902 this._panes = []; 3903 delete this._mapPane; 3904 delete this._renderer; 3905 3906 return this; 3907 }, 3908 3909 // @section Other Methods 3910 // @method createPane(name: String, container?: HTMLElement): HTMLElement 3911 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 3912 // then returns it. The pane is created as a child of `container`, or 3913 // as a child of the main map pane if not set. 3914 createPane: function (name, container) { 3915 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3916 pane = create$1('div', className, container || this._mapPane); 3917 3918 if (name) { 3919 this._panes[name] = pane; 3920 } 3921 return pane; 3922 }, 3923 3924 // @section Methods for Getting Map State 3925 3926 // @method getCenter(): LatLng 3927 // Returns the geographical center of the map view 3928 getCenter: function () { 3929 this._checkIfLoaded(); 3930 3931 if (this._lastCenter && !this._moved()) { 3932 return this._lastCenter.clone(); 3933 } 3934 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3935 }, 3936 3937 // @method getZoom(): Number 3938 // Returns the current zoom level of the map view 3939 getZoom: function () { 3940 return this._zoom; 3941 }, 3942 3943 // @method getBounds(): LatLngBounds 3944 // Returns the geographical bounds visible in the current map view 3945 getBounds: function () { 3946 var bounds = this.getPixelBounds(), 3947 sw = this.unproject(bounds.getBottomLeft()), 3948 ne = this.unproject(bounds.getTopRight()); 3949 3950 return new LatLngBounds(sw, ne); 3951 }, 3952 3953 // @method getMinZoom(): Number 3954 // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default. 3955 getMinZoom: function () { 3956 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3957 }, 3958 3959 // @method getMaxZoom(): Number 3960 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3961 getMaxZoom: function () { 3962 return this.options.maxZoom === undefined ? 3963 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3964 this.options.maxZoom; 3965 }, 3966 3967 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 3968 // Returns the maximum zoom level on which the given bounds fit to the map 3969 // view in its entirety. If `inside` (optional) is set to `true`, the method 3970 // instead returns the minimum zoom level on which the map view fits into 3971 // the given bounds in its entirety. 3972 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3973 bounds = toLatLngBounds(bounds); 3974 padding = toPoint(padding || [0, 0]); 3975 3976 var zoom = this.getZoom() || 0, 3977 min = this.getMinZoom(), 3978 max = this.getMaxZoom(), 3979 nw = bounds.getNorthWest(), 3980 se = bounds.getSouthEast(), 3981 size = this.getSize().subtract(padding), 3982 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3983 snap = Browser.any3d ? this.options.zoomSnap : 1, 3984 scalex = size.x / boundsSize.x, 3985 scaley = size.y / boundsSize.y, 3986 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 3987 3988 zoom = this.getScaleZoom(scale, zoom); 3989 3990 if (snap) { 3991 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3992 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3993 } 3994 3995 return Math.max(min, Math.min(max, zoom)); 3996 }, 3997 3998 // @method getSize(): Point 3999 // Returns the current size of the map container (in pixels). 4000 getSize: function () { 4001 if (!this._size || this._sizeChanged) { 4002 this._size = new Point( 4003 this._container.clientWidth || 0, 4004 this._container.clientHeight || 0); 4005 4006 this._sizeChanged = false; 4007 } 4008 return this._size.clone(); 4009 }, 4010 4011 // @method getPixelBounds(): Bounds 4012 // Returns the bounds of the current map view in projected pixel 4013 // coordinates (sometimes useful in layer and overlay implementations). 4014 getPixelBounds: function (center, zoom) { 4015 var topLeftPoint = this._getTopLeftPoint(center, zoom); 4016 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 4017 }, 4018 4019 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 4020 // the map pane? "left point of the map layer" can be confusing, specially 4021 // since there can be negative offsets. 4022 // @method getPixelOrigin(): Point 4023 // Returns the projected pixel coordinates of the top left point of 4024 // the map layer (useful in custom layer and overlay implementations). 4025 getPixelOrigin: function () { 4026 this._checkIfLoaded(); 4027 return this._pixelOrigin; 4028 }, 4029 4030 // @method getPixelWorldBounds(zoom?: Number): Bounds 4031 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 4032 // If `zoom` is omitted, the map's current zoom level is used. 4033 getPixelWorldBounds: function (zoom) { 4034 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 4035 }, 4036 4037 // @section Other Methods 4038 4039 // @method getPane(pane: String|HTMLElement): HTMLElement 4040 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 4041 getPane: function (pane) { 4042 return typeof pane === 'string' ? this._panes[pane] : pane; 4043 }, 4044 4045 // @method getPanes(): Object 4046 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 4047 // the panes as values. 4048 getPanes: function () { 4049 return this._panes; 4050 }, 4051 4052 // @method getContainer: HTMLElement 4053 // Returns the HTML element that contains the map. 4054 getContainer: function () { 4055 return this._container; 4056 }, 4057 4058 4059 // @section Conversion Methods 4060 4061 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 4062 // Returns the scale factor to be applied to a map transition from zoom level 4063 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 4064 getZoomScale: function (toZoom, fromZoom) { 4065 // TODO replace with universal implementation after refactoring projections 4066 var crs = this.options.crs; 4067 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 4068 return crs.scale(toZoom) / crs.scale(fromZoom); 4069 }, 4070 4071 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 4072 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 4073 // level and everything is scaled by a factor of `scale`. Inverse of 4074 // [`getZoomScale`](#map-getZoomScale). 4075 getScaleZoom: function (scale, fromZoom) { 4076 var crs = this.options.crs; 4077 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 4078 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 4079 return isNaN(zoom) ? Infinity : zoom; 4080 }, 4081 4082 // @method project(latlng: LatLng, zoom: Number): Point 4083 // Projects a geographical coordinate `LatLng` according to the projection 4084 // of the map's CRS, then scales it according to `zoom` and the CRS's 4085 // `Transformation`. The result is pixel coordinate relative to 4086 // the CRS origin. 4087 project: function (latlng, zoom) { 4088 zoom = zoom === undefined ? this._zoom : zoom; 4089 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 4090 }, 4091 4092 // @method unproject(point: Point, zoom: Number): LatLng 4093 // Inverse of [`project`](#map-project). 4094 unproject: function (point, zoom) { 4095 zoom = zoom === undefined ? this._zoom : zoom; 4096 return this.options.crs.pointToLatLng(toPoint(point), zoom); 4097 }, 4098 4099 // @method layerPointToLatLng(point: Point): LatLng 4100 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4101 // returns the corresponding geographical coordinate (for the current zoom level). 4102 layerPointToLatLng: function (point) { 4103 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 4104 return this.unproject(projectedPoint); 4105 }, 4106 4107 // @method latLngToLayerPoint(latlng: LatLng): Point 4108 // Given a geographical coordinate, returns the corresponding pixel coordinate 4109 // relative to the [origin pixel](#map-getpixelorigin). 4110 latLngToLayerPoint: function (latlng) { 4111 var projectedPoint = this.project(toLatLng(latlng))._round(); 4112 return projectedPoint._subtract(this.getPixelOrigin()); 4113 }, 4114 4115 // @method wrapLatLng(latlng: LatLng): LatLng 4116 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 4117 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 4118 // CRS's bounds. 4119 // By default this means longitude is wrapped around the dateline so its 4120 // value is between -180 and +180 degrees. 4121 wrapLatLng: function (latlng) { 4122 return this.options.crs.wrapLatLng(toLatLng(latlng)); 4123 }, 4124 4125 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 4126 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 4127 // its center is within the CRS's bounds. 4128 // By default this means the center longitude is wrapped around the dateline so its 4129 // value is between -180 and +180 degrees, and the majority of the bounds 4130 // overlaps the CRS's bounds. 4131 wrapLatLngBounds: function (latlng) { 4132 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 4133 }, 4134 4135 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 4136 // Returns the distance between two geographical coordinates according to 4137 // the map's CRS. By default this measures distance in meters. 4138 distance: function (latlng1, latlng2) { 4139 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 4140 }, 4141 4142 // @method containerPointToLayerPoint(point: Point): Point 4143 // Given a pixel coordinate relative to the map container, returns the corresponding 4144 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 4145 containerPointToLayerPoint: function (point) { // (Point) 4146 return toPoint(point).subtract(this._getMapPanePos()); 4147 }, 4148 4149 // @method layerPointToContainerPoint(point: Point): Point 4150 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4151 // returns the corresponding pixel coordinate relative to the map container. 4152 layerPointToContainerPoint: function (point) { // (Point) 4153 return toPoint(point).add(this._getMapPanePos()); 4154 }, 4155 4156 // @method containerPointToLatLng(point: Point): LatLng 4157 // Given a pixel coordinate relative to the map container, returns 4158 // the corresponding geographical coordinate (for the current zoom level). 4159 containerPointToLatLng: function (point) { 4160 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4161 return this.layerPointToLatLng(layerPoint); 4162 }, 4163 4164 // @method latLngToContainerPoint(latlng: LatLng): Point 4165 // Given a geographical coordinate, returns the corresponding pixel coordinate 4166 // relative to the map container. 4167 latLngToContainerPoint: function (latlng) { 4168 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4169 }, 4170 4171 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4172 // Given a MouseEvent object, returns the pixel coordinate relative to the 4173 // map container where the event took place. 4174 mouseEventToContainerPoint: function (e) { 4175 return getMousePosition(e, this._container); 4176 }, 4177 4178 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4179 // Given a MouseEvent object, returns the pixel coordinate relative to 4180 // the [origin pixel](#map-getpixelorigin) where the event took place. 4181 mouseEventToLayerPoint: function (e) { 4182 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4183 }, 4184 4185 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4186 // Given a MouseEvent object, returns geographical coordinate where the 4187 // event took place. 4188 mouseEventToLatLng: function (e) { // (MouseEvent) 4189 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4190 }, 4191 4192 4193 // map initialization methods 4194 4195 _initContainer: function (id) { 4196 var container = this._container = get(id); 4197 4198 if (!container) { 4199 throw new Error('Map container not found.'); 4200 } else if (container._leaflet_id) { 4201 throw new Error('Map container is already initialized.'); 4202 } 4203 4204 on(container, 'scroll', this._onScroll, this); 4205 this._containerId = stamp(container); 4206 }, 4207 4208 _initLayout: function () { 4209 var container = this._container; 4210 4211 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d; 4212 4213 addClass(container, 'leaflet-container' + 4214 (Browser.touch ? ' leaflet-touch' : '') + 4215 (Browser.retina ? ' leaflet-retina' : '') + 4216 (Browser.ielt9 ? ' leaflet-oldie' : '') + 4217 (Browser.safari ? ' leaflet-safari' : '') + 4218 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4219 4220 var position = getStyle(container, 'position'); 4221 4222 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') { 4223 container.style.position = 'relative'; 4224 } 4225 4226 this._initPanes(); 4227 4228 if (this._initControlPos) { 4229 this._initControlPos(); 4230 } 4231 }, 4232 4233 _initPanes: function () { 4234 var panes = this._panes = {}; 4235 this._paneRenderers = {}; 4236 4237 // @section 4238 // 4239 // Panes are DOM elements used to control the ordering of layers on the map. You 4240 // can access panes with [`map.getPane`](#map-getpane) or 4241 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4242 // [`map.createPane`](#map-createpane) method. 4243 // 4244 // Every map has the following default panes that differ only in zIndex. 4245 // 4246 // @pane mapPane: HTMLElement = 'auto' 4247 // Pane that contains all other map panes 4248 4249 this._mapPane = this.createPane('mapPane', this._container); 4250 setPosition(this._mapPane, new Point(0, 0)); 4251 4252 // @pane tilePane: HTMLElement = 200 4253 // Pane for `GridLayer`s and `TileLayer`s 4254 this.createPane('tilePane'); 4255 // @pane overlayPane: HTMLElement = 400 4256 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4257 this.createPane('overlayPane'); 4258 // @pane shadowPane: HTMLElement = 500 4259 // Pane for overlay shadows (e.g. `Marker` shadows) 4260 this.createPane('shadowPane'); 4261 // @pane markerPane: HTMLElement = 600 4262 // Pane for `Icon`s of `Marker`s 4263 this.createPane('markerPane'); 4264 // @pane tooltipPane: HTMLElement = 650 4265 // Pane for `Tooltip`s. 4266 this.createPane('tooltipPane'); 4267 // @pane popupPane: HTMLElement = 700 4268 // Pane for `Popup`s. 4269 this.createPane('popupPane'); 4270 4271 if (!this.options.markerZoomAnimation) { 4272 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4273 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4274 } 4275 }, 4276 4277 4278 // private methods that modify map state 4279 4280 // @section Map state change events 4281 _resetView: function (center, zoom, noMoveStart) { 4282 setPosition(this._mapPane, new Point(0, 0)); 4283 4284 var loading = !this._loaded; 4285 this._loaded = true; 4286 zoom = this._limitZoom(zoom); 4287 4288 this.fire('viewprereset'); 4289 4290 var zoomChanged = this._zoom !== zoom; 4291 this 4292 ._moveStart(zoomChanged, noMoveStart) 4293 ._move(center, zoom) 4294 ._moveEnd(zoomChanged); 4295 4296 // @event viewreset: Event 4297 // Fired when the map needs to redraw its content (this usually happens 4298 // on map zoom or load). Very useful for creating custom overlays. 4299 this.fire('viewreset'); 4300 4301 // @event load: Event 4302 // Fired when the map is initialized (when its center and zoom are set 4303 // for the first time). 4304 if (loading) { 4305 this.fire('load'); 4306 } 4307 }, 4308 4309 _moveStart: function (zoomChanged, noMoveStart) { 4310 // @event zoomstart: Event 4311 // Fired when the map zoom is about to change (e.g. before zoom animation). 4312 // @event movestart: Event 4313 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4314 if (zoomChanged) { 4315 this.fire('zoomstart'); 4316 } 4317 if (!noMoveStart) { 4318 this.fire('movestart'); 4319 } 4320 return this; 4321 }, 4322 4323 _move: function (center, zoom, data, supressEvent) { 4324 if (zoom === undefined) { 4325 zoom = this._zoom; 4326 } 4327 var zoomChanged = this._zoom !== zoom; 4328 4329 this._zoom = zoom; 4330 this._lastCenter = center; 4331 this._pixelOrigin = this._getNewPixelOrigin(center); 4332 4333 if (!supressEvent) { 4334 // @event zoom: Event 4335 // Fired repeatedly during any change in zoom level, 4336 // including zoom and fly animations. 4337 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4338 this.fire('zoom', data); 4339 } 4340 4341 // @event move: Event 4342 // Fired repeatedly during any movement of the map, 4343 // including pan and fly animations. 4344 this.fire('move', data); 4345 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530 4346 this.fire('zoom', data); 4347 } 4348 return this; 4349 }, 4350 4351 _moveEnd: function (zoomChanged) { 4352 // @event zoomend: Event 4353 // Fired when the map zoom changed, after any animations. 4354 if (zoomChanged) { 4355 this.fire('zoomend'); 4356 } 4357 4358 // @event moveend: Event 4359 // Fired when the center of the map stops changing 4360 // (e.g. user stopped dragging the map or after non-centered zoom). 4361 return this.fire('moveend'); 4362 }, 4363 4364 _stop: function () { 4365 cancelAnimFrame(this._flyToFrame); 4366 if (this._panAnim) { 4367 this._panAnim.stop(); 4368 } 4369 return this; 4370 }, 4371 4372 _rawPanBy: function (offset) { 4373 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4374 }, 4375 4376 _getZoomSpan: function () { 4377 return this.getMaxZoom() - this.getMinZoom(); 4378 }, 4379 4380 _panInsideMaxBounds: function () { 4381 if (!this._enforcingBounds) { 4382 this.panInsideBounds(this.options.maxBounds); 4383 } 4384 }, 4385 4386 _checkIfLoaded: function () { 4387 if (!this._loaded) { 4388 throw new Error('Set map center and zoom first.'); 4389 } 4390 }, 4391 4392 // DOM event handling 4393 4394 // @section Interaction events 4395 _initEvents: function (remove) { 4396 this._targets = {}; 4397 this._targets[stamp(this._container)] = this; 4398 4399 var onOff = remove ? off : on; 4400 4401 // @event click: MouseEvent 4402 // Fired when the user clicks (or taps) the map. 4403 // @event dblclick: MouseEvent 4404 // Fired when the user double-clicks (or double-taps) the map. 4405 // @event mousedown: MouseEvent 4406 // Fired when the user pushes the mouse button on the map. 4407 // @event mouseup: MouseEvent 4408 // Fired when the user releases the mouse button on the map. 4409 // @event mouseover: MouseEvent 4410 // Fired when the mouse enters the map. 4411 // @event mouseout: MouseEvent 4412 // Fired when the mouse leaves the map. 4413 // @event mousemove: MouseEvent 4414 // Fired while the mouse moves over the map. 4415 // @event contextmenu: MouseEvent 4416 // Fired when the user pushes the right mouse button on the map, prevents 4417 // default browser context menu from showing if there are listeners on 4418 // this event. Also fired on mobile when the user holds a single touch 4419 // for a second (also called long press). 4420 // @event keypress: KeyboardEvent 4421 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused. 4422 // @event keydown: KeyboardEvent 4423 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event, 4424 // the `keydown` event is fired for keys that produce a character value and for keys 4425 // that do not produce a character value. 4426 // @event keyup: KeyboardEvent 4427 // Fired when the user releases a key from the keyboard while the map is focused. 4428 onOff(this._container, 'click dblclick mousedown mouseup ' + 4429 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this); 4430 4431 if (this.options.trackResize) { 4432 onOff(window, 'resize', this._onResize, this); 4433 } 4434 4435 if (Browser.any3d && this.options.transform3DLimit) { 4436 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4437 } 4438 }, 4439 4440 _onResize: function () { 4441 cancelAnimFrame(this._resizeRequest); 4442 this._resizeRequest = requestAnimFrame( 4443 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4444 }, 4445 4446 _onScroll: function () { 4447 this._container.scrollTop = 0; 4448 this._container.scrollLeft = 0; 4449 }, 4450 4451 _onMoveEnd: function () { 4452 var pos = this._getMapPanePos(); 4453 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4454 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4455 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/ 4456 this._resetView(this.getCenter(), this.getZoom()); 4457 } 4458 }, 4459 4460 _findEventTargets: function (e, type) { 4461 var targets = [], 4462 target, 4463 isHover = type === 'mouseout' || type === 'mouseover', 4464 src = e.target || e.srcElement, 4465 dragging = false; 4466 4467 while (src) { 4468 target = this._targets[stamp(src)]; 4469 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) { 4470 // Prevent firing click after you just dragged an object. 4471 dragging = true; 4472 break; 4473 } 4474 if (target && target.listens(type, true)) { 4475 if (isHover && !isExternalTarget(src, e)) { break; } 4476 targets.push(target); 4477 if (isHover) { break; } 4478 } 4479 if (src === this._container) { break; } 4480 src = src.parentNode; 4481 } 4482 if (!targets.length && !dragging && !isHover && this.listens(type, true)) { 4483 targets = [this]; 4484 } 4485 return targets; 4486 }, 4487 4488 _isClickDisabled: function (el) { 4489 while (el && el !== this._container) { 4490 if (el['_leaflet_disable_click']) { return true; } 4491 el = el.parentNode; 4492 } 4493 }, 4494 4495 _handleDOMEvent: function (e) { 4496 var el = (e.target || e.srcElement); 4497 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) { 4498 return; 4499 } 4500 4501 var type = e.type; 4502 4503 if (type === 'mousedown') { 4504 // prevents outline when clicking on keyboard-focusable element 4505 preventOutline(el); 4506 } 4507 4508 this._fireDOMEvent(e, type); 4509 }, 4510 4511 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4512 4513 _fireDOMEvent: function (e, type, canvasTargets) { 4514 4515 if (e.type === 'click') { 4516 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4517 // @event preclick: MouseEvent 4518 // Fired before mouse click on the map (sometimes useful when you 4519 // want something to happen on click before any existing click 4520 // handlers start running). 4521 var synth = extend({}, e); 4522 synth.type = 'preclick'; 4523 this._fireDOMEvent(synth, synth.type, canvasTargets); 4524 } 4525 4526 // Find the layer the event is propagating from and its parents. 4527 var targets = this._findEventTargets(e, type); 4528 4529 if (canvasTargets) { 4530 var filtered = []; // pick only targets with listeners 4531 for (var i = 0; i < canvasTargets.length; i++) { 4532 if (canvasTargets[i].listens(type, true)) { 4533 filtered.push(canvasTargets[i]); 4534 } 4535 } 4536 targets = filtered.concat(targets); 4537 } 4538 4539 if (!targets.length) { return; } 4540 4541 if (type === 'contextmenu') { 4542 preventDefault(e); 4543 } 4544 4545 var target = targets[0]; 4546 var data = { 4547 originalEvent: e 4548 }; 4549 4550 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') { 4551 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4552 data.containerPoint = isMarker ? 4553 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4554 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4555 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4556 } 4557 4558 for (i = 0; i < targets.length; i++) { 4559 targets[i].fire(type, data, true); 4560 if (data.originalEvent._stopped || 4561 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4562 } 4563 }, 4564 4565 _draggableMoved: function (obj) { 4566 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4567 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4568 }, 4569 4570 _clearHandlers: function () { 4571 for (var i = 0, len = this._handlers.length; i < len; i++) { 4572 this._handlers[i].disable(); 4573 } 4574 }, 4575 4576 // @section Other Methods 4577 4578 // @method whenReady(fn: Function, context?: Object): this 4579 // Runs the given function `fn` when the map gets initialized with 4580 // a view (center and zoom) and at least one layer, or immediately 4581 // if it's already initialized, optionally passing a function context. 4582 whenReady: function (callback, context) { 4583 if (this._loaded) { 4584 callback.call(context || this, {target: this}); 4585 } else { 4586 this.on('load', callback, context); 4587 } 4588 return this; 4589 }, 4590 4591 4592 // private methods for getting map state 4593 4594 _getMapPanePos: function () { 4595 return getPosition(this._mapPane) || new Point(0, 0); 4596 }, 4597 4598 _moved: function () { 4599 var pos = this._getMapPanePos(); 4600 return pos && !pos.equals([0, 0]); 4601 }, 4602 4603 _getTopLeftPoint: function (center, zoom) { 4604 var pixelOrigin = center && zoom !== undefined ? 4605 this._getNewPixelOrigin(center, zoom) : 4606 this.getPixelOrigin(); 4607 return pixelOrigin.subtract(this._getMapPanePos()); 4608 }, 4609 4610 _getNewPixelOrigin: function (center, zoom) { 4611 var viewHalf = this.getSize()._divideBy(2); 4612 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4613 }, 4614 4615 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4616 var topLeft = this._getNewPixelOrigin(center, zoom); 4617 return this.project(latlng, zoom)._subtract(topLeft); 4618 }, 4619 4620 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4621 var topLeft = this._getNewPixelOrigin(center, zoom); 4622 return toBounds([ 4623 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4624 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4625 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4626 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4627 ]); 4628 }, 4629 4630 // layer point of the current center 4631 _getCenterLayerPoint: function () { 4632 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4633 }, 4634 4635 // offset of the specified place to the current center in pixels 4636 _getCenterOffset: function (latlng) { 4637 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4638 }, 4639 4640 // adjust center for view to get inside bounds 4641 _limitCenter: function (center, zoom, bounds) { 4642 4643 if (!bounds) { return center; } 4644 4645 var centerPoint = this.project(center, zoom), 4646 viewHalf = this.getSize().divideBy(2), 4647 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4648 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4649 4650 // If offset is less than a pixel, ignore. 4651 // This prevents unstable projections from getting into 4652 // an infinite loop of tiny offsets. 4653 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) { 4654 return center; 4655 } 4656 4657 return this.unproject(centerPoint.add(offset), zoom); 4658 }, 4659 4660 // adjust offset for view to get inside bounds 4661 _limitOffset: function (offset, bounds) { 4662 if (!bounds) { return offset; } 4663 4664 var viewBounds = this.getPixelBounds(), 4665 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4666 4667 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4668 }, 4669 4670 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4671 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4672 var projectedMaxBounds = toBounds( 4673 this.project(maxBounds.getNorthEast(), zoom), 4674 this.project(maxBounds.getSouthWest(), zoom) 4675 ), 4676 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4677 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4678 4679 dx = this._rebound(minOffset.x, -maxOffset.x), 4680 dy = this._rebound(minOffset.y, -maxOffset.y); 4681 4682 return new Point(dx, dy); 4683 }, 4684 4685 _rebound: function (left, right) { 4686 return left + right > 0 ? 4687 Math.round(left - right) / 2 : 4688 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4689 }, 4690 4691 _limitZoom: function (zoom) { 4692 var min = this.getMinZoom(), 4693 max = this.getMaxZoom(), 4694 snap = Browser.any3d ? this.options.zoomSnap : 1; 4695 if (snap) { 4696 zoom = Math.round(zoom / snap) * snap; 4697 } 4698 return Math.max(min, Math.min(max, zoom)); 4699 }, 4700 4701 _onPanTransitionStep: function () { 4702 this.fire('move'); 4703 }, 4704 4705 _onPanTransitionEnd: function () { 4706 removeClass(this._mapPane, 'leaflet-pan-anim'); 4707 this.fire('moveend'); 4708 }, 4709 4710 _tryAnimatedPan: function (center, options) { 4711 // difference between the new and current centers in pixels 4712 var offset = this._getCenterOffset(center)._trunc(); 4713 4714 // don't animate too far unless animate: true specified in options 4715 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4716 4717 this.panBy(offset, options); 4718 4719 return true; 4720 }, 4721 4722 _createAnimProxy: function () { 4723 4724 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4725 this._panes.mapPane.appendChild(proxy); 4726 4727 this.on('zoomanim', function (e) { 4728 var prop = TRANSFORM, 4729 transform = this._proxy.style[prop]; 4730 4731 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4732 4733 // workaround for case when transform is the same and so transitionend event is not fired 4734 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4735 this._onZoomTransitionEnd(); 4736 } 4737 }, this); 4738 4739 this.on('load moveend', this._animMoveEnd, this); 4740 4741 this._on('unload', this._destroyAnimProxy, this); 4742 }, 4743 4744 _destroyAnimProxy: function () { 4745 remove(this._proxy); 4746 this.off('load moveend', this._animMoveEnd, this); 4747 delete this._proxy; 4748 }, 4749 4750 _animMoveEnd: function () { 4751 var c = this.getCenter(), 4752 z = this.getZoom(); 4753 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 4754 }, 4755 4756 _catchTransitionEnd: function (e) { 4757 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 4758 this._onZoomTransitionEnd(); 4759 } 4760 }, 4761 4762 _nothingToAnimate: function () { 4763 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 4764 }, 4765 4766 _tryAnimatedZoom: function (center, zoom, options) { 4767 4768 if (this._animatingZoom) { return true; } 4769 4770 options = options || {}; 4771 4772 // don't animate if disabled, not supported or zoom difference is too large 4773 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 4774 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 4775 4776 // offset is the pixel coords of the zoom origin relative to the current center 4777 var scale = this.getZoomScale(zoom), 4778 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 4779 4780 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 4781 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 4782 4783 requestAnimFrame(function () { 4784 this 4785 ._moveStart(true, options.noMoveStart || false) 4786 ._animateZoom(center, zoom, true); 4787 }, this); 4788 4789 return true; 4790 }, 4791 4792 _animateZoom: function (center, zoom, startAnim, noUpdate) { 4793 if (!this._mapPane) { return; } 4794 4795 if (startAnim) { 4796 this._animatingZoom = true; 4797 4798 // remember what center/zoom to set after animation 4799 this._animateToCenter = center; 4800 this._animateToZoom = zoom; 4801 4802 addClass(this._mapPane, 'leaflet-zoom-anim'); 4803 } 4804 4805 // @section Other Events 4806 // @event zoomanim: ZoomAnimEvent 4807 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom. 4808 this.fire('zoomanim', { 4809 center: center, 4810 zoom: zoom, 4811 noUpdate: noUpdate 4812 }); 4813 4814 if (!this._tempFireZoomEvent) { 4815 this._tempFireZoomEvent = this._zoom !== this._animateToZoom; 4816 } 4817 4818 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 4819 4820 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 4821 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 4822 }, 4823 4824 _onZoomTransitionEnd: function () { 4825 if (!this._animatingZoom) { return; } 4826 4827 if (this._mapPane) { 4828 removeClass(this._mapPane, 'leaflet-zoom-anim'); 4829 } 4830 4831 this._animatingZoom = false; 4832 4833 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 4834 4835 if (this._tempFireZoomEvent) { 4836 this.fire('zoom'); 4837 } 4838 delete this._tempFireZoomEvent; 4839 4840 this.fire('move'); 4841 4842 this._moveEnd(true); 4843 } 4844}); 4845 4846// @section 4847 4848// @factory L.map(id: String, options?: Map options) 4849// Instantiates a map object given the DOM ID of a `<div>` element 4850// and optionally an object literal with `Map options`. 4851// 4852// @alternative 4853// @factory L.map(el: HTMLElement, options?: Map options) 4854// Instantiates a map object given an instance of a `<div>` HTML element 4855// and optionally an object literal with `Map options`. 4856function createMap(id, options) { 4857 return new Map(id, options); 4858} 4859 4860/* 4861 * @class Control 4862 * @aka L.Control 4863 * @inherits Class 4864 * 4865 * L.Control is a base class for implementing map controls. Handles positioning. 4866 * All other controls extend from this class. 4867 */ 4868 4869var Control = Class.extend({ 4870 // @section 4871 // @aka Control Options 4872 options: { 4873 // @option position: String = 'topright' 4874 // The position of the control (one of the map corners). Possible values are `'topleft'`, 4875 // `'topright'`, `'bottomleft'` or `'bottomright'` 4876 position: 'topright' 4877 }, 4878 4879 initialize: function (options) { 4880 setOptions(this, options); 4881 }, 4882 4883 /* @section 4884 * Classes extending L.Control will inherit the following methods: 4885 * 4886 * @method getPosition: string 4887 * Returns the position of the control. 4888 */ 4889 getPosition: function () { 4890 return this.options.position; 4891 }, 4892 4893 // @method setPosition(position: string): this 4894 // Sets the position of the control. 4895 setPosition: function (position) { 4896 var map = this._map; 4897 4898 if (map) { 4899 map.removeControl(this); 4900 } 4901 4902 this.options.position = position; 4903 4904 if (map) { 4905 map.addControl(this); 4906 } 4907 4908 return this; 4909 }, 4910 4911 // @method getContainer: HTMLElement 4912 // Returns the HTMLElement that contains the control. 4913 getContainer: function () { 4914 return this._container; 4915 }, 4916 4917 // @method addTo(map: Map): this 4918 // Adds the control to the given map. 4919 addTo: function (map) { 4920 this.remove(); 4921 this._map = map; 4922 4923 var container = this._container = this.onAdd(map), 4924 pos = this.getPosition(), 4925 corner = map._controlCorners[pos]; 4926 4927 addClass(container, 'leaflet-control'); 4928 4929 if (pos.indexOf('bottom') !== -1) { 4930 corner.insertBefore(container, corner.firstChild); 4931 } else { 4932 corner.appendChild(container); 4933 } 4934 4935 this._map.on('unload', this.remove, this); 4936 4937 return this; 4938 }, 4939 4940 // @method remove: this 4941 // Removes the control from the map it is currently active on. 4942 remove: function () { 4943 if (!this._map) { 4944 return this; 4945 } 4946 4947 remove(this._container); 4948 4949 if (this.onRemove) { 4950 this.onRemove(this._map); 4951 } 4952 4953 this._map.off('unload', this.remove, this); 4954 this._map = null; 4955 4956 return this; 4957 }, 4958 4959 _refocusOnMap: function (e) { 4960 // if map exists and event is not a keyboard event 4961 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 4962 this._map.getContainer().focus(); 4963 } 4964 } 4965}); 4966 4967var control = function (options) { 4968 return new Control(options); 4969}; 4970 4971/* @section Extension methods 4972 * @uninheritable 4973 * 4974 * Every control should extend from `L.Control` and (re-)implement the following methods. 4975 * 4976 * @method onAdd(map: Map): HTMLElement 4977 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 4978 * 4979 * @method onRemove(map: Map) 4980 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 4981 */ 4982 4983/* @namespace Map 4984 * @section Methods for Layers and Controls 4985 */ 4986Map.include({ 4987 // @method addControl(control: Control): this 4988 // Adds the given control to the map 4989 addControl: function (control) { 4990 control.addTo(this); 4991 return this; 4992 }, 4993 4994 // @method removeControl(control: Control): this 4995 // Removes the given control from the map 4996 removeControl: function (control) { 4997 control.remove(); 4998 return this; 4999 }, 5000 5001 _initControlPos: function () { 5002 var corners = this._controlCorners = {}, 5003 l = 'leaflet-', 5004 container = this._controlContainer = 5005 create$1('div', l + 'control-container', this._container); 5006 5007 function createCorner(vSide, hSide) { 5008 var className = l + vSide + ' ' + l + hSide; 5009 5010 corners[vSide + hSide] = create$1('div', className, container); 5011 } 5012 5013 createCorner('top', 'left'); 5014 createCorner('top', 'right'); 5015 createCorner('bottom', 'left'); 5016 createCorner('bottom', 'right'); 5017 }, 5018 5019 _clearControlPos: function () { 5020 for (var i in this._controlCorners) { 5021 remove(this._controlCorners[i]); 5022 } 5023 remove(this._controlContainer); 5024 delete this._controlCorners; 5025 delete this._controlContainer; 5026 } 5027}); 5028 5029/* 5030 * @class Control.Layers 5031 * @aka L.Control.Layers 5032 * @inherits Control 5033 * 5034 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`. 5035 * 5036 * @example 5037 * 5038 * ```js 5039 * var baseLayers = { 5040 * "Mapbox": mapbox, 5041 * "OpenStreetMap": osm 5042 * }; 5043 * 5044 * var overlays = { 5045 * "Marker": marker, 5046 * "Roads": roadsLayer 5047 * }; 5048 * 5049 * L.control.layers(baseLayers, overlays).addTo(map); 5050 * ``` 5051 * 5052 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 5053 * 5054 * ```js 5055 * { 5056 * "<someName1>": layer1, 5057 * "<someName2>": layer2 5058 * } 5059 * ``` 5060 * 5061 * The layer names can contain HTML, which allows you to add additional styling to the items: 5062 * 5063 * ```js 5064 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer} 5065 * ``` 5066 */ 5067 5068var Layers = Control.extend({ 5069 // @section 5070 // @aka Control.Layers options 5071 options: { 5072 // @option collapsed: Boolean = true 5073 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation. 5074 collapsed: true, 5075 position: 'topright', 5076 5077 // @option autoZIndex: Boolean = true 5078 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off. 5079 autoZIndex: true, 5080 5081 // @option hideSingleBase: Boolean = false 5082 // If `true`, the base layers in the control will be hidden when there is only one. 5083 hideSingleBase: false, 5084 5085 // @option sortLayers: Boolean = false 5086 // Whether to sort the layers. When `false`, layers will keep the order 5087 // in which they were added to the control. 5088 sortLayers: false, 5089 5090 // @option sortFunction: Function = * 5091 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 5092 // that will be used for sorting the layers, when `sortLayers` is `true`. 5093 // The function receives both the `L.Layer` instances and their names, as in 5094 // `sortFunction(layerA, layerB, nameA, nameB)`. 5095 // By default, it sorts layers alphabetically by their name. 5096 sortFunction: function (layerA, layerB, nameA, nameB) { 5097 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 5098 } 5099 }, 5100 5101 initialize: function (baseLayers, overlays, options) { 5102 setOptions(this, options); 5103 5104 this._layerControlInputs = []; 5105 this._layers = []; 5106 this._lastZIndex = 0; 5107 this._handlingClick = false; 5108 this._preventClick = false; 5109 5110 for (var i in baseLayers) { 5111 this._addLayer(baseLayers[i], i); 5112 } 5113 5114 for (i in overlays) { 5115 this._addLayer(overlays[i], i, true); 5116 } 5117 }, 5118 5119 onAdd: function (map) { 5120 this._initLayout(); 5121 this._update(); 5122 5123 this._map = map; 5124 map.on('zoomend', this._checkDisabledLayers, this); 5125 5126 for (var i = 0; i < this._layers.length; i++) { 5127 this._layers[i].layer.on('add remove', this._onLayerChange, this); 5128 } 5129 5130 return this._container; 5131 }, 5132 5133 addTo: function (map) { 5134 Control.prototype.addTo.call(this, map); 5135 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 5136 return this._expandIfNotCollapsed(); 5137 }, 5138 5139 onRemove: function () { 5140 this._map.off('zoomend', this._checkDisabledLayers, this); 5141 5142 for (var i = 0; i < this._layers.length; i++) { 5143 this._layers[i].layer.off('add remove', this._onLayerChange, this); 5144 } 5145 }, 5146 5147 // @method addBaseLayer(layer: Layer, name: String): this 5148 // Adds a base layer (radio button entry) with the given name to the control. 5149 addBaseLayer: function (layer, name) { 5150 this._addLayer(layer, name); 5151 return (this._map) ? this._update() : this; 5152 }, 5153 5154 // @method addOverlay(layer: Layer, name: String): this 5155 // Adds an overlay (checkbox entry) with the given name to the control. 5156 addOverlay: function (layer, name) { 5157 this._addLayer(layer, name, true); 5158 return (this._map) ? this._update() : this; 5159 }, 5160 5161 // @method removeLayer(layer: Layer): this 5162 // Remove the given layer from the control. 5163 removeLayer: function (layer) { 5164 layer.off('add remove', this._onLayerChange, this); 5165 5166 var obj = this._getLayer(stamp(layer)); 5167 if (obj) { 5168 this._layers.splice(this._layers.indexOf(obj), 1); 5169 } 5170 return (this._map) ? this._update() : this; 5171 }, 5172 5173 // @method expand(): this 5174 // Expand the control container if collapsed. 5175 expand: function () { 5176 addClass(this._container, 'leaflet-control-layers-expanded'); 5177 this._section.style.height = null; 5178 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 5179 if (acceptableHeight < this._section.clientHeight) { 5180 addClass(this._section, 'leaflet-control-layers-scrollbar'); 5181 this._section.style.height = acceptableHeight + 'px'; 5182 } else { 5183 removeClass(this._section, 'leaflet-control-layers-scrollbar'); 5184 } 5185 this._checkDisabledLayers(); 5186 return this; 5187 }, 5188 5189 // @method collapse(): this 5190 // Collapse the control container if expanded. 5191 collapse: function () { 5192 removeClass(this._container, 'leaflet-control-layers-expanded'); 5193 return this; 5194 }, 5195 5196 _initLayout: function () { 5197 var className = 'leaflet-control-layers', 5198 container = this._container = create$1('div', className), 5199 collapsed = this.options.collapsed; 5200 5201 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 5202 container.setAttribute('aria-haspopup', true); 5203 5204 disableClickPropagation(container); 5205 disableScrollPropagation(container); 5206 5207 var section = this._section = create$1('section', className + '-list'); 5208 5209 if (collapsed) { 5210 this._map.on('click', this.collapse, this); 5211 5212 on(container, { 5213 mouseenter: this._expandSafely, 5214 mouseleave: this.collapse 5215 }, this); 5216 } 5217 5218 var link = this._layersLink = create$1('a', className + '-toggle', container); 5219 link.href = '#'; 5220 link.title = 'Layers'; 5221 link.setAttribute('role', 'button'); 5222 5223 on(link, { 5224 keydown: function (e) { 5225 if (e.keyCode === 13) { 5226 this._expandSafely(); 5227 } 5228 }, 5229 // Certain screen readers intercept the key event and instead send a click event 5230 click: function (e) { 5231 preventDefault(e); 5232 this._expandSafely(); 5233 } 5234 }, this); 5235 5236 if (!collapsed) { 5237 this.expand(); 5238 } 5239 5240 this._baseLayersList = create$1('div', className + '-base', section); 5241 this._separator = create$1('div', className + '-separator', section); 5242 this._overlaysList = create$1('div', className + '-overlays', section); 5243 5244 container.appendChild(section); 5245 }, 5246 5247 _getLayer: function (id) { 5248 for (var i = 0; i < this._layers.length; i++) { 5249 5250 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5251 return this._layers[i]; 5252 } 5253 } 5254 }, 5255 5256 _addLayer: function (layer, name, overlay) { 5257 if (this._map) { 5258 layer.on('add remove', this._onLayerChange, this); 5259 } 5260 5261 this._layers.push({ 5262 layer: layer, 5263 name: name, 5264 overlay: overlay 5265 }); 5266 5267 if (this.options.sortLayers) { 5268 this._layers.sort(bind(function (a, b) { 5269 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5270 }, this)); 5271 } 5272 5273 if (this.options.autoZIndex && layer.setZIndex) { 5274 this._lastZIndex++; 5275 layer.setZIndex(this._lastZIndex); 5276 } 5277 5278 this._expandIfNotCollapsed(); 5279 }, 5280 5281 _update: function () { 5282 if (!this._container) { return this; } 5283 5284 empty(this._baseLayersList); 5285 empty(this._overlaysList); 5286 5287 this._layerControlInputs = []; 5288 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5289 5290 for (i = 0; i < this._layers.length; i++) { 5291 obj = this._layers[i]; 5292 this._addItem(obj); 5293 overlaysPresent = overlaysPresent || obj.overlay; 5294 baseLayersPresent = baseLayersPresent || !obj.overlay; 5295 baseLayersCount += !obj.overlay ? 1 : 0; 5296 } 5297 5298 // Hide base layers section if there's only one layer. 5299 if (this.options.hideSingleBase) { 5300 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5301 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5302 } 5303 5304 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5305 5306 return this; 5307 }, 5308 5309 _onLayerChange: function (e) { 5310 if (!this._handlingClick) { 5311 this._update(); 5312 } 5313 5314 var obj = this._getLayer(stamp(e.target)); 5315 5316 // @namespace Map 5317 // @section Layer events 5318 // @event baselayerchange: LayersControlEvent 5319 // Fired when the base layer is changed through the [layers control](#control-layers). 5320 // @event overlayadd: LayersControlEvent 5321 // Fired when an overlay is selected through the [layers control](#control-layers). 5322 // @event overlayremove: LayersControlEvent 5323 // Fired when an overlay is deselected through the [layers control](#control-layers). 5324 // @namespace Control.Layers 5325 var type = obj.overlay ? 5326 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5327 (e.type === 'add' ? 'baselayerchange' : null); 5328 5329 if (type) { 5330 this._map.fire(type, obj); 5331 } 5332 }, 5333 5334 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079) 5335 _createRadioElement: function (name, checked) { 5336 5337 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5338 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5339 5340 var radioFragment = document.createElement('div'); 5341 radioFragment.innerHTML = radioHtml; 5342 5343 return radioFragment.firstChild; 5344 }, 5345 5346 _addItem: function (obj) { 5347 var label = document.createElement('label'), 5348 checked = this._map.hasLayer(obj.layer), 5349 input; 5350 5351 if (obj.overlay) { 5352 input = document.createElement('input'); 5353 input.type = 'checkbox'; 5354 input.className = 'leaflet-control-layers-selector'; 5355 input.defaultChecked = checked; 5356 } else { 5357 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked); 5358 } 5359 5360 this._layerControlInputs.push(input); 5361 input.layerId = stamp(obj.layer); 5362 5363 on(input, 'click', this._onInputClick, this); 5364 5365 var name = document.createElement('span'); 5366 name.innerHTML = ' ' + obj.name; 5367 5368 // Helps from preventing layer control flicker when checkboxes are disabled 5369 // https://github.com/Leaflet/Leaflet/issues/2771 5370 var holder = document.createElement('span'); 5371 5372 label.appendChild(holder); 5373 holder.appendChild(input); 5374 holder.appendChild(name); 5375 5376 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5377 container.appendChild(label); 5378 5379 this._checkDisabledLayers(); 5380 return label; 5381 }, 5382 5383 _onInputClick: function () { 5384 // expanding the control on mobile with a click can cause adding a layer - we don't want this 5385 if (this._preventClick) { 5386 return; 5387 } 5388 5389 var inputs = this._layerControlInputs, 5390 input, layer; 5391 var addedLayers = [], 5392 removedLayers = []; 5393 5394 this._handlingClick = true; 5395 5396 for (var i = inputs.length - 1; i >= 0; i--) { 5397 input = inputs[i]; 5398 layer = this._getLayer(input.layerId).layer; 5399 5400 if (input.checked) { 5401 addedLayers.push(layer); 5402 } else if (!input.checked) { 5403 removedLayers.push(layer); 5404 } 5405 } 5406 5407 // Bugfix issue 2318: Should remove all old layers before readding new ones 5408 for (i = 0; i < removedLayers.length; i++) { 5409 if (this._map.hasLayer(removedLayers[i])) { 5410 this._map.removeLayer(removedLayers[i]); 5411 } 5412 } 5413 for (i = 0; i < addedLayers.length; i++) { 5414 if (!this._map.hasLayer(addedLayers[i])) { 5415 this._map.addLayer(addedLayers[i]); 5416 } 5417 } 5418 5419 this._handlingClick = false; 5420 5421 this._refocusOnMap(); 5422 }, 5423 5424 _checkDisabledLayers: function () { 5425 var inputs = this._layerControlInputs, 5426 input, 5427 layer, 5428 zoom = this._map.getZoom(); 5429 5430 for (var i = inputs.length - 1; i >= 0; i--) { 5431 input = inputs[i]; 5432 layer = this._getLayer(input.layerId).layer; 5433 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5434 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5435 5436 } 5437 }, 5438 5439 _expandIfNotCollapsed: function () { 5440 if (this._map && !this.options.collapsed) { 5441 this.expand(); 5442 } 5443 return this; 5444 }, 5445 5446 _expandSafely: function () { 5447 var section = this._section; 5448 this._preventClick = true; 5449 on(section, 'click', preventDefault); 5450 this.expand(); 5451 var that = this; 5452 setTimeout(function () { 5453 off(section, 'click', preventDefault); 5454 that._preventClick = false; 5455 }); 5456 } 5457 5458}); 5459 5460 5461// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5462// Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. 5463var layers = function (baseLayers, overlays, options) { 5464 return new Layers(baseLayers, overlays, options); 5465}; 5466 5467/* 5468 * @class Control.Zoom 5469 * @aka L.Control.Zoom 5470 * @inherits Control 5471 * 5472 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`. 5473 */ 5474 5475var Zoom = Control.extend({ 5476 // @section 5477 // @aka Control.Zoom options 5478 options: { 5479 position: 'topleft', 5480 5481 // @option zoomInText: String = '<span aria-hidden="true">+</span>' 5482 // The text set on the 'zoom in' button. 5483 zoomInText: '<span aria-hidden="true">+</span>', 5484 5485 // @option zoomInTitle: String = 'Zoom in' 5486 // The title set on the 'zoom in' button. 5487 zoomInTitle: 'Zoom in', 5488 5489 // @option zoomOutText: String = '<span aria-hidden="true">−</span>' 5490 // The text set on the 'zoom out' button. 5491 zoomOutText: '<span aria-hidden="true">−</span>', 5492 5493 // @option zoomOutTitle: String = 'Zoom out' 5494 // The title set on the 'zoom out' button. 5495 zoomOutTitle: 'Zoom out' 5496 }, 5497 5498 onAdd: function (map) { 5499 var zoomName = 'leaflet-control-zoom', 5500 container = create$1('div', zoomName + ' leaflet-bar'), 5501 options = this.options; 5502 5503 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5504 zoomName + '-in', container, this._zoomIn); 5505 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5506 zoomName + '-out', container, this._zoomOut); 5507 5508 this._updateDisabled(); 5509 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5510 5511 return container; 5512 }, 5513 5514 onRemove: function (map) { 5515 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5516 }, 5517 5518 disable: function () { 5519 this._disabled = true; 5520 this._updateDisabled(); 5521 return this; 5522 }, 5523 5524 enable: function () { 5525 this._disabled = false; 5526 this._updateDisabled(); 5527 return this; 5528 }, 5529 5530 _zoomIn: function (e) { 5531 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5532 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5533 } 5534 }, 5535 5536 _zoomOut: function (e) { 5537 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5538 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5539 } 5540 }, 5541 5542 _createButton: function (html, title, className, container, fn) { 5543 var link = create$1('a', className, container); 5544 link.innerHTML = html; 5545 link.href = '#'; 5546 link.title = title; 5547 5548 /* 5549 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5550 */ 5551 link.setAttribute('role', 'button'); 5552 link.setAttribute('aria-label', title); 5553 5554 disableClickPropagation(link); 5555 on(link, 'click', stop); 5556 on(link, 'click', fn, this); 5557 on(link, 'click', this._refocusOnMap, this); 5558 5559 return link; 5560 }, 5561 5562 _updateDisabled: function () { 5563 var map = this._map, 5564 className = 'leaflet-disabled'; 5565 5566 removeClass(this._zoomInButton, className); 5567 removeClass(this._zoomOutButton, className); 5568 this._zoomInButton.setAttribute('aria-disabled', 'false'); 5569 this._zoomOutButton.setAttribute('aria-disabled', 'false'); 5570 5571 if (this._disabled || map._zoom === map.getMinZoom()) { 5572 addClass(this._zoomOutButton, className); 5573 this._zoomOutButton.setAttribute('aria-disabled', 'true'); 5574 } 5575 if (this._disabled || map._zoom === map.getMaxZoom()) { 5576 addClass(this._zoomInButton, className); 5577 this._zoomInButton.setAttribute('aria-disabled', 'true'); 5578 } 5579 } 5580}); 5581 5582// @namespace Map 5583// @section Control options 5584// @option zoomControl: Boolean = true 5585// Whether a [zoom control](#control-zoom) is added to the map by default. 5586Map.mergeOptions({ 5587 zoomControl: true 5588}); 5589 5590Map.addInitHook(function () { 5591 if (this.options.zoomControl) { 5592 // @section Controls 5593 // @property zoomControl: Control.Zoom 5594 // The default zoom control (only available if the 5595 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5596 this.zoomControl = new Zoom(); 5597 this.addControl(this.zoomControl); 5598 } 5599}); 5600 5601// @namespace Control.Zoom 5602// @factory L.control.zoom(options: Control.Zoom options) 5603// Creates a zoom control 5604var zoom = function (options) { 5605 return new Zoom(options); 5606}; 5607 5608/* 5609 * @class Control.Scale 5610 * @aka L.Control.Scale 5611 * @inherits Control 5612 * 5613 * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`. 5614 * 5615 * @example 5616 * 5617 * ```js 5618 * L.control.scale().addTo(map); 5619 * ``` 5620 */ 5621 5622var Scale = Control.extend({ 5623 // @section 5624 // @aka Control.Scale options 5625 options: { 5626 position: 'bottomleft', 5627 5628 // @option maxWidth: Number = 100 5629 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5630 maxWidth: 100, 5631 5632 // @option metric: Boolean = True 5633 // Whether to show the metric scale line (m/km). 5634 metric: true, 5635 5636 // @option imperial: Boolean = True 5637 // Whether to show the imperial scale line (mi/ft). 5638 imperial: true 5639 5640 // @option updateWhenIdle: Boolean = false 5641 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5642 }, 5643 5644 onAdd: function (map) { 5645 var className = 'leaflet-control-scale', 5646 container = create$1('div', className), 5647 options = this.options; 5648 5649 this._addScales(options, className + '-line', container); 5650 5651 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5652 map.whenReady(this._update, this); 5653 5654 return container; 5655 }, 5656 5657 onRemove: function (map) { 5658 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5659 }, 5660 5661 _addScales: function (options, className, container) { 5662 if (options.metric) { 5663 this._mScale = create$1('div', className, container); 5664 } 5665 if (options.imperial) { 5666 this._iScale = create$1('div', className, container); 5667 } 5668 }, 5669 5670 _update: function () { 5671 var map = this._map, 5672 y = map.getSize().y / 2; 5673 5674 var maxMeters = map.distance( 5675 map.containerPointToLatLng([0, y]), 5676 map.containerPointToLatLng([this.options.maxWidth, y])); 5677 5678 this._updateScales(maxMeters); 5679 }, 5680 5681 _updateScales: function (maxMeters) { 5682 if (this.options.metric && maxMeters) { 5683 this._updateMetric(maxMeters); 5684 } 5685 if (this.options.imperial && maxMeters) { 5686 this._updateImperial(maxMeters); 5687 } 5688 }, 5689 5690 _updateMetric: function (maxMeters) { 5691 var meters = this._getRoundNum(maxMeters), 5692 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5693 5694 this._updateScale(this._mScale, label, meters / maxMeters); 5695 }, 5696 5697 _updateImperial: function (maxMeters) { 5698 var maxFeet = maxMeters * 3.2808399, 5699 maxMiles, miles, feet; 5700 5701 if (maxFeet > 5280) { 5702 maxMiles = maxFeet / 5280; 5703 miles = this._getRoundNum(maxMiles); 5704 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5705 5706 } else { 5707 feet = this._getRoundNum(maxFeet); 5708 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5709 } 5710 }, 5711 5712 _updateScale: function (scale, text, ratio) { 5713 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5714 scale.innerHTML = text; 5715 }, 5716 5717 _getRoundNum: function (num) { 5718 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5719 d = num / pow10; 5720 5721 d = d >= 10 ? 10 : 5722 d >= 5 ? 5 : 5723 d >= 3 ? 3 : 5724 d >= 2 ? 2 : 1; 5725 5726 return pow10 * d; 5727 } 5728}); 5729 5730 5731// @factory L.control.scale(options?: Control.Scale options) 5732// Creates an scale control with the given options. 5733var scale = function (options) { 5734 return new Scale(options); 5735}; 5736 5737var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>'; 5738 5739 5740/* 5741 * @class Control.Attribution 5742 * @aka L.Control.Attribution 5743 * @inherits Control 5744 * 5745 * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control. 5746 */ 5747 5748var Attribution = Control.extend({ 5749 // @section 5750 // @aka Control.Attribution options 5751 options: { 5752 position: 'bottomright', 5753 5754 // @option prefix: String|false = 'Leaflet' 5755 // The HTML text shown before the attributions. Pass `false` to disable. 5756 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>' 5757 }, 5758 5759 initialize: function (options) { 5760 setOptions(this, options); 5761 5762 this._attributions = {}; 5763 }, 5764 5765 onAdd: function (map) { 5766 map.attributionControl = this; 5767 this._container = create$1('div', 'leaflet-control-attribution'); 5768 disableClickPropagation(this._container); 5769 5770 // TODO ugly, refactor 5771 for (var i in map._layers) { 5772 if (map._layers[i].getAttribution) { 5773 this.addAttribution(map._layers[i].getAttribution()); 5774 } 5775 } 5776 5777 this._update(); 5778 5779 map.on('layeradd', this._addAttribution, this); 5780 5781 return this._container; 5782 }, 5783 5784 onRemove: function (map) { 5785 map.off('layeradd', this._addAttribution, this); 5786 }, 5787 5788 _addAttribution: function (ev) { 5789 if (ev.layer.getAttribution) { 5790 this.addAttribution(ev.layer.getAttribution()); 5791 ev.layer.once('remove', function () { 5792 this.removeAttribution(ev.layer.getAttribution()); 5793 }, this); 5794 } 5795 }, 5796 5797 // @method setPrefix(prefix: String|false): this 5798 // The HTML text shown before the attributions. Pass `false` to disable. 5799 setPrefix: function (prefix) { 5800 this.options.prefix = prefix; 5801 this._update(); 5802 return this; 5803 }, 5804 5805 // @method addAttribution(text: String): this 5806 // Adds an attribution text (e.g. `'© OpenStreetMap contributors'`). 5807 addAttribution: function (text) { 5808 if (!text) { return this; } 5809 5810 if (!this._attributions[text]) { 5811 this._attributions[text] = 0; 5812 } 5813 this._attributions[text]++; 5814 5815 this._update(); 5816 5817 return this; 5818 }, 5819 5820 // @method removeAttribution(text: String): this 5821 // Removes an attribution text. 5822 removeAttribution: function (text) { 5823 if (!text) { return this; } 5824 5825 if (this._attributions[text]) { 5826 this._attributions[text]--; 5827 this._update(); 5828 } 5829 5830 return this; 5831 }, 5832 5833 _update: function () { 5834 if (!this._map) { return; } 5835 5836 var attribs = []; 5837 5838 for (var i in this._attributions) { 5839 if (this._attributions[i]) { 5840 attribs.push(i); 5841 } 5842 } 5843 5844 var prefixAndAttribs = []; 5845 5846 if (this.options.prefix) { 5847 prefixAndAttribs.push(this.options.prefix); 5848 } 5849 if (attribs.length) { 5850 prefixAndAttribs.push(attribs.join(', ')); 5851 } 5852 5853 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> '); 5854 } 5855}); 5856 5857// @namespace Map 5858// @section Control options 5859// @option attributionControl: Boolean = true 5860// Whether a [attribution control](#control-attribution) is added to the map by default. 5861Map.mergeOptions({ 5862 attributionControl: true 5863}); 5864 5865Map.addInitHook(function () { 5866 if (this.options.attributionControl) { 5867 new Attribution().addTo(this); 5868 } 5869}); 5870 5871// @namespace Control.Attribution 5872// @factory L.control.attribution(options: Control.Attribution options) 5873// Creates an attribution control. 5874var attribution = function (options) { 5875 return new Attribution(options); 5876}; 5877 5878Control.Layers = Layers; 5879Control.Zoom = Zoom; 5880Control.Scale = Scale; 5881Control.Attribution = Attribution; 5882 5883control.layers = layers; 5884control.zoom = zoom; 5885control.scale = scale; 5886control.attribution = attribution; 5887 5888/* 5889 L.Handler is a base class for handler classes that are used internally to inject 5890 interaction features like dragging to classes like Map and Marker. 5891*/ 5892 5893// @class Handler 5894// @aka L.Handler 5895// Abstract class for map interaction handlers 5896 5897var Handler = Class.extend({ 5898 initialize: function (map) { 5899 this._map = map; 5900 }, 5901 5902 // @method enable(): this 5903 // Enables the handler 5904 enable: function () { 5905 if (this._enabled) { return this; } 5906 5907 this._enabled = true; 5908 this.addHooks(); 5909 return this; 5910 }, 5911 5912 // @method disable(): this 5913 // Disables the handler 5914 disable: function () { 5915 if (!this._enabled) { return this; } 5916 5917 this._enabled = false; 5918 this.removeHooks(); 5919 return this; 5920 }, 5921 5922 // @method enabled(): Boolean 5923 // Returns `true` if the handler is enabled 5924 enabled: function () { 5925 return !!this._enabled; 5926 } 5927 5928 // @section Extension methods 5929 // Classes inheriting from `Handler` must implement the two following methods: 5930 // @method addHooks() 5931 // Called when the handler is enabled, should add event hooks. 5932 // @method removeHooks() 5933 // Called when the handler is disabled, should remove the event hooks added previously. 5934}); 5935 5936// @section There is static function which can be called without instantiating L.Handler: 5937// @function addTo(map: Map, name: String): this 5938// Adds a new Handler to the given map with the given name. 5939Handler.addTo = function (map, name) { 5940 map.addHandler(name, this); 5941 return this; 5942}; 5943 5944var Mixin = {Events: Events}; 5945 5946/* 5947 * @class Draggable 5948 * @aka L.Draggable 5949 * @inherits Evented 5950 * 5951 * A class for making DOM elements draggable (including touch support). 5952 * Used internally for map and marker dragging. Only works for elements 5953 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 5954 * 5955 * @example 5956 * ```js 5957 * var draggable = new L.Draggable(elementToDrag); 5958 * draggable.enable(); 5959 * ``` 5960 */ 5961 5962var START = Browser.touch ? 'touchstart mousedown' : 'mousedown'; 5963 5964var Draggable = Evented.extend({ 5965 5966 options: { 5967 // @section 5968 // @aka Draggable options 5969 // @option clickTolerance: Number = 3 5970 // The max number of pixels a user can shift the mouse pointer during a click 5971 // for it to be considered a valid click (as opposed to a mouse drag). 5972 clickTolerance: 3 5973 }, 5974 5975 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 5976 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 5977 initialize: function (element, dragStartTarget, preventOutline, options) { 5978 setOptions(this, options); 5979 5980 this._element = element; 5981 this._dragStartTarget = dragStartTarget || element; 5982 this._preventOutline = preventOutline; 5983 }, 5984 5985 // @method enable() 5986 // Enables the dragging ability 5987 enable: function () { 5988 if (this._enabled) { return; } 5989 5990 on(this._dragStartTarget, START, this._onDown, this); 5991 5992 this._enabled = true; 5993 }, 5994 5995 // @method disable() 5996 // Disables the dragging ability 5997 disable: function () { 5998 if (!this._enabled) { return; } 5999 6000 // If we're currently dragging this draggable, 6001 // disabling it counts as first ending the drag. 6002 if (Draggable._dragging === this) { 6003 this.finishDrag(true); 6004 } 6005 6006 off(this._dragStartTarget, START, this._onDown, this); 6007 6008 this._enabled = false; 6009 this._moved = false; 6010 }, 6011 6012 _onDown: function (e) { 6013 // Ignore the event if disabled; this happens in IE11 6014 // under some circumstances, see #3666. 6015 if (!this._enabled) { return; } 6016 6017 this._moved = false; 6018 6019 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 6020 6021 if (e.touches && e.touches.length !== 1) { 6022 // Finish dragging to avoid conflict with touchZoom 6023 if (Draggable._dragging === this) { 6024 this.finishDrag(); 6025 } 6026 return; 6027 } 6028 6029 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 6030 Draggable._dragging = this; // Prevent dragging multiple objects at once. 6031 6032 if (this._preventOutline) { 6033 preventOutline(this._element); 6034 } 6035 6036 disableImageDrag(); 6037 disableTextSelection(); 6038 6039 if (this._moving) { return; } 6040 6041 // @event down: Event 6042 // Fired when a drag is about to start. 6043 this.fire('down'); 6044 6045 var first = e.touches ? e.touches[0] : e, 6046 sizedParent = getSizedParentNode(this._element); 6047 6048 this._startPoint = new Point(first.clientX, first.clientY); 6049 this._startPos = getPosition(this._element); 6050 6051 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 6052 this._parentScale = getScale(sizedParent); 6053 6054 var mouseevent = e.type === 'mousedown'; 6055 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this); 6056 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this); 6057 }, 6058 6059 _onMove: function (e) { 6060 // Ignore the event if disabled; this happens in IE11 6061 // under some circumstances, see #3666. 6062 if (!this._enabled) { return; } 6063 6064 if (e.touches && e.touches.length > 1) { 6065 this._moved = true; 6066 return; 6067 } 6068 6069 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 6070 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 6071 6072 if (!offset.x && !offset.y) { return; } 6073 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 6074 6075 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 6076 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 6077 // and we can use the cached value for the scale. 6078 offset.x /= this._parentScale.x; 6079 offset.y /= this._parentScale.y; 6080 6081 preventDefault(e); 6082 6083 if (!this._moved) { 6084 // @event dragstart: Event 6085 // Fired when a drag starts 6086 this.fire('dragstart'); 6087 6088 this._moved = true; 6089 6090 addClass(document.body, 'leaflet-dragging'); 6091 6092 this._lastTarget = e.target || e.srcElement; 6093 // IE and Edge do not give the <use> element, so fetch it 6094 // if necessary 6095 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) { 6096 this._lastTarget = this._lastTarget.correspondingUseElement; 6097 } 6098 addClass(this._lastTarget, 'leaflet-drag-target'); 6099 } 6100 6101 this._newPos = this._startPos.add(offset); 6102 this._moving = true; 6103 6104 this._lastEvent = e; 6105 this._updatePosition(); 6106 }, 6107 6108 _updatePosition: function () { 6109 var e = {originalEvent: this._lastEvent}; 6110 6111 // @event predrag: Event 6112 // Fired continuously during dragging *before* each corresponding 6113 // update of the element's position. 6114 this.fire('predrag', e); 6115 setPosition(this._element, this._newPos); 6116 6117 // @event drag: Event 6118 // Fired continuously during dragging. 6119 this.fire('drag', e); 6120 }, 6121 6122 _onUp: function () { 6123 // Ignore the event if disabled; this happens in IE11 6124 // under some circumstances, see #3666. 6125 if (!this._enabled) { return; } 6126 this.finishDrag(); 6127 }, 6128 6129 finishDrag: function (noInertia) { 6130 removeClass(document.body, 'leaflet-dragging'); 6131 6132 if (this._lastTarget) { 6133 removeClass(this._lastTarget, 'leaflet-drag-target'); 6134 this._lastTarget = null; 6135 } 6136 6137 off(document, 'mousemove touchmove', this._onMove, this); 6138 off(document, 'mouseup touchend touchcancel', this._onUp, this); 6139 6140 enableImageDrag(); 6141 enableTextSelection(); 6142 6143 var fireDragend = this._moved && this._moving; 6144 6145 this._moving = false; 6146 Draggable._dragging = false; 6147 6148 if (fireDragend) { 6149 // @event dragend: DragEndEvent 6150 // Fired when the drag ends. 6151 this.fire('dragend', { 6152 noInertia: noInertia, 6153 distance: this._newPos.distanceTo(this._startPos) 6154 }); 6155 } 6156 } 6157 6158}); 6159 6160/* 6161 * @namespace PolyUtil 6162 * Various utility functions for polygon geometries. 6163 */ 6164 6165/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6166 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)). 6167 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6168 * performance. Note that polygon points needs different algorithm for clipping 6169 * than polyline, so there's a separate method for it. 6170 */ 6171function clipPolygon(points, bounds, round) { 6172 var clippedPoints, 6173 edges = [1, 4, 2, 8], 6174 i, j, k, 6175 a, b, 6176 len, edge, p; 6177 6178 for (i = 0, len = points.length; i < len; i++) { 6179 points[i]._code = _getBitCode(points[i], bounds); 6180 } 6181 6182 // for each edge (left, bottom, right, top) 6183 for (k = 0; k < 4; k++) { 6184 edge = edges[k]; 6185 clippedPoints = []; 6186 6187 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6188 a = points[i]; 6189 b = points[j]; 6190 6191 // if a is inside the clip window 6192 if (!(a._code & edge)) { 6193 // if b is outside the clip window (a->b goes out of screen) 6194 if (b._code & edge) { 6195 p = _getEdgeIntersection(b, a, edge, bounds, round); 6196 p._code = _getBitCode(p, bounds); 6197 clippedPoints.push(p); 6198 } 6199 clippedPoints.push(a); 6200 6201 // else if b is inside the clip window (a->b enters the screen) 6202 } else if (!(b._code & edge)) { 6203 p = _getEdgeIntersection(b, a, edge, bounds, round); 6204 p._code = _getBitCode(p, bounds); 6205 clippedPoints.push(p); 6206 } 6207 } 6208 points = clippedPoints; 6209 } 6210 6211 return points; 6212} 6213 6214/* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng 6215 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon. 6216 */ 6217function polygonCenter(latlngs, crs) { 6218 var i, j, p1, p2, f, area, x, y, center; 6219 6220 if (!latlngs || latlngs.length === 0) { 6221 throw new Error('latlngs not passed'); 6222 } 6223 6224 if (!isFlat(latlngs)) { 6225 console.warn('latlngs are not flat! Only the first ring will be used'); 6226 latlngs = latlngs[0]; 6227 } 6228 6229 var centroidLatLng = toLatLng([0, 0]); 6230 6231 var bounds = toLatLngBounds(latlngs); 6232 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 6233 // tests showed that below 1700 rounding errors are happening 6234 if (areaBounds < 1700) { 6235 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors 6236 centroidLatLng = centroid(latlngs); 6237 } 6238 6239 var len = latlngs.length; 6240 var points = []; 6241 for (i = 0; i < len; i++) { 6242 var latlng = toLatLng(latlngs[i]); 6243 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 6244 } 6245 6246 area = x = y = 0; 6247 6248 // polygon centroid algorithm; 6249 for (i = 0, j = len - 1; i < len; j = i++) { 6250 p1 = points[i]; 6251 p2 = points[j]; 6252 6253 f = p1.y * p2.x - p2.y * p1.x; 6254 x += (p1.x + p2.x) * f; 6255 y += (p1.y + p2.y) * f; 6256 area += f * 3; 6257 } 6258 6259 if (area === 0) { 6260 // Polygon is so small that all points are on same pixel. 6261 center = points[0]; 6262 } else { 6263 center = [x / area, y / area]; 6264 } 6265 6266 var latlngCenter = crs.unproject(toPoint(center)); 6267 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 6268} 6269 6270/* @function centroid(latlngs: LatLng[]): LatLng 6271 * Returns the 'center of mass' of the passed LatLngs. 6272 */ 6273function centroid(coords) { 6274 var latSum = 0; 6275 var lngSum = 0; 6276 var len = 0; 6277 for (var i = 0; i < coords.length; i++) { 6278 var latlng = toLatLng(coords[i]); 6279 latSum += latlng.lat; 6280 lngSum += latlng.lng; 6281 len++; 6282 } 6283 return toLatLng([latSum / len, lngSum / len]); 6284} 6285 6286var PolyUtil = { 6287 __proto__: null, 6288 clipPolygon: clipPolygon, 6289 polygonCenter: polygonCenter, 6290 centroid: centroid 6291}; 6292 6293/* 6294 * @namespace LineUtil 6295 * 6296 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 6297 */ 6298 6299// Simplify polyline with vertex reduction and Douglas-Peucker simplification. 6300// Improves rendering performance dramatically by lessening the number of points to draw. 6301 6302// @function simplify(points: Point[], tolerance: Number): Point[] 6303// Dramatically reduces the number of points in a polyline while retaining 6304// its shape and returns a new array of simplified points, using the 6305// [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm). 6306// Used for a huge performance boost when processing/displaying Leaflet polylines for 6307// each zoom level and also reducing visual noise. tolerance affects the amount of 6308// simplification (lesser value means higher quality but slower and with more points). 6309// Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/). 6310function simplify(points, tolerance) { 6311 if (!tolerance || !points.length) { 6312 return points.slice(); 6313 } 6314 6315 var sqTolerance = tolerance * tolerance; 6316 6317 // stage 1: vertex reduction 6318 points = _reducePoints(points, sqTolerance); 6319 6320 // stage 2: Douglas-Peucker simplification 6321 points = _simplifyDP(points, sqTolerance); 6322 6323 return points; 6324} 6325 6326// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 6327// Returns the distance between point `p` and segment `p1` to `p2`. 6328function pointToSegmentDistance(p, p1, p2) { 6329 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 6330} 6331 6332// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 6333// Returns the closest point from a point `p` on a segment `p1` to `p2`. 6334function closestPointOnSegment(p, p1, p2) { 6335 return _sqClosestPointOnSegment(p, p1, p2); 6336} 6337 6338// Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm 6339function _simplifyDP(points, sqTolerance) { 6340 6341 var len = points.length, 6342 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 6343 markers = new ArrayConstructor(len); 6344 6345 markers[0] = markers[len - 1] = 1; 6346 6347 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 6348 6349 var i, 6350 newPoints = []; 6351 6352 for (i = 0; i < len; i++) { 6353 if (markers[i]) { 6354 newPoints.push(points[i]); 6355 } 6356 } 6357 6358 return newPoints; 6359} 6360 6361function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6362 6363 var maxSqDist = 0, 6364 index, i, sqDist; 6365 6366 for (i = first + 1; i <= last - 1; i++) { 6367 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6368 6369 if (sqDist > maxSqDist) { 6370 index = i; 6371 maxSqDist = sqDist; 6372 } 6373 } 6374 6375 if (maxSqDist > sqTolerance) { 6376 markers[index] = 1; 6377 6378 _simplifyDPStep(points, markers, sqTolerance, first, index); 6379 _simplifyDPStep(points, markers, sqTolerance, index, last); 6380 } 6381} 6382 6383// reduce points that are too close to each other to a single point 6384function _reducePoints(points, sqTolerance) { 6385 var reducedPoints = [points[0]]; 6386 6387 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6388 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6389 reducedPoints.push(points[i]); 6390 prev = i; 6391 } 6392 } 6393 if (prev < len - 1) { 6394 reducedPoints.push(points[len - 1]); 6395 } 6396 return reducedPoints; 6397} 6398 6399var _lastCode; 6400 6401// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean 6402// Clips the segment a to b by rectangular bounds with the 6403// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6404// (modifying the segment points directly!). Used by Leaflet to only show polyline 6405// points that are on the screen or near, increasing performance. 6406function clipSegment(a, b, bounds, useLastCode, round) { 6407 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6408 codeB = _getBitCode(b, bounds), 6409 6410 codeOut, p, newCode; 6411 6412 // save 2nd code to avoid calculating it on the next segment 6413 _lastCode = codeB; 6414 6415 while (true) { 6416 // if a,b is inside the clip window (trivial accept) 6417 if (!(codeA | codeB)) { 6418 return [a, b]; 6419 } 6420 6421 // if a,b is outside the clip window (trivial reject) 6422 if (codeA & codeB) { 6423 return false; 6424 } 6425 6426 // other cases 6427 codeOut = codeA || codeB; 6428 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6429 newCode = _getBitCode(p, bounds); 6430 6431 if (codeOut === codeA) { 6432 a = p; 6433 codeA = newCode; 6434 } else { 6435 b = p; 6436 codeB = newCode; 6437 } 6438 } 6439} 6440 6441function _getEdgeIntersection(a, b, code, bounds, round) { 6442 var dx = b.x - a.x, 6443 dy = b.y - a.y, 6444 min = bounds.min, 6445 max = bounds.max, 6446 x, y; 6447 6448 if (code & 8) { // top 6449 x = a.x + dx * (max.y - a.y) / dy; 6450 y = max.y; 6451 6452 } else if (code & 4) { // bottom 6453 x = a.x + dx * (min.y - a.y) / dy; 6454 y = min.y; 6455 6456 } else if (code & 2) { // right 6457 x = max.x; 6458 y = a.y + dy * (max.x - a.x) / dx; 6459 6460 } else if (code & 1) { // left 6461 x = min.x; 6462 y = a.y + dy * (min.x - a.x) / dx; 6463 } 6464 6465 return new Point(x, y, round); 6466} 6467 6468function _getBitCode(p, bounds) { 6469 var code = 0; 6470 6471 if (p.x < bounds.min.x) { // left 6472 code |= 1; 6473 } else if (p.x > bounds.max.x) { // right 6474 code |= 2; 6475 } 6476 6477 if (p.y < bounds.min.y) { // bottom 6478 code |= 4; 6479 } else if (p.y > bounds.max.y) { // top 6480 code |= 8; 6481 } 6482 6483 return code; 6484} 6485 6486// square distance (to avoid unnecessary Math.sqrt calls) 6487function _sqDist(p1, p2) { 6488 var dx = p2.x - p1.x, 6489 dy = p2.y - p1.y; 6490 return dx * dx + dy * dy; 6491} 6492 6493// return closest point on segment or distance to that point 6494function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6495 var x = p1.x, 6496 y = p1.y, 6497 dx = p2.x - x, 6498 dy = p2.y - y, 6499 dot = dx * dx + dy * dy, 6500 t; 6501 6502 if (dot > 0) { 6503 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6504 6505 if (t > 1) { 6506 x = p2.x; 6507 y = p2.y; 6508 } else if (t > 0) { 6509 x += dx * t; 6510 y += dy * t; 6511 } 6512 } 6513 6514 dx = p.x - x; 6515 dy = p.y - y; 6516 6517 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6518} 6519 6520 6521// @function isFlat(latlngs: LatLng[]): Boolean 6522// Returns true if `latlngs` is a flat array, false is nested. 6523function isFlat(latlngs) { 6524 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6525} 6526 6527function _flat(latlngs) { 6528 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6529 return isFlat(latlngs); 6530} 6531 6532/* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng 6533 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline. 6534 */ 6535function polylineCenter(latlngs, crs) { 6536 var i, halfDist, segDist, dist, p1, p2, ratio, center; 6537 6538 if (!latlngs || latlngs.length === 0) { 6539 throw new Error('latlngs not passed'); 6540 } 6541 6542 if (!isFlat(latlngs)) { 6543 console.warn('latlngs are not flat! Only the first ring will be used'); 6544 latlngs = latlngs[0]; 6545 } 6546 6547 var centroidLatLng = toLatLng([0, 0]); 6548 6549 var bounds = toLatLngBounds(latlngs); 6550 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 6551 // tests showed that below 1700 rounding errors are happening 6552 if (areaBounds < 1700) { 6553 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors 6554 centroidLatLng = centroid(latlngs); 6555 } 6556 6557 var len = latlngs.length; 6558 var points = []; 6559 for (i = 0; i < len; i++) { 6560 var latlng = toLatLng(latlngs[i]); 6561 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 6562 } 6563 6564 for (i = 0, halfDist = 0; i < len - 1; i++) { 6565 halfDist += points[i].distanceTo(points[i + 1]) / 2; 6566 } 6567 6568 // The line is so small in the current view that all points are on the same pixel. 6569 if (halfDist === 0) { 6570 center = points[0]; 6571 } else { 6572 for (i = 0, dist = 0; i < len - 1; i++) { 6573 p1 = points[i]; 6574 p2 = points[i + 1]; 6575 segDist = p1.distanceTo(p2); 6576 dist += segDist; 6577 6578 if (dist > halfDist) { 6579 ratio = (dist - halfDist) / segDist; 6580 center = [ 6581 p2.x - ratio * (p2.x - p1.x), 6582 p2.y - ratio * (p2.y - p1.y) 6583 ]; 6584 break; 6585 } 6586 } 6587 } 6588 6589 var latlngCenter = crs.unproject(toPoint(center)); 6590 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 6591} 6592 6593var LineUtil = { 6594 __proto__: null, 6595 simplify: simplify, 6596 pointToSegmentDistance: pointToSegmentDistance, 6597 closestPointOnSegment: closestPointOnSegment, 6598 clipSegment: clipSegment, 6599 _getEdgeIntersection: _getEdgeIntersection, 6600 _getBitCode: _getBitCode, 6601 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6602 isFlat: isFlat, 6603 _flat: _flat, 6604 polylineCenter: polylineCenter 6605}; 6606 6607/* 6608 * @namespace Projection 6609 * @section 6610 * Leaflet comes with a set of already defined Projections out of the box: 6611 * 6612 * @projection L.Projection.LonLat 6613 * 6614 * Equirectangular, or Plate Carree projection â the most simple projection, 6615 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6616 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6617 * `EPSG:4326` and `Simple` CRS. 6618 */ 6619 6620var LonLat = { 6621 project: function (latlng) { 6622 return new Point(latlng.lng, latlng.lat); 6623 }, 6624 6625 unproject: function (point) { 6626 return new LatLng(point.y, point.x); 6627 }, 6628 6629 bounds: new Bounds([-180, -90], [180, 90]) 6630}; 6631 6632/* 6633 * @namespace Projection 6634 * @projection L.Projection.Mercator 6635 * 6636 * Elliptical Mercator projection â more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS. 6637 */ 6638 6639var Mercator = { 6640 R: 6378137, 6641 R_MINOR: 6356752.314245179, 6642 6643 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6644 6645 project: function (latlng) { 6646 var d = Math.PI / 180, 6647 r = this.R, 6648 y = latlng.lat * d, 6649 tmp = this.R_MINOR / r, 6650 e = Math.sqrt(1 - tmp * tmp), 6651 con = e * Math.sin(y); 6652 6653 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6654 y = -r * Math.log(Math.max(ts, 1E-10)); 6655 6656 return new Point(latlng.lng * d * r, y); 6657 }, 6658 6659 unproject: function (point) { 6660 var d = 180 / Math.PI, 6661 r = this.R, 6662 tmp = this.R_MINOR / r, 6663 e = Math.sqrt(1 - tmp * tmp), 6664 ts = Math.exp(-point.y / r), 6665 phi = Math.PI / 2 - 2 * Math.atan(ts); 6666 6667 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6668 con = e * Math.sin(phi); 6669 con = Math.pow((1 - con) / (1 + con), e / 2); 6670 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6671 phi += dphi; 6672 } 6673 6674 return new LatLng(phi * d, point.x * d / r); 6675 } 6676}; 6677 6678/* 6679 * @class Projection 6680 6681 * An object with methods for projecting geographical coordinates of the world onto 6682 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection). 6683 6684 * @property bounds: Bounds 6685 * The bounds (specified in CRS units) where the projection is valid 6686 6687 * @method project(latlng: LatLng): Point 6688 * Projects geographical coordinates into a 2D point. 6689 * Only accepts actual `L.LatLng` instances, not arrays. 6690 6691 * @method unproject(point: Point): LatLng 6692 * The inverse of `project`. Projects a 2D point into a geographical location. 6693 * Only accepts actual `L.Point` instances, not arrays. 6694 6695 * Note that the projection instances do not inherit from Leaflet's `Class` object, 6696 * and can't be instantiated. Also, new classes can't inherit from them, 6697 * and methods can't be added to them with the `include` function. 6698 6699 */ 6700 6701var index = { 6702 __proto__: null, 6703 LonLat: LonLat, 6704 Mercator: Mercator, 6705 SphericalMercator: SphericalMercator 6706}; 6707 6708/* 6709 * @namespace CRS 6710 * @crs L.CRS.EPSG3395 6711 * 6712 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6713 */ 6714var EPSG3395 = extend({}, Earth, { 6715 code: 'EPSG:3395', 6716 projection: Mercator, 6717 6718 transformation: (function () { 6719 var scale = 0.5 / (Math.PI * Mercator.R); 6720 return toTransformation(scale, 0.5, -scale, 0.5); 6721 }()) 6722}); 6723 6724/* 6725 * @namespace CRS 6726 * @crs L.CRS.EPSG4326 6727 * 6728 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6729 * 6730 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6731 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6732 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6733 * whole earth at zoom level zero, and that the tile coordinate origin
6733is (-180,+90), 6734 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6735 */ 6736 6737var EPSG4326 = extend({}, Earth, { 6738 code: 'EPSG:4326', 6739 projection: LonLat, 6740 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
vendor: 8,645 bytes, lines 6740-7054
6740 6741}); 6742 6743/* 6744 * @namespace CRS 6745 * @crs L.CRS.Simple 6746 * 6747 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6748 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6749 * axis should still be inverted (going from bottom to top). `distance()` returns 6750 * simple euclidean distance. 6751 */ 6752 6753var Simple = extend({}, CRS, { 6754 projection: LonLat, 6755 transformation: toTransformation(1, 0, -1, 0), 6756 6757 scale: function (zoom) { 6758 return Math.pow(2, zoom); 6759 }, 6760 6761 zoom: function (scale) { 6762 return Math.log(scale) / Math.LN2; 6763 }, 6764 6765 distance: function (latlng1, latlng2) { 6766 var dx = latlng2.lng - latlng1.lng, 6767 dy = latlng2.lat - latlng1.lat; 6768 6769 return Math.sqrt(dx * dx + dy * dy); 6770 }, 6771 6772 infinite: true 6773}); 6774 6775CRS.Earth = Earth; 6776CRS.EPSG3395 = EPSG3395; 6777CRS.EPSG3857 = EPSG3857; 6778CRS.EPSG900913 = EPSG900913; 6779CRS.EPSG4326 = EPSG4326; 6780CRS.Simple = Simple; 6781 6782/* 6783 * @class Layer 6784 * @inherits Evented 6785 * @aka L.Layer 6786 * @aka ILayer 6787 * 6788 * A set of methods from the Layer base class that all Leaflet layers use. 6789 * Inherits all methods, options and events from `L.Evented`. 6790 * 6791 * @example 6792 * 6793 * ```js 6794 * var layer = L.marker(latlng).addTo(map); 6795 * layer.addTo(map); 6796 * layer.remove(); 6797 * ``` 6798 * 6799 * @event add: Event 6800 * Fired after the layer is added to a map 6801 * 6802 * @event remove: Event 6803 * Fired after the layer is removed from a map 6804 */ 6805 6806 6807var Layer = Evented.extend({ 6808 6809 // Classes extending `L.Layer` will inherit the following options: 6810 options: { 6811 // @option pane: String = 'overlayPane' 6812 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default. 6813 pane: 'overlayPane', 6814 6815 // @option attribution: String = null 6816 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers. 6817 attribution: null, 6818 6819 bubblingMouseEvents: true 6820 }, 6821 6822 /* @section 6823 * Classes extending `L.Layer` will inherit the following methods: 6824 * 6825 * @method addTo(map: Map|LayerGroup): this 6826 * Adds the layer to the given map or layer group. 6827 */ 6828 addTo: function (map) { 6829 map.addLayer(this); 6830 return this; 6831 }, 6832 6833 // @method remove: this 6834 // Removes the layer from the map it is currently active on. 6835 remove: function () { 6836 return this.removeFrom(this._map || this._mapToAdd); 6837 }, 6838 6839 // @method removeFrom(map: Map): this 6840 // Removes the layer from the given map 6841 // 6842 // @alternative 6843 // @method removeFrom(group: LayerGroup): this 6844 // Removes the layer from the given `LayerGroup` 6845 removeFrom: function (obj) { 6846 if (obj) { 6847 obj.removeLayer(this); 6848 } 6849 return this; 6850 }, 6851 6852 // @method getPane(name? : String): HTMLElement 6853 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 6854 getPane: function (name) { 6855 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 6856 }, 6857 6858 addInteractiveTarget: function (targetEl) { 6859 this._map._targets[stamp(targetEl)] = this; 6860 return this; 6861 }, 6862 6863 removeInteractiveTarget: function (targetEl) { 6864 delete this._map._targets[stamp(targetEl)]; 6865 return this; 6866 }, 6867 6868 // @method getAttribution: String 6869 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 6870 getAttribution: function () { 6871 return this.options.attribution; 6872 }, 6873 6874 _layerAdd: function (e) { 6875 var map = e.target; 6876 6877 // check in case layer gets added and then removed before the map is ready 6878 if (!map.hasLayer(this)) { return; } 6879 6880 this._map = map; 6881 this._zoomAnimated = map._zoomAnimated; 6882 6883 if (this.getEvents) { 6884 var events = this.getEvents(); 6885 map.on(events, this); 6886 this.once('remove', function () { 6887 map.off(events, this); 6888 }, this); 6889 } 6890 6891 this.onAdd(map); 6892 6893 this.fire('add'); 6894 map.fire('layeradd', {layer: this}); 6895 } 6896}); 6897 6898/* @section Extension methods 6899 * @uninheritable 6900 * 6901 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 6902 * 6903 * @method onAdd(map: Map): this 6904 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer). 6905 * 6906 * @method onRemove(map: Map): this 6907 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer). 6908 * 6909 * @method getEvents(): Object 6910 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer. 6911 * 6912 * @method getAttribution(): String 6913 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 6914 * 6915 * @method beforeAdd(map: Map): this 6916 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only. 6917 */ 6918 6919 6920/* @namespace Map 6921 * @section Layer events 6922 * 6923 * @event layeradd: LayerEvent 6924 * Fired when a new layer is added to the map. 6925 * 6926 * @event layerremove: LayerEvent 6927 * Fired when some layer is removed from the map 6928 * 6929 * @section Methods for Layers and Controls 6930 */ 6931Map.include({ 6932 // @method addLayer(layer: Layer): this 6933 // Adds the given layer to the map 6934 addLayer: function (layer) { 6935 if (!layer._layerAdd) { 6936 throw new Error('The provided object is not a Layer.'); 6937 } 6938 6939 var id = stamp(layer); 6940 if (this._layers[id]) { return this; } 6941 this._layers[id] = layer; 6942 6943 layer._mapToAdd = this; 6944 6945 if (layer.beforeAdd) { 6946 layer.beforeAdd(this); 6947 } 6948 6949 this.whenReady(layer._layerAdd, layer); 6950 6951 return this; 6952 }, 6953 6954 // @method removeLayer(layer: Layer): this 6955 // Removes the given layer from the map. 6956 removeLayer: function (layer) { 6957 var id = stamp(layer); 6958 6959 if (!this._layers[id]) { return this; } 6960 6961 if (this._loaded) { 6962 layer.onRemove(this); 6963 } 6964 6965 delete this._layers[id]; 6966 6967 if (this._loaded) { 6968 this.fire('layerremove', {layer: layer}); 6969 layer.fire('remove'); 6970 } 6971 6972 layer._map = layer._mapToAdd = null; 6973 6974 return this; 6975 }, 6976 6977 // @method hasLayer(layer: Layer): Boolean 6978 // Returns `true` if the given layer is currently added to the map 6979 hasLayer: function (layer) { 6980 return stamp(layer) in this._layers; 6981 }, 6982 6983 /* @method eachLayer(fn: Function, context?: Object): this 6984 * Iterates over the layers of the map, optionally specifying context of the iterator function. 6985 * ``` 6986 * map.eachLayer(function(layer){ 6987 * layer.bindPopup('Hello'); 6988 * }); 6989 * ``` 6990 */ 6991 eachLayer: function (method, context) { 6992 for (var i in this._layers) { 6993 method.call(context, this._layers[i]); 6994 } 6995 return this; 6996 }, 6997 6998 _addLayers: function (layers) { 6999 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 7000 7001 for (var i = 0, len = layers.length; i < len; i++) { 7002 this.addLayer(layers[i]); 7003 } 7004 }, 7005 7006 _addZoomLimit: function (layer) { 7007 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 7008 this._zoomBoundLayers[stamp(layer)] = layer; 7009 this._updateZoomLevels(); 7010 } 7011 }, 7012 7013 _removeZoomLimit: function (layer) { 7014 var id = stamp(layer); 7015 7016 if (this._zoomBoundLayers[id]) { 7017 delete this._zoomBoundLayers[id]; 7018 this._updateZoomLevels(); 7019 } 7020 }, 7021 7022 _updateZoomLevels: function () { 7023 var minZoom = Infinity, 7024 maxZoom = -Infinity, 7025 oldZoomSpan = this._getZoomSpan(); 7026 7027 for (var i in this._zoomBoundLayers) { 7028 var options = this._zoomBoundLayers[i].options; 7029 7030 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 7031 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 7032 } 7033 7034 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 7035 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 7036 7037 // @section Map state change events 7038 // @event zoomlevelschange: Event 7039 // Fired when the number of zoomlevels on the map is changed due 7040 // to adding or removing a layer. 7041 if (oldZoomSpan !== this._getZoomSpan()) { 7042 this.fire('zoomlevelschange'); 7043 } 7044 7045 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 7046 this.setZoom(this._layersMaxZoom); 7047 } 7048 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 7049 this.setZoom(this._layersMinZoom); 7050 } 7051 } 7052}); 7053 7054/*
vendor: 27,523 bytes, lines 7054-8090
7054 7055 * @class LayerGroup 7056 * @aka L.LayerGroup 7057 * @inherits Interactive layer 7058 * 7059 * Used to group several layers and handle them as one. If you add it to the map, 7060 * any layers added or removed from the group will be added/removed on the map as 7061 * well. Extends `Layer`. 7062 * 7063 * @example 7064 * 7065 * ```js 7066 * L.layerGroup([marker1, marker2]) 7067 * .addLayer(polyline) 7068 * .addTo(map); 7069 * ``` 7070 */ 7071 7072var LayerGroup = Layer.extend({ 7073 7074 initialize: function (layers, options) { 7075 setOptions(this, options); 7076 7077 this._layers = {}; 7078 7079 var i, len; 7080 7081 if (layers) { 7082 for (i = 0, len = layers.length; i < len; i++) { 7083 this.addLayer(layers[i]); 7084 } 7085 } 7086 }, 7087 7088 // @method addLayer(layer: Layer): this 7089 // Adds the given layer to the group. 7090 addLayer: function (layer) { 7091 var id = this.getLayerId(layer); 7092 7093 this._layers[id] = layer; 7094 7095 if (this._map) { 7096 this._map.addLayer(layer); 7097 } 7098 7099 return this; 7100 }, 7101 7102 // @method removeLayer(layer: Layer): this 7103 // Removes the given layer from the group. 7104 // @alternative 7105 // @method removeLayer(id: Number): this 7106 // Removes the layer with the given internal ID from the group. 7107 removeLayer: function (layer) { 7108 var id = layer in this._layers ? layer : this.getLayerId(layer); 7109 7110 if (this._map && this._layers[id]) { 7111 this._map.removeLayer(this._layers[id]); 7112 } 7113 7114 delete this._layers[id]; 7115 7116 return this; 7117 }, 7118 7119 // @method hasLayer(layer: Layer): Boolean 7120 // Returns `true` if the given layer is currently added to the group. 7121 // @alternative 7122 // @method hasLayer(id: Number): Boolean 7123 // Returns `true` if the given internal ID is currently added to the group. 7124 hasLayer: function (layer) { 7125 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer); 7126 return layerId in this._layers; 7127 }, 7128 7129 // @method clearLayers(): this 7130 // Removes all the layers from the group. 7131 clearLayers: function () { 7132 return this.eachLayer(this.removeLayer, this); 7133 }, 7134 7135 // @method invoke(methodName: String, â¦): this 7136 // Calls `methodName` on every layer contained in this group, passing any 7137 // additional parameters. Has no effect if the layers contained do not 7138 // implement `methodName`. 7139 invoke: function (methodName) { 7140 var args = Array.prototype.slice.call(arguments, 1), 7141 i, layer; 7142 7143 for (i in this._layers) { 7144 layer = this._layers[i]; 7145 7146 if (layer[methodName]) { 7147 layer[methodName].apply(layer, args); 7148 } 7149 } 7150 7151 return this; 7152 }, 7153 7154 onAdd: function (map) { 7155 this.eachLayer(map.addLayer, map); 7156 }, 7157 7158 onRemove: function (map) { 7159 this.eachLayer(map.removeLayer, map); 7160 }, 7161 7162 // @method eachLayer(fn: Function, context?: Object): this 7163 // Iterates over the layers of the group, optionally specifying context of the iterator function. 7164 // ```js 7165 // group.eachLayer(function (layer) { 7166 // layer.bindPopup('Hello'); 7167 // }); 7168 // ``` 7169 eachLayer: function (method, context) { 7170 for (var i in this._layers) { 7171 method.call(context, this._layers[i]); 7172 } 7173 return this; 7174 }, 7175 7176 // @method getLayer(id: Number): Layer 7177 // Returns the layer with the given internal ID. 7178 getLayer: function (id) { 7179 return this._layers[id]; 7180 }, 7181 7182 // @method getLayers(): Layer[] 7183 // Returns an array of all the layers added to the group. 7184 getLayers: function () { 7185 var layers = []; 7186 this.eachLayer(layers.push, layers); 7187 return layers; 7188 }, 7189 7190 // @method setZIndex(zIndex: Number): this 7191 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 7192 setZIndex: function (zIndex) { 7193 return this.invoke('setZIndex', zIndex); 7194 }, 7195 7196 // @method getLayerId(layer: Layer): Number 7197 // Returns the internal ID for a layer 7198 getLayerId: function (layer) { 7199 return stamp(layer); 7200 } 7201}); 7202 7203 7204// @factory L.layerGroup(layers?: Layer[], options?: Object) 7205// Create a layer group, optionally given an initial set of layers and an `options` object. 7206var layerGroup = function (layers, options) { 7207 return new LayerGroup(layers, options); 7208}; 7209 7210/* 7211 * @class FeatureGroup 7212 * @aka L.FeatureGroup 7213 * @inherits LayerGroup 7214 * 7215 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 7216 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 7217 * * Events are propagated to the `FeatureGroup`, so if the group has an event 7218 * handler, it will handle events from any of the layers. This includes mouse events 7219 * and custom events. 7220 * * Has `layeradd` and `layerremove` events 7221 * 7222 * @example 7223 * 7224 * ```js 7225 * L.featureGroup([marker1, marker2, polyline]) 7226 * .bindPopup('Hello world!') 7227 * .on('click', function() { alert('Clicked on a member of the group!'); }) 7228 * .addTo(map); 7229 * ``` 7230 */ 7231 7232var FeatureGroup = LayerGroup.extend({ 7233 7234 addLayer: function (layer) { 7235 if (this.hasLayer(layer)) { 7236 return this; 7237 } 7238 7239 layer.addEventParent(this); 7240 7241 LayerGroup.prototype.addLayer.call(this, layer); 7242 7243 // @event layeradd: LayerEvent 7244 // Fired when a layer is added to this `FeatureGroup` 7245 return this.fire('layeradd', {layer: layer}); 7246 }, 7247 7248 removeLayer: function (layer) { 7249 if (!this.hasLayer(layer)) { 7250 return this; 7251 } 7252 if (layer in this._layers) { 7253 layer = this._layers[layer]; 7254 } 7255 7256 layer.removeEventParent(this); 7257 7258 LayerGroup.prototype.removeLayer.call(this, layer); 7259 7260 // @event layerremove: LayerEvent 7261 // Fired when a layer is removed from this `FeatureGroup` 7262 return this.fire('layerremove', {layer: layer}); 7263 }, 7264 7265 // @method setStyle(style: Path options): this 7266 // Sets the given path options to each layer of the group that has a `setStyle` method. 7267 setStyle: function (style) { 7268 return this.invoke('setStyle', style); 7269 }, 7270 7271 // @method bringToFront(): this 7272 // Brings the layer group to the top of all other layers 7273 bringToFront: function () { 7274 return this.invoke('bringToFront'); 7275 }, 7276 7277 // @method bringToBack(): this 7278 // Brings the layer group to the back of all other layers 7279 bringToBack: function () { 7280 return this.invoke('bringToBack'); 7281 }, 7282 7283 // @method getBounds(): LatLngBounds 7284 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 7285 getBounds: function () { 7286 var bounds = new LatLngBounds(); 7287 7288 for (var id in this._layers) { 7289 var layer = this._layers[id]; 7290 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 7291 } 7292 return bounds; 7293 } 7294}); 7295 7296// @factory L.featureGroup(layers?: Layer[], options?: Object) 7297// Create a feature group, optionally given an initial set of layers and an `options` object. 7298var featureGroup = function (layers, options) { 7299 return new FeatureGroup(layers, options); 7300}; 7301 7302/* 7303 * @class Icon 7304 * @aka L.Icon 7305 * 7306 * Represents an icon to provide when creating a marker. 7307 * 7308 * @example 7309 * 7310 * ```js 7311 * var myIcon = L.icon({ 7312 * iconUrl: 'my-icon.png', 7313 * iconRetinaUrl: '[email protected]', 7314 * iconSize: [38, 95], 7315 * iconAnchor: [22, 94], 7316 * popupAnchor: [-3, -76], 7317 * shadowUrl: 'my-icon-shadow.png', 7318 * shadowRetinaUrl: '[email protected]', 7319 * shadowSize: [68, 95], 7320 * shadowAnchor: [22, 94] 7321 * }); 7322 * 7323 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 7324 * ``` 7325 * 7326 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 7327 * 7328 */ 7329 7330var Icon = Class.extend({ 7331 7332 /* @section 7333 * @aka Icon options 7334 * 7335 * @option iconUrl: String = null 7336 * **(required)** The URL to the icon image (absolute or relative to your script path). 7337 * 7338 * @option iconRetinaUrl: String = null 7339 * The URL to a retina sized version of the icon image (absolute or relative to your 7340 * script path). Used for Retina screen devices. 7341 * 7342 * @option iconSize: Point = null 7343 * Size of the icon image in pixels. 7344 * 7345 * @option iconAnchor: Point = null 7346 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 7347 * will be aligned so that this point is at the marker's geographical location. Centered 7348 * by default if size is specified, also can be set in CSS with negative margins. 7349 * 7350 * @option popupAnchor: Point = [0, 0] 7351 * The coordinates of the point from which popups will "open", relative to the icon anchor. 7352 * 7353 * @option tooltipAnchor: Point = [0, 0] 7354 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7355 * 7356 * @option shadowUrl: String = null 7357 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7358 * 7359 * @option shadowRetinaUrl: String = null 7360 * 7361 * @option shadowSize: Point = null 7362 * Size of the shadow image in pixels. 7363 * 7364 * @option shadowAnchor: Point = null 7365 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7366 * as iconAnchor if not specified). 7367 * 7368 * @option className: String = '' 7369 * A custom class name to assign to both icon and shadow images. Empty by default. 7370 */ 7371 7372 options: { 7373 popupAnchor: [0, 0], 7374 tooltipAnchor: [0, 0], 7375 7376 // @option crossOrigin: Boolean|String = false 7377 // Whether the crossOrigin attribute will be added to the tiles. 7378 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data. 7379 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 7380 crossOrigin: false 7381 }, 7382 7383 initialize: function (options) { 7384 setOptions(this, options); 7385 }, 7386 7387 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7388 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7389 // styled according to the options. 7390 createIcon: function (oldIcon) { 7391 return this._createIcon('icon', oldIcon); 7392 }, 7393 7394 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7395 // As `createIcon`, but for the shadow beneath it. 7396 createShadow: function (oldIcon) { 7397 return this._createIcon('shadow', oldIcon); 7398 }, 7399 7400 _createIcon: function (name, oldIcon) { 7401 var src = this._getIconUrl(name); 7402 7403 if (!src) { 7404 if (name === 'icon') { 7405 throw new Error('iconUrl not set in Icon options (see the docs).'); 7406 } 7407 return null; 7408 } 7409 7410 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7411 this._setIconStyles(img, name); 7412 7413 if (this.options.crossOrigin || this.options.crossOrigin === '') { 7414 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 7415 } 7416 7417 return img; 7418 }, 7419 7420 _setIconStyles: function (img, name) { 7421 var options = this.options; 7422 var sizeOption = options[name + 'Size']; 7423 7424 if (typeof sizeOption === 'number') { 7425 sizeOption = [sizeOption, sizeOption]; 7426 } 7427 7428 var size = toPoint(sizeOption), 7429 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7430 size && size.divideBy(2, true)); 7431 7432 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7433 7434 if (anchor) { 7435 img.style.marginLeft = (-anchor.x) + 'px'; 7436 img.style.marginTop = (-anchor.y) + 'px'; 7437 } 7438 7439 if (size) { 7440 img.style.width = size.x + 'px'; 7441 img.style.height = size.y + 'px'; 7442 } 7443 }, 7444 7445 _createImg: function (src, el) { 7446 el = el || document.createElement('img'); 7447 el.src = src; 7448 return el; 7449 }, 7450 7451 _getIconUrl: function (name) { 7452 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7453 } 7454}); 7455 7456 7457// @factory L.icon(options: Icon options) 7458// Creates an icon instance with the given options. 7459function icon(options) { 7460 return new Icon(options); 7461} 7462 7463/* 7464 * @miniclass Icon.Default (Icon) 7465 * @aka L.Icon.Default 7466 * @section 7467 * 7468 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7469 * no icon is specified. Points to the blue marker image distributed with Leaflet 7470 * releases. 7471 * 7472 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7473 * (which is a set of `Icon options`). 7474 * 7475 * If you want to _completely_ replace the default icon, override the 7476 * `L.Marker.prototype.options.icon` with your own icon instead. 7477 */ 7478 7479var IconDefault = Icon.extend({ 7480 7481 options: { 7482 iconUrl: 'marker-icon.png', 7483 iconRetinaUrl: 'marker-icon-2x.png', 7484 shadowUrl: 'marker-shadow.png', 7485 iconSize: [25, 41], 7486 iconAnchor: [12, 41], 7487 popupAnchor: [1, -34], 7488 tooltipAnchor: [16, -28], 7489 shadowSize: [41, 41] 7490 }, 7491 7492 _getIconUrl: function (name) { 7493 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only 7494 IconDefault.imagePath = this._detectIconPath(); 7495 } 7496 7497 // @option imagePath: String 7498 // `Icon.Default` will try to auto-detect the location of the 7499 // blue icon images. If you are placing these images in a non-standard 7500 // way, set this option to point to the right path. 7501 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7502 }, 7503 7504 _stripUrl: function (path) { // separate function to use in tests 7505 var strip = function (str, re, idx) { 7506 var match = re.exec(str); 7507 return match && match[idx]; 7508 }; 7509 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2); 7510 return path && strip(path, /^(.*)marker-icon\.png$/, 1); 7511 }, 7512 7513 _detectIconPath: function () { 7514 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7515 var path = getStyle(el, 'background-image') || 7516 getStyle(el, 'backgroundImage'); // IE8 7517 7518 document.body.removeChild(el); 7519 path = this._stripUrl(path); 7520 if (path) { return path; } 7521 var link = document.querySelector('link[href$="leaflet.css"]'); 7522 if (!link) { return ''; } 7523 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1); 7524 } 7525}); 7526 7527/* 7528 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7529 */ 7530 7531 7532/* @namespace Marker 7533 * @section Interaction handlers 7534 * 7535 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example: 7536 * 7537 * ```js 7538 * marker.dragging.disable(); 7539 * ``` 7540 * 7541 * @property dragging: Handler 7542 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7543 */ 7544 7545var MarkerDrag = Handler.extend({ 7546 initialize: function (marker) { 7547 this._marker = marker; 7548 }, 7549 7550 addHooks: function () { 7551 var icon = this._marker._icon; 7552 7553 if (!this._draggable) { 7554 this._draggable = new Draggable(icon, icon, true); 7555 } 7556 7557 this._draggable.on({ 7558 dragstart: this._onDragStart, 7559 predrag: this._onPreDrag, 7560 drag: this._onDrag, 7561 dragend: this._onDragEnd 7562 }, this).enable(); 7563 7564 addClass(icon, 'leaflet-marker-draggable'); 7565 }, 7566 7567 removeHooks: function () { 7568 this._draggable.off({ 7569 dragstart: this._onDragStart, 7570 predrag: this._onPreDrag, 7571 drag: this._onDrag, 7572 dragend: this._onDragEnd 7573 }, this).disable(); 7574 7575 if (this._marker._icon) { 7576 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7577 } 7578 }, 7579 7580 moved: function () { 7581 return this._draggable && this._draggable._moved; 7582 }, 7583 7584 _adjustPan: function (e) { 7585 var marker = this._marker, 7586 map = marker._map, 7587 speed = this._marker.options.autoPanSpeed, 7588 padding = this._marker.options.autoPanPadding, 7589 iconPos = getPosition(marker._icon), 7590 bounds = map.getPixelBounds(), 7591 origin = map.getPixelOrigin(); 7592 7593 var panBounds = toBounds( 7594 bounds.min._subtract(origin).add(padding), 7595 bounds.max._subtract(origin).subtract(padding) 7596 ); 7597 7598 if (!panBounds.contains(iconPos)) { 7599 // Compute incremental movement 7600 var movement = toPoint( 7601 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7602 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7603 7604 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7605 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7606 ).multiplyBy(speed); 7607 7608 map.panBy(movement, {animate: false}); 7609 7610 this._draggable._newPos._add(movement); 7611 this._draggable._startPos._add(movement); 7612 7613 setPosition(marker._icon, this._draggable._newPos); 7614 this._onDrag(e); 7615 7616 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7617 } 7618 }, 7619 7620 _onDragStart: function () { 7621 // @section Dragging events 7622 // @event dragstart: Event 7623 // Fired when the user starts dragging the marker. 7624 7625 // @event movestart: Event 7626 // Fired when the marker starts moving (because of dragging). 7627 7628 this._oldLatLng = this._marker.getLatLng(); 7629 7630 // When using ES6 imports it could not be set when `Popup` was not imported as well 7631 this._marker.closePopup && this._marker.closePopup(); 7632 7633 this._marker 7634 .fire('movestart') 7635 .fire('dragstart'); 7636 }, 7637 7638 _onPreDrag: function (e) { 7639 if (this._marker.options.autoPan) { 7640 cancelAnimFrame(this._panRequest); 7641 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7642 } 7643 }, 7644 7645 _onDrag: function (e) { 7646 var marker = this._marker, 7647 shadow = marker._shadow, 7648 iconPos = getPosition(marker._icon), 7649 latlng = marker._map.layerPointToLatLng(iconPos); 7650 7651 // update shadow position 7652 if (shadow) { 7653 setPosition(shadow, iconPos); 7654 } 7655 7656 marker._latlng = latlng; 7657 e.latlng = latlng; 7658 e.oldLatLng = this._oldLatLng; 7659 7660 // @event drag: Event 7661 // Fired repeatedly while the user drags the marker. 7662 marker 7663 .fire('move', e) 7664 .fire('drag', e); 7665 }, 7666 7667 _onDragEnd: function (e) { 7668 // @event dragend: DragEndEvent 7669 // Fired when the user stops dragging the marker. 7670 7671 cancelAnimFrame(this._panRequest); 7672 7673 // @event moveend: Event 7674 // Fired when the marker stops moving (because of dragging). 7675 delete this._oldLatLng; 7676 this._marker 7677 .fire('moveend') 7678 .fire('dragend', e); 7679 } 7680}); 7681 7682/* 7683 * @class Marker 7684 * @inherits Interactive layer 7685 * @aka L.Marker 7686 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7687 * 7688 * @example 7689 * 7690 * ```js 7691 * L.marker([50.5, 30.5]).addTo(map); 7692 * ``` 7693 */ 7694 7695var Marker = Layer.extend({ 7696 7697 // @section 7698 // @aka Marker options 7699 options: { 7700 // @option icon: Icon = * 7701 // Icon instance to use for rendering the marker. 7702 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7703 // If not specified, a common instance of `L.Icon.Default` is used. 7704 icon: new IconDefault(), 7705 7706 // Option inherited from "Interactive layer" abstract class 7707 interactive: true, 7708 7709 // @option keyboard: Boolean = true 7710 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7711 keyboard: true, 7712 7713 // @option title: String = '' 7714 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7715 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 7716 title: '', 7717 7718 // @option alt: String = 'Marker' 7719 // Text for the `alt` attribute of the icon image. 7720 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 7721 alt: 'Marker', 7722 7723 // @option zIndexOffset: Number = 0 7724 // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively). 7725 zIndexOffset: 0, 7726 7727 // @option opacity: Number = 1.0 7728 // The opacity of the marker. 7729 opacity: 1, 7730 7731 // @option riseOnHover: Boolean = false 7732 // If `true`, the marker will get on top of others when you hover the mouse over it. 7733 riseOnHover: false, 7734 7735 // @option riseOffset: Number = 250 7736 // The z-index offset used for the `riseOnHover` feature. 7737 riseOffset: 250, 7738 7739 // @option pane: String = 'markerPane' 7740 // `Map pane` where the markers icon will be added. 7741 pane: 'markerPane', 7742 7743 // @option shadowPane: String = 'shadowPane' 7744 // `Map pane` where the markers shadow will be added. 7745 shadowPane: 'shadowPane', 7746 7747 // @option bubblingMouseEvents: Boolean = false 7748 // When `true`, a mouse event on this marker will trigger the same event on the map 7749 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7750 bubblingMouseEvents: false, 7751 7752 // @option autoPanOnFocus: Boolean = true 7753 // When `true`, the map will pan whenever the marker is focused (via 7754 // e.g. pressing `tab` on the keyboard) to ensure the marker is 7755 // visible within the map's bounds 7756 autoPanOnFocus: true, 7757 7758 // @section Draggable marker options 7759 // @option draggable: Boolean = false 7760 // Whether the marker is draggable with mouse/touch or not. 7761 draggable: false, 7762 7763 // @option autoPan: Boolean = false 7764 // Whether to pan the map when dragging this marker near its edge or not. 7765 autoPan: false, 7766 7767 // @option autoPanPadding: Point = Point(50, 50) 7768 // Distance (in pixels to the left/right and to the top/bottom) of the 7769 // map edge to start panning the map. 7770 autoPanPadding: [50, 50], 7771 7772 // @option autoPanSpeed: Number = 10 7773 // Number of pixels the map should pan by. 7774 autoPanSpeed: 10 7775 }, 7776 7777 /* @section 7778 * 7779 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 7780 */ 7781 7782 initialize: function (latlng, options) { 7783 setOptions(this, options); 7784 this._latlng = toLatLng(latlng); 7785 }, 7786 7787 onAdd: function (map) { 7788 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 7789 7790 if (this._zoomAnimated) { 7791 map.on('zoomanim', this._animateZoom, this); 7792 } 7793 7794 this._initIcon(); 7795 this.update(); 7796 }, 7797 7798 onRemove: function (map) { 7799 if (this.dragging && this.dragging.enabled()) { 7800 this.options.draggable = true; 7801 this.dragging.removeHooks(); 7802 } 7803 delete this.dragging; 7804 7805 if (this._zoomAnimated) { 7806 map.off('zoomanim', this._animateZoom, this); 7807 } 7808 7809 this._removeIcon(); 7810 this._removeShadow(); 7811 }, 7812 7813 getEvents: function () { 7814 return { 7815 zoom: this.update, 7816 viewreset: this.update 7817 }; 7818 }, 7819 7820 // @method getLatLng: LatLng 7821 // Returns the current geographical position of the marker. 7822 getLatLng: function () { 7823 return this._latlng; 7824 }, 7825 7826 // @method setLatLng(latlng: LatLng): this 7827 // Changes the marker position to the given point. 7828 setLatLng: function (latlng) { 7829 var oldLatLng = this._latlng; 7830 this._latlng = toLatLng(latlng); 7831 this.update(); 7832 7833 // @event move: Event 7834 // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 7835 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7836 }, 7837 7838 // @method setZIndexOffset(offset: Number): this 7839 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 7840 setZIndexOffset: function (offset) { 7841 this.options.zIndexOffset = offset; 7842 return this.update(); 7843 }, 7844 7845 // @method getIcon: Icon 7846 // Returns the current icon used by the marker 7847 getIcon: function () { 7848 return this.options.icon; 7849 }, 7850 7851 // @method setIcon(icon: Icon): this 7852 // Changes the marker icon. 7853 setIcon: function (icon) { 7854 7855 this.options.icon = icon; 7856 7857 if (this._map) { 7858 this._initIcon(); 7859 this.update(); 7860 } 7861 7862 if (this._popup) { 7863 this.bindPopup(this._popup, this._popup.options); 7864 } 7865 7866 return this; 7867 }, 7868 7869 getElement: function () { 7870 return this._icon; 7871 }, 7872 7873 update: function () { 7874 7875 if (this._icon && this._map) { 7876 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 7877 this._setPos(pos); 7878 } 7879 7880 return this; 7881 }, 7882 7883 _initIcon: function () { 7884 var options = this.options, 7885 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7886 7887 var icon = options.icon.createIcon(this._icon), 7888 addIcon = false; 7889 7890 // if we're not reusing the icon, remove the old one and init new one 7891 if (icon !== this._icon) { 7892 if (this._icon) { 7893 this._removeIcon(); 7894 } 7895 addIcon = true; 7896 7897 if (options.title) { 7898 icon.title = options.title; 7899 } 7900 7901 if (icon.tagName === 'IMG') { 7902 icon.alt = options.alt || ''; 7903 } 7904 } 7905 7906 addClass(icon, classToAdd); 7907 7908 if (options.keyboard) { 7909 icon.tabIndex = '0'; 7910 icon.setAttribute('role', 'button'); 7911 } 7912 7913 this._icon = icon; 7914 7915 if (options.riseOnHover) { 7916 this.on({ 7917 mouseover: this._bringToFront, 7918 mouseout: this._resetZIndex 7919 }); 7920 } 7921 7922 if (this.options.autoPanOnFocus) { 7923 on(icon, 'focus', this._panOnFocus, this); 7924 } 7925 7926 var newShadow = options.icon.createShadow(this._shadow), 7927 addShadow = false; 7928 7929 if (newShadow !== this._shadow) { 7930 this._removeShadow(); 7931 addShadow = true; 7932 } 7933 7934 if (newShadow) { 7935 addClass(newShadow, classToAdd); 7936 newShadow.alt = ''; 7937 } 7938 this._shadow = newShadow; 7939 7940 7941 if (options.opacity < 1) { 7942 this._updateOpacity(); 7943 } 7944 7945 7946 if (addIcon) { 7947 this.getPane().appendChild(this._icon); 7948 } 7949 this._initInteraction(); 7950 if (newShadow && addShadow) { 7951 this.getPane(options.shadowPane).appendChild(this._shadow); 7952 } 7953 }, 7954 7955 _removeIcon: function () { 7956 if (this.options.riseOnHover) { 7957 this.off({ 7958 mouseover: this._bringToFront, 7959 mouseout: this._resetZIndex 7960 }); 7961 } 7962 7963 if (this.options.autoPanOnFocus) { 7964 off(this._icon, 'focus', this._panOnFocus, this); 7965 } 7966 7967 remove(this._icon); 7968 this.removeInteractiveTarget(this._icon); 7969 7970 this._icon = null; 7971 }, 7972 7973 _removeShadow: function () { 7974 if (this._shadow) { 7975 remove(this._shadow); 7976 } 7977 this._shadow = null; 7978 }, 7979 7980 _setPos: function (pos) { 7981 7982 if (this._icon) { 7983 setPosition(this._icon, pos); 7984 } 7985 7986 if (this._shadow) { 7987 setPosition(this._shadow, pos); 7988 } 7989 7990 this._zIndex = pos.y + this.options.zIndexOffset; 7991 7992 this._resetZIndex(); 7993 }, 7994 7995 _updateZIndex: function (offset) { 7996 if (this._icon) { 7997 this._icon.style.zIndex = this._zIndex + offset; 7998 } 7999 }, 8000 8001 _animateZoom: function (opt) { 8002 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 8003 8004 this._setPos(pos); 8005 }, 8006 8007 _initInteraction: function () { 8008 8009 if (!this.options.interactive) { return; } 8010 8011 addClass(this._icon, 'leaflet-interactive'); 8012 8013 this.addInteractiveTarget(this._icon); 8014 8015 if (MarkerDrag) { 8016 var draggable = this.options.draggable; 8017 if (this.dragging) { 8018 draggable = this.dragging.enabled(); 8019 this.dragging.disable(); 8020 } 8021 8022 this.dragging = new MarkerDrag(this); 8023 8024 if (draggable) { 8025 this.dragging.enable(); 8026 } 8027 } 8028 }, 8029 8030 // @method setOpacity(opacity: Number): this 8031 // Changes the opacity of the marker. 8032 setOpacity: function (opacity) { 8033 this.options.opacity = opacity; 8034 if (this._map) { 8035 this._updateOpacity(); 8036 } 8037 8038 return this; 8039 }, 8040 8041 _updateOpacity: function () { 8042 var opacity = this.options.opacity; 8043 8044 if (this._icon) { 8045 setOpacity(this._icon, opacity); 8046 } 8047 8048 if (this._shadow) { 8049 setOpacity(this._shadow, opacity); 8050 } 8051 }, 8052 8053 _bringToFront: function () { 8054 this._updateZIndex(this.options.riseOffset); 8055 }, 8056 8057 _resetZIndex: function () { 8058 this._updateZIndex(0); 8059 }, 8060 8061 _panOnFocus: function () { 8062 var map = this._map; 8063 if (!map) { return; } 8064 8065 var iconOpts = this.options.icon.options; 8066 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0); 8067 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0); 8068 8069 map.panInside(this._latlng, { 8070 paddingTopLeft: anchor, 8071 paddingBottomRight: size.subtract(anchor) 8072 }); 8073 }, 8074 8075 _getPopupAnchor: function () { 8076 return this.options.icon.options.popupAnchor; 8077 }, 8078 8079 _getTooltipAnchor: function () { 8080 return this.options.icon.options.tooltipAnchor; 8081 } 8082}); 8083 8084 8085// factory L.marker(latlng: LatLng, options? : Marker options) 8086 8087// @factory L.marker(latlng: LatLng, options? : Marker options) 8088// Instantiates a Marker object given a geographical point and optionally an options object. 8089function marker(latlng, options) { 8090 return new Marker(latlng, options);
vendor: 22,477 bytes, lines 8090-8902
8090 8091} 8092 8093/* 8094 * @class Path 8095 * @aka L.Path 8096 * @inherits Interactive layer 8097 * 8098 * An abstract class that contains options and constants shared between vector 8099 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 8100 */ 8101 8102var Path = Layer.extend({ 8103 8104 // @section 8105 // @aka Path options 8106 options: { 8107 // @option stroke: Boolean = true 8108 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 8109 stroke: true, 8110 8111 // @option color: String = '#3388ff' 8112 // Stroke color 8113 color: '#3388ff', 8114 8115 // @option weight: Number = 3 8116 // Stroke width in pixels 8117 weight: 3, 8118 8119 // @option opacity: Number = 1.0 8120 // Stroke opacity 8121 opacity: 1, 8122 8123 // @option lineCap: String= 'round' 8124 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 8125 lineCap: 'round', 8126 8127 // @option lineJoin: String = 'round' 8128 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 8129 lineJoin: 'round', 8130 8131 // @option dashArray: String = null 8132 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). 8133 dashArray: null, 8134 8135 // @option dashOffset: String = null 8136 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). 8137 dashOffset: null, 8138 8139 // @option fill: Boolean = depends 8140 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 8141 fill: false, 8142 8143 // @option fillColor: String = * 8144 // Fill color. Defaults to the value of the [`color`](#path-color) option 8145 fillColor: null, 8146 8147 // @option fillOpacity: Number = 0.2 8148 // Fill opacity. 8149 fillOpacity: 0.2, 8150 8151 // @option fillRule: String = 'evenodd' 8152 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 8153 fillRule: 'evenodd', 8154 8155 // className: '', 8156 8157 // Option inherited from "Interactive layer" abstract class 8158 interactive: true, 8159 8160 // @option bubblingMouseEvents: Boolean = true 8161 // When `true`, a mouse event on this path will trigger the same event on the map 8162 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 8163 bubblingMouseEvents: true 8164 }, 8165 8166 beforeAdd: function (map) { 8167 // Renderer is set here because we need to call renderer.getEvents 8168 // before this.getEvents. 8169 this._renderer = map.getRenderer(this); 8170 }, 8171 8172 onAdd: function () { 8173 this._renderer._initPath(this); 8174 this._reset(); 8175 this._renderer._addPath(this); 8176 }, 8177 8178 onRemove: function () { 8179 this._renderer._removePath(this); 8180 }, 8181 8182 // @method redraw(): this 8183 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 8184 redraw: function () { 8185 if (this._map) { 8186 this._renderer._updatePath(this); 8187 } 8188 return this; 8189 }, 8190 8191 // @method setStyle(style: Path options): this 8192 // Changes the appearance of a Path based on the options in the `Path options` object. 8193 setStyle: function (style) { 8194 setOptions(this, style); 8195 if (this._renderer) { 8196 this._renderer._updateStyle(this); 8197 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) { 8198 this._updateBounds(); 8199 } 8200 } 8201 return this; 8202 }, 8203 8204 // @method bringToFront(): this 8205 // Brings the layer to the top of all path layers. 8206 bringToFront: function () { 8207 if (this._renderer) { 8208 this._renderer._bringToFront(this); 8209 } 8210 return this; 8211 }, 8212 8213 // @method bringToBack(): this 8214 // Brings the layer to the bottom of all path layers. 8215 bringToBack: function () { 8216 if (this._renderer) { 8217 this._renderer._bringToBack(this); 8218 } 8219 return this; 8220 }, 8221 8222 getElement: function () { 8223 return this._path; 8224 }, 8225 8226 _reset: function () { 8227 // defined in child classes 8228 this._project(); 8229 this._update(); 8230 }, 8231 8232 _clickTolerance: function () { 8233 // used when doing hit detection for Canvas layers 8234 return (this.options.stroke ? this.options.weight / 2 : 0) + 8235 (this._renderer.options.tolerance || 0); 8236 } 8237}); 8238 8239/* 8240 * @class CircleMarker 8241 * @aka L.CircleMarker 8242 * @inherits Path 8243 * 8244 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 8245 */ 8246 8247var CircleMarker = Path.extend({ 8248 8249 // @section 8250 // @aka CircleMarker options 8251 options: { 8252 fill: true, 8253 8254 // @option radius: Number = 10 8255 // Radius of the circle marker, in pixels 8256 radius: 10 8257 }, 8258 8259 initialize: function (latlng, options) { 8260 setOptions(this, options); 8261 this._latlng = toLatLng(latlng); 8262 this._radius = this.options.radius; 8263 }, 8264 8265 // @method setLatLng(latLng: LatLng): this 8266 // Sets the position of a circle marker to a new location. 8267 setLatLng: function (latlng) { 8268 var oldLatLng = this._latlng; 8269 this._latlng = toLatLng(latlng); 8270 this.redraw(); 8271 8272 // @event move: Event 8273 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 8274 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 8275 }, 8276 8277 // @method getLatLng(): LatLng 8278 // Returns the current geographical position of the circle marker 8279 getLatLng: function () { 8280 return this._latlng; 8281 }, 8282 8283 // @method setRadius(radius: Number): this 8284 // Sets the radius of a circle marker. Units are in pixels. 8285 setRadius: function (radius) { 8286 this.options.radius = this._radius = radius; 8287 return this.redraw(); 8288 }, 8289 8290 // @method getRadius(): Number 8291 // Returns the current radius of the circle 8292 getRadius: function () { 8293 return this._radius; 8294 }, 8295 8296 setStyle : function (options) { 8297 var radius = options && options.radius || this._radius; 8298 Path.prototype.setStyle.call(this, options); 8299 this.setRadius(radius); 8300 return this; 8301 }, 8302 8303 _project: function () { 8304 this._point = this._map.latLngToLayerPoint(this._latlng); 8305 this._updateBounds(); 8306 }, 8307 8308 _updateBounds: function () { 8309 var r = this._radius, 8310 r2 = this._radiusY || r, 8311 w = this._clickTolerance(), 8312 p = [r + w, r2 + w]; 8313 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 8314 }, 8315 8316 _update: function () { 8317 if (this._map) { 8318 this._updatePath(); 8319 } 8320 }, 8321 8322 _updatePath: function () { 8323 this._renderer._updateCircle(this); 8324 }, 8325 8326 _empty: function () { 8327 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 8328 }, 8329 8330 // Needed by the `Canvas` renderer for interactivity 8331 _containsPoint: function (p) { 8332 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 8333 } 8334}); 8335 8336 8337// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 8338// Instantiates a circle marker object given a geographical point, and an optional options object. 8339function circleMarker(latlng, options) { 8340 return new CircleMarker(latlng, options); 8341} 8342 8343/* 8344 * @class Circle 8345 * @aka L.Circle 8346 * @inherits CircleMarker 8347 * 8348 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 8349 * 8350 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 8351 * 8352 * @example 8353 * 8354 * ```js 8355 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 8356 * ``` 8357 */ 8358 8359var Circle = CircleMarker.extend({ 8360 8361 initialize: function (latlng, options, legacyOptions) { 8362 if (typeof options === 'number') { 8363 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 8364 options = extend({}, legacyOptions, {radius: options}); 8365 } 8366 setOptions(this, options); 8367 this._latlng = toLatLng(latlng); 8368 8369 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 8370 8371 // @section 8372 // @aka Circle options 8373 // @option radius: Number; Radius of the circle, in meters. 8374 this._mRadius = this.options.radius; 8375 }, 8376 8377 // @method setRadius(radius: Number): this 8378 // Sets the radius of a circle. Units are in meters. 8379 setRadius: function (radius) { 8380 this._mRadius = radius; 8381 return this.redraw(); 8382 }, 8383 8384 // @method getRadius(): Number 8385 // Returns the current radius of a circle. Units are in meters. 8386 getRadius: function () { 8387 return this._mRadius; 8388 }, 8389 8390 // @method getBounds(): LatLngBounds 8391 // Returns the `LatLngBounds` of the path. 8392 getBounds: function () { 8393 var half = [this._radius, this._radiusY || this._radius]; 8394 8395 return new LatLngBounds( 8396 this._map.layerPointToLatLng(this._point.subtract(half)), 8397 this._map.layerPointToLatLng(this._point.add(half))); 8398 }, 8399 8400 setStyle: Path.prototype.setStyle, 8401 8402 _project: function () { 8403 8404 var lng = this._latlng.lng, 8405 lat = this._latlng.lat, 8406 map = this._map, 8407 crs = map.options.crs; 8408 8409 if (crs.distance === Earth.distance) { 8410 var d = Math.PI / 180, 8411 latR = (this._mRadius / Earth.R) / d, 8412 top = map.project([lat + latR, lng]), 8413 bottom = map.project([lat - latR, lng]), 8414 p = top.add(bottom).divideBy(2), 8415 lat2 = map.unproject(p).lat, 8416 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8417 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8418 8419 if (isNaN(lngR) || lngR === 0) { 8420 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8421 } 8422 8423 this._point = p.subtract(map.getPixelOrigin()); 8424 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 8425 this._radiusY = p.y - top.y; 8426 8427 } else { 8428 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8429 8430 this._point = map.latLngToLayerPoint(this._latlng); 8431 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8432 } 8433 8434 this._updateBounds(); 8435 } 8436}); 8437 8438// @factory L.circle(latlng: LatLng, options?: Circle options) 8439// Instantiates a circle object given a geographical point, and an options object 8440// which contains the circle radius. 8441// @alternative 8442// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8443// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8444// Do not use in new applications or plugins. 8445function circle(latlng, options, legacyOptions) { 8446 return new Circle(latlng, options, legacyOptions); 8447} 8448 8449/* 8450 * @class Polyline 8451 * @aka L.Polyline 8452 * @inherits Path 8453 * 8454 * A class for drawing polyline overlays on a map. Extends `Path`. 8455 * 8456 * @example 8457 * 8458 * ```js 8459 * // create a red polyline from an array of LatLng points 8460 * var latlngs = [ 8461 * [45.51, -122.68], 8462 * [37.77, -122.43], 8463 * [34.04, -118.2] 8464 * ]; 8465 * 8466 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8467 * 8468 * // zoom the map to the polyline 8469 * map.fitBounds(polyline.getBounds()); 8470 * ``` 8471 * 8472 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8473 * 8474 * ```js 8475 * // create a red polyline from an array of arrays of LatLng points 8476 * var latlngs = [ 8477 * [[45.51, -122.68], 8478 * [37.77, -122.43], 8479 * [34.04, -118.2]], 8480 * [[40.78, -73.91], 8481 * [41.83, -87.62], 8482 * [32.76, -96.72]] 8483 * ]; 8484 * ``` 8485 */ 8486 8487 8488var Polyline = Path.extend({ 8489 8490 // @section 8491 // @aka Polyline options 8492 options: { 8493 // @option smoothFactor: Number = 1.0 8494 // How much to simplify the polyline on each zoom level. More means 8495 // better performance and smoother look, and less means more accurate representation. 8496 smoothFactor: 1.0, 8497 8498 // @option noClip: Boolean = false 8499 // Disable polyline clipping. 8500 noClip: false 8501 }, 8502 8503 initialize: function (latlngs, options) { 8504 setOptions(this, options); 8505 this._setLatLngs(latlngs); 8506 }, 8507 8508 // @method getLatLngs(): LatLng[] 8509 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8510 getLatLngs: function () { 8511 return this._latlngs; 8512 }, 8513 8514 // @method setLatLngs(latlngs: LatLng[]): this 8515 // Replaces all the points in the polyline with the given array of geographical points. 8516 setLatLngs: function (latlngs) { 8517 this._setLatLngs(latlngs); 8518 return this.redraw(); 8519 }, 8520 8521 // @method isEmpty(): Boolean 8522 // Returns `true` if the Polyline has no LatLngs. 8523 isEmpty: function () { 8524 return !this._latlngs.length; 8525 }, 8526 8527 // @method closestLayerPoint(p: Point): Point 8528 // Returns the point closest to `p` on the Polyline. 8529 closestLayerPoint: function (p) { 8530 var minDistance = Infinity, 8531 minPoint = null, 8532 closest = _sqClosestPointOnSegment, 8533 p1, p2; 8534 8535 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8536 var points = this._parts[j]; 8537 8538 for (var i = 1, len = points.length; i < len; i++) { 8539 p1 = points[i - 1]; 8540 p2 = points[i]; 8541 8542 var sqDist = closest(p, p1, p2, true); 8543 8544 if (sqDist < minDistance) { 8545 minDistance = sqDist; 8546 minPoint = closest(p, p1, p2); 8547 } 8548 } 8549 } 8550 if (minPoint) { 8551 minPoint.distance = Math.sqrt(minDistance); 8552 } 8553 return minPoint; 8554 }, 8555 8556 // @method getCenter(): LatLng 8557 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline. 8558 getCenter: function () { 8559 // throws error when not yet added to map as this center calculation requires projected coordinates 8560 if (!this._map) { 8561 throw new Error('Must add layer to map before using getCenter()'); 8562 } 8563 return polylineCenter(this._defaultShape(), this._map.options.crs); 8564 }, 8565 8566 // @method getBounds(): LatLngBounds 8567 // Returns the `LatLngBounds` of the path. 8568 getBounds: function () { 8569 return this._bounds; 8570 }, 8571 8572 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this 8573 // Adds a given point to the polyline. By default, adds to the first ring of 8574 // the polyline in case of a multi-polyline, but can be overridden by passing 8575 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8576 addLatLng: function (latlng, latlngs) { 8577 latlngs = latlngs || this._defaultShape(); 8578 latlng = toLatLng(latlng); 8579 latlngs.push(latlng); 8580 this._bounds.extend(latlng); 8581 return this.redraw(); 8582 }, 8583 8584 _setLatLngs: function (latlngs) { 8585 this._bounds = new LatLngBounds(); 8586 this._latlngs = this._convertLatLngs(latlngs); 8587 }, 8588 8589 _defaultShape: function () { 8590 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8591 }, 8592 8593 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8594 _convertLatLngs: function (latlngs) { 8595 var result = [], 8596 flat = isFlat(latlngs); 8597 8598 for (var i = 0, len = latlngs.length; i < len; i++) { 8599 if (flat) { 8600 result[i] = toLatLng(latlngs[i]); 8601 this._bounds.extend(result[i]); 8602 } else { 8603 result[i] = this._convertLatLngs(latlngs[i]); 8604 } 8605 } 8606 8607 return result; 8608 }, 8609 8610 _project: function () { 8611 var pxBounds = new Bounds(); 8612 this._rings = []; 8613 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8614 8615 if (this._bounds.isValid() && pxBounds.isValid()) { 8616 this._rawPxBounds = pxBounds; 8617 this._updateBounds(); 8618 } 8619 }, 8620 8621 _updateBounds: function () { 8622 var w = this._clickTolerance(), 8623 p = new Point(w, w); 8624 8625 if (!this._rawPxBounds) { 8626 return; 8627 } 8628 8629 this._pxBounds = new Bounds([ 8630 this._rawPxBounds.min.subtract(p), 8631 this._rawPxBounds.max.add(p) 8632 ]); 8633 }, 8634 8635 // recursively turns latlngs into a set of rings with projected coordinates 8636 _projectLatlngs: function (latlngs, result, projectedBounds) { 8637 var flat = latlngs[0] instanceof LatLng, 8638 len = latlngs.length, 8639 i, ring; 8640 8641 if (flat) { 8642 ring = []; 8643 for (i = 0; i < len; i++) { 8644 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8645 projectedBounds.extend(ring[i]); 8646 } 8647 result.push(ring); 8648 } else { 8649 for (i = 0; i < len; i++) { 8650 this._projectLatlngs(latlngs[i], result, projectedBounds); 8651 } 8652 } 8653 }, 8654 8655 // clip polyline by renderer bounds so that we have less to render for performance 8656 _clipPoints: function () { 8657 var bounds = this._renderer._bounds; 8658 8659 this._parts = []; 8660 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8661 return; 8662 } 8663 8664 if (this.options.noClip) { 8665 this._parts = this._rings; 8666 return; 8667 } 8668 8669 var parts = this._parts, 8670 i, j, k, len, len2, segment, points; 8671 8672 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8673 points = this._rings[i]; 8674 8675 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8676 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8677 8678 if (!segment) { continue; } 8679 8680 parts[k] = parts[k] || []; 8681 parts[k].push(segment[0]); 8682 8683 // if segment goes out of screen, or it's the last one, it's the end of the line part 8684 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8685 parts[k].push(segment[1]); 8686 k++; 8687 } 8688 } 8689 } 8690 }, 8691 8692 // simplify each clipped part of the polyline for performance 8693 _simplifyPoints: function () { 8694 var parts = this._parts, 8695 tolerance = this.options.smoothFactor; 8696 8697 for (var i = 0, len = parts.length; i < len; i++) { 8698 parts[i] = simplify(parts[i], tolerance); 8699 } 8700 }, 8701 8702 _update: function () { 8703 if (!this._map) { return; } 8704 8705 this._clipPoints(); 8706 this._simplifyPoints(); 8707 this._updatePath(); 8708 }, 8709 8710 _updatePath: function () { 8711 this._renderer._updatePoly(this); 8712 }, 8713 8714 // Needed by the `Canvas` renderer for interactivity 8715 _containsPoint: function (p, closed) { 8716 var i, j, k, len, len2, part, 8717 w = this._clickTolerance(); 8718 8719 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8720 8721 // hit detection for polylines 8722 for (i = 0, len = this._parts.length; i < len; i++) { 8723 part = this._parts[i]; 8724 8725 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8726 if (!closed && (j === 0)) { continue; } 8727 8728 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8729 return true; 8730 } 8731 } 8732 } 8733 return false; 8734 } 8735}); 8736 8737// @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8738// Instantiates a polyline object given an array of geographical points and 8739// optionally an options object. You can create a `Polyline` object with 8740// multiple separate lines (`MultiPolyline`) by passing an array of arrays 8741// of geographic points. 8742function polyline(latlngs, options) { 8743 return new Polyline(latlngs, options); 8744} 8745 8746// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8747Polyline._flat = _flat; 8748 8749/* 8750 * @class Polygon 8751 * @aka L.Polygon 8752 * @inherits Polyline 8753 * 8754 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8755 * 8756 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one â it's better to filter out such points. 8757 * 8758 * 8759 * @example 8760 * 8761 * ```js 8762 * // create a red polygon from an array of LatLng points 8763 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 8764 * 8765 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8766 * 8767 * // zoom the map to the polygon 8768 * map.fitBounds(polygon.getBounds()); 8769 * ``` 8770 * 8771 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape: 8772 * 8773 * ```js 8774 * var latlngs = [ 8775 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8776 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8777 * ]; 8778 * ``` 8779 * 8780 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8781 * 8782 * ```js 8783 * var latlngs = [ 8784 * [ // first polygon 8785 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8786 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8787 * ], 8788 * [ // second polygon 8789 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 8790 * ] 8791 * ]; 8792 * ``` 8793 */ 8794 8795var Polygon = Polyline.extend({ 8796 8797 options: { 8798 fill: true 8799 }, 8800 8801 isEmpty: function () { 8802 return !this._latlngs.length || !this._latlngs[0].length; 8803 }, 8804 8805 // @method getCenter(): LatLng 8806 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon. 8807 getCenter: function () { 8808 // throws error when not yet added to map as this center calculation requires projected coordinates 8809 if (!this._map) { 8810 throw new Error('Must add layer to map before using getCenter()'); 8811 } 8812 return polygonCenter(this._defaultShape(), this._map.options.crs); 8813 }, 8814 8815 _convertLatLngs: function (latlngs) { 8816 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 8817 len = result.length; 8818 8819 // remove last point if it equals first one 8820 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 8821 result.pop(); 8822 } 8823 return result; 8824 }, 8825 8826 _setLatLngs: function (latlngs) { 8827 Polyline.prototype._setLatLngs.call(this, latlngs); 8828 if (isFlat(this._latlngs)) { 8829 this._latlngs = [this._latlngs]; 8830 } 8831 }, 8832 8833 _defaultShape: function () { 8834 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8835 }, 8836 8837 _clipPoints: function () { 8838 // polygons need a different clipping algorithm so we redefine that 8839 8840 var bounds = this._renderer._bounds, 8841 w = this.options.weight, 8842 p = new Point(w, w); 8843 8844 // increase clip padding by stroke width to avoid stroke on clip edges 8845 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8846 8847 this._parts = []; 8848 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8849 return; 8850 } 8851 8852 if (this.options.noClip) { 8853 this._parts = this._rings; 8854 return; 8855 } 8856 8857 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8858 clipped = clipPolygon(this._rings[i], bounds, true); 8859 if (clipped.length) { 8860 this._parts.push(clipped); 8861 } 8862 } 8863 }, 8864 8865 _updatePath: function () { 8866 this._renderer._updatePoly(this, true); 8867 }, 8868 8869 // Needed by the `Canvas` renderer for interactivity 8870 _containsPoint: function (p) { 8871 var inside = false, 8872 part, p1, p2, i, j, k, len, len2; 8873 8874 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8875 8876 // ray casting algorithm for detecting if point is in polygon 8877 for (i = 0, len = this._parts.length; i < len; i++) { 8878 part = this._parts[i]; 8879 8880 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8881 p1 = part[j]; 8882 p2 = part[k]; 8883 8884 if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { 8885 inside = !inside; 8886 } 8887 } 8888 } 8889 8890 // also check if it's on polygon stroke 8891 return inside || Polyline.prototype._containsPoint.call(this, p, true); 8892 } 8893 8894}); 8895 8896 8897// @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8898function polygon(latlngs, options) { 8899 return new Polygon(latlngs, options); 8900} 8901 8902/*
8902 8903 * @class GeoJSON 8904 * @aka L.GeoJSON 8905 * @inherits FeatureGroup 8906 * 8907 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 8908 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 8909 * 8910 * @example 8911 * 8912 * ```js 8913 * L.geoJSON(data, { 8914 * style: function (feature) { 8915 * return {color: feature.properties.color}; 8916 * } 8917 * }).bindPopup(function (layer) { 8918 * return layer.feature.properties.description; 8919 * }).addTo(map); 8920 * ``` 8921 */ 8922 8923var GeoJSON = FeatureGroup.extend({ 8924 8925 /* @section 8926 * @aka GeoJSON options 8927 * 8928 * @option pointToLayer: Function = * 8929 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 8930 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 8931 * The default is to spawn a default `Marker`: 8932 * ```js 8933 * function(geoJsonPoint, latlng) { 8934 * return L.marker(latlng); 8935 * } 8936 * ``` 8937 * 8938 * @option style: Function = * 8939 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8940 * called internally when data is added. 8941 * The default value is to not override any defaults: 8942 * ```js 8943 * function (geoJsonFeature) { 8944 * return {} 8945 * } 8946 * ``` 8947 * 8948 * @option onEachFeature: Function = * 8949 * A `Function` that will be called once for each created `Feature`, after it has 8950 * been created and styled. Useful for attaching events and popups to features. 8951 * The default is to do nothing with the newly created layers: 8952 * ```js 8953 * function (feature, layer) {} 8954 * ``` 8955 * 8956 * @option filter: Function = * 8957 * A `Function` that will be used to decide whether to include a feature or not. 8958 * The default is to include all features: 8959 * ```js 8960 * function (geoJsonFeature) { 8961 * return true; 8962 * } 8963 * ``` 8964 * Note: dynamically changing the `filter` option will have effect only on newly 8965 * added data. It will _not_ re-evaluate already included features. 8966 * 8967 * @option coordsToLatLng: Function = * 8968 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 8969 * The default is the `coordsToLatLng` static method. 8970 * 8971 * @option markersInheritOptions: Boolean = false 8972 * Whether default Markers for "Point" type Features inherit from group options. 8973 */ 8974 8975 initialize: function (geojson, options) { 8976 setOptions(this, options); 8977 8978 this._layers = {}; 8979 8980 if (geojson) { 8981 this.addData(geojson); 8982 } 8983 }, 8984 8985 // @method addData( <GeoJSON> data ): this 8986 // Adds a GeoJSON object to the layer. 8987 addData: function (geojson) { 8988 var features = isArray(geojson) ? geojson : geojson.features, 8989 i, len, feature; 8990 8991 if (features) { 8992 for (i = 0, len = features.length; i < len; i++) { 8993 // only add this if geometry or geometries are set and not null 8994 feature = features[i]; 8995 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 8996 this.addData(feature); 8997 } 8998 } 8999 return this; 9000 } 9001 9002 var options = this.options; 9003 9004 if (options.filter && !options.filter(geojson)) { return this; } 9005 9006 var layer = geometryToLayer(geojson, options); 9007 if (!layer) { 9008 return this; 9009 } 9010 layer.feature = asFeature(geojson); 9011 9012 layer.defaultOptions = layer.options; 9013 this.resetStyle(layer); 9014 9015 if (options.onEachFeature) { 9016 options.onEachFeature(geojson, layer); 9017 } 9018 9019 return this.addLayer(layer); 9020 }, 9021 9022 // @method resetStyle( <Path> layer? ): this 9023 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 9024 // If `layer` is omitted, the style of all features in the current layer is reset. 9025 resetStyle: function (layer) { 9026 if (layer === undefined) { 9027 return this.eachLayer(this.resetStyle, this); 9028 } 9029 // reset any custom styles 9030 layer.options = extend({}, layer.defaultOptions); 9031 this._setLayerStyle(layer, this.options.style); 9032 return this; 9033 }, 9034 9035 // @method setStyle( <Function> style ): this 9036 // Changes styles of GeoJSON vector layers with the given style function. 9037 setStyle: function (style) { 9038 return this.eachLayer(function (layer) { 9039 this._setLayerStyle(layer, style); 9040 }, this); 9041 }, 9042 9043 _setLayerStyle: function (layer, style) { 9044 if (layer.setStyle) { 9045 if (typeof style === 'function') { 9046 style = style(layer.feature); 9047 } 9048 layer.setStyle(style); 9049 } 9050 } 9051}); 9052 9053// @section 9054// There are several static functions which can be called without instantiating L.GeoJSON: 9055 9056// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 9057// Creates a `Layer` from a given GeoJSON feature. Can use a custom 9058// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 9059// functions if provided as options. 9060function geometryToLayer(geojson, options) { 9061 9062 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 9063 coords = geometry ? geometry.coordinates : null, 9064 layers = [], 9065 pointToLayer = options && options.pointToLayer, 9066 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 9067 latlng, latlngs, i, len; 9068 9069 if (!coords && !geometry) { 9070 return null; 9071 } 9072 9073 switch (geometry.type) { 9074 case 'Point': 9075 latlng = _coordsToLatLng(coords); 9076 return _pointToLayer(pointToLayer, geojson, latlng, options); 9077 9078 case 'MultiPoint':
vendor: 29,750 bytes, lines 9079-10119
9079 for (i = 0, len = coords.length; i < len; i++) { 9080 latlng = _coordsToLatLng(coords[i]); 9081 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 9082 } 9083 return new FeatureGroup(layers); 9084 9085 case 'LineString': 9086 case 'MultiLineString': 9087 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 9088 return new Polyline(latlngs, options); 9089 9090 case 'Polygon': 9091 case 'MultiPolygon': 9092 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 9093 return new Polygon(latlngs, options); 9094 9095 case 'GeometryCollection': 9096 for (i = 0, len = geometry.geometries.length; i < len; i++) { 9097 var geoLayer = geometryToLayer({ 9098 geometry: geometry.geometries[i], 9099 type: 'Feature', 9100 properties: geojson.properties 9101 }, options); 9102 9103 if (geoLayer) { 9104 layers.push(geoLayer); 9105 } 9106 } 9107 return new FeatureGroup(layers); 9108 9109 case 'FeatureCollection': 9110 for (i = 0, len = geometry.features.length; i < len; i++) { 9111 var featureLayer = geometryToLayer(geometry.features[i], options); 9112 9113 if (featureLayer) { 9114 layers.push(featureLayer); 9115 } 9116 } 9117 return new FeatureGroup(layers); 9118 9119 default: 9120 throw new Error('Invalid GeoJSON object.'); 9121 } 9122} 9123 9124function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 9125 return pointToLayerFn ? 9126 pointToLayerFn(geojson, latlng) : 9127 new Marker(latlng, options && options.markersInheritOptions && options); 9128} 9129 9130// @function coordsToLatLng(coords: Array): LatLng 9131// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 9132// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 9133function coordsToLatLng(coords) { 9134 return new LatLng(coords[1], coords[0], coords[2]); 9135} 9136 9137// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 9138// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 9139// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 9140// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 9141function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 9142 var latlngs = []; 9143 9144 for (var i = 0, len = coords.length, latlng; i < len; i++) { 9145 latlng = levelsDeep ? 9146 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 9147 (_coordsToLatLng || coordsToLatLng)(coords[i]); 9148 9149 latlngs.push(latlng); 9150 } 9151 9152 return latlngs; 9153} 9154 9155// @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array 9156// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 9157// Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 9158function latLngToCoords(latlng, precision) { 9159 latlng = toLatLng(latlng); 9160 return latlng.alt !== undefined ? 9161 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 9162 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 9163} 9164 9165// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array 9166// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 9167// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default. 9168// Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 9169function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 9170 var coords = []; 9171 9172 for (var i = 0, len = latlngs.length; i < len; i++) { 9173 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion 9174 coords.push(levelsDeep ? 9175 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) : 9176 latLngToCoords(latlngs[i], precision)); 9177 } 9178 9179 if (!levelsDeep && closed && coords.length > 0) { 9180 coords.push(coords[0].slice()); 9181 } 9182 9183 return coords; 9184} 9185 9186function getFeature(layer, newGeometry) { 9187 return layer.feature ? 9188 extend({}, layer.feature, {geometry: newGeometry}) : 9189 asFeature(newGeometry); 9190} 9191 9192// @function asFeature(geojson: Object): Object 9193// Normalize GeoJSON geometries/features into GeoJSON features. 9194function asFeature(geojson) { 9195 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 9196 return geojson; 9197 } 9198 9199 return { 9200 type: 'Feature', 9201 properties: {}, 9202 geometry: geojson 9203 }; 9204} 9205 9206var PointToGeoJSON = { 9207 toGeoJSON: function (precision) { 9208 return getFeature(this, { 9209 type: 'Point', 9210 coordinates: latLngToCoords(this.getLatLng(), precision) 9211 }); 9212 } 9213}; 9214 9215// @namespace Marker 9216// @section Other methods 9217// @method toGeoJSON(precision?: Number|false): Object 9218// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9219// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 9220Marker.include(PointToGeoJSON); 9221 9222// @namespace CircleMarker 9223// @method toGeoJSON(precision?: Number|false): Object 9224// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9225// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 9226Circle.include(PointToGeoJSON); 9227CircleMarker.include(PointToGeoJSON); 9228 9229 9230// @namespace Polyline 9231// @method toGeoJSON(precision?: Number|false): Object 9232// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9233// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 9234Polyline.include({ 9235 toGeoJSON: function (precision) { 9236 var multi = !isFlat(this._latlngs); 9237 9238 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 9239 9240 return getFeature(this, { 9241 type: (multi ? 'Multi' : '') + 'LineString', 9242 coordinates: coords 9243 }); 9244 } 9245}); 9246 9247// @namespace Polygon 9248// @method toGeoJSON(precision?: Number|false): Object 9249// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9250// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 9251Polygon.include({ 9252 toGeoJSON: function (precision) { 9253 var holes = !isFlat(this._latlngs), 9254 multi = holes && !isFlat(this._latlngs[0]); 9255 9256 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 9257 9258 if (!holes) { 9259 coords = [coords]; 9260 } 9261 9262 return getFeature(this, { 9263 type: (multi ? 'Multi' : '') + 'Polygon', 9264 coordinates: coords 9265 }); 9266 } 9267}); 9268 9269 9270// @namespace LayerGroup 9271LayerGroup.include({ 9272 toMultiPoint: function (precision) { 9273 var coords = []; 9274 9275 this.eachLayer(function (layer) { 9276 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 9277 }); 9278 9279 return getFeature(this, { 9280 type: 'MultiPoint', 9281 coordinates: coords 9282 }); 9283 }, 9284 9285 // @method toGeoJSON(precision?: Number|false): Object 9286 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9287 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 9288 toGeoJSON: function (precision) { 9289 9290 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 9291 9292 if (type === 'MultiPoint') { 9293 return this.toMultiPoint(precision); 9294 } 9295 9296 var isGeometryCollection = type === 'GeometryCollection', 9297 jsons = []; 9298 9299 this.eachLayer(function (layer) { 9300 if (layer.toGeoJSON) { 9301 var json = layer.toGeoJSON(precision); 9302 if (isGeometryCollection) { 9303 jsons.push(json.geometry); 9304 } else { 9305 var feature = asFeature(json); 9306 // Squash nested feature collections 9307 if (feature.type === 'FeatureCollection') { 9308 jsons.push.apply(jsons, feature.features); 9309 } else { 9310 jsons.push(feature); 9311 } 9312 } 9313 } 9314 }); 9315 9316 if (isGeometryCollection) { 9317 return getFeature(this, { 9318 geometries: jsons, 9319 type: 'GeometryCollection' 9320 }); 9321 } 9322 9323 return { 9324 type: 'FeatureCollection', 9325 features: jsons 9326 }; 9327 } 9328}); 9329 9330// @namespace GeoJSON 9331// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9332// Creates a GeoJSON layer. Optionally accepts an object in 9333// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 9334// (you can alternatively add it later with `addData` method) and an `options` object. 9335function geoJSON(geojson, options) { 9336 return new GeoJSON(geojson, options); 9337} 9338 9339// Backward compatibility. 9340var geoJson = geoJSON; 9341 9342/* 9343 * @class ImageOverlay 9344 * @aka L.ImageOverlay 9345 * @inherits Interactive layer 9346 * 9347 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 9348 * 9349 * @example 9350 * 9351 * ```js 9352 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 9353 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 9354 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 9355 * ``` 9356 */ 9357 9358var ImageOverlay = Layer.extend({ 9359 9360 // @section 9361 // @aka ImageOverlay options 9362 options: { 9363 // @option opacity: Number = 1.0 9364 // The opacity of the image overlay. 9365 opacity: 1, 9366 9367 // @option alt: String = '' 9368 // Text for the `alt` attribute of the image (useful for accessibility). 9369 alt: '', 9370 9371 // @option interactive: Boolean = false 9372 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 9373 interactive: false, 9374 9375 // @option crossOrigin: Boolean|String = false 9376 // Whether the crossOrigin attribute will be added to the image. 9377 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data. 9378 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 9379 crossOrigin: false, 9380 9381 // @option errorOverlayUrl: String = '' 9382 // URL to the overlay image to show in place of the overlay that failed to load. 9383 errorOverlayUrl: '', 9384 9385 // @option zIndex: Number = 1 9386 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 9387 zIndex: 1, 9388 9389 // @option className: String = '' 9390 // A custom class name to assign to the image. Empty by default. 9391 className: '' 9392 }, 9393 9394 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 9395 this._url = url; 9396 this._bounds = toLatLngBounds(bounds); 9397 9398 setOptions(this, options); 9399 }, 9400 9401 onAdd: function () { 9402 if (!this._image) { 9403 this._initImage(); 9404 9405 if (this.options.opacity < 1) { 9406 this._updateOpacity(); 9407 } 9408 } 9409 9410 if (this.options.interactive) { 9411 addClass(this._image, 'leaflet-interactive'); 9412 this.addInteractiveTarget(this._image); 9413 } 9414 9415 this.getPane().appendChild(this._image); 9416 this._reset(); 9417 }, 9418 9419 onRemove: function () { 9420 remove(this._image); 9421 if (this.options.interactive) { 9422 this.removeInteractiveTarget(this._image); 9423 } 9424 }, 9425 9426 // @method setOpacity(opacity: Number): this 9427 // Sets the opacity of the overlay. 9428 setOpacity: function (opacity) { 9429 this.options.opacity = opacity; 9430 9431 if (this._image) { 9432 this._updateOpacity(); 9433 } 9434 return this; 9435 }, 9436 9437 setStyle: function (styleOpts) { 9438 if (styleOpts.opacity) { 9439 this.setOpacity(styleOpts.opacity); 9440 } 9441 return this; 9442 }, 9443 9444 // @method bringToFront(): this 9445 // Brings the layer to the top of all overlays. 9446 bringToFront: function () { 9447 if (this._map) { 9448 toFront(this._image); 9449 } 9450 return this; 9451 }, 9452 9453 // @method bringToBack(): this 9454 // Brings the layer to the bottom of all overlays. 9455 bringToBack: function () { 9456 if (this._map) { 9457 toBack(this._image); 9458 } 9459 return this; 9460 }, 9461 9462 // @method setUrl(url: String): this 9463 // Changes the URL of the image. 9464 setUrl: function (url) { 9465 this._url = url; 9466 9467 if (this._image) { 9468 this._image.src = url; 9469 } 9470 return this; 9471 }, 9472 9473 // @method setBounds(bounds: LatLngBounds): this 9474 // Update the bounds that this ImageOverlay covers 9475 setBounds: function (bounds) { 9476 this._bounds = toLatLngBounds(bounds); 9477 9478 if (this._map) { 9479 this._reset(); 9480 } 9481 return this; 9482 }, 9483 9484 getEvents: function () { 9485 var events = { 9486 zoom: this._reset, 9487 viewreset: this._reset 9488 }; 9489 9490 if (this._zoomAnimated) { 9491 events.zoomanim = this._animateZoom; 9492 } 9493 9494 return events; 9495 }, 9496 9497 // @method setZIndex(value: Number): this 9498 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9499 setZIndex: function (value) { 9500 this.options.zIndex = value; 9501 this._updateZIndex(); 9502 return this; 9503 }, 9504 9505 // @method getBounds(): LatLngBounds 9506 // Get the bounds that this ImageOverlay covers 9507 getBounds: function () { 9508 return this._bounds; 9509 }, 9510 9511 // @method getElement(): HTMLElement 9512 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9513 // used by this overlay. 9514 getElement: function () { 9515 return this._image; 9516 }, 9517 9518 _initImage: function () { 9519 var wasElementSupplied = this._url.tagName === 'IMG'; 9520 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9521 9522 addClass(img, 'leaflet-image-layer'); 9523 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9524 if (this.options.className) { addClass(img, this.options.className); } 9525 9526 img.onselectstart = falseFn; 9527 img.onmousemove = falseFn; 9528 9529 // @event load: Event 9530 // Fired when the ImageOverlay layer has loaded its image 9531 img.onload = bind(this.fire, this, 'load'); 9532 img.onerror = bind(this._overlayOnError, this, 'error'); 9533 9534 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9535 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9536 } 9537 9538 if (this.options.zIndex) { 9539 this._updateZIndex(); 9540 } 9541 9542 if (wasElementSupplied) { 9543 this._url = img.src; 9544 return; 9545 } 9546 9547 img.src = this._url; 9548 img.alt = this.options.alt; 9549 }, 9550 9551 _animateZoom: function (e) { 9552 var scale = this._map.getZoomScale(e.zoom), 9553 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9554 9555 setTransform(this._image, offset, scale); 9556 }, 9557 9558 _reset: function () { 9559 var image = this._image, 9560 bounds = new Bounds( 9561 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9562 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9563 size = bounds.getSize(); 9564 9565 setPosition(image, bounds.min); 9566 9567 image.style.width = size.x + 'px'; 9568 image.style.height = size.y + 'px'; 9569 }, 9570 9571 _updateOpacity: function () { 9572 setOpacity(this._image, this.options.opacity); 9573 }, 9574 9575 _updateZIndex: function () { 9576 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9577 this._image.style.zIndex = this.options.zIndex; 9578 } 9579 }, 9580 9581 _overlayOnError: function () { 9582 // @event error: Event 9583 // Fired when the ImageOverlay layer fails to load its image 9584 this.fire('error'); 9585 9586 var errorUrl = this.options.errorOverlayUrl; 9587 if (errorUrl && this._url !== errorUrl) { 9588 this._url = errorUrl; 9589 this._image.src = errorUrl; 9590 } 9591 }, 9592 9593 // @method getCenter(): LatLng 9594 // Returns the center of the ImageOverlay. 9595 getCenter: function () { 9596 return this._bounds.getCenter(); 9597 } 9598}); 9599 9600// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9601// Instantiates an image overlay object given the URL of the image and the 9602// geographical bounds it is tied to. 9603var imageOverlay = function (url, bounds, options) { 9604 return new ImageOverlay(url, bounds, options); 9605}; 9606 9607/* 9608 * @class VideoOverlay 9609 * @aka L.VideoOverlay 9610 * @inherits ImageOverlay 9611 * 9612 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9613 * 9614 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9615 * HTML5 element. 9616 * 9617 * @example 9618 * 9619 * ```js 9620 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9621 * videoBounds = [[ 32, -130], [ 13, -100]]; 9622 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9623 * ``` 9624 */ 9625 9626var VideoOverlay = ImageOverlay.extend({ 9627 9628 // @section 9629 // @aka VideoOverlay options 9630 options: { 9631 // @option autoplay: Boolean = true 9632 // Whether the video starts playing automatically when loaded. 9633 // On some browsers autoplay will only work with `muted: true` 9634 autoplay: true, 9635 9636 // @option loop: Boolean = true 9637 // Whether the video will loop back to the beginning when played. 9638 loop: true, 9639 9640 // @option keepAspectRatio: Boolean = true 9641 // Whether the video will save aspect ratio after the projection. 9642 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 9643 keepAspectRatio: true, 9644 9645 // @option muted: Boolean = false 9646 // Whether the video starts on mute when loaded. 9647 muted: false, 9648 9649 // @option playsInline: Boolean = true 9650 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode. 9651 playsInline: true 9652 }, 9653 9654 _initImage: function () { 9655 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9656 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9657 9658 addClass(vid, 'leaflet-image-layer'); 9659 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9660 if (this.options.className) { addClass(vid, this.options.className); } 9661 9662 vid.onselectstart = falseFn; 9663 vid.onmousemove = falseFn; 9664 9665 // @event load: Event 9666 // Fired when the video has finished loading the first frame 9667 vid.onloadeddata = bind(this.fire, this, 'load'); 9668 9669 if (wasElementSupplied) { 9670 var sourceElements = vid.getElementsByTagName('source'); 9671 var sources = []; 9672 for (var j = 0; j < sourceElements.length; j++) { 9673 sources.push(sourceElements[j].src); 9674 } 9675 9676 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9677 return; 9678 } 9679 9680 if (!isArray(this._url)) { this._url = [this._url]; } 9681 9682 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) { 9683 vid.style['objectFit'] = 'fill'; 9684 } 9685 vid.autoplay = !!this.options.autoplay; 9686 vid.loop = !!this.options.loop; 9687 vid.muted = !!this.options.muted; 9688 vid.playsInline = !!this.options.playsInline; 9689 for (var i = 0; i < this._url.length; i++) { 9690 var source = create$1('source'); 9691 source.src = this._url[i]; 9692 vid.appendChild(source); 9693 } 9694 } 9695 9696 // @method getElement(): HTMLVideoElement 9697 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9698 // used by this overlay. 9699}); 9700 9701 9702// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9703// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9704// geographical bounds it is tied to. 9705 9706function videoOverlay(video, bounds, options) { 9707 return new VideoOverlay(video, bounds, options); 9708} 9709 9710/* 9711 * @class SVGOverlay 9712 * @aka L.SVGOverlay 9713 * @inherits ImageOverlay 9714 * 9715 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`. 9716 * 9717 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. 9718 * 9719 * @example 9720 * 9721 * ```js 9722 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9723 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); 9724 * svgElement.setAttribute('viewBox', "0 0 200 200"); 9725 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>'; 9726 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ]; 9727 * L.svgOverlay(svgElement, svgElementBounds).addTo(map); 9728 * ``` 9729 */ 9730 9731var SVGOverlay = ImageOverlay.extend({ 9732 _initImage: function () { 9733 var el = this._image = this._url; 9734 9735 addClass(el, 'leaflet-image-layer'); 9736 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); } 9737 if (this.options.className) { addClass(el, this.options.className); } 9738 9739 el.onselectstart = falseFn; 9740 el.onmousemove = falseFn; 9741 } 9742 9743 // @method getElement(): SVGElement 9744 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement) 9745 // used by this overlay. 9746}); 9747 9748 9749// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options) 9750// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to. 9751// A viewBox attribute is required on the SVG element to zoom in and out properly. 9752 9753function svgOverlay(el, bounds, options) { 9754 return new SVGOverlay(el, bounds, options); 9755} 9756 9757/* 9758 * @class DivOverlay 9759 * @inherits Interactive layer 9760 * @aka L.DivOverlay 9761 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins. 9762 */ 9763 9764// @namespace DivOverlay 9765var DivOverlay = Layer.extend({ 9766 9767 // @section 9768 // @aka DivOverlay options 9769 options: { 9770 // @option interactive: Boolean = false 9771 // If true, the popup/tooltip will listen to the mouse events. 9772 interactive: false, 9773 9774 // @option offset: Point = Point(0, 0) 9775 // The offset of the overlay position. 9776 offset: [0, 0], 9777 9778 // @option className: String = '' 9779 // A custom CSS class name to assign to the overlay. 9780 className: '', 9781 9782 // @option pane: String = undefined 9783 // `Map pane` where the overlay will be added. 9784 pane: undefined, 9785 9786 // @option content: String|HTMLElement|Function = '' 9787 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be 9788 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay. 9789 content: '' 9790 }, 9791 9792 initialize: function (options, source) { 9793 if (options && (options instanceof LatLng || isArray(options))) { 9794 this._latlng = toLatLng(options); 9795 setOptions(this, source); 9796 } else { 9797 setOptions(this, options); 9798 this._source = source; 9799 } 9800 if (this.options.content) { 9801 this._content = this.options.content; 9802 } 9803 }, 9804 9805 // @method openOn(map: Map): this 9806 // Adds the overlay to the map. 9807 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`. 9808 openOn: function (map) { 9809 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 9810 if (!map.hasLayer(this)) { 9811 map.addLayer(this); 9812 } 9813 return this; 9814 }, 9815 9816 // @method close(): this 9817 // Closes the overlay. 9818 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)` 9819 // and `layer.closePopup()`/`.closeTooltip()`. 9820 close: function () { 9821 if (this._map) { 9822 this._map.removeLayer(this); 9823 } 9824 return this; 9825 }, 9826 9827 // @method toggle(layer?: Layer): this 9828 // Opens or closes the overlay bound to layer depending on its current state. 9829 // Argument may be omitted only for overlay bound to layer. 9830 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`. 9831 toggle: function (layer) { 9832 if (this._map) { 9833 this.close(); 9834 } else { 9835 if (arguments.length) { 9836 this._source = layer; 9837 } else { 9838 layer = this._source; 9839 } 9840 this._prepareOpen(); 9841 9842 // open the overlay on the map 9843 this.openOn(layer._map); 9844 } 9845 return this; 9846 }, 9847 9848 onAdd: function (map) { 9849 this._zoomAnimated = map._zoomAnimated; 9850 9851 if (!this._container) { 9852 this._initLayout(); 9853 } 9854 9855 if (map._fadeAnimated) { 9856 setOpacity(this._container, 0); 9857 } 9858 9859 clearTimeout(this._removeTimeout); 9860 this.getPane().appendChild(this._container); 9861 this.update(); 9862 9863 if (map._fadeAnimated) { 9864 setOpacity(this._container, 1); 9865 } 9866 9867 this.bringToFront(); 9868 9869 if (this.options.interactive) { 9870 addClass(this._container, 'leaflet-interactive'); 9871 this.addInteractiveTarget(this._container); 9872 } 9873 }, 9874 9875 onRemove: function (map) { 9876 if (map._fadeAnimated) { 9877 setOpacity(this._container, 0); 9878 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200); 9879 } else { 9880 remove(this._container); 9881 } 9882 9883 if (this.options.interactive) { 9884 removeClass(this._container, 'leaflet-interactive'); 9885 this.removeInteractiveTarget(this._container); 9886 } 9887 }, 9888 9889 // @namespace DivOverlay 9890 // @method getLatLng: LatLng 9891 // Returns the geographical point of the overlay. 9892 getLatLng: function () { 9893 return this._latlng; 9894 }, 9895 9896 // @method setLatLng(latlng: LatLng): this 9897 // Sets the geographical point where the overlay will open. 9898 setLatLng: function (latlng) { 9899 this._latlng = toLatLng(latlng); 9900 if (this._map) { 9901 this._updatePosition(); 9902 this._adjustPan(); 9903 } 9904 return this; 9905 }, 9906 9907 // @method getContent: String|HTMLElement 9908 // Returns the content of the overlay. 9909 getContent: function () { 9910 return this._content; 9911 }, 9912 9913 // @method setContent(htmlContent: String|HTMLElement|Function): this 9914 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function. 9915 // The function should return a `String` or `HTMLElement` to be used in the overlay. 9916 setContent: function (content) { 9917 this._content = content; 9918 this.update(); 9919 return this; 9920 }, 9921 9922 // @method getElement: String|HTMLElement 9923 // Returns the HTML container of the overlay. 9924 getElement: function () { 9925 return this._container; 9926 }, 9927 9928 // @method update: null 9929 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded. 9930 update: function () { 9931 if (!this._map) { return; } 9932 9933 this._container.style.visibility = 'hidden'; 9934 9935 this._updateContent(); 9936 this._updateLayout(); 9937 this._updatePosition(); 9938 9939 this._container.style.visibility = ''; 9940 9941 this._adjustPan(); 9942 }, 9943 9944 getEvents: function () { 9945 var events = { 9946 zoom: this._updatePosition, 9947 viewreset: this._updatePosition 9948 }; 9949 9950 if (this._zoomAnimated) { 9951 events.zoomanim = this._animateZoom; 9952 } 9953 return events; 9954 }, 9955 9956 // @method isOpen: Boolean 9957 // Returns `true` when the overlay is visible on the map. 9958 isOpen: function () { 9959 return !!this._map && this._map.hasLayer(this); 9960 }, 9961 9962 // @method bringToFront: this 9963 // Brings this overlay in front of other overlays (in the same map pane). 9964 bringToFront: function () { 9965 if (this._map) { 9966 toFront(this._container); 9967 } 9968 return this; 9969 }, 9970 9971 // @method bringToBack: this 9972 // Brings this overlay to the back of other overlays (in the same map pane). 9973 bringToBack: function () { 9974 if (this._map) { 9975 toBack(this._container); 9976 } 9977 return this; 9978 }, 9979 9980 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup) 9981 _prepareOpen: function (latlng) { 9982 var source = this._source; 9983 if (!source._map) { return false; } 9984 9985 if (source instanceof FeatureGroup) { 9986 source = null; 9987 var layers = this._source._layers; 9988 for (var id in layers) { 9989 if (layers[id]._map) { 9990 source = layers[id]; 9991 break; 9992 } 9993 } 9994 if (!source) { return false; } // Unable to get source layer. 9995 9996 // set overlay source to this layer 9997 this._source = source; 9998 } 9999 10000 if (!latlng) { 10001 if (source.getCenter) { 10002 latlng = source.getCenter(); 10003 } else if (source.getLatLng) { 10004 latlng = source.getLatLng(); 10005 } else if (source.getBounds) { 10006 latlng = source.getBounds().getCenter(); 10007 } else { 10008 throw new Error('Unable to get source layer LatLng.'); 10009 } 10010 } 10011 this.setLatLng(latlng); 10012 10013 if (this._map) { 10014 // update the overlay (content, layout, etc...) 10015 this.update(); 10016 } 10017 10018 return true; 10019 }, 10020 10021 _updateContent: function () { 10022 if (!this._content) { return; } 10023 10024 var node = this._contentNode; 10025 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 10026 10027 if (typeof content === 'string') { 10028 node.innerHTML = content; 10029 } else { 10030 while (node.hasChildNodes()) { 10031 node.removeChild(node.firstChild); 10032 } 10033 node.appendChild(content); 10034 } 10035 10036 // @namespace DivOverlay 10037 // @section DivOverlay events 10038 // @event contentupdate: Event 10039 // Fired when the content of the overlay is updated 10040 this.fire('contentupdate'); 10041 }, 10042 10043 _updatePosition: function () { 10044 if (!this._map) { return; } 10045 10046 var pos = this._map.latLngToLayerPoint(this._latlng), 10047 offset = toPoint(this.options.offset), 10048 anchor = this._getAnchor(); 10049 10050 if (this._zoomAnimated) { 10051 setPosition(this._container, pos.add(anchor)); 10052 } else { 10053 offset = offset.add(pos).add(anchor); 10054 } 10055 10056 var bottom = this._containerBottom = -offset.y, 10057 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 10058 10059 // bottom position the overlay in case the height of the overlay changes (images loading etc) 10060 this._container.style.bottom = bottom + 'px'; 10061 this._container.style.left = left + 'px'; 10062 }, 10063 10064 _getAnchor: function () { 10065 return [0, 0]; 10066 } 10067 10068}); 10069 10070Map.include({ 10071 _initOverlay: function (OverlayClass, content, latlng, options) { 10072 var overlay = content; 10073 if (!(overlay instanceof OverlayClass)) { 10074 overlay = new OverlayClass(options).setContent(content); 10075 } 10076 if (latlng) { 10077 overlay.setLatLng(latlng); 10078 } 10079 return overlay; 10080 } 10081}); 10082 10083 10084Layer.include({ 10085 _initOverlay: function (OverlayClass, old, content, options) { 10086 var overlay = content; 10087 if (overlay instanceof OverlayClass) { 10088 setOptions(overlay, options); 10089 overlay._source = this; 10090 } else { 10091 overlay = (old && !options) ? old : new OverlayClass(options, this); 10092 overlay.setContent(content); 10093 } 10094 return overlay; 10095 } 10096}); 10097 10098/* 10099 * @class Popup 10100 * @inherits DivOverlay 10101 * @aka L.Popup 10102 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 10103 * open popups while making sure that only one popup is open at one time 10104 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 10105 * 10106 * @example 10107 * 10108 * If you want to just bind a popup to marker click and then open it, it's really easy: 10109 * 10110 * ```js 10111 * marker.bindPopup(popupContent).openPopup(); 10112 * ``` 10113 * Path overlays like polylines also have a `bindPopup` method. 10114 * 10115 * A popup can be also standalone: 10116 * 10117 * ```js 10118 * var popup = L.popup() 10119 * .setLatLng(latlng)
10120 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 10121 * .openOn(map); 10122 * ``` 10123 * or 10124 * ```js 10125 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>') 10126 * .openOn(map); 10127 * ``` 10128 */ 10129 10130 10131// @namespace Popup 10132var Popup = DivOverlay.extend({ 10133 10134 // @section 10135 // @aka Popup options 10136 options: { 10137 // @option pane: String = 'popupPane' 10138 // `Map pane` where the popup will be added. 10139 pane: 'popupPane', 10140 10141 // @option offset: Point = Point(0, 7) 10142 // The offset of the popup position. 10143 offset: [0, 7], 10144 10145 // @option maxWidth: Number = 300 10146 // Max width of the popup, in pixels. 10147 maxWidth: 300, 10148 10149 // @option minWidth: Number = 50 10150 // Min width of the popup, in pixels. 10151 minWidth: 50, 10152 10153 // @option maxHeight: Number = null 10154 // If set, creates a scrollable container of the given height 10155 // inside a popup if its content exceeds it. 10156 // The scrollable container can be styled using the 10157 // `leaflet-popup-scrolled` CSS class selector. 10158 maxHeight: null, 10159 10160 // @option autoPan: Boolean = true 10161 // Set it to `false` if you don't want the map to do panning animation 10162 // to fit the opened popup. 10163 autoPan: true, 10164 10165 // @option autoPanPaddingTopLeft: Point = null 10166 // The margin between the popup and the top left corner of the map 10167 // view after autopanning was performed. 10168 autoPanPaddingTopLeft: null, 10169 10170 // @option autoPanPaddingBottomRight: Point = null 10171 // The margin between the popup and the bottom right corner of the map 10172 // view after autopanning was performed. 10173 autoPanPaddingBottomRight: null, 10174 10175 // @option autoPanPadding: Point = Point(5, 5) 10176 // Equivalent of setting both top left and bottom right autopan padding to the same value. 10177 autoPanPadding: [5, 5], 10178 10179 // @option keepInView: Boolean = false 10180 // Set it to `true` if you want to prevent users from panning the popup 10181 // off of the screen while it is open. 10182 keepInView: false, 10183 10184 // @option closeButton: Boolean = true 10185 // Controls the presence of a close button in the popup. 10186 closeButton: true, 10187 10188 // @option autoClose: Boolean = true 10189 // Set it to `false` if you want to override the default behavior of 10190 // the popup closing when another popup is opened. 10191 autoClose: true, 10192 10193 // @option closeOnEscapeKey: Boolean = true 10194 // Set it to `false` if you want to override the default behavior of 10195 // the ESC key for closing of the popup. 10196 closeOnEscapeKey: true, 10197 10198 // @option closeOnClick: Boolean = * 10199 // Set it if you want to override the default behavior of the popup closing when user clicks 10200 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 10201 10202 // @option className: String = '' 10203 // A custom CSS class name to assign to the popup. 10204 className: '' 10205 }, 10206 10207 // @namespace Popup 10208 // @method openOn(map: Map): this 10209 // Alternative to `map.openPopup(popup)`. 10210 // Adds the popup to the map and closes the previous one. 10211 openOn: function (map) { 10212 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 10213 10214 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) { 10215 map.removeLayer(map._popup); 10216 } 10217 map._popup = this; 10218 10219 return DivOverlay.prototype.openOn.call(this, map); 10220 }, 10221 10222 onAdd: function (map) { 10223 DivOverlay.prototype.onAdd.call(this, map); 10224 10225 // @namespace Map 10226 // @section Popup events 10227 // @event popupopen: PopupEvent 10228 // Fired when a popup is opened in the map 10229 map.fire('popupopen', {popup: this}); 10230 10231 if (this._source) { 10232 // @namespace Layer 10233 // @section Popup events 10234 // @event popupopen: PopupEvent 10235 // Fired when a popup bound to this layer is opened 10236 this._source.fire('popupopen', {popup: this}, true); 10237 // For non-path layers, we toggle the popup when clicking 10238 // again the layer, so prevent the map to reopen it. 10239 if (!(this._source instanceof Path)) { 10240 this._source.on('preclick', stopPropagation); 10241 } 10242 } 10243 }, 10244 10245 onRemove: function (map) { 10246 DivOverlay.prototype.onRemove.call(this, map); 10247 10248 // @namespace Map 10249 // @section Popup events 10250 // @event popupclose: PopupEvent 10251 // Fired when a popup in the map is closed 10252 map.fire('popupclose', {popup: this}); 10253 10254 if (this._source) { 10255 // @namespace Layer 10256 // @section Popup events 10257 // @event popupclose: PopupEvent 10258 // Fired when a popup bound to this layer is closed 10259 this._source.fire('popupclose', {popup: this}, true); 10260 if (!(this._source instanceof Path)) { 10261 this._source.off('preclick', stopPropagation); 10262 } 10263 } 10264 }, 10265 10266 getEvents: function () { 10267 var events = DivOverlay.prototype.getEvents.call(this); 10268 10269 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 10270 events.preclick = this.close; 10271 } 10272 10273 if (this.options.keepInView) { 10274 events.moveend = this._adjustPan; 10275 } 10276 10277 return events; 10278 }, 10279 10280 _initLayout: function () { 10281 var prefix = 'leaflet-popup', 10282 container = this._container = create$1('div', 10283 prefix + ' ' + (this.options.className || '') + 10284 ' leaflet-zoom-animated'); 10285 10286 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container); 10287 this._contentNode = create$1('div', prefix + '-content', wrapper); 10288 10289 disableClickPropagation(container); 10290 disableScrollPropagation(this._contentNode); 10291 on(container, 'contextmenu', stopPropagation); 10292 10293 this._tipContainer = create$1('div', prefix + '-tip-container', container); 10294 this._tip = create$1('div', prefix + '-tip', this._tipContainer); 10295 10296 if (this.options.closeButton) { 10297 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container); 10298 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399 10299 closeButton.setAttribute('aria-label', 'Close popup'); 10300 closeButton.href = '#close'; 10301 closeButton.innerHTML = '<span aria-hidden="true">×</span>'; 10302 10303 on(closeButton, 'click', function (ev) { 10304 preventDefault(ev); 10305 this.close(); 10306 }, this); 10307 } 10308 }, 10309 10310 _updateLayout: function () { 10311 var container = this._contentNode, 10312 style = container.style; 10313 10314 style.width = ''; 10315 style.whiteSpace = 'nowrap'; 10316 10317 var width = container.offsetWidth; 10318 width = Math.min(width, this.options.maxWidth); 10319 width = Math.max(width, this.options.minWidth); 10320 10321 style.width = (width + 1) + 'px'; 10322 style.whiteSpace = ''; 10323 10324 style.height = ''; 10325 10326 var height = container.offsetHeight, 10327 maxHeight = this.options.maxHeight, 10328 scrolledClass = 'leaflet-popup-scrolled'; 10329 10330 if (maxHeight && height > maxHeight) { 10331 style.height = maxHeight + 'px'; 10332 addClass(container, scrolledClass); 10333 } else { 10334 removeClass(container, scrolledClass); 10335 } 10336 10337 this._containerWidth = this._container.offsetWidth; 10338 }, 10339 10340 _animateZoom: function (e) { 10341 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 10342 anchor = this._getAnchor(); 10343 setPosition(this._container, pos.add(anchor)); 10344 }, 10345 10346 _adjustPan: function () { 10347 if (!this.options.autoPan) { return; } 10348 if (this._map._panAnim) { this._map._panAnim.stop(); } 10349 10350 // We can endlessly recurse if keepInView is set and the view resets. 10351 // Let's guard against that by exiting early if we're responding to our own autopan. 10352 if (this._autopanning) { 10353 this._autopanning = false;
vendor: 7,050 bytes, lines 10354-10593
10354 return; 10355 } 10356 10357 var map = this._map, 10358 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0, 10359 containerHeight = this._container.offsetHeight + marginBottom, 10360 containerWidth = this._containerWidth, 10361 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 10362 10363 layerPos._add(getPosition(this._container)); 10364 10365 var containerPos = map.layerPointToContainerPoint(layerPos), 10366 padding = toPoint(this.options.autoPanPadding), 10367 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), 10368 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), 10369 size = map.getSize(), 10370 dx = 0, 10371 dy = 0; 10372 10373 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 10374 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 10375 } 10376 if (containerPos.x - dx - paddingTL.x < 0) { // left 10377 dx = containerPos.x - paddingTL.x; 10378 } 10379 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 10380 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 10381 } 10382 if (containerPos.y - dy - paddingTL.y < 0) { // top 10383 dy = containerPos.y - paddingTL.y; 10384 } 10385 10386 // @namespace Map 10387 // @section Popup events 10388 // @event autopanstart: Event 10389 // Fired when the map starts autopanning when opening a popup. 10390 if (dx || dy) { 10391 // Track that we're autopanning, as this function will be re-ran on moveend 10392 if (this.options.keepInView) { 10393 this._autopanning = true; 10394 } 10395 10396 map 10397 .fire('autopanstart') 10398 .panBy([dx, dy]); 10399 } 10400 }, 10401 10402 _getAnchor: function () { 10403 // Where should we anchor the popup on the source layer? 10404 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 10405 } 10406 10407}); 10408 10409// @namespace Popup 10410// @factory L.popup(options?: Popup options, source?: Layer) 10411// Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers. 10412// @alternative 10413// @factory L.popup(latlng: LatLng, options?: Popup options) 10414// Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location. 10415var popup = function (options, source) { 10416 return new Popup(options, source); 10417}; 10418 10419 10420/* @namespace Map 10421 * @section Interaction Options 10422 * @option closePopupOnClick: Boolean = true 10423 * Set it to `false` if you don't want popups to close when user clicks the map. 10424 */ 10425Map.mergeOptions({ 10426 closePopupOnClick: true 10427}); 10428 10429 10430// @namespace Map 10431// @section Methods for Layers and Controls 10432Map.include({ 10433 // @method openPopup(popup: Popup): this 10434 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 10435 // @alternative 10436 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 10437 // Creates a popup with the specified content and options and opens it in the given point on a map. 10438 openPopup: function (popup, latlng, options) { 10439 this._initOverlay(Popup, popup, latlng, options) 10440 .openOn(this); 10441 10442 return this; 10443 }, 10444 10445 // @method closePopup(popup?: Popup): this 10446 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 10447 closePopup: function (popup) { 10448 popup = arguments.length ? popup : this._popup; 10449 if (popup) { 10450 popup.close(); 10451 } 10452 return this; 10453 } 10454}); 10455 10456/* 10457 * @namespace Layer 10458 * @section Popup methods example 10459 * 10460 * All layers share a set of methods convenient for binding popups to it. 10461 * 10462 * ```js 10463 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 10464 * layer.openPopup(); 10465 * layer.closePopup(); 10466 * ``` 10467 * 10468 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened. 10469 */ 10470 10471// @section Popup methods 10472Layer.include({ 10473 10474 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 10475 // Binds a popup to the layer with the passed `content` and sets up the 10476 // necessary event listeners. If a `Function` is passed it will receive 10477 // the layer as the first argument and should return a `String` or `HTMLElement`. 10478 bindPopup: function (content, options) { 10479 this._popup = this._initOverlay(Popup, this._popup, content, options); 10480 if (!this._popupHandlersAdded) { 10481 this.on({ 10482 click: this._openPopup, 10483 keypress: this._onKeyPress, 10484 remove: this.closePopup, 10485 move: this._movePopup 10486 }); 10487 this._popupHandlersAdded = true; 10488 } 10489 10490 return this; 10491 }, 10492 10493 // @method unbindPopup(): this 10494 // Removes the popup previously bound with `bindPopup`. 10495 unbindPopup: function () { 10496 if (this._popup) { 10497 this.off({ 10498 click: this._openPopup, 10499 keypress: this._onKeyPress, 10500 remove: this.closePopup, 10501 move: this._movePopup 10502 }); 10503 this._popupHandlersAdded = false; 10504 this._popup = null; 10505 } 10506 return this; 10507 }, 10508 10509 // @method openPopup(latlng?: LatLng): this 10510 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 10511 openPopup: function (latlng) { 10512 if (this._popup) { 10513 if (!(this instanceof FeatureGroup)) { 10514 this._popup._source = this; 10515 } 10516 if (this._popup._prepareOpen(latlng || this._latlng)) { 10517 // open the popup on the map 10518 this._popup.openOn(this._map); 10519 } 10520 } 10521 return this; 10522 }, 10523 10524 // @method closePopup(): this 10525 // Closes the popup bound to this layer if it is open. 10526 closePopup: function () { 10527 if (this._popup) { 10528 this._popup.close(); 10529 } 10530 return this; 10531 }, 10532 10533 // @method togglePopup(): this 10534 // Opens or closes the popup bound to this layer depending on its current state. 10535 togglePopup: function () { 10536 if (this._popup) { 10537 this._popup.toggle(this); 10538 } 10539 return this; 10540 }, 10541 10542 // @method isPopupOpen(): boolean 10543 // Returns `true` if the popup bound to this layer is currently open. 10544 isPopupOpen: function () { 10545 return (this._popup ? this._popup.isOpen() : false); 10546 }, 10547 10548 // @method setPopupContent(content: String|HTMLElement|Popup): this 10549 // Sets the content of the popup bound to this layer. 10550 setPopupContent: function (content) { 10551 if (this._popup) { 10552 this._popup.setContent(content); 10553 } 10554 return this; 10555 }, 10556 10557 // @method getPopup(): Popup 10558 // Returns the popup bound to this layer. 10559 getPopup: function () { 10560 return this._popup; 10561 }, 10562 10563 _openPopup: function (e) { 10564 if (!this._popup || !this._map) { 10565 return; 10566 } 10567 // prevent map click 10568 stop(e); 10569 10570 var target = e.layer || e.target; 10571 if (this._popup._source === target && !(target instanceof Path)) { 10572 // treat it like a marker and figure out 10573 // if we should toggle it open/closed 10574 if (this._map.hasLayer(this._popup)) { 10575 this.closePopup(); 10576 } else { 10577 this.openPopup(e.latlng); 10578 } 10579 return; 10580 } 10581 this._popup._source = target; 10582 this.openPopup(e.latlng); 10583 }, 10584 10585 _movePopup: function (e) { 10586 this._popup.setLatLng(e.latlng); 10587 }, 10588 10589 _onKeyPress: function (e) { 10590 if (e.originalEvent.keyCode === 13) { 10591 this._openPopup(e); 10592 } 10593 }
vendor: 39,520 bytes, lines 10593-12021
10593 10594}); 10595 10596/* 10597 * @class Tooltip 10598 * @inherits DivOverlay 10599 * @aka L.Tooltip 10600 * Used to display small texts on top of map layers. 10601 * 10602 * @example 10603 * If you want to just bind a tooltip to marker: 10604 * 10605 * ```js 10606 * marker.bindTooltip("my tooltip text").openTooltip(); 10607 * ``` 10608 * Path overlays like polylines also have a `bindTooltip` method. 10609 * 10610 * A tooltip can be also standalone: 10611 * 10612 * ```js 10613 * var tooltip = L.tooltip() 10614 * .setLatLng(latlng) 10615 * .setContent('Hello world!<br />This is a nice tooltip.') 10616 * .addTo(map); 10617 * ``` 10618 * or 10619 * ```js 10620 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'}) 10621 * .addTo(map); 10622 * ``` 10623 * 10624 * 10625 * Note about tooltip offset. Leaflet takes two options in consideration 10626 * for computing tooltip offsetting: 10627 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 10628 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 10629 * move it to the bottom. Negatives will move to the left and top. 10630 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 10631 * should adapt this value if you use a custom icon. 10632 */ 10633 10634 10635// @namespace Tooltip 10636var Tooltip = DivOverlay.extend({ 10637 10638 // @section 10639 // @aka Tooltip options 10640 options: { 10641 // @option pane: String = 'tooltipPane' 10642 // `Map pane` where the tooltip will be added. 10643 pane: 'tooltipPane', 10644 10645 // @option offset: Point = Point(0, 0) 10646 // Optional offset of the tooltip position. 10647 offset: [0, 0], 10648 10649 // @option direction: String = 'auto' 10650 // Direction where to open the tooltip. Possible values are: `right`, `left`, 10651 // `top`, `bottom`, `center`, `auto`. 10652 // `auto` will dynamically switch between `right` and `left` according to the tooltip 10653 // position on the map. 10654 direction: 'auto', 10655 10656 // @option permanent: Boolean = false 10657 // Whether to open the tooltip permanently or only on mouseover. 10658 permanent: false, 10659 10660 // @option sticky: Boolean = false 10661 // If true, the tooltip will follow the mouse instead of being fixed at the feature center. 10662 sticky: false, 10663 10664 // @option opacity: Number = 0.9 10665 // Tooltip container opacity. 10666 opacity: 0.9 10667 }, 10668 10669 onAdd: function (map) { 10670 DivOverlay.prototype.onAdd.call(this, map); 10671 this.setOpacity(this.options.opacity); 10672 10673 // @namespace Map 10674 // @section Tooltip events 10675 // @event tooltipopen: TooltipEvent 10676 // Fired when a tooltip is opened in the map. 10677 map.fire('tooltipopen', {tooltip: this}); 10678 10679 if (this._source) { 10680 this.addEventParent(this._source); 10681 10682 // @namespace Layer 10683 // @section Tooltip events 10684 // @event tooltipopen: TooltipEvent 10685 // Fired when a tooltip bound to this layer is opened. 10686 this._source.fire('tooltipopen', {tooltip: this}, true); 10687 } 10688 }, 10689 10690 onRemove: function (map) { 10691 DivOverlay.prototype.onRemove.call(this, map); 10692 10693 // @namespace Map 10694 // @section Tooltip events 10695 // @event tooltipclose: TooltipEvent 10696 // Fired when a tooltip in the map is closed. 10697 map.fire('tooltipclose', {tooltip: this}); 10698 10699 if (this._source) { 10700 this.removeEventParent(this._source); 10701 10702 // @namespace Layer 10703 // @section Tooltip events 10704 // @event tooltipclose: TooltipEvent 10705 // Fired when a tooltip bound to this layer is closed. 10706 this._source.fire('tooltipclose', {tooltip: this}, true); 10707 } 10708 }, 10709 10710 getEvents: function () { 10711 var events = DivOverlay.prototype.getEvents.call(this); 10712 10713 if (!this.options.permanent) { 10714 events.preclick = this.close; 10715 } 10716 10717 return events; 10718 }, 10719 10720 _initLayout: function () { 10721 var prefix = 'leaflet-tooltip', 10722 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 10723 10724 this._contentNode = this._container = create$1('div', className); 10725 10726 this._container.setAttribute('role', 'tooltip'); 10727 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this)); 10728 }, 10729 10730 _updateLayout: function () {}, 10731 10732 _adjustPan: function () {}, 10733 10734 _setPosition: function (pos) { 10735 var subX, subY, 10736 map = this._map, 10737 container = this._container, 10738 centerPoint = map.latLngToContainerPoint(map.getCenter()), 10739 tooltipPoint = map.layerPointToContainerPoint(pos), 10740 direction = this.options.direction, 10741 tooltipWidth = container.offsetWidth, 10742 tooltipHeight = container.offsetHeight, 10743 offset = toPoint(this.options.offset), 10744 anchor = this._getAnchor(); 10745 10746 if (direction === 'top') { 10747 subX = tooltipWidth / 2; 10748 subY = tooltipHeight; 10749 } else if (direction === 'bottom') { 10750 subX = tooltipWidth / 2; 10751 subY = 0; 10752 } else if (direction === 'center') { 10753 subX = tooltipWidth / 2; 10754 subY = tooltipHeight / 2; 10755 } else if (direction === 'right') { 10756 subX = 0; 10757 subY = tooltipHeight / 2; 10758 } else if (direction === 'left') { 10759 subX = tooltipWidth; 10760 subY = tooltipHeight / 2; 10761 } else if (tooltipPoint.x < centerPoint.x) { 10762 direction = 'right'; 10763 subX = 0; 10764 subY = tooltipHeight / 2; 10765 } else { 10766 direction = 'left'; 10767 subX = tooltipWidth + (offset.x + anchor.x) * 2; 10768 subY = tooltipHeight / 2; 10769 } 10770 10771 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor); 10772 10773 removeClass(container, 'leaflet-tooltip-right'); 10774 removeClass(container, 'leaflet-tooltip-left'); 10775 removeClass(container, 'leaflet-tooltip-top'); 10776 removeClass(container, 'leaflet-tooltip-bottom'); 10777 addClass(container, 'leaflet-tooltip-' + direction); 10778 setPosition(container, pos); 10779 }, 10780 10781 _updatePosition: function () { 10782 var pos = this._map.latLngToLayerPoint(this._latlng); 10783 this._setPosition(pos); 10784 }, 10785 10786 setOpacity: function (opacity) { 10787 this.options.opacity = opacity; 10788 10789 if (this._container) { 10790 setOpacity(this._container, opacity); 10791 } 10792 }, 10793 10794 _animateZoom: function (e) { 10795 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 10796 this._setPosition(pos); 10797 }, 10798 10799 _getAnchor: function () { 10800 // Where should we anchor the tooltip on the source layer? 10801 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 10802 } 10803 10804}); 10805 10806// @namespace Tooltip 10807// @factory L.tooltip(options?: Tooltip options, source?: Layer) 10808// Instantiates a `Tooltip` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers. 10809// @alternative 10810// @factory L.tooltip(latlng: LatLng, options?: Tooltip options) 10811// Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location. 10812var tooltip = function (options, source) { 10813 return new Tooltip(options, source); 10814}; 10815 10816// @namespace Map 10817// @section Methods for Layers and Controls 10818Map.include({ 10819 10820 // @method openTooltip(tooltip: Tooltip): this 10821 // Opens the specified tooltip. 10822 // @alternative 10823 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 10824 // Creates a tooltip with the specified content and options and open it. 10825 openTooltip: function (tooltip, latlng, options) { 10826 this._initOverlay(Tooltip, tooltip, latlng, options) 10827 .openOn(this); 10828 10829 return this; 10830 }, 10831 10832 // @method closeTooltip(tooltip: Tooltip): this 10833 // Closes the tooltip given as parameter. 10834 closeTooltip: function (tooltip) { 10835 tooltip.close(); 10836 return this; 10837 } 10838 10839}); 10840 10841/* 10842 * @namespace Layer 10843 * @section Tooltip methods example 10844 * 10845 * All layers share a set of methods convenient for binding tooltips to it. 10846 * 10847 * ```js 10848 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 10849 * layer.openTooltip(); 10850 * layer.closeTooltip(); 10851 * ``` 10852 */ 10853 10854// @section Tooltip methods 10855Layer.include({ 10856 10857 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 10858 // Binds a tooltip to the layer with the passed `content` and sets up the 10859 // necessary event listeners. If a `Function` is passed it will receive 10860 // the layer as the first argument and should return a `String` or `HTMLElement`. 10861 bindTooltip: function (content, options) { 10862 10863 if (this._tooltip && this.isTooltipOpen()) { 10864 this.unbindTooltip(); 10865 } 10866 10867 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options); 10868 this._initTooltipInteractions(); 10869 10870 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 10871 this.openTooltip(); 10872 } 10873 10874 return this; 10875 }, 10876 10877 // @method unbindTooltip(): this 10878 // Removes the tooltip previously bound with `bindTooltip`. 10879 unbindTooltip: function () { 10880 if (this._tooltip) { 10881 this._initTooltipInteractions(true); 10882 this.closeTooltip(); 10883 this._tooltip = null; 10884 } 10885 return this; 10886 }, 10887 10888 _initTooltipInteractions: function (remove) { 10889 if (!remove && this._tooltipHandlersAdded) { return; } 10890 var onOff = remove ? 'off' : 'on', 10891 events = { 10892 remove: this.closeTooltip, 10893 move: this._moveTooltip 10894 }; 10895 if (!this._tooltip.options.permanent) { 10896 events.mouseover = this._openTooltip; 10897 events.mouseout = this.closeTooltip; 10898 events.click = this._openTooltip; 10899 if (this._map) { 10900 this._addFocusListeners(); 10901 } else { 10902 events.add = this._addFocusListeners; 10903 } 10904 } else { 10905 events.add = this._openTooltip; 10906 } 10907 if (this._tooltip.options.sticky) { 10908 events.mousemove = this._moveTooltip; 10909 } 10910 this[onOff](events); 10911 this._tooltipHandlersAdded = !remove; 10912 }, 10913 10914 // @method openTooltip(latlng?: LatLng): this 10915 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 10916 openTooltip: function (latlng) { 10917 if (this._tooltip) { 10918 if (!(this instanceof FeatureGroup)) { 10919 this._tooltip._source = this; 10920 } 10921 if (this._tooltip._prepareOpen(latlng)) { 10922 // open the tooltip on the map 10923 this._tooltip.openOn(this._map); 10924 10925 if (this.getElement) { 10926 this._setAriaDescribedByOnLayer(this); 10927 } else if (this.eachLayer) { 10928 this.eachLayer(this._setAriaDescribedByOnLayer, this); 10929 } 10930 } 10931 } 10932 return this; 10933 }, 10934 10935 // @method closeTooltip(): this 10936 // Closes the tooltip bound to this layer if it is open. 10937 closeTooltip: function () { 10938 if (this._tooltip) { 10939 return this._tooltip.close(); 10940 } 10941 }, 10942 10943 // @method toggleTooltip(): this 10944 // Opens or closes the tooltip bound to this layer depending on its current state. 10945 toggleTooltip: function () { 10946 if (this._tooltip) { 10947 this._tooltip.toggle(this); 10948 } 10949 return this; 10950 }, 10951 10952 // @method isTooltipOpen(): boolean 10953 // Returns `true` if the tooltip bound to this layer is currently open. 10954 isTooltipOpen: function () { 10955 return this._tooltip.isOpen(); 10956 }, 10957 10958 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this 10959 // Sets the content of the tooltip bound to this layer. 10960 setTooltipContent: function (content) { 10961 if (this._tooltip) { 10962 this._tooltip.setContent(content); 10963 } 10964 return this; 10965 }, 10966 10967 // @method getTooltip(): Tooltip 10968 // Returns the tooltip bound to this layer. 10969 getTooltip: function () { 10970 return this._tooltip; 10971 }, 10972 10973 _addFocusListeners: function () { 10974 if (this.getElement) { 10975 this._addFocusListenersOnLayer(this); 10976 } else if (this.eachLayer) { 10977 this.eachLayer(this._addFocusListenersOnLayer, this); 10978 } 10979 }, 10980 10981 _addFocusListenersOnLayer: function (layer) { 10982 var el = typeof layer.getElement === 'function' && layer.getElement(); 10983 if (el) { 10984 on(el, 'focus', function () { 10985 this._tooltip._source = layer; 10986 this.openTooltip(); 10987 }, this); 10988 on(el, 'blur', this.closeTooltip, this); 10989 } 10990 }, 10991 10992 _setAriaDescribedByOnLayer: function (layer) { 10993 var el = typeof layer.getElement === 'function' && layer.getElement(); 10994 if (el) { 10995 el.setAttribute('aria-describedby', this._tooltip._container.id); 10996 } 10997 }, 10998 10999 11000 _openTooltip: function (e) { 11001 if (!this._tooltip || !this._map) { 11002 return; 11003 } 11004 11005 // If the map is moving, we will show the tooltip after it's done. 11006 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) { 11007 this._openOnceFlag = true; 11008 var that = this; 11009 this._map.once('moveend', function () { 11010 that._openOnceFlag = false; 11011 that._openTooltip(e); 11012 }); 11013 return; 11014 } 11015 11016 this._tooltip._source = e.layer || e.target; 11017 11018 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined); 11019 }, 11020 11021 _moveTooltip: function (e) { 11022 var latlng = e.latlng, containerPoint, layerPoint; 11023 if (this._tooltip.options.sticky && e.originalEvent) { 11024 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 11025 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 11026 latlng = this._map.layerPointToLatLng(layerPoint); 11027 } 11028 this._tooltip.setLatLng(latlng); 11029 } 11030}); 11031 11032/* 11033 * @class DivIcon 11034 * @aka L.DivIcon 11035 * @inherits Icon 11036 * 11037 * Represents a lightweight icon for markers that uses a simple `<div>` 11038 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 11039 * 11040 * @example 11041 * ```js 11042 * var myIcon = L.divIcon({className: 'my-div-icon'}); 11043 * // you can set .my-div-icon styles in CSS 11044 * 11045 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 11046 * ``` 11047 * 11048 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 11049 */ 11050 11051var DivIcon = Icon.extend({ 11052 options: { 11053 // @section 11054 // @aka DivIcon options 11055 iconSize: [12, 12], // also can be set through CSS 11056 11057 // iconAnchor: (Point), 11058 // popupAnchor: (Point), 11059 11060 // @option html: String|HTMLElement = '' 11061 // Custom HTML code to put inside the div element, empty by default. Alternatively, 11062 // an instance of `HTMLElement`. 11063 html: false, 11064 11065 // @option bgPos: Point = [0, 0] 11066 // Optional relative position of the background, in pixels 11067 bgPos: null, 11068 11069 className: 'leaflet-div-icon' 11070 }, 11071 11072 createIcon: function (oldIcon) { 11073 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 11074 options = this.options; 11075 11076 if (options.html instanceof Element) { 11077 empty(div); 11078 div.appendChild(options.html); 11079 } else { 11080 div.innerHTML = options.html !== false ? options.html : ''; 11081 } 11082 11083 if (options.bgPos) { 11084 var bgPos = toPoint(options.bgPos); 11085 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 11086 } 11087 this._setIconStyles(div, 'icon'); 11088 11089 return div; 11090 }, 11091 11092 createShadow: function () { 11093 return null; 11094 } 11095}); 11096 11097// @factory L.divIcon(options: DivIcon options) 11098// Creates a `DivIcon` instance with the given options. 11099function divIcon(options) { 11100 return new DivIcon(options); 11101} 11102 11103Icon.Default = IconDefault; 11104 11105/* 11106 * @class GridLayer 11107 * @inherits Layer 11108 * @aka L.GridLayer 11109 * 11110 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 11111 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you. 11112 * 11113 * 11114 * @section Synchronous usage 11115 * @example 11116 * 11117 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile. 11118 * 11119 * ```js 11120 * var CanvasLayer = L.GridLayer.extend({ 11121 * createTile: function(coords){ 11122 * // create a <canvas> element for drawing 11123 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 11124 * 11125 * // setup tile width and height according to the options 11126 * var size = this.getTileSize(); 11127 * tile.width = size.x; 11128 * tile.height = size.y; 11129 * 11130 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 11131 * var ctx = tile.getContext('2d'); 11132 * 11133 * // return the tile so it can be rendered on screen 11134 * return tile; 11135 * } 11136 * }); 11137 * ``` 11138 * 11139 * @section Asynchronous usage 11140 * @example 11141 * 11142 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback. 11143 * 11144 * ```js 11145 * var CanvasLayer = L.GridLayer.extend({ 11146 * createTile: function(coords, done){ 11147 * var error; 11148 * 11149 * // create a <canvas> element for drawing 11150 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 11151 * 11152 * // setup tile width and height according to the options 11153 * var size = this.getTileSize(); 11154 * tile.width = size.x; 11155 * tile.height = size.y; 11156 * 11157 * // draw something asynchronously and pass the tile to the done() callback 11158 * setTimeout(function() { 11159 * done(error, tile); 11160 * }, 1000); 11161 * 11162 * return tile; 11163 * } 11164 * }); 11165 * ``` 11166 * 11167 * @section 11168 */ 11169 11170 11171var GridLayer = Layer.extend({ 11172 11173 // @section 11174 // @aka GridLayer options 11175 options: { 11176 // @option tileSize: Number|Point = 256 11177 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 11178 tileSize: 256, 11179 11180 // @option opacity: Number = 1.0 11181 // Opacity of the tiles. Can be used in the `createTile()` function. 11182 opacity: 1, 11183 11184 // @option updateWhenIdle: Boolean = (depends) 11185 // Load new tiles only when panning ends. 11186 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 11187 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 11188 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 11189 updateWhenIdle: Browser.mobile, 11190 11191 // @option updateWhenZooming: Boolean = true 11192 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends. 11193 updateWhenZooming: true, 11194 11195 // @option updateInterval: Number = 200 11196 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 11197 updateInterval: 200, 11198 11199 // @option zIndex: Number = 1 11200 // The explicit zIndex of the tile layer. 11201 zIndex: 1, 11202 11203 // @option bounds: LatLngBounds = undefined 11204 // If set, tiles will only be loaded inside the set `LatLngBounds`. 11205 bounds: null, 11206 11207 // @option minZoom: Number = 0 11208 // The minimum zoom level down to which this layer will be displayed (inclusive). 11209 minZoom: 0, 11210 11211 // @option maxZoom: Number = undefined 11212 // The maximum zoom level up to which this layer will be displayed (inclusive). 11213 maxZoom: undefined, 11214 11215 // @option maxNativeZoom: Number = undefined 11216 // Maximum zoom number the tile source has available. If it is specified, 11217 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 11218 // from `maxNativeZoom` level and auto-scaled. 11219 maxNativeZoom: undefined, 11220 11221 // @option minNativeZoom: Number = undefined 11222 // Minimum zoom number the tile source has available. If it is specified, 11223 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 11224 // from `minNativeZoom` level and auto-scaled. 11225 minNativeZoom: undefined, 11226 11227 // @option noWrap: Boolean = false 11228 // Whether the layer is wrapped around the antimeridian. If `true`, the 11229 // GridLayer will only be displayed once at low zoom levels. Has no 11230 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 11231 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 11232 // tiles outside the CRS limits. 11233 noWrap: false, 11234 11235 // @option pane: String = 'tilePane' 11236 // `Map pane` where the grid layer will be added. 11237 pane: 'tilePane', 11238 11239 // @option className: String = '' 11240 // A custom class name to assign to the tile layer. Empty by default. 11241 className: '', 11242 11243 // @option keepBuffer: Number = 2 11244 // When panning the map, keep this many rows and columns of tiles before unloading them. 11245 keepBuffer: 2 11246 }, 11247 11248 initialize: function (options) { 11249 setOptions(this, options); 11250 }, 11251 11252 onAdd: function () { 11253 this._initContainer(); 11254 11255 this._levels = {}; 11256 this._tiles = {}; 11257 11258 this._resetView(); // implicit _update() call 11259 }, 11260 11261 beforeAdd: function (map) { 11262 map._addZoomLimit(this); 11263 }, 11264 11265 onRemove: function (map) { 11266 this._removeAllTiles(); 11267 remove(this._container); 11268 map._removeZoomLimit(this); 11269 this._container = null; 11270 this._tileZoom = undefined; 11271 }, 11272 11273 // @method bringToFront: this 11274 // Brings the tile layer to the top of all tile layers. 11275 bringToFront: function () { 11276 if (this._map) { 11277 toFront(this._container); 11278 this._setAutoZIndex(Math.max); 11279 } 11280 return this; 11281 }, 11282 11283 // @method bringToBack: this 11284 // Brings the tile layer to the bottom of all tile layers. 11285 bringToBack: function () { 11286 if (this._map) { 11287 toBack(this._container); 11288 this._setAutoZIndex(Math.min); 11289 } 11290 return this; 11291 }, 11292 11293 // @method getContainer: HTMLElement 11294 // Returns the HTML element that contains the tiles for this layer. 11295 getContainer: function () { 11296 return this._container; 11297 }, 11298 11299 // @method setOpacity(opacity: Number): this 11300 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 11301 setOpacity: function (opacity) { 11302 this.options.opacity = opacity; 11303 this._updateOpacity(); 11304 return this; 11305 }, 11306 11307 // @method setZIndex(zIndex: Number): this 11308 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 11309 setZIndex: function (zIndex) { 11310 this.options.zIndex = zIndex; 11311 this._updateZIndex(); 11312 11313 return this; 11314 }, 11315 11316 // @method isLoading: Boolean 11317 // Returns `true` if any tile in the grid layer has not finished loading. 11318 isLoading: function () { 11319 return this._loading; 11320 }, 11321 11322 // @method redraw: this 11323 // Causes the layer to clear all the tiles and request them again. 11324 redraw: function () { 11325 if (this._map) { 11326 this._removeAllTiles(); 11327 var tileZoom = this._clampZoom(this._map.getZoom()); 11328 if (tileZoom !== this._tileZoom) { 11329 this._tileZoom = tileZoom; 11330 this._updateLevels(); 11331 } 11332 this._update(); 11333 } 11334 return this; 11335 }, 11336 11337 getEvents: function () { 11338 var events = { 11339 viewprereset: this._invalidateAll, 11340 viewreset: this._resetView, 11341 zoom: this._resetView, 11342 moveend: this._onMoveEnd 11343 }; 11344 11345 if (!this.options.updateWhenIdle) { 11346 // update tiles on move, but not more often than once per given interval 11347 if (!this._onMove) { 11348 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 11349 } 11350 11351 events.move = this._onMove; 11352 } 11353 11354 if (this._zoomAnimated) { 11355 events.zoomanim = this._animateZoom; 11356 } 11357 11358 return events; 11359 }, 11360 11361 // @section Extension methods 11362 // Layers extending `GridLayer` shall reimplement the following method. 11363 // @method createTile(coords: Object, done?: Function): HTMLElement 11364 // Called only internally, must be overridden by classes extending `GridLayer`. 11365 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 11366 // is specified, it must be called when the tile has finished loading and drawing. 11367 createTile: function () { 11368 return document.createElement('div'); 11369 }, 11370 11371 // @section 11372 // @method getTileSize: Point 11373 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 11374 getTileSize: function () { 11375 var s = this.options.tileSize; 11376 return s instanceof Point ? s : new Point(s, s); 11377 }, 11378 11379 _updateZIndex: function () { 11380 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 11381 this._container.style.zIndex = this.options.zIndex; 11382 } 11383 }, 11384 11385 _setAutoZIndex: function (compare) { 11386 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 11387 11388 var layers = this.getPane().children, 11389 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 11390 11391 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 11392 11393 zIndex = layers[i].style.zIndex; 11394 11395 if (layers[i] !== this._container && zIndex) { 11396 edgeZIndex = compare(edgeZIndex, +zIndex); 11397 } 11398 } 11399 11400 if (isFinite(edgeZIndex)) { 11401 this.options.zIndex = edgeZIndex + compare(-1, 1); 11402 this._updateZIndex(); 11403 } 11404 }, 11405 11406 _updateOpacity: function () { 11407 if (!this._map) { return; } 11408 11409 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 11410 if (Browser.ielt9) { return; } 11411 11412 setOpacity(this._container, this.options.opacity); 11413 11414 var now = +new Date(), 11415 nextFrame = false, 11416 willPrune = false; 11417 11418 for (var key in this._tiles) { 11419 var tile = this._tiles[key]; 11420 if (!tile.current || !tile.loaded) { continue; } 11421 11422 var fade = Math.min(1, (now - tile.loaded) / 200); 11423 11424 setOpacity(tile.el, fade); 11425 if (fade < 1) { 11426 nextFrame = true; 11427 } else { 11428 if (tile.active) { 11429 willPrune = true; 11430 } else { 11431 this._onOpaqueTile(tile); 11432 } 11433 tile.active = true; 11434 } 11435 } 11436 11437 if (willPrune && !this._noPrune) { this._pruneTiles(); } 11438 11439 if (nextFrame) { 11440 cancelAnimFrame(this._fadeFrame); 11441 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11442 } 11443 }, 11444 11445 _onOpaqueTile: falseFn, 11446 11447 _initContainer: function () { 11448 if (this._container) { return; } 11449 11450 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || '')); 11451 this._updateZIndex(); 11452 11453 if (this.options.opacity < 1) { 11454 this._updateOpacity(); 11455 } 11456 11457 this.getPane().appendChild(this._container); 11458 }, 11459 11460 _updateLevels: function () { 11461 11462 var zoom = this._tileZoom, 11463 maxZoom = this.options.maxZoom; 11464 11465 if (zoom === undefined) { return undefined; } 11466 11467 for (var z in this._levels) { 11468 z = Number(z); 11469 if (this._levels[z].el.children.length || z === zoom) { 11470 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 11471 this._onUpdateLevel(z); 11472 } else { 11473 remove(this._levels[z].el); 11474 this._removeTilesAtZoom(z); 11475 this._onRemoveLevel(z); 11476 delete this._levels[z]; 11477 } 11478 } 11479 11480 var level = this._levels[zoom], 11481 map = this._map; 11482 11483 if (!level) { 11484 level = this._levels[zoom] = {}; 11485 11486 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 11487 level.el.style.zIndex = maxZoom; 11488 11489 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 11490 level.zoom = zoom; 11491 11492 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 11493 11494 // force the browser to consider the newly added element for transition 11495 falseFn(level.el.offsetWidth); 11496 11497 this._onCreateLevel(level); 11498 } 11499 11500 this._level = level; 11501 11502 return level; 11503 }, 11504 11505 _onUpdateLevel: falseFn, 11506 11507 _onRemoveLevel: falseFn, 11508 11509 _onCreateLevel: falseFn, 11510 11511 _pruneTiles: function () { 11512 if (!this._map) { 11513 return; 11514 } 11515 11516 var key, tile; 11517 11518 var zoom = this._map.getZoom(); 11519 if (zoom > this.options.maxZoom || 11520 zoom < this.options.minZoom) { 11521 this._removeAllTiles(); 11522 return; 11523 } 11524 11525 for (key in this._tiles) { 11526 tile = this._tiles[key]; 11527 tile.retain = tile.current; 11528 } 11529 11530 for (key in this._tiles) { 11531 tile = this._tiles[key]; 11532 if (tile.current && !tile.active) { 11533 var coords = tile.coords; 11534 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 11535 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 11536 } 11537 } 11538 } 11539 11540 for (key in this._tiles) { 11541 if (!this._tiles[key].retain) { 11542 this._removeTile(key); 11543 } 11544 } 11545 }, 11546 11547 _removeTilesAtZoom: function (zoom) { 11548 for (var key in this._tiles) { 11549 if (this._tiles[key].coords.z !== zoom) { 11550 continue; 11551 } 11552 this._removeTile(key); 11553 } 11554 }, 11555 11556 _removeAllTiles: function () { 11557 for (var key in this._tiles) { 11558 this._removeTile(key); 11559 } 11560 }, 11561 11562 _invalidateAll: function () { 11563 for (var z in this._levels) { 11564 remove(this._levels[z].el); 11565 this._onRemoveLevel(Number(z)); 11566 delete this._levels[z]; 11567 } 11568 this._removeAllTiles(); 11569 11570 this._tileZoom = undefined; 11571 }, 11572 11573 _retainParent: function (x, y, z, minZoom) { 11574 var x2 = Math.floor(x / 2), 11575 y2 = Math.floor(y / 2), 11576 z2 = z - 1, 11577 coords2 = new Point(+x2, +y2); 11578 coords2.z = +z2; 11579 11580 var key = this._tileCoordsToKey(coords2), 11581 tile = this._tiles[key]; 11582 11583 if (tile && tile.active) { 11584 tile.retain = true; 11585 return true; 11586 11587 } else if (tile && tile.loaded) { 11588 tile.retain = true; 11589 } 11590 11591 if (z2 > minZoom) { 11592 return this._retainParent(x2, y2, z2, minZoom); 11593 } 11594 11595 return false; 11596 }, 11597 11598 _retainChildren: function (x, y, z, maxZoom) { 11599 11600 for (var i = 2 * x; i < 2 * x + 2; i++) { 11601 for (var j = 2 * y; j < 2 * y + 2; j++) { 11602 11603 var coords = new Point(i, j); 11604 coords.z = z + 1; 11605 11606 var key = this._tileCoordsToKey(coords), 11607 tile = this._tiles[key]; 11608 11609 if (tile && tile.active) { 11610 tile.retain = true; 11611 continue; 11612 11613 } else if (tile && tile.loaded) { 11614 tile.retain = true; 11615 } 11616 11617 if (z + 1 < maxZoom) { 11618 this._retainChildren(i, j, z + 1, maxZoom); 11619 } 11620 } 11621 } 11622 }, 11623 11624 _resetView: function (e) { 11625 var animating = e && (e.pinch || e.flyTo); 11626 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 11627 }, 11628 11629 _animateZoom: function (e) { 11630 this._setView(e.center, e.zoom, true, e.noUpdate); 11631 }, 11632 11633 _clampZoom: function (zoom) { 11634 var options = this.options; 11635 11636 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) { 11637 return options.minNativeZoom; 11638 } 11639 11640 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) { 11641 return options.maxNativeZoom; 11642 } 11643 11644 return zoom; 11645 }, 11646 11647 _setView: function (center, zoom, noPrune, noUpdate) { 11648 var tileZoom = Math.round(zoom); 11649 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 11650 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 11651 tileZoom = undefined; 11652 } else { 11653 tileZoom = this._clampZoom(tileZoom); 11654 } 11655 11656 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 11657 11658 if (!noUpdate || tileZoomChanged) { 11659 11660 this._tileZoom = tileZoom; 11661 11662 if (this._abortLoading) { 11663 this._abortLoading(); 11664 } 11665 11666 this._updateLevels(); 11667 this._resetGrid(); 11668 11669 if (tileZoom !== undefined) { 11670 this._update(center); 11671 } 11672 11673 if (!noPrune) { 11674 this._pruneTiles(); 11675 } 11676 11677 // Flag to prevent _updateOpacity from pruning tiles during 11678 // a zoom anim or a pinch gesture 11679 this._noPrune = !!noPrune; 11680 } 11681 11682 this._setZoomTransforms(center, zoom); 11683 }, 11684 11685 _setZoomTransforms: function (center, zoom) { 11686 for (var i in this._levels) { 11687 this._setZoomTransform(this._levels[i], center, zoom); 11688 } 11689 }, 11690 11691 _setZoomTransform: function (level, center, zoom) { 11692 var scale = this._map.getZoomScale(zoom, level.zoom), 11693 translate = level.origin.multiplyBy(scale) 11694 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 11695 11696 if (Browser.any3d) { 11697 setTransform(level.el, translate, scale); 11698 } else { 11699 setPosition(level.el, translate); 11700 } 11701 }, 11702 11703 _resetGrid: function () { 11704 var map = this._map, 11705 crs = map.options.crs, 11706 tileSize = this._tileSize = this.getTileSize(), 11707 tileZoom = this._tileZoom; 11708 11709 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 11710 if (bounds) { 11711 this._globalTileRange = this._pxBoundsToTileRange(bounds); 11712 } 11713 11714 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 11715 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 11716 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 11717 ]; 11718 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 11719 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 11720 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 11721 ]; 11722 }, 11723 11724 _onMoveEnd: function () { 11725 if (!this._map || this._map._animatingZoom) { return; } 11726 11727 this._update(); 11728 }, 11729 11730 _getTiledPixelBounds: function (center) { 11731 var map = this._map, 11732 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 11733 scale = map.getZoomScale(mapZoom, this._tileZoom), 11734 pixelCenter = map.project(center, this._tileZoom).floor(), 11735 halfSize = map.getSize().divideBy(scale * 2); 11736 11737 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 11738 }, 11739 11740 // Private method to load tiles in the grid's active zoom level according to map bounds 11741 _update: function (center) { 11742 var map = this._map; 11743 if (!map) { return; } 11744 var zoom = this._clampZoom(map.getZoom()); 11745 11746 if (center === undefined) { center = map.getCenter(); } 11747 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 11748 11749 var pixelBounds = this._getTiledPixelBounds(center), 11750 tileRange = this._pxBoundsToTileRange(pixelBounds), 11751 tileCenter = tileRange.getCenter(), 11752 queue = [], 11753 margin = this.options.keepBuffer, 11754 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 11755 tileRange.getTopRight().add([margin, -margin])); 11756 11757 // Sanity check: panic if the tile range contains Infinity somewhere. 11758 if (!(isFinite(tileRange.min.x) && 11759 isFinite(tileRange.min.y) && 11760 isFinite(tileRange.max.x) && 11761 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 11762 11763 for (var key in this._tiles) { 11764 var c = this._tiles[key].coords; 11765 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 11766 this._tiles[key].current = false; 11767 } 11768 } 11769 11770 // _update just loads more tiles. If the tile zoom level differs too much 11771 // from the map's, let _setView reset levels and prune old tiles. 11772 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 11773 11774 // create a queue of coordinates to load tiles from 11775 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 11776 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 11777 var coords = new Point(i, j); 11778 coords.z = this._tileZoom; 11779 11780 if (!this._isValidTile(coords)) { continue; } 11781 11782 var tile = this._tiles[this._tileCoordsToKey(coords)]; 11783 if (tile) { 11784 tile.current = true; 11785 } else { 11786 queue.push(coords); 11787 } 11788 } 11789 } 11790 11791 // sort tile queue to load tiles in order of their distance to center 11792 queue.sort(function (a, b) { 11793 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 11794 }); 11795 11796 if (queue.length !== 0) { 11797 // if it's the first batch of tiles to load 11798 if (!this._loading) { 11799 this._loading = true; 11800 // @event loading: Event 11801 // Fired when the grid layer starts loading tiles. 11802 this.fire('loading'); 11803 } 11804 11805 // create DOM fragment to append tiles in one batch 11806 var fragment = document.createDocumentFragment(); 11807 11808 for (i = 0; i < queue.length; i++) { 11809 this._addTile(queue[i], fragment); 11810 } 11811 11812 this._level.el.appendChild(fragment); 11813 } 11814 }, 11815 11816 _isValidTile: function (coords) { 11817 var crs = this._map.options.crs; 11818 11819 if (!crs.infinite) { 11820 // don't load tile if it's out of bounds and not wrapped 11821 var bounds = this._globalTileRange; 11822 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 11823 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 11824 } 11825 11826 if (!this.options.bounds) { return true; } 11827 11828 // don't load tile if it doesn't intersect the bounds in options 11829 var tileBounds = this._tileCoordsToBounds(coords); 11830 return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 11831 }, 11832 11833 _keyToBounds: function (key) { 11834 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 11835 }, 11836 11837 _tileCoordsToNwSe: function (coords) { 11838 var map = this._map, 11839 tileSize = this.getTileSize(), 11840 nwPoint = coords.scaleBy(tileSize), 11841 sePoint = nwPoint.add(tileSize), 11842 nw = map.unproject(nwPoint, coords.z), 11843 se = map.unproject(sePoint, coords.z); 11844 return [nw, se]; 11845 }, 11846 11847 // converts tile coordinates to its geographical bounds 11848 _tileCoordsToBounds: function (coords) { 11849 var bp = this._tileCoordsToNwSe(coords), 11850 bounds = new LatLngBounds(bp[0], bp[1]); 11851 11852 if (!this.options.noWrap) { 11853 bounds = this._map.wrapLatLngBounds(bounds); 11854 } 11855 return bounds; 11856 }, 11857 // converts tile coordinates to key for the tile cache 11858 _tileCoordsToKey: function (coords) { 11859 return coords.x + ':' + coords.y + ':' + coords.z; 11860 }, 11861 11862 // converts tile cache key to coordinates 11863 _keyToTileCoords: function (key) { 11864 var k = key.split(':'), 11865 coords = new Point(+k[0], +k[1]); 11866 coords.z = +k[2]; 11867 return coords; 11868 }, 11869 11870 _removeTile: function (key) { 11871 var tile = this._tiles[key]; 11872 if (!tile) { return; } 11873 11874 remove(tile.el); 11875 11876 delete this._tiles[key]; 11877 11878 // @event tileunload: TileEvent 11879 // Fired when a tile is removed (e.g. when a tile goes off the screen). 11880 this.fire('tileunload', { 11881 tile: tile.el, 11882 coords: this._keyToTileCoords(key) 11883 }); 11884 }, 11885 11886 _initTile: function (tile) { 11887 addClass(tile, 'leaflet-tile'); 11888 11889 var tileSize = this.getTileSize(); 11890 tile.style.width = tileSize.x + 'px'; 11891 tile.style.height = tileSize.y + 'px'; 11892 11893 tile.onselectstart = falseFn; 11894 tile.onmousemove = falseFn; 11895 11896 // update opacity on tiles in IE7-8 because of filter inheritance problems 11897 if (Browser.ielt9 && this.options.opacity < 1) { 11898 setOpacity(tile, this.options.opacity); 11899 } 11900 }, 11901 11902 _addTile: function (coords, container) { 11903 var tilePos = this._getTilePos(coords), 11904 key = this._tileCoordsToKey(coords); 11905 11906 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 11907 11908 this._initTile(tile); 11909 11910 // if createTile is defined with a second argument ("done" callback), 11911 // we know that tile is async and will be ready later; otherwise 11912 if (this.createTile.length < 2) { 11913 // mark tile as ready, but delay one frame for opacity animation to happen 11914 requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 11915 } 11916 11917 setPosition(tile, tilePos); 11918 11919 // save tile in cache 11920 this._tiles[key] = { 11921 el: tile, 11922 coords: coords, 11923 current: true 11924 }; 11925 11926 container.appendChild(tile); 11927 // @event tileloadstart: TileEvent 11928 // Fired when a tile is requested and starts loading. 11929 this.fire('tileloadstart', { 11930 tile: tile, 11931 coords: coords 11932 }); 11933 }, 11934 11935 _tileReady: function (coords, err, tile) { 11936 if (err) { 11937 // @event tileerror: TileErrorEvent 11938 // Fired when there is an error loading a tile. 11939 this.fire('tileerror', { 11940 error: err, 11941 tile: tile, 11942 coords: coords 11943 }); 11944 } 11945 11946 var key = this._tileCoordsToKey(coords); 11947 11948 tile = this._tiles[key]; 11949 if (!tile) { return; } 11950 11951 tile.loaded = +new Date(); 11952 if (this._map._fadeAnimated) { 11953 setOpacity(tile.el, 0); 11954 cancelAnimFrame(this._fadeFrame); 11955 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11956 } else { 11957 tile.active = true; 11958 this._pruneTiles(); 11959 } 11960 11961 if (!err) { 11962 addClass(tile.el, 'leaflet-tile-loaded'); 11963 11964 // @event tileload: TileEvent 11965 // Fired when a tile loads. 11966 this.fire('tileload', { 11967 tile: tile.el, 11968 coords: coords 11969 }); 11970 } 11971 11972 if (this._noTilesToLoad()) { 11973 this._loading = false; 11974 // @event load: Event 11975 // Fired when the grid layer loaded all visible tiles. 11976 this.fire('load'); 11977 11978 if (Browser.ielt9 || !this._map._fadeAnimated) { 11979 requestAnimFrame(this._pruneTiles, this); 11980 } else { 11981 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 11982 // to trigger a pruning. 11983 setTimeout(bind(this._pruneTiles, this), 250); 11984 } 11985 } 11986 }, 11987 11988 _getTilePos: function (coords) { 11989 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 11990 }, 11991 11992 _wrapCoords: function (coords) { 11993 var newCoords = new Point( 11994 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 11995 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y); 11996 newCoords.z = coords.z; 11997 return newCoords; 11998 }, 11999 12000 _pxBoundsToTileRange: function (bounds) { 12001 var tileSize = this.getTileSize(); 12002 return new Bounds( 12003 bounds.min.unscaleBy(tileSize).floor(), 12004 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 12005 }, 12006 12007 _noTilesToLoad: function () { 12008 for (var key in this._tiles) { 12009 if (!this._tiles[key].loaded) { return false; } 12010 } 12011 return true; 12012 } 12013}); 12014 12015// @factory L.gridLayer(options?: GridLayer options) 12016// Creates a new instance of GridLayer with the supplied options. 12017function gridLayer(options) { 12018 return new GridLayer(options); 12019} 12020 12021/*
12021 12022 * @class TileLayer 12023 * @inherits GridLayer 12024 * @aka L.TileLayer 12025 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`. 12026 * 12027 * @example 12028 * 12029 * ```js 12030 * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map); 12031 * ``` 12032 * 12033 * @section URL template 12034 * @example 12035 * 12036 * A string of the following form: 12037 * 12038 * ``` 12039 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 12040 * ``` 12041 * 12042 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` â zoom level, `{x}` and `{y}` â tile coordinates. `{r}` can be used to add "@2x" to the URL to load retina tiles. 12043 * 12044 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 12045 * 12046 * ``` 12047 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 12048 * ``` 12049 */ 12050 12051 12052var TileLayer = GridLayer.extend({ 12053 12054 // @section 12055 // @aka TileLayer options 12056 options: { 12057 // @option minZoom: Number = 0 12058 // The minimum zoom level down to which this layer will be displayed (inclusive). 12059 minZoom: 0, 12060 12061 // @option maxZoom: Number = 18 12062 // The maximum zoom level up to which this layer will be displayed (inclusive). 12063 maxZoom: 18, 12064 12065 // @option subdomains: String|String[] = 'abc' 12066 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of str
vendor: 11,431 bytes, lines 12066-12433
12066ings. 12067 subdomains: 'abc', 12068 12069 // @option errorTileUrl: String = '' 12070 // URL to the tile image to show in place of the tile that failed to load. 12071 errorTileUrl: '', 12072 12073 // @option zoomOffset: Number = 0 12074 // The zoom number used in tile URLs will be offset with this value. 12075 zoomOffset: 0, 12076 12077 // @option tms: Boolean = false 12078 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 12079 tms: false, 12080 12081 // @option zoomReverse: Boolean = false 12082 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 12083 zoomReverse: false, 12084 12085 // @option detectRetina: Boolean = false 12086 // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution. 12087 detectRetina: false, 12088 12089 // @option crossOrigin: Boolean|String = false 12090 // Whether the crossOrigin attribute will be added to the tiles. 12091 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data. 12092 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 12093 crossOrigin: false, 12094 12095 // @option referrerPolicy: Boolean|String = false 12096 // Whether the referrerPolicy attribute will be added to the tiles. 12097 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided. 12098 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer 12099 // (e.g. to validate an API token). 12100 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values. 12101 referrerPolicy: false 12102 }, 12103 12104 initialize: function (url, options) { 12105 12106 this._url = url; 12107 12108 options = setOptions(this, options); 12109 12110 // detecting retina displays, adjusting tileSize and zoom levels 12111 if (options.detectRetina && Browser.retina && options.maxZoom > 0) { 12112 12113 options.tileSize = Math.floor(options.tileSize / 2); 12114 12115 if (!options.zoomReverse) { 12116 options.zoomOffset++; 12117 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1); 12118 } else { 12119 options.zoomOffset--; 12120 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1); 12121 } 12122 12123 options.minZoom = Math.max(0, options.minZoom); 12124 } else if (!options.zoomReverse) { 12125 // make sure maxZoom is gte minZoom 12126 options.maxZoom = Math.max(options.minZoom, options.maxZoom); 12127 } else { 12128 // make sure minZoom is lte maxZoom 12129 options.minZoom = Math.min(options.maxZoom, options.minZoom); 12130 } 12131 12132 if (typeof options.subdomains === 'string') { 12133 options.subdomains = options.subdomains.split(''); 12134 } 12135 12136 this.on('tileunload', this._onTileRemove); 12137 }, 12138 12139 // @method setUrl(url: String, noRedraw?: Boolean): this 12140 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 12141 // If the URL does not change, the layer will not be redrawn unless 12142 // the noRedraw parameter is set to false. 12143 setUrl: function (url, noRedraw) { 12144 if (this._url === url && noRedraw === undefined) { 12145 noRedraw = true; 12146 } 12147 12148 this._url = url; 12149 12150 if (!noRedraw) { 12151 this.redraw(); 12152 } 12153 return this; 12154 }, 12155 12156 // @method createTile(coords: Object, done?: Function): HTMLElement 12157 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 12158 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 12159 // callback is called when the tile has been loaded. 12160 createTile: function (coords, done) { 12161 var tile = document.createElement('img'); 12162 12163 on(tile, 'load', bind(this._tileOnLoad, this, done, tile)); 12164 on(tile, 'error', bind(this._tileOnError, this, done, tile)); 12165 12166 if (this.options.crossOrigin || this.options.crossOrigin === '') { 12167 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 12168 } 12169 12170 // for this new option we follow the documented behavior 12171 // more closely by only setting the property when string 12172 if (typeof this.options.referrerPolicy === 'string') { 12173 tile.referrerPolicy = this.options.referrerPolicy; 12174 } 12175 12176 // The alt attribute is set to the empty string, 12177 // allowing screen readers to ignore the decorative image tiles. 12178 // https://www.w3.org/WAI/tutorials/images/decorative/ 12179 // https://www.w3.org/TR/html-aria/#el-img-empty-alt 12180 tile.alt = ''; 12181 12182 tile.src = this.getTileUrl(coords); 12183 12184 return tile; 12185 }, 12186 12187 // @section Extension methods 12188 // @uninheritable 12189 // Layers extending `TileLayer` might reimplement the following method. 12190 // @method getTileUrl(coords: Object): String 12191 // Called only internally, returns the URL for a tile given its coordinates. 12192 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 12193 getTileUrl: function (coords) { 12194 var data = { 12195 r: Browser.retina ? '@2x' : '', 12196 s: this._getSubdomain(coords), 12197 x: coords.x, 12198 y: coords.y, 12199 z: this._getZoomForUrl() 12200 }; 12201 if (this._map && !this._map.options.crs.infinite) { 12202 var invertedY = this._globalTileRange.max.y - coords.y; 12203 if (this.options.tms) { 12204 data['y'] = invertedY; 12205 } 12206 data['-y'] = invertedY; 12207 } 12208 12209 return template(this._url, extend(data, this.options)); 12210 }, 12211 12212 _tileOnLoad: function (done, tile) { 12213 // For https://github.com/Leaflet/Leaflet/issues/3332 12214 if (Browser.ielt9) { 12215 setTimeout(bind(done, this, null, tile), 0); 12216 } else { 12217 done(null, tile); 12218 } 12219 }, 12220 12221 _tileOnError: function (done, tile, e) { 12222 var errorUrl = this.options.errorTileUrl; 12223 if (errorUrl && tile.getAttribute('src') !== errorUrl) { 12224 tile.src = errorUrl; 12225 } 12226 done(e, tile); 12227 }, 12228 12229 _onTileRemove: function (e) { 12230 e.tile.onload = null; 12231 }, 12232 12233 _getZoomForUrl: function () { 12234 var zoom = this._tileZoom, 12235 maxZoom = this.options.maxZoom, 12236 zoomReverse = this.options.zoomReverse, 12237 zoomOffset = this.options.zoomOffset; 12238 12239 if (zoomReverse) { 12240 zoom = maxZoom - zoom; 12241 } 12242 12243 return zoom + zoomOffset; 12244 }, 12245 12246 _getSubdomain: function (tilePoint) { 12247 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 12248 return this.options.subdomains[index]; 12249 }, 12250 12251 // stops loading all tiles in the background layer 12252 _abortLoading: function () { 12253 var i, tile; 12254 for (i in this._tiles) { 12255 if (this._tiles[i].coords.z !== this._tileZoom) { 12256 tile = this._tiles[i].el; 12257 12258 tile.onload = falseFn; 12259 tile.onerror = falseFn; 12260 12261 if (!tile.complete) { 12262 tile.src = emptyImageUrl; 12263 var coords = this._tiles[i].coords; 12264 remove(tile); 12265 delete this._tiles[i]; 12266 // @event tileabort: TileEvent 12267 // Fired when a tile was loading but is now not wanted. 12268 this.fire('tileabort', { 12269 tile: tile, 12270 coords: coords 12271 }); 12272 } 12273 } 12274 } 12275 }, 12276 12277 _removeTile: function (key) { 12278 var tile = this._tiles[key]; 12279 if (!tile) { return; } 12280 12281 // Cancels any pending http requests associated with the tile 12282 tile.el.setAttribute('src', emptyImageUrl); 12283 12284 return GridLayer.prototype._removeTile.call(this, key); 12285 }, 12286 12287 _tileReady: function (coords, err, tile) { 12288 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) { 12289 return; 12290 } 12291 12292 return GridLayer.prototype._tileReady.call(this, coords, err, tile); 12293 } 12294}); 12295 12296 12297// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 12298// Instantiates a tile layer object given a `URL template` and optionally an options object. 12299 12300function tileLayer(url, options) { 12301 return new TileLayer(url, options); 12302} 12303 12304/* 12305 * @class TileLayer.WMS 12306 * @inherits TileLayer 12307 * @aka L.TileLayer.WMS 12308 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 12309 * 12310 * @example 12311 * 12312 * ```js 12313 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 12314 * layers: 'nexrad-n0r-900913', 12315 * format: 'image/png', 12316 * transparent: true, 12317 * attribution: "Weather data © 2012 IEM Nexrad" 12318 * }); 12319 * ``` 12320 */ 12321 12322var TileLayerWMS = TileLayer.extend({ 12323 12324 // @section 12325 // @aka TileLayer.WMS options 12326 // If any custom options not documented here are used, they will be sent to the 12327 // WMS server as extra parameters in each request URL. This can be useful for 12328 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 12329 defaultWmsParams: { 12330 service: 'WMS', 12331 request: 'GetMap', 12332 12333 // @option layers: String = '' 12334 // **(required)** Comma-separated list of WMS layers to show. 12335 layers: '', 12336 12337 // @option styles: String = '' 12338 // Comma-separated list of WMS styles. 12339 styles: '', 12340 12341 // @option format: String = 'image/jpeg' 12342 // WMS image format (use `'image/png'` for layers with transparency). 12343 format: 'image/jpeg', 12344 12345 // @option transparent: Boolean = false 12346 // If `true`, the WMS service will return images with transparency. 12347 transparent: false, 12348 12349 // @option version: String = '1.1.1' 12350 // Version of the WMS service to use 12351 version: '1.1.1' 12352 }, 12353 12354 options: { 12355 // @option crs: CRS = null 12356 // Coordinate Reference System to use for the WMS requests, defaults to 12357 // map CRS. Don't change this if you're not sure what it means. 12358 crs: null, 12359 12360 // @option uppercase: Boolean = false 12361 // If `true`, WMS request parameter keys will be uppercase. 12362 uppercase: false 12363 }, 12364 12365 initialize: function (url, options) { 12366 12367 this._url = url; 12368 12369 var wmsParams = extend({}, this.defaultWmsParams); 12370 12371 // all keys that are not TileLayer options go to WMS params 12372 for (var i in options) { 12373 if (!(i in this.options)) { 12374 wmsParams[i] = options[i]; 12375 } 12376 } 12377 12378 options = setOptions(this, options); 12379 12380 var realRetina = options.detectRetina && Browser.retina ? 2 : 1; 12381 var tileSize = this.getTileSize(); 12382 wmsParams.width = tileSize.x * realRetina; 12383 wmsParams.height = tileSize.y * realRetina; 12384 12385 this.wmsParams = wmsParams; 12386 }, 12387 12388 onAdd: function (map) { 12389 12390 this._crs = this.options.crs || map.options.crs; 12391 this._wmsVersion = parseFloat(this.wmsParams.version); 12392 12393 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 12394 this.wmsParams[projectionKey] = this._crs.code; 12395 12396 TileLayer.prototype.onAdd.call(this, map); 12397 }, 12398 12399 getTileUrl: function (coords) { 12400 12401 var tileBounds = this._tileCoordsToNwSe(coords), 12402 crs = this._crs, 12403 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), 12404 min = bounds.min, 12405 max = bounds.max, 12406 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? 12407 [min.y, min.x, max.y, max.x] : 12408 [min.x, min.y, max.x, max.y]).join(','), 12409 url = TileLayer.prototype.getTileUrl.call(this, coords); 12410 return url + 12411 getParamString(this.wmsParams, url, this.options.uppercase) + 12412 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 12413 }, 12414 12415 // @method setParams(params: Object, noRedraw?: Boolean): this 12416 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 12417 setParams: function (params, noRedraw) { 12418 12419 extend(this.wmsParams, params); 12420 12421 if (!noRedraw) { 12422 this.redraw(); 12423 } 12424 12425 return this; 12426 } 12427}); 12428 12429 12430// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 12431// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 12432function tileLayerWMS(url, options) { 12433 return new TileLayerWMS(url, options);
vendor: 52,931 bytes, lines 12433-14418
12433 12434} 12435 12436TileLayer.WMS = TileLayerWMS; 12437tileLayer.wms = tileLayerWMS; 12438 12439/* 12440 * @class Renderer 12441 * @inherits Layer 12442 * @aka L.Renderer 12443 * 12444 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 12445 * DOM container of the renderer, its bounds, and its zoom animation. 12446 * 12447 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 12448 * itself can be added or removed to the map. All paths use a renderer, which can 12449 * be implicit (the map will decide the type of renderer and use it automatically) 12450 * or explicit (using the [`renderer`](#path-renderer) option of the path). 12451 * 12452 * Do not use this class directly, use `SVG` and `Canvas` instead. 12453 * 12454 * @event update: Event 12455 * Fired when the renderer updates its bounds, center and zoom, for example when 12456 * its map has moved 12457 */ 12458 12459var Renderer = Layer.extend({ 12460 12461 // @section 12462 // @aka Renderer options 12463 options: { 12464 // @option padding: Number = 0.1 12465 // How much to extend the clip area around the map view (relative to its size) 12466 // e.g. 0.1 would be 10% of map view in each direction 12467 padding: 0.1 12468 }, 12469 12470 initialize: function (options) { 12471 setOptions(this, options); 12472 stamp(this); 12473 this._layers = this._layers || {}; 12474 }, 12475 12476 onAdd: function () { 12477 if (!this._container) { 12478 this._initContainer(); // defined by renderer implementations 12479 12480 // always keep transform-origin as 0 0 12481 addClass(this._container, 'leaflet-zoom-animated'); 12482 } 12483 12484 this.getPane().appendChild(this._container); 12485 this._update(); 12486 this.on('update', this._updatePaths, this); 12487 }, 12488 12489 onRemove: function () { 12490 this.off('update', this._updatePaths, this); 12491 this._destroyContainer(); 12492 }, 12493 12494 getEvents: function () { 12495 var events = { 12496 viewreset: this._reset, 12497 zoom: this._onZoom, 12498 moveend: this._update, 12499 zoomend: this._onZoomEnd 12500 }; 12501 if (this._zoomAnimated) { 12502 events.zoomanim = this._onAnimZoom; 12503 } 12504 return events; 12505 }, 12506 12507 _onAnimZoom: function (ev) { 12508 this._updateTransform(ev.center, ev.zoom); 12509 }, 12510 12511 _onZoom: function () { 12512 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 12513 }, 12514 12515 _updateTransform: function (center, zoom) { 12516 var scale = this._map.getZoomScale(zoom, this._zoom), 12517 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 12518 currentCenterPoint = this._map.project(this._center, zoom), 12519 12520 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint) 12521 .subtract(this._map._getNewPixelOrigin(center, zoom)); 12522 12523 if (Browser.any3d) { 12524 setTransform(this._container, topLeftOffset, scale); 12525 } else { 12526 setPosition(this._container, topLeftOffset); 12527 } 12528 }, 12529 12530 _reset: function () { 12531 this._update(); 12532 this._updateTransform(this._center, this._zoom); 12533 12534 for (var id in this._layers) { 12535 this._layers[id]._reset(); 12536 } 12537 }, 12538 12539 _onZoomEnd: function () { 12540 for (var id in this._layers) { 12541 this._layers[id]._project(); 12542 } 12543 }, 12544 12545 _updatePaths: function () { 12546 for (var id in this._layers) { 12547 this._layers[id]._update(); 12548 } 12549 }, 12550 12551 _update: function () { 12552 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 12553 // Subclasses are responsible of firing the 'update' event. 12554 var p = this.options.padding, 12555 size = this._map.getSize(), 12556 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 12557 12558 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 12559 12560 this._center = this._map.getCenter(); 12561 this._zoom = this._map.getZoom(); 12562 } 12563}); 12564 12565/* 12566 * @class Canvas 12567 * @inherits Renderer 12568 * @aka L.Canvas 12569 * 12570 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 12571 * Inherits `Renderer`. 12572 * 12573 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not 12574 * available in all web browsers, notably IE8, and overlapping geometries might 12575 * not display properly in some edge cases. 12576 * 12577 * @example 12578 * 12579 * Use Canvas by default for all paths in the map: 12580 * 12581 * ```js 12582 * var map = L.map('map', { 12583 * renderer: L.canvas() 12584 * }); 12585 * ``` 12586 * 12587 * Use a Canvas renderer with extra padding for specific vector geometries: 12588 * 12589 * ```js 12590 * var map = L.map('map'); 12591 * var myRenderer = L.canvas({ padding: 0.5 }); 12592 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12593 * var circle = L.circle( center, { renderer: myRenderer } ); 12594 * ``` 12595 */ 12596 12597var Canvas = Renderer.extend({ 12598 12599 // @section 12600 // @aka Canvas options 12601 options: { 12602 // @option tolerance: Number = 0 12603 // How much to extend the click tolerance around a path/object on the map. 12604 tolerance: 0 12605 }, 12606 12607 getEvents: function () { 12608 var events = Renderer.prototype.getEvents.call(this); 12609 events.viewprereset = this._onViewPreReset; 12610 return events; 12611 }, 12612 12613 _onViewPreReset: function () { 12614 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 12615 this._postponeUpdatePaths = true; 12616 }, 12617 12618 onAdd: function () { 12619 Renderer.prototype.onAdd.call(this); 12620 12621 // Redraw vectors since canvas is cleared upon removal, 12622 // in case of removing the renderer itself from the map. 12623 this._draw(); 12624 }, 12625 12626 _initContainer: function () { 12627 var container = this._container = document.createElement('canvas'); 12628 12629 on(container, 'mousemove', this._onMouseMove, this); 12630 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); 12631 on(container, 'mouseout', this._handleMouseOut, this); 12632 container['_leaflet_disable_events'] = true; 12633 12634 this._ctx = container.getContext('2d'); 12635 }, 12636 12637 _destroyContainer: function () { 12638 cancelAnimFrame(this._redrawRequest); 12639 delete this._ctx; 12640 remove(this._container); 12641 off(this._container); 12642 delete this._container; 12643 }, 12644 12645 _updatePaths: function () { 12646 if (this._postponeUpdatePaths) { return; } 12647 12648 var layer; 12649 this._redrawBounds = null; 12650 for (var id in this._layers) { 12651 layer = this._layers[id]; 12652 layer._update(); 12653 } 12654 this._redraw(); 12655 }, 12656 12657 _update: function () { 12658 if (this._map._animatingZoom && this._bounds) { return; } 12659 12660 Renderer.prototype._update.call(this); 12661 12662 var b = this._bounds, 12663 container = this._container, 12664 size = b.getSize(), 12665 m = Browser.retina ? 2 : 1; 12666 12667 setPosition(container, b.min); 12668 12669 // set canvas size (also clearing it); use double size on retina 12670 container.width = m * size.x; 12671 container.height = m * size.y; 12672 container.style.width = size.x + 'px'; 12673 container.style.height = size.y + 'px'; 12674 12675 if (Browser.retina) { 12676 this._ctx.scale(2, 2); 12677 } 12678 12679 // translate so we use the same path coordinates after canvas element moves 12680 this._ctx.translate(-b.min.x, -b.min.y); 12681 12682 // Tell paths to redraw themselves 12683 this.fire('update'); 12684 }, 12685 12686 _reset: function () { 12687 Renderer.prototype._reset.call(this); 12688 12689 if (this._postponeUpdatePaths) { 12690 this._postponeUpdatePaths = false; 12691 this._updatePaths(); 12692 } 12693 }, 12694 12695 _initPath: function (layer) { 12696 this._updateDashArray(layer); 12697 this._layers[stamp(layer)] = layer; 12698 12699 var order = layer._order = { 12700 layer: layer, 12701 prev: this._drawLast, 12702 next: null 12703 }; 12704 if (this._drawLast) { this._drawLast.next = order; } 12705 this._drawLast = order; 12706 this._drawFirst = this._drawFirst || this._drawLast; 12707 }, 12708 12709 _addPath: function (layer) { 12710 this._requestRedraw(layer); 12711 }, 12712 12713 _removePath: function (layer) { 12714 var order = layer._order; 12715 var next = order.next; 12716 var prev = order.prev; 12717 12718 if (next) { 12719 next.prev = prev; 12720 } else { 12721 this._drawLast = prev; 12722 } 12723 if (prev) { 12724 prev.next = next; 12725 } else { 12726 this._drawFirst = next; 12727 } 12728 12729 delete layer._order; 12730 12731 delete this._layers[stamp(layer)]; 12732 12733 this._requestRedraw(layer); 12734 }, 12735 12736 _updatePath: function (layer) { 12737 // Redraw the union of the layer's old pixel 12738 // bounds and the new pixel bounds. 12739 this._extendRedrawBounds(layer); 12740 layer._project(); 12741 layer._update(); 12742 // The redraw will extend the redraw bounds 12743 // with the new pixel bounds. 12744 this._requestRedraw(layer); 12745 }, 12746 12747 _updateStyle: function (layer) { 12748 this._updateDashArray(layer); 12749 this._requestRedraw(layer); 12750 }, 12751 12752 _updateDashArray: function (layer) { 12753 if (typeof layer.options.dashArray === 'string') { 12754 var parts = layer.options.dashArray.split(/[, ]+/), 12755 dashArray = [], 12756 dashValue, 12757 i; 12758 for (i = 0; i < parts.length; i++) { 12759 dashValue = Number(parts[i]); 12760 // Ignore dash array containing invalid lengths 12761 if (isNaN(dashValue)) { return; } 12762 dashArray.push(dashValue); 12763 } 12764 layer.options._dashArray = dashArray; 12765 } else { 12766 layer.options._dashArray = layer.options.dashArray; 12767 } 12768 }, 12769 12770 _requestRedraw: function (layer) { 12771 if (!this._map) { return; } 12772 12773 this._extendRedrawBounds(layer); 12774 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 12775 }, 12776 12777 _extendRedrawBounds: function (layer) { 12778 if (layer._pxBounds) { 12779 var padding = (layer.options.weight || 0) + 1; 12780 this._redrawBounds = this._redrawBounds || new Bounds(); 12781 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 12782 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 12783 } 12784 }, 12785 12786 _redraw: function () { 12787 this._redrawRequest = null; 12788 12789 if (this._redrawBounds) { 12790 this._redrawBounds.min._floor(); 12791 this._redrawBounds.max._ceil(); 12792 } 12793 12794 this._clear(); // clear layers in redraw bounds 12795 this._draw(); // draw layers 12796 12797 this._redrawBounds = null; 12798 }, 12799 12800 _clear: function () { 12801 var bounds = this._redrawBounds; 12802 if (bounds) { 12803 var size = bounds.getSize(); 12804 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 12805 } else { 12806 this._ctx.save(); 12807 this._ctx.setTransform(1, 0, 0, 1, 0, 0); 12808 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 12809 this._ctx.restore(); 12810 } 12811 }, 12812 12813 _draw: function () { 12814 var layer, bounds = this._redrawBounds; 12815 this._ctx.save(); 12816 if (bounds) { 12817 var size = bounds.getSize(); 12818 this._ctx.beginPath(); 12819 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 12820 this._ctx.clip(); 12821 } 12822 12823 this._drawing = true; 12824 12825 for (var order = this._drawFirst; order; order = order.next) { 12826 layer = order.layer; 12827 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 12828 layer._updatePath(); 12829 } 12830 } 12831 12832 this._drawing = false; 12833 12834 this._ctx.restore(); // Restore state before clipping. 12835 }, 12836 12837 _updatePoly: function (layer, closed) { 12838 if (!this._drawing) { return; } 12839 12840 var i, j, len2, p, 12841 parts = layer._parts, 12842 len = parts.length, 12843 ctx = this._ctx; 12844 12845 if (!len) { return; } 12846 12847 ctx.beginPath(); 12848 12849 for (i = 0; i < len; i++) { 12850 for (j = 0, len2 = parts[i].length; j < len2; j++) { 12851 p = parts[i][j]; 12852 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 12853 } 12854 if (closed) { 12855 ctx.closePath(); 12856 } 12857 } 12858 12859 this._fillStroke(ctx, layer); 12860 12861 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 12862 }, 12863 12864 _updateCircle: function (layer) { 12865 12866 if (!this._drawing || layer._empty()) { return; } 12867 12868 var p = layer._point, 12869 ctx = this._ctx, 12870 r = Math.max(Math.round(layer._radius), 1), 12871 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 12872 12873 if (s !== 1) { 12874 ctx.save(); 12875 ctx.scale(1, s); 12876 } 12877 12878 ctx.beginPath(); 12879 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 12880 12881 if (s !== 1) { 12882 ctx.restore(); 12883 } 12884 12885 this._fillStroke(ctx, layer); 12886 }, 12887 12888 _fillStroke: function (ctx, layer) { 12889 var options = layer.options; 12890 12891 if (options.fill) { 12892 ctx.globalAlpha = options.fillOpacity; 12893 ctx.fillStyle = options.fillColor || options.color; 12894 ctx.fill(options.fillRule || 'evenodd'); 12895 } 12896 12897 if (options.stroke && options.weight !== 0) { 12898 if (ctx.setLineDash) { 12899 ctx.setLineDash(layer.options && layer.options._dashArray || []); 12900 } 12901 ctx.globalAlpha = options.opacity; 12902 ctx.lineWidth = options.weight; 12903 ctx.strokeStyle = options.color; 12904 ctx.lineCap = options.lineCap; 12905 ctx.lineJoin = options.lineJoin; 12906 ctx.stroke(); 12907 } 12908 }, 12909 12910 // Canvas obviously doesn't have mouse events for individual drawn objects, 12911 // so we emulate that by calculating what's under the mouse on mousemove/click manually 12912 12913 _onClick: function (e) { 12914 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 12915 12916 for (var order = this._drawFirst; order; order = order.next) { 12917 layer = order.layer; 12918 if (layer.options.interactive && layer._containsPoint(point)) { 12919 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) { 12920 clickedLayer = layer; 12921 } 12922 } 12923 } 12924 this._fireEvent(clickedLayer ? [clickedLayer] : false, e); 12925 }, 12926 12927 _onMouseMove: function (e) { 12928 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 12929 12930 var point = this._map.mouseEventToLayerPoint(e); 12931 this._handleMouseHover(e, point); 12932 }, 12933 12934 12935 _handleMouseOut: function (e) { 12936 var layer = this._hoveredLayer; 12937 if (layer) { 12938 // if we're leaving the layer, fire mouseout 12939 removeClass(this._container, 'leaflet-interactive'); 12940 this._fireEvent([layer], e, 'mouseout'); 12941 this._hoveredLayer = null; 12942 this._mouseHoverThrottled = false; 12943 } 12944 }, 12945 12946 _handleMouseHover: function (e, point) { 12947 if (this._mouseHoverThrottled) { 12948 return; 12949 } 12950 12951 var layer, candidateHoveredLayer; 12952 12953 for (var order = this._drawFirst; order; order = order.next) { 12954 layer = order.layer; 12955 if (layer.options.interactive && layer._containsPoint(point)) { 12956 candidateHoveredLayer = layer; 12957 } 12958 } 12959 12960 if (candidateHoveredLayer !== this._hoveredLayer) { 12961 this._handleMouseOut(e); 12962 12963 if (candidateHoveredLayer) { 12964 addClass(this._container, 'leaflet-interactive'); // change cursor 12965 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 12966 this._hoveredLayer = candidateHoveredLayer; 12967 } 12968 } 12969 12970 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e); 12971 12972 this._mouseHoverThrottled = true; 12973 setTimeout(bind(function () { 12974 this._mouseHoverThrottled = false; 12975 }, this), 32); 12976 }, 12977 12978 _fireEvent: function (layers, e, type) { 12979 this._map._fireDOMEvent(e, type || e.type, layers); 12980 }, 12981 12982 _bringToFront: function (layer) { 12983 var order = layer._order; 12984 12985 if (!order) { return; } 12986 12987 var next = order.next; 12988 var prev = order.prev; 12989 12990 if (next) { 12991 next.prev = prev; 12992 } else { 12993 // Already last 12994 return; 12995 } 12996 if (prev) { 12997 prev.next = next; 12998 } else if (next) { 12999 // Update first entry unless this is the 13000 // single entry 13001 this._drawFirst = next; 13002 } 13003 13004 order.prev = this._drawLast; 13005 this._drawLast.next = order; 13006 13007 order.next = null; 13008 this._drawLast = order; 13009 13010 this._requestRedraw(layer); 13011 }, 13012 13013 _bringToBack: function (layer) { 13014 var order = layer._order; 13015 13016 if (!order) { return; } 13017 13018 var next = order.next; 13019 var prev = order.prev; 13020 13021 if (prev) { 13022 prev.next = next; 13023 } else { 13024 // Already first 13025 return; 13026 } 13027 if (next) { 13028 next.prev = prev; 13029 } else if (prev) { 13030 // Update last entry unless this is the 13031 // single entry 13032 this._drawLast = prev; 13033 } 13034 13035 order.prev = null; 13036 13037 order.next = this._drawFirst; 13038 this._drawFirst.prev = order; 13039 this._drawFirst = order; 13040 13041 this._requestRedraw(layer); 13042 } 13043}); 13044 13045// @factory L.canvas(options?: Renderer options) 13046// Creates a Canvas renderer with the given options. 13047function canvas(options) { 13048 return Browser.canvas ? new Canvas(options) : null; 13049} 13050 13051/* 13052 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 13053 */ 13054 13055 13056var vmlCreate = (function () { 13057 try { 13058 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 13059 return function (name) { 13060 return document.createElement('<lvml:' + name + ' class="lvml">'); 13061 }; 13062 } catch (e) { 13063 // Do not return fn from catch block so `e` can be garbage collected 13064 // See https://github.com/Leaflet/Leaflet/pull/7279 13065 } 13066 return function (name) { 13067 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 13068 }; 13069})(); 13070 13071 13072/* 13073 * @class SVG 13074 * 13075 * 13076 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 13077 * with old versions of Internet Explorer. 13078 */ 13079 13080// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences 13081var vmlMixin = { 13082 13083 _initContainer: function () { 13084 this._container = create$1('div', 'leaflet-vml-container'); 13085 }, 13086 13087 _update: function () { 13088 if (this._map._animatingZoom) { return; } 13089 Renderer.prototype._update.call(this); 13090 this.fire('update'); 13091 }, 13092 13093 _initPath: function (layer) { 13094 var container = layer._container = vmlCreate('shape'); 13095 13096 addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 13097 13098 container.coordsize = '1 1'; 13099 13100 layer._path = vmlCreate('path'); 13101 container.appendChild(layer._path); 13102 13103 this._updateStyle(layer); 13104 this._layers[stamp(layer)] = layer; 13105 }, 13106 13107 _addPath: function (layer) { 13108 var container = layer._container; 13109 this._container.appendChild(container); 13110 13111 if (layer.options.interactive) { 13112 layer.addInteractiveTarget(container); 13113 } 13114 }, 13115 13116 _removePath: function (layer) { 13117 var container = layer._container; 13118 remove(container); 13119 layer.removeInteractiveTarget(container); 13120 delete this._layers[stamp(layer)]; 13121 }, 13122 13123 _updateStyle: function (layer) { 13124 var stroke = layer._stroke, 13125 fill = layer._fill, 13126 options = layer.options, 13127 container = layer._container; 13128 13129 container.stroked = !!options.stroke; 13130 container.filled = !!options.fill; 13131 13132 if (options.stroke) { 13133 if (!stroke) { 13134 stroke = layer._stroke = vmlCreate('stroke'); 13135 } 13136 container.appendChild(stroke); 13137 stroke.weight = options.weight + 'px'; 13138 stroke.color = options.color; 13139 stroke.opacity = options.opacity; 13140 13141 if (options.dashArray) { 13142 stroke.dashStyle = isArray(options.dashArray) ? 13143 options.dashArray.join(' ') : 13144 options.dashArray.replace(/( *, *)/g, ' '); 13145 } else { 13146 stroke.dashStyle = ''; 13147 } 13148 stroke.endcap = options.lineCap.replace('butt', 'flat'); 13149 stroke.joinstyle = options.lineJoin; 13150 13151 } else if (stroke) { 13152 container.removeChild(stroke); 13153 layer._stroke = null; 13154 } 13155 13156 if (options.fill) { 13157 if (!fill) { 13158 fill = layer._fill = vmlCreate('fill'); 13159 } 13160 container.appendChild(fill); 13161 fill.color = options.fillColor || options.color; 13162 fill.opacity = options.fillOpacity; 13163 13164 } else if (fill) { 13165 container.removeChild(fill); 13166 layer._fill = null; 13167 } 13168 }, 13169 13170 _updateCircle: function (layer) { 13171 var p = layer._point.round(), 13172 r = Math.round(layer._radius), 13173 r2 = Math.round(layer._radiusY || r); 13174 13175 this._setPath(layer, layer._empty() ? 'M0 0' : 13176 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 13177 }, 13178 13179 _setPath: function (layer, path) { 13180 layer._path.v = path; 13181 }, 13182 13183 _bringToFront: function (layer) { 13184 toFront(layer._container); 13185 }, 13186 13187 _bringToBack: function (layer) { 13188 toBack(layer._container); 13189 } 13190}; 13191 13192var create = Browser.vml ? vmlCreate : svgCreate; 13193 13194/* 13195 * @class SVG 13196 * @inherits Renderer 13197 * @aka L.SVG 13198 * 13199 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 13200 * Inherits `Renderer`. 13201 * 13202 * Due to [technical limitations](https://caniuse.com/svg), SVG is not 13203 * available in all web browsers, notably Android 2.x and 3.x. 13204 * 13205 * Although SVG is not available on IE7 and IE8, these browsers support 13206 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 13207 * (a now deprecated technology), and the SVG renderer will fall back to VML in 13208 * this case. 13209 * 13210 * @example 13211 * 13212 * Use SVG by default for all paths in the map: 13213 * 13214 * ```js 13215 * var map = L.map('map', { 13216 * renderer: L.svg() 13217 * }); 13218 * ``` 13219 * 13220 * Use a SVG renderer with extra padding for specific vector geometries: 13221 * 13222 * ```js 13223 * var map = L.map('map'); 13224 * var myRenderer = L.svg({ padding: 0.5 }); 13225 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 13226 * var circle = L.circle( center, { renderer: myRenderer } ); 13227 * ``` 13228 */ 13229 13230var SVG = Renderer.extend({ 13231 13232 _initContainer: function () { 13233 this._container = create('svg'); 13234 13235 // makes it possible to click through svg root; we'll reset it back in individual paths 13236 this._container.setAttribute('pointer-events', 'none'); 13237 13238 this._rootGroup = create('g'); 13239 this._container.appendChild(this._rootGroup); 13240 }, 13241 13242 _destroyContainer: function () { 13243 remove(this._container); 13244 off(this._container); 13245 delete this._container; 13246 delete this._rootGroup; 13247 delete this._svgSize; 13248 }, 13249 13250 _update: function () { 13251 if (this._map._animatingZoom && this._bounds) { return; } 13252 13253 Renderer.prototype._update.call(this); 13254 13255 var b = this._bounds, 13256 size = b.getSize(), 13257 container = this._container; 13258 13259 // set size of svg-container if changed 13260 if (!this._svgSize || !this._svgSize.equals(size)) { 13261 this._svgSize = size; 13262 container.setAttribute('width', size.x); 13263 container.setAttribute('height', size.y); 13264 } 13265 13266 // movement: update container viewBox so that we don't have to change coordinates of individual layers 13267 setPosition(container, b.min); 13268 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 13269 13270 this.fire('update'); 13271 }, 13272 13273 // methods below are called by vector layers implementations 13274 13275 _initPath: function (layer) { 13276 var path = layer._path = create('path'); 13277 13278 // @namespace Path 13279 // @option className: String = null 13280 // Custom class name set on an element. Only for SVG renderer. 13281 if (layer.options.className) { 13282 addClass(path, layer.options.className); 13283 } 13284 13285 if (layer.options.interactive) { 13286 addClass(path, 'leaflet-interactive'); 13287 } 13288 13289 this._updateStyle(layer); 13290 this._layers[stamp(layer)] = layer; 13291 }, 13292 13293 _addPath: function (layer) { 13294 if (!this._rootGroup) { this._initContainer(); } 13295 this._rootGroup.appendChild(layer._path); 13296 layer.addInteractiveTarget(layer._path); 13297 }, 13298 13299 _removePath: function (layer) { 13300 remove(layer._path); 13301 layer.removeInteractiveTarget(layer._path); 13302 delete this._layers[stamp(layer)]; 13303 }, 13304 13305 _updatePath: function (layer) { 13306 layer._project(); 13307 layer._update(); 13308 }, 13309 13310 _updateStyle: function (layer) { 13311 var path = layer._path, 13312 options = layer.options; 13313 13314 if (!path) { return; } 13315 13316 if (options.stroke) { 13317 path.setAttribute('stroke', options.color); 13318 path.setAttribute('stroke-opacity', options.opacity); 13319 path.setAttribute('stroke-width', options.weight); 13320 path.setAttribute('stroke-linecap', options.lineCap); 13321 path.setAttribute('stroke-linejoin', options.lineJoin); 13322 13323 if (options.dashArray) { 13324 path.setAttribute('stroke-dasharray', options.dashArray); 13325 } else { 13326 path.removeAttribute('stroke-dasharray'); 13327 } 13328 13329 if (options.dashOffset) { 13330 path.setAttribute('stroke-dashoffset', options.dashOffset); 13331 } else { 13332 path.removeAttribute('stroke-dashoffset'); 13333 } 13334 } else { 13335 path.setAttribute('stroke', 'none'); 13336 } 13337 13338 if (options.fill) { 13339 path.setAttribute('fill', options.fillColor || options.color); 13340 path.setAttribute('fill-opacity', options.fillOpacity); 13341 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 13342 } else { 13343 path.setAttribute('fill', 'none'); 13344 } 13345 }, 13346 13347 _updatePoly: function (layer, closed) { 13348 this._setPath(layer, pointsToPath(layer._parts, closed)); 13349 }, 13350 13351 _updateCircle: function (layer) { 13352 var p = layer._point, 13353 r = Math.max(Math.round(layer._radius), 1), 13354 r2 = Math.max(Math.round(layer._radiusY), 1) || r, 13355 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 13356 13357 // drawing a circle with two half-arcs 13358 var d = layer._empty() ? 'M0 0' : 13359 'M' + (p.x - r) + ',' + p.y + 13360 arc + (r * 2) + ',0 ' + 13361 arc + (-r * 2) + ',0 '; 13362 13363 this._setPath(layer, d); 13364 }, 13365 13366 _setPath: function (layer, path) { 13367 layer._path.setAttribute('d', path); 13368 }, 13369 13370 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 13371 _bringToFront: function (layer) { 13372 toFront(layer._path); 13373 }, 13374 13375 _bringToBack: function (layer) { 13376 toBack(layer._path); 13377 } 13378}); 13379 13380if (Browser.vml) { 13381 SVG.include(vmlMixin); 13382} 13383 13384// @namespace SVG 13385// @factory L.svg(options?: Renderer options) 13386// Creates a SVG renderer with the given options. 13387function svg(options) { 13388 return Browser.svg || Browser.vml ? new SVG(options) : null; 13389} 13390 13391Map.include({ 13392 // @namespace Map; @method getRenderer(layer: Path): Renderer 13393 // Returns the instance of `Renderer` that should be used to render the given 13394 // `Path`. It will ensure that the `renderer` options of the map and paths 13395 // are respected, and that the renderers do exist on the map. 13396 getRenderer: function (layer) { 13397 // @namespace Path; @option renderer: Renderer 13398 // Use this specific instance of `Renderer` for this path. Takes 13399 // precedence over the map's [default renderer](#map-renderer). 13400 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 13401 13402 if (!renderer) { 13403 renderer = this._renderer = this._createRenderer(); 13404 } 13405 13406 if (!this.hasLayer(renderer)) { 13407 this.addLayer(renderer); 13408 } 13409 return renderer; 13410 }, 13411 13412 _getPaneRenderer: function (name) { 13413 if (name === 'overlayPane' || name === undefined) { 13414 return false; 13415 } 13416 13417 var renderer = this._paneRenderers[name]; 13418 if (renderer === undefined) { 13419 renderer = this._createRenderer({pane: name}); 13420 this._paneRenderers[name] = renderer; 13421 } 13422 return renderer; 13423 }, 13424 13425 _createRenderer: function (options) { 13426 // @namespace Map; @option preferCanvas: Boolean = false 13427 // Whether `Path`s should be rendered on a `Canvas` renderer. 13428 // By default, all `Path`s are rendered in a `SVG` renderer. 13429 return (this.options.preferCanvas && canvas(options)) || svg(options); 13430 } 13431}); 13432 13433/* 13434 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. 13435 */ 13436 13437/* 13438 * @class Rectangle 13439 * @aka L.Rectangle 13440 * @inherits Polygon 13441 * 13442 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 13443 * 13444 * @example 13445 * 13446 * ```js 13447 * // define rectangle geographical bounds 13448 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 13449 * 13450 * // create an orange rectangle 13451 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 13452 * 13453 * // zoom the map to the rectangle bounds 13454 * map.fitBounds(bounds); 13455 * ``` 13456 * 13457 */ 13458 13459 13460var Rectangle = Polygon.extend({ 13461 initialize: function (latLngBounds, options) { 13462 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 13463 }, 13464 13465 // @method setBounds(latLngBounds: LatLngBounds): this 13466 // Redraws the rectangle with the passed bounds. 13467 setBounds: function (latLngBounds) { 13468 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 13469 }, 13470 13471 _boundsToLatLngs: function (latLngBounds) { 13472 latLngBounds = toLatLngBounds(latLngBounds); 13473 return [ 13474 latLngBounds.getSouthWest(), 13475 latLngBounds.getNorthWest(), 13476 latLngBounds.getNorthEast(), 13477 latLngBounds.getSouthEast() 13478 ]; 13479 } 13480}); 13481 13482 13483// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 13484function rectangle(latLngBounds, options) { 13485 return new Rectangle(latLngBounds, options); 13486} 13487 13488SVG.create = create; 13489SVG.pointsToPath = pointsToPath; 13490 13491GeoJSON.geometryToLayer = geometryToLayer; 13492GeoJSON.coordsToLatLng = coordsToLatLng; 13493GeoJSON.coordsToLatLngs = coordsToLatLngs; 13494GeoJSON.latLngToCoords = latLngToCoords; 13495GeoJSON.latLngsToCoords = latLngsToCoords; 13496GeoJSON.getFeature = getFeature; 13497GeoJSON.asFeature = asFeature; 13498 13499/* 13500 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 13501 * (zoom to a selected bounding box), enabled by default. 13502 */ 13503 13504// @namespace Map 13505// @section Interaction Options 13506Map.mergeOptions({ 13507 // @option boxZoom: Boolean = true 13508 // Whether the map can be zoomed to a rectangular area specified by 13509 // dragging the mouse while pressing the shift key. 13510 boxZoom: true 13511}); 13512 13513var BoxZoom = Handler.extend({ 13514 initialize: function (map) { 13515 this._map = map; 13516 this._container = map._container; 13517 this._pane = map._panes.overlayPane; 13518 this._resetStateTimeout = 0; 13519 map.on('unload', this._destroy, this); 13520 }, 13521 13522 addHooks: function () { 13523 on(this._container, 'mousedown', this._onMouseDown, this); 13524 }, 13525 13526 removeHooks: function () { 13527 off(this._container, 'mousedown', this._onMouseDown, this); 13528 }, 13529 13530 moved: function () { 13531 return this._moved; 13532 }, 13533 13534 _destroy: function () { 13535 remove(this._pane); 13536 delete this._pane; 13537 }, 13538 13539 _resetState: function () { 13540 this._resetStateTimeout = 0; 13541 this._moved = false; 13542 }, 13543 13544 _clearDeferredResetState: function () { 13545 if (this._resetStateTimeout !== 0) { 13546 clearTimeout(this._resetStateTimeout); 13547 this._resetStateTimeout = 0; 13548 } 13549 }, 13550 13551 _onMouseDown: function (e) { 13552 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 13553 13554 // Clear the deferred resetState if it hasn't executed yet, otherwise it 13555 // will interrupt the interaction and orphan a box element in the container. 13556 this._clearDeferredResetState(); 13557 this._resetState(); 13558 13559 disableTextSelection(); 13560 disableImageDrag(); 13561 13562 this._startPoint = this._map.mouseEventToContainerPoint(e); 13563 13564 on(document, { 13565 contextmenu: stop, 13566 mousemove: this._onMouseMove, 13567 mouseup: this._onMouseUp, 13568 keydown: this._onKeyDown 13569 }, this); 13570 }, 13571 13572 _onMouseMove: function (e) { 13573 if (!this._moved) { 13574 this._moved = true; 13575 13576 this._box = create$1('div', 'leaflet-zoom-box', this._container); 13577 addClass(this._container, 'leaflet-crosshair'); 13578 13579 this._map.fire('boxzoomstart'); 13580 } 13581 13582 this._point = this._map.mouseEventToContainerPoint(e); 13583 13584 var bounds = new Bounds(this._point, this._startPoint), 13585 size = bounds.getSize(); 13586 13587 setPosition(this._box, bounds.min); 13588 13589 this._box.style.width = size.x + 'px'; 13590 this._box.style.height = size.y + 'px'; 13591 }, 13592 13593 _finish: function () { 13594 if (this._moved) { 13595 remove(this._box); 13596 removeClass(this._container, 'leaflet-crosshair'); 13597 } 13598 13599 enableTextSelection(); 13600 enableImageDrag(); 13601 13602 off(document, { 13603 contextmenu: stop, 13604 mousemove: this._onMouseMove, 13605 mouseup: this._onMouseUp, 13606 keydown: this._onKeyDown 13607 }, this); 13608 }, 13609 13610 _onMouseUp: function (e) { 13611 if ((e.which !== 1) && (e.button !== 1)) { return; } 13612 13613 this._finish(); 13614 13615 if (!this._moved) { return; } 13616 // Postpone to next JS tick so internal click event handling 13617 // still see it as "moved". 13618 this._clearDeferredResetState(); 13619 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 13620 13621 var bounds = new LatLngBounds( 13622 this._map.containerPointToLatLng(this._startPoint), 13623 this._map.containerPointToLatLng(this._point)); 13624 13625 this._map 13626 .fitBounds(bounds) 13627 .fire('boxzoomend', {boxZoomBounds: bounds}); 13628 }, 13629 13630 _onKeyDown: function (e) { 13631 if (e.keyCode === 27) { 13632 this._finish(); 13633 this._clearDeferredResetState(); 13634 this._resetState(); 13635 } 13636 } 13637}); 13638 13639// @section Handlers 13640// @property boxZoom: Handler 13641// Box (shift-drag with mouse) zoom handler. 13642Map.addInitHook('addHandler', 'boxZoom', BoxZoom); 13643 13644/* 13645 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 13646 */ 13647 13648// @namespace Map 13649// @section Interaction Options 13650 13651Map.mergeOptions({ 13652 // @option doubleClickZoom: Boolean|String = true 13653 // Whether the map can be zoomed in by double clicking on it and 13654 // zoomed out by double clicking while holding shift. If passed 13655 // `'center'`, double-click zoom will zoom to the center of the 13656 // view regardless of where the mouse was. 13657 doubleClickZoom: true 13658}); 13659 13660var DoubleClickZoom = Handler.extend({ 13661 addHooks: function () { 13662 this._map.on('dblclick', this._onDoubleClick, this); 13663 }, 13664 13665 removeHooks: function () { 13666 this._map.off('dblclick', this._onDoubleClick, this); 13667 }, 13668 13669 _onDoubleClick: function (e) { 13670 var map = this._map, 13671 oldZoom = map.getZoom(), 13672 delta = map.options.zoomDelta, 13673 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 13674 13675 if (map.options.doubleClickZoom === 'center') { 13676 map.setZoom(zoom); 13677 } else { 13678 map.setZoomAround(e.containerPoint, zoom); 13679 } 13680 } 13681}); 13682 13683// @section Handlers 13684// 13685// Map properties include interaction handlers that allow you to control 13686// interaction behavior in runtime, enabling or disabling certain features such 13687// as dragging or touch zoom (see `Handler` methods). For example: 13688// 13689// ```js 13690// map.doubleClickZoom.disable(); 13691// ``` 13692// 13693// @property doubleClickZoom: Handler 13694// Double click zoom handler. 13695Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); 13696 13697/* 13698 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 13699 */ 13700 13701// @namespace Map 13702// @section Interaction Options 13703Map.mergeOptions({ 13704 // @option dragging: Boolean = true 13705 // Whether the map is draggable with mouse/touch or not. 13706 dragging: true, 13707 13708 // @section Panning Inertia Options 13709 // @option inertia: Boolean = * 13710 // If enabled, panning of the map will have an inertia effect where 13711 // the map builds momentum while dragging and continues moving in 13712 // the same direction for some time. Feels especially nice on touch 13713 // devices. Enabled by default. 13714 inertia: true, 13715 13716 // @option inertiaDeceleration: Number = 3000 13717 // The rate with which the inertial movement slows down, in pixels/second². 13718 inertiaDeceleration: 3400, // px/s^2 13719 13720 // @option inertiaMaxSpeed: Number = Infinity 13721 // Max speed of the inertial movement, in pixels/second. 13722 inertiaMaxSpeed: Infinity, // px/s 13723 13724 // @option easeLinearity: Number = 0.2 13725 easeLinearity: 0.2, 13726 13727 // TODO refactor, move to CRS 13728 // @option worldCopyJump: Boolean = false 13729 // With this option enabled, the map tracks when you pan to another "copy" 13730 // of the world and seamlessly jumps to the original one so that all overlays 13731 // like markers and vector layers are still visible. 13732 worldCopyJump: false, 13733 13734 // @option maxBoundsViscosity: Number = 0.0 13735 // If `maxBounds` is set, this option will control how solid the bounds 13736 // are when dragging the map around. The default value of `0.0` allows the 13737 // user to drag outside the bounds at normal speed, higher values will 13738 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 13739 // solid, preventing the user from dragging outside the bounds. 13740 maxBoundsViscosity: 0.0 13741}); 13742 13743var Drag = Handler.extend({ 13744 addHooks: function () { 13745 if (!this._draggable) { 13746 var map = this._map; 13747 13748 this._draggable = new Draggable(map._mapPane, map._container); 13749 13750 this._draggable.on({ 13751 dragstart: this._onDragStart, 13752 drag: this._onDrag, 13753 dragend: this._onDragEnd 13754 }, this); 13755 13756 this._draggable.on('predrag', this._onPreDragLimit, this); 13757 if (map.options.worldCopyJump) { 13758 this._draggable.on('predrag', this._onPreDragWrap, this); 13759 map.on('zoomend', this._onZoomEnd, this); 13760 13761 map.whenReady(this._onZoomEnd, this); 13762 } 13763 } 13764 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 13765 this._draggable.enable(); 13766 this._positions = []; 13767 this._times = []; 13768 }, 13769 13770 removeHooks: function () { 13771 removeClass(this._map._container, 'leaflet-grab'); 13772 removeClass(this._map._container, 'leaflet-touch-drag'); 13773 this._draggable.disable(); 13774 }, 13775 13776 moved: function () { 13777 return this._draggable && this._draggable._moved; 13778 }, 13779 13780 moving: function () { 13781 return this._draggable && this._draggable._moving; 13782 }, 13783 13784 _onDragStart: function () { 13785 var map = this._map; 13786 13787 map._stop(); 13788 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 13789 var bounds = toLatLngBounds(this._map.options.maxBounds); 13790 13791 this._offsetLimit = toBounds( 13792 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 13793 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 13794 .add(this._map.getSize())); 13795 13796 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 13797 } else { 13798 this._offsetLimit = null; 13799 } 13800 13801 map 13802 .fire('movestart') 13803 .fire('dragstart'); 13804 13805 if (map.options.inertia) { 13806 this._positions = []; 13807 this._times = []; 13808 } 13809 }, 13810 13811 _onDrag: function (e) { 13812 if (this._map.options.inertia) { 13813 var time = this._lastTime = +new Date(), 13814 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 13815 13816 this._positions.push(pos); 13817 this._times.push(time); 13818 13819 this._prunePositions(time); 13820 } 13821 13822 this._map 13823 .fire('move', e) 13824 .fire('drag', e); 13825 }, 13826 13827 _prunePositions: function (time) { 13828 while (this._positions.length > 1 && time - this._times[0] > 50) { 13829 this._positions.shift(); 13830 this._times.shift(); 13831 } 13832 }, 13833 13834 _onZoomEnd: function () { 13835 var pxCenter = this._map.getSize().divideBy(2), 13836 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 13837 13838 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 13839 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 13840 }, 13841 13842 _viscousLimit: function (value, threshold) { 13843 return value - (value - threshold) * this._viscosity; 13844 }, 13845 13846 _onPreDragLimit: function () { 13847 if (!this._viscosity || !this._offsetLimit) { return; } 13848 13849 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 13850 13851 var limit = this._offsetLimit; 13852 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 13853 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 13854 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 13855 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 13856 13857 this._draggable._newPos = this._draggable._startPos.add(offset); 13858 }, 13859 13860 _onPreDragWrap: function () { 13861 // TODO refactor to be able to adjust map pane position after zoom 13862 var worldWidth = this._worldWidth, 13863 halfWidth = Math.round(worldWidth / 2), 13864 dx = this._initialWorldOffset, 13865 x = this._draggable._newPos.x, 13866 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 13867 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 13868 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 13869 13870 this._draggable._absPos = this._draggable._newPos.clone(); 13871 this._draggable._newPos.x = newX; 13872 }, 13873 13874 _onDragEnd: function (e) { 13875 var map = this._map, 13876 options = map.options, 13877 13878 noInertia = !options.inertia || e.noInertia || this._times.length < 2; 13879 13880 map.fire('dragend', e); 13881 13882 if (noInertia) { 13883 map.fire('moveend'); 13884 13885 } else { 13886 this._prunePositions(+new Date()); 13887 13888 var direction = this._lastPos.subtract(this._positions[0]), 13889 duration = (this._lastTime - this._times[0]) / 1000, 13890 ease = options.easeLinearity, 13891 13892 speedVector = direction.multiplyBy(ease / duration), 13893 speed = speedVector.distanceTo([0, 0]), 13894 13895 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 13896 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 13897 13898 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 13899 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 13900 13901 if (!offset.x && !offset.y) { 13902 map.fire('moveend'); 13903 13904 } else { 13905 offset = map._limitOffset(offset, map.options.maxBounds); 13906 13907 requestAnimFrame(function () { 13908 map.panBy(offset, { 13909 duration: decelerationDuration, 13910 easeLinearity: ease, 13911 noMoveStart: true, 13912 animate: true 13913 }); 13914 }); 13915 } 13916 } 13917 } 13918}); 13919 13920// @section Handlers 13921// @property dragging: Handler 13922// Map dragging handler (by both mouse and touch). 13923Map.addInitHook('addHandler', 'dragging', Drag); 13924 13925/* 13926 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 13927 */ 13928 13929// @namespace Map 13930// @section Keyboard Navigation Options 13931Map.mergeOptions({ 13932 // @option keyboard: Boolean = true 13933 // Makes the map focusable and allows users to navigate the map with keyboard 13934 // arrows and `+`/`-` keys. 13935 keyboard: true, 13936 13937 // @option keyboardPanDelta: Number = 80 13938 // Amount of pixels to pan when pressing an arrow key. 13939 keyboardPanDelta: 80 13940}); 13941 13942var Keyboard = Handler.extend({ 13943 13944 keyCodes: { 13945 left: [37], 13946 right: [39], 13947 down: [40], 13948 up: [38], 13949 zoomIn: [187, 107, 61, 171], 13950 zoomOut: [189, 109, 54, 173] 13951 }, 13952 13953 initialize: function (map) { 13954 this._map = map; 13955 13956 this._setPanDelta(map.options.keyboardPanDelta); 13957 this._setZoomDelta(map.options.zoomDelta); 13958 }, 13959 13960 addHooks: function () { 13961 var container = this._map._container; 13962 13963 // make the container focusable by tabbing 13964 if (container.tabIndex <= 0) { 13965 container.tabIndex = '0'; 13966 } 13967 13968 on(container, { 13969 focus: this._onFocus, 13970 blur: this._onBlur, 13971 mousedown: this._onMouseDown 13972 }, this); 13973 13974 this._map.on({ 13975 focus: this._addHooks, 13976 blur: this._removeHooks 13977 }, this); 13978 }, 13979 13980 removeHooks: function () { 13981 this._removeHooks(); 13982 13983 off(this._map._container, { 13984 focus: this._onFocus, 13985 blur: this._onBlur, 13986 mousedown: this._onMouseDown 13987 }, this); 13988 13989 this._map.off({ 13990 focus: this._addHooks, 13991 blur: this._removeHooks 13992 }, this); 13993 }, 13994 13995 _onMouseDown: function () { 13996 if (this._focused) { return; } 13997 13998 var body = document.body, 13999 docEl = document.documentElement, 14000 top = body.scrollTop || docEl.scrollTop, 14001 left = body.scrollLeft || docEl.scrollLeft; 14002 14003 this._map._container.focus(); 14004 14005 window.scrollTo(left, top); 14006 }, 14007 14008 _onFocus: function () { 14009 this._focused = true; 14010 this._map.fire('focus'); 14011 }, 14012 14013 _onBlur: function () { 14014 this._focused = false; 14015 this._map.fire('blur'); 14016 }, 14017 14018 _setPanDelta: function (panDelta) { 14019 var keys = this._panKeys = {}, 14020 codes = this.keyCodes, 14021 i, len; 14022 14023 for (i = 0, len = codes.left.length; i < len; i++) { 14024 keys[codes.left[i]] = [-1 * panDelta, 0]; 14025 } 14026 for (i = 0, len = codes.right.length; i < len; i++) { 14027 keys[codes.right[i]] = [panDelta, 0]; 14028 } 14029 for (i = 0, len = codes.down.length; i < len; i++) { 14030 keys[codes.down[i]] = [0, panDelta]; 14031 } 14032 for (i = 0, len = codes.up.length; i < len; i++) { 14033 keys[codes.up[i]] = [0, -1 * panDelta]; 14034 } 14035 }, 14036 14037 _setZoomDelta: function (zoomDelta) { 14038 var keys = this._zoomKeys = {}, 14039 codes = this.keyCodes, 14040 i, len; 14041 14042 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 14043 keys[codes.zoomIn[i]] = zoomDelta; 14044 } 14045 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 14046 keys[codes.zoomOut[i]] = -zoomDelta; 14047 } 14048 }, 14049 14050 _addHooks: function () { 14051 on(document, 'keydown', this._onKeyDown, this); 14052 }, 14053 14054 _removeHooks: function () { 14055 off(document, 'keydown', this._onKeyDown, this); 14056 }, 14057 14058 _onKeyDown: function (e) { 14059 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 14060 14061 var key = e.keyCode, 14062 map = this._map, 14063 offset; 14064 14065 if (key in this._panKeys) { 14066 if (!map._panAnim || !map._panAnim._inProgress) { 14067 offset = this._panKeys[key]; 14068 if (e.shiftKey) { 14069 offset = toPoint(offset).multiplyBy(3); 14070 } 14071 14072 if (map.options.maxBounds) { 14073 offset = map._limitOffset(toPoint(offset), map.options.maxBounds); 14074 } 14075 14076 if (map.options.worldCopyJump) { 14077 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset))); 14078 map.panTo(newLatLng); 14079 } else { 14080 map.panBy(offset); 14081 } 14082 } 14083 } else if (key in this._zoomKeys) { 14084 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 14085 14086 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) { 14087 map.closePopup(); 14088 14089 } else { 14090 return; 14091 } 14092 14093 stop(e); 14094 } 14095}); 14096 14097// @section Handlers 14098// @section Handlers 14099// @property keyboard: Handler 14100// Keyboard navigation handler. 14101Map.addInitHook('addHandler', 'keyboard', Keyboard); 14102 14103/* 14104 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 14105 */ 14106 14107// @namespace Map 14108// @section Interaction Options 14109Map.mergeOptions({ 14110 // @section Mouse wheel options 14111 // @option scrollWheelZoom: Boolean|String = true 14112 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 14113 // it will zoom to the center of the view regardless of where the mouse was. 14114 scrollWheelZoom: true, 14115 14116 // @option wheelDebounceTime: Number = 40 14117 // Limits the rate at which a wheel can fire (in milliseconds). By default 14118 // user can't zoom via wheel more often than once per 40 ms. 14119 wheelDebounceTime: 40, 14120 14121 // @option wheelPxPerZoomLevel: Number = 60 14122 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 14123 // mean a change of one full zoom level. Smaller values will make wheel-zooming 14124 // faster (and vice versa). 14125 wheelPxPerZoomLevel: 60 14126}); 14127 14128var ScrollWheelZoom = Handler.extend({ 14129 addHooks: function () { 14130 on(this._map._container, 'wheel', this._onWheelScroll, this); 14131 14132 this._delta = 0; 14133 }, 14134 14135 removeHooks: function () { 14136 off(this._map._container, 'wheel', this._onWheelScroll, this); 14137 }, 14138 14139 _onWheelScroll: function (e) { 14140 var delta = getWheelDelta(e); 14141 14142 var debounce = this._map.options.wheelDebounceTime; 14143 14144 this._delta += delta; 14145 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 14146 14147 if (!this._startTime) { 14148 this._startTime = +new Date(); 14149 } 14150 14151 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 14152 14153 clearTimeout(this._timer); 14154 this._timer = setTimeout(bind(this._performZoom, this), left); 14155 14156 stop(e); 14157 }, 14158 14159 _performZoom: function () { 14160 var map = this._map, 14161 zoom = map.getZoom(), 14162 snap = this._map.options.zoomSnap || 0; 14163 14164 map._stop(); // stop panning and fly animations if any 14165 14166 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 14167 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 14168 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 14169 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 14170 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 14171 14172 this._delta = 0; 14173 this._startTime = null; 14174 14175 if (!delta) { return; } 14176 14177 if (map.options.scrollWheelZoom === 'center') { 14178 map.setZoom(zoom + delta); 14179 } else { 14180 map.setZoomAround(this._lastMousePos, zoom + delta); 14181 } 14182 } 14183}); 14184 14185// @section Handlers 14186// @property scrollWheelZoom: Handler 14187// Scroll wheel zoom handler. 14188Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); 14189 14190/* 14191 * L.Map.TapHold is used to simulate `contextmenu` event on long hold, 14192 * which otherwise is not fired by mobile Safari. 14193 */ 14194 14195var tapHoldDelay = 600; 14196 14197// @namespace Map 14198// @section Interaction Options 14199Map.mergeOptions({ 14200 // @section Touch interaction options 14201 // @option tapHold: Boolean 14202 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari. 14203 tapHold: Browser.touchNative && Browser.safari && Browser.mobile, 14204 14205 // @option tapTolerance: Number = 15 14206 // The max number of pixels a user can shift his finger during touch 14207 // for it to be considered a valid tap. 14208 tapTolerance: 15 14209}); 14210 14211var TapHold = Handler.extend({ 14212 addHooks: function () { 14213 on(this._map._container, 'touchstart', this._onDown, this); 14214 }, 14215 14216 removeHooks: function () { 14217 off(this._map._container, 'touchstart', this._onDown, this); 14218 }, 14219 14220 _onDown: function (e) { 14221 clearTimeout(this._holdTimeout); 14222 if (e.touches.length !== 1) { return; } 14223 14224 var first = e.touches[0]; 14225 this._startPos = this._newPos = new Point(first.clientX, first.clientY); 14226 14227 this._holdTimeout = setTimeout(bind(function () { 14228 this._cancel(); 14229 if (!this._isTapValid()) { return; } 14230 14231 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events 14232 on(document, 'touchend', preventDefault); 14233 on(document, 'touchend touchcancel', this._cancelClickPrevent); 14234 this._simulateEvent('contextmenu', first); 14235 }, this), tapHoldDelay); 14236 14237 on(document, 'touchend touchcancel contextmenu', this._cancel, this); 14238 on(document, 'touchmove', this._onMove, this); 14239 }, 14240 14241 _cancelClickPrevent: function cancelClickPrevent() { 14242 off(document, 'touchend', preventDefault); 14243 off(document, 'touchend touchcancel', cancelClickPrevent); 14244 }, 14245 14246 _cancel: function () { 14247 clearTimeout(this._holdTimeout); 14248 off(document, 'touchend touchcancel contextmenu', this._cancel, this); 14249 off(document, 'touchmove', this._onMove, this); 14250 }, 14251 14252 _onMove: function (e) { 14253 var first = e.touches[0]; 14254 this._newPos = new Point(first.clientX, first.clientY); 14255 }, 14256 14257 _isTapValid: function () { 14258 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 14259 }, 14260 14261 _simulateEvent: function (type, e) { 14262 var simulatedEvent = new MouseEvent(type, { 14263 bubbles: true, 14264 cancelable: true, 14265 view: window, 14266 // detail: 1, 14267 screenX: e.screenX, 14268 screenY: e.screenY, 14269 clientX: e.clientX, 14270 clientY: e.clientY, 14271 // button: 2, 14272 // buttons: 2 14273 }); 14274 14275 simulatedEvent._simulated = true; 14276 14277 e.target.dispatchEvent(simulatedEvent); 14278 } 14279}); 14280 14281// @section Handlers 14282// @property tapHold: Handler 14283// Long tap handler to simulate `contextmenu` event (useful in mobile Safari). 14284Map.addInitHook('addHandler', 'tapHold', TapHold); 14285 14286/* 14287 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. 14288 */ 14289 14290// @namespace Map 14291// @section Interaction Options 14292Map.mergeOptions({ 14293 // @section Touch interaction options 14294 // @option touchZoom: Boolean|String = * 14295 // Whether the map can be zoomed by touch-dragging with two fingers. If 14296 // passed `'center'`, it will zoom to the center of the view regardless of 14297 // where the touch events (fingers) were. Enabled for touch-capable web 14298 // browsers. 14299 touchZoom: Browser.touch, 14300 14301 // @option bounceAtZoomLimits: Boolean = true 14302 // Set it to false if you don't want the map to zoom beyond min/max zoom 14303 // and then bounce back when pinch-zooming. 14304 bounceAtZoomLimits: true 14305}); 14306 14307var TouchZoom = Handler.extend({ 14308 addHooks: function () { 14309 addClass(this._map._container, 'leaflet-touch-zoom'); 14310 on(this._map._container, 'touchstart', this._onTouchStart, this); 14311 }, 14312 14313 removeHooks: function () { 14314 removeClass(this._map._container, 'leaflet-touch-zoom'); 14315 off(this._map._container, 'touchstart', this._onTouchStart, this); 14316 }, 14317 14318 _onTouchStart: function (e) { 14319 var map = this._map; 14320 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 14321 14322 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 14323 p2 = map.mouseEventToContainerPoint(e.touches[1]); 14324 14325 this._centerPoint = map.getSize()._divideBy(2); 14326 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 14327 if (map.options.touchZoom !== 'center') { 14328 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 14329 } 14330 14331 this._startDist = p1.distanceTo(p2); 14332 this._startZoom = map.getZoom(); 14333 14334 this._moved = false; 14335 this._zooming = true; 14336 14337 map._stop(); 14338 14339 on(document, 'touchmove', this._onTouchMove, this); 14340 on(document, 'touchend touchcancel', this._onTouchEnd, this); 14341 14342 preventDefault(e); 14343 }, 14344 14345 _onTouchMove: function (e) { 14346 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 14347 14348 var map = this._map, 14349 p1 = map.mouseEventToContainerPoint(e.touches[0]), 14350 p2 = map.mouseEventToContainerPoint(e.touches[1]), 14351 scale = p1.distanceTo(p2) / this._startDist; 14352 14353 this._zoom = map.getScaleZoom(scale, this._startZoom); 14354 14355 if (!map.options.bounceAtZoomLimits && ( 14356 (this._zoom < map.getMinZoom() && scale < 1) || 14357 (this._zoom > map.getMaxZoom() && scale > 1))) { 14358 this._zoom = map._limitZoom(this._zoom); 14359 } 14360 14361 if (map.options.touchZoom === 'center') { 14362 this._center = this._startLatLng; 14363 if (scale === 1) { return; } 14364 } else { 14365 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 14366 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 14367 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 14368 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 14369 } 14370 14371 if (!this._moved) { 14372 map._moveStart(true, false); 14373 this._moved = true; 14374 } 14375 14376 cancelAnimFrame(this._animRequest); 14377 14378 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined); 14379 this._animRequest = requestAnimFrame(moveFn, this, true); 14380 14381 preventDefault(e); 14382 }, 14383 14384 _onTouchEnd: function () { 14385 if (!this._moved || !this._zooming) { 14386 this._zooming = false; 14387 return; 14388 } 14389 14390 this._zooming = false; 14391 cancelAnimFrame(this._animRequest); 14392 14393 off(document, 'touchmove', this._onTouchMove, this); 14394 off(document, 'touchend touchcancel', this._onTouchEnd, this); 14395 14396 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 14397 if (this._map.options.zoomAnimation) { 14398 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 14399 } else { 14400 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 14401 } 14402 } 14403}); 14404 14405// @section Handlers 14406// @property touchZoom: Handler 14407// Touch zoom handler. 14408Map.addInitHook('addHandler', 'touchZoom', TouchZoom); 14409 14410Map.BoxZoom = BoxZoom; 14411Map.DoubleClickZoom = DoubleClickZoom; 14412Map.Drag = Drag; 14413Map.Keyboard = Keyboard; 14414Map.ScrollWheelZoom = ScrollWheelZoom; 14415Map.TapHold = TapHold; 14416Map.TouchZoom = TouchZoom; 14417 14418export { Bounds, Browser, CRS, Canvas, Circle, CircleMarker, Class, Control, DivIcon, DivOverlay, DomEvent, DomUtil, Draggable, Evented, FeatureGroup, GeoJSON, GridLayer, Handler, Icon, ImageOverlay, LatLng, LatLngBounds, Layer, LayerGroup, LineUtil, Map, Marker, Mixin, Path, Point, PolyUtil, Polygon, Polyline, Popup, PosAnimation, index as Projection, Rectangle, Renderer, SVG, SVGOverlay, TileLayer, Tooltip, Transformation, Util, VideoOverlay, bind, toBounds as bounds, canvas, circle, circleMarker, control, divIcon, extend, featureGroup, geoJSON, geoJson, gridLayer, icon, imageOverlay, toLatLng as latLng, toLatLngBounds as latLngBounds, layerGroup, createMap as map, marker, toPoint as point, polygon, polyline, popup, rectangle, setOptions, stamp, svg, svgOverlay, tileLayer, tooltip, toTransformation as transformation, version, videoOverlay };
14419
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.