1/* @preserve 2 * Leaflet 1.8.0-beta.0+main.0ea4073, a JS library for interactive maps. https://leafletjs.com 3 * (c) 2010-2022 Vladimir Agafonkin, (c) 2010-2011 CloudMade 4 */ 5 6(function (global, factory) { 7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 8 typeof define === 'function' && define.amd ? define(['exports'], factory) : 9 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {})); 10 })(this, (function (exports) { 'use strict'; 11 12 var version = "1.8.0-beta.0+main.32783c97"; 13 14 /* 15 * @namespace Util 16 * 17 * Various utility functions, used by Leaflet internally. 18 */ 19 20 // @function extend(dest: Object, src?: Object): Object 21 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. 22 function extend(dest) { 23 var i, j, len, src; 24 25 for (j = 1, len = arguments.length; j < len; j++) { 26 src = arguments[j]; 27 for (i in src) { 28 dest[i] = src[i];
vendor: 14,357 bytes, lines 29-406
29 } 30 } 31 return dest; 32 } 33 34 // @function create(proto: Object, properties?: Object): Object 35 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) 36 var create$2 = Object.create || (function () { 37 function F() {} 38 return function (proto) { 39 F.prototype = proto; 40 return new F(); 41 }; 42 })(); 43 44 // @function bind(fn: Function, ââ¬Â¦): Function 45 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). 46 // Has a `L.bind()` shortcut. 47 function bind(fn, obj) { 48 var slice = Array.prototype.slice; 49 50 if (fn.bind) { 51 return fn.bind.apply(fn, slice.call(arguments, 1)); 52 } 53 54 var args = slice.call(arguments, 2); 55 56 return function () { 57 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 58 }; 59 } 60 61 // @property lastId: Number 62 // Last unique ID used by [`stamp()`](#util-stamp) 63 var lastId = 0; 64 65 // @function stamp(obj: Object): Number 66 // Returns the unique ID of an object, assigning it one if it doesn't have it. 67 function stamp(obj) { 68 if (!('_leaflet_id' in obj)) { 69 obj['_leaflet_id'] = ++lastId; 70 } 71 return obj._leaflet_id; 72 } 73 74 // @function throttle(fn: Function, time: Number, context: Object): Function 75 // Returns a function which executes function `fn` with the given scope `context` 76 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function 77 // `fn` will be called no more than one time per given amount of `time`. The arguments 78 // received by the bound function will be any arguments passed when binding the 79 // function, followed by any arguments passed when invoking the bound function. 80 // Has an `L.throttle` shortcut. 81 function throttle(fn, time, context) { 82 var lock, args, wrapperFn, later; 83 84 later = function () { 85 // reset lock and call if queued 86 lock = false; 87 if (args) { 88 wrapperFn.apply(context, args); 89 args = false; 90 } 91 }; 92 93 wrapperFn = function () { 94 if (lock) { 95 // called too soon, queue to call later 96 args = arguments; 97 98 } else { 99 // call and lock until later 100 fn.apply(context, arguments); 101 setTimeout(later, time); 102 lock = true; 103 } 104 }; 105 106 return wrapperFn; 107 } 108 109 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number 110 // Returns the number `num` modulo `range` in such a way so it lies within 111 // `range[0]` and `range[1]`. The returned value will be always smaller than 112 // `range[1]` unless `includeMax` is set to `true`. 113 function wrapNum(x, range, includeMax) { 114 var max = range[1], 115 min = range[0], 116 d = max - min; 117 return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 118 } 119 120 // @function falseFn(): Function 121 // Returns a function which always returns `false`. 122 function falseFn() { return false; } 123 124 // @function formatNum(num: Number, precision?: Number|false): Number 125 // Returns the number `num` rounded with specified `precision`. 126 // The default `precision` value is 6 decimal places. 127 // `false` can be passed to skip any processing (can be useful to avoid round-off errors). 128 function formatNum(num, precision) { 129 if (precision === false) { return num; } 130 var pow = Math.pow(10, precision === undefined ? 6 : precision); 131 return Math.round(num * pow) / pow; 132 } 133 134 // @function trim(str: String): String 135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) 136 function trim(str) { 137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); 138 } 139 140 // @function splitWords(str: String): String[] 141 // Trims and splits the string on whitespace and returns the array of parts. 142 function splitWords(str) { 143 return trim(str).split(/\s+/); 144 } 145 146 // @function setOptions(obj: Object, options: Object): Object 147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. 148 function setOptions(obj, options) { 149 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) { 150 obj.options = obj.options ? create$2(obj.options) : {}; 151 } 152 for (var i in options) { 153 obj.options[i] = options[i]; 154 } 155 return obj.options; 156 } 157 158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String 159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` 160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will 161 // be appended at the end. If `uppercase` is `true`, the parameter names will 162 // be uppercased (e.g. `'?A=foo&B=bar'`) 163 function getParamString(obj, existingUrl, uppercase) { 164 var params = []; 165 for (var i in obj) { 166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); 167 } 168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); 169 } 170 171 var templateRe = /\{ *([\w_ -]+) *\}/g; 172 173 // @function template(str: String, data: Object): String 174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` 175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string 176 // `('Hello foo, bar')`. You can also specify functions instead of strings for 177 // data values ââ¬â they will be evaluated passing `data` as an argument. 178 function template(str, data) { 179 return str.replace(templateRe, function (str, key) { 180 var value = data[key]; 181 182 if (value === undefined) { 183 throw new Error('No value provided for variable ' + str); 184 185 } else if (typeof value === 'function') { 186 value = value(data); 187 } 188 return value; 189 }); 190 } 191 192 // @function isArray(obj): Boolean 193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) 194 var isArray = Array.isArray || function (obj) { 195 return (Object.prototype.toString.call(obj) === '[object Array]'); 196 }; 197 198 // @function indexOf(array: Array, el: Object): Number 199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) 200 function indexOf(array, el) { 201 for (var i = 0; i < array.length; i++) { 202 if (array[i] === el) { return i; } 203 } 204 return -1; 205 } 206 207 // @property emptyImageUrl: String 208 // Data URI string containing a base64-encoded empty GIF image. 209 // Used as a hack to free memory from unused images on WebKit-powered 210 // mobile devices (by setting image `src` to this string). 211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; 212 213 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/ 214 215 function getPrefixed(name) { 216 return window['webkit' + name] || window['moz' + name] || window['ms' + name]; 217 } 218 219 var lastTime = 0; 220 221 // fallback for IE 7-8 222 function timeoutDefer(fn) { 223 var time = +new Date(), 224 timeToCall = Math.max(0, 16 - (time - lastTime)); 225 226 lastTime = time + timeToCall; 227 return window.setTimeout(fn, timeToCall); 228 } 229 230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; 231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || 232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; 233 234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number 235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to 236 // `context` if given. When `immediate` is set, `fn` is called immediately if 237 // the browser doesn't have native support for 238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), 239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request. 240 function requestAnimFrame(fn, context, immediate) { 241 if (immediate && requestFn === timeoutDefer) { 242 fn.call(context); 243 } else { 244 return requestFn.call(window, bind(fn, context)); 245 } 246 } 247 248 // @function cancelAnimFrame(id: Number): undefined 249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). 250 function cancelAnimFrame(id) { 251 if (id) { 252 cancelFn.call(window, id); 253 } 254 } 255 256 var Util = { 257 __proto__: null, 258 extend: extend, 259 create: create$2, 260 bind: bind, 261 get lastId () { return lastId; }, 262 stamp: stamp, 263 throttle: throttle, 264 wrapNum: wrapNum, 265 falseFn: falseFn, 266 formatNum: formatNum, 267 trim: trim, 268 splitWords: splitWords, 269 setOptions: setOptions, 270 getParamString: getParamString, 271 template: template, 272 isArray: isArray, 273 indexOf: indexOf, 274 emptyImageUrl: emptyImageUrl, 275 requestFn: requestFn, 276 cancelFn: cancelFn, 277 requestAnimFrame: requestAnimFrame, 278 cancelAnimFrame: cancelAnimFrame 279 }; 280 281 // @class Class 282 // @aka L.Class 283 284 // @section 285 // @uninheritable 286 287 // Thanks to John Resig and Dean Edwards for inspiration! 288 289 function Class() {} 290 291 Class.extend = function (props) { 292 293 // @function extend(props: Object): Function 294 // [Extends the current class](#class-inheritance) given the properties to be included. 295 // Returns a Javascript function that is a class constructor (to be called with `new`). 296 var NewClass = function () { 297 298 setOptions(this); 299 300 // call the constructor 301 if (this.initialize) { 302 this.initialize.apply(this, arguments); 303 } 304 305 // call all constructor hooks 306 this.callInitHooks(); 307 }; 308 309 var parentProto = NewClass.__super__ = this.prototype; 310 311 var proto = create$2(parentProto); 312 proto.constructor = NewClass; 313 314 NewClass.prototype = proto; 315 316 // inherit parent's statics 317 for (var i in this) { 318 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') { 319 NewClass[i] = this[i]; 320 } 321 } 322 323 // mix static properties into the class 324 if (props.statics) { 325 extend(NewClass, props.statics); 326 } 327 328 // mix includes into the prototype 329 if (props.includes) { 330 checkDeprecatedMixinEvents(props.includes); 331 extend.apply(null, [proto].concat(props.includes)); 332 } 333 334 // mix given properties into the prototype 335 extend(proto, props); 336 delete proto.statics; 337 delete proto.includes; 338 339 // merge options 340 if (proto.options) { 341 proto.options = parentProto.options ? create$2(parentProto.options) : {}; 342 extend(proto.options, props.options); 343 } 344 345 proto._initHooks = []; 346 347 // add method for calling all hooks 348 proto.callInitHooks = function () { 349 350 if (this._initHooksCalled) { return; } 351 352 if (parentProto.callInitHooks) { 353 parentProto.callInitHooks.call(this); 354 } 355 356 this._initHooksCalled = true; 357 358 for (var i = 0, len = proto._initHooks.length; i < len; i++) { 359 proto._initHooks[i].call(this); 360 } 361 }; 362 363 return NewClass; 364 }; 365 366 367 // @function include(properties: Object): this 368 // [Includes a mixin](#class-includes) into the current class. 369 Class.include = function (props) { 370 var parentOptions = this.prototype.options; 371 extend(this.prototype, props); 372 if (props.options) { 373 this.prototype.options = parentOptions; 374 this.mergeOptions(props.options); 375 } 376 return this; 377 }; 378 379 // @function mergeOptions(options: Object): this 380 // [Merges `options`](#class-options) into the defaults of the class. 381 Class.mergeOptions = function (options) { 382 extend(this.prototype.options, options); 383 return this; 384 }; 385 386 // @function addInitHook(fn: Function): this 387 // Adds a [constructor hook](#class-constructor-hooks) to the class. 388 Class.addInitHook = function (fn) { // (Function) || (String, args...) 389 var args = Array.prototype.slice.call(arguments, 1); 390 391 var init = typeof fn === 'function' ? fn : function () { 392 this[fn].apply(this, args); 393 }; 394 395 this.prototype._initHooks = this.prototype._initHooks || []; 396 this.prototype._initHooks.push(init); 397 return this; 398 }; 399 400 function checkDeprecatedMixinEvents(includes) { 401 if (typeof L === 'undefined' || !L || !L.Mixin) { return; } 402 403 includes = isArray(includes) ? includes : [includes]; 404 405 for (var i = 0; i < includes.length; i++) { 406 if (includes[i] === L.Mixin.Events) {
407 console.warn('Deprecated include of L.Mixin.Events: ' + 408 'this property will be removed in future releases, ' + 409 'please inherit from L.Evented instead.', new Error().stack); 410 } 411 } 412 } 413 414 /* 415 * @class Evented 416 * @aka L.Evented 417 * @inherits Class 418 * 419 * 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). 420 * 421 * @example 422 * 423 * ```js 424 * map.on('click', function(e) { 425 * alert(e.latlng); 426 * } ); 427 * ``` 428 * 429 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: 430 * 431 * ```js 432 * function onClick(e) { ... } 433 * 434 * map.on('click', onClick); 435 * map.off('click', onClick); 436 * ``` 437 */ 438 439 var Events = { 440 /* @method on(type: String, fn: Function, context?: Object): this 441 * 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'`). 442 * 443 * @alternative 444 * @method on(eventMap: Object): this 445 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 446 */ 447 on: function (types, fn, context) { 448 449 // types can be a map of types/handlers 450 if (typeof types === 'object') { 451 for (var type in types) { 452 // we don't process space-separated events here for performance; 453 // it's a hot path since Layer uses the on(obj) syntax 454 this._on(type, types[type], fn); 455 } 456 457 } else { 458 // types can be a string of space-separated words 459 types = splitWords(types); 460 461 for (var i = 0, len = types.length; i < len; i++) { 462 this._on(types[i], fn, context); 463 } 464 } 465 466 return this; 467 }, 468 469 /* @method off(type: String, fn?: Function, context?: Object): this 470 * 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. 471 * 472 * @alternative 473 * @method off(eventMap: Object): this 474 * Removes a set of type/listener pairs. 475 * 476 * @alternative 477 * @method off: this 478 * Removes all listeners to all events on the object. This includes implicitly attached events. 479 */ 480 off: function (types, fn, context) { 481 482 if (!arguments.length) { 483 // clear all listeners if called without arguments 484 delete this._events; 485 486 } else if (typeof types === 'object') { 487 for (var type in types) { 488 this._off(type, types[type], fn); 489 } 490 491 } else { 492 types = splitWords(types); 493 494 var removeAll = arguments.length === 1; 495 for (var i = 0, len = types.length; i < len; i++) { 496 if (removeAll) { 497 this._off(types[i]); 498 } else { 499 this._off(types[i], fn, context); 500 } 501 } 502 } 503 504 return this; 505 }, 506 507 // attach listener (without syntactic sugar now) 508 _on: function (type, fn, context) { 509 if (typeof fn !== 'function') { 510 console.warn('wrong listener type: ' + typeof fn); 511 return; 512 } 513 this._events = this._events || {}; 514 515 /* get/init listeners for type */ 516 var typeListeners = this._events[type]; 517 if (!typeListeners) { 518 typeListeners = []; 519 this._events[type] = typeListeners; 520 } 521 522 if (context === this) { 523 // Less memory footprint. 524 context = undefined; 525 } 526 var newListener = {fn: fn, ctx: context}, 527 listeners = typeListeners; 528 529 // check if fn already there 530 for (var i = 0, len = listeners.length; i < len; i++) { 531 if (listeners[i].fn === fn && listeners[i].ctx === context) { 532 return; 533 } 534 } 535 536 listeners.push(newListener); 537 }, 538 539 _off: function (type, fn, context) { 540 var listeners, 541 i, 542 len; 543 544 if (!this._events) { return; } 545 546 listeners = this._events[type]; 547 548 if (!listeners) { 549 return; 550 } 551 552 if (arguments.length === 1) { // remove all 553 if (this._firingCount) { 554 // Set all removed listeners to noop 555 // so they are not called if remove happens in fire 556 for (i = 0, len = listeners.length; i < len; i++) { 557 listeners[i].fn = falseFn; 558 } 559 } 560 // clear all listeners for a type if function isn't specified 561 delete this._events[type]; 562 return; 563 } 564 565 if (context === this) { 566 context = undefined; 567 } 568 569 if (typeof fn !== 'function') { 570 console.warn('wrong listener type: ' + typeof fn); 571 return; 572 } 573 // find fn and remove it 574 for (i = 0, len = listeners.length; i < len; i++) { 575 var l = listeners[i];
576 if (l.ctx !== context) { continue; } 577 if (l.fn === fn) { 578 if (this._firingCount) { 579 // set the removed listener to noop so that's not called if remove happens in fire 580 l.fn = falseFn; 581 582 /* copy array in case events are being fired */ 583 this._events[type] = listeners = listeners.slice(); 584 } 585 listeners.splice(i, 1); 586 587 return; 588 } 589 } 590 console.warn('listener not found'); 591 }, 592 593 // @method fire(type: String, data?: Object, propagate?: Boolean): this 594 // Fires an event of the specified type. You can optionally provide a data 595 // object ââ¬â the first argument of the listener function will contain its 596 // properties. The event can optionally be propagated to event parents. 597 fire: function (type, data, propagate) { 598 if (!this.listens(type, propagate)) { return this; } 599 600 var event = extend({}, data, { 601 type: type, 602 target: this, 603 sourceTarget: data && data.sourceTarget || this 604 }); 605 606 if (this._events) { 607 var listeners = this._events[type]; 608 609 if (listeners) { 610 this._firingCount = (this._firingCount + 1) || 1; 611 for (var i = 0, len = listeners.length; i < len; i++) { 612 var l = listeners[i]; 613 l.fn.call(l.ctx || this, event); 614 } 615 616 this._firingCount--; 617 } 618 } 619 620 if (propagate) { 621 // propagate the event to parents (set with addEventParent) 622 this._propagateEvent(event); 623 } 624 625 return this; 626 }, 627 628 // @method listens(type: String, propagate?: Boolean): Boolean 629 // Returns `true` if a particular event type has any listeners attached to it. 630 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it. 631 listens: function (type, propagate) { 632 if (typeof type !== 'string') { 633 console.warn('"string" type argument expected'); 634 } 635 var listeners = this._events && this._events[type]; 636 if (listeners && listeners.length) { return true; } 637 638 if (propagate) { 639 // also check parents for listeners if event propagates 640 for (var id in this._eventParents) { 641 if (this._eventParents[id].listens(type, propagate)) { return true; } 642 } 643 } 644 return false; 645 }, 646 647 // @method once(ââ¬Â¦): this 648 // Behaves as [`on(ââ¬Â¦)`](#evented-on), except the listener will only get fired once and then removed. 649 once: function (types, fn, context) { 650 651 if (typeof types === 'object') { 652 for (var type in types) { 653 this.once(type, types[type], fn); 654 } 655 return this; 656 } 657 658 var handler = bind(function () { 659 this 660 .off(types, fn, context) 661 .off(types, handler, context); 662 }, this); 663 664 // add a listener that's executed once and removed after that 665 return this 666 .on(types, fn, context) 667 .on(types, handler, context); 668 }, 669 670 // @method addEventParent(obj: Evented): this 671 // Adds an event parent - an `Evented` that will receive propagated events 672 addEventParent: function (obj) { 673 this._eventParents = this._eventParents || {}; 674 this._eventParents[stamp(obj)] = obj; 675 return this; 676 }, 677 678 // @method removeEventParent(obj: Evented): this 679 // Removes an event parent, so it will stop receiving propagated events 680 removeEventParent: function (obj) { 681 if (this._eventParents) { 682 delete this._eventParents[stamp(obj)]; 683 } 684 return this; 685 }, 686 687 _propagateEvent: function (e) { 688 for (var id in this._eventParents) { 689 this._eventParents[id].fire(e.type, extend({ 690 layer: e.target, 691 propagatedFrom: e.target 692 }, e), true); 693 } 694 } 695 }; 696 697 // aliases; we should ditch those eventually 698 699 // @method addEventListener(ââ¬Â¦): this 700 // Alias to [`on(ââ¬Â¦)`](#evented-on) 701 Events.addEventListener = Events.on; 702 703 // @method removeEventListener(ââ¬Â¦): this 704 // Alias to [`off(ââ¬Â¦)`](#evented-off) 705 706 // @method clearAllEventListeners(ââ¬Â¦): this 707 // Alias to [`off()`](#evented-off) 708 Events.removeEventListener = Events.clearAllEventListeners = Events.off; 709 710 // @method addOneTimeEventListener(ââ¬Â¦): this 711 // Alias to [`once(ââ¬Â¦)`](#evented-once) 712 Events.addOneTimeEventListener = Events.once; 713 714 // @method fireEvent(ââ¬Â¦): this 715 // Alias to [`fire(ââ¬Â¦)`](#evented-fire) 716 Events.fireEvent = Events.fire; 717 718 // @method hasEventListeners(ââ¬Â¦): Boolean 719 // Alias to [`listens(ââ¬Â¦)`](#evented-listens) 720 Events.hasEventListeners = Events.listens; 721 722 var Evented = Class.extend(Events); 723 724 /* 725 * @class Point 726 * @aka L.Point 727 * 728 * Represents a point with `x` and `y` coordinates in pixels. 729 * 730 * @example 731 * 732 * ```js 733 * var point = L.point(200, 300); 734 * ``` 735 * 736 * 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: 737 * 738 * ```js 739 * map.panBy([200, 300]); 740 * map.panBy(L.point(200, 300)); 741 * ``` 742 * 743 * Note that `Point` does not inherit from Leaflet's `Class` object, 744 * which means new classes can't inherit from it, and new methods 745 * can't be added to it with the `include` function. 746 */ 747 748 function Point(x, y, round) { 749 // @property x: Number; The `x` coordinate of the point 750 this.x = (round ? Math.round(x) : x); 751 // @property y: Number; The `y` coordinate of the point 752 this.y = (round ? Math.round(y) : y); 753 } 754 755 var trunc = Math.trunc || function (v) { 756 return v > 0 ? Math.floor(v) : Math.ceil(v); 757 }; 758 759 Point.prototype = { 760 761 // @method clone(): Point 762 // Returns a copy of the current point. 763 clone: function () { 764 return new Point(this.x, this.y); 765 }, 766 767 // @method add(otherPoint: Point): Point 768 // Returns the result of addition of the current and the given points. 769 add: function (point) { 770 // non-destructive, returns a new point 771 return this.clone()._add(toPoint(point)); 772 }, 773 774 _add: function (point) { 775 // destructive, used directly for performance in situations where it's s
vendor: 26,462 bytes, lines 775-1505
775afe to modify existing point 776 this.x += point.x; 777 this.y += point.y; 778 return this; 779 }, 780 781 // @method subtract(otherPoint: Point): Point 782 // Returns the result of subtraction of the given point from the current. 783 subtract: function (point) { 784 return this.clone()._subtract(toPoint(point)); 785 }, 786 787 _subtract: function (point) { 788 this.x -= point.x; 789 this.y -= point.y; 790 return this; 791 }, 792 793 // @method divideBy(num: Number): Point 794 // Returns the result of division of the current point by the given number. 795 divideBy: function (num) { 796 return this.clone()._divideBy(num); 797 }, 798 799 _divideBy: function (num) { 800 this.x /= num; 801 this.y /= num; 802 return this; 803 }, 804 805 // @method multiplyBy(num: Number): Point 806 // Returns the result of multiplication of the current point by the given number. 807 multiplyBy: function (num) { 808 return this.clone()._multiplyBy(num); 809 }, 810 811 _multiplyBy: function (num) { 812 this.x *= num; 813 this.y *= num; 814 return this; 815 }, 816 817 // @method scaleBy(scale: Point): Point 818 // Multiply each coordinate of the current point by each coordinate of 819 // `scale`. In linear algebra terms, multiply the point by the 820 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 821 // defined by `scale`. 822 scaleBy: function (point) { 823 return new Point(this.x * point.x, this.y * point.y); 824 }, 825 826 // @method unscaleBy(scale: Point): Point 827 // Inverse of `scaleBy`. Divide each coordinate of the current point by 828 // each coordinate of `scale`. 829 unscaleBy: function (point) { 830 return new Point(this.x / point.x, this.y / point.y); 831 }, 832 833 // @method round(): Point 834 // Returns a copy of the current point with rounded coordinates. 835 round: function () { 836 return this.clone()._round(); 837 }, 838 839 _round: function () { 840 this.x = Math.round(this.x); 841 this.y = Math.round(this.y); 842 return this; 843 }, 844 845 // @method floor(): Point 846 // Returns a copy of the current point with floored coordinates (rounded down). 847 floor: function () { 848 return this.clone()._floor(); 849 }, 850 851 _floor: function () { 852 this.x = Math.floor(this.x); 853 this.y = Math.floor(this.y); 854 return this; 855 }, 856 857 // @method ceil(): Point 858 // Returns a copy of the current point with ceiled coordinates (rounded up). 859 ceil: function () { 860 return this.clone()._ceil(); 861 }, 862 863 _ceil: function () { 864 this.x = Math.ceil(this.x); 865 this.y = Math.ceil(this.y); 866 return this; 867 }, 868 869 // @method trunc(): Point 870 // Returns a copy of the current point with truncated coordinates (rounded towards zero). 871 trunc: function () { 872 return this.clone()._trunc(); 873 }, 874 875 _trunc: function () { 876 this.x = trunc(this.x); 877 this.y = trunc(this.y); 878 return this; 879 }, 880 881 // @method distanceTo(otherPoint: Point): Number 882 // Returns the cartesian distance between the current and the given points. 883 distanceTo: function (point) { 884 point = toPoint(point); 885 886 var x = point.x - this.x, 887 y = point.y - this.y; 888 889 return Math.sqrt(x * x + y * y); 890 }, 891 892 // @method equals(otherPoint: Point): Boolean 893 // Returns `true` if the given point has the same coordinates. 894 equals: function (point) { 895 point = toPoint(point); 896 897 return point.x === this.x && 898 point.y === this.y; 899 }, 900 901 // @method contains(otherPoint: Point): Boolean 902 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 903 contains: function (point) { 904 point = toPoint(point); 905 906 return Math.abs(point.x) <= Math.abs(this.x) && 907 Math.abs(point.y) <= Math.abs(this.y); 908 }, 909 910 // @method toString(): String 911 // Returns a string representation of the point for debugging purposes. 912 toString: function () { 913 return 'Point(' + 914 formatNum(this.x) + ', ' + 915 formatNum(this.y) + ')'; 916 } 917 }; 918 919 // @factory L.point(x: Number, y: Number, round?: Boolean) 920 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. 921 922 // @alternative 923 // @factory L.point(coords: Number[]) 924 // Expects an array of the form `[x, y]` instead. 925 926 // @alternative 927 // @factory L.point(coords: Object) 928 // Expects a plain object of the form `{x: Number, y: Number}` instead. 929 function toPoint(x, y, round) { 930 if (x instanceof Point) { 931 return x; 932 } 933 if (isArray(x)) { 934 return new Point(x[0], x[1]); 935 } 936 if (x === undefined || x === null) { 937 return x; 938 } 939 if (typeof x === 'object' && 'x' in x && 'y' in x) { 940 return new Point(x.x, x.y); 941 } 942 return new Point(x, y, round); 943 } 944 945 /* 946 * @class Bounds 947 * @aka L.Bounds 948 * 949 * Represents a rectangular area in pixel coordinates. 950 * 951 * @example 952 * 953 * ```js 954 * var p1 = L.point(10, 10), 955 * p2 = L.point(40, 60), 956 * bounds = L.bounds(p1, p2); 957 * ``` 958 * 959 * 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: 960 * 961 * ```js 962 * otherBounds.intersects([[10, 10], [40, 60]]); 963 * ``` 964 * 965 * Note that `Bounds` does not inherit from Leaflet's `Class` object, 966 * which means new classes can't inherit from it, and new methods 967 * can't be added to it with the `include` function. 968 */ 969 970 function Bounds(a, b) { 971 if (!a) { return; } 972 973 var points = b ? [a, b] : a; 974 975 for (var i = 0, len = points.length; i < len; i++) { 976 this.extend(points[i]); 977 } 978 } 979 980 Bounds.prototype = { 981 // @method extend(point: Point): this 982 // Extends the bounds to contain the given point. 983 extend: function (point) { // (Point) 984 point = toPoint(point); 985 986 // @property min: Point 987 // The top left corner of the rectangle. 988 // @property max: Point 989 // The bottom right corner of the rectangle. 990 if (!this.min && !this.max) { 991 this.min = point.clone(); 992 this.max = point.clone(); 993 } else { 994 this.min.x = Math.min(point.x, this.min.x); 995 this.max.x = Math.max(point.x, this.max.x); 996 this.min.y = Math.min(point.y, this.min.y); 997 this.max.y = Math.max(point.y, this.max.y); 998 } 999 return this; 1000 }, 1001 1002 // @method getCenter(round?: Boolean): Point 1003 // Returns the center point of the bounds. 1004 getCenter: function (round) { 1005 return new Point( 1006 (this.min.x + this.max.x) / 2, 1007 (this.min.y + this.max.y) / 2, round); 1008 }, 1009 1010 // @method getBottomLeft(): Point 1011 // Returns the bottom-left point of the bounds. 1012 getBottomLeft: function () { 1013 return new Point(this.min.x, this.max.y); 1014 }, 1015 1016 // @method getTopRight(): Point 1017 // Returns the top-right point of the bounds. 1018 getTopRight: function () { // -> Point 1019 return new Point(this.max.x, this.min.y); 1020 }, 1021 1022 // @method getTopLeft(): Point 1023 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 1024 getTopLeft: function () { 1025 return this.min; // left, top 1026 }, 1027 1028 // @method getBottomRight(): Point 1029 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 1030 getBottomRight: function () { 1031 return this.max; // right, bottom 1032 }, 1033 1034 // @method getSize(): Point 1035 // Returns the size of the given bounds 1036 getSize: function () { 1037 return this.max.subtract(this.min); 1038 }, 1039 1040 // @method contains(otherBounds: Bounds): Boolean 1041 // Returns `true` if the rectangle contains the given one. 1042 // @alternative 1043 // @method contains(point: Point): Boolean 1044 // Returns `true` if the rectangle contains the given point. 1045 contains: function (obj) { 1046 var min, max; 1047 1048 if (typeof obj[0] === 'number' || obj instanceof Point) { 1049 obj = toPoint(obj); 1050 } else { 1051 obj = toBounds(obj); 1052 } 1053 1054 if (obj instanceof Bounds) { 1055 min = obj.min; 1056 max = obj.max; 1057 } else { 1058 min = max = obj; 1059 } 1060 1061 return (min.x >= this.min.x) && 1062 (max.x <= this.max.x) && 1063 (min.y >= this.min.y) && 1064 (max.y <= this.max.y); 1065 }, 1066 1067 // @method intersects(otherBounds: Bounds): Boolean 1068 // Returns `true` if the rectangle intersects the given bounds. Two bounds 1069 // intersect if they have at least one point in common. 1070 intersects: function (bounds) { // (Bounds) -> Boolean 1071 bounds = toBounds(bounds); 1072 1073 var min = this.min, 1074 max = this.max, 1075 min2 = bounds.min, 1076 max2 = bounds.max, 1077 xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 1078 yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 1079 1080 return xIntersects && yIntersects; 1081 }, 1082 1083 // @method overlaps(otherBounds: Bounds): Boolean 1084 // Returns `true` if the rectangle overlaps the given bounds. Two bounds 1085 // overlap if their intersection is an area. 1086 overlaps: function (bounds) { // (Bounds) -> Boolean 1087 bounds = toBounds(bounds); 1088 1089 var min = this.min, 1090 max = this.max, 1091 min2 = bounds.min, 1092 max2 = bounds.max, 1093 xOverlaps = (max2.x > min.x) && (min2.x < max.x), 1094 yOverlaps = (max2.y > min.y) && (min2.y < max.y); 1095 1096 return xOverlaps && yOverlaps; 1097 }, 1098 1099 isValid: function () { 1100 return !!(this.min && this.max); 1101 } 1102 }; 1103 1104 1105 // @factory L.bounds(corner1: Point, corner2: Point) 1106 // Creates a Bounds object from two corners coordinate pairs. 1107 // @alternative 1108 // @factory L.bounds(points: Point[]) 1109 // Creates a Bounds object from the given array of points. 1110 function toBounds(a, b) { 1111 if (!a || a instanceof Bounds) { 1112 return a; 1113 } 1114 return new Bounds(a, b); 1115 } 1116 1117 /* 1118 * @class LatLngBounds 1119 * @aka L.LatLngBounds 1120 * 1121 * Represents a rectangular geographical area on a map. 1122 * 1123 * @example 1124 * 1125 * ```js 1126 * var corner1 = L.latLng(40.712, -74.227), 1127 * corner2 = L.latLng(40.774, -74.125), 1128 * bounds = L.latLngBounds(corner1, corner2); 1129 * ``` 1130 * 1131 * 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: 1132 * 1133 * ```js 1134 * map.fitBounds([ 1135 * [40.712, -74.227], 1136 * [40.774, -74.125] 1137 * ]); 1138 * ``` 1139 * 1140 * 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. 1141 * 1142 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object, 1143 * which means new classes can't inherit from it, and new methods 1144 * can't be added to it with the `include` function. 1145 */ 1146 1147 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) 1148 if (!corner1) { return; } 1149 1150 var latlngs = corner2 ? [corner1, corner2] : corner1; 1151 1152 for (var i = 0, len = latlngs.length; i < len; i++) { 1153 this.extend(latlngs[i]); 1154 } 1155 } 1156 1157 LatLngBounds.prototype = { 1158 1159 // @method extend(latlng: LatLng): this 1160 // Extend the bounds to contain the given point 1161 1162 // @alternative 1163 // @method extend(otherBounds: LatLngBounds): this 1164 // Extend the bounds to contain the given bounds 1165 extend: function (obj) { 1166 var sw = this._southWest, 1167 ne = this._northEast, 1168 sw2, ne2; 1169 1170 if (obj instanceof LatLng) { 1171 sw2 = obj; 1172 ne2 = obj; 1173 1174 } else if (obj instanceof LatLngBounds) { 1175 sw2 = obj._southWest; 1176 ne2 = obj._northEast; 1177 1178 if (!sw2 || !ne2) { return this; } 1179 1180 } else { 1181 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 1182 } 1183 1184 if (!sw && !ne) { 1185 this._southWest = new LatLng(sw2.lat, sw2.lng); 1186 this._northEast = new LatLng(ne2.lat, ne2.lng); 1187 } else { 1188 sw.lat = Math.min(sw2.lat, sw.lat); 1189 sw.lng = Math.min(sw2.lng, sw.lng); 1190 ne.lat = Math.max(ne2.lat, ne.lat); 1191 ne.lng = Math.max(ne2.lng, ne.lng); 1192 } 1193 1194 return this; 1195 }, 1196 1197 // @method pad(bufferRatio: Number): LatLngBounds 1198 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 1199 // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 1200 // Negative values will retract the bounds. 1201 pad: function (bufferRatio) { 1202 var sw = this._southWest, 1203 ne = this._northEast, 1204 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 1205 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 1206 1207 return new LatLngBounds( 1208 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 1209 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 1210 }, 1211 1212 // @method getCenter(): LatLng 1213 // Returns the center point of the bounds. 1214 getCenter: function () { 1215 return new LatLng( 1216 (this._southWest.lat + this._northEast.lat) / 2, 1217 (this._southWest.lng + this._northEast.lng) / 2); 1218 }, 1219 1220 // @method getSouthWest(): LatLng 1221 // Returns the south-west point of the bounds. 1222 getSouthWest: function () { 1223 return this._southWest; 1224 }, 1225 1226 // @method getNorthEast(): LatLng 1227 // Returns the north-east point of the bounds. 1228 getNorthEast: function () { 1229 return this._northEast; 1230 }, 1231 1232 // @method getNorthWest(): LatLng 1233 // Returns the north-west point of the bounds. 1234 getNorthWest: function () { 1235 return new LatLng(this.getNorth(), this.getWest()); 1236 }, 1237 1238 // @method getSouthEast(): LatLng 1239 // Returns the south-east point of the bounds. 1240 getSouthEast: function () { 1241 return new LatLng(this.getSouth(), this.getEast()); 1242 }, 1243 1244 // @method getWest(): Number 1245 // Returns the west longitude of the bounds 1246 getWest: function () { 1247 return this._southWest.lng; 1248 }, 1249 1250 // @method getSouth(): Number 1251 // Returns the south latitude of the bounds 1252 getSouth: function () { 1253 return this._southWest.lat; 1254 }, 1255 1256 // @method getEast(): Number 1257 // Returns the east longitude of the bounds 1258 getEast: function () { 1259 return this._northEast.lng; 1260 }, 1261 1262 // @method getNorth(): Number 1263 // Returns the north latitude of the bounds 1264 getNorth: function () { 1265 return this._northEast.lat; 1266 }, 1267 1268 // @method contains(otherBounds: LatLngBounds): Boolean 1269 // Returns `true` if the rectangle contains the given one. 1270 1271 // @alternative 1272 // @method contains (latlng: LatLng): Boolean 1273 // Returns `true` if the rectangle contains the given point. 1274 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 1275 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { 1276 obj = toLatLng(obj); 1277 } else { 1278 obj = toLatLngBounds(obj); 1279 } 1280 1281 var sw = this._southWest, 1282 ne = this._northEast, 1283 sw2, ne2; 1284 1285 if (obj instanceof LatLngBounds) { 1286 sw2 = obj.getSouthWest(); 1287 ne2 = obj.getNorthEast(); 1288 } else { 1289 sw2 = ne2 = obj; 1290 } 1291 1292 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 1293 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 1294 }, 1295 1296 // @method intersects(otherBounds: LatLngBounds): Boolean 1297 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 1298 intersects: function (bounds) { 1299 bounds = toLatLngBounds(bounds); 1300 1301 var sw = this._southWest, 1302 ne = this._northEast, 1303 sw2 = bounds.getSouthWest(), 1304 ne2 = bounds.getNorthEast(), 1305 1306 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 1307 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 1308 1309 return latIntersects && lngIntersects; 1310 }, 1311 1312 // @method overlaps(otherBounds: LatLngBounds): Boolean 1313 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 1314 overlaps: function (bounds) { 1315 bounds = toLatLngBounds(bounds); 1316 1317 var sw = this._southWest, 1318 ne = this._northEast, 1319 sw2 = bounds.getSouthWest(), 1320 ne2 = bounds.getNorthEast(), 1321 1322 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 1323 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 1324 1325 return latOverlaps && lngOverlaps; 1326 }, 1327 1328 // @method toBBoxString(): String 1329 // 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. 1330 toBBoxString: function () { 1331 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 1332 }, 1333 1334 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 1335 // 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. 1336 equals: function (bounds, maxMargin) { 1337 if (!bounds) { return false; } 1338 1339 bounds = toLatLngBounds(bounds); 1340 1341 return this._southWest.equals(bounds.getSouthWest(), maxMargin) && 1342 this._northEast.equals(bounds.getNorthEast(), maxMargin); 1343 }, 1344 1345 // @method isValid(): Boolean 1346 // Returns `true` if the bounds are properly initialized. 1347 isValid: function () { 1348 return !!(this._southWest && this._northEast); 1349 } 1350 }; 1351 1352 // TODO International date line? 1353 1354 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) 1355 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. 1356 1357 // @alternative 1358 // @factory L.latLngBounds(latlngs: LatLng[]) 1359 // 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). 1360 function toLatLngBounds(a, b) { 1361 if (a instanceof LatLngBounds) { 1362 return a; 1363 } 1364 return new LatLngBounds(a, b); 1365 } 1366 1367 /* @class LatLng 1368 * @aka L.LatLng 1369 * 1370 * Represents a geographical point with a certain latitude and longitude. 1371 * 1372 * @example 1373 * 1374 * ``` 1375 * var latlng = L.latLng(50.5, 30.5); 1376 * ``` 1377 * 1378 * 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: 1379 * 1380 * ``` 1381 * map.panTo([50, 30]); 1382 * map.panTo({lon: 30, lat: 50}); 1383 * map.panTo({lat: 50, lng: 30}); 1384 * map.panTo(L.latLng(50, 30)); 1385 * ``` 1386 * 1387 * Note that `LatLng` does not inherit from Leaflet's `Class` object, 1388 * which means new classes can't inherit from it, and new methods 1389 * can't be added to it with the `include` function. 1390 */ 1391 1392 function LatLng(lat, lng, alt) { 1393 if (isNaN(lat) || isNaN(lng)) { 1394 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 1395 } 1396 1397 // @property lat: Number 1398 // Latitude in degrees 1399 this.lat = +lat; 1400 1401 // @property lng: Number 1402 // Longitude in degrees 1403 this.lng = +lng; 1404 1405 // @property alt: Number 1406 // Altitude in meters (optional) 1407 if (alt !== undefined) { 1408 this.alt = +alt; 1409 } 1410 } 1411 1412 LatLng.prototype = { 1413 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 1414 // 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. 1415 equals: function (obj, maxMargin) { 1416 if (!obj) { return false; } 1417 1418 obj = toLatLng(obj); 1419 1420 var margin = Math.max( 1421 Math.abs(this.lat - obj.lat), 1422 Math.abs(this.lng - obj.lng)); 1423 1424 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 1425 }, 1426 1427 // @method toString(): String 1428 // Returns a string representation of the point (for debugging purposes). 1429 toString: function (precision) { 1430 return 'LatLng(' + 1431 formatNum(this.lat, precision) + ', ' + 1432 formatNum(this.lng, precision) + ')'; 1433 }, 1434 1435 // @method distanceTo(otherLatLng: LatLng): Number 1436 // 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). 1437 distanceTo: function (other) { 1438 return Earth.distance(this, toLatLng(other)); 1439 }, 1440 1441 // @method wrap(): LatLng 1442 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1443 wrap: function () { 1444 return Earth.wrapLatLng(this); 1445 }, 1446 1447 // @method toBounds(sizeInMeters: Number): LatLngBounds 1448 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1449 toBounds: function (sizeInMeters) { 1450 var latAccuracy = 180 * sizeInMeters / 40075017, 1451 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 1452 1453 return toLatLngBounds( 1454 [this.lat - latAccuracy, this.lng - lngAccuracy], 1455 [this.lat + latAccuracy, this.lng + lngAccuracy]); 1456 }, 1457 1458 clone: function () { 1459 return new LatLng(this.lat, this.lng, this.alt); 1460 } 1461 }; 1462 1463 1464 1465 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng 1466 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). 1467 1468 // @alternative 1469 // @factory L.latLng(coords: Array): LatLng 1470 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. 1471 1472 // @alternative 1473 // @factory L.latLng(coords: Object): LatLng 1474 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. 1475 1476 function toLatLng(a, b, c) { 1477 if (a instanceof LatLng) { 1478 return a; 1479 } 1480 if (isArray(a) && typeof a[0] !== 'object') { 1481 if (a.length === 3) { 1482 return new LatLng(a[0], a[1], a[2]); 1483 } 1484 if (a.length === 2) { 1485 return new LatLng(a[0], a[1]); 1486 } 1487 return null; 1488 } 1489 if (a === undefined || a === null) { 1490 return a; 1491 } 1492 if (typeof a === 'object' && 'lat' in a) { 1493 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 1494 } 1495 if (b === undefined) { 1496 return null; 1497 } 1498 return new LatLng(a, b, c); 1499 } 1500 1501 /* 1502 * @namespace CRS 1503 * @crs L.CRS.Base 1504 * Object that defines coordinate reference systems for projecting 1505 * geographical points into pixel (screen) coordinates and back (and to
1506 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See 1507 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system). 1508 * 1509 * Leaflet defines the most usual CRSs by default. If you want to use a 1510 * CRS not defined by default, take a look at the 1511 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. 1512 * 1513 * Note that the CRS instances do not inherit from Leaflet's `Class` object, 1514 * and can't be instantiated. Also, new classes can't inherit from them, 1515 * and methods can't be added to them with the `include` function. 1516 */ 1517 1518 var CRS = { 1519 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1520 // Projects geographical coordinates into pixel coordinates for a given zoom. 1521 latLngToPoint: function (latlng, zoom) { 1522 var projectedPoint = this.projection.project(latlng), 1523 scale = this.scale(zoom); 1524 1525 return this.transformation._transform(projectedPoint, scale); 1526 }, 1527 1528 // @method pointToLatLng(point: Point, zoom: Number): LatLng 1529 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1530 // zoom into geographical coordinates. 1531 pointToLatLng: function (point, zoom) { 1532 var scale = this.scale(zoom), 1533 untransformedPoint = this.transformation.untransform(point, scale); 1534 1535 return this.projection.unproject(untransformedPoint); 1536 }, 1537 1538 // @method project(latlng: LatLng): Point 1539 // Projects geographical coordinates into coordinates in units accepted for 1540 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1541 project: function (latlng) { 1542 return this.projection.project(latlng); 1543 }, 1544 1545 // @method unproject(point: Point): LatLng 1546 // Given a projected coordinate returns the corresponding LatLng. 1547 // The inverse of `project`. 1548 unproject: function (point) { 1549 return this.projection.unproject(point); 1550 }, 1551 1552 // @method scale(zoom: Number): Number 1553 // Returns the scale used when transforming projected coordinates into 1554 // pixel coordinates for a particular zoom. For example, it returns 1555 // `256 * 2^zoom` for Mercator-based CRS. 1556 scale: function (zoom) { 1557 return 256 * Math.pow(2, zoom); 1558 }, 1559 1560 // @method zoom(scale: Number): Number 1561 // Inverse of `scale()`, returns the zoom level corresponding to a scale 1562 // factor of `scale`. 1563 zoom: function (scale) { 1564 return Math.log(scale / 256) / Math.LN2; 1565 }, 1566 1567 // @method getProjectedBounds(zoom: Number): Bounds 1568 // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1569 getProjectedBounds: function (zoom) { 1570 if (this.infinite) { return null; } 1571 1572 var b = this.projection.bounds, 1573 s = this.scale(zoom), 1574 min = this.transformation.transform(b.min, s), 1575 max = this.transformation.transform(b.max, s); 1576 1577 return new Bounds(min, max); 1578 }, 1579 1580 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1581 // Returns the distance between two geographical coordinates. 1582 1583 // @property code: String 1584 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1585 // 1586 // @property wrapLng: Number[] 1587 // An array of two numbers defining whether the longitude (horizontal) coordinate 1588 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1589 // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1590 // 1591 // @property wrapLat: Number[] 1592 // Like `wrapLng`, but for the latitude (vertical) axis. 1593 1594 // wrapLng: [min, max], 1595 // wrapLat: [min, max], 1596 1597 // @property infinite: Boolean 1598 // If true, the coordinate space will be unbounded (infinite in both axes) 1599 infinite: false, 1600 1601 // @method wrapLatLng(latlng: LatLng): LatLng 1602 // Returns a `LatLng` where lat and lng has been wrapped according to the 1603 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1604 wrapLatLng: function (latlng) { 1605 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 1606 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 1607 alt = latlng.alt; 1608
vendor: 115,451 bytes, lines 1609-4500
1609 return new LatLng(lat, lng, alt); 1610 }, 1611 1612 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1613 // Returns a `LatLngBounds` with the same size as the given one, ensuring 1614 // that its center is within the CRS's bounds. 1615 // Only accepts actual `L.LatLngBounds` instances, not arrays. 1616 wrapLatLngBounds: function (bounds) { 1617 var center = bounds.getCenter(), 1618 newCenter = this.wrapLatLng(center), 1619 latShift = center.lat - newCenter.lat, 1620 lngShift = center.lng - newCenter.lng; 1621 1622 if (latShift === 0 && lngShift === 0) { 1623 return bounds; 1624 } 1625 1626 var sw = bounds.getSouthWest(), 1627 ne = bounds.getNorthEast(), 1628 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), 1629 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1630 1631 return new LatLngBounds(newSw, newNe); 1632 } 1633 }; 1634 1635 /* 1636 * @namespace CRS 1637 * @crs L.CRS.Earth 1638 * 1639 * Serves as the base for CRS that are global such that they cover the earth. 1640 * Can only be used as the base for other CRS and cannot be used directly, 1641 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns 1642 * meters. 1643 */ 1644 1645 var Earth = extend({}, CRS, { 1646 wrapLng: [-180, 180], 1647 1648 // Mean Earth Radius, as recommended for use by 1649 // the International Union of Geodesy and Geophysics, 1650 // see https://rosettacode.org/wiki/Haversine_formula 1651 R: 6371000, 1652 1653 // distance between two geographical points using spherical law of cosines approximation 1654 distance: function (latlng1, latlng2) { 1655 var rad = Math.PI / 180, 1656 lat1 = latlng1.lat * rad, 1657 lat2 = latlng2.lat * rad, 1658 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), 1659 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), 1660 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, 1661 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1662 return this.R * c; 1663 } 1664 }); 1665 1666 /* 1667 * @namespace Projection 1668 * @projection L.Projection.SphericalMercator 1669 * 1670 * Spherical Mercator projection ââ¬â the most common projection for online maps, 1671 * used by almost all free and commercial tile providers. Assumes that Earth is 1672 * a sphere. Used by the `EPSG:3857` CRS. 1673 */ 1674 1675 var earthRadius = 6378137; 1676 1677 var SphericalMercator = { 1678 1679 R: earthRadius, 1680 MAX_LATITUDE: 85.0511287798, 1681 1682 project: function (latlng) { 1683 var d = Math.PI / 180, 1684 max = this.MAX_LATITUDE, 1685 lat = Math.max(Math.min(max, latlng.lat), -max), 1686 sin = Math.sin(lat * d); 1687 1688 return new Point( 1689 this.R * latlng.lng * d, 1690 this.R * Math.log((1 + sin) / (1 - sin)) / 2); 1691 }, 1692 1693 unproject: function (point) { 1694 var d = 180 / Math.PI; 1695 1696 return new LatLng( 1697 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 1698 point.x * d / this.R); 1699 }, 1700 1701 bounds: (function () { 1702 var d = earthRadius * Math.PI; 1703 return new Bounds([-d, -d], [d, d]); 1704 })() 1705 }; 1706 1707 /* 1708 * @class Transformation 1709 * @aka L.Transformation 1710 * 1711 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` 1712 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing 1713 * the reverse. Used by Leaflet in its projections code. 1714 * 1715 * @example 1716 * 1717 * ```js 1718 * var transformation = L.transformation(2, 5, -1, 10), 1719 * p = L.point(1, 2), 1720 * p2 = transformation.transform(p), // L.point(7, 8) 1721 * p3 = transformation.untransform(p2); // L.point(1, 2) 1722 * ``` 1723 */ 1724 1725 1726 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) 1727 // Creates a `Transformation` object with the given coefficients. 1728 function Transformation(a, b, c, d) { 1729 if (isArray(a)) { 1730 // use array properties 1731 this._a = a[0]; 1732 this._b = a[1]; 1733 this._c = a[2]; 1734 this._d = a[3]; 1735 return; 1736 } 1737 this._a = a; 1738 this._b = b; 1739 this._c = c; 1740 this._d = d; 1741 } 1742 1743 Transformation.prototype = { 1744 // @method transform(point: Point, scale?: Number): Point 1745 // Returns a transformed point, optionally multiplied by the given scale. 1746 // Only accepts actual `L.Point` instances, not arrays. 1747 transform: function (point, scale) { // (Point, Number) -> Point 1748 return this._transform(point.clone(), scale); 1749 }, 1750 1751 // destructive transform (faster) 1752 _transform: function (point, scale) { 1753 scale = scale || 1; 1754 point.x = scale * (this._a * point.x + this._b); 1755 point.y = scale * (this._c * point.y + this._d); 1756 return point; 1757 }, 1758 1759 // @method untransform(point: Point, scale?: Number): Point 1760 // Returns the reverse transformation of the given point, optionally divided 1761 // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1762 untransform: function (point, scale) { 1763 scale = scale || 1; 1764 return new Point( 1765 (point.x / scale - this._b) / this._a, 1766 (point.y / scale - this._d) / this._c); 1767 } 1768 }; 1769 1770 // factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1771 1772 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number) 1773 // Instantiates a Transformation object with the given coefficients. 1774 1775 // @alternative 1776 // @factory L.transformation(coefficients: Array): Transformation 1777 // Expects an coefficients array of the form 1778 // `[a: Number, b: Number, c: Number, d: Number]`. 1779 1780 function toTransformation(a, b, c, d) { 1781 return new Transformation(a, b, c, d); 1782 } 1783 1784 /* 1785 * @namespace CRS 1786 * @crs L.CRS.EPSG3857 1787 * 1788 * The most common CRS for online maps, used by almost all free and commercial 1789 * tile providers. Uses Spherical Mercator projection. Set in by default in 1790 * Map's `crs` option. 1791 */ 1792 1793 var EPSG3857 = extend({}, Earth, { 1794 code: 'EPSG:3857', 1795 projection: SphericalMercator, 1796 1797 transformation: (function () { 1798 var scale = 0.5 / (Math.PI * SphericalMercator.R); 1799 return toTransformation(scale, 0.5, -scale, 0.5); 1800 }()) 1801 }); 1802 1803 var EPSG900913 = extend({}, EPSG3857, { 1804 code: 'EPSG:900913' 1805 }); 1806 1807 // @namespace SVG; @section 1808 // There are several static functions which can be called without instantiating L.SVG: 1809 1810 // @function create(name: String): SVGElement 1811 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), 1812 // corresponding to the class name passed. For example, using 'line' will return 1813 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). 1814 function svgCreate(name) { 1815 return document.createElementNS('http://www.w3.org/2000/svg', name); 1816 } 1817 1818 // @function pointsToPath(rings: Point[], closed: Boolean): String 1819 // Generates a SVG path string for multiple rings, with each ring turning 1820 // into "M..L..L.." instructions 1821 function pointsToPath(rings, closed) { 1822 var str = '', 1823 i, j, len, len2, points, p; 1824 1825 for (i = 0, len = rings.length; i < len; i++) { 1826 points = rings[i]; 1827 1828 for (j = 0, len2 = points.length; j < len2; j++) { 1829 p = points[j]; 1830 str += (j ? 'L' : 'M') + p.x + ' ' + p.y; 1831 } 1832 1833 // closes the ring for polygons; "x" is VML syntax 1834 str += closed ? (Browser.svg ? 'z' : 'x') : ''; 1835 } 1836 1837 // SVG complains about empty path strings 1838 return str || 'M0 0'; 1839 } 1840 1841 /* 1842 * @namespace Browser 1843 * @aka L.Browser 1844 * 1845 * A namespace with static properties for browser/feature detection used by Leaflet internally. 1846 * 1847 * @example 1848 * 1849 * ```js 1850 * if (L.Browser.ielt9) { 1851 * alert('Upgrade your browser, dude!'); 1852 * } 1853 * ``` 1854 */ 1855 1856 var style = document.documentElement.style; 1857 1858 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). 1859 var ie = 'ActiveXObject' in window; 1860 1861 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. 1862 var ielt9 = ie && !document.addEventListener; 1863 1864 // @property edge: Boolean; `true` for the Edge web browser. 1865 var edge = 'msLaunchUri' in navigator && !('documentMode' in document); 1866 1867 // @property webkit: Boolean; 1868 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). 1869 var webkit = userAgentContains('webkit'); 1870 1871 // @property android: Boolean 1872 // **Deprecated.** `true` for any browser running on an Android platform. 1873 var android = userAgentContains('android'); 1874 1875 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3. 1876 var android23 = userAgentContains('android 2') || userAgentContains('android 3'); 1877 1878 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ 1879 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit 1880 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome) 1881 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); 1882 1883 // @property opera: Boolean; `true` for the Opera browser 1884 var opera = !!window.opera; 1885 1886 // @property chrome: Boolean; `true` for the Chrome browser. 1887 var chrome = !edge && userAgentContains('chrome'); 1888 1889 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox. 1890 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; 1891 1892 // @property safari: Boolean; `true` for the Safari browser. 1893 var safari = !chrome && userAgentContains('safari'); 1894 1895 var phantom = userAgentContains('phantom'); 1896 1897 // @property opera12: Boolean 1898 // `true` for the Opera browser supporting CSS transforms (version 12 or later). 1899 var opera12 = 'OTransition' in style; 1900 1901 // @property win: Boolean; `true` when the browser is running in a Windows platform 1902 var win = navigator.platform.indexOf('Win') === 0; 1903 1904 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. 1905 var ie3d = ie && ('transition' in style); 1906 1907 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. 1908 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; 1909 1910 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. 1911 var gecko3d = 'MozPerspective' in style; 1912 1913 // @property any3d: Boolean 1914 // `true` for all browsers supporting CSS transforms. 1915 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1916 1917 // @property mobile: Boolean; `true` for all browsers running in a mobile device. 1918 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); 1919 1920 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. 1921 var mobileWebkit = mobile && webkit; 1922 1923 // @property mobileWebkit3d: Boolean 1924 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. 1925 var mobileWebkit3d = mobile && webkit3d; 1926 1927 // @property msPointer: Boolean 1928 // `true` for browsers implementing the Microsoft touch events model (notably IE10). 1929 var msPointer = !window.PointerEvent && window.MSPointerEvent; 1930 1931 // @property pointer: Boolean 1932 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). 1933 var pointer = !!(window.PointerEvent || msPointer); 1934 1935 // @property touchNative: Boolean 1936 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). 1937 // **This does not necessarily mean** that the browser is running in a computer with 1938 // a touchscreen, it only means that the browser is capable of understanding 1939 // touch events. 1940 var touchNative = 'ontouchstart' in window || !!window.TouchEvent; 1941 1942 // @property touch: Boolean 1943 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events. 1944 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners. 1945 var touch = !window.L_NO_TOUCH && (touchNative || pointer); 1946 1947 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. 1948 var mobileOpera = mobile && opera; 1949 1950 // @property mobileGecko: Boolean 1951 // `true` for gecko-based browsers running in a mobile device. 1952 var mobileGecko = mobile && gecko; 1953 1954 // @property retina: Boolean 1955 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. 1956 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; 1957 1958 // @property passiveEvents: Boolean 1959 // `true` for browsers that support passive events. 1960 var passiveEvents = (function () { 1961 var supportsPassiveOption = false; 1962 try { 1963 var opts = Object.defineProperty({}, 'passive', { 1964 get: function () { // eslint-disable-line getter-return 1965 supportsPassiveOption = true; 1966 } 1967 }); 1968 window.addEventListener('testPassiveEventSupport', falseFn, opts); 1969 window.removeEventListener('testPassiveEventSupport', falseFn, opts); 1970 } catch (e) { 1971 // Errors can safely be ignored since this is only a browser support test. 1972 } 1973 return supportsPassiveOption; 1974 }()); 1975 1976 // @property canvas: Boolean 1977 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 1978 var canvas$1 = (function () { 1979 return !!document.createElement('canvas').getContext; 1980 }()); 1981 1982 // @property svg: Boolean 1983 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). 1984 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect); 1985 1986 // @property vml: Boolean 1987 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). 1988 var vml = !svg$1 && (function () { 1989 try { 1990 var div = document.createElement('div'); 1991 div.innerHTML = '<v:shape adj="1"/>'; 1992 1993 var shape = div.firstChild; 1994 shape.style.behavior = 'url(#default#VML)'; 1995 1996 return shape && (typeof shape.adj === 'object'); 1997 1998 } catch (e) { 1999 return false; 2000 } 2001 }()); 2002 2003 2004 function userAgentContains(str) { 2005 return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 2006 } 2007 2008 2009 var Browser = { 2010 ie: ie, 2011 ielt9: ielt9, 2012 edge: edge, 2013 webkit: webkit, 2014 android: android, 2015 android23: android23, 2016 androidStock: androidStock, 2017 opera: opera, 2018 chrome: chrome, 2019 gecko: gecko, 2020 safari: safari, 2021 phantom: phantom, 2022 opera12: opera12, 2023 win: win, 2024 ie3d: ie3d, 2025 webkit3d: webkit3d, 2026 gecko3d: gecko3d, 2027 any3d: any3d, 2028 mobile: mobile, 2029 mobileWebkit: mobileWebkit, 2030 mobileWebkit3d: mobileWebkit3d, 2031 msPointer: msPointer, 2032 pointer: pointer, 2033 touch: touch, 2034 touchNative: touchNative, 2035 mobileOpera: mobileOpera, 2036 mobileGecko: mobileGecko, 2037 retina: retina, 2038 passiveEvents: passiveEvents, 2039 canvas: canvas$1, 2040 svg: svg$1, 2041 vml: vml, 2042 }; 2043 2044 /* 2045 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. 2046 */ 2047 2048 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown'; 2049 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove'; 2050 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup'; 2051 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel'; 2052 var pEvent = { 2053 touchstart : POINTER_DOWN, 2054 touchmove : POINTER_MOVE, 2055 touchend : POINTER_UP, 2056 touchcancel : POINTER_CANCEL 2057 }; 2058 var handle = { 2059 touchstart : _onPointerStart, 2060 touchmove : _handlePointer, 2061 touchend : _handlePointer, 2062 touchcancel : _handlePointer 2063 }; 2064 var _pointers = {}; 2065 var _pointerDocListener = false; 2066 2067 // Provides a touch events wrapper for (ms)pointer events. 2068 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 2069 2070 function addPointerListener(obj, type, handler) { 2071 if (type === 'touchstart') { 2072 _addPointerDocListener(); 2073 } 2074 if (!handle[type]) { 2075 console.warn('wrong event specified:', type); 2076 return L.Util.falseFn; 2077 } 2078 handler = handle[type].bind(this, handler); 2079 obj.addEventListener(pEvent[type], handler, false); 2080 return handler; 2081 } 2082 2083 function removePointerListener(obj, type, handler) { 2084 if (!pEvent[type]) { 2085 console.warn('wrong event specified:', type); 2086 return; 2087 } 2088 obj.removeEventListener(pEvent[type], handler, false); 2089 } 2090 2091 function _globalPointerDown(e) { 2092 _pointers[e.pointerId] = e; 2093 } 2094 2095 function _globalPointerMove(e) { 2096 if (_pointers[e.pointerId]) { 2097 _pointers[e.pointerId] = e; 2098 } 2099 } 2100 2101 function _globalPointerUp(e) { 2102 delete _pointers[e.pointerId]; 2103 } 2104 2105 function _addPointerDocListener() { 2106 // need to keep track of what pointers and how many are active to provide e.touches emulation 2107 if (!_pointerDocListener) { 2108 // we listen document as any drags that end by moving the touch off the screen get fired there 2109 document.addEventListener(POINTER_DOWN, _globalPointerDown, true); 2110 document.addEventListener(POINTER_MOVE, _globalPointerMove, true); 2111 document.addEventListener(POINTER_UP, _globalPointerUp, true); 2112 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 2113 2114 _pointerDocListener = true; 2115 } 2116 } 2117 2118 function _handlePointer(handler, e) { 2119 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; } 2120 2121 e.touches = []; 2122 for (var i in _pointers) { 2123 e.touches.push(_pointers[i]); 2124 } 2125 e.changedTouches = [e]; 2126 2127 handler(e); 2128 } 2129 2130 function _onPointerStart(handler, e) { 2131 // IE10 specific: MsTouch needs preventDefault. See #2000 2132 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) { 2133 preventDefault(e); 2134 } 2135 _handlePointer(handler, e); 2136 } 2137 2138 /* 2139 * Extends the event handling code with double tap support for mobile browsers. 2140 * 2141 * Note: currently most browsers fire native dblclick, with only a few exceptions 2142 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386) 2143 */ 2144 2145 function makeDblclick(event) { 2146 // in modern browsers `type` cannot be just overridden: 2147 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only 2148 var newEvent = {}, 2149 prop, i; 2150 for (i in event) { 2151 prop = event[i]; 2152 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop; 2153 } 2154 event = newEvent; 2155 newEvent.type = 'dblclick'; 2156 newEvent.detail = 2; 2157 newEvent.isTrusted = false; 2158 newEvent._simulated = true; // for debug purposes 2159 return newEvent; 2160 } 2161 2162 var delay = 200; 2163 function addDoubleTapListener(obj, handler) { 2164 // Most browsers handle double tap natively 2165 obj.addEventListener('dblclick', handler); 2166 2167 // On some platforms the browser doesn't fire native dblclicks for touch events. 2168 // It seems that in all such cases `detail` property of `click` event is always `1`. 2169 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed. 2170 var last = 0, 2171 detail; 2172 function simDblclick(e) { 2173 if (e.detail !== 1) { 2174 detail = e.detail; // keep in sync to avoid false dblclick in some cases 2175 return; 2176 } 2177 2178 if (e.pointerType === 'mouse' || 2179 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) { 2180 2181 return; 2182 } 2183 2184 var now = Date.now(); 2185 if (now - last <= delay) { 2186 detail++; 2187 if (detail === 2) { 2188 handler(makeDblclick(e)); 2189 } 2190 } else { 2191 detail = 1; 2192 } 2193 last = now; 2194 } 2195 2196 obj.addEventListener('click', simDblclick); 2197 2198 return { 2199 dblclick: handler, 2200 simDblclick: simDblclick 2201 }; 2202 } 2203 2204 function removeDoubleTapListener(obj, handlers) { 2205 obj.removeEventListener('dblclick', handlers.dblclick); 2206 obj.removeEventListener('click', handlers.simDblclick); 2207 } 2208 2209 /* 2210 * @namespace DomUtil 2211 * 2212 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) 2213 * tree, used by Leaflet internally. 2214 * 2215 * Most functions expecting or returning a `HTMLElement` also work for 2216 * SVG elements. The only difference is that classes refer to CSS classes 2217 * in HTML and SVG classes in SVG. 2218 */ 2219 2220 2221 // @property TRANSFORM: String 2222 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit). 2223 var TRANSFORM = testProp( 2224 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 2225 2226 // webkitTransition comes first because some browser versions that drop vendor prefix don't do 2227 // the same for the transitionend event, in particular the Android 4.1 stock browser 2228 2229 // @property TRANSITION: String 2230 // Vendor-prefixed transition style name. 2231 var TRANSITION = testProp( 2232 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); 2233 2234 // @property TRANSITION_END: String 2235 // Vendor-prefixed transitionend event name. 2236 var TRANSITION_END = 2237 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; 2238 2239 2240 // @function get(id: String|HTMLElement): HTMLElement 2241 // Returns an element given its DOM id, or returns the element itself 2242 // if it was passed directly. 2243 function get(id) { 2244 return typeof id === 'string' ? document.getElementById(id) : id; 2245 } 2246 2247 // @function getStyle(el: HTMLElement, styleAttrib: String): String 2248 // Returns the value for a certain style attribute on an element, 2249 // including computed values or values set through CSS. 2250 function getStyle(el, style) { 2251 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); 2252 2253 if ((!value || value === 'auto') && document.defaultView) { 2254 var css = document.defaultView.getComputedStyle(el, null); 2255 value = css ? css[style] : null; 2256 } 2257 return value === 'auto' ? null : value; 2258 } 2259 2260 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement 2261 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. 2262 function create$1(tagName, className, container) { 2263 var el = document.createElement(tagName); 2264 el.className = className || ''; 2265 2266 if (container) { 2267 container.appendChild(el); 2268 } 2269 return el; 2270 } 2271 2272 // @function remove(el: HTMLElement) 2273 // Removes `el` from its parent element 2274 function remove(el) { 2275 var parent = el.parentNode; 2276 if (parent) { 2277 parent.removeChild(el); 2278 } 2279 } 2280 2281 // @function empty(el: HTMLElement) 2282 // Removes all of `el`'s children elements from `el` 2283 function empty(el) { 2284 while (el.firstChild) { 2285 el.removeChild(el.firstChild); 2286 } 2287 } 2288 2289 // @function toFront(el: HTMLElement) 2290 // Makes `el` the last child of its parent, so it renders in front of the other children. 2291 function toFront(el) { 2292 var parent = el.parentNode; 2293 if (parent && parent.lastChild !== el) { 2294 parent.appendChild(el); 2295 } 2296 } 2297 2298 // @function toBack(el: HTMLElement) 2299 // Makes `el` the first child of its parent, so it renders behind the other children. 2300 function toBack(el) { 2301 var parent = el.parentNode; 2302 if (parent && parent.firstChild !== el) { 2303 parent.insertBefore(el, parent.firstChild); 2304 } 2305 } 2306 2307 // @function hasClass(el: HTMLElement, name: String): Boolean 2308 // Returns `true` if the element's class attribute contains `name`. 2309 function hasClass(el, name) { 2310 if (el.classList !== undefined) { 2311 return el.classList.contains(name); 2312 } 2313 var className = getClass(el); 2314 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); 2315 } 2316 2317 // @function addClass(el: HTMLElement, name: String) 2318 // Adds `name` to the element's class attribute. 2319 function addClass(el, name) { 2320 if (el.classList !== undefined) { 2321 var classes = splitWords(name); 2322 for (var i = 0, len = classes.length; i < len; i++) { 2323 el.classList.add(classes[i]); 2324 } 2325 } else if (!hasClass(el, name)) { 2326 var className = getClass(el); 2327 setClass(el, (className ? className + ' ' : '') + name); 2328 } 2329 } 2330 2331 // @function removeClass(el: HTMLElement, name: String) 2332 // Removes `name` from the element's class attribute. 2333 function removeClass(el, name) { 2334 if (el.classList !== undefined) { 2335 el.classList.remove(name); 2336 } else { 2337 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); 2338 } 2339 } 2340 2341 // @function setClass(el: HTMLElement, name: String) 2342 // Sets the element's class. 2343 function setClass(el, name) { 2344 if (el.className.baseVal === undefined) { 2345 el.className = name; 2346 } else { 2347 // in case of SVG element 2348 el.className.baseVal = name; 2349 } 2350 } 2351 2352 // @function getClass(el: HTMLElement): String 2353 // Returns the element's class. 2354 function getClass(el) { 2355 // Check if the element is an SVGElementInstance and use the correspondingElement instead 2356 // (Required for linked SVG elements in IE11.) 2357 if (el.correspondingElement) { 2358 el = el.correspondingElement; 2359 } 2360 return el.className.baseVal === undefined ? el.className : el.className.baseVal; 2361 } 2362 2363 // @function setOpacity(el: HTMLElement, opacity: Number) 2364 // Set the opacity of an element (including old IE support). 2365 // `opacity` must be a number from `0` to `1`. 2366 function setOpacity(el, value) { 2367 if ('opacity' in el.style) { 2368 el.style.opacity = value; 2369 } else if ('filter' in el.style) { 2370 _setOpacityIE(el, value); 2371 } 2372 } 2373 2374 function _setOpacityIE(el, value) { 2375 var filter = false, 2376 filterName = 'DXImageTransform.Microsoft.Alpha'; 2377 2378 // filters collection throws an error if we try to retrieve a filter that doesn't exist 2379 try { 2380 filter = el.filters.item(filterName); 2381 } catch (e) { 2382 // don't set opacity to 1 if we haven't already set an opacity, 2383 // it isn't needed and breaks transparent pngs. 2384 if (value === 1) { return; } 2385 } 2386 2387 value = Math.round(value * 100); 2388 2389 if (filter) { 2390 filter.Enabled = (value !== 100); 2391 filter.Opacity = value; 2392 } else { 2393 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; 2394 } 2395 } 2396 2397 // @function testProp(props: String[]): String|false 2398 // Goes through the array of style names and returns the first name 2399 // that is a valid style name for an element. If no such name is found, 2400 // it returns false. Useful for vendor-prefixed styles like `transform`. 2401 function testProp(props) { 2402 var style = document.documentElement.style; 2403 2404 for (var i = 0; i < props.length; i++) { 2405 if (props[i] in style) { 2406 return props[i]; 2407 } 2408 } 2409 return false; 2410 } 2411 2412 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) 2413 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels 2414 // and optionally scaled by `scale`. Does not have an effect if the 2415 // browser doesn't support 3D CSS transforms. 2416 function setTransform(el, offset, scale) { 2417 var pos = offset || new Point(0, 0); 2418 2419 el.style[TRANSFORM] = 2420 (Browser.ie3d ? 2421 'translate(' + pos.x + 'px,' + pos.y + 'px)' : 2422 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + 2423 (scale ? ' scale(' + scale + ')' : ''); 2424 } 2425 2426 // @function setPosition(el: HTMLElement, position: Point) 2427 // Sets the position of `el` to coordinates specified by `position`, 2428 // using CSS translate or top/left positioning depending on the browser 2429 // (used by Leaflet internally to position its layers). 2430 function setPosition(el, point) { 2431 2432 /*eslint-disable */ 2433 el._leaflet_pos = point; 2434 /* eslint-enable */ 2435 2436 if (Browser.any3d) { 2437 setTransform(el, point); 2438 } else { 2439 el.style.left = point.x + 'px'; 2440 el.style.top = point.y + 'px'; 2441 } 2442 } 2443 2444 // @function getPosition(el: HTMLElement): Point 2445 // Returns the coordinates of an element previously positioned with setPosition. 2446 function getPosition(el) { 2447 // this method is only used for elements previously positioned using setPosition, 2448 // so it's safe to cache the position for performance 2449 2450 return el._leaflet_pos || new Point(0, 0); 2451 } 2452 2453 // @function disableTextSelection() 2454 // Prevents the user from generating `selectstart` DOM events, usually generated 2455 // when the user drags the mouse through a page with text. Used internally 2456 // by Leaflet to override the behaviour of any click-and-drag interaction on 2457 // the map. Affects drag interactions on the whole document. 2458 2459 // @function enableTextSelection() 2460 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). 2461 var disableTextSelection; 2462 var enableTextSelection; 2463 var _userSelect; 2464 if ('onselectstart' in document) { 2465 disableTextSelection = function () { 2466 on(window, 'selectstart', preventDefault); 2467 }; 2468 enableTextSelection = function () { 2469 off(window, 'selectstart', preventDefault); 2470 }; 2471 } else { 2472 var userSelectProperty = testProp( 2473 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); 2474 2475 disableTextSelection = function () { 2476 if (userSelectProperty) { 2477 var style = document.documentElement.style; 2478 _userSelect = style[userSelectProperty]; 2479 style[userSelectProperty] = 'none'; 2480 } 2481 }; 2482 enableTextSelection = function () { 2483 if (userSelectProperty) { 2484 document.documentElement.style[userSelectProperty] = _userSelect; 2485 _userSelect = undefined; 2486 } 2487 }; 2488 } 2489 2490 // @function disableImageDrag() 2491 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but 2492 // for `dragstart` DOM events, usually generated when the user drags an image. 2493 function disableImageDrag() { 2494 on(window, 'dragstart', preventDefault); 2495 } 2496 2497 // @function enableImageDrag() 2498 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). 2499 function enableImageDrag() { 2500 off(window, 'dragstart', preventDefault); 2501 } 2502 2503 var _outlineElement, _outlineStyle; 2504 // @function preventOutline(el: HTMLElement) 2505 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) 2506 // of the element `el` invisible. Used internally by Leaflet to prevent 2507 // focusable elements from displaying an outline when the user performs a 2508 // drag interaction on them. 2509 function preventOutline(element) { 2510 while (element.tabIndex === -1) { 2511 element = element.parentNode; 2512 } 2513 if (!element.style) { return; } 2514 restoreOutline(); 2515 _outlineElement = element; 2516 _outlineStyle = element.style.outline; 2517 element.style.outline = 'none'; 2518 on(window, 'keydown', restoreOutline); 2519 } 2520 2521 // @function restoreOutline() 2522 // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). 2523 function restoreOutline() { 2524 if (!_outlineElement) { return; } 2525 _outlineElement.style.outline = _outlineStyle; 2526 _outlineElement = undefined; 2527 _outlineStyle = undefined; 2528 off(window, 'keydown', restoreOutline); 2529 } 2530 2531 // @function getSizedParentNode(el: HTMLElement): HTMLElement 2532 // Finds the closest parent node which size (width and height) is not null. 2533 function getSizedParentNode(element) { 2534 do { 2535 element = element.parentNode; 2536 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 2537 return element; 2538 } 2539 2540 // @function getScale(el: HTMLElement): Object 2541 // Computes the CSS scale currently applied on the element. 2542 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively, 2543 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). 2544 function getScale(element) { 2545 var rect = element.getBoundingClientRect(); // Read-only in old browsers. 2546 2547 return { 2548 x: rect.width / element.offsetWidth || 1, 2549 y: rect.height / element.offsetHeight || 1, 2550 boundingClientRect: rect 2551 }; 2552 } 2553 2554 var DomUtil = { 2555 __proto__: null, 2556 TRANSFORM: TRANSFORM, 2557 TRANSITION: TRANSITION, 2558 TRANSITION_END: TRANSITION_END, 2559 get: get, 2560 getStyle: getStyle, 2561 create: create$1, 2562 remove: remove, 2563 empty: empty, 2564 toFront: toFront, 2565 toBack: toBack, 2566 hasClass: hasClass, 2567 addClass: addClass, 2568 removeClass: removeClass, 2569 setClass: setClass, 2570 getClass: getClass, 2571 setOpacity: setOpacity, 2572 testProp: testProp, 2573 setTransform: setTransform, 2574 setPosition: setPosition, 2575 getPosition: getPosition, 2576 get disableTextSelection () { return disableTextSelection; }, 2577 get enableTextSelection () { return enableTextSelection; }, 2578 disableImageDrag: disableImageDrag, 2579 enableImageDrag: enableImageDrag, 2580 preventOutline: preventOutline, 2581 restoreOutline: restoreOutline, 2582 getSizedParentNode: getSizedParentNode, 2583 getScale: getScale 2584 }; 2585 2586 /* 2587 * @namespace DomEvent 2588 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. 2589 */ 2590 2591 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. 2592 2593 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this 2594 // Adds a listener function (`fn`) to a particular DOM event type of the 2595 // element `el`. You can optionally specify the context of the listener 2596 // (object the `this` keyword will point to). You can also pass several 2597 // space-separated types (e.g. `'click dblclick'`). 2598 2599 // @alternative 2600 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this 2601 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2602 function on(obj, types, fn, context) { 2603 2604 if (types && typeof types === 'object') { 2605 for (var type in types) { 2606 addOne(obj, type, types[type], fn); 2607 } 2608 } else { 2609 types = splitWords(types); 2610 2611 for (var i = 0, len = types.length; i < len; i++) { 2612 addOne(obj, types[i], fn, context); 2613 } 2614 } 2615 2616 return this; 2617 } 2618 2619 var eventsKey = '_leaflet_events'; 2620 2621 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this 2622 // Removes a previously added listener function. 2623 // Note that if you passed a custom context to on, you must pass the same 2624 // context to `off` in order to remove the listener. 2625 2626 // @alternative 2627 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this 2628 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 2629 2630 // @alternative 2631 // @function off(el: HTMLElement, types: String): this 2632 // Removes all previously added listeners of given types. 2633 2634 // @alternative 2635 // @function off(el: HTMLElement): this 2636 // Removes all previously added listeners from given HTMLElement 2637 function off(obj, types, fn, context) { 2638 2639 if (arguments.length === 1) { 2640 batchRemove(obj); 2641 delete obj[eventsKey]; 2642 2643 } else if (types && typeof types === 'object') { 2644 for (var type in types) { 2645 removeOne(obj, type, types[type], fn); 2646 } 2647 2648 } else { 2649 types = splitWords(types); 2650 2651 if (arguments.length === 2) { 2652 batchRemove(obj, function (type) { 2653 return indexOf(types, type) !== -1; 2654 }); 2655 } else { 2656 for (var i = 0, len = types.length; i < len; i++) { 2657 removeOne(obj, types[i], fn, context); 2658 } 2659 } 2660 } 2661 2662 return this; 2663 } 2664 2665 function batchRemove(obj, filterFn) { 2666 for (var id in obj[eventsKey]) { 2667 var type = id.split(/\d/)[0]; 2668 if (!filterFn || filterFn(type)) { 2669 removeOne(obj, type, null, null, id); 2670 } 2671 } 2672 } 2673 2674 var mouseSubst = { 2675 mouseenter: 'mouseover', 2676 mouseleave: 'mouseout', 2677 wheel: !('onwheel' in window) && 'mousewheel' 2678 }; 2679 2680 function addOne(obj, type, fn, context) { 2681 var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2682 2683 if (obj[eventsKey] && obj[eventsKey][id]) { return this; } 2684 2685 var handler = function (e) { 2686 return fn.call(context || obj, e || window.event); 2687 }; 2688 2689 var originalHandler = handler; 2690 2691 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 2692 // Needs DomEvent.Pointer.js 2693 handler = addPointerListener(obj, type, handler); 2694 2695 } else if (Browser.touch && (type === 'dblclick')) { 2696 handler = addDoubleTapListener(obj, handler); 2697 2698 } else if ('addEventListener' in obj) { 2699 2700 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') { 2701 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false); 2702 2703 } else if (type === 'mouseenter' || type === 'mouseleave') { 2704 handler = function (e) { 2705 e = e || window.event; 2706 if (isExternalTarget(obj, e)) { 2707 originalHandler(e); 2708 } 2709 }; 2710 obj.addEventListener(mouseSubst[type], handler, false); 2711 2712 } else { 2713 obj.addEventListener(type, originalHandler, false); 2714 } 2715 2716 } else { 2717 obj.attachEvent('on' + type, handler); 2718 } 2719 2720 obj[eventsKey] = obj[eventsKey] || {}; 2721 obj[eventsKey][id] = handler; 2722 } 2723 2724 function removeOne(obj, type, fn, context, id) { 2725 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : ''); 2726 var handler = obj[eventsKey] && obj[eventsKey][id]; 2727 2728 if (!handler) { return this; } 2729 2730 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) { 2731 removePointerListener(obj, type, handler); 2732 2733 } else if (Browser.touch && (type === 'dblclick')) { 2734 removeDoubleTapListener(obj, handler); 2735 2736 } else if ('removeEventListener' in obj) { 2737 2738 obj.removeEventListener(mouseSubst[type] || type, handler, false); 2739 2740 } else { 2741 obj.detachEvent('on' + type, handler); 2742 } 2743 2744 obj[eventsKey][id] = null; 2745 } 2746 2747 // @function stopPropagation(ev: DOMEvent): this 2748 // Stop the given event from propagation to parent elements. Used inside the listener functions: 2749 // ```js 2750 // L.DomEvent.on(div, 'click', function (ev) { 2751 // L.DomEvent.stopPropagation(ev); 2752 // }); 2753 // ``` 2754 function stopPropagation(e) { 2755 2756 if (e.stopPropagation) { 2757 e.stopPropagation(); 2758 } else if (e.originalEvent) { // In case of Leaflet event. 2759 e.originalEvent._stopped = true; 2760 } else { 2761 e.cancelBubble = true; 2762 } 2763 2764 return this; 2765 } 2766 2767 // @function disableScrollPropagation(el: HTMLElement): this 2768 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants). 2769 function disableScrollPropagation(el) { 2770 addOne(el, 'wheel', stopPropagation); 2771 return this; 2772 } 2773 2774 // @function disableClickPropagation(el: HTMLElement): this 2775 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`, 2776 // `'mousedown'` and `'touchstart'` events (plus browser variants). 2777 function disableClickPropagation(el) { 2778 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation); 2779 el['_leaflet_disable_click'] = true; 2780 return this; 2781 } 2782 2783 // @function preventDefault(ev: DOMEvent): this 2784 // Prevents the default action of the DOM Event `ev` from happening (such as 2785 // following a link in the href of the a element, or doing a POST request 2786 // with page reload when a `<form>` is submitted). 2787 // Use it inside listener functions. 2788 function preventDefault(e) { 2789 if (e.preventDefault) { 2790 e.preventDefault(); 2791 } else { 2792 e.returnValue = false; 2793 } 2794 return this; 2795 } 2796 2797 // @function stop(ev: DOMEvent): this 2798 // Does `stopPropagation` and `preventDefault` at the same time. 2799 function stop(e) { 2800 preventDefault(e); 2801 stopPropagation(e); 2802 return this; 2803 } 2804 2805 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point 2806 // Gets normalized mouse position from a DOM event relative to the 2807 // `container` (border excluded) or to the whole page if not specified. 2808 function getMousePosition(e, container) { 2809 if (!container) { 2810 return new Point(e.clientX, e.clientY); 2811 } 2812 2813 var scale = getScale(container), 2814 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y) 2815 2816 return new Point( 2817 // offset.left/top values are in page scale (like clientX/Y), 2818 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 2819 (e.clientX - offset.left) / scale.x - container.clientLeft, 2820 (e.clientY - offset.top) / scale.y - container.clientTop 2821 ); 2822 } 2823 2824 // Chrome on Win scrolls double the pixels as in other platforms (see #4538), 2825 // and Firefox scrolls device pixels, not CSS pixels 2826 var wheelPxFactor = 2827 (Browser.win && Browser.chrome) ? 2 * window.devicePixelRatio : 2828 Browser.gecko ? window.devicePixelRatio : 1; 2829 2830 // @function getWheelDelta(ev: DOMEvent): Number 2831 // Gets normalized wheel delta from a wheel DOM event, in vertical 2832 // pixels scrolled (negative if scrolling down). 2833 // Events from pointing devices without precise scrolling are mapped to 2834 // a best guess of 60 pixels. 2835 function getWheelDelta(e) { 2836 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta 2837 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels 2838 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines 2839 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages 2840 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events 2841 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels 2842 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines 2843 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 2844 0; 2845 } 2846 2847 // check if element really left/entered the event target (for mouseenter/mouseleave) 2848 function isExternalTarget(el, e) { 2849 2850 var related = e.relatedTarget; 2851 2852 if (!related) { return true; } 2853 2854 try { 2855 while (related && (related !== el)) { 2856 related = related.parentNode; 2857 } 2858 } catch (err) { 2859 return false; 2860 } 2861 return (related !== el); 2862 } 2863 2864 var DomEvent = { 2865 __proto__: null, 2866 on: on, 2867 off: off, 2868 stopPropagation: stopPropagation, 2869 disableScrollPropagation: disableScrollPropagation, 2870 disableClickPropagation: disableClickPropagation, 2871 preventDefault: preventDefault, 2872 stop: stop, 2873 getMousePosition: getMousePosition, 2874 getWheelDelta: getWheelDelta, 2875 isExternalTarget: isExternalTarget, 2876 addListener: on, 2877 removeListener: off 2878 }; 2879 2880 /* 2881 * @class PosAnimation 2882 * @aka L.PosAnimation 2883 * @inherits Evented 2884 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9. 2885 * 2886 * @example 2887 * ```js 2888 * var fx = new L.PosAnimation(); 2889 * fx.run(el, [300, 500], 0.5); 2890 * ``` 2891 * 2892 * @constructor L.PosAnimation() 2893 * Creates a `PosAnimation` object. 2894 * 2895 */ 2896 2897 var PosAnimation = Evented.extend({ 2898 2899 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 2900 // Run an animation of a given element to a new position, optionally setting 2901 // duration in seconds (`0.25` by default) and easing linearity factor (3rd 2902 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1), 2903 // `0.5` by default). 2904 run: function (el, newPos, duration, easeLinearity) { 2905 this.stop(); 2906 2907 this._el = el; 2908 this._inProgress = true; 2909 this._duration = duration || 0.25; 2910 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 2911 2912 this._startPos = getPosition(el); 2913 this._offset = newPos.subtract(this._startPos); 2914 this._startTime = +new Date(); 2915 2916 // @event start: Event 2917 // Fired when the animation starts 2918 this.fire('start'); 2919 2920 this._animate(); 2921 }, 2922 2923 // @method stop() 2924 // Stops the animation (if currently running). 2925 stop: function () { 2926 if (!this._inProgress) { return; } 2927 2928 this._step(true); 2929 this._complete(); 2930 }, 2931 2932 _animate: function () { 2933 // animation loop 2934 this._animId = requestAnimFrame(this._animate, this); 2935 this._step(); 2936 }, 2937 2938 _step: function (round) { 2939 var elapsed = (+new Date()) - this._startTime, 2940 duration = this._duration * 1000; 2941 2942 if (elapsed < duration) { 2943 this._runFrame(this._easeOut(elapsed / duration), round); 2944 } else { 2945 this._runFrame(1); 2946 this._complete(); 2947 } 2948 }, 2949 2950 _runFrame: function (progress, round) { 2951 var pos = this._startPos.add(this._offset.multiplyBy(progress)); 2952 if (round) { 2953 pos._round(); 2954 } 2955 setPosition(this._el, pos); 2956 2957 // @event step: Event 2958 // Fired continuously during the animation. 2959 this.fire('step'); 2960 }, 2961 2962 _complete: function () { 2963 cancelAnimFrame(this._animId); 2964 2965 this._inProgress = false; 2966 // @event end: Event 2967 // Fired when the animation ends. 2968 this.fire('end'); 2969 }, 2970 2971 _easeOut: function (t) { 2972 return 1 - Math.pow(1 - t, this._easeOutPower); 2973 } 2974 }); 2975 2976 /* 2977 * @class Map 2978 * @aka L.Map 2979 * @inherits Evented 2980 * 2981 * The central class of the API ââ¬â it is used to create a map on a page and manipulate it. 2982 * 2983 * @example 2984 * 2985 * ```js 2986 * // initialize the map on the "map" div with a given center and zoom 2987 * var map = L.map('map', { 2988 * center: [51.505, -0.09], 2989 * zoom: 13 2990 * }); 2991 * ``` 2992 * 2993 */ 2994 2995 var Map = Evented.extend({ 2996 2997 options: { 2998 // @section Map State Options 2999 // @option crs: CRS = L.CRS.EPSG3857 3000 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 3001 // sure what it means. 3002 crs: EPSG3857, 3003 3004 // @option center: LatLng = undefined 3005 // Initial geographic center of the map 3006 center: undefined, 3007 3008 // @option zoom: Number = undefined 3009 // Initial map zoom level 3010 zoom: undefined, 3011 3012 // @option minZoom: Number = * 3013 // Minimum zoom level of the map. 3014 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3015 // the lowest of their `minZoom` options will be used instead. 3016 minZoom: undefined, 3017 3018 // @option maxZoom: Number = * 3019 // Maximum zoom level of the map. 3020 // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 3021 // the highest of their `maxZoom` options will be used instead. 3022 maxZoom: undefined, 3023 3024 // @option layers: Layer[] = [] 3025 // Array of layers that will be added to the map initially 3026 layers: [], 3027 3028 // @option maxBounds: LatLngBounds = null 3029 // When this option is set, the map restricts the view to the given 3030 // geographical bounds, bouncing the user back if the user tries to pan 3031 // outside the view. To set the restriction dynamically, use 3032 // [`setMaxBounds`](#map-setmaxbounds) method. 3033 maxBounds: undefined, 3034 3035 // @option renderer: Renderer = * 3036 // The default method for drawing vector layers on the map. `L.SVG` 3037 // or `L.Canvas` by default depending on browser support. 3038 renderer: undefined, 3039 3040 3041 // @section Animation Options 3042 // @option zoomAnimation: Boolean = true 3043 // Whether the map zoom animation is enabled. By default it's enabled 3044 // in all browsers that support CSS3 Transitions except Android. 3045 zoomAnimation: true, 3046 3047 // @option zoomAnimationThreshold: Number = 4 3048 // Won't animate zoom if the zoom difference exceeds this value. 3049 zoomAnimationThreshold: 4, 3050 3051 // @option fadeAnimation: Boolean = true 3052 // Whether the tile fade animation is enabled. By default it's enabled 3053 // in all browsers that support CSS3 Transitions except Android. 3054 fadeAnimation: true, 3055 3056 // @option markerZoomAnimation: Boolean = true 3057 // Whether markers animate their zoom with the zoom animation, if disabled 3058 // they will disappear for the length of the animation. By default it's 3059 // enabled in all browsers that support CSS3 Transitions except Android. 3060 markerZoomAnimation: true, 3061 3062 // @option transform3DLimit: Number = 2^23 3063 // Defines the maximum size of a CSS translation transform. The default 3064 // value should not be changed unless a web browser positions layers in 3065 // the wrong place after doing a large `panBy`. 3066 transform3DLimit: 8388608, // Precision limit of a 32-bit float 3067 3068 // @section Interaction Options 3069 // @option zoomSnap: Number = 1 3070 // Forces the map's zoom level to always be a multiple of this, particularly 3071 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 3072 // By default, the zoom level snaps to the nearest integer; lower values 3073 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 3074 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 3075 zoomSnap: 1, 3076 3077 // @option zoomDelta: Number = 1 3078 // Controls how much the map's zoom level will change after a 3079 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 3080 // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 3081 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 3082 zoomDelta: 1, 3083 3084 // @option trackResize: Boolean = true 3085 // Whether the map automatically handles browser window resize to update itself. 3086 trackResize: true 3087 }, 3088 3089 initialize: function (id, options) { // (HTMLElement or String, Object) 3090 options = setOptions(this, options); 3091 3092 // Make sure to assign internal flags at the beginning, 3093 // to avoid inconsistent state in some edge cases. 3094 this._handlers = []; 3095 this._layers = {}; 3096 this._zoomBoundLayers = {}; 3097 this._sizeChanged = true; 3098 3099 this._initContainer(id); 3100 this._initLayout(); 3101 3102 // hack for https://github.com/Leaflet/Leaflet/issues/1980 3103 this._onResize = bind(this._onResize, this); 3104 3105 this._initEvents(); 3106 3107 if (options.maxBounds) { 3108 this.setMaxBounds(options.maxBounds); 3109 } 3110 3111 if (options.zoom !== undefined) { 3112 this._zoom = this._limitZoom(options.zoom); 3113 } 3114 3115 if (options.center && options.zoom !== undefined) { 3116 this.setView(toLatLng(options.center), options.zoom, {reset: true}); 3117 } 3118 3119 this.callInitHooks(); 3120 3121 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera 3122 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera && 3123 this.options.zoomAnimation; 3124 3125 // zoom transitions run with the same duration for all layers, so if one of transitionend events 3126 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally 3127 if (this._zoomAnimated) { 3128 this._createAnimProxy(); 3129 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 3130 } 3131 3132 this._addLayers(this.options.layers); 3133 }, 3134 3135 3136 // @section Methods for modifying map state 3137 3138 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 3139 // Sets the view of the map (geographical center and zoom) with the given 3140 // animation options. 3141 setView: function (center, zoom, options) { 3142 3143 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 3144 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); 3145 options = options || {}; 3146 3147 this._stop(); 3148 3149 if (this._loaded && !options.reset && options !== true) { 3150 3151 if (options.animate !== undefined) { 3152 options.zoom = extend({animate: options.animate}, options.zoom); 3153 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan); 3154 } 3155 3156 // try animating pan or zoom 3157 var moved = (this._zoom !== zoom) ? 3158 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) : 3159 this._tryAnimatedPan(center, options.pan); 3160 3161 if (moved) { 3162 // prevent resize handler call, the view will refresh after animation anyway 3163 clearTimeout(this._sizeTimer); 3164 return this; 3165 } 3166 } 3167 3168 // animation didn't start, just reset the map view 3169 this._resetView(center, zoom); 3170 3171 return this; 3172 }, 3173 3174 // @method setZoom(zoom: Number, options?: Zoom/pan options): this 3175 // Sets the zoom of the map. 3176 setZoom: function (zoom, options) { 3177 if (!this._loaded) { 3178 this._zoom = zoom; 3179 return this; 3180 } 3181 return this.setView(this.getCenter(), zoom, {zoom: options}); 3182 }, 3183 3184 // @method zoomIn(delta?: Number, options?: Zoom options): this 3185 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3186 zoomIn: function (delta, options) { 3187 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3188 return this.setZoom(this._zoom + delta, options); 3189 }, 3190 3191 // @method zoomOut(delta?: Number, options?: Zoom options): this 3192 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 3193 zoomOut: function (delta, options) { 3194 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 3195 return this.setZoom(this._zoom - delta, options); 3196 }, 3197 3198 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 3199 // Zooms the map while keeping a specified geographical point on the map 3200 // stationary (e.g. used internally for scroll zoom and double-click zoom). 3201 // @alternative 3202 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 3203 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 3204 setZoomAround: function (latlng, zoom, options) { 3205 var scale = this.getZoomScale(zoom), 3206 viewHalf = this.getSize().divideBy(2), 3207 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), 3208 3209 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), 3210 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 3211 3212 return this.setView(newCenter, zoom, {zoom: options}); 3213 }, 3214 3215 _getBoundsCenterZoom: function (bounds, options) { 3216 3217 options = options || {}; 3218 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 3219 3220 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3221 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3222 3223 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 3224 3225 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom; 3226 3227 if (zoom === Infinity) { 3228 return { 3229 center: bounds.getCenter(), 3230 zoom: zoom 3231 }; 3232 } 3233 3234 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), 3235 3236 swPoint = this.project(bounds.getSouthWest(), zoom), 3237 nePoint = this.project(bounds.getNorthEast(), zoom), 3238 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); 3239 3240 return { 3241 center: center, 3242 zoom: zoom 3243 }; 3244 }, 3245 3246 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 3247 // Sets a map view that contains the given geographical bounds with the 3248 // maximum zoom level possible. 3249 fitBounds: function (bounds, options) { 3250 3251 bounds = toLatLngBounds(bounds); 3252 3253 if (!bounds.isValid()) { 3254 throw new Error('Bounds are not valid.'); 3255 } 3256 3257 var target = this._getBoundsCenterZoom(bounds, options); 3258 return this.setView(target.center, target.zoom, options); 3259 }, 3260 3261 // @method fitWorld(options?: fitBounds options): this 3262 // Sets a map view that mostly contains the whole world with the maximum 3263 // zoom level possible. 3264 fitWorld: function (options) { 3265 return this.fitBounds([[-90, -180], [90, 180]], options); 3266 }, 3267 3268 // @method panTo(latlng: LatLng, options?: Pan options): this 3269 // Pans the map to a given center. 3270 panTo: function (center, options) { // (LatLng) 3271 return this.setView(center, this._zoom, {pan: options}); 3272 }, 3273 3274 // @method panBy(offset: Point, options?: Pan options): this 3275 // Pans the map by a given number of pixels (animated). 3276 panBy: function (offset, options) { 3277 offset = toPoint(offset).round(); 3278 options = options || {}; 3279 3280 if (!offset.x && !offset.y) { 3281 return this.fire('moveend'); 3282 } 3283 // If we pan too far, Chrome gets issues with tiles 3284 // and makes them disappear or appear in the wrong place (slightly offset) #2602 3285 if (options.animate !== true && !this.getSize().contains(offset)) { 3286 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 3287 return this; 3288 } 3289 3290 if (!this._panAnim) { 3291 this._panAnim = new PosAnimation(); 3292 3293 this._panAnim.on({ 3294 'step': this._onPanTransitionStep, 3295 'end': this._onPanTransitionEnd 3296 }, this); 3297 } 3298 3299 // don't fire movestart if animating inertia 3300 if (!options.noMoveStart) { 3301 this.fire('movestart'); 3302 } 3303 3304 // animate pan unless animate: false specified 3305 if (options.animate !== false) { 3306 addClass(this._mapPane, 'leaflet-pan-anim'); 3307 3308 var newPos = this._getMapPanePos().subtract(offset).round(); 3309 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 3310 } else { 3311 this._rawPanBy(offset); 3312 this.fire('move').fire('moveend'); 3313 } 3314 3315 return this; 3316 }, 3317 3318 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 3319 // Sets the view of the map (geographical center and zoom) performing a smooth 3320 // pan-zoom animation. 3321 flyTo: function (targetCenter, targetZoom, options) { 3322 3323 options = options || {}; 3324 if (options.animate === false || !Browser.any3d) { 3325 return this.setView(targetCenter, targetZoom, options); 3326 } 3327 3328 this._stop(); 3329 3330 var from = this.project(this.getCenter()), 3331 to = this.project(targetCenter), 3332 size = this.getSize(), 3333 startZoom = this._zoom; 3334 3335 targetCenter = toLatLng(targetCenter); 3336 targetZoom = targetZoom === undefined ? startZoom : targetZoom; 3337 3338 var w0 = Math.max(size.x, size.y), 3339 w1 = w0 * this.getZoomScale(startZoom, targetZoom), 3340 u1 = (to.distanceTo(from)) || 1, 3341 rho = 1.42, 3342 rho2 = rho * rho; 3343 3344 function r(i) { 3345 var s1 = i ? -1 : 1, 3346 s2 = i ? w1 : w0, 3347 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, 3348 b1 = 2 * s2 * rho2 * u1, 3349 b = t1 / b1, 3350 sq = Math.sqrt(b * b + 1) - b; 3351 3352 // workaround for floating point precision bug when sq = 0, log = -Infinite, 3353 // thus triggering an infinite loop in flyTo 3354 var log = sq < 0.000000001 ? -18 : Math.log(sq); 3355 3356 return log; 3357 } 3358 3359 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; } 3360 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; } 3361 function tanh(n) { return sinh(n) / cosh(n); } 3362 3363 var r0 = r(0); 3364 3365 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); } 3366 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; } 3367 3368 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); } 3369 3370 var start = Date.now(), 3371 S = (r(1) - r0) / rho, 3372 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8; 3373 3374 function frame() { 3375 var t = (Date.now() - start) / duration, 3376 s = easeOut(t) * S; 3377 3378 if (t <= 1) { 3379 this._flyToFrame = requestAnimFrame(frame, this); 3380 3381 this._move( 3382 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 3383 this.getScaleZoom(w0 / w(s), startZoom), 3384 {flyTo: true}); 3385 3386 } else { 3387 this 3388 ._move(targetCenter, targetZoom) 3389 ._moveEnd(true); 3390 } 3391 } 3392 3393 this._moveStart(true, options.noMoveStart); 3394 3395 frame.call(this); 3396 return this; 3397 }, 3398 3399 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 3400 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 3401 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 3402 flyToBounds: function (bounds, options) { 3403 var target = this._getBoundsCenterZoom(bounds, options); 3404 return this.flyTo(target.center, target.zoom, options); 3405 }, 3406 3407 // @method setMaxBounds(bounds: LatLngBounds): this 3408 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 3409 setMaxBounds: function (bounds) { 3410 bounds = toLatLngBounds(bounds); 3411 3412 if (!bounds.isValid()) { 3413 this.options.maxBounds = null; 3414 return this.off('moveend', this._panInsideMaxBounds); 3415 } else if (this.options.maxBounds) { 3416 this.off('moveend', this._panInsideMaxBounds); 3417 } 3418 3419 this.options.maxBounds = bounds; 3420 3421 if (this._loaded) { 3422 this._panInsideMaxBounds(); 3423 } 3424 3425 return this.on('moveend', this._panInsideMaxBounds); 3426 }, 3427 3428 // @method setMinZoom(zoom: Number): this 3429 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 3430 setMinZoom: function (zoom) { 3431 var oldZoom = this.options.minZoom; 3432 this.options.minZoom = zoom; 3433 3434 if (this._loaded && oldZoom !== zoom) { 3435 this.fire('zoomlevelschange'); 3436 3437 if (this.getZoom() < this.options.minZoom) { 3438 return this.setZoom(zoom); 3439 } 3440 } 3441 3442 return this; 3443 }, 3444 3445 // @method setMaxZoom(zoom: Number): this 3446 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 3447 setMaxZoom: function (zoom) { 3448 var oldZoom = this.options.maxZoom; 3449 this.options.maxZoom = zoom; 3450 3451 if (this._loaded && oldZoom !== zoom) { 3452 this.fire('zoomlevelschange'); 3453 3454 if (this.getZoom() > this.options.maxZoom) { 3455 return this.setZoom(zoom); 3456 } 3457 } 3458 3459 return this; 3460 }, 3461 3462 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 3463 // 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. 3464 panInsideBounds: function (bounds, options) { 3465 this._enforcingBounds = true; 3466 var center = this.getCenter(), 3467 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 3468 3469 if (!center.equals(newCenter)) { 3470 this.panTo(newCenter, options); 3471 } 3472 3473 this._enforcingBounds = false; 3474 return this; 3475 }, 3476 3477 // @method panInside(latlng: LatLng, options?: padding options): this 3478 // Pans the map the minimum amount to make the `latlng` visible. Use 3479 // padding options to fit the display to more restricted bounds. 3480 // If `latlng` is already within the (optionally padded) display bounds, 3481 // the map will not be panned. 3482 panInside: function (latlng, options) { 3483 options = options || {}; 3484 3485 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), 3486 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), 3487 pixelCenter = this.project(this.getCenter()), 3488 pixelPoint = this.project(latlng), 3489 pixelBounds = this.getPixelBounds(), 3490 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]), 3491 paddedSize = paddedBounds.getSize(); 3492 3493 if (!paddedBounds.contains(pixelPoint)) { 3494 this._enforcingBounds = true; 3495 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter()); 3496 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize); 3497 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x; 3498 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y; 3499 this.panTo(this.unproject(pixelCenter), options); 3500 this._enforcingBounds = false; 3501 } 3502 return this; 3503 }, 3504 3505 // @method invalidateSize(options: Zoom/pan options): this 3506 // Checks if the map container size changed and updates the map if so ââ¬â 3507 // call it after you've changed the map size dynamically, also animating 3508 // pan by default. If `options.pan` is `false`, panning will not occur. 3509 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 3510 // that it doesn't happen often even if the method is called many 3511 // times in a row. 3512 3513 // @alternative 3514 // @method invalidateSize(animate: Boolean): this 3515 // Checks if the map container size changed and updates the map if so ââ¬â 3516 // call it after you've changed the map size dynamically, also animating 3517 // pan by default. 3518 invalidateSize: function (options) { 3519 if (!this._loaded) { return this; } 3520 3521 options = extend({ 3522 animate: false, 3523 pan: true 3524 }, options === true ? {animate: true} : options); 3525 3526 var oldSize = this.getSize(); 3527 this._sizeChanged = true; 3528 this._lastCenter = null; 3529 3530 var newSize = this.getSize(), 3531 oldCenter = oldSize.divideBy(2).round(), 3532 newCenter = newSize.divideBy(2).round(), 3533 offset = oldCenter.subtract(newCenter); 3534 3535 if (!offset.x && !offset.y) { return this; } 3536 3537 if (options.animate && options.pan) { 3538 this.panBy(offset); 3539 3540 } else { 3541 if (options.pan) { 3542 this._rawPanBy(offset); 3543 } 3544 3545 this.fire('move'); 3546 3547 if (options.debounceMoveend) { 3548 clearTimeout(this._sizeTimer); 3549 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200); 3550 } else { 3551 this.fire('moveend'); 3552 } 3553 } 3554 3555 // @section Map state change events 3556 // @event resize: ResizeEvent 3557 // Fired when the map is resized. 3558 return this.fire('resize', { 3559 oldSize: oldSize, 3560 newSize: newSize 3561 }); 3562 }, 3563 3564 // @section Methods for modifying map state 3565 // @method stop(): this 3566 // Stops the currently running `panTo` or `flyTo` animation, if any. 3567 stop: function () { 3568 this.setZoom(this._limitZoom(this._zoom)); 3569 if (!this.options.zoomSnap) { 3570 this.fire('viewreset'); 3571 } 3572 return this._stop(); 3573 }, 3574 3575 // @section Geolocation methods 3576 // @method locate(options?: Locate options): this 3577 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 3578 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 3579 // and optionally sets the map view to the user's location with respect to 3580 // detection accuracy (or to the world view if geolocation failed). 3581 // Note that, if your page doesn't use HTTPS, this method will fail in 3582 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 3583 // See `Locate options` for more details. 3584 locate: function (options) { 3585 3586 options = this._locateOptions = extend({ 3587 timeout: 10000, 3588 watch: false 3589 // setView: false 3590 // maxZoom: <Number> 3591 // maximumAge: 0 3592 // enableHighAccuracy: false 3593 }, options); 3594 3595 if (!('geolocation' in navigator)) { 3596 this._handleGeolocationError({ 3597 code: 0, 3598 message: 'Geolocation not supported.' 3599 }); 3600 return this; 3601 } 3602 3603 var onResponse = bind(this._handleGeolocationResponse, this), 3604 onError = bind(this._handleGeolocationError, this); 3605 3606 if (options.watch) { 3607 this._locationWatchId = 3608 navigator.geolocation.watchPosition(onResponse, onError, options); 3609 } else { 3610 navigator.geolocation.getCurrentPosition(onResponse, onError, options); 3611 } 3612 return this; 3613 }, 3614 3615 // @method stopLocate(): this 3616 // Stops watching location previously initiated by `map.locate({watch: true})` 3617 // and aborts resetting the map view if map.locate was called with 3618 // `{setView: true}`. 3619 stopLocate: function () { 3620 if (navigator.geolocation && navigator.geolocation.clearWatch) { 3621 navigator.geolocation.clearWatch(this._locationWatchId); 3622 } 3623 if (this._locateOptions) { 3624 this._locateOptions.setView = false; 3625 } 3626 return this; 3627 }, 3628 3629 _handleGeolocationError: function (error) { 3630 if (!this._container._leaflet_id) { return; } 3631 3632 var c = error.code, 3633 message = error.message || 3634 (c === 1 ? 'permission denied' : 3635 (c === 2 ? 'position unavailable' : 'timeout')); 3636 3637 if (this._locateOptions.setView && !this._loaded) { 3638 this.fitWorld(); 3639 } 3640 3641 // @section Location events 3642 // @event locationerror: ErrorEvent 3643 // Fired when geolocation (using the [`locate`](#map-locate) method) failed. 3644 this.fire('locationerror', { 3645 code: c, 3646 message: 'Geolocation error: ' + message + '.' 3647 }); 3648 }, 3649 3650 _handleGeolocationResponse: function (pos) { 3651 if (!this._container._leaflet_id) { return; } 3652 3653 var lat = pos.coords.latitude, 3654 lng = pos.coords.longitude, 3655 latlng = new LatLng(lat, lng), 3656 bounds = latlng.toBounds(pos.coords.accuracy * 2), 3657 options = this._locateOptions; 3658 3659 if (options.setView) { 3660 var zoom = this.getBoundsZoom(bounds); 3661 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom); 3662 } 3663 3664 var data = { 3665 latlng: latlng, 3666 bounds: bounds, 3667 timestamp: pos.timestamp 3668 }; 3669 3670 for (var i in pos.coords) { 3671 if (typeof pos.coords[i] === 'number') { 3672 data[i] = pos.coords[i]; 3673 } 3674 } 3675 3676 // @event locationfound: LocationEvent 3677 // Fired when geolocation (using the [`locate`](#map-locate) method) 3678 // went successfully. 3679 this.fire('locationfound', data); 3680 }, 3681 3682 // TODO Appropriate docs section? 3683 // @section Other Methods 3684 // @method addHandler(name: String, HandlerClass: Function): this 3685 // Adds a new `Handler` to the map, given its name and constructor function. 3686 addHandler: function (name, HandlerClass) { 3687 if (!HandlerClass) { return this; } 3688 3689 var handler = this[name] = new HandlerClass(this); 3690 3691 this._handlers.push(handler); 3692 3693 if (this.options[name]) { 3694 handler.enable(); 3695 } 3696 3697 return this; 3698 }, 3699 3700 // @method remove(): this 3701 // Destroys the map and clears all related event listeners. 3702 remove: function () { 3703 3704 this._initEvents(true); 3705 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); } 3706 3707 if (this._containerId !== this._container._leaflet_id) { 3708 throw new Error('Map container is being reused by another instance'); 3709 } 3710 3711 try { 3712 // throws error in IE6-8 3713 delete this._container._leaflet_id; 3714 delete this._containerId; 3715 } catch (e) { 3716 /*eslint-disable */ 3717 this._container._leaflet_id = undefined; 3718 /* eslint-enable */ 3719 this._containerId = undefined; 3720 } 3721 3722 if (this._locationWatchId !== undefined) { 3723 this.stopLocate(); 3724 } 3725 3726 this._stop(); 3727 3728 remove(this._mapPane); 3729 3730 if (this._clearControlPos) { 3731 this._clearControlPos(); 3732 } 3733 if (this._resizeRequest) { 3734 cancelAnimFrame(this._resizeRequest); 3735 this._resizeRequest = null; 3736 } 3737 3738 this._clearHandlers(); 3739 3740 if (this._loaded) { 3741 // @section Map state change events 3742 // @event unload: Event 3743 // Fired when the map is destroyed with [remove](#map-remove) method. 3744 this.fire('unload'); 3745 } 3746 3747 var i; 3748 for (i in this._layers) { 3749 this._layers[i].remove(); 3750 } 3751 for (i in this._panes) { 3752 remove(this._panes[i]); 3753 } 3754 3755 this._layers = []; 3756 this._panes = []; 3757 delete this._mapPane; 3758 delete this._renderer; 3759 3760 return this; 3761 }, 3762 3763 // @section Other Methods 3764 // @method createPane(name: String, container?: HTMLElement): HTMLElement 3765 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 3766 // then returns it. The pane is created as a child of `container`, or 3767 // as a child of the main map pane if not set. 3768 createPane: function (name, container) { 3769 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 3770 pane = create$1('div', className, container || this._mapPane); 3771 3772 if (name) { 3773 this._panes[name] = pane; 3774 } 3775 return pane; 3776 }, 3777 3778 // @section Methods for Getting Map State 3779 3780 // @method getCenter(): LatLng 3781 // Returns the geographical center of the map view 3782 getCenter: function () { 3783 this._checkIfLoaded(); 3784 3785 if (this._lastCenter && !this._moved()) { 3786 return this._lastCenter; 3787 } 3788 return this.layerPointToLatLng(this._getCenterLayerPoint()); 3789 }, 3790 3791 // @method getZoom(): Number 3792 // Returns the current zoom level of the map view 3793 getZoom: function () { 3794 return this._zoom; 3795 }, 3796 3797 // @method getBounds(): LatLngBounds 3798 // Returns the geographical bounds visible in the current map view 3799 getBounds: function () { 3800 var bounds = this.getPixelBounds(), 3801 sw = this.unproject(bounds.getBottomLeft()), 3802 ne = this.unproject(bounds.getTopRight()); 3803 3804 return new LatLngBounds(sw, ne); 3805 }, 3806 3807 // @method getMinZoom(): Number 3808 // 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. 3809 getMinZoom: function () { 3810 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; 3811 }, 3812 3813 // @method getMaxZoom(): Number 3814 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 3815 getMaxZoom: function () { 3816 return this.options.maxZoom === undefined ? 3817 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : 3818 this.options.maxZoom; 3819 }, 3820 3821 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 3822 // Returns the maximum zoom level on which the given bounds fit to the map 3823 // view in its entirety. If `inside` (optional) is set to `true`, the method 3824 // instead returns the minimum zoom level on which the map view fits into 3825 // the given bounds in its entirety. 3826 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number 3827 bounds = toLatLngBounds(bounds); 3828 padding = toPoint(padding || [0, 0]); 3829 3830 var zoom = this.getZoom() || 0, 3831 min = this.getMinZoom(), 3832 max = this.getMaxZoom(), 3833 nw = bounds.getNorthWest(), 3834 se = bounds.getSouthEast(), 3835 size = this.getSize().subtract(padding), 3836 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), 3837 snap = Browser.any3d ? this.options.zoomSnap : 1, 3838 scalex = size.x / boundsSize.x, 3839 scaley = size.y / boundsSize.y, 3840 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 3841 3842 zoom = this.getScaleZoom(scale, zoom); 3843 3844 if (snap) { 3845 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level 3846 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; 3847 } 3848 3849 return Math.max(min, Math.min(max, zoom)); 3850 }, 3851 3852 // @method getSize(): Point 3853 // Returns the current size of the map container (in pixels). 3854 getSize: function () { 3855 if (!this._size || this._sizeChanged) { 3856 this._size = new Point( 3857 this._container.clientWidth || 0, 3858 this._container.clientHeight || 0); 3859 3860 this._sizeChanged = false; 3861 } 3862 return this._size.clone(); 3863 }, 3864 3865 // @method getPixelBounds(): Bounds 3866 // Returns the bounds of the current map view in projected pixel 3867 // coordinates (sometimes useful in layer and overlay implementations). 3868 getPixelBounds: function (center, zoom) { 3869 var topLeftPoint = this._getTopLeftPoint(center, zoom); 3870 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 3871 }, 3872 3873 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 3874 // the map pane? "left point of the map layer" can be confusing, specially 3875 // since there can be negative offsets. 3876 // @method getPixelOrigin(): Point 3877 // Returns the projected pixel coordinates of the top left point of 3878 // the map layer (useful in custom layer and overlay implementations). 3879 getPixelOrigin: function () { 3880 this._checkIfLoaded(); 3881 return this._pixelOrigin; 3882 }, 3883 3884 // @method getPixelWorldBounds(zoom?: Number): Bounds 3885 // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 3886 // If `zoom` is omitted, the map's current zoom level is used. 3887 getPixelWorldBounds: function (zoom) { 3888 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); 3889 }, 3890 3891 // @section Other Methods 3892 3893 // @method getPane(pane: String|HTMLElement): HTMLElement 3894 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 3895 getPane: function (pane) { 3896 return typeof pane === 'string' ? this._panes[pane] : pane; 3897 }, 3898 3899 // @method getPanes(): Object 3900 // Returns a plain object containing the names of all [panes](#map-pane) as keys and 3901 // the panes as values. 3902 getPanes: function () { 3903 return this._panes; 3904 }, 3905 3906 // @method getContainer: HTMLElement 3907 // Returns the HTML element that contains the map. 3908 getContainer: function () { 3909 return this._container; 3910 }, 3911 3912 3913 // @section Conversion Methods 3914 3915 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 3916 // Returns the scale factor to be applied to a map transition from zoom level 3917 // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 3918 getZoomScale: function (toZoom, fromZoom) { 3919 // TODO replace with universal implementation after refactoring projections 3920 var crs = this.options.crs; 3921 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3922 return crs.scale(toZoom) / crs.scale(fromZoom); 3923 }, 3924 3925 // @method getScaleZoom(scale: Number, fromZoom: Number): Number 3926 // Returns the zoom level that the map would end up at, if it is at `fromZoom` 3927 // level and everything is scaled by a factor of `scale`. Inverse of 3928 // [`getZoomScale`](#map-getZoomScale). 3929 getScaleZoom: function (scale, fromZoom) { 3930 var crs = this.options.crs; 3931 fromZoom = fromZoom === undefined ? this._zoom : fromZoom; 3932 var zoom = crs.zoom(scale * crs.scale(fromZoom)); 3933 return isNaN(zoom) ? Infinity : zoom; 3934 }, 3935 3936 // @method project(latlng: LatLng, zoom: Number): Point 3937 // Projects a geographical coordinate `LatLng` according to the projection 3938 // of the map's CRS, then scales it according to `zoom` and the CRS's 3939 // `Transformation`. The result is pixel coordinate relative to 3940 // the CRS origin. 3941 project: function (latlng, zoom) { 3942 zoom = zoom === undefined ? this._zoom : zoom; 3943 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); 3944 }, 3945 3946 // @method unproject(point: Point, zoom: Number): LatLng 3947 // Inverse of [`project`](#map-project). 3948 unproject: function (point, zoom) { 3949 zoom = zoom === undefined ? this._zoom : zoom; 3950 return this.options.crs.pointToLatLng(toPoint(point), zoom); 3951 }, 3952 3953 // @method layerPointToLatLng(point: Point): LatLng 3954 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 3955 // returns the corresponding geographical coordinate (for the current zoom level). 3956 layerPointToLatLng: function (point) { 3957 var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 3958 return this.unproject(projectedPoint); 3959 }, 3960 3961 // @method latLngToLayerPoint(latlng: LatLng): Point 3962 // Given a geographical coordinate, returns the corresponding pixel coordinate 3963 // relative to the [origin pixel](#map-getpixelorigin). 3964 latLngToLayerPoint: function (latlng) { 3965 var projectedPoint = this.project(toLatLng(latlng))._round(); 3966 return projectedPoint._subtract(this.getPixelOrigin()); 3967 }, 3968 3969 // @method wrapLatLng(latlng: LatLng): LatLng 3970 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 3971 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 3972 // CRS's bounds. 3973 // By default this means longitude is wrapped around the dateline so its 3974 // value is between -180 and +180 degrees. 3975 wrapLatLng: function (latlng) { 3976 return this.options.crs.wrapLatLng(toLatLng(latlng)); 3977 }, 3978 3979 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 3980 // Returns a `LatLngBounds` with the same size as the given one, ensuring that 3981 // its center is within the CRS's bounds. 3982 // By default this means the center longitude is wrapped around the dateline so its 3983 // value is between -180 and +180 degrees, and the majority of the bounds 3984 // overlaps the CRS's bounds. 3985 wrapLatLngBounds: function (latlng) { 3986 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 3987 }, 3988 3989 // @method distance(latlng1: LatLng, latlng2: LatLng): Number 3990 // Returns the distance between two geographical coordinates according to 3991 // the map's CRS. By default this measures distance in meters. 3992 distance: function (latlng1, latlng2) { 3993 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 3994 }, 3995 3996 // @method containerPointToLayerPoint(point: Point): Point 3997 // Given a pixel coordinate relative to the map container, returns the corresponding 3998 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 3999 containerPointToLayerPoint: function (point) { // (Point) 4000 return toPoint(point).subtract(this._getMapPanePos()); 4001 }, 4002 4003 // @method layerPointToContainerPoint(point: Point): Point 4004 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 4005 // returns the corresponding pixel coordinate relative to the map container. 4006 layerPointToContainerPoint: function (point) { // (Point) 4007 return toPoint(point).add(this._getMapPanePos()); 4008 }, 4009 4010 // @method containerPointToLatLng(point: Point): LatLng 4011 // Given a pixel coordinate relative to the map container, returns 4012 // the corresponding geographical coordinate (for the current zoom level). 4013 containerPointToLatLng: function (point) { 4014 var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 4015 return this.layerPointToLatLng(layerPoint); 4016 }, 4017 4018 // @method latLngToContainerPoint(latlng: LatLng): Point 4019 // Given a geographical coordinate, returns the corresponding pixel coordinate 4020 // relative to the map container. 4021 latLngToContainerPoint: function (latlng) { 4022 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 4023 }, 4024 4025 // @method mouseEventToContainerPoint(ev: MouseEvent): Point 4026 // Given a MouseEvent object, returns the pixel coordinate relative to the 4027 // map container where the event took place. 4028 mouseEventToContainerPoint: function (e) { 4029 return getMousePosition(e, this._container); 4030 }, 4031 4032 // @method mouseEventToLayerPoint(ev: MouseEvent): Point 4033 // Given a MouseEvent object, returns the pixel coordinate relative to 4034 // the [origin pixel](#map-getpixelorigin) where the event took place. 4035 mouseEventToLayerPoint: function (e) { 4036 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 4037 }, 4038 4039 // @method mouseEventToLatLng(ev: MouseEvent): LatLng 4040 // Given a MouseEvent object, returns geographical coordinate where the 4041 // event took place. 4042 mouseEventToLatLng: function (e) { // (MouseEvent) 4043 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 4044 }, 4045 4046 4047 // map initialization methods 4048 4049 _initContainer: function (id) { 4050 var container = this._container = get(id); 4051 4052 if (!container) { 4053 throw new Error('Map container not found.'); 4054 } else if (container._leaflet_id) { 4055 throw new Error('Map container is already initialized.'); 4056 } 4057 4058 on(container, 'scroll', this._onScroll, this); 4059 this._containerId = stamp(container); 4060 }, 4061 4062 _initLayout: function () { 4063 var container = this._container; 4064 4065 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d; 4066 4067 addClass(container, 'leaflet-container' + 4068 (Browser.touch ? ' leaflet-touch' : '') + 4069 (Browser.retina ? ' leaflet-retina' : '') + 4070 (Browser.ielt9 ? ' leaflet-oldie' : '') + 4071 (Browser.safari ? ' leaflet-safari' : '') + 4072 (this._fadeAnimated ? ' leaflet-fade-anim' : '')); 4073 4074 var position = getStyle(container, 'position'); 4075 4076 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { 4077 container.style.position = 'relative'; 4078 } 4079 4080 this._initPanes(); 4081 4082 if (this._initControlPos) { 4083 this._initControlPos(); 4084 } 4085 }, 4086 4087 _initPanes: function () { 4088 var panes = this._panes = {}; 4089 this._paneRenderers = {}; 4090 4091 // @section 4092 // 4093 // Panes are DOM elements used to control the ordering of layers on the map. You 4094 // can access panes with [`map.getPane`](#map-getpane) or 4095 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the 4096 // [`map.createPane`](#map-createpane) method. 4097 // 4098 // Every map has the following default panes that differ only in zIndex. 4099 // 4100 // @pane mapPane: HTMLElement = 'auto' 4101 // Pane that contains all other map panes 4102 4103 this._mapPane = this.createPane('mapPane', this._container); 4104 setPosition(this._mapPane, new Point(0, 0)); 4105 4106 // @pane tilePane: HTMLElement = 200 4107 // Pane for `GridLayer`s and `TileLayer`s 4108 this.createPane('tilePane'); 4109 // @pane overlayPane: HTMLElement = 400 4110 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s 4111 this.createPane('overlayPane'); 4112 // @pane shadowPane: HTMLElement = 500 4113 // Pane for overlay shadows (e.g. `Marker` shadows) 4114 this.createPane('shadowPane'); 4115 // @pane markerPane: HTMLElement = 600 4116 // Pane for `Icon`s of `Marker`s 4117 this.createPane('markerPane'); 4118 // @pane tooltipPane: HTMLElement = 650 4119 // Pane for `Tooltip`s. 4120 this.createPane('tooltipPane'); 4121 // @pane popupPane: HTMLElement = 700 4122 // Pane for `Popup`s. 4123 this.createPane('popupPane'); 4124 4125 if (!this.options.markerZoomAnimation) { 4126 addClass(panes.markerPane, 'leaflet-zoom-hide'); 4127 addClass(panes.shadowPane, 'leaflet-zoom-hide'); 4128 } 4129 }, 4130 4131 4132 // private methods that modify map state 4133 4134 // @section Map state change events 4135 _resetView: function (center, zoom) { 4136 setPosition(this._mapPane, new Point(0, 0)); 4137 4138 var loading = !this._loaded; 4139 this._loaded = true; 4140 zoom = this._limitZoom(zoom); 4141 4142 this.fire('viewprereset'); 4143 4144 var zoomChanged = this._zoom !== zoom; 4145 this 4146 ._moveStart(zoomChanged, false) 4147 ._move(center, zoom) 4148 ._moveEnd(zoomChanged); 4149 4150 // @event viewreset: Event 4151 // Fired when the map needs to redraw its content (this usually happens 4152 // on map zoom or load). Very useful for creating custom overlays. 4153 this.fire('viewreset'); 4154 4155 // @event load: Event 4156 // Fired when the map is initialized (when its center and zoom are set 4157 // for the first time). 4158 if (loading) { 4159 this.fire('load'); 4160 } 4161 }, 4162 4163 _moveStart: function (zoomChanged, noMoveStart) { 4164 // @event zoomstart: Event 4165 // Fired when the map zoom is about to change (e.g. before zoom animation). 4166 // @event movestart: Event 4167 // Fired when the view of the map starts changing (e.g. user starts dragging the map). 4168 if (zoomChanged) { 4169 this.fire('zoomstart'); 4170 } 4171 if (!noMoveStart) { 4172 this.fire('movestart'); 4173 } 4174 return this; 4175 }, 4176 4177 _move: function (center, zoom, data, supressEvent) { 4178 if (zoom === undefined) { 4179 zoom = this._zoom; 4180 } 4181 var zoomChanged = this._zoom !== zoom; 4182 4183 this._zoom = zoom; 4184 this._lastCenter = center; 4185 this._pixelOrigin = this._getNewPixelOrigin(center); 4186 4187 if (!supressEvent) { 4188 // @event zoom: Event 4189 // Fired repeatedly during any change in zoom level, 4190 // including zoom and fly animations. 4191 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 4192 this.fire('zoom', data); 4193 } 4194 4195 // @event move: Event 4196 // Fired repeatedly during any movement of the map, 4197 // including pan and fly animations. 4198 this.fire('move', data); 4199 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530 4200 this.fire('zoom', data); 4201 } 4202 return this; 4203 }, 4204 4205 _moveEnd: function (zoomChanged) { 4206 // @event zoomend: Event 4207 // Fired when the map zoom changed, after any animations. 4208 if (zoomChanged) { 4209 this.fire('zoomend'); 4210 } 4211 4212 // @event moveend: Event 4213 // Fired when the center of the map stops changing 4214 // (e.g. user stopped dragging the map or after non-centered zoom). 4215 return this.fire('moveend'); 4216 }, 4217 4218 _stop: function () { 4219 cancelAnimFrame(this._flyToFrame); 4220 if (this._panAnim) { 4221 this._panAnim.stop(); 4222 } 4223 return this; 4224 }, 4225 4226 _rawPanBy: function (offset) { 4227 setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 4228 }, 4229 4230 _getZoomSpan: function () { 4231 return this.getMaxZoom() - this.getMinZoom(); 4232 }, 4233 4234 _panInsideMaxBounds: function () { 4235 if (!this._enforcingBounds) { 4236 this.panInsideBounds(this.options.maxBounds); 4237 } 4238 }, 4239 4240 _checkIfLoaded: function () { 4241 if (!this._loaded) { 4242 throw new Error('Set map center and zoom first.'); 4243 } 4244 }, 4245 4246 // DOM event handling 4247 4248 // @section Interaction events 4249 _initEvents: function (remove) { 4250 this._targets = {}; 4251 this._targets[stamp(this._container)] = this; 4252 4253 var onOff = remove ? off : on; 4254 4255 // @event click: MouseEvent 4256 // Fired when the user clicks (or taps) the map. 4257 // @event dblclick: MouseEvent 4258 // Fired when the user double-clicks (or double-taps) the map. 4259 // @event mousedown: MouseEvent 4260 // Fired when the user pushes the mouse button on the map. 4261 // @event mouseup: MouseEvent 4262 // Fired when the user releases the mouse button on the map. 4263 // @event mouseover: MouseEvent 4264 // Fired when the mouse enters the map. 4265 // @event mouseout: MouseEvent 4266 // Fired when the mouse leaves the map. 4267 // @event mousemove: MouseEvent 4268 // Fired while the mouse moves over the map. 4269 // @event contextmenu: MouseEvent 4270 // Fired when the user pushes the right mouse button on the map, prevents 4271 // default browser context menu from showing if there are listeners on 4272 // this event. Also fired on mobile when the user holds a single touch 4273 // for a second (also called long press). 4274 // @event keypress: KeyboardEvent 4275 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused. 4276 // @event keydown: KeyboardEvent 4277 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event, 4278 // the `keydown` event is fired for keys that produce a character value and for keys 4279 // that do not produce a character value. 4280 // @event keyup: KeyboardEvent 4281 // Fired when the user releases a key from the keyboard while the map is focused. 4282 onOff(this._container, 'click dblclick mousedown mouseup ' + 4283 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this); 4284 4285 if (this.options.trackResize) { 4286 onOff(window, 'resize', this._onResize, this); 4287 } 4288 4289 if (Browser.any3d && this.options.transform3DLimit) { 4290 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); 4291 } 4292 }, 4293 4294 _onResize: function () { 4295 cancelAnimFrame(this._resizeRequest); 4296 this._resizeRequest = requestAnimFrame( 4297 function () { this.invalidateSize({debounceMoveend: true}); }, this); 4298 }, 4299 4300 _onScroll: function () { 4301 this._container.scrollTop = 0; 4302 this._container.scrollLeft = 0; 4303 }, 4304 4305 _onMoveEnd: function () { 4306 var pos = this._getMapPanePos(); 4307 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 4308 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have 4309 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/ 4310 this._resetView(this.getCenter(), this.getZoom()); 4311 } 4312 }, 4313 4314 _findEventTargets: function (e, type) { 4315 var targets = [], 4316 target, 4317 isHover = type === 'mouseout' || type === 'mouseover', 4318 src = e.target || e.srcElement, 4319 dragging = false; 4320 4321 while (src) { 4322 target = this._targets[stamp(src)]; 4323 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) { 4324 // Prevent firing click after you just dragged an object. 4325 dragging = true; 4326 break; 4327 } 4328 if (target && target.listens(type, true)) { 4329 if (isHover && !isExternalTarget(src, e)) { break; } 4330 targets.push(target); 4331 if (isHover) { break; } 4332 } 4333 if (src === this._container) { break; } 4334 src = src.parentNode; 4335 } 4336 if (!targets.length && !dragging && !isHover && this.listens(type, true)) { 4337 targets = [this]; 4338 } 4339 return targets; 4340 }, 4341 4342 _isClickDisabled: function (el) { 4343 while (el !== this._container) { 4344 if (el['_leaflet_disable_click']) { return true; } 4345 el = el.parentNode; 4346 } 4347 }, 4348 4349 _handleDOMEvent: function (e) { 4350 var el = (e.target || e.srcElement); 4351 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) { 4352 return; 4353 } 4354 4355 var type = e.type; 4356 4357 if (type === 'mousedown') { 4358 // prevents outline when clicking on keyboard-focusable element 4359 preventOutline(el); 4360 } 4361 4362 this._fireDOMEvent(e, type); 4363 }, 4364 4365 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'], 4366 4367 _fireDOMEvent: function (e, type, canvasTargets) { 4368 4369 if (e.type === 'click') { 4370 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). 4371 // @event preclick: MouseEvent 4372 // Fired before mouse click on the map (sometimes useful when you 4373 // want something to happen on click before any existing click 4374 // handlers start running). 4375 var synth = extend({}, e); 4376 synth.type = 'preclick'; 4377 this._fireDOMEvent(synth, synth.type, canvasTargets); 4378 } 4379 4380 // Find the layer the event is propagating from and its parents. 4381 var targets = this._findEventTargets(e, type); 4382 4383 if (canvasTargets) { 4384 var filtered = []; // pick only targets with listeners 4385 for (var i = 0; i < canvasTargets.length; i++) { 4386 if (canvasTargets[i].listens(type, true)) { 4387 filtered.push(canvasTargets[i]); 4388 } 4389 } 4390 targets = filtered.concat(targets); 4391 } 4392 4393 if (!targets.length) { return; } 4394 4395 if (type === 'contextmenu') { 4396 preventDefault(e); 4397 } 4398 4399 var target = targets[0]; 4400 var data = { 4401 originalEvent: e 4402 }; 4403 4404 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') { 4405 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 4406 data.containerPoint = isMarker ? 4407 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 4408 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 4409 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 4410 } 4411 4412 for (i = 0; i < targets.length; i++) { 4413 targets[i].fire(type, data, true); 4414 if (data.originalEvent._stopped || 4415 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; } 4416 } 4417 }, 4418 4419 _draggableMoved: function (obj) { 4420 obj = obj.dragging && obj.dragging.enabled() ? obj : this; 4421 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); 4422 }, 4423 4424 _clearHandlers: function () { 4425 for (var i = 0, len = this._handlers.length; i < len; i++) { 4426 this._handlers[i].disable(); 4427 } 4428 }, 4429 4430 // @section Other Methods 4431 4432 // @method whenReady(fn: Function, context?: Object): this 4433 // Runs the given function `fn` when the map gets initialized with 4434 // a view (center and zoom) and at least one layer, or immediately 4435 // if it's already initialized, optionally passing a function context. 4436 whenReady: function (callback, context) { 4437 if (this._loaded) { 4438 callback.call(context || this, {target: this}); 4439 } else { 4440 this.on('load', callback, context); 4441 } 4442 return this; 4443 }, 4444 4445 4446 // private methods for getting map state 4447 4448 _getMapPanePos: function () { 4449 return getPosition(this._mapPane) || new Point(0, 0); 4450 }, 4451 4452 _moved: function () { 4453 var pos = this._getMapPanePos(); 4454 return pos && !pos.equals([0, 0]); 4455 }, 4456 4457 _getTopLeftPoint: function (center, zoom) { 4458 var pixelOrigin = center && zoom !== undefined ? 4459 this._getNewPixelOrigin(center, zoom) : 4460 this.getPixelOrigin(); 4461 return pixelOrigin.subtract(this._getMapPanePos()); 4462 }, 4463 4464 _getNewPixelOrigin: function (center, zoom) { 4465 var viewHalf = this.getSize()._divideBy(2); 4466 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 4467 }, 4468 4469 _latLngToNewLayerPoint: function (latlng, zoom, center) { 4470 var topLeft = this._getNewPixelOrigin(center, zoom); 4471 return this.project(latlng, zoom)._subtract(topLeft); 4472 }, 4473 4474 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { 4475 var topLeft = this._getNewPixelOrigin(center, zoom); 4476 return toBounds([ 4477 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), 4478 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), 4479 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), 4480 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft) 4481 ]); 4482 }, 4483 4484 // layer point of the current center 4485 _getCenterLayerPoint: function () { 4486 return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 4487 }, 4488 4489 // offset of the specified place to the current center in pixels 4490 _getCenterOffset: function (latlng) { 4491 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 4492 }, 4493 4494 // adjust center for view to get inside bounds 4495 _limitCenter: function (center, zoom, bounds) { 4496 4497 if (!bounds) { return center; } 4498 4499 var centerPoint = this.project(center, zoom), 4500 viewHalf = this.getSize().divideBy(2),
4501 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), 4502 offset = this._getBoundsOffset(viewBounds, bounds, zoom); 4503 4504 // If offset is less than a pixel, ignore. 4505 // This prevents unstable projections from getting into 4506 // an infinite loop of tiny offsets. 4507 if (offset.round().equals([0, 0])) { 4508 return center; 4509 } 4510 4511 return this.unproject(centerPoint.add(offset), zoom); 4512 }, 4513 4514 // adjust offset for view to get inside bounds 4515 _limitOffset: function (offset, bounds) { 4516 if (!bounds) { return offset; } 4517 4518 var viewBounds = this.getPixelBounds(), 4519 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 4520 4521 return offset.add(this._getBoundsOffset(newBounds, bounds)); 4522 }, 4523 4524 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 4525 _getBoundsOffset: function (pxBounds, maxBounds, zoom) { 4526 var projectedMaxBounds = toBounds( 4527 this.project(maxBounds.getNorthEast(), zoom),
vendor: 13,538 bytes, lines 4528-4921
4528 this.project(maxBounds.getSouthWest(), zoom) 4529 ), 4530 minOffset = projectedMaxBounds.min.subtract(pxBounds.min), 4531 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), 4532 4533 dx = this._rebound(minOffset.x, -maxOffset.x), 4534 dy = this._rebound(minOffset.y, -maxOffset.y); 4535 4536 return new Point(dx, dy); 4537 }, 4538 4539 _rebound: function (left, right) { 4540 return left + right > 0 ? 4541 Math.round(left - right) / 2 : 4542 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 4543 }, 4544 4545 _limitZoom: function (zoom) { 4546 var min = this.getMinZoom(), 4547 max = this.getMaxZoom(), 4548 snap = Browser.any3d ? this.options.zoomSnap : 1; 4549 if (snap) { 4550 zoom = Math.round(zoom / snap) * snap; 4551 } 4552 return Math.max(min, Math.min(max, zoom)); 4553 }, 4554 4555 _onPanTransitionStep: function () { 4556 this.fire('move'); 4557 }, 4558 4559 _onPanTransitionEnd: function () { 4560 removeClass(this._mapPane, 'leaflet-pan-anim'); 4561 this.fire('moveend'); 4562 }, 4563 4564 _tryAnimatedPan: function (center, options) { 4565 // difference between the new and current centers in pixels 4566 var offset = this._getCenterOffset(center)._trunc(); 4567 4568 // don't animate too far unless animate: true specified in options 4569 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; } 4570 4571 this.panBy(offset, options); 4572 4573 return true; 4574 }, 4575 4576 _createAnimProxy: function () { 4577 4578 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated'); 4579 this._panes.mapPane.appendChild(proxy); 4580 4581 this.on('zoomanim', function (e) { 4582 var prop = TRANSFORM, 4583 transform = this._proxy.style[prop]; 4584 4585 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 4586 4587 // workaround for case when transform is the same and so transitionend event is not fired 4588 if (transform === this._proxy.style[prop] && this._animatingZoom) { 4589 this._onZoomTransitionEnd(); 4590 } 4591 }, this); 4592 4593 this.on('load moveend', this._animMoveEnd, this); 4594 4595 this._on('unload', this._destroyAnimProxy, this); 4596 }, 4597 4598 _destroyAnimProxy: function () { 4599 remove(this._proxy); 4600 this.off('load moveend', this._animMoveEnd, this); 4601 delete this._proxy; 4602 }, 4603 4604 _animMoveEnd: function () { 4605 var c = this.getCenter(), 4606 z = this.getZoom(); 4607 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 4608 }, 4609 4610 _catchTransitionEnd: function (e) { 4611 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { 4612 this._onZoomTransitionEnd(); 4613 } 4614 }, 4615 4616 _nothingToAnimate: function () { 4617 return !this._container.getElementsByClassName('leaflet-zoom-animated').length; 4618 }, 4619 4620 _tryAnimatedZoom: function (center, zoom, options) { 4621 4622 if (this._animatingZoom) { return true; } 4623 4624 options = options || {}; 4625 4626 // don't animate if disabled, not supported or zoom difference is too large 4627 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || 4628 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; } 4629 4630 // offset is the pixel coords of the zoom origin relative to the current center 4631 var scale = this.getZoomScale(zoom), 4632 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); 4633 4634 // don't animate if the zoom origin isn't within one screen from the current center, unless forced 4635 if (options.animate !== true && !this.getSize().contains(offset)) { return false; } 4636 4637 requestAnimFrame(function () { 4638 this 4639 ._moveStart(true, false) 4640 ._animateZoom(center, zoom, true); 4641 }, this); 4642 4643 return true; 4644 }, 4645 4646 _animateZoom: function (center, zoom, startAnim, noUpdate) { 4647 if (!this._mapPane) { return; } 4648 4649 if (startAnim) { 4650 this._animatingZoom = true; 4651 4652 // remember what center/zoom to set after animation 4653 this._animateToCenter = center; 4654 this._animateToZoom = zoom; 4655 4656 addClass(this._mapPane, 'leaflet-zoom-anim'); 4657 } 4658 4659 // @section Other Events 4660 // @event zoomanim: ZoomAnimEvent 4661 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom. 4662 this.fire('zoomanim', { 4663 center: center, 4664 zoom: zoom, 4665 noUpdate: noUpdate 4666 }); 4667 4668 if (!this._tempFireZoomEvent) { 4669 this._tempFireZoomEvent = this._zoom !== this._animateToZoom; 4670 } 4671 4672 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 4673 4674 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 4675 setTimeout(bind(this._onZoomTransitionEnd, this), 250); 4676 }, 4677 4678 _onZoomTransitionEnd: function () { 4679 if (!this._animatingZoom) { return; } 4680 4681 if (this._mapPane) { 4682 removeClass(this._mapPane, 'leaflet-zoom-anim'); 4683 } 4684 4685 this._animatingZoom = false; 4686 4687 this._move(this._animateToCenter, this._animateToZoom, undefined, true); 4688 4689 if (this._tempFireZoomEvent) { 4690 this.fire('zoom'); 4691 } 4692 delete this._tempFireZoomEvent; 4693 4694 this.fire('move'); 4695 4696 // This anim frame should prevent an obscure iOS webkit tile loading race condition. 4697 requestAnimFrame(function () { 4698 this._moveEnd(true); 4699 }, this); 4700 } 4701 }); 4702 4703 // @section 4704 4705 // @factory L.map(id: String, options?: Map options) 4706 // Instantiates a map object given the DOM ID of a `<div>` element 4707 // and optionally an object literal with `Map options`. 4708 // 4709 // @alternative 4710 // @factory L.map(el: HTMLElement, options?: Map options) 4711 // Instantiates a map object given an instance of a `<div>` HTML element 4712 // and optionally an object literal with `Map options`. 4713 function createMap(id, options) { 4714 return new Map(id, options); 4715 } 4716 4717 /* 4718 * @class Control 4719 * @aka L.Control 4720 * @inherits Class 4721 * 4722 * L.Control is a base class for implementing map controls. Handles positioning. 4723 * All other controls extend from this class. 4724 */ 4725 4726 var Control = Class.extend({ 4727 // @section 4728 // @aka Control Options 4729 options: { 4730 // @option position: String = 'topright' 4731 // The position of the control (one of the map corners). Possible values are `'topleft'`, 4732 // `'topright'`, `'bottomleft'` or `'bottomright'` 4733 position: 'topright' 4734 }, 4735 4736 initialize: function (options) { 4737 setOptions(this, options); 4738 }, 4739 4740 /* @section 4741 * Classes extending L.Control will inherit the following methods: 4742 * 4743 * @method getPosition: string 4744 * Returns the position of the control. 4745 */ 4746 getPosition: function () { 4747 return this.options.position; 4748 }, 4749 4750 // @method setPosition(position: string): this 4751 // Sets the position of the control. 4752 setPosition: function (position) { 4753 var map = this._map; 4754 4755 if (map) { 4756 map.removeControl(this); 4757 } 4758 4759 this.options.position = position; 4760 4761 if (map) { 4762 map.addControl(this); 4763 } 4764 4765 return this; 4766 }, 4767 4768 // @method getContainer: HTMLElement 4769 // Returns the HTMLElement that contains the control. 4770 getContainer: function () { 4771 return this._container; 4772 }, 4773 4774 // @method addTo(map: Map): this 4775 // Adds the control to the given map. 4776 addTo: function (map) { 4777 this.remove(); 4778 this._map = map; 4779 4780 var container = this._container = this.onAdd(map), 4781 pos = this.getPosition(), 4782 corner = map._controlCorners[pos]; 4783 4784 addClass(container, 'leaflet-control'); 4785 4786 if (pos.indexOf('bottom') !== -1) { 4787 corner.insertBefore(container, corner.firstChild); 4788 } else { 4789 corner.appendChild(container); 4790 } 4791 4792 this._map.on('unload', this.remove, this); 4793 4794 return this; 4795 }, 4796 4797 // @method remove: this 4798 // Removes the control from the map it is currently active on. 4799 remove: function () { 4800 if (!this._map) { 4801 return this; 4802 } 4803 4804 remove(this._container); 4805 4806 if (this.onRemove) { 4807 this.onRemove(this._map); 4808 } 4809 4810 this._map.off('unload', this.remove, this); 4811 this._map = null; 4812 4813 return this; 4814 }, 4815 4816 _refocusOnMap: function (e) { 4817 // if map exists and event is not a keyboard event 4818 if (this._map && e && e.screenX > 0 && e.screenY > 0) { 4819 this._map.getContainer().focus(); 4820 } 4821 } 4822 }); 4823 4824 var control = function (options) { 4825 return new Control(options); 4826 }; 4827 4828 /* @section Extension methods 4829 * @uninheritable 4830 * 4831 * Every control should extend from `L.Control` and (re-)implement the following methods. 4832 * 4833 * @method onAdd(map: Map): HTMLElement 4834 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo). 4835 * 4836 * @method onRemove(map: Map) 4837 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove). 4838 */ 4839 4840 /* @namespace Map 4841 * @section Methods for Layers and Controls 4842 */ 4843 Map.include({ 4844 // @method addControl(control: Control): this 4845 // Adds the given control to the map 4846 addControl: function (control) { 4847 control.addTo(this); 4848 return this; 4849 }, 4850 4851 // @method removeControl(control: Control): this 4852 // Removes the given control from the map 4853 removeControl: function (control) { 4854 control.remove(); 4855 return this; 4856 }, 4857 4858 _initControlPos: function () { 4859 var corners = this._controlCorners = {}, 4860 l = 'leaflet-', 4861 container = this._controlContainer = 4862 create$1('div', l + 'control-container', this._container); 4863 4864 function createCorner(vSide, hSide) { 4865 var className = l + vSide + ' ' + l + hSide; 4866 4867 corners[vSide + hSide] = create$1('div', className, container); 4868 } 4869 4870 createCorner('top', 'left'); 4871 createCorner('top', 'right'); 4872 createCorner('bottom', 'left'); 4873 createCorner('bottom', 'right'); 4874 }, 4875 4876 _clearControlPos: function () { 4877 for (var i in this._controlCorners) { 4878 remove(this._controlCorners[i]); 4879 } 4880 remove(this._controlContainer); 4881 delete this._controlCorners; 4882 delete this._controlContainer; 4883 } 4884 }); 4885 4886 /* 4887 * @class Control.Layers 4888 * @aka L.Control.Layers 4889 * @inherits Control 4890 * 4891 * 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`. 4892 * 4893 * @example 4894 * 4895 * ```js 4896 * var baseLayers = { 4897 * "Mapbox": mapbox, 4898 * "OpenStreetMap": osm 4899 * }; 4900 * 4901 * var overlays = { 4902 * "Marker": marker, 4903 * "Roads": roadsLayer 4904 * }; 4905 * 4906 * L.control.layers(baseLayers, overlays).addTo(map); 4907 * ``` 4908 * 4909 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: 4910 * 4911 * ```js 4912 * { 4913 * "<someName1>": layer1, 4914 * "<someName2>": layer2 4915 * } 4916 * ``` 4917 * 4918 * The layer names can contain HTML, which allows you to add additional styling to the items: 4919 * 4920 * ```js 4921 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>
4921My Layer</span>": myLayer} 4922 * ``` 4923 */ 4924 4925 var Layers = Control.extend({ 4926 // @section 4927 // @aka Control.Layers options 4928 options: { 4929 // @option collapsed: Boolean = true 4930 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation. 4931 collapsed: true, 4932 position: 'topright', 4933 4934 // @option autoZIndex: Boolean = true 4935 // 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. 4936 autoZIndex: true, 4937 4938 // @option hideSingleBase: Boolean = false 4939 // If `true`, the base layers in the control will be hidden when there is only one. 4940 hideSingleBase: false, 4941 4942 // @option sortLayers: Boolean = false 4943 // Whether to sort the layers. When `false`, layers will keep the order 4944 // in which they were added to the control. 4945 sortLayers: false, 4946 4947 // @option sortFunction: Function = * 4948 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 4949 // that will be used for sorting the layers, when `sortLayers` is `true`. 4950 // The function receives both the `L.Layer` instances and their names, as in 4951 // `sortFunction(layerA, layerB, nameA, nameB)`. 4952 // By default, it sorts layers alphabetically by their name. 4953 sortFunction: function (layerA, layerB, nameA, nameB) { 4954 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0); 4955 } 4956 }, 4957 4958 initialize: function (baseLayers, overlays, options) { 4959 setOptions(this, options); 4960 4961 this._layerControlInputs = []; 4962 this._layers = []; 4963 this._lastZIndex = 0; 4964 this._handlingClick = false; 4965 4966 for (var i in baseLayers) { 4967 this._addLayer(baseLayers[i], i); 4968 } 4969 4970 for (i in overlays) { 4971 this._addLayer(overlays[i], i, true); 4972 } 4973 }, 4974 4975 onAdd: function (map) { 4976 this._initLayout(); 4977 this._update(); 4978 4979 this._map = map; 4980 map.on('zoomend', this._checkDisabledLayers, this); 4981 4982 for (var i = 0; i < this._layers.length; i++) { 4983 this._layers[i].layer.on('add remove', this._onLayerChange, this); 4984 } 4985 4986 return this._container; 4987 }, 4988 4989 addTo: function (map) { 4990 Control.prototype.addTo.call(this, map); 4991 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height. 4992 return this._expandIfNotCollapsed(); 4993 }, 4994 4995 onRemove: function () { 4996 this._map.off('zoomend', this._checkDisabledLayers, this); 4997 4998 for (var i = 0; i < this._layers.length; i++) { 4999 this._layers[i].layer.off('add remove', this._onLayerChange, this); 5000 } 5001 }, 5002 5003 // @method addBaseLayer(layer: Layer, name: String): this 5004 // Adds a base layer (radio button entry) with the given name to the control. 5005 addBaseLayer: function (layer, name) { 5006 this._addLayer(layer, name); 5007 return (this._map) ? this._update() : this; 5008 }, 5009 5010 // @method addOverlay(layer: Layer, name: String): this 5011 // Adds an overlay (checkbox entry) with the given name to the control. 5012 addOverlay: function (layer, name) { 5013 this._addLayer(layer, name, true); 5014 return (this._map) ? this._update() : this; 5015 }, 5016 5017 // @method removeLayer(layer: Layer): this 5018 // Remove the given layer from the control. 5019 removeLayer: function (layer) { 5020 layer.off('add remove', this._onLayerChange, this); 5021 5022 var obj = this._getLayer(stamp(layer));
vendor: 129,416 bytes, lines 5023-8680
5023 if (obj) { 5024 this._layers.splice(this._layers.indexOf(obj), 1); 5025 } 5026 return (this._map) ? this._update() : this; 5027 }, 5028 5029 // @method expand(): this 5030 // Expand the control container if collapsed. 5031 expand: function () { 5032 addClass(this._container, 'leaflet-control-layers-expanded'); 5033 this._section.style.height = null; 5034 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 5035 if (acceptableHeight < this._section.clientHeight) { 5036 addClass(this._section, 'leaflet-control-layers-scrollbar'); 5037 this._section.style.height = acceptableHeight + 'px'; 5038 } else { 5039 removeClass(this._section, 'leaflet-control-layers-scrollbar'); 5040 } 5041 this._checkDisabledLayers(); 5042 return this; 5043 }, 5044 5045 // @method collapse(): this 5046 // Collapse the control container if expanded. 5047 collapse: function () { 5048 removeClass(this._container, 'leaflet-control-layers-expanded'); 5049 return this; 5050 }, 5051 5052 _initLayout: function () { 5053 var className = 'leaflet-control-layers', 5054 container = this._container = create$1('div', className), 5055 collapsed = this.options.collapsed; 5056 5057 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released 5058 container.setAttribute('aria-haspopup', true); 5059 5060 disableClickPropagation(container); 5061 disableScrollPropagation(container); 5062 5063 var section = this._section = create$1('section', className + '-list'); 5064 5065 if (collapsed) { 5066 this._map.on('click', this.collapse, this); 5067 5068 on(container, { 5069 mouseenter: function () { 5070 on(section, 'click', preventDefault); 5071 this.expand(); 5072 setTimeout(function () { 5073 off(section, 'click', preventDefault); 5074 }); 5075 }, 5076 mouseleave: this.collapse 5077 }, this); 5078 } 5079 5080 var link = this._layersLink = create$1('a', className + '-toggle', container); 5081 link.href = '#'; 5082 link.title = 'Layers'; 5083 link.setAttribute('role', 'button'); 5084 5085 on(link, 'click', preventDefault); // prevent link function 5086 on(link, 'focus', this.expand, this); 5087 5088 if (!collapsed) { 5089 this.expand(); 5090 } 5091 5092 this._baseLayersList = create$1('div', className + '-base', section); 5093 this._separator = create$1('div', className + '-separator', section); 5094 this._overlaysList = create$1('div', className + '-overlays', section); 5095 5096 container.appendChild(section); 5097 }, 5098 5099 _getLayer: function (id) { 5100 for (var i = 0; i < this._layers.length; i++) { 5101 5102 if (this._layers[i] && stamp(this._layers[i].layer) === id) { 5103 return this._layers[i]; 5104 } 5105 } 5106 }, 5107 5108 _addLayer: function (layer, name, overlay) { 5109 if (this._map) { 5110 layer.on('add remove', this._onLayerChange, this); 5111 } 5112 5113 this._layers.push({ 5114 layer: layer, 5115 name: name, 5116 overlay: overlay 5117 }); 5118 5119 if (this.options.sortLayers) { 5120 this._layers.sort(bind(function (a, b) { 5121 return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 5122 }, this)); 5123 } 5124 5125 if (this.options.autoZIndex && layer.setZIndex) { 5126 this._lastZIndex++; 5127 layer.setZIndex(this._lastZIndex); 5128 } 5129 5130 this._expandIfNotCollapsed(); 5131 }, 5132 5133 _update: function () { 5134 if (!this._container) { return this; } 5135 5136 empty(this._baseLayersList); 5137 empty(this._overlaysList); 5138 5139 this._layerControlInputs = []; 5140 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 5141 5142 for (i = 0; i < this._layers.length; i++) { 5143 obj = this._layers[i]; 5144 this._addItem(obj); 5145 overlaysPresent = overlaysPresent || obj.overlay; 5146 baseLayersPresent = baseLayersPresent || !obj.overlay; 5147 baseLayersCount += !obj.overlay ? 1 : 0; 5148 } 5149 5150 // Hide base layers section if there's only one layer. 5151 if (this.options.hideSingleBase) { 5152 baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 5153 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; 5154 } 5155 5156 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; 5157 5158 return this; 5159 }, 5160 5161 _onLayerChange: function (e) { 5162 if (!this._handlingClick) { 5163 this._update(); 5164 } 5165 5166 var obj = this._getLayer(stamp(e.target)); 5167 5168 // @namespace Map 5169 // @section Layer events 5170 // @event baselayerchange: LayersControlEvent 5171 // Fired when the base layer is changed through the [layers control](#control-layers). 5172 // @event overlayadd: LayersControlEvent 5173 // Fired when an overlay is selected through the [layers control](#control-layers). 5174 // @event overlayremove: LayersControlEvent 5175 // Fired when an overlay is deselected through the [layers control](#control-layers). 5176 // @namespace Control.Layers 5177 var type = obj.overlay ? 5178 (e.type === 'add' ? 'overlayadd' : 'overlayremove') : 5179 (e.type === 'add' ? 'baselayerchange' : null); 5180 5181 if (type) { 5182 this._map.fire(type, obj); 5183 } 5184 }, 5185 5186 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079) 5187 _createRadioElement: function (name, checked) { 5188 5189 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + 5190 name + '"' + (checked ? ' checked="checked"' : '') + '/>'; 5191 5192 var radioFragment = document.createElement('div'); 5193 radioFragment.innerHTML = radioHtml; 5194 5195 return radioFragment.firstChild; 5196 }, 5197 5198 _addItem: function (obj) { 5199 var label = document.createElement('label'), 5200 checked = this._map.hasLayer(obj.layer), 5201 input; 5202 5203 if (obj.overlay) { 5204 input = document.createElement('input'); 5205 input.type = 'checkbox'; 5206 input.className = 'leaflet-control-layers-selector'; 5207 input.defaultChecked = checked; 5208 } else { 5209 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked); 5210 } 5211 5212 this._layerControlInputs.push(input); 5213 input.layerId = stamp(obj.layer); 5214 5215 on(input, 'click', this._onInputClick, this); 5216 5217 var name = document.createElement('span'); 5218 name.innerHTML = ' ' + obj.name; 5219 5220 // Helps from preventing layer control flicker when checkboxes are disabled 5221 // https://github.com/Leaflet/Leaflet/issues/2771 5222 var holder = document.createElement('span'); 5223 5224 label.appendChild(holder); 5225 holder.appendChild(input); 5226 holder.appendChild(name); 5227 5228 var container = obj.overlay ? this._overlaysList : this._baseLayersList; 5229 container.appendChild(label); 5230 5231 this._checkDisabledLayers(); 5232 return label; 5233 }, 5234 5235 _onInputClick: function () { 5236 var inputs = this._layerControlInputs, 5237 input, layer; 5238 var addedLayers = [], 5239 removedLayers = []; 5240 5241 this._handlingClick = true; 5242 5243 for (var i = inputs.length - 1; i >= 0; i--) { 5244 input = inputs[i]; 5245 layer = this._getLayer(input.layerId).layer; 5246 5247 if (input.checked) { 5248 addedLayers.push(layer); 5249 } else if (!input.checked) { 5250 removedLayers.push(layer); 5251 } 5252 } 5253 5254 // Bugfix issue 2318: Should remove all old layers before readding new ones 5255 for (i = 0; i < removedLayers.length; i++) { 5256 if (this._map.hasLayer(removedLayers[i])) { 5257 this._map.removeLayer(removedLayers[i]); 5258 } 5259 } 5260 for (i = 0; i < addedLayers.length; i++) { 5261 if (!this._map.hasLayer(addedLayers[i])) { 5262 this._map.addLayer(addedLayers[i]); 5263 } 5264 } 5265 5266 this._handlingClick = false; 5267 5268 this._refocusOnMap(); 5269 }, 5270 5271 _checkDisabledLayers: function () { 5272 var inputs = this._layerControlInputs, 5273 input, 5274 layer, 5275 zoom = this._map.getZoom(); 5276 5277 for (var i = inputs.length - 1; i >= 0; i--) { 5278 input = inputs[i]; 5279 layer = this._getLayer(input.layerId).layer; 5280 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || 5281 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); 5282 5283 } 5284 }, 5285 5286 _expandIfNotCollapsed: function () { 5287 if (this._map && !this.options.collapsed) { 5288 this.expand(); 5289 } 5290 return this; 5291 } 5292 5293 }); 5294 5295 5296 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) 5297 // 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. 5298 var layers = function (baseLayers, overlays, options) { 5299 return new Layers(baseLayers, overlays, options); 5300 }; 5301 5302 /* 5303 * @class Control.Zoom 5304 * @aka L.Control.Zoom 5305 * @inherits Control 5306 * 5307 * 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`. 5308 */ 5309 5310 var Zoom = Control.extend({ 5311 // @section 5312 // @aka Control.Zoom options 5313 options: { 5314 position: 'topleft', 5315 5316 // @option zoomInText: String = '<span aria-hidden="true">+</span>' 5317 // The text set on the 'zoom in' button. 5318 zoomInText: '<span aria-hidden="true">+</span>', 5319 5320 // @option zoomInTitle: String = 'Zoom in' 5321 // The title set on the 'zoom in' button. 5322 zoomInTitle: 'Zoom in', 5323 5324 // @option zoomOutText: String = '<span aria-hidden="true">−</span>' 5325 // The text set on the 'zoom out' button. 5326 zoomOutText: '<span aria-hidden="true">−</span>', 5327 5328 // @option zoomOutTitle: String = 'Zoom out' 5329 // The title set on the 'zoom out' button. 5330 zoomOutTitle: 'Zoom out' 5331 }, 5332 5333 onAdd: function (map) { 5334 var zoomName = 'leaflet-control-zoom', 5335 container = create$1('div', zoomName + ' leaflet-bar'), 5336 options = this.options; 5337 5338 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 5339 zoomName + '-in', container, this._zoomIn); 5340 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle, 5341 zoomName + '-out', container, this._zoomOut); 5342 5343 this._updateDisabled(); 5344 map.on('zoomend zoomlevelschange', this._updateDisabled, this); 5345 5346 return container; 5347 }, 5348 5349 onRemove: function (map) { 5350 map.off('zoomend zoomlevelschange', this._updateDisabled, this); 5351 }, 5352 5353 disable: function () { 5354 this._disabled = true; 5355 this._updateDisabled(); 5356 return this; 5357 }, 5358 5359 enable: function () { 5360 this._disabled = false; 5361 this._updateDisabled(); 5362 return this; 5363 }, 5364 5365 _zoomIn: function (e) { 5366 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 5367 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5368 } 5369 }, 5370 5371 _zoomOut: function (e) { 5372 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 5373 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 5374 } 5375 }, 5376 5377 _createButton: function (html, title, className, container, fn) { 5378 var link = create$1('a', className, container); 5379 link.innerHTML = html; 5380 link.href = '#'; 5381 link.title = title; 5382 5383 /* 5384 * Will force screen readers like VoiceOver to read this as "Zoom in - button" 5385 */ 5386 link.setAttribute('role', 'button'); 5387 link.setAttribute('aria-label', title); 5388 5389 disableClickPropagation(link); 5390 on(link, 'click', stop); 5391 on(link, 'click', fn, this); 5392 on(link, 'click', this._refocusOnMap, this); 5393 5394 return link; 5395 }, 5396 5397 _updateDisabled: function () { 5398 var map = this._map, 5399 className = 'leaflet-disabled'; 5400 5401 removeClass(this._zoomInButton, className); 5402 removeClass(this._zoomOutButton, className); 5403 this._zoomInButton.setAttribute('aria-disabled', 'false'); 5404 this._zoomOutButton.setAttribute('aria-disabled', 'false'); 5405 5406 if (this._disabled || map._zoom === map.getMinZoom()) { 5407 addClass(this._zoomOutButton, className); 5408 this._zoomOutButton.setAttribute('aria-disabled', 'true'); 5409 } 5410 if (this._disabled || map._zoom === map.getMaxZoom()) { 5411 addClass(this._zoomInButton, className); 5412 this._zoomInButton.setAttribute('aria-disabled', 'true'); 5413 } 5414 } 5415 }); 5416 5417 // @namespace Map 5418 // @section Control options 5419 // @option zoomControl: Boolean = true 5420 // Whether a [zoom control](#control-zoom) is added to the map by default. 5421 Map.mergeOptions({ 5422 zoomControl: true 5423 }); 5424 5425 Map.addInitHook(function () { 5426 if (this.options.zoomControl) { 5427 // @section Controls 5428 // @property zoomControl: Control.Zoom 5429 // The default zoom control (only available if the 5430 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map). 5431 this.zoomControl = new Zoom(); 5432 this.addControl(this.zoomControl); 5433 } 5434 }); 5435 5436 // @namespace Control.Zoom 5437 // @factory L.control.zoom(options: Control.Zoom options) 5438 // Creates a zoom control 5439 var zoom = function (options) { 5440 return new Zoom(options); 5441 }; 5442 5443 /* 5444 * @class Control.Scale 5445 * @aka L.Control.Scale 5446 * @inherits Control 5447 * 5448 * 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`. 5449 * 5450 * @example 5451 * 5452 * ```js 5453 * L.control.scale().addTo(map); 5454 * ``` 5455 */ 5456 5457 var Scale = Control.extend({ 5458 // @section 5459 // @aka Control.Scale options 5460 options: { 5461 position: 'bottomleft', 5462 5463 // @option maxWidth: Number = 100 5464 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 5465 maxWidth: 100, 5466 5467 // @option metric: Boolean = True 5468 // Whether to show the metric scale line (m/km). 5469 metric: true, 5470 5471 // @option imperial: Boolean = True 5472 // Whether to show the imperial scale line (mi/ft). 5473 imperial: true 5474 5475 // @option updateWhenIdle: Boolean = false 5476 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 5477 }, 5478 5479 onAdd: function (map) { 5480 var className = 'leaflet-control-scale', 5481 container = create$1('div', className), 5482 options = this.options; 5483 5484 this._addScales(options, className + '-line', container); 5485 5486 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5487 map.whenReady(this._update, this); 5488 5489 return container; 5490 }, 5491 5492 onRemove: function (map) { 5493 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this); 5494 }, 5495 5496 _addScales: function (options, className, container) { 5497 if (options.metric) { 5498 this._mScale = create$1('div', className, container); 5499 } 5500 if (options.imperial) { 5501 this._iScale = create$1('div', className, container); 5502 } 5503 }, 5504 5505 _update: function () { 5506 var map = this._map, 5507 y = map.getSize().y / 2; 5508 5509 var maxMeters = map.distance( 5510 map.containerPointToLatLng([0, y]), 5511 map.containerPointToLatLng([this.options.maxWidth, y])); 5512 5513 this._updateScales(maxMeters); 5514 }, 5515 5516 _updateScales: function (maxMeters) { 5517 if (this.options.metric && maxMeters) { 5518 this._updateMetric(maxMeters); 5519 } 5520 if (this.options.imperial && maxMeters) { 5521 this._updateImperial(maxMeters); 5522 } 5523 }, 5524 5525 _updateMetric: function (maxMeters) { 5526 var meters = this._getRoundNum(maxMeters), 5527 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km'; 5528 5529 this._updateScale(this._mScale, label, meters / maxMeters); 5530 }, 5531 5532 _updateImperial: function (maxMeters) { 5533 var maxFeet = maxMeters * 3.2808399, 5534 maxMiles, miles, feet; 5535 5536 if (maxFeet > 5280) { 5537 maxMiles = maxFeet / 5280; 5538 miles = this._getRoundNum(maxMiles); 5539 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles); 5540 5541 } else { 5542 feet = this._getRoundNum(maxFeet); 5543 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet); 5544 } 5545 }, 5546 5547 _updateScale: function (scale, text, ratio) { 5548 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px'; 5549 scale.innerHTML = text; 5550 }, 5551 5552 _getRoundNum: function (num) { 5553 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1), 5554 d = num / pow10; 5555 5556 d = d >= 10 ? 10 : 5557 d >= 5 ? 5 : 5558 d >= 3 ? 3 : 5559 d >= 2 ? 2 : 1; 5560 5561 return pow10 * d; 5562 } 5563 }); 5564 5565 5566 // @factory L.control.scale(options?: Control.Scale options) 5567 // Creates an scale control with the given options. 5568 var scale = function (options) { 5569 return new Scale(options); 5570 }; 5571 5572 /* 5573 * @class Control.Attribution 5574 * @aka L.Control.Attribution 5575 * @inherits Control 5576 * 5577 * 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. 5578 */ 5579 5580 var Attribution = Control.extend({ 5581 // @section 5582 // @aka Control.Attribution options 5583 options: { 5584 position: 'bottomright', 5585 5586 // @option prefix: String|false = 'Leaflet' 5587 // The HTML text shown before the attributions. Pass `false` to disable. 5588 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">Leaflet</a>' 5589 5590 }, 5591 5592 initialize: function (options) { 5593 setOptions(this, options); 5594 5595 this._attributions = {}; 5596 }, 5597 5598 onAdd: function (map) { 5599 map.attributionControl = this; 5600 this._container = create$1('div', 'leaflet-control-attribution'); 5601 disableClickPropagation(this._container); 5602 5603 // TODO ugly, refactor 5604 for (var i in map._layers) { 5605 if (map._layers[i].getAttribution) { 5606 this.addAttribution(map._layers[i].getAttribution()); 5607 } 5608 } 5609 5610 this._update(); 5611 5612 map.on('layeradd', this._addAttribution, this); 5613 5614 return this._container; 5615 }, 5616 5617 onRemove: function (map) { 5618 map.off('layeradd', this._addAttribution, this); 5619 }, 5620 5621 _addAttribution: function (ev) { 5622 if (ev.layer.getAttribution) { 5623 this.addAttribution(ev.layer.getAttribution()); 5624 ev.layer.once('remove', function () { 5625 this.removeAttribution(ev.layer.getAttribution()); 5626 }, this); 5627 } 5628 }, 5629 5630 // @method setPrefix(prefix: String|false): this 5631 // The HTML text shown before the attributions. Pass `false` to disable. 5632 setPrefix: function (prefix) { 5633 this.options.prefix = prefix; 5634 this._update(); 5635 return this; 5636 }, 5637 5638 // @method addAttribution(text: String): this 5639 // Adds an attribution text (e.g. `'Vector data © Mapbox'`). 5640 addAttribution: function (text) { 5641 if (!text) { return this; } 5642 5643 if (!this._attributions[text]) { 5644 this._attributions[text] = 0; 5645 } 5646 this._attributions[text]++; 5647 5648 this._update(); 5649 5650 return this; 5651 }, 5652 5653 // @method removeAttribution(text: String): this 5654 // Removes an attribution text. 5655 removeAttribution: function (text) { 5656 if (!text) { return this; } 5657 5658 if (this._attributions[text]) { 5659 this._attributions[text]--; 5660 this._update(); 5661 } 5662 5663 return this; 5664 }, 5665 5666 _update: function () { 5667 if (!this._map) { return; } 5668 5669 var attribs = []; 5670 5671 for (var i in this._attributions) { 5672 if (this._attributions[i]) { 5673 attribs.push(i); 5674 } 5675 } 5676 5677 var prefixAndAttribs = []; 5678 5679 if (this.options.prefix) { 5680 prefixAndAttribs.push(this.options.prefix); 5681 } 5682 if (attribs.length) { 5683 prefixAndAttribs.push(attribs.join(', ')); 5684 } 5685 5686 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> '); 5687 } 5688 }); 5689 5690 // @namespace Map 5691 // @section Control options 5692 // @option attributionControl: Boolean = true 5693 // Whether a [attribution control](#control-attribution) is added to the map by default. 5694 Map.mergeOptions({ 5695 attributionControl: true 5696 }); 5697 5698 Map.addInitHook(function () { 5699 if (this.options.attributionControl) { 5700 new Attribution().addTo(this); 5701 } 5702 }); 5703 5704 // @namespace Control.Attribution 5705 // @factory L.control.attribution(options: Control.Attribution options) 5706 // Creates an attribution control. 5707 var attribution = function (options) { 5708 return new Attribution(options); 5709 }; 5710 5711 Control.Layers = Layers; 5712 Control.Zoom = Zoom; 5713 Control.Scale = Scale; 5714 Control.Attribution = Attribution; 5715 5716 control.layers = layers; 5717 control.zoom = zoom; 5718 control.scale = scale; 5719 control.attribution = attribution; 5720 5721 /* 5722 L.Handler is a base class for handler classes that are used internally to inject 5723 interaction features like dragging to classes like Map and Marker. 5724 */ 5725 5726 // @class Handler 5727 // @aka L.Handler 5728 // Abstract class for map interaction handlers 5729 5730 var Handler = Class.extend({ 5731 initialize: function (map) { 5732 this._map = map; 5733 }, 5734 5735 // @method enable(): this 5736 // Enables the handler 5737 enable: function () { 5738 if (this._enabled) { return this; } 5739 5740 this._enabled = true; 5741 this.addHooks(); 5742 return this; 5743 }, 5744 5745 // @method disable(): this 5746 // Disables the handler 5747 disable: function () { 5748 if (!this._enabled) { return this; } 5749 5750 this._enabled = false; 5751 this.removeHooks(); 5752 return this; 5753 }, 5754 5755 // @method enabled(): Boolean 5756 // Returns `true` if the handler is enabled 5757 enabled: function () { 5758 return !!this._enabled; 5759 } 5760 5761 // @section Extension methods 5762 // Classes inheriting from `Handler` must implement the two following methods: 5763 // @method addHooks() 5764 // Called when the handler is enabled, should add event hooks. 5765 // @method removeHooks() 5766 // Called when the handler is disabled, should remove the event hooks added previously. 5767 }); 5768 5769 // @section There is static function which can be called without instantiating L.Handler: 5770 // @function addTo(map: Map, name: String): this 5771 // Adds a new Handler to the given map with the given name. 5772 Handler.addTo = function (map, name) { 5773 map.addHandler(name, this); 5774 return this; 5775 }; 5776 5777 var Mixin = {Events: Events}; 5778 5779 /* 5780 * @class Draggable 5781 * @aka L.Draggable 5782 * @inherits Evented 5783 * 5784 * A class for making DOM elements draggable (including touch support). 5785 * Used internally for map and marker dragging. Only works for elements 5786 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). 5787 * 5788 * @example 5789 * ```js 5790 * var draggable = new L.Draggable(elementToDrag); 5791 * draggable.enable(); 5792 * ``` 5793 */ 5794 5795 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown'; 5796 5797 var Draggable = Evented.extend({ 5798 5799 options: { 5800 // @section 5801 // @aka Draggable options 5802 // @option clickTolerance: Number = 3 5803 // The max number of pixels a user can shift the mouse pointer during a click 5804 // for it to be considered a valid click (as opposed to a mouse drag). 5805 clickTolerance: 3 5806 }, 5807 5808 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 5809 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 5810 initialize: function (element, dragStartTarget, preventOutline, options) { 5811 setOptions(this, options); 5812 5813 this._element = element; 5814 this._dragStartTarget = dragStartTarget || element; 5815 this._preventOutline = preventOutline; 5816 }, 5817 5818 // @method enable() 5819 // Enables the dragging ability 5820 enable: function () { 5821 if (this._enabled) { return; } 5822 5823 on(this._dragStartTarget, START, this._onDown, this); 5824 5825 this._enabled = true; 5826 }, 5827 5828 // @method disable() 5829 // Disables the dragging ability 5830 disable: function () { 5831 if (!this._enabled) { return; } 5832 5833 // If we're currently dragging this draggable, 5834 // disabling it counts as first ending the drag. 5835 if (Draggable._dragging === this) { 5836 this.finishDrag(); 5837 } 5838 5839 off(this._dragStartTarget, START, this._onDown, this); 5840 5841 this._enabled = false; 5842 this._moved = false; 5843 }, 5844 5845 _onDown: function (e) { 5846 // Ignore the event if disabled; this happens in IE11 5847 // under some circumstances, see #3666. 5848 if (!this._enabled) { return; } 5849 5850 this._moved = false; 5851 5852 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; } 5853 5854 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } 5855 Draggable._dragging = this; // Prevent dragging multiple objects at once. 5856 5857 if (this._preventOutline) { 5858 preventOutline(this._element); 5859 } 5860 5861 disableImageDrag(); 5862 disableTextSelection(); 5863 5864 if (this._moving) { return; } 5865 5866 // @event down: Event 5867 // Fired when a drag is about to start. 5868 this.fire('down'); 5869 5870 var first = e.touches ? e.touches[0] : e, 5871 sizedParent = getSizedParentNode(this._element); 5872 5873 this._startPoint = new Point(first.clientX, first.clientY); 5874 this._startPos = getPosition(this._element); 5875 5876 // Cache the scale, so that we can continuously compensate for it during drag (_onMove). 5877 this._parentScale = getScale(sizedParent); 5878 5879 var mouseevent = e.type === 'mousedown'; 5880 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this); 5881 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this); 5882 }, 5883 5884 _onMove: function (e) { 5885 // Ignore the event if disabled; this happens in IE11 5886 // under some circumstances, see #3666. 5887 if (!this._enabled) { return; } 5888 5889 if (e.touches && e.touches.length > 1) { 5890 this._moved = true; 5891 return; 5892 } 5893 5894 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 5895 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 5896 5897 if (!offset.x && !offset.y) { return; } 5898 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } 5899 5900 // We assume that the parent container's position, border and scale do not change for the duration of the drag. 5901 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) 5902 // and we can use the cached value for the scale. 5903 offset.x /= this._parentScale.x; 5904 offset.y /= this._parentScale.y; 5905 5906 preventDefault(e); 5907 5908 if (!this._moved) { 5909 // @event dragstart: Event 5910 // Fired when a drag starts 5911 this.fire('dragstart'); 5912 5913 this._moved = true; 5914 5915 addClass(document.body, 'leaflet-dragging'); 5916 5917 this._lastTarget = e.target || e.srcElement; 5918 // IE and Edge do not give the <use> element, so fetch it 5919 // if necessary 5920 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) { 5921 this._lastTarget = this._lastTarget.correspondingUseElement; 5922 } 5923 addClass(this._lastTarget, 'leaflet-drag-target'); 5924 } 5925 5926 this._newPos = this._startPos.add(offset); 5927 this._moving = true; 5928 5929 this._lastEvent = e; 5930 this._updatePosition(); 5931 }, 5932 5933 _updatePosition: function () { 5934 var e = {originalEvent: this._lastEvent}; 5935 5936 // @event predrag: Event 5937 // Fired continuously during dragging *before* each corresponding 5938 // update of the element's position. 5939 this.fire('predrag', e); 5940 setPosition(this._element, this._newPos); 5941 5942 // @event drag: Event 5943 // Fired continuously during dragging. 5944 this.fire('drag', e); 5945 }, 5946 5947 _onUp: function () { 5948 // Ignore the event if disabled; this happens in IE11 5949 // under some circumstances, see #3666. 5950 if (!this._enabled) { return; } 5951 this.finishDrag(); 5952 }, 5953 5954 finishDrag: function () { 5955 removeClass(document.body, 'leaflet-dragging'); 5956 5957 if (this._lastTarget) { 5958 removeClass(this._lastTarget, 'leaflet-drag-target'); 5959 this._lastTarget = null; 5960 } 5961 5962 off(document, 'mousemove touchmove', this._onMove, this); 5963 off(document, 'mouseup touchend touchcancel', this._onUp, this); 5964 5965 enableImageDrag(); 5966 enableTextSelection(); 5967 5968 if (this._moved && this._moving) { 5969 5970 // @event dragend: DragEndEvent 5971 // Fired when the drag ends. 5972 this.fire('dragend', { 5973 distance: this._newPos.distanceTo(this._startPos) 5974 }); 5975 } 5976 5977 this._moving = false; 5978 Draggable._dragging = false; 5979 } 5980 5981 }); 5982 5983 /* 5984 * @namespace LineUtil 5985 * 5986 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast. 5987 */ 5988 5989 // Simplify polyline with vertex reduction and Douglas-Peucker simplification. 5990 // Improves rendering performance dramatically by lessening the number of points to draw. 5991 5992 // @function simplify(points: Point[], tolerance: Number): Point[] 5993 // Dramatically reduces the number of points in a polyline while retaining 5994 // its shape and returns a new array of simplified points, using the 5995 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm). 5996 // Used for a huge performance boost when processing/displaying Leaflet polylines for 5997 // each zoom level and also reducing visual noise. tolerance affects the amount of 5998 // simplification (lesser value means higher quality but slower and with more points). 5999 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/). 6000 function simplify(points, tolerance) { 6001 if (!tolerance || !points.length) { 6002 return points.slice(); 6003 } 6004 6005 var sqTolerance = tolerance * tolerance; 6006 6007 // stage 1: vertex reduction 6008 points = _reducePoints(points, sqTolerance); 6009 6010 // stage 2: Douglas-Peucker simplification 6011 points = _simplifyDP(points, sqTolerance); 6012 6013 return points; 6014 } 6015 6016 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number 6017 // Returns the distance between point `p` and segment `p1` to `p2`. 6018 function pointToSegmentDistance(p, p1, p2) { 6019 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 6020 } 6021 6022 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number 6023 // Returns the closest point from a point `p` on a segment `p1` to `p2`. 6024 function closestPointOnSegment(p, p1, p2) { 6025 return _sqClosestPointOnSegment(p, p1, p2); 6026 } 6027 6028 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm 6029 function _simplifyDP(points, sqTolerance) { 6030 6031 var len = points.length, 6032 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, 6033 markers = new ArrayConstructor(len); 6034 6035 markers[0] = markers[len - 1] = 1; 6036 6037 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 6038 6039 var i, 6040 newPoints = []; 6041 6042 for (i = 0; i < len; i++) { 6043 if (markers[i]) { 6044 newPoints.push(points[i]); 6045 } 6046 } 6047 6048 return newPoints; 6049 } 6050 6051 function _simplifyDPStep(points, markers, sqTolerance, first, last) { 6052 6053 var maxSqDist = 0, 6054 index, i, sqDist; 6055 6056 for (i = first + 1; i <= last - 1; i++) { 6057 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 6058 6059 if (sqDist > maxSqDist) { 6060 index = i; 6061 maxSqDist = sqDist; 6062 } 6063 } 6064 6065 if (maxSqDist > sqTolerance) { 6066 markers[index] = 1; 6067 6068 _simplifyDPStep(points, markers, sqTolerance, first, index); 6069 _simplifyDPStep(points, markers, sqTolerance, index, last); 6070 } 6071 } 6072 6073 // reduce points that are too close to each other to a single point 6074 function _reducePoints(points, sqTolerance) { 6075 var reducedPoints = [points[0]]; 6076 6077 for (var i = 1, prev = 0, len = points.length; i < len; i++) { 6078 if (_sqDist(points[i], points[prev]) > sqTolerance) { 6079 reducedPoints.push(points[i]); 6080 prev = i; 6081 } 6082 } 6083 if (prev < len - 1) { 6084 reducedPoints.push(points[len - 1]); 6085 } 6086 return reducedPoints; 6087 } 6088 6089 var _lastCode; 6090 6091 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean 6092 // Clips the segment a to b by rectangular bounds with the 6093 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) 6094 // (modifying the segment points directly!). Used by Leaflet to only show polyline 6095 // points that are on the screen or near, increasing performance. 6096 function clipSegment(a, b, bounds, useLastCode, round) { 6097 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), 6098 codeB = _getBitCode(b, bounds), 6099 6100 codeOut, p, newCode; 6101 6102 // save 2nd code to avoid calculating it on the next segment 6103 _lastCode = codeB; 6104 6105 while (true) { 6106 // if a,b is inside the clip window (trivial accept) 6107 if (!(codeA | codeB)) { 6108 return [a, b]; 6109 } 6110 6111 // if a,b is outside the clip window (trivial reject) 6112 if (codeA & codeB) { 6113 return false; 6114 } 6115 6116 // other cases 6117 codeOut = codeA || codeB; 6118 p = _getEdgeIntersection(a, b, codeOut, bounds, round); 6119 newCode = _getBitCode(p, bounds); 6120 6121 if (codeOut === codeA) { 6122 a = p; 6123 codeA = newCode; 6124 } else { 6125 b = p; 6126 codeB = newCode; 6127 } 6128 } 6129 } 6130 6131 function _getEdgeIntersection(a, b, code, bounds, round) { 6132 var dx = b.x - a.x, 6133 dy = b.y - a.y, 6134 min = bounds.min, 6135 max = bounds.max, 6136 x, y; 6137 6138 if (code & 8) { // top 6139 x = a.x + dx * (max.y - a.y) / dy; 6140 y = max.y; 6141 6142 } else if (code & 4) { // bottom 6143 x = a.x + dx * (min.y - a.y) / dy; 6144 y = min.y; 6145 6146 } else if (code & 2) { // right 6147 x = max.x; 6148 y = a.y + dy * (max.x - a.x) / dx; 6149 6150 } else if (code & 1) { // left 6151 x = min.x; 6152 y = a.y + dy * (min.x - a.x) / dx; 6153 } 6154 6155 return new Point(x, y, round); 6156 } 6157 6158 function _getBitCode(p, bounds) { 6159 var code = 0; 6160 6161 if (p.x < bounds.min.x) { // left 6162 code |= 1; 6163 } else if (p.x > bounds.max.x) { // right 6164 code |= 2; 6165 } 6166 6167 if (p.y < bounds.min.y) { // bottom 6168 code |= 4; 6169 } else if (p.y > bounds.max.y) { // top 6170 code |= 8; 6171 } 6172 6173 return code; 6174 } 6175 6176 // square distance (to avoid unnecessary Math.sqrt calls) 6177 function _sqDist(p1, p2) { 6178 var dx = p2.x - p1.x, 6179 dy = p2.y - p1.y; 6180 return dx * dx + dy * dy; 6181 } 6182 6183 // return closest point on segment or distance to that point 6184 function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 6185 var x = p1.x, 6186 y = p1.y, 6187 dx = p2.x - x, 6188 dy = p2.y - y, 6189 dot = dx * dx + dy * dy, 6190 t; 6191 6192 if (dot > 0) { 6193 t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 6194 6195 if (t > 1) { 6196 x = p2.x; 6197 y = p2.y; 6198 } else if (t > 0) { 6199 x += dx * t; 6200 y += dy * t; 6201 } 6202 } 6203 6204 dx = p.x - x; 6205 dy = p.y - y; 6206 6207 return sqDist ? dx * dx + dy * dy : new Point(x, y); 6208 } 6209 6210 6211 // @function isFlat(latlngs: LatLng[]): Boolean 6212 // Returns true if `latlngs` is a flat array, false is nested. 6213 function isFlat(latlngs) { 6214 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); 6215 } 6216 6217 function _flat(latlngs) { 6218 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.'); 6219 return isFlat(latlngs); 6220 } 6221 6222 var LineUtil = { 6223 __proto__: null, 6224 simplify: simplify, 6225 pointToSegmentDistance: pointToSegmentDistance, 6226 closestPointOnSegment: closestPointOnSegment, 6227 clipSegment: clipSegment, 6228 _getEdgeIntersection: _getEdgeIntersection, 6229 _getBitCode: _getBitCode, 6230 _sqClosestPointOnSegment: _sqClosestPointOnSegment, 6231 isFlat: isFlat, 6232 _flat: _flat 6233 }; 6234 6235 /* 6236 * @namespace PolyUtil 6237 * Various utility functions for polygon geometries. 6238 */ 6239 6240 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] 6241 * 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)). 6242 * Used by Leaflet to only show polygon points that are on the screen or near, increasing 6243 * performance. Note that polygon points needs different algorithm for clipping 6244 * than polyline, so there's a separate method for it. 6245 */ 6246 function clipPolygon(points, bounds, round) { 6247 var clippedPoints, 6248 edges = [1, 4, 2, 8], 6249 i, j, k, 6250 a, b, 6251 len, edge, p; 6252 6253 for (i = 0, len = points.length; i < len; i++) { 6254 points[i]._code = _getBitCode(points[i], bounds); 6255 } 6256 6257 // for each edge (left, bottom, right, top) 6258 for (k = 0; k < 4; k++) { 6259 edge = edges[k]; 6260 clippedPoints = []; 6261 6262 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 6263 a = points[i]; 6264 b = points[j]; 6265 6266 // if a is inside the clip window 6267 if (!(a._code & edge)) { 6268 // if b is outside the clip window (a->b goes out of screen) 6269 if (b._code & edge) { 6270 p = _getEdgeIntersection(b, a, edge, bounds, round); 6271 p._code = _getBitCode(p, bounds); 6272 clippedPoints.push(p); 6273 } 6274 clippedPoints.push(a); 6275 6276 // else if b is inside the clip window (a->b enters the screen) 6277 } else if (!(b._code & edge)) { 6278 p = _getEdgeIntersection(b, a, edge, bounds, round); 6279 p._code = _getBitCode(p, bounds); 6280 clippedPoints.push(p); 6281 } 6282 } 6283 points = clippedPoints; 6284 } 6285 6286 return points; 6287 } 6288 6289 var PolyUtil = { 6290 __proto__: null, 6291 clipPolygon: clipPolygon 6292 }; 6293 6294 /* 6295 * @namespace Projection 6296 * @section 6297 * Leaflet comes with a set of already defined Projections out of the box: 6298 * 6299 * @projection L.Projection.LonLat 6300 * 6301 * Equirectangular, or Plate Carree projection ââ¬â the most simple projection, 6302 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as 6303 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the 6304 * `EPSG:4326` and `Simple` CRS. 6305 */ 6306 6307 var LonLat = { 6308 project: function (latlng) { 6309 return new Point(latlng.lng, latlng.lat); 6310 }, 6311 6312 unproject: function (point) { 6313 return new LatLng(point.y, point.x); 6314 }, 6315 6316 bounds: new Bounds([-180, -90], [180, 90]) 6317 }; 6318 6319 /* 6320 * @namespace Projection 6321 * @projection L.Projection.Mercator 6322 * 6323 * Elliptical Mercator projection ââ¬â more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS. 6324 */ 6325 6326 var Mercator = { 6327 R: 6378137, 6328 R_MINOR: 6356752.314245179, 6329 6330 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), 6331 6332 project: function (latlng) { 6333 var d = Math.PI / 180, 6334 r = this.R, 6335 y = latlng.lat * d, 6336 tmp = this.R_MINOR / r, 6337 e = Math.sqrt(1 - tmp * tmp), 6338 con = e * Math.sin(y); 6339 6340 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 6341 y = -r * Math.log(Math.max(ts, 1E-10)); 6342 6343 return new Point(latlng.lng * d * r, y); 6344 }, 6345 6346 unproject: function (point) { 6347 var d = 180 / Math.PI, 6348 r = this.R, 6349 tmp = this.R_MINOR / r, 6350 e = Math.sqrt(1 - tmp * tmp), 6351 ts = Math.exp(-point.y / r), 6352 phi = Math.PI / 2 - 2 * Math.atan(ts); 6353 6354 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 6355 con = e * Math.sin(phi); 6356 con = Math.pow((1 - con) / (1 + con), e / 2); 6357 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 6358 phi += dphi; 6359 } 6360 6361 return new LatLng(phi * d, point.x * d / r); 6362 } 6363 }; 6364 6365 /* 6366 * @class Projection 6367 6368 * An object with methods for projecting geographical coordinates of the world onto 6369 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection). 6370 6371 * @property bounds: Bounds 6372 * The bounds (specified in CRS units) where the projection is valid 6373 6374 * @method project(latlng: LatLng): Point 6375 * Projects geographical coordinates into a 2D point. 6376 * Only accepts actual `L.LatLng` instances, not arrays. 6377 6378 * @method unproject(point: Point): LatLng 6379 * The inverse of `project`. Projects a 2D point into a geographical location. 6380 * Only accepts actual `L.Point` instances, not arrays. 6381 6382 * Note that the projection instances do not inherit from Leaflet's `Class` object, 6383 * and can't be instantiated. Also, new classes can't inherit from them, 6384 * and methods can't be added to them with the `include` function. 6385 6386 */ 6387 6388 var index = { 6389 __proto__: null, 6390 LonLat: LonLat, 6391 Mercator: Mercator, 6392 SphericalMercator: SphericalMercator 6393 }; 6394 6395 /* 6396 * @namespace CRS 6397 * @crs L.CRS.EPSG3395 6398 * 6399 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. 6400 */ 6401 var EPSG3395 = extend({}, Earth, { 6402 code: 'EPSG:3395', 6403 projection: Mercator, 6404 6405 transformation: (function () { 6406 var scale = 0.5 / (Math.PI * Mercator.R); 6407 return toTransformation(scale, 0.5, -scale, 0.5); 6408 }()) 6409 }); 6410 6411 /* 6412 * @namespace CRS 6413 * @crs L.CRS.EPSG4326 6414 * 6415 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection. 6416 * 6417 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic), 6418 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer` 6419 * with this CRS, ensure that there are two 256x256 pixel tiles covering the 6420 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90), 6421 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. 6422 */ 6423 6424 var EPSG4326 = extend({}, Earth, { 6425 code: 'EPSG:4326', 6426 projection: LonLat, 6427 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 6428 }); 6429 6430 /* 6431 * @namespace CRS 6432 * @crs L.CRS.Simple 6433 * 6434 * A simple CRS that maps longitude and latitude into `x` and `y` directly. 6435 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y` 6436 * axis should still be inverted (going from bottom to top). `distance()` returns 6437 * simple euclidean distance. 6438 */ 6439 6440 var Simple = extend({}, CRS, { 6441 projection: LonLat, 6442 transformation: toTransformation(1, 0, -1, 0), 6443 6444 scale: function (zoom) { 6445 return Math.pow(2, zoom); 6446 }, 6447 6448 zoom: function (scale) { 6449 return Math.log(scale) / Math.LN2; 6450 }, 6451 6452 distance: function (latlng1, latlng2) { 6453 var dx = latlng2.lng - latlng1.lng, 6454 dy = latlng2.lat - latlng1.lat; 6455 6456 return Math.sqrt(dx * dx + dy * dy); 6457 }, 6458 6459 infinite: true 6460 }); 6461 6462 CRS.Earth = Earth; 6463 CRS.EPSG3395 = EPSG3395; 6464 CRS.EPSG3857 = EPSG3857; 6465 CRS.EPSG900913 = EPSG900913; 6466 CRS.EPSG4326 = EPSG4326; 6467 CRS.Simple = Simple; 6468 6469 /* 6470 * @class Layer 6471 * @inherits Evented 6472 * @aka L.Layer 6473 * @aka ILayer 6474 * 6475 * A set of methods from the Layer base class that all Leaflet layers use. 6476 * Inherits all methods, options and events from `L.Evented`. 6477 * 6478 * @example 6479 * 6480 * ```js 6481 * var layer = L.marker(latlng).addTo(map); 6482 * layer.addTo(map); 6483 * layer.remove(); 6484 * ``` 6485 * 6486 * @event add: Event 6487 * Fired after the layer is added to a map 6488 * 6489 * @event remove: Event 6490 * Fired after the layer is removed from a map 6491 */ 6492 6493 6494 var Layer = Evented.extend({ 6495 6496 // Classes extending `L.Layer` will inherit the following options: 6497 options: { 6498 // @option pane: String = 'overlayPane' 6499 // 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. 6500 pane: 'overlayPane', 6501 6502 // @option attribution: String = null 6503 // 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. 6504 attribution: null, 6505 6506 bubblingMouseEvents: true 6507 }, 6508 6509 /* @section 6510 * Classes extending `L.Layer` will inherit the following methods: 6511 * 6512 * @method addTo(map: Map|LayerGroup): this 6513 * Adds the layer to the given map or layer group. 6514 */ 6515 addTo: function (map) { 6516 map.addLayer(this); 6517 return this; 6518 }, 6519 6520 // @method remove: this 6521 // Removes the layer from the map it is currently active on. 6522 remove: function () { 6523 return this.removeFrom(this._map || this._mapToAdd); 6524 }, 6525 6526 // @method removeFrom(map: Map): this 6527 // Removes the layer from the given map 6528 // 6529 // @alternative 6530 // @method removeFrom(group: LayerGroup): this 6531 // Removes the layer from the given `LayerGroup` 6532 removeFrom: function (obj) { 6533 if (obj) { 6534 obj.removeLayer(this); 6535 } 6536 return this; 6537 }, 6538 6539 // @method getPane(name? : String): HTMLElement 6540 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 6541 getPane: function (name) { 6542 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane); 6543 }, 6544 6545 addInteractiveTarget: function (targetEl) { 6546 this._map._targets[stamp(targetEl)] = this; 6547 return this; 6548 }, 6549 6550 removeInteractiveTarget: function (targetEl) { 6551 delete this._map._targets[stamp(targetEl)]; 6552 return this; 6553 }, 6554 6555 // @method getAttribution: String 6556 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 6557 getAttribution: function () { 6558 return this.options.attribution; 6559 }, 6560 6561 _layerAdd: function (e) { 6562 var map = e.target; 6563 6564 // check in case layer gets added and then removed before the map is ready 6565 if (!map.hasLayer(this)) { return; } 6566 6567 this._map = map; 6568 this._zoomAnimated = map._zoomAnimated; 6569 6570 if (this.getEvents) { 6571 var events = this.getEvents(); 6572 map.on(events, this); 6573 this.once('remove', function () { 6574 map.off(events, this); 6575 }, this); 6576 } 6577 6578 this.onAdd(map); 6579 6580 this.fire('add'); 6581 map.fire('layeradd', {layer: this}); 6582 } 6583 }); 6584 6585 /* @section Extension methods 6586 * @uninheritable 6587 * 6588 * Every layer should extend from `L.Layer` and (re-)implement the following methods. 6589 * 6590 * @method onAdd(map: Map): this 6591 * 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). 6592 * 6593 * @method onRemove(map: Map): this 6594 * 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). 6595 * 6596 * @method getEvents(): Object 6597 * 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. 6598 * 6599 * @method getAttribution(): String 6600 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible. 6601 * 6602 * @method beforeAdd(map: Map): this 6603 * 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. 6604 */ 6605 6606 6607 /* @namespace Map 6608 * @section Layer events 6609 * 6610 * @event layeradd: LayerEvent 6611 * Fired when a new layer is added to the map. 6612 * 6613 * @event layerremove: LayerEvent 6614 * Fired when some layer is removed from the map 6615 * 6616 * @section Methods for Layers and Controls 6617 */ 6618 Map.include({ 6619 // @method addLayer(layer: Layer): this 6620 // Adds the given layer to the map 6621 addLayer: function (layer) { 6622 if (!layer._layerAdd) { 6623 throw new Error('The provided object is not a Layer.'); 6624 } 6625 6626 var id = stamp(layer); 6627 if (this._layers[id]) { return this; } 6628 this._layers[id] = layer; 6629 6630 layer._mapToAdd = this; 6631 6632 if (layer.beforeAdd) { 6633 layer.beforeAdd(this); 6634 } 6635 6636 this.whenReady(layer._layerAdd, layer); 6637 6638 return this; 6639 }, 6640 6641 // @method removeLayer(layer: Layer): this 6642 // Removes the given layer from the map. 6643 removeLayer: function (layer) { 6644 var id = stamp(layer); 6645 6646 if (!this._layers[id]) { return this; } 6647 6648 if (this._loaded) { 6649 layer.onRemove(this); 6650 } 6651 6652 delete this._layers[id]; 6653 6654 if (this._loaded) { 6655 this.fire('layerremove', {layer: layer}); 6656 layer.fire('remove'); 6657 } 6658 6659 layer._map = layer._mapToAdd = null; 6660 6661 return this; 6662 }, 6663 6664 // @method hasLayer(layer: Layer): Boolean 6665 // Returns `true` if the given layer is currently added to the map 6666 hasLayer: function (layer) { 6667 return stamp(layer) in this._layers; 6668 }, 6669 6670 /* @method eachLayer(fn: Function, context?: Object): this 6671 * Iterates over the layers of the map, optionally specifying context of the iterator function. 6672 * ``` 6673 * map.eachLayer(function(layer){ 6674 * layer.bindPopup('Hello'); 6675 * }); 6676 * ``` 6677 */ 6678 eachLayer: function (method, context) { 6679 for (var i in this._layers) { 6680 method.call(context, this._layers[i]); 6681 } 6682 return this; 6683 }, 6684 6685 _addLayers: function (layers) { 6686 layers = layers ? (isArray(layers) ? layers : [layers]) : []; 6687 6688 for (var i = 0, len = layers.length; i < len; i++) { 6689 this.addLayer(layers[i]); 6690 } 6691 }, 6692 6693 _addZoomLimit: function (layer) { 6694 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 6695 this._zoomBoundLayers[stamp(layer)] = layer; 6696 this._updateZoomLevels(); 6697 } 6698 }, 6699 6700 _removeZoomLimit: function (layer) { 6701 var id = stamp(layer); 6702 6703 if (this._zoomBoundLayers[id]) { 6704 delete this._zoomBoundLayers[id]; 6705 this._updateZoomLevels(); 6706 } 6707 }, 6708 6709 _updateZoomLevels: function () { 6710 var minZoom = Infinity, 6711 maxZoom = -Infinity, 6712 oldZoomSpan = this._getZoomSpan(); 6713 6714 for (var i in this._zoomBoundLayers) { 6715 var options = this._zoomBoundLayers[i].options; 6716 6717 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom); 6718 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom); 6719 } 6720 6721 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom; 6722 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom; 6723 6724 // @section Map state change events 6725 // @event zoomlevelschange: Event 6726 // Fired when the number of zoomlevels on the map is changed due 6727 // to adding or removing a layer. 6728 if (oldZoomSpan !== this._getZoomSpan()) { 6729 this.fire('zoomlevelschange'); 6730 } 6731 6732 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 6733 this.setZoom(this._layersMaxZoom); 6734 } 6735 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 6736 this.setZoom(this._layersMinZoom); 6737 } 6738 } 6739 }); 6740 6741 /* 6742 * @class LayerGroup 6743 * @aka L.LayerGroup 6744 * @inherits Interactive layer 6745 * 6746 * Used to group several layers and handle them as one. If you add it to the map, 6747 * any layers added or removed from the group will be added/removed on the map as 6748 * well. Extends `Layer`. 6749 * 6750 * @example 6751 * 6752 * ```js 6753 * L.layerGroup([marker1, marker2]) 6754 * .addLayer(polyline) 6755 * .addTo(map); 6756 * ``` 6757 */ 6758 6759 var LayerGroup = Layer.extend({ 6760 6761 initialize: function (layers, options) { 6762 setOptions(this, options); 6763 6764 this._layers = {}; 6765 6766 var i, len; 6767 6768 if (layers) { 6769 for (i = 0, len = layers.length; i < len; i++) { 6770 this.addLayer(layers[i]); 6771 } 6772 } 6773 }, 6774 6775 // @method addLayer(layer: Layer): this 6776 // Adds the given layer to the group. 6777 addLayer: function (layer) { 6778 var id = this.getLayerId(layer); 6779 6780 this._layers[id] = layer; 6781 6782 if (this._map) { 6783 this._map.addLayer(layer); 6784 } 6785 6786 return this; 6787 }, 6788 6789 // @method removeLayer(layer: Layer): this 6790 // Removes the given layer from the group. 6791 // @alternative 6792 // @method removeLayer(id: Number): this 6793 // Removes the layer with the given internal ID from the group. 6794 removeLayer: function (layer) { 6795 var id = layer in this._layers ? layer : this.getLayerId(layer); 6796 6797 if (this._map && this._layers[id]) { 6798 this._map.removeLayer(this._layers[id]); 6799 } 6800 6801 delete this._layers[id]; 6802 6803 return this; 6804 }, 6805 6806 // @method hasLayer(layer: Layer): Boolean 6807 // Returns `true` if the given layer is currently added to the group. 6808 // @alternative 6809 // @method hasLayer(id: Number): Boolean 6810 // Returns `true` if the given internal ID is currently added to the group. 6811 hasLayer: function (layer) { 6812 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer); 6813 return layerId in this._layers; 6814 }, 6815 6816 // @method clearLayers(): this 6817 // Removes all the layers from the group. 6818 clearLayers: function () { 6819 return this.eachLayer(this.removeLayer, this); 6820 }, 6821 6822 // @method invoke(methodName: String, ââ¬Â¦): this 6823 // Calls `methodName` on every layer contained in this group, passing any 6824 // additional parameters. Has no effect if the layers contained do not 6825 // implement `methodName`. 6826 invoke: function (methodName) { 6827 var args = Array.prototype.slice.call(arguments, 1), 6828 i, layer; 6829 6830 for (i in this._layers) { 6831 layer = this._layers[i]; 6832 6833 if (layer[methodName]) { 6834 layer[methodName].apply(layer, args); 6835 } 6836 } 6837 6838 return this; 6839 }, 6840 6841 onAdd: function (map) { 6842 this.eachLayer(map.addLayer, map); 6843 }, 6844 6845 onRemove: function (map) { 6846 this.eachLayer(map.removeLayer, map); 6847 }, 6848 6849 // @method eachLayer(fn: Function, context?: Object): this 6850 // Iterates over the layers of the group, optionally specifying context of the iterator function. 6851 // ```js 6852 // group.eachLayer(function (layer) { 6853 // layer.bindPopup('Hello'); 6854 // }); 6855 // ``` 6856 eachLayer: function (method, context) { 6857 for (var i in this._layers) { 6858 method.call(context, this._layers[i]); 6859 } 6860 return this; 6861 }, 6862 6863 // @method getLayer(id: Number): Layer 6864 // Returns the layer with the given internal ID. 6865 getLayer: function (id) { 6866 return this._layers[id]; 6867 }, 6868 6869 // @method getLayers(): Layer[] 6870 // Returns an array of all the layers added to the group. 6871 getLayers: function () { 6872 var layers = []; 6873 this.eachLayer(layers.push, layers); 6874 return layers; 6875 }, 6876 6877 // @method setZIndex(zIndex: Number): this 6878 // Calls `setZIndex` on every layer contained in this group, passing the z-index. 6879 setZIndex: function (zIndex) { 6880 return this.invoke('setZIndex', zIndex); 6881 }, 6882 6883 // @method getLayerId(layer: Layer): Number 6884 // Returns the internal ID for a layer 6885 getLayerId: function (layer) { 6886 return stamp(layer); 6887 } 6888 }); 6889 6890 6891 // @factory L.layerGroup(layers?: Layer[], options?: Object) 6892 // Create a layer group, optionally given an initial set of layers and an `options` object. 6893 var layerGroup = function (layers, options) { 6894 return new LayerGroup(layers, options); 6895 }; 6896 6897 /* 6898 * @class FeatureGroup 6899 * @aka L.FeatureGroup 6900 * @inherits LayerGroup 6901 * 6902 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers: 6903 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip)) 6904 * * Events are propagated to the `FeatureGroup`, so if the group has an event 6905 * handler, it will handle events from any of the layers. This includes mouse events 6906 * and custom events. 6907 * * Has `layeradd` and `layerremove` events 6908 * 6909 * @example 6910 * 6911 * ```js 6912 * L.featureGroup([marker1, marker2, polyline]) 6913 * .bindPopup('Hello world!') 6914 * .on('click', function() { alert('Clicked on a member of the group!'); }) 6915 * .addTo(map); 6916 * ``` 6917 */ 6918 6919 var FeatureGroup = LayerGroup.extend({ 6920 6921 addLayer: function (layer) { 6922 if (this.hasLayer(layer)) { 6923 return this; 6924 } 6925 6926 layer.addEventParent(this); 6927 6928 LayerGroup.prototype.addLayer.call(this, layer); 6929 6930 // @event layeradd: LayerEvent 6931 // Fired when a layer is added to this `FeatureGroup` 6932 return this.fire('layeradd', {layer: layer}); 6933 }, 6934 6935 removeLayer: function (layer) { 6936 if (!this.hasLayer(layer)) { 6937 return this; 6938 } 6939 if (layer in this._layers) { 6940 layer = this._layers[layer]; 6941 } 6942 6943 layer.removeEventParent(this); 6944 6945 LayerGroup.prototype.removeLayer.call(this, layer); 6946 6947 // @event layerremove: LayerEvent 6948 // Fired when a layer is removed from this `FeatureGroup` 6949 return this.fire('layerremove', {layer: layer}); 6950 }, 6951 6952 // @method setStyle(style: Path options): this 6953 // Sets the given path options to each layer of the group that has a `setStyle` method. 6954 setStyle: function (style) { 6955 return this.invoke('setStyle', style); 6956 }, 6957 6958 // @method bringToFront(): this 6959 // Brings the layer group to the top of all other layers 6960 bringToFront: function () { 6961 return this.invoke('bringToFront'); 6962 }, 6963 6964 // @method bringToBack(): this 6965 // Brings the layer group to the back of all other layers 6966 bringToBack: function () { 6967 return this.invoke('bringToBack'); 6968 }, 6969 6970 // @method getBounds(): LatLngBounds 6971 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 6972 getBounds: function () { 6973 var bounds = new LatLngBounds(); 6974 6975 for (var id in this._layers) { 6976 var layer = this._layers[id]; 6977 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 6978 } 6979 return bounds; 6980 } 6981 }); 6982 6983 // @factory L.featureGroup(layers?: Layer[], options?: Object) 6984 // Create a feature group, optionally given an initial set of layers and an `options` object. 6985 var featureGroup = function (layers, options) { 6986 return new FeatureGroup(layers, options); 6987 }; 6988 6989 /* 6990 * @class Icon 6991 * @aka L.Icon 6992 * 6993 * Represents an icon to provide when creating a marker. 6994 * 6995 * @example 6996 * 6997 * ```js 6998 * var myIcon = L.icon({ 6999 * iconUrl: 'my-icon.png', 7000 * iconRetinaUrl: '[email protected]', 7001 * iconSize: [38, 95], 7002 * iconAnchor: [22, 94], 7003 * popupAnchor: [-3, -76], 7004 * shadowUrl: 'my-icon-shadow.png', 7005 * shadowRetinaUrl: '[email protected]', 7006 * shadowSize: [68, 95], 7007 * shadowAnchor: [22, 94] 7008 * }); 7009 * 7010 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 7011 * ``` 7012 * 7013 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default. 7014 * 7015 */ 7016 7017 var Icon = Class.extend({ 7018 7019 /* @section 7020 * @aka Icon options 7021 * 7022 * @option iconUrl: String = null 7023 * **(required)** The URL to the icon image (absolute or relative to your script path). 7024 * 7025 * @option iconRetinaUrl: String = null 7026 * The URL to a retina sized version of the icon image (absolute or relative to your 7027 * script path). Used for Retina screen devices. 7028 * 7029 * @option iconSize: Point = null 7030 * Size of the icon image in pixels. 7031 * 7032 * @option iconAnchor: Point = null 7033 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 7034 * will be aligned so that this point is at the marker's geographical location. Centered 7035 * by default if size is specified, also can be set in CSS with negative margins. 7036 * 7037 * @option popupAnchor: Point = [0, 0] 7038 * The coordinates of the point from which popups will "open", relative to the icon anchor. 7039 * 7040 * @option tooltipAnchor: Point = [0, 0] 7041 * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 7042 * 7043 * @option shadowUrl: String = null 7044 * The URL to the icon shadow image. If not specified, no shadow image will be created. 7045 * 7046 * @option shadowRetinaUrl: String = null 7047 * 7048 * @option shadowSize: Point = null 7049 * Size of the shadow image in pixels. 7050 * 7051 * @option shadowAnchor: Point = null 7052 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 7053 * as iconAnchor if not specified). 7054 * 7055 * @option className: String = '' 7056 * A custom class name to assign to both icon and shadow images. Empty by default. 7057 */ 7058 7059 options: { 7060 popupAnchor: [0, 0], 7061 tooltipAnchor: [0, 0], 7062 7063 // @option crossOrigin: Boolean|String = false 7064 // Whether the crossOrigin attribute will be added to the tiles. 7065 // 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. 7066 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 7067 crossOrigin: false 7068 }, 7069 7070 initialize: function (options) { 7071 setOptions(this, options); 7072 }, 7073 7074 // @method createIcon(oldIcon?: HTMLElement): HTMLElement 7075 // Called internally when the icon has to be shown, returns a `<img>` HTML element 7076 // styled according to the options. 7077 createIcon: function (oldIcon) { 7078 return this._createIcon('icon', oldIcon); 7079 }, 7080 7081 // @method createShadow(oldIcon?: HTMLElement): HTMLElement 7082 // As `createIcon`, but for the shadow beneath it. 7083 createShadow: function (oldIcon) { 7084 return this._createIcon('shadow', oldIcon); 7085 }, 7086 7087 _createIcon: function (name, oldIcon) { 7088 var src = this._getIconUrl(name); 7089 7090 if (!src) { 7091 if (name === 'icon') { 7092 throw new Error('iconUrl not set in Icon options (see the docs).'); 7093 } 7094 return null; 7095 } 7096 7097 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null); 7098 this._setIconStyles(img, name); 7099 7100 if (this.options.crossOrigin || this.options.crossOrigin === '') { 7101 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 7102 } 7103 7104 return img; 7105 }, 7106 7107 _setIconStyles: function (img, name) { 7108 var options = this.options; 7109 var sizeOption = options[name + 'Size']; 7110 7111 if (typeof sizeOption === 'number') { 7112 sizeOption = [sizeOption, sizeOption]; 7113 } 7114 7115 var size = toPoint(sizeOption), 7116 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor || 7117 size && size.divideBy(2, true)); 7118 7119 img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); 7120 7121 if (anchor) { 7122 img.style.marginLeft = (-anchor.x) + 'px'; 7123 img.style.marginTop = (-anchor.y) + 'px'; 7124 } 7125 7126 if (size) { 7127 img.style.width = size.x + 'px'; 7128 img.style.height = size.y + 'px'; 7129 } 7130 }, 7131 7132 _createImg: function (src, el) { 7133 el = el || document.createElement('img'); 7134 el.src = src; 7135 return el; 7136 }, 7137 7138 _getIconUrl: function (name) { 7139 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; 7140 } 7141 }); 7142 7143 7144 // @factory L.icon(options: Icon options) 7145 // Creates an icon instance with the given options. 7146 function icon(options) { 7147 return new Icon(options); 7148 } 7149 7150 /* 7151 * @miniclass Icon.Default (Icon) 7152 * @aka L.Icon.Default 7153 * @section 7154 * 7155 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when 7156 * no icon is specified. Points to the blue marker image distributed with Leaflet 7157 * releases. 7158 * 7159 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options` 7160 * (which is a set of `Icon options`). 7161 * 7162 * If you want to _completely_ replace the default icon, override the 7163 * `L.Marker.prototype.options.icon` with your own icon instead. 7164 */ 7165 7166 var IconDefault = Icon.extend({ 7167 7168 options: { 7169 iconUrl: 'marker-icon.png', 7170 iconRetinaUrl: 'marker-icon-2x.png', 7171 shadowUrl: 'marker-shadow.png', 7172 iconSize: [25, 41], 7173 iconAnchor: [12, 41], 7174 popupAnchor: [1, -34], 7175 tooltipAnchor: [16, -28], 7176 shadowSize: [41, 41] 7177 }, 7178 7179 _getIconUrl: function (name) { 7180 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only 7181 IconDefault.imagePath = this._detectIconPath(); 7182 } 7183 7184 // @option imagePath: String 7185 // `Icon.Default` will try to auto-detect the location of the 7186 // blue icon images. If you are placing these images in a non-standard 7187 // way, set this option to point to the right path. 7188 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 7189 }, 7190 7191 _stripUrl: function (path) { // separate function to use in tests 7192 var strip = function (str, re, idx) { 7193 var match = re.exec(str); 7194 return match && match[idx]; 7195 }; 7196 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2); 7197 return path && strip(path, /^(.*)marker-icon\.png$/, 1); 7198 }, 7199 7200 _detectIconPath: function () { 7201 var el = create$1('div', 'leaflet-default-icon-path', document.body); 7202 var path = getStyle(el, 'background-image') || 7203 getStyle(el, 'backgroundImage'); // IE8 7204 7205 document.body.removeChild(el); 7206 path = this._stripUrl(path); 7207 if (path) { return path; } 7208 var link = document.querySelector('link[href$="leaflet.css"]'); 7209 if (!link) { return ''; } 7210 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1); 7211 } 7212 }); 7213 7214 /* 7215 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. 7216 */ 7217 7218 7219 /* @namespace Marker 7220 * @section Interaction handlers 7221 * 7222 * 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: 7223 * 7224 * ```js 7225 * marker.dragging.disable(); 7226 * ``` 7227 * 7228 * @property dragging: Handler 7229 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)). 7230 */ 7231 7232 var MarkerDrag = Handler.extend({ 7233 initialize: function (marker) { 7234 this._marker = marker; 7235 }, 7236 7237 addHooks: function () { 7238 var icon = this._marker._icon; 7239 7240 if (!this._draggable) { 7241 this._draggable = new Draggable(icon, icon, true); 7242 } 7243 7244 this._draggable.on({ 7245 dragstart: this._onDragStart, 7246 predrag: this._onPreDrag, 7247 drag: this._onDrag, 7248 dragend: this._onDragEnd 7249 }, this).enable(); 7250 7251 addClass(icon, 'leaflet-marker-draggable'); 7252 }, 7253 7254 removeHooks: function () { 7255 this._draggable.off({ 7256 dragstart: this._onDragStart, 7257 predrag: this._onPreDrag, 7258 drag: this._onDrag, 7259 dragend: this._onDragEnd 7260 }, this).disable(); 7261 7262 if (this._marker._icon) { 7263 removeClass(this._marker._icon, 'leaflet-marker-draggable'); 7264 } 7265 }, 7266 7267 moved: function () { 7268 return this._draggable && this._draggable._moved; 7269 }, 7270 7271 _adjustPan: function (e) { 7272 var marker = this._marker, 7273 map = marker._map, 7274 speed = this._marker.options.autoPanSpeed, 7275 padding = this._marker.options.autoPanPadding, 7276 iconPos = getPosition(marker._icon), 7277 bounds = map.getPixelBounds(), 7278 origin = map.getPixelOrigin(); 7279 7280 var panBounds = toBounds( 7281 bounds.min._subtract(origin).add(padding), 7282 bounds.max._subtract(origin).subtract(padding) 7283 ); 7284 7285 if (!panBounds.contains(iconPos)) { 7286 // Compute incremental movement 7287 var movement = toPoint( 7288 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - 7289 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 7290 7291 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - 7292 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 7293 ).multiplyBy(speed); 7294 7295 map.panBy(movement, {animate: false}); 7296 7297 this._draggable._newPos._add(movement); 7298 this._draggable._startPos._add(movement); 7299 7300 setPosition(marker._icon, this._draggable._newPos); 7301 this._onDrag(e); 7302 7303 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7304 } 7305 }, 7306 7307 _onDragStart: function () { 7308 // @section Dragging events 7309 // @event dragstart: Event 7310 // Fired when the user starts dragging the marker. 7311 7312 // @event movestart: Event 7313 // Fired when the marker starts moving (because of dragging). 7314 7315 this._oldLatLng = this._marker.getLatLng(); 7316 7317 // When using ES6 imports it could not be set when `Popup` was not imported as well 7318 this._marker.closePopup && this._marker.closePopup(); 7319 7320 this._marker 7321 .fire('movestart') 7322 .fire('dragstart'); 7323 }, 7324 7325 _onPreDrag: function (e) { 7326 if (this._marker.options.autoPan) { 7327 cancelAnimFrame(this._panRequest); 7328 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 7329 } 7330 }, 7331 7332 _onDrag: function (e) { 7333 var marker = this._marker, 7334 shadow = marker._shadow, 7335 iconPos = getPosition(marker._icon), 7336 latlng = marker._map.layerPointToLatLng(iconPos); 7337 7338 // update shadow position 7339 if (shadow) { 7340 setPosition(shadow, iconPos); 7341 } 7342 7343 marker._latlng = latlng; 7344 e.latlng = latlng; 7345 e.oldLatLng = this._oldLatLng; 7346 7347 // @event drag: Event 7348 // Fired repeatedly while the user drags the marker. 7349 marker 7350 .fire('move', e) 7351 .fire('drag', e); 7352 }, 7353 7354 _onDragEnd: function (e) { 7355 // @event dragend: DragEndEvent 7356 // Fired when the user stops dragging the marker. 7357 7358 cancelAnimFrame(this._panRequest); 7359 7360 // @event moveend: Event 7361 // Fired when the marker stops moving (because of dragging). 7362 delete this._oldLatLng; 7363 this._marker 7364 .fire('moveend') 7365 .fire('dragend', e); 7366 } 7367 }); 7368 7369 /* 7370 * @class Marker 7371 * @inherits Interactive layer 7372 * @aka L.Marker 7373 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. 7374 * 7375 * @example 7376 * 7377 * ```js 7378 * L.marker([50.5, 30.5]).addTo(map); 7379 * ``` 7380 */ 7381 7382 var Marker = Layer.extend({ 7383 7384 // @section 7385 // @aka Marker options 7386 options: { 7387 // @option icon: Icon = * 7388 // Icon instance to use for rendering the marker. 7389 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 7390 // If not specified, a common instance of `L.Icon.Default` is used. 7391 icon: new IconDefault(), 7392 7393 // Option inherited from "Interactive layer" abstract class 7394 interactive: true, 7395 7396 // @option keyboard: Boolean = true 7397 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 7398 keyboard: true, 7399 7400 // @option title: String = '' 7401 // Text for the browser tooltip that appear on marker hover (no tooltip by default). 7402 title: '', 7403 7404 // @option alt: String = 'Marker' 7405 // Text for the `alt` attribute of the icon image (useful for accessibility). 7406 alt: 'Marker', 7407 7408 // @option zIndexOffset: Number = 0 7409 // 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). 7410 zIndexOffset: 0, 7411 7412 // @option opacity: Number = 1.0 7413 // The opacity of the marker. 7414 opacity: 1, 7415 7416 // @option riseOnHover: Boolean = false 7417 // If `true`, the marker will get on top of others when you hover the mouse over it. 7418 riseOnHover: false, 7419 7420 // @option riseOffset: Number = 250 7421 // The z-index offset used for the `riseOnHover` feature. 7422 riseOffset: 250, 7423 7424 // @option pane: String = 'markerPane' 7425 // `Map pane` where the markers icon will be added. 7426 pane: 'markerPane', 7427 7428 // @option shadowPane: String = 'shadowPane' 7429 // `Map pane` where the markers shadow will be added. 7430 shadowPane: 'shadowPane', 7431 7432 // @option bubblingMouseEvents: Boolean = false 7433 // When `true`, a mouse event on this marker will trigger the same event on the map 7434 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7435 bubblingMouseEvents: false, 7436 7437 // @option autoPanOnFocus: Boolean = true 7438 // When `true`, the map will pan whenever the marker is focused (via 7439 // e.g. pressing `tab` on the keyboard) to ensure the marker is 7440 // visible within the map's bounds 7441 autoPanOnFocus: true, 7442 7443 // @section Draggable marker options 7444 // @option draggable: Boolean = false 7445 // Whether the marker is draggable with mouse/touch or not. 7446 draggable: false, 7447 7448 // @option autoPan: Boolean = false 7449 // Whether to pan the map when dragging this marker near its edge or not. 7450 autoPan: false, 7451 7452 // @option autoPanPadding: Point = Point(50, 50) 7453 // Distance (in pixels to the left/right and to the top/bottom) of the 7454 // map edge to start panning the map. 7455 autoPanPadding: [50, 50], 7456 7457 // @option autoPanSpeed: Number = 10 7458 // Number of pixels the map should pan by. 7459 autoPanSpeed: 10 7460 }, 7461 7462 /* @section 7463 * 7464 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 7465 */ 7466 7467 initialize: function (latlng, options) { 7468 setOptions(this, options); 7469 this._latlng = toLatLng(latlng); 7470 }, 7471 7472 onAdd: function (map) { 7473 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; 7474 7475 if (this._zoomAnimated) { 7476 map.on('zoomanim', this._animateZoom, this); 7477 } 7478 7479 this._initIcon(); 7480 this.update(); 7481 }, 7482 7483 onRemove: function (map) { 7484 if (this.dragging && this.dragging.enabled()) { 7485 this.options.draggable = true; 7486 this.dragging.removeHooks(); 7487 } 7488 delete this.dragging; 7489 7490 if (this._zoomAnimated) { 7491 map.off('zoomanim', this._animateZoom, this); 7492 } 7493 7494 this._removeIcon(); 7495 this._removeShadow(); 7496 }, 7497 7498 getEvents: function () { 7499 return { 7500 zoom: this.update, 7501 viewreset: this.update 7502 }; 7503 }, 7504 7505 // @method getLatLng: LatLng 7506 // Returns the current geographical position of the marker. 7507 getLatLng: function () { 7508 return this._latlng; 7509 }, 7510 7511 // @method setLatLng(latlng: LatLng): this 7512 // Changes the marker position to the given point. 7513 setLatLng: function (latlng) { 7514 var oldLatLng = this._latlng; 7515 this._latlng = toLatLng(latlng); 7516 this.update(); 7517 7518 // @event move: Event 7519 // 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`. 7520 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7521 }, 7522 7523 // @method setZIndexOffset(offset: Number): this 7524 // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 7525 setZIndexOffset: function (offset) { 7526 this.options.zIndexOffset = offset; 7527 return this.update(); 7528 }, 7529 7530 // @method getIcon: Icon 7531 // Returns the current icon used by the marker 7532 getIcon: function () { 7533 return this.options.icon; 7534 }, 7535 7536 // @method setIcon(icon: Icon): this 7537 // Changes the marker icon. 7538 setIcon: function (icon) { 7539 7540 this.options.icon = icon; 7541 7542 if (this._map) { 7543 this._initIcon(); 7544 this.update(); 7545 } 7546 7547 if (this._popup) { 7548 this.bindPopup(this._popup, this._popup.options); 7549 } 7550 7551 return this; 7552 }, 7553 7554 getElement: function () { 7555 return this._icon; 7556 }, 7557 7558 update: function () { 7559 7560 if (this._icon && this._map) { 7561 var pos = this._map.latLngToLayerPoint(this._latlng).round(); 7562 this._setPos(pos); 7563 } 7564 7565 return this; 7566 }, 7567 7568 _initIcon: function () { 7569 var options = this.options, 7570 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 7571 7572 var icon = options.icon.createIcon(this._icon), 7573 addIcon = false; 7574 7575 // if we're not reusing the icon, remove the old one and init new one 7576 if (icon !== this._icon) { 7577 if (this._icon) { 7578 this._removeIcon(); 7579 } 7580 addIcon = true; 7581 7582 if (options.title) { 7583 icon.title = options.title; 7584 } 7585 7586 if (icon.tagName === 'IMG') { 7587 icon.alt = options.alt || ''; 7588 } 7589 } 7590 7591 addClass(icon, classToAdd); 7592 7593 if (options.keyboard) { 7594 icon.tabIndex = '0'; 7595 icon.setAttribute('role', 'button'); 7596 } 7597 7598 this._icon = icon; 7599 7600 if (options.riseOnHover) { 7601 this.on({ 7602 mouseover: this._bringToFront, 7603 mouseout: this._resetZIndex 7604 }); 7605 } 7606 7607 if (this.options.autoPanOnFocus) { 7608 on(icon, 'focus', this._panOnFocus, this); 7609 } 7610 7611 var newShadow = options.icon.createShadow(this._shadow), 7612 addShadow = false; 7613 7614 if (newShadow !== this._shadow) { 7615 this._removeShadow(); 7616 addShadow = true; 7617 } 7618 7619 if (newShadow) { 7620 addClass(newShadow, classToAdd); 7621 newShadow.alt = ''; 7622 } 7623 this._shadow = newShadow; 7624 7625 7626 if (options.opacity < 1) { 7627 this._updateOpacity(); 7628 } 7629 7630 7631 if (addIcon) { 7632 this.getPane().appendChild(this._icon); 7633 } 7634 this._initInteraction(); 7635 if (newShadow && addShadow) { 7636 this.getPane(options.shadowPane).appendChild(this._shadow); 7637 } 7638 }, 7639 7640 _removeIcon: function () { 7641 if (this.options.riseOnHover) { 7642 this.off({ 7643 mouseover: this._bringToFront, 7644 mouseout: this._resetZIndex 7645 }); 7646 } 7647 7648 if (this.options.autoPanOnFocus) { 7649 off(this._icon, 'focus', this._panOnFocus, this); 7650 } 7651 7652 remove(this._icon); 7653 this.removeInteractiveTarget(this._icon); 7654 7655 this._icon = null; 7656 }, 7657 7658 _removeShadow: function () { 7659 if (this._shadow) { 7660 remove(this._shadow); 7661 } 7662 this._shadow = null; 7663 }, 7664 7665 _setPos: function (pos) { 7666 7667 if (this._icon) { 7668 setPosition(this._icon, pos); 7669 } 7670 7671 if (this._shadow) { 7672 setPosition(this._shadow, pos); 7673 } 7674 7675 this._zIndex = pos.y + this.options.zIndexOffset; 7676 7677 this._resetZIndex(); 7678 }, 7679 7680 _updateZIndex: function (offset) { 7681 if (this._icon) { 7682 this._icon.style.zIndex = this._zIndex + offset; 7683 } 7684 }, 7685 7686 _animateZoom: function (opt) { 7687 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 7688 7689 this._setPos(pos); 7690 }, 7691 7692 _initInteraction: function () { 7693 7694 if (!this.options.interactive) { return; } 7695 7696 addClass(this._icon, 'leaflet-interactive'); 7697 7698 this.addInteractiveTarget(this._icon); 7699 7700 if (MarkerDrag) { 7701 var draggable = this.options.draggable; 7702 if (this.dragging) { 7703 draggable = this.dragging.enabled(); 7704 this.dragging.disable(); 7705 } 7706 7707 this.dragging = new MarkerDrag(this); 7708 7709 if (draggable) { 7710 this.dragging.enable(); 7711 } 7712 } 7713 }, 7714 7715 // @method setOpacity(opacity: Number): this 7716 // Changes the opacity of the marker. 7717 setOpacity: function (opacity) { 7718 this.options.opacity = opacity; 7719 if (this._map) { 7720 this._updateOpacity(); 7721 } 7722 7723 return this; 7724 }, 7725 7726 _updateOpacity: function () { 7727 var opacity = this.options.opacity; 7728 7729 if (this._icon) { 7730 setOpacity(this._icon, opacity); 7731 } 7732 7733 if (this._shadow) { 7734 setOpacity(this._shadow, opacity); 7735 } 7736 }, 7737 7738 _bringToFront: function () { 7739 this._updateZIndex(this.options.riseOffset); 7740 }, 7741 7742 _resetZIndex: function () { 7743 this._updateZIndex(0); 7744 }, 7745 7746 _panOnFocus: function () { 7747 var map = this._map; 7748 if (!map) { return; } 7749 7750 var iconOpts = this.options.icon.options; 7751 var size = toPoint(iconOpts.iconSize); 7752 var anchor = toPoint(iconOpts.iconAnchor); 7753 7754 map.panInside(this._latlng, { 7755 paddingTopLeft: anchor, 7756 paddingBottomRight: size.subtract(anchor) 7757 }); 7758 }, 7759 7760 _getPopupAnchor: function () { 7761 return this.options.icon.options.popupAnchor; 7762 }, 7763 7764 _getTooltipAnchor: function () { 7765 return this.options.icon.options.tooltipAnchor; 7766 } 7767 }); 7768 7769 7770 // factory L.marker(latlng: LatLng, options? : Marker options) 7771 7772 // @factory L.marker(latlng: LatLng, options? : Marker options) 7773 // Instantiates a Marker object given a geographical point and optionally an options object. 7774 function marker(latlng, options) { 7775 return new Marker(latlng, options); 7776 } 7777 7778 /* 7779 * @class Path 7780 * @aka L.Path 7781 * @inherits Interactive layer 7782 * 7783 * An abstract class that contains options and constants shared between vector 7784 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. 7785 */ 7786 7787 var Path = Layer.extend({ 7788 7789 // @section 7790 // @aka Path options 7791 options: { 7792 // @option stroke: Boolean = true 7793 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 7794 stroke: true, 7795 7796 // @option color: String = '#3388ff' 7797 // Stroke color 7798 color: '#3388ff', 7799 7800 // @option weight: Number = 3 7801 // Stroke width in pixels 7802 weight: 3, 7803 7804 // @option opacity: Number = 1.0 7805 // Stroke opacity 7806 opacity: 1, 7807 7808 // @option lineCap: String= 'round' 7809 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 7810 lineCap: 'round', 7811 7812 // @option lineJoin: String = 'round' 7813 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 7814 lineJoin: 'round', 7815 7816 // @option dashArray: String = null 7817 // 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). 7818 dashArray: null, 7819 7820 // @option dashOffset: String = null 7821 // 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). 7822 dashOffset: null, 7823 7824 // @option fill: Boolean = depends 7825 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 7826 fill: false, 7827 7828 // @option fillColor: String = * 7829 // Fill color. Defaults to the value of the [`color`](#path-color) option 7830 fillColor: null, 7831 7832 // @option fillOpacity: Number = 0.2 7833 // Fill opacity. 7834 fillOpacity: 0.2, 7835 7836 // @option fillRule: String = 'evenodd' 7837 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 7838 fillRule: 'evenodd', 7839 7840 // className: '', 7841 7842 // Option inherited from "Interactive layer" abstract class 7843 interactive: true, 7844 7845 // @option bubblingMouseEvents: Boolean = true 7846 // When `true`, a mouse event on this path will trigger the same event on the map 7847 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 7848 bubblingMouseEvents: true 7849 }, 7850 7851 beforeAdd: function (map) { 7852 // Renderer is set here because we need to call renderer.getEvents 7853 // before this.getEvents. 7854 this._renderer = map.getRenderer(this); 7855 }, 7856 7857 onAdd: function () { 7858 this._renderer._initPath(this); 7859 this._reset(); 7860 this._renderer._addPath(this); 7861 }, 7862 7863 onRemove: function () { 7864 this._renderer._removePath(this); 7865 }, 7866 7867 // @method redraw(): this 7868 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 7869 redraw: function () { 7870 if (this._map) { 7871 this._renderer._updatePath(this); 7872 } 7873 return this; 7874 }, 7875 7876 // @method setStyle(style: Path options): this 7877 // Changes the appearance of a Path based on the options in the `Path options` object. 7878 setStyle: function (style) { 7879 setOptions(this, style); 7880 if (this._renderer) { 7881 this._renderer._updateStyle(this); 7882 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) { 7883 this._updateBounds(); 7884 } 7885 } 7886 return this; 7887 }, 7888 7889 // @method bringToFront(): this 7890 // Brings the layer to the top of all path layers. 7891 bringToFront: function () { 7892 if (this._renderer) { 7893 this._renderer._bringToFront(this); 7894 } 7895 return this; 7896 }, 7897 7898 // @method bringToBack(): this 7899 // Brings the layer to the bottom of all path layers. 7900 bringToBack: function () { 7901 if (this._renderer) { 7902 this._renderer._bringToBack(this); 7903 } 7904 return this; 7905 }, 7906 7907 getElement: function () { 7908 return this._path; 7909 }, 7910 7911 _reset: function () { 7912 // defined in child classes 7913 this._project(); 7914 this._update(); 7915 }, 7916 7917 _clickTolerance: function () { 7918 // used when doing hit detection for Canvas layers 7919 return (this.options.stroke ? this.options.weight / 2 : 0) + 7920 (this._renderer.options.tolerance || 0); 7921 } 7922 }); 7923 7924 /* 7925 * @class CircleMarker 7926 * @aka L.CircleMarker 7927 * @inherits Path 7928 * 7929 * A circle of a fixed size with radius specified in pixels. Extends `Path`. 7930 */ 7931 7932 var CircleMarker = Path.extend({ 7933 7934 // @section 7935 // @aka CircleMarker options 7936 options: { 7937 fill: true, 7938 7939 // @option radius: Number = 10 7940 // Radius of the circle marker, in pixels 7941 radius: 10 7942 }, 7943 7944 initialize: function (latlng, options) { 7945 setOptions(this, options); 7946 this._latlng = toLatLng(latlng); 7947 this._radius = this.options.radius; 7948 }, 7949 7950 // @method setLatLng(latLng: LatLng): this 7951 // Sets the position of a circle marker to a new location. 7952 setLatLng: function (latlng) { 7953 var oldLatLng = this._latlng; 7954 this._latlng = toLatLng(latlng); 7955 this.redraw(); 7956 7957 // @event move: Event 7958 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. 7959 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); 7960 }, 7961 7962 // @method getLatLng(): LatLng 7963 // Returns the current geographical position of the circle marker 7964 getLatLng: function () { 7965 return this._latlng; 7966 }, 7967 7968 // @method setRadius(radius: Number): this 7969 // Sets the radius of a circle marker. Units are in pixels. 7970 setRadius: function (radius) { 7971 this.options.radius = this._radius = radius; 7972 return this.redraw(); 7973 }, 7974 7975 // @method getRadius(): Number 7976 // Returns the current radius of the circle 7977 getRadius: function () { 7978 return this._radius; 7979 }, 7980 7981 setStyle : function (options) { 7982 var radius = options && options.radius || this._radius; 7983 Path.prototype.setStyle.call(this, options); 7984 this.setRadius(radius); 7985 return this; 7986 }, 7987 7988 _project: function () { 7989 this._point = this._map.latLngToLayerPoint(this._latlng); 7990 this._updateBounds(); 7991 }, 7992 7993 _updateBounds: function () { 7994 var r = this._radius, 7995 r2 = this._radiusY || r, 7996 w = this._clickTolerance(), 7997 p = [r + w, r2 + w]; 7998 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 7999 }, 8000 8001 _update: function () { 8002 if (this._map) { 8003 this._updatePath(); 8004 } 8005 }, 8006 8007 _updatePath: function () { 8008 this._renderer._updateCircle(this); 8009 }, 8010 8011 _empty: function () { 8012 return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 8013 }, 8014 8015 // Needed by the `Canvas` renderer for interactivity 8016 _containsPoint: function (p) { 8017 return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 8018 } 8019 }); 8020 8021 8022 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) 8023 // Instantiates a circle marker object given a geographical point, and an optional options object. 8024 function circleMarker(latlng, options) { 8025 return new CircleMarker(latlng, options); 8026 } 8027 8028 /* 8029 * @class Circle 8030 * @aka L.Circle 8031 * @inherits CircleMarker 8032 * 8033 * A class for drawing circle overlays on a map. Extends `CircleMarker`. 8034 * 8035 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). 8036 * 8037 * @example 8038 * 8039 * ```js 8040 * L.circle([50.5, 30.5], {radius: 200}).addTo(map); 8041 * ``` 8042 */ 8043 8044 var Circle = CircleMarker.extend({ 8045 8046 initialize: function (latlng, options, legacyOptions) { 8047 if (typeof options === 'number') { 8048 // Backwards compatibility with 0.7.x factory (latlng, radius, options?) 8049 options = extend({}, legacyOptions, {radius: options}); 8050 } 8051 setOptions(this, options); 8052 this._latlng = toLatLng(latlng); 8053 8054 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } 8055 8056 // @section 8057 // @aka Circle options 8058 // @option radius: Number; Radius of the circle, in meters. 8059 this._mRadius = this.options.radius; 8060 }, 8061 8062 // @method setRadius(radius: Number): this 8063 // Sets the radius of a circle. Units are in meters. 8064 setRadius: function (radius) { 8065 this._mRadius = radius; 8066 return this.redraw(); 8067 }, 8068 8069 // @method getRadius(): Number 8070 // Returns the current radius of a circle. Units are in meters. 8071 getRadius: function () { 8072 return this._mRadius; 8073 }, 8074 8075 // @method getBounds(): LatLngBounds 8076 // Returns the `LatLngBounds` of the path. 8077 getBounds: function () { 8078 var half = [this._radius, this._radiusY || this._radius]; 8079 8080 return new LatLngBounds( 8081 this._map.layerPointToLatLng(this._point.subtract(half)), 8082 this._map.layerPointToLatLng(this._point.add(half))); 8083 }, 8084 8085 setStyle: Path.prototype.setStyle, 8086 8087 _project: function () { 8088 8089 var lng = this._latlng.lng, 8090 lat = this._latlng.lat, 8091 map = this._map, 8092 crs = map.options.crs; 8093 8094 if (crs.distance === Earth.distance) { 8095 var d = Math.PI / 180, 8096 latR = (this._mRadius / Earth.R) / d, 8097 top = map.project([lat + latR, lng]), 8098 bottom = map.project([lat - latR, lng]), 8099 p = top.add(bottom).divideBy(2), 8100 lat2 = map.unproject(p).lat, 8101 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / 8102 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 8103 8104 if (isNaN(lngR) || lngR === 0) { 8105 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 8106 } 8107 8108 this._point = p.subtract(map.getPixelOrigin()); 8109 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x; 8110 this._radiusY = p.y - top.y; 8111 8112 } else { 8113 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 8114 8115 this._point = map.latLngToLayerPoint(this._latlng); 8116 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; 8117 } 8118 8119 this._updateBounds(); 8120 } 8121 }); 8122 8123 // @factory L.circle(latlng: LatLng, options?: Circle options) 8124 // Instantiates a circle object given a geographical point, and an options object 8125 // which contains the circle radius. 8126 // @alternative 8127 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) 8128 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code. 8129 // Do not use in new applications or plugins. 8130 function circle(latlng, options, legacyOptions) { 8131 return new Circle(latlng, options, legacyOptions); 8132 } 8133 8134 /* 8135 * @class Polyline 8136 * @aka L.Polyline 8137 * @inherits Path 8138 * 8139 * A class for drawing polyline overlays on a map. Extends `Path`. 8140 * 8141 * @example 8142 * 8143 * ```js 8144 * // create a red polyline from an array of LatLng points 8145 * var latlngs = [ 8146 * [45.51, -122.68], 8147 * [37.77, -122.43], 8148 * [34.04, -118.2] 8149 * ]; 8150 * 8151 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map); 8152 * 8153 * // zoom the map to the polyline 8154 * map.fitBounds(polyline.getBounds()); 8155 * ``` 8156 * 8157 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape: 8158 * 8159 * ```js 8160 * // create a red polyline from an array of arrays of LatLng points 8161 * var latlngs = [ 8162 * [[45.51, -122.68], 8163 * [37.77, -122.43], 8164 * [34.04, -118.2]], 8165 * [[40.78, -73.91], 8166 * [41.83, -87.62], 8167 * [32.76, -96.72]] 8168 * ]; 8169 * ``` 8170 */ 8171 8172 8173 var Polyline = Path.extend({ 8174 8175 // @section 8176 // @aka Polyline options 8177 options: { 8178 // @option smoothFactor: Number = 1.0 8179 // How much to simplify the polyline on each zoom level. More means 8180 // better performance and smoother look, and less means more accurate representation. 8181 smoothFactor: 1.0, 8182 8183 // @option noClip: Boolean = false 8184 // Disable polyline clipping. 8185 noClip: false 8186 }, 8187 8188 initialize: function (latlngs, options) { 8189 setOptions(this, options); 8190 this._setLatLngs(latlngs); 8191 }, 8192 8193 // @method getLatLngs(): LatLng[] 8194 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 8195 getLatLngs: function () { 8196 return this._latlngs; 8197 }, 8198 8199 // @method setLatLngs(latlngs: LatLng[]): this 8200 // Replaces all the points in the polyline with the given array of geographical points. 8201 setLatLngs: function (latlngs) { 8202 this._setLatLngs(latlngs); 8203 return this.redraw(); 8204 }, 8205 8206 // @method isEmpty(): Boolean 8207 // Returns `true` if the Polyline has no LatLngs. 8208 isEmpty: function () { 8209 return !this._latlngs.length; 8210 }, 8211 8212 // @method closestLayerPoint(p: Point): Point 8213 // Returns the point closest to `p` on the Polyline. 8214 closestLayerPoint: function (p) { 8215 var minDistance = Infinity, 8216 minPoint = null, 8217 closest = _sqClosestPointOnSegment, 8218 p1, p2; 8219 8220 for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 8221 var points = this._parts[j]; 8222 8223 for (var i = 1, len = points.length; i < len; i++) { 8224 p1 = points[i - 1]; 8225 p2 = points[i]; 8226 8227 var sqDist = closest(p, p1, p2, true); 8228 8229 if (sqDist < minDistance) { 8230 minDistance = sqDist; 8231 minPoint = closest(p, p1, p2); 8232 } 8233 } 8234 } 8235 if (minPoint) { 8236 minPoint.distance = Math.sqrt(minDistance); 8237 } 8238 return minPoint; 8239 }, 8240 8241 // @method getCenter(): LatLng 8242 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline. 8243 getCenter: function () { 8244 // throws error when not yet added to map as this center calculation requires projected coordinates 8245 if (!this._map) { 8246 throw new Error('Must add layer to map before using getCenter()'); 8247 } 8248 8249 var i, halfDist, segDist, dist, p1, p2, ratio, 8250 points = this._rings[0], 8251 len = points.length; 8252 8253 if (!len) { return null; } 8254 8255 // polyline centroid algorithm; only uses the first ring if there are multiple 8256 8257 for (i = 0, halfDist = 0; i < len - 1; i++) { 8258 halfDist += points[i].distanceTo(points[i + 1]) / 2; 8259 } 8260 8261 // The line is so small in the current view that all points are on the same pixel. 8262 if (halfDist === 0) { 8263 return this._map.layerPointToLatLng(points[0]); 8264 } 8265 8266 for (i = 0, dist = 0; i < len - 1; i++) { 8267 p1 = points[i]; 8268 p2 = points[i + 1]; 8269 segDist = p1.distanceTo(p2); 8270 dist += segDist; 8271 8272 if (dist > halfDist) { 8273 ratio = (dist - halfDist) / segDist; 8274 return this._map.layerPointToLatLng([ 8275 p2.x - ratio * (p2.x - p1.x), 8276 p2.y - ratio * (p2.y - p1.y) 8277 ]); 8278 } 8279 } 8280 }, 8281 8282 // @method getBounds(): LatLngBounds 8283 // Returns the `LatLngBounds` of the path. 8284 getBounds: function () { 8285 return this._bounds; 8286 }, 8287 8288 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this 8289 // Adds a given point to the polyline. By default, adds to the first ring of 8290 // the polyline in case of a multi-polyline, but can be overridden by passing 8291 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 8292 addLatLng: function (latlng, latlngs) { 8293 latlngs = latlngs || this._defaultShape(); 8294 latlng = toLatLng(latlng); 8295 latlngs.push(latlng); 8296 this._bounds.extend(latlng); 8297 return this.redraw(); 8298 }, 8299 8300 _setLatLngs: function (latlngs) { 8301 this._bounds = new LatLngBounds(); 8302 this._latlngs = this._convertLatLngs(latlngs); 8303 }, 8304 8305 _defaultShape: function () { 8306 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 8307 }, 8308 8309 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 8310 _convertLatLngs: function (latlngs) { 8311 var result = [], 8312 flat = isFlat(latlngs); 8313 8314 for (var i = 0, len = latlngs.length; i < len; i++) { 8315 if (flat) { 8316 result[i] = toLatLng(latlngs[i]); 8317 this._bounds.extend(result[i]); 8318 } else { 8319 result[i] = this._convertLatLngs(latlngs[i]); 8320 } 8321 } 8322 8323 return result; 8324 }, 8325 8326 _project: function () { 8327 var pxBounds = new Bounds(); 8328 this._rings = []; 8329 this._projectLatlngs(this._latlngs, this._rings, pxBounds); 8330 8331 if (this._bounds.isValid() && pxBounds.isValid()) { 8332 this._rawPxBounds = pxBounds; 8333 this._updateBounds(); 8334 } 8335 }, 8336 8337 _updateBounds: function () { 8338 var w = this._clickTolerance(), 8339 p = new Point(w, w); 8340 8341 if (!this._rawPxBounds) { 8342 return; 8343 } 8344 8345 this._pxBounds = new Bounds([ 8346 this._rawPxBounds.min.subtract(p), 8347 this._rawPxBounds.max.add(p) 8348 ]); 8349 }, 8350 8351 // recursively turns latlngs into a set of rings with projected coordinates 8352 _projectLatlngs: function (latlngs, result, projectedBounds) { 8353 var flat = latlngs[0] instanceof LatLng, 8354 len = latlngs.length, 8355 i, ring; 8356 8357 if (flat) { 8358 ring = []; 8359 for (i = 0; i < len; i++) { 8360 ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 8361 projectedBounds.extend(ring[i]); 8362 } 8363 result.push(ring); 8364 } else { 8365 for (i = 0; i < len; i++) { 8366 this._projectLatlngs(latlngs[i], result, projectedBounds); 8367 } 8368 } 8369 }, 8370 8371 // clip polyline by renderer bounds so that we have less to render for performance 8372 _clipPoints: function () { 8373 var bounds = this._renderer._bounds; 8374 8375 this._parts = []; 8376 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8377 return; 8378 } 8379 8380 if (this.options.noClip) { 8381 this._parts = this._rings; 8382 return; 8383 } 8384 8385 var parts = this._parts, 8386 i, j, k, len, len2, segment, points; 8387 8388 for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 8389 points = this._rings[i]; 8390 8391 for (j = 0, len2 = points.length; j < len2 - 1; j++) { 8392 segment = clipSegment(points[j], points[j + 1], bounds, j, true); 8393 8394 if (!segment) { continue; } 8395 8396 parts[k] = parts[k] || []; 8397 parts[k].push(segment[0]); 8398 8399 // if segment goes out of screen, or it's the last one, it's the end of the line part 8400 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) { 8401 parts[k].push(segment[1]); 8402 k++; 8403 } 8404 } 8405 } 8406 }, 8407 8408 // simplify each clipped part of the polyline for performance 8409 _simplifyPoints: function () { 8410 var parts = this._parts, 8411 tolerance = this.options.smoothFactor; 8412 8413 for (var i = 0, len = parts.length; i < len; i++) { 8414 parts[i] = simplify(parts[i], tolerance); 8415 } 8416 }, 8417 8418 _update: function () { 8419 if (!this._map) { return; } 8420 8421 this._clipPoints(); 8422 this._simplifyPoints(); 8423 this._updatePath(); 8424 }, 8425 8426 _updatePath: function () { 8427 this._renderer._updatePoly(this); 8428 }, 8429 8430 // Needed by the `Canvas` renderer for interactivity 8431 _containsPoint: function (p, closed) { 8432 var i, j, k, len, len2, part, 8433 w = this._clickTolerance(); 8434 8435 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8436 8437 // hit detection for polylines 8438 for (i = 0, len = this._parts.length; i < len; i++) { 8439 part = this._parts[i]; 8440 8441 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8442 if (!closed && (j === 0)) { continue; } 8443 8444 if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 8445 return true; 8446 } 8447 } 8448 } 8449 return false; 8450 } 8451 }); 8452 8453 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options) 8454 // Instantiates a polyline object given an array of geographical points and 8455 // optionally an options object. You can create a `Polyline` object with 8456 // multiple separate lines (`MultiPolyline`) by passing an array of arrays 8457 // of geographic points. 8458 function polyline(latlngs, options) { 8459 return new Polyline(latlngs, options); 8460 } 8461 8462 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1. 8463 Polyline._flat = _flat; 8464 8465 /* 8466 * @class Polygon 8467 * @aka L.Polygon 8468 * @inherits Polyline 8469 * 8470 * A class for drawing polygon overlays on a map. Extends `Polyline`. 8471 * 8472 * 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. 8473 * 8474 * 8475 * @example 8476 * 8477 * ```js 8478 * // create a red polygon from an array of LatLng points 8479 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]]; 8480 * 8481 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map); 8482 * 8483 * // zoom the map to the polygon 8484 * map.fitBounds(polygon.getBounds()); 8485 * ``` 8486 * 8487 * 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: 8488 * 8489 * ```js 8490 * var latlngs = [ 8491 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8492 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8493 * ]; 8494 * ``` 8495 * 8496 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape. 8497 * 8498 * ```js 8499 * var latlngs = [ 8500 * [ // first polygon 8501 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring 8502 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole 8503 * ], 8504 * [ // second polygon 8505 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]] 8506 * ] 8507 * ]; 8508 * ``` 8509 */ 8510 8511 var Polygon = Polyline.extend({ 8512 8513 options: { 8514 fill: true 8515 }, 8516 8517 isEmpty: function () { 8518 return !this._latlngs.length || !this._latlngs[0].length; 8519 }, 8520 8521 getCenter: function () { 8522 // throws error when not yet added to map as this center calculation requires projected coordinates 8523 if (!this._map) { 8524 throw new Error('Must add layer to map before using getCenter()'); 8525 } 8526 8527 var i, j, p1, p2, f, area, x, y, center, 8528 points = this._rings[0], 8529 len = points.length; 8530 8531 if (!len) { return null; } 8532 8533 // polygon centroid algorithm; only uses the first ring if there are multiple 8534 8535 area = x = y = 0; 8536 8537 for (i = 0, j = len - 1; i < len; j = i++) { 8538 p1 = points[i]; 8539 p2 = points[j]; 8540 8541 f = p1.y * p2.x - p2.y * p1.x; 8542 x += (p1.x + p2.x) * f; 8543 y += (p1.y + p2.y) * f; 8544 area += f * 3; 8545 } 8546 8547 if (area === 0) { 8548 // Polygon is so small that all points are on same pixel. 8549 center = points[0]; 8550 } else { 8551 center = [x / area, y / area]; 8552 } 8553 return this._map.layerPointToLatLng(center); 8554 }, 8555 8556 _convertLatLngs: function (latlngs) { 8557 var result = Polyline.prototype._convertLatLngs.call(this, latlngs), 8558 len = result.length; 8559 8560 // remove last point if it equals first one 8561 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 8562 result.pop(); 8563 } 8564 return result; 8565 }, 8566 8567 _setLatLngs: function (latlngs) { 8568 Polyline.prototype._setLatLngs.call(this, latlngs); 8569 if (isFlat(this._latlngs)) { 8570 this._latlngs = [this._latlngs]; 8571 } 8572 }, 8573 8574 _defaultShape: function () { 8575 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 8576 }, 8577 8578 _clipPoints: function () { 8579 // polygons need a different clipping algorithm so we redefine that 8580 8581 var bounds = this._renderer._bounds, 8582 w = this.options.weight, 8583 p = new Point(w, w); 8584 8585 // increase clip padding by stroke width to avoid stroke on clip edges 8586 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 8587 8588 this._parts = []; 8589 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 8590 return; 8591 } 8592 8593 if (this.options.noClip) { 8594 this._parts = this._rings; 8595 return; 8596 } 8597 8598 for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 8599 clipped = clipPolygon(this._rings[i], bounds, true); 8600 if (clipped.length) { 8601 this._parts.push(clipped); 8602 } 8603 } 8604 }, 8605 8606 _updatePath: function () { 8607 this._renderer._updatePoly(this, true); 8608 }, 8609 8610 // Needed by the `Canvas` renderer for interactivity 8611 _containsPoint: function (p) { 8612 var inside = false, 8613 part, p1, p2, i, j, k, len, len2; 8614 8615 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; } 8616 8617 // ray casting algorithm for detecting if point is in polygon 8618 for (i = 0, len = this._parts.length; i < len; i++) { 8619 part = this._parts[i]; 8620 8621 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 8622 p1 = part[j]; 8623 p2 = part[k]; 8624 8625 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)) { 8626 inside = !inside; 8627 } 8628 } 8629 } 8630 8631 // also check if it's on polygon stroke 8632 return inside || Polyline.prototype._containsPoint.call(this, p, true); 8633 } 8634 8635 }); 8636 8637 8638 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options) 8639 function polygon(latlngs, options) { 8640 return new Polygon(latlngs, options); 8641 } 8642 8643 /* 8644 * @class GeoJSON 8645 * @aka L.GeoJSON 8646 * @inherits FeatureGroup 8647 * 8648 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse 8649 * GeoJSON data and display it on the map. Extends `FeatureGroup`. 8650 * 8651 * @example 8652 * 8653 * ```js 8654 * L.geoJSON(data, { 8655 * style: function (feature) { 8656 * return {color: feature.properties.color}; 8657 * } 8658 * }).bindPopup(function (layer) { 8659 * return layer.feature.properties.description; 8660 * }).addTo(map); 8661 * ``` 8662 */ 8663 8664 var GeoJSON = FeatureGroup.extend({ 8665 8666 /* @section 8667 * @aka GeoJSON options 8668 * 8669 * @option pointToLayer: Function = * 8670 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 8671 * called when data is added, passing the GeoJSON point feature and its `LatLng`. 8672 * The default is to spawn a default `Marker`: 8673 * ```js 8674 * function(geoJsonPoint, latlng) { 8675 * return L.marker(latlng); 8676 * } 8677 * ``` 8678 * 8679 * @option style: Function = * 8680 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8681 * called internally when data is added. 8682 * The default value is to not override any defaults: 8683 * ```js 8684 * function (geoJsonFeature) { 8685 * return {} 8686 * } 8687 * ``` 8688 * 8689 * @option onEachFeature: Function = * 8690 * A `Function` that will be called once for each created `Feature`, after it has 8691 * been created and styled. Useful for attaching events and popups to features. 8692 * The default is to do nothing with the newly created layers: 8693 * ```js 8694 * function (feature, layer) {} 8695 * ``` 8696 * 8697 * @option filter: Function = * 8698 * A `Function` that will be used to decide whether to include a feature or not. 8699 * The default is to include all features: 8700 * ```js 8701 * function (geoJsonFeature) { 8702 * return true; 8703 * } 8704 * ``` 8705 * Note: dynamically changing the `filter` option will have effect only on newly 8706 * added data. It will _not_ re-evaluate already included features. 8707 * 8708 * @option coordsToLatLng: Function = * 8709 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 8710 * The default is the `coordsToLatLng` static method. 8711 * 8712 * @option markersInheritOptions: Boolean = false 8713 * Whether default Markers for "Point" type Features inherit from group options. 8714 */ 8715 8716 initialize: function (geojson, options) { 8717 setOptions(this, options); 8718 8719 this._layers = {}; 8720 8721 if (geojson) { 8722 this.addData(geojson); 8723 } 8724 }, 8725 8726 // @method addData( <GeoJSON> data ): this 8727 // Adds a GeoJSON object to the layer. 8728 addData: function (geojson) { 8729 var features = isArray(geojson) ? geojson : geojson.features, 8730 i, len, feature; 8731 8732 if (features) { 8733 for (i = 0, len = features.length; i < len; i++) { 8734 // only add this if geometry or geometries are set and not null 8735 feature = features[i]; 8736 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 8737 this.addData(feature); 8738 } 8739 } 8740 return this; 8741 } 8742 8743 var options = this.options; 8744 8745 if (options.filter && !options.filter(geojson)) { return this; } 8746 8747 var layer = geometryToLayer(geojson, options); 8748 if (!layer) { 8749 return this; 8750 } 8751 layer.feature = asFeature(geojson); 8752 8753 layer.defaultOptions = layer.options; 8754 this.resetStyle(layer); 8755 8756 if (options.onEachFeature) { 8757 options.onEachFeature(geojson, layer); 8758 } 8759 8760 return this.addLayer(layer); 8761 }, 8762 8763 // @method resetStyle( <Path> layer? ): this 8764 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 8765 // If `layer` is omitted, the style of all features in the current layer is reset. 8766 resetStyle: function (layer) { 8767 if (layer === undefined) { 8768 return this.eachLayer(this.resetStyle, this); 8769 } 8770 // reset any custom styles 8771 layer.options = extend({}, layer.defaultOptions); 8772 this._setLayerStyle(layer, this.options.style); 8773 return this; 8774 }, 8775 8776 // @method setStyle( <Function> style ): this 8777 // Changes styles of GeoJSON vector layers with the given style function. 8778 setStyle: function (style) { 8779 return this.eachLayer(function (layer) { 8780 this._setLayerStyle(layer, style); 8781 }, this); 8782 }, 8783 8784 _setLayerStyle: function (layer, style) { 8785 if (layer.setStyle) { 8786 if (typeof style === 'function') { 8787 style = style(layer.feature); 8788 } 8789 layer.setStyle(style); 8790 } 8791 } 8792 });
vendor: 148,295 bytes, lines 8793-12815
8793 8794 // @section 8795 // There are several static functions which can be called without instantiating L.GeoJSON: 8796 8797 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer 8798 // Creates a `Layer` from a given GeoJSON feature. Can use a custom 8799 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) 8800 // functions if provided as options. 8801 function geometryToLayer(geojson, options) { 8802 8803 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, 8804 coords = geometry ? geometry.coordinates : null, 8805 layers = [], 8806 pointToLayer = options && options.pointToLayer, 8807 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, 8808 latlng, latlngs, i, len; 8809 8810 if (!coords && !geometry) { 8811 return null; 8812 } 8813 8814 switch (geometry.type) { 8815 case 'Point': 8816 latlng = _coordsToLatLng(coords); 8817 return _pointToLayer(pointToLayer, geojson, latlng, options); 8818 8819 case 'MultiPoint': 8820 for (i = 0, len = coords.length; i < len; i++) { 8821 latlng = _coordsToLatLng(coords[i]); 8822 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 8823 } 8824 return new FeatureGroup(layers); 8825 8826 case 'LineString': 8827 case 'MultiLineString': 8828 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); 8829 return new Polyline(latlngs, options); 8830 8831 case 'Polygon': 8832 case 'MultiPolygon': 8833 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); 8834 return new Polygon(latlngs, options); 8835 8836 case 'GeometryCollection': 8837 for (i = 0, len = geometry.geometries.length; i < len; i++) { 8838 var layer = geometryToLayer({ 8839 geometry: geometry.geometries[i], 8840 type: 'Feature', 8841 properties: geojson.properties 8842 }, options); 8843 8844 if (layer) { 8845 layers.push(layer); 8846 } 8847 } 8848 return new FeatureGroup(layers); 8849 8850 default: 8851 throw new Error('Invalid GeoJSON object.'); 8852 } 8853 } 8854 8855 function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 8856 return pointToLayerFn ? 8857 pointToLayerFn(geojson, latlng) : 8858 new Marker(latlng, options && options.markersInheritOptions && options); 8859 } 8860 8861 // @function coordsToLatLng(coords: Array): LatLng 8862 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) 8863 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. 8864 function coordsToLatLng(coords) { 8865 return new LatLng(coords[1], coords[0], coords[2]); 8866 } 8867 8868 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array 8869 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. 8870 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). 8871 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. 8872 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 8873 var latlngs = []; 8874 8875 for (var i = 0, len = coords.length, latlng; i < len; i++) { 8876 latlng = levelsDeep ? 8877 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : 8878 (_coordsToLatLng || coordsToLatLng)(coords[i]); 8879 8880 latlngs.push(latlng); 8881 } 8882 8883 return latlngs; 8884 } 8885 8886 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array 8887 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) 8888 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 8889 function latLngToCoords(latlng, precision) { 8890 return latlng.alt !== undefined ? 8891 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : 8892 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 8893 } 8894 8895 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array 8896 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) 8897 // `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. 8898 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function. 8899 function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 8900 var coords = []; 8901 8902 for (var i = 0, len = latlngs.length; i < len; i++) { 8903 coords.push(levelsDeep ? 8904 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) : 8905 latLngToCoords(latlngs[i], precision)); 8906 } 8907 8908 if (!levelsDeep && closed) { 8909 coords.push(coords[0]); 8910 } 8911 8912 return coords; 8913 } 8914 8915 function getFeature(layer, newGeometry) { 8916 return layer.feature ? 8917 extend({}, layer.feature, {geometry: newGeometry}) : 8918 asFeature(newGeometry); 8919 } 8920 8921 // @function asFeature(geojson: Object): Object 8922 // Normalize GeoJSON geometries/features into GeoJSON features. 8923 function asFeature(geojson) { 8924 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { 8925 return geojson; 8926 } 8927 8928 return { 8929 type: 'Feature', 8930 properties: {}, 8931 geometry: geojson 8932 }; 8933 } 8934 8935 var PointToGeoJSON = { 8936 toGeoJSON: function (precision) { 8937 return getFeature(this, { 8938 type: 'Point', 8939 coordinates: latLngToCoords(this.getLatLng(), precision) 8940 }); 8941 } 8942 }; 8943 8944 // @namespace Marker 8945 // @section Other methods 8946 // @method toGeoJSON(precision?: Number|false): Object 8947 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8948 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). 8949 Marker.include(PointToGeoJSON); 8950 8951 // @namespace CircleMarker 8952 // @method toGeoJSON(precision?: Number|false): Object 8953 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8954 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). 8955 Circle.include(PointToGeoJSON); 8956 CircleMarker.include(PointToGeoJSON); 8957 8958 8959 // @namespace Polyline 8960 // @method toGeoJSON(precision?: Number|false): Object 8961 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8962 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). 8963 Polyline.include({ 8964 toGeoJSON: function (precision) { 8965 var multi = !isFlat(this._latlngs); 8966 8967 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 8968 8969 return getFeature(this, { 8970 type: (multi ? 'Multi' : '') + 'LineString', 8971 coordinates: coords 8972 }); 8973 } 8974 }); 8975 8976 // @namespace Polygon 8977 // @method toGeoJSON(precision?: Number|false): Object 8978 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 8979 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). 8980 Polygon.include({ 8981 toGeoJSON: function (precision) { 8982 var holes = !isFlat(this._latlngs), 8983 multi = holes && !isFlat(this._latlngs[0]); 8984 8985 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 8986 8987 if (!holes) { 8988 coords = [coords]; 8989 } 8990 8991 return getFeature(this, { 8992 type: (multi ? 'Multi' : '') + 'Polygon', 8993 coordinates: coords 8994 }); 8995 } 8996 }); 8997 8998 8999 // @namespace LayerGroup 9000 LayerGroup.include({ 9001 toMultiPoint: function (precision) { 9002 var coords = []; 9003 9004 this.eachLayer(function (layer) { 9005 coords.push(layer.toGeoJSON(precision).geometry.coordinates); 9006 }); 9007 9008 return getFeature(this, { 9009 type: 'MultiPoint', 9010 coordinates: coords 9011 }); 9012 }, 9013 9014 // @method toGeoJSON(precision?: Number|false): Object 9015 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 9016 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 9017 toGeoJSON: function (precision) { 9018 9019 var type = this.feature && this.feature.geometry && this.feature.geometry.type; 9020 9021 if (type === 'MultiPoint') { 9022 return this.toMultiPoint(precision); 9023 } 9024 9025 var isGeometryCollection = type === 'GeometryCollection', 9026 jsons = []; 9027 9028 this.eachLayer(function (layer) { 9029 if (layer.toGeoJSON) { 9030 var json = layer.toGeoJSON(precision); 9031 if (isGeometryCollection) { 9032 jsons.push(json.geometry); 9033 } else { 9034 var feature = asFeature(json); 9035 // Squash nested feature collections 9036 if (feature.type === 'FeatureCollection') { 9037 jsons.push.apply(jsons, feature.features); 9038 } else { 9039 jsons.push(feature); 9040 } 9041 } 9042 } 9043 }); 9044 9045 if (isGeometryCollection) { 9046 return getFeature(this, { 9047 geometries: jsons, 9048 type: 'GeometryCollection' 9049 }); 9050 } 9051 9052 return { 9053 type: 'FeatureCollection', 9054 features: jsons 9055 }; 9056 } 9057 }); 9058 9059 // @namespace GeoJSON 9060 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options) 9061 // Creates a GeoJSON layer. Optionally accepts an object in 9062 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map 9063 // (you can alternatively add it later with `addData` method) and an `options` object. 9064 function geoJSON(geojson, options) { 9065 return new GeoJSON(geojson, options); 9066 } 9067 9068 // Backward compatibility. 9069 var geoJson = geoJSON; 9070 9071 /* 9072 * @class ImageOverlay 9073 * @aka L.ImageOverlay 9074 * @inherits Interactive layer 9075 * 9076 * Used to load and display a single image over specific bounds of the map. Extends `Layer`. 9077 * 9078 * @example 9079 * 9080 * ```js 9081 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg', 9082 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]]; 9083 * L.imageOverlay(imageUrl, imageBounds).addTo(map); 9084 * ``` 9085 */ 9086 9087 var ImageOverlay = Layer.extend({ 9088 9089 // @section 9090 // @aka ImageOverlay options 9091 options: { 9092 // @option opacity: Number = 1.0 9093 // The opacity of the image overlay. 9094 opacity: 1, 9095 9096 // @option alt: String = '' 9097 // Text for the `alt` attribute of the image (useful for accessibility). 9098 alt: '', 9099 9100 // @option interactive: Boolean = false 9101 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 9102 interactive: false, 9103 9104 // @option crossOrigin: Boolean|String = false 9105 // Whether the crossOrigin attribute will be added to the image. 9106 // 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. 9107 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 9108 crossOrigin: false, 9109 9110 // @option errorOverlayUrl: String = '' 9111 // URL to the overlay image to show in place of the overlay that failed to load. 9112 errorOverlayUrl: '', 9113 9114 // @option zIndex: Number = 1 9115 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 9116 zIndex: 1, 9117 9118 // @option className: String = '' 9119 // A custom class name to assign to the image. Empty by default. 9120 className: '' 9121 }, 9122 9123 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) 9124 this._url = url; 9125 this._bounds = toLatLngBounds(bounds); 9126 9127 setOptions(this, options); 9128 }, 9129 9130 onAdd: function () { 9131 if (!this._image) { 9132 this._initImage(); 9133 9134 if (this.options.opacity < 1) { 9135 this._updateOpacity(); 9136 } 9137 } 9138 9139 if (this.options.interactive) { 9140 addClass(this._image, 'leaflet-interactive'); 9141 this.addInteractiveTarget(this._image); 9142 } 9143 9144 this.getPane().appendChild(this._image); 9145 this._reset(); 9146 }, 9147 9148 onRemove: function () { 9149 remove(this._image); 9150 if (this.options.interactive) { 9151 this.removeInteractiveTarget(this._image); 9152 } 9153 }, 9154 9155 // @method setOpacity(opacity: Number): this 9156 // Sets the opacity of the overlay. 9157 setOpacity: function (opacity) { 9158 this.options.opacity = opacity; 9159 9160 if (this._image) { 9161 this._updateOpacity(); 9162 } 9163 return this; 9164 }, 9165 9166 setStyle: function (styleOpts) { 9167 if (styleOpts.opacity) { 9168 this.setOpacity(styleOpts.opacity); 9169 } 9170 return this; 9171 }, 9172 9173 // @method bringToFront(): this 9174 // Brings the layer to the top of all overlays. 9175 bringToFront: function () { 9176 if (this._map) { 9177 toFront(this._image); 9178 } 9179 return this; 9180 }, 9181 9182 // @method bringToBack(): this 9183 // Brings the layer to the bottom of all overlays. 9184 bringToBack: function () { 9185 if (this._map) { 9186 toBack(this._image); 9187 } 9188 return this; 9189 }, 9190 9191 // @method setUrl(url: String): this 9192 // Changes the URL of the image. 9193 setUrl: function (url) { 9194 this._url = url; 9195 9196 if (this._image) { 9197 this._image.src = url; 9198 } 9199 return this; 9200 }, 9201 9202 // @method setBounds(bounds: LatLngBounds): this 9203 // Update the bounds that this ImageOverlay covers 9204 setBounds: function (bounds) { 9205 this._bounds = toLatLngBounds(bounds); 9206 9207 if (this._map) { 9208 this._reset(); 9209 } 9210 return this; 9211 }, 9212 9213 getEvents: function () { 9214 var events = { 9215 zoom: this._reset, 9216 viewreset: this._reset 9217 }; 9218 9219 if (this._zoomAnimated) { 9220 events.zoomanim = this._animateZoom; 9221 } 9222 9223 return events; 9224 }, 9225 9226 // @method setZIndex(value: Number): this 9227 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 9228 setZIndex: function (value) { 9229 this.options.zIndex = value; 9230 this._updateZIndex(); 9231 return this; 9232 }, 9233 9234 // @method getBounds(): LatLngBounds 9235 // Get the bounds that this ImageOverlay covers 9236 getBounds: function () { 9237 return this._bounds; 9238 }, 9239 9240 // @method getElement(): HTMLElement 9241 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 9242 // used by this overlay. 9243 getElement: function () { 9244 return this._image; 9245 }, 9246 9247 _initImage: function () { 9248 var wasElementSupplied = this._url.tagName === 'IMG'; 9249 var img = this._image = wasElementSupplied ? this._url : create$1('img'); 9250 9251 addClass(img, 'leaflet-image-layer'); 9252 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); } 9253 if (this.options.className) { addClass(img, this.options.className); } 9254 9255 img.onselectstart = falseFn; 9256 img.onmousemove = falseFn; 9257 9258 // @event load: Event 9259 // Fired when the ImageOverlay layer has loaded its image 9260 img.onload = bind(this.fire, this, 'load'); 9261 img.onerror = bind(this._overlayOnError, this, 'error'); 9262 9263 if (this.options.crossOrigin || this.options.crossOrigin === '') { 9264 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 9265 } 9266 9267 if (this.options.zIndex) { 9268 this._updateZIndex(); 9269 } 9270 9271 if (wasElementSupplied) { 9272 this._url = img.src; 9273 return; 9274 } 9275 9276 img.src = this._url; 9277 img.alt = this.options.alt; 9278 }, 9279 9280 _animateZoom: function (e) { 9281 var scale = this._map.getZoomScale(e.zoom), 9282 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 9283 9284 setTransform(this._image, offset, scale); 9285 }, 9286 9287 _reset: function () { 9288 var image = this._image, 9289 bounds = new Bounds( 9290 this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 9291 this._map.latLngToLayerPoint(this._bounds.getSouthEast())), 9292 size = bounds.getSize(); 9293 9294 setPosition(image, bounds.min); 9295 9296 image.style.width = size.x + 'px'; 9297 image.style.height = size.y + 'px'; 9298 }, 9299 9300 _updateOpacity: function () { 9301 setOpacity(this._image, this.options.opacity); 9302 }, 9303 9304 _updateZIndex: function () { 9305 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) { 9306 this._image.style.zIndex = this.options.zIndex; 9307 } 9308 }, 9309 9310 _overlayOnError: function () { 9311 // @event error: Event 9312 // Fired when the ImageOverlay layer fails to load its image 9313 this.fire('error'); 9314 9315 var errorUrl = this.options.errorOverlayUrl; 9316 if (errorUrl && this._url !== errorUrl) { 9317 this._url = errorUrl; 9318 this._image.src = errorUrl; 9319 } 9320 }, 9321 9322 // @method getCenter(): LatLng 9323 // Returns the center of the ImageOverlay. 9324 getCenter: function () { 9325 return this._bounds.getCenter(); 9326 } 9327 }); 9328 9329 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) 9330 // Instantiates an image overlay object given the URL of the image and the 9331 // geographical bounds it is tied to. 9332 var imageOverlay = function (url, bounds, options) { 9333 return new ImageOverlay(url, bounds, options); 9334 }; 9335 9336 /* 9337 * @class VideoOverlay 9338 * @aka L.VideoOverlay 9339 * @inherits ImageOverlay 9340 * 9341 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`. 9342 * 9343 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video) 9344 * HTML5 element. 9345 * 9346 * @example 9347 * 9348 * ```js 9349 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm', 9350 * videoBounds = [[ 32, -130], [ 13, -100]]; 9351 * L.videoOverlay(videoUrl, videoBounds ).addTo(map); 9352 * ``` 9353 */ 9354 9355 var VideoOverlay = ImageOverlay.extend({ 9356 9357 // @section 9358 // @aka VideoOverlay options 9359 options: { 9360 // @option autoplay: Boolean = true 9361 // Whether the video starts playing automatically when loaded. 9362 // On some browsers autoplay will only work with `muted: true` 9363 autoplay: true, 9364 9365 // @option loop: Boolean = true 9366 // Whether the video will loop back to the beginning when played. 9367 loop: true, 9368 9369 // @option keepAspectRatio: Boolean = true 9370 // Whether the video will save aspect ratio after the projection. 9371 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 9372 keepAspectRatio: true, 9373 9374 // @option muted: Boolean = false 9375 // Whether the video starts on mute when loaded. 9376 muted: false, 9377 9378 // @option playsInline: Boolean = true 9379 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode. 9380 playsInline: true 9381 }, 9382 9383 _initImage: function () { 9384 var wasElementSupplied = this._url.tagName === 'VIDEO'; 9385 var vid = this._image = wasElementSupplied ? this._url : create$1('video'); 9386 9387 addClass(vid, 'leaflet-image-layer'); 9388 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); } 9389 if (this.options.className) { addClass(vid, this.options.className); } 9390 9391 vid.onselectstart = falseFn; 9392 vid.onmousemove = falseFn; 9393 9394 // @event load: Event 9395 // Fired when the video has finished loading the first frame 9396 vid.onloadeddata = bind(this.fire, this, 'load'); 9397 9398 if (wasElementSupplied) { 9399 var sourceElements = vid.getElementsByTagName('source'); 9400 var sources = []; 9401 for (var j = 0; j < sourceElements.length; j++) { 9402 sources.push(sourceElements[j].src); 9403 } 9404 9405 this._url = (sourceElements.length > 0) ? sources : [vid.src]; 9406 return; 9407 } 9408 9409 if (!isArray(this._url)) { this._url = [this._url]; } 9410 9411 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) { 9412 vid.style['objectFit'] = 'fill'; 9413 } 9414 vid.autoplay = !!this.options.autoplay; 9415 vid.loop = !!this.options.loop; 9416 vid.muted = !!this.options.muted; 9417 vid.playsInline = !!this.options.playsInline; 9418 for (var i = 0; i < this._url.length; i++) { 9419 var source = create$1('source'); 9420 source.src = this._url[i]; 9421 vid.appendChild(source); 9422 } 9423 } 9424 9425 // @method getElement(): HTMLVideoElement 9426 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 9427 // used by this overlay. 9428 }); 9429 9430 9431 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options) 9432 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the 9433 // geographical bounds it is tied to. 9434 9435 function videoOverlay(video, bounds, options) { 9436 return new VideoOverlay(video, bounds, options); 9437 } 9438 9439 /* 9440 * @class SVGOverlay 9441 * @aka L.SVGOverlay 9442 * @inherits ImageOverlay 9443 * 9444 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`. 9445 * 9446 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element. 9447 * 9448 * @example 9449 * 9450 * ```js 9451 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 9452 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); 9453 * svgElement.setAttribute('viewBox', "0 0 200 200"); 9454 * 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"/>'; 9455 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ]; 9456 * L.svgOverlay(svgElement, svgElementBounds).addTo(map); 9457 * ``` 9458 */ 9459 9460 var SVGOverlay = ImageOverlay.extend({ 9461 _initImage: function () { 9462 var el = this._image = this._url; 9463 9464 addClass(el, 'leaflet-image-layer'); 9465 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); } 9466 if (this.options.className) { addClass(el, this.options.className); } 9467 9468 el.onselectstart = falseFn; 9469 el.onmousemove = falseFn; 9470 } 9471 9472 // @method getElement(): SVGElement 9473 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement) 9474 // used by this overlay. 9475 }); 9476 9477 9478 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options) 9479 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to. 9480 // A viewBox attribute is required on the SVG element to zoom in and out properly. 9481 9482 function svgOverlay(el, bounds, options) { 9483 return new SVGOverlay(el, bounds, options); 9484 } 9485 9486 /* 9487 * @class DivOverlay 9488 * @inherits Interactive layer 9489 * @aka L.DivOverlay 9490 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins. 9491 */ 9492 9493 // @namespace DivOverlay 9494 var DivOverlay = Layer.extend({ 9495 9496 // @section 9497 // @aka DivOverlay options 9498 options: { 9499 // @option interactive: Boolean = false 9500 // If true, the popup/tooltip will listen to the mouse events. 9501 interactive: false, 9502 9503 // @option offset: Point = Point(0, 0) 9504 // The offset of the overlay position. 9505 offset: [0, 0], 9506 9507 // @option className: String = '' 9508 // A custom CSS class name to assign to the overlay. 9509 className: '', 9510 9511 // @option pane: String = undefined 9512 // `Map pane` where the overlay will be added. 9513 pane: undefined 9514 }, 9515 9516 initialize: function (options, source) { 9517 setOptions(this, options); 9518 9519 this._source = source; 9520 }, 9521 9522 // @method openOn(map: Map): this 9523 // Adds the overlay to the map. 9524 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`. 9525 openOn: function (map) { 9526 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 9527 if (!map.hasLayer(this)) { 9528 map.addLayer(this); 9529 } 9530 return this; 9531 }, 9532 9533 // @method close(): this 9534 // Closes the overlay. 9535 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)` 9536 // and `layer.closePopup()`/`.closeTooltip()`. 9537 close: function () { 9538 if (this._map) { 9539 this._map.removeLayer(this); 9540 } 9541 return this; 9542 }, 9543 9544 // @method toggle(layer?: Layer): this 9545 // Opens or closes the overlay bound to layer depending on its current state. 9546 // Argument may be omitted only for overlay bound to layer. 9547 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`. 9548 toggle: function (layer) { 9549 if (this._map) { 9550 this.close(); 9551 } else { 9552 if (arguments.length) { 9553 this._source = layer; 9554 } else { 9555 layer = this._source; 9556 } 9557 this._prepareOpen(); 9558 9559 // open the overlay on the map 9560 this.openOn(layer._map); 9561 } 9562 return this; 9563 }, 9564 9565 onAdd: function (map) { 9566 this._zoomAnimated = map._zoomAnimated; 9567 9568 if (!this._container) { 9569 this._initLayout(); 9570 } 9571 9572 if (map._fadeAnimated) { 9573 setOpacity(this._container, 0); 9574 } 9575 9576 clearTimeout(this._removeTimeout); 9577 this.getPane().appendChild(this._container); 9578 this.update(); 9579 9580 if (map._fadeAnimated) { 9581 setOpacity(this._container, 1); 9582 } 9583 9584 this.bringToFront(); 9585 9586 if (this.options.interactive) { 9587 addClass(this._container, 'leaflet-interactive'); 9588 this.addInteractiveTarget(this._container); 9589 } 9590 }, 9591 9592 onRemove: function (map) { 9593 if (map._fadeAnimated) { 9594 setOpacity(this._container, 0); 9595 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200); 9596 } else { 9597 remove(this._container); 9598 } 9599 9600 if (this.options.interactive) { 9601 removeClass(this._container, 'leaflet-interactive'); 9602 this.removeInteractiveTarget(this._container); 9603 } 9604 }, 9605 9606 // @namespace DivOverlay 9607 // @method getLatLng: LatLng 9608 // Returns the geographical point of the overlay. 9609 getLatLng: function () { 9610 return this._latlng; 9611 }, 9612 9613 // @method setLatLng(latlng: LatLng): this 9614 // Sets the geographical point where the overlay will open. 9615 setLatLng: function (latlng) { 9616 this._latlng = toLatLng(latlng); 9617 if (this._map) { 9618 this._updatePosition(); 9619 this._adjustPan(); 9620 } 9621 return this; 9622 }, 9623 9624 // @method getContent: String|HTMLElement 9625 // Returns the content of the overlay. 9626 getContent: function () { 9627 return this._content; 9628 }, 9629 9630 // @method setContent(htmlContent: String|HTMLElement|Function): this 9631 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function. 9632 // The function should return a `String` or `HTMLElement` to be used in the overlay. 9633 setContent: function (content) { 9634 this._content = content; 9635 this.update(); 9636 return this; 9637 }, 9638 9639 // @method getElement: String|HTMLElement 9640 // Returns the HTML container of the overlay. 9641 getElement: function () { 9642 return this._container; 9643 }, 9644 9645 // @method update: null 9646 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded. 9647 update: function () { 9648 if (!this._map) { return; } 9649 9650 this._container.style.visibility = 'hidden'; 9651 9652 this._updateContent(); 9653 this._updateLayout(); 9654 this._updatePosition(); 9655 9656 this._container.style.visibility = ''; 9657 9658 this._adjustPan(); 9659 }, 9660 9661 getEvents: function () { 9662 var events = { 9663 zoom: this._updatePosition, 9664 viewreset: this._updatePosition 9665 }; 9666 9667 if (this._zoomAnimated) { 9668 events.zoomanim = this._animateZoom; 9669 } 9670 return events; 9671 }, 9672 9673 // @method isOpen: Boolean 9674 // Returns `true` when the overlay is visible on the map. 9675 isOpen: function () { 9676 return !!this._map && this._map.hasLayer(this); 9677 }, 9678 9679 // @method bringToFront: this 9680 // Brings this overlay in front of other overlays (in the same map pane). 9681 bringToFront: function () { 9682 if (this._map) { 9683 toFront(this._container); 9684 } 9685 return this; 9686 }, 9687 9688 // @method bringToBack: this 9689 // Brings this overlay to the back of other overlays (in the same map pane). 9690 bringToBack: function () { 9691 if (this._map) { 9692 toBack(this._container); 9693 } 9694 return this; 9695 }, 9696 9697 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup) 9698 _prepareOpen: function (latlng) { 9699 var source = this._source; 9700 if (!source._map) { return false; } 9701 9702 if (source instanceof FeatureGroup) { 9703 source = null; 9704 var layers = this._source._layers; 9705 for (var id in layers) { 9706 if (layers[id]._map) { 9707 source = layers[id]; 9708 break; 9709 } 9710 } 9711 if (!source) { return false; } // Unable to get source layer. 9712 9713 // set overlay source to this layer 9714 this._source = source; 9715 } 9716 9717 if (!latlng) { 9718 if (source.getCenter) { 9719 latlng = source.getCenter(); 9720 } else if (source.getLatLng) { 9721 latlng = source.getLatLng(); 9722 } else if (source.getBounds) { 9723 latlng = source.getBounds().getCenter(); 9724 } else { 9725 throw new Error('Unable to get source layer LatLng.'); 9726 } 9727 } 9728 this.setLatLng(latlng); 9729 9730 if (this._map) { 9731 // update the overlay (content, layout, etc...) 9732 this.update(); 9733 } 9734 9735 return true; 9736 }, 9737 9738 _updateContent: function () { 9739 if (!this._content) { return; } 9740 9741 var node = this._contentNode; 9742 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content; 9743 9744 if (typeof content === 'string') { 9745 node.innerHTML = content; 9746 } else { 9747 while (node.hasChildNodes()) { 9748 node.removeChild(node.firstChild); 9749 } 9750 node.appendChild(content); 9751 } 9752 9753 // @namespace DivOverlay 9754 // @section DivOverlay events 9755 // @event contentupdate: Event 9756 // Fired when the content of the overlay is updated 9757 this.fire('contentupdate'); 9758 }, 9759 9760 _updatePosition: function () { 9761 if (!this._map) { return; } 9762 9763 var pos = this._map.latLngToLayerPoint(this._latlng), 9764 offset = toPoint(this.options.offset), 9765 anchor = this._getAnchor(); 9766 9767 if (this._zoomAnimated) { 9768 setPosition(this._container, pos.add(anchor)); 9769 } else { 9770 offset = offset.add(pos).add(anchor); 9771 } 9772 9773 var bottom = this._containerBottom = -offset.y, 9774 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 9775 9776 // bottom position the overlay in case the height of the overlay changes (images loading etc) 9777 this._container.style.bottom = bottom + 'px'; 9778 this._container.style.left = left + 'px'; 9779 }, 9780 9781 _getAnchor: function () { 9782 return [0, 0]; 9783 } 9784 9785 }); 9786 9787 Map.include({ 9788 _initOverlay: function (OverlayClass, content, latlng, options) { 9789 var overlay = content; 9790 if (!(overlay instanceof OverlayClass)) { 9791 overlay = new OverlayClass(options).setContent(content); 9792 } 9793 if (latlng) { 9794 overlay.setLatLng(latlng); 9795 } 9796 return overlay; 9797 } 9798 }); 9799 9800 9801 Layer.include({ 9802 _initOverlay: function (OverlayClass, old, content, options) { 9803 var overlay = content; 9804 if (overlay instanceof OverlayClass) { 9805 setOptions(overlay, options); 9806 overlay._source = this; 9807 } else { 9808 overlay = (old && !options) ? old : new OverlayClass(options, this); 9809 overlay.setContent(content); 9810 } 9811 return overlay; 9812 } 9813 }); 9814 9815 /* 9816 * @class Popup 9817 * @inherits DivOverlay 9818 * @aka L.Popup 9819 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to 9820 * open popups while making sure that only one popup is open at one time 9821 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want. 9822 * 9823 * @example 9824 * 9825 * If you want to just bind a popup to marker click and then open it, it's really easy: 9826 * 9827 * ```js 9828 * marker.bindPopup(popupContent).openPopup(); 9829 * ``` 9830 * Path overlays like polylines also have a `bindPopup` method. 9831 * Here's a more complicated way to open a popup on a map: 9832 * 9833 * ```js 9834 * var popup = L.popup() 9835 * .setLatLng(latlng) 9836 * .setContent('<p>Hello world!<br />This is a nice popup.</p>') 9837 * .openOn(map); 9838 * ``` 9839 */ 9840 9841 9842 // @namespace Popup 9843 var Popup = DivOverlay.extend({ 9844 9845 // @section 9846 // @aka Popup options 9847 options: { 9848 // @option pane: String = 'popupPane' 9849 // `Map pane` where the popup will be added. 9850 pane: 'popupPane', 9851 9852 // @option offset: Point = Point(0, 7) 9853 // The offset of the popup position. 9854 offset: [0, 7], 9855 9856 // @option maxWidth: Number = 300 9857 // Max width of the popup, in pixels. 9858 maxWidth: 300, 9859 9860 // @option minWidth: Number = 50 9861 // Min width of the popup, in pixels. 9862 minWidth: 50, 9863 9864 // @option maxHeight: Number = null 9865 // If set, creates a scrollable container of the given height 9866 // inside a popup if its content exceeds it. 9867 maxHeight: null, 9868 9869 // @option autoPan: Boolean = true 9870 // Set it to `false` if you don't want the map to do panning animation 9871 // to fit the opened popup. 9872 autoPan: true, 9873 9874 // @option autoPanPaddingTopLeft: Point = null 9875 // The margin between the popup and the top left corner of the map 9876 // view after autopanning was performed. 9877 autoPanPaddingTopLeft: null, 9878 9879 // @option autoPanPaddingBottomRight: Point = null 9880 // The margin between the popup and the bottom right corner of the map 9881 // view after autopanning was performed. 9882 autoPanPaddingBottomRight: null, 9883 9884 // @option autoPanPadding: Point = Point(5, 5) 9885 // Equivalent of setting both top left and bottom right autopan padding to the same value. 9886 autoPanPadding: [5, 5], 9887 9888 // @option keepInView: Boolean = false 9889 // Set it to `true` if you want to prevent users from panning the popup 9890 // off of the screen while it is open. 9891 keepInView: false, 9892 9893 // @option closeButton: Boolean = true 9894 // Controls the presence of a close button in the popup. 9895 closeButton: true, 9896 9897 // @option autoClose: Boolean = true 9898 // Set it to `false` if you want to override the default behavior of 9899 // the popup closing when another popup is opened. 9900 autoClose: true, 9901 9902 // @option closeOnEscapeKey: Boolean = true 9903 // Set it to `false` if you want to override the default behavior of 9904 // the ESC key for closing of the popup. 9905 closeOnEscapeKey: true, 9906 9907 // @option closeOnClick: Boolean = * 9908 // Set it if you want to override the default behavior of the popup closing when user clicks 9909 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 9910 9911 // @option className: String = '' 9912 // A custom CSS class name to assign to the popup. 9913 className: '' 9914 }, 9915 9916 // @namespace Popup 9917 // @method openOn(map: Map): this 9918 // Alternative to `map.openPopup(popup)`. 9919 // Adds the popup to the map and closes the previous one. 9920 openOn: function (map) { 9921 map = arguments.length ? map : this._source._map; // experimental, not the part of public api 9922 9923 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) { 9924 map.removeLayer(map._popup); 9925 } 9926 map._popup = this; 9927 9928 return DivOverlay.prototype.openOn.call(this, map); 9929 }, 9930 9931 onAdd: function (map) { 9932 DivOverlay.prototype.onAdd.call(this, map); 9933 9934 // @namespace Map 9935 // @section Popup events 9936 // @event popupopen: PopupEvent 9937 // Fired when a popup is opened in the map 9938 map.fire('popupopen', {popup: this}); 9939 9940 if (this._source) { 9941 // @namespace Layer 9942 // @section Popup events 9943 // @event popupopen: PopupEvent 9944 // Fired when a popup bound to this layer is opened 9945 this._source.fire('popupopen', {popup: this}, true); 9946 // For non-path layers, we toggle the popup when clicking 9947 // again the layer, so prevent the map to reopen it. 9948 if (!(this._source instanceof Path)) { 9949 this._source.on('preclick', stopPropagation); 9950 } 9951 } 9952 }, 9953 9954 onRemove: function (map) { 9955 DivOverlay.prototype.onRemove.call(this, map); 9956 9957 // @namespace Map 9958 // @section Popup events 9959 // @event popupclose: PopupEvent 9960 // Fired when a popup in the map is closed 9961 map.fire('popupclose', {popup: this}); 9962 9963 if (this._source) { 9964 // @namespace Layer 9965 // @section Popup events 9966 // @event popupclose: PopupEvent 9967 // Fired when a popup bound to this layer is closed 9968 this._source.fire('popupclose', {popup: this}, true); 9969 if (!(this._source instanceof Path)) { 9970 this._source.off('preclick', stopPropagation); 9971 } 9972 } 9973 }, 9974 9975 getEvents: function () { 9976 var events = DivOverlay.prototype.getEvents.call(this); 9977 9978 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 9979 events.preclick = this.close; 9980 } 9981 9982 if (this.options.keepInView) { 9983 events.moveend = this._adjustPan; 9984 } 9985 9986 return events; 9987 }, 9988 9989 _initLayout: function () { 9990 var prefix = 'leaflet-popup', 9991 container = this._container = create$1('div', 9992 prefix + ' ' + (this.options.className || '') + 9993 ' leaflet-zoom-animated'); 9994 9995 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container); 9996 this._contentNode = create$1('div', prefix + '-content', wrapper); 9997 9998 disableClickPropagation(container); 9999 disableScrollPropagation(this._contentNode); 10000 on(container, 'contextmenu', stopPropagation); 10001 10002 this._tipContainer = create$1('div', prefix + '-tip-container', container); 10003 this._tip = create$1('div', prefix + '-tip', this._tipContainer); 10004 10005 if (this.options.closeButton) { 10006 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container); 10007 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399 10008 closeButton.setAttribute('aria-label', 'Close popup'); 10009 closeButton.href = '#close'; 10010 closeButton.innerHTML = '<span aria-hidden="true">×</span>'; 10011 10012 on(closeButton, 'click', this.close, this); 10013 } 10014 }, 10015 10016 _updateLayout: function () { 10017 var container = this._contentNode, 10018 style = container.style; 10019 10020 style.width = ''; 10021 style.whiteSpace = 'nowrap'; 10022 10023 var width = container.offsetWidth; 10024 width = Math.min(width, this.options.maxWidth); 10025 width = Math.max(width, this.options.minWidth); 10026 10027 style.width = (width + 1) + 'px'; 10028 style.whiteSpace = ''; 10029 10030 style.height = ''; 10031 10032 var height = container.offsetHeight, 10033 maxHeight = this.options.maxHeight, 10034 scrolledClass = 'leaflet-popup-scrolled'; 10035 10036 if (maxHeight && height > maxHeight) { 10037 style.height = maxHeight + 'px'; 10038 addClass(container, scrolledClass); 10039 } else { 10040 removeClass(container, scrolledClass); 10041 } 10042 10043 this._containerWidth = this._container.offsetWidth; 10044 }, 10045 10046 _animateZoom: function (e) { 10047 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), 10048 anchor = this._getAnchor(); 10049 setPosition(this._container, pos.add(anchor)); 10050 }, 10051 10052 _adjustPan: function (e) { 10053 if (!this.options.autoPan) { return; } 10054 if (this._map._panAnim) { this._map._panAnim.stop(); } 10055 10056 var map = this._map, 10057 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0, 10058 containerHeight = this._container.offsetHeight + marginBottom, 10059 containerWidth = this._containerWidth, 10060 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 10061 10062 layerPos._add(getPosition(this._container)); 10063 10064 var containerPos = map.layerPointToContainerPoint(layerPos), 10065 padding = toPoint(this.options.autoPanPadding), 10066 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), 10067 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), 10068 size = map.getSize(), 10069 dx = 0, 10070 dy = 0; 10071 10072 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right 10073 dx = containerPos.x + containerWidth - size.x + paddingBR.x; 10074 } 10075 if (containerPos.x - dx - paddingTL.x < 0) { // left 10076 dx = containerPos.x - paddingTL.x; 10077 } 10078 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom 10079 dy = containerPos.y + containerHeight - size.y + paddingBR.y; 10080 } 10081 if (containerPos.y - dy - paddingTL.y < 0) { // top 10082 dy = containerPos.y - paddingTL.y; 10083 } 10084 10085 // @namespace Map 10086 // @section Popup events 10087 // @event autopanstart: Event 10088 // Fired when the map starts autopanning when opening a popup. 10089 if (dx || dy) { 10090 map 10091 .fire('autopanstart') 10092 .panBy([dx, dy], {animate: e && e.type === 'moveend'}); 10093 } 10094 }, 10095 10096 _getAnchor: function () { 10097 // Where should we anchor the popup on the source layer? 10098 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 10099 } 10100 10101 }); 10102 10103 // @namespace Popup 10104 // @factory L.popup(options?: Popup options, source?: Layer) 10105 // 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. 10106 var popup = function (options, source) { 10107 return new Popup(options, source); 10108 }; 10109 10110 10111 /* @namespace Map 10112 * @section Interaction Options 10113 * @option closePopupOnClick: Boolean = true 10114 * Set it to `false` if you don't want popups to close when user clicks the map. 10115 */ 10116 Map.mergeOptions({ 10117 closePopupOnClick: true 10118 }); 10119 10120 10121 // @namespace Map 10122 // @section Methods for Layers and Controls 10123 Map.include({ 10124 // @method openPopup(popup: Popup): this 10125 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 10126 // @alternative 10127 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 10128 // Creates a popup with the specified content and options and opens it in the given point on a map. 10129 openPopup: function (popup, latlng, options) { 10130 this._initOverlay(Popup, popup, latlng, options) 10131 .openOn(this); 10132 10133 return this; 10134 }, 10135 10136 // @method closePopup(popup?: Popup): this 10137 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 10138 closePopup: function (popup) { 10139 popup = arguments.length ? popup : this._popup; 10140 if (popup) { 10141 popup.close(); 10142 } 10143 return this; 10144 } 10145 }); 10146 10147 /* 10148 * @namespace Layer 10149 * @section Popup methods example 10150 * 10151 * All layers share a set of methods convenient for binding popups to it. 10152 * 10153 * ```js 10154 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map); 10155 * layer.openPopup(); 10156 * layer.closePopup(); 10157 * ``` 10158 * 10159 * 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. 10160 */ 10161 10162 // @section Popup methods 10163 Layer.include({ 10164 10165 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 10166 // Binds a popup to the layer with the passed `content` and sets up the 10167 // necessary event listeners. If a `Function` is passed it will receive 10168 // the layer as the first argument and should return a `String` or `HTMLElement`. 10169 bindPopup: function (content, options) { 10170 this._popup = this._initOverlay(Popup, this._popup, content, options); 10171 if (!this._popupHandlersAdded) { 10172 this.on({ 10173 click: this._openPopup, 10174 keypress: this._onKeyPress, 10175 remove: this.closePopup, 10176 move: this._movePopup 10177 }); 10178 this._popupHandlersAdded = true; 10179 } 10180 10181 return this; 10182 }, 10183 10184 // @method unbindPopup(): this 10185 // Removes the popup previously bound with `bindPopup`. 10186 unbindPopup: function () { 10187 if (this._popup) { 10188 this.off({ 10189 click: this._openPopup, 10190 keypress: this._onKeyPress, 10191 remove: this.closePopup, 10192 move: this._movePopup 10193 }); 10194 this._popupHandlersAdded = false; 10195 this._popup = null; 10196 } 10197 return this; 10198 }, 10199 10200 // @method openPopup(latlng?: LatLng): this 10201 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 10202 openPopup: function (latlng) { 10203 if (this._popup && this._popup._prepareOpen(latlng)) { 10204 // open the popup on the map 10205 this._popup.openOn(this._map); 10206 } 10207 return this; 10208 }, 10209 10210 // @method closePopup(): this 10211 // Closes the popup bound to this layer if it is open. 10212 closePopup: function () { 10213 if (this._popup) { 10214 this._popup.close(); 10215 } 10216 return this; 10217 }, 10218 10219 // @method togglePopup(): this 10220 // Opens or closes the popup bound to this layer depending on its current state. 10221 togglePopup: function () { 10222 if (this._popup) { 10223 this._popup.toggle(this); 10224 } 10225 return this; 10226 }, 10227 10228 // @method isPopupOpen(): boolean 10229 // Returns `true` if the popup bound to this layer is currently open. 10230 isPopupOpen: function () { 10231 return (this._popup ? this._popup.isOpen() : false); 10232 }, 10233 10234 // @method setPopupContent(content: String|HTMLElement|Popup): this 10235 // Sets the content of the popup bound to this layer. 10236 setPopupContent: function (content) { 10237 if (this._popup) { 10238 this._popup.setContent(content); 10239 } 10240 return this; 10241 }, 10242 10243 // @method getPopup(): Popup 10244 // Returns the popup bound to this layer. 10245 getPopup: function () { 10246 return this._popup; 10247 }, 10248 10249 _openPopup: function (e) { 10250 if (!this._popup || !this._map) { 10251 return; 10252 } 10253 // prevent map click 10254 stop(e); 10255 10256 var target = e.layer || e.target; 10257 if (this._popup._source === target && !(target instanceof Path)) { 10258 // treat it like a marker and figure out 10259 // if we should toggle it open/closed 10260 if (this._map.hasLayer(this._popup)) { 10261 this.closePopup(); 10262 } else { 10263 this.openPopup(e.latlng); 10264 } 10265 return; 10266 } 10267 this._popup._source = target; 10268 this.openPopup(e.latlng); 10269 }, 10270 10271 _movePopup: function (e) { 10272 this._popup.setLatLng(e.latlng); 10273 }, 10274 10275 _onKeyPress: function (e) { 10276 if (e.originalEvent.keyCode === 13) { 10277 this._openPopup(e); 10278 } 10279 } 10280 }); 10281 10282 /* 10283 * @class Tooltip 10284 * @inherits DivOverlay 10285 * @aka L.Tooltip 10286 * Used to display small texts on top of map layers. 10287 * 10288 * @example 10289 * 10290 * ```js 10291 * marker.bindTooltip("my tooltip text").openTooltip(); 10292 * ``` 10293 * Note about tooltip offset. Leaflet takes two options in consideration 10294 * for computing tooltip offsetting: 10295 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip. 10296 * Add a positive x offset to move the tooltip to the right, and a positive y offset to 10297 * move it to the bottom. Negatives will move to the left and top. 10298 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You 10299 * should adapt this value if you use a custom icon. 10300 */ 10301 10302 10303 // @namespace Tooltip 10304 var Tooltip = DivOverlay.extend({ 10305 10306 // @section 10307 // @aka Tooltip options 10308 options: { 10309 // @option pane: String = 'tooltipPane' 10310 // `Map pane` where the tooltip will be added. 10311 pane: 'tooltipPane', 10312 10313 // @option offset: Point = Point(0, 0) 10314 // Optional offset of the tooltip position. 10315 offset: [0, 0], 10316 10317 // @option direction: String = 'auto' 10318 // Direction where to open the tooltip. Possible values are: `right`, `left`, 10319 // `top`, `bottom`, `center`, `auto`. 10320 // `auto` will dynamically switch between `right` and `left` according to the tooltip 10321 // position on the map. 10322 direction: 'auto', 10323 10324 // @option permanent: Boolean = false 10325 // Whether to open the tooltip permanently or only on mouseover. 10326 permanent: false, 10327 10328 // @option sticky: Boolean = false 10329 // If true, the tooltip will follow the mouse instead of being fixed at the feature center. 10330 sticky: false, 10331 10332 // @option opacity: Number = 0.9 10333 // Tooltip container opacity. 10334 opacity: 0.9 10335 }, 10336 10337 onAdd: function (map) { 10338 DivOverlay.prototype.onAdd.call(this, map); 10339 this.setOpacity(this.options.opacity); 10340 10341 // @namespace Map 10342 // @section Tooltip events 10343 // @event tooltipopen: TooltipEvent 10344 // Fired when a tooltip is opened in the map. 10345 map.fire('tooltipopen', {tooltip: this}); 10346 10347 if (this._source) { 10348 this.addEventParent(this._source); 10349 10350 // @namespace Layer 10351 // @section Tooltip events 10352 // @event tooltipopen: TooltipEvent 10353 // Fired when a tooltip bound to this layer is opened. 10354 this._source.fire('tooltipopen', {tooltip: this}, true); 10355 } 10356 }, 10357 10358 onRemove: function (map) { 10359 DivOverlay.prototype.onRemove.call(this, map); 10360 10361 // @namespace Map 10362 // @section Tooltip events 10363 // @event tooltipclose: TooltipEvent 10364 // Fired when a tooltip in the map is closed. 10365 map.fire('tooltipclose', {tooltip: this}); 10366 10367 if (this._source) { 10368 this.removeEventParent(this._source); 10369 10370 // @namespace Layer 10371 // @section Tooltip events 10372 // @event tooltipclose: TooltipEvent 10373 // Fired when a tooltip bound to this layer is closed. 10374 this._source.fire('tooltipclose', {tooltip: this}, true); 10375 } 10376 }, 10377 10378 getEvents: function () { 10379 var events = DivOverlay.prototype.getEvents.call(this); 10380 10381 if (!this.options.permanent) { 10382 events.preclick = this.close; 10383 } 10384 10385 return events; 10386 }, 10387 10388 _initLayout: function () { 10389 var prefix = 'leaflet-tooltip', 10390 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 10391 10392 this._contentNode = this._container = create$1('div', className); 10393 }, 10394 10395 _updateLayout: function () {}, 10396 10397 _adjustPan: function () {}, 10398 10399 _setPosition: function (pos) { 10400 var subX, subY, 10401 map = this._map, 10402 container = this._container, 10403 centerPoint = map.latLngToContainerPoint(map.getCenter()), 10404 tooltipPoint = map.layerPointToContainerPoint(pos), 10405 direction = this.options.direction, 10406 tooltipWidth = container.offsetWidth, 10407 tooltipHeight = container.offsetHeight, 10408 offset = toPoint(this.options.offset), 10409 anchor = this._getAnchor(); 10410 10411 if (direction === 'top') { 10412 subX = tooltipWidth / 2; 10413 subY = tooltipHeight; 10414 } else if (direction === 'bottom') { 10415 subX = tooltipWidth / 2; 10416 subY = 0; 10417 } else if (direction === 'center') { 10418 subX = tooltipWidth / 2; 10419 subY = tooltipHeight / 2; 10420 } else if (direction === 'right') { 10421 subX = 0; 10422 subY = tooltipHeight / 2; 10423 } else if (direction === 'left') { 10424 subX = tooltipWidth; 10425 subY = tooltipHeight / 2; 10426 } else if (tooltipPoint.x < centerPoint.x) { 10427 direction = 'right'; 10428 subX = 0; 10429 subY = tooltipHeight / 2; 10430 } else { 10431 direction = 'left'; 10432 subX = tooltipWidth + (offset.x + anchor.x) * 2; 10433 subY = tooltipHeight / 2; 10434 } 10435 10436 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor); 10437 10438 removeClass(container, 'leaflet-tooltip-right'); 10439 removeClass(container, 'leaflet-tooltip-left'); 10440 removeClass(container, 'leaflet-tooltip-top'); 10441 removeClass(container, 'leaflet-tooltip-bottom'); 10442 addClass(container, 'leaflet-tooltip-' + direction); 10443 setPosition(container, pos); 10444 }, 10445 10446 _updatePosition: function () { 10447 var pos = this._map.latLngToLayerPoint(this._latlng); 10448 this._setPosition(pos); 10449 }, 10450 10451 setOpacity: function (opacity) { 10452 this.options.opacity = opacity; 10453 10454 if (this._container) { 10455 setOpacity(this._container, opacity); 10456 } 10457 }, 10458 10459 _animateZoom: function (e) { 10460 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 10461 this._setPosition(pos); 10462 }, 10463 10464 _getAnchor: function () { 10465 // Where should we anchor the tooltip on the source layer? 10466 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 10467 } 10468 10469 }); 10470 10471 // @namespace Tooltip 10472 // @factory L.tooltip(options?: Tooltip options, source?: Layer) 10473 // 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. 10474 var tooltip = function (options, source) { 10475 return new Tooltip(options, source); 10476 }; 10477 10478 // @namespace Map 10479 // @section Methods for Layers and Controls 10480 Map.include({ 10481 10482 // @method openTooltip(tooltip: Tooltip): this 10483 // Opens the specified tooltip. 10484 // @alternative 10485 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 10486 // Creates a tooltip with the specified content and options and open it. 10487 openTooltip: function (tooltip, latlng, options) { 10488 this._initOverlay(Tooltip, tooltip, latlng, options) 10489 .openOn(this); 10490 10491 return this; 10492 }, 10493 10494 // @method closeTooltip(tooltip: Tooltip): this 10495 // Closes the tooltip given as parameter. 10496 closeTooltip: function (tooltip) { 10497 tooltip.close(); 10498 return this; 10499 } 10500 10501 }); 10502 10503 /* 10504 * @namespace Layer 10505 * @section Tooltip methods example 10506 * 10507 * All layers share a set of methods convenient for binding tooltips to it. 10508 * 10509 * ```js 10510 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map); 10511 * layer.openTooltip(); 10512 * layer.closeTooltip(); 10513 * ``` 10514 */ 10515 10516 // @section Tooltip methods 10517 Layer.include({ 10518 10519 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 10520 // Binds a tooltip to the layer with the passed `content` and sets up the 10521 // necessary event listeners. If a `Function` is passed it will receive 10522 // the layer as the first argument and should return a `String` or `HTMLElement`. 10523 bindTooltip: function (content, options) { 10524 10525 if (this._tooltip && this.isTooltipOpen()) { 10526 this.unbindTooltip(); 10527 } 10528 10529 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options); 10530 this._initTooltipInteractions(); 10531 10532 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 10533 this.openTooltip(); 10534 } 10535 10536 return this; 10537 }, 10538 10539 // @method unbindTooltip(): this 10540 // Removes the tooltip previously bound with `bindTooltip`. 10541 unbindTooltip: function () { 10542 if (this._tooltip) { 10543 this._initTooltipInteractions(true); 10544 this.closeTooltip(); 10545 this._tooltip = null; 10546 } 10547 return this; 10548 }, 10549 10550 _initTooltipInteractions: function (remove) { 10551 if (!remove && this._tooltipHandlersAdded) { return; } 10552 var onOff = remove ? 'off' : 'on', 10553 events = { 10554 remove: this.closeTooltip, 10555 move: this._moveTooltip 10556 }; 10557 if (!this._tooltip.options.permanent) { 10558 events.mouseover = this._openTooltip; 10559 events.mouseout = this.closeTooltip; 10560 events.click = this._openTooltip; 10561 } else { 10562 events.add = this._openTooltip; 10563 } 10564 if (this._tooltip.options.sticky) { 10565 events.mousemove = this._moveTooltip; 10566 } 10567 this[onOff](events); 10568 this._tooltipHandlersAdded = !remove; 10569 }, 10570 10571 // @method openTooltip(latlng?: LatLng): this 10572 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 10573 openTooltip: function (latlng) { 10574 if (this._tooltip && this._tooltip._prepareOpen(latlng)) { 10575 // open the tooltip on the map 10576 this._tooltip.openOn(this._map); 10577 } 10578 return this; 10579 }, 10580 10581 // @method closeTooltip(): this 10582 // Closes the tooltip bound to this layer if it is open. 10583 closeTooltip: function () { 10584 if (this._tooltip) { 10585 return this._tooltip.close(); 10586 } 10587 }, 10588 10589 // @method toggleTooltip(): this 10590 // Opens or closes the tooltip bound to this layer depending on its current state. 10591 toggleTooltip: function () { 10592 if (this._tooltip) { 10593 this._tooltip.toggle(this); 10594 } 10595 return this; 10596 }, 10597 10598 // @method isTooltipOpen(): boolean 10599 // Returns `true` if the tooltip bound to this layer is currently open. 10600 isTooltipOpen: function () { 10601 return this._tooltip.isOpen(); 10602 }, 10603 10604 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this 10605 // Sets the content of the tooltip bound to this layer. 10606 setTooltipContent: function (content) { 10607 if (this._tooltip) { 10608 this._tooltip.setContent(content); 10609 } 10610 return this; 10611 }, 10612 10613 // @method getTooltip(): Tooltip 10614 // Returns the tooltip bound to this layer. 10615 getTooltip: function () { 10616 return this._tooltip; 10617 }, 10618 10619 _openTooltip: function (e) { 10620 if (!this._tooltip || !this._map || (this._map.dragging && this._map.dragging.moving())) { 10621 return; 10622 } 10623 this._tooltip._source = e.layer || e.target; 10624 10625 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined); 10626 }, 10627 10628 _moveTooltip: function (e) { 10629 var latlng = e.latlng, containerPoint, layerPoint; 10630 if (this._tooltip.options.sticky && e.originalEvent) { 10631 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 10632 layerPoint = this._map.containerPointToLayerPoint(containerPoint); 10633 latlng = this._map.layerPointToLatLng(layerPoint); 10634 } 10635 this._tooltip.setLatLng(latlng); 10636 } 10637 }); 10638 10639 /* 10640 * @class DivIcon 10641 * @aka L.DivIcon 10642 * @inherits Icon 10643 * 10644 * Represents a lightweight icon for markers that uses a simple `<div>` 10645 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options. 10646 * 10647 * @example 10648 * ```js 10649 * var myIcon = L.divIcon({className: 'my-div-icon'}); 10650 * // you can set .my-div-icon styles in CSS 10651 * 10652 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map); 10653 * ``` 10654 * 10655 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. 10656 */ 10657 10658 var DivIcon = Icon.extend({ 10659 options: { 10660 // @section 10661 // @aka DivIcon options 10662 iconSize: [12, 12], // also can be set through CSS 10663 10664 // iconAnchor: (Point), 10665 // popupAnchor: (Point), 10666 10667 // @option html: String|HTMLElement = '' 10668 // Custom HTML code to put inside the div element, empty by default. Alternatively, 10669 // an instance of `HTMLElement`. 10670 html: false, 10671 10672 // @option bgPos: Point = [0, 0] 10673 // Optional relative position of the background, in pixels 10674 bgPos: null, 10675 10676 className: 'leaflet-div-icon' 10677 }, 10678 10679 createIcon: function (oldIcon) { 10680 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'), 10681 options = this.options; 10682 10683 if (options.html instanceof Element) { 10684 empty(div); 10685 div.appendChild(options.html); 10686 } else { 10687 div.innerHTML = options.html !== false ? options.html : ''; 10688 } 10689 10690 if (options.bgPos) { 10691 var bgPos = toPoint(options.bgPos); 10692 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; 10693 } 10694 this._setIconStyles(div, 'icon'); 10695 10696 return div; 10697 }, 10698 10699 createShadow: function () { 10700 return null; 10701 } 10702 }); 10703 10704 // @factory L.divIcon(options: DivIcon options) 10705 // Creates a `DivIcon` instance with the given options. 10706 function divIcon(options) { 10707 return new DivIcon(options); 10708 } 10709 10710 Icon.Default = IconDefault; 10711 10712 /* 10713 * @class GridLayer 10714 * @inherits Layer 10715 * @aka L.GridLayer 10716 * 10717 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`. 10718 * 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. 10719 * 10720 * 10721 * @section Synchronous usage 10722 * @example 10723 * 10724 * 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. 10725 * 10726 * ```js 10727 * var CanvasLayer = L.GridLayer.extend({ 10728 * createTile: function(coords){ 10729 * // create a <canvas> element for drawing 10730 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10731 * 10732 * // setup tile width and height according to the options 10733 * var size = this.getTileSize(); 10734 * tile.width = size.x; 10735 * tile.height = size.y; 10736 * 10737 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z 10738 * var ctx = tile.getContext('2d'); 10739 * 10740 * // return the tile so it can be rendered on screen 10741 * return tile; 10742 * } 10743 * }); 10744 * ``` 10745 * 10746 * @section Asynchronous usage 10747 * @example 10748 * 10749 * 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. 10750 * 10751 * ```js 10752 * var CanvasLayer = L.GridLayer.extend({ 10753 * createTile: function(coords, done){ 10754 * var error; 10755 * 10756 * // create a <canvas> element for drawing 10757 * var tile = L.DomUtil.create('canvas', 'leaflet-tile'); 10758 * 10759 * // setup tile width and height according to the options 10760 * var size = this.getTileSize(); 10761 * tile.width = size.x; 10762 * tile.height = size.y; 10763 * 10764 * // draw something asynchronously and pass the tile to the done() callback 10765 * setTimeout(function() { 10766 * done(error, tile); 10767 * }, 1000); 10768 * 10769 * return tile; 10770 * } 10771 * }); 10772 * ``` 10773 * 10774 * @section 10775 */ 10776 10777 10778 var GridLayer = Layer.extend({ 10779 10780 // @section 10781 // @aka GridLayer options 10782 options: { 10783 // @option tileSize: Number|Point = 256 10784 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 10785 tileSize: 256, 10786 10787 // @option opacity: Number = 1.0 10788 // Opacity of the tiles. Can be used in the `createTile()` function. 10789 opacity: 1, 10790 10791 // @option updateWhenIdle: Boolean = (depends) 10792 // Load new tiles only when panning ends. 10793 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 10794 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 10795 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 10796 updateWhenIdle: Browser.mobile, 10797 10798 // @option updateWhenZooming: Boolean = true 10799 // 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. 10800 updateWhenZooming: true, 10801 10802 // @option updateInterval: Number = 200 10803 // Tiles will not update more than once every `updateInterval` milliseconds when panning. 10804 updateInterval: 200, 10805 10806 // @option zIndex: Number = 1 10807 // The explicit zIndex of the tile layer. 10808 zIndex: 1, 10809 10810 // @option bounds: LatLngBounds = undefined 10811 // If set, tiles will only be loaded inside the set `LatLngBounds`. 10812 bounds: null, 10813 10814 // @option minZoom: Number = 0 10815 // The minimum zoom level down to which this layer will be displayed (inclusive). 10816 minZoom: 0, 10817 10818 // @option maxZoom: Number = undefined 10819 // The maximum zoom level up to which this layer will be displayed (inclusive). 10820 maxZoom: undefined, 10821 10822 // @option maxNativeZoom: Number = undefined 10823 // Maximum zoom number the tile source has available. If it is specified, 10824 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 10825 // from `maxNativeZoom` level and auto-scaled. 10826 maxNativeZoom: undefined, 10827 10828 // @option minNativeZoom: Number = undefined 10829 // Minimum zoom number the tile source has available. If it is specified, 10830 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 10831 // from `minNativeZoom` level and auto-scaled. 10832 minNativeZoom: undefined, 10833 10834 // @option noWrap: Boolean = false 10835 // Whether the layer is wrapped around the antimeridian. If `true`, the 10836 // GridLayer will only be displayed once at low zoom levels. Has no 10837 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 10838 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 10839 // tiles outside the CRS limits. 10840 noWrap: false, 10841 10842 // @option pane: String = 'tilePane' 10843 // `Map pane` where the grid layer will be added. 10844 pane: 'tilePane', 10845 10846 // @option className: String = '' 10847 // A custom class name to assign to the tile layer. Empty by default. 10848 className: '', 10849 10850 // @option keepBuffer: Number = 2 10851 // When panning the map, keep this many rows and columns of tiles before unloading them. 10852 keepBuffer: 2 10853 }, 10854 10855 initialize: function (options) { 10856 setOptions(this, options); 10857 }, 10858 10859 onAdd: function () { 10860 this._initContainer(); 10861 10862 this._levels = {}; 10863 this._tiles = {}; 10864 10865 this._resetView(); // implicit _update() call 10866 }, 10867 10868 beforeAdd: function (map) { 10869 map._addZoomLimit(this); 10870 }, 10871 10872 onRemove: function (map) { 10873 this._removeAllTiles(); 10874 remove(this._container); 10875 map._removeZoomLimit(this); 10876 this._container = null; 10877 this._tileZoom = undefined; 10878 }, 10879 10880 // @method bringToFront: this 10881 // Brings the tile layer to the top of all tile layers. 10882 bringToFront: function () { 10883 if (this._map) { 10884 toFront(this._container); 10885 this._setAutoZIndex(Math.max); 10886 } 10887 return this; 10888 }, 10889 10890 // @method bringToBack: this 10891 // Brings the tile layer to the bottom of all tile layers. 10892 bringToBack: function () { 10893 if (this._map) { 10894 toBack(this._container); 10895 this._setAutoZIndex(Math.min); 10896 } 10897 return this; 10898 }, 10899 10900 // @method getContainer: HTMLElement 10901 // Returns the HTML element that contains the tiles for this layer. 10902 getContainer: function () { 10903 return this._container; 10904 }, 10905 10906 // @method setOpacity(opacity: Number): this 10907 // Changes the [opacity](#gridlayer-opacity) of the grid layer. 10908 setOpacity: function (opacity) { 10909 this.options.opacity = opacity; 10910 this._updateOpacity(); 10911 return this; 10912 }, 10913 10914 // @method setZIndex(zIndex: Number): this 10915 // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 10916 setZIndex: function (zIndex) { 10917 this.options.zIndex = zIndex; 10918 this._updateZIndex(); 10919 10920 return this; 10921 }, 10922 10923 // @method isLoading: Boolean 10924 // Returns `true` if any tile in the grid layer has not finished loading. 10925 isLoading: function () { 10926 return this._loading; 10927 }, 10928 10929 // @method redraw: this 10930 // Causes the layer to clear all the tiles and request them again. 10931 redraw: function () { 10932 if (this._map) { 10933 this._removeAllTiles(); 10934 var tileZoom = this._clampZoom(this._map.getZoom()); 10935 if (tileZoom !== this._tileZoom) { 10936 this._tileZoom = tileZoom; 10937 this._updateLevels(); 10938 } 10939 this._update(); 10940 } 10941 return this; 10942 }, 10943 10944 getEvents: function () { 10945 var events = { 10946 viewprereset: this._invalidateAll, 10947 viewreset: this._resetView, 10948 zoom: this._resetView, 10949 moveend: this._onMoveEnd 10950 }; 10951 10952 if (!this.options.updateWhenIdle) { 10953 // update tiles on move, but not more often than once per given interval 10954 if (!this._onMove) { 10955 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 10956 } 10957 10958 events.move = this._onMove; 10959 } 10960 10961 if (this._zoomAnimated) { 10962 events.zoomanim = this._animateZoom; 10963 } 10964 10965 return events; 10966 }, 10967 10968 // @section Extension methods 10969 // Layers extending `GridLayer` shall reimplement the following method. 10970 // @method createTile(coords: Object, done?: Function): HTMLElement 10971 // Called only internally, must be overridden by classes extending `GridLayer`. 10972 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 10973 // is specified, it must be called when the tile has finished loading and drawing. 10974 createTile: function () { 10975 return document.createElement('div'); 10976 }, 10977 10978 // @section 10979 // @method getTileSize: Point 10980 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 10981 getTileSize: function () { 10982 var s = this.options.tileSize; 10983 return s instanceof Point ? s : new Point(s, s); 10984 }, 10985 10986 _updateZIndex: function () { 10987 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { 10988 this._container.style.zIndex = this.options.zIndex; 10989 } 10990 }, 10991 10992 _setAutoZIndex: function (compare) { 10993 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back) 10994 10995 var layers = this.getPane().children, 10996 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min 10997 10998 for (var i = 0, len = layers.length, zIndex; i < len; i++) { 10999 11000 zIndex = layers[i].style.zIndex; 11001 11002 if (layers[i] !== this._container && zIndex) { 11003 edgeZIndex = compare(edgeZIndex, +zIndex); 11004 } 11005 } 11006 11007 if (isFinite(edgeZIndex)) { 11008 this.options.zIndex = edgeZIndex + compare(-1, 1); 11009 this._updateZIndex(); 11010 } 11011 }, 11012 11013 _updateOpacity: function () { 11014 if (!this._map) { return; } 11015 11016 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles 11017 if (Browser.ielt9) { return; } 11018 11019 setOpacity(this._container, this.options.opacity); 11020 11021 var now = +new Date(), 11022 nextFrame = false, 11023 willPrune = false; 11024 11025 for (var key in this._tiles) { 11026 var tile = this._tiles[key]; 11027 if (!tile.current || !tile.loaded) { continue; } 11028 11029 var fade = Math.min(1, (now - tile.loaded) / 200); 11030 11031 setOpacity(tile.el, fade); 11032 if (fade < 1) { 11033 nextFrame = true; 11034 } else { 11035 if (tile.active) { 11036 willPrune = true; 11037 } else { 11038 this._onOpaqueTile(tile); 11039 } 11040 tile.active = true; 11041 } 11042 } 11043 11044 if (willPrune && !this._noPrune) { this._pruneTiles(); } 11045 11046 if (nextFrame) { 11047 cancelAnimFrame(this._fadeFrame); 11048 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11049 } 11050 }, 11051 11052 _onOpaqueTile: falseFn, 11053 11054 _initContainer: function () { 11055 if (this._container) { return; } 11056 11057 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || '')); 11058 this._updateZIndex(); 11059 11060 if (this.options.opacity < 1) { 11061 this._updateOpacity(); 11062 } 11063 11064 this.getPane().appendChild(this._container); 11065 }, 11066 11067 _updateLevels: function () { 11068 11069 var zoom = this._tileZoom, 11070 maxZoom = this.options.maxZoom; 11071 11072 if (zoom === undefined) { return undefined; } 11073 11074 for (var z in this._levels) { 11075 z = Number(z); 11076 if (this._levels[z].el.children.length || z === zoom) { 11077 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); 11078 this._onUpdateLevel(z); 11079 } else { 11080 remove(this._levels[z].el); 11081 this._removeTilesAtZoom(z); 11082 this._onRemoveLevel(z); 11083 delete this._levels[z]; 11084 } 11085 } 11086 11087 var level = this._levels[zoom], 11088 map = this._map; 11089 11090 if (!level) { 11091 level = this._levels[zoom] = {}; 11092 11093 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); 11094 level.el.style.zIndex = maxZoom; 11095 11096 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 11097 level.zoom = zoom; 11098 11099 this._setZoomTransform(level, map.getCenter(), map.getZoom()); 11100 11101 // force the browser to consider the newly added element for transition 11102 falseFn(level.el.offsetWidth); 11103 11104 this._onCreateLevel(level); 11105 } 11106 11107 this._level = level; 11108 11109 return level; 11110 }, 11111 11112 _onUpdateLevel: falseFn, 11113 11114 _onRemoveLevel: falseFn, 11115 11116 _onCreateLevel: falseFn, 11117 11118 _pruneTiles: function () { 11119 if (!this._map) { 11120 return; 11121 } 11122 11123 var key, tile; 11124 11125 var zoom = this._map.getZoom(); 11126 if (zoom > this.options.maxZoom || 11127 zoom < this.options.minZoom) { 11128 this._removeAllTiles(); 11129 return; 11130 } 11131 11132 for (key in this._tiles) { 11133 tile = this._tiles[key]; 11134 tile.retain = tile.current; 11135 } 11136 11137 for (key in this._tiles) { 11138 tile = this._tiles[key]; 11139 if (tile.current && !tile.active) { 11140 var coords = tile.coords; 11141 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 11142 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 11143 } 11144 } 11145 } 11146 11147 for (key in this._tiles) { 11148 if (!this._tiles[key].retain) { 11149 this._removeTile(key); 11150 } 11151 } 11152 }, 11153 11154 _removeTilesAtZoom: function (zoom) { 11155 for (var key in this._tiles) { 11156 if (this._tiles[key].coords.z !== zoom) { 11157 continue; 11158 } 11159 this._removeTile(key); 11160 } 11161 }, 11162 11163 _removeAllTiles: function () { 11164 for (var key in this._tiles) { 11165 this._removeTile(key); 11166 } 11167 }, 11168 11169 _invalidateAll: function () { 11170 for (var z in this._levels) { 11171 remove(this._levels[z].el); 11172 this._onRemoveLevel(Number(z)); 11173 delete this._levels[z]; 11174 } 11175 this._removeAllTiles(); 11176 11177 this._tileZoom = undefined; 11178 }, 11179 11180 _retainParent: function (x, y, z, minZoom) { 11181 var x2 = Math.floor(x / 2), 11182 y2 = Math.floor(y / 2), 11183 z2 = z - 1, 11184 coords2 = new Point(+x2, +y2); 11185 coords2.z = +z2; 11186 11187 var key = this._tileCoordsToKey(coords2), 11188 tile = this._tiles[key]; 11189 11190 if (tile && tile.active) { 11191 tile.retain = true; 11192 return true; 11193 11194 } else if (tile && tile.loaded) { 11195 tile.retain = true; 11196 } 11197 11198 if (z2 > minZoom) { 11199 return this._retainParent(x2, y2, z2, minZoom); 11200 } 11201 11202 return false; 11203 }, 11204 11205 _retainChildren: function (x, y, z, maxZoom) { 11206 11207 for (var i = 2 * x; i < 2 * x + 2; i++) { 11208 for (var j = 2 * y; j < 2 * y + 2; j++) { 11209 11210 var coords = new Point(i, j); 11211 coords.z = z + 1; 11212 11213 var key = this._tileCoordsToKey(coords), 11214 tile = this._tiles[key]; 11215 11216 if (tile && tile.active) { 11217 tile.retain = true; 11218 continue; 11219 11220 } else if (tile && tile.loaded) { 11221 tile.retain = true; 11222 } 11223 11224 if (z + 1 < maxZoom) { 11225 this._retainChildren(i, j, z + 1, maxZoom); 11226 } 11227 } 11228 } 11229 }, 11230 11231 _resetView: function (e) { 11232 var animating = e && (e.pinch || e.flyTo); 11233 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 11234 }, 11235 11236 _animateZoom: function (e) { 11237 this._setView(e.center, e.zoom, true, e.noUpdate); 11238 }, 11239 11240 _clampZoom: function (zoom) { 11241 var options = this.options; 11242 11243 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) { 11244 return options.minNativeZoom; 11245 } 11246 11247 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) { 11248 return options.maxNativeZoom; 11249 } 11250 11251 return zoom; 11252 }, 11253 11254 _setView: function (center, zoom, noPrune, noUpdate) { 11255 var tileZoom = Math.round(zoom); 11256 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) || 11257 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) { 11258 tileZoom = undefined; 11259 } else { 11260 tileZoom = this._clampZoom(tileZoom); 11261 } 11262 11263 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom); 11264 11265 if (!noUpdate || tileZoomChanged) { 11266 11267 this._tileZoom = tileZoom; 11268 11269 if (this._abortLoading) { 11270 this._abortLoading(); 11271 } 11272 11273 this._updateLevels(); 11274 this._resetGrid(); 11275 11276 if (tileZoom !== undefined) { 11277 this._update(center); 11278 } 11279 11280 if (!noPrune) { 11281 this._pruneTiles(); 11282 } 11283 11284 // Flag to prevent _updateOpacity from pruning tiles during 11285 // a zoom anim or a pinch gesture 11286 this._noPrune = !!noPrune; 11287 } 11288 11289 this._setZoomTransforms(center, zoom); 11290 }, 11291 11292 _setZoomTransforms: function (center, zoom) { 11293 for (var i in this._levels) { 11294 this._setZoomTransform(this._levels[i], center, zoom); 11295 } 11296 }, 11297 11298 _setZoomTransform: function (level, center, zoom) { 11299 var scale = this._map.getZoomScale(zoom, level.zoom), 11300 translate = level.origin.multiplyBy(scale) 11301 .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); 11302 11303 if (Browser.any3d) { 11304 setTransform(level.el, translate, scale); 11305 } else { 11306 setPosition(level.el, translate); 11307 } 11308 }, 11309 11310 _resetGrid: function () { 11311 var map = this._map, 11312 crs = map.options.crs, 11313 tileSize = this._tileSize = this.getTileSize(), 11314 tileZoom = this._tileZoom; 11315 11316 var bounds = this._map.getPixelWorldBounds(this._tileZoom); 11317 if (bounds) { 11318 this._globalTileRange = this._pxBoundsToTileRange(bounds); 11319 } 11320 11321 this._wrapX = crs.wrapLng && !this.options.noWrap && [ 11322 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 11323 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 11324 ]; 11325 this._wrapY = crs.wrapLat && !this.options.noWrap && [ 11326 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 11327 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 11328 ]; 11329 }, 11330 11331 _onMoveEnd: function () { 11332 if (!this._map || this._map._animatingZoom) { return; } 11333 11334 this._update(); 11335 }, 11336 11337 _getTiledPixelBounds: function (center) { 11338 var map = this._map, 11339 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(), 11340 scale = map.getZoomScale(mapZoom, this._tileZoom), 11341 pixelCenter = map.project(center, this._tileZoom).floor(), 11342 halfSize = map.getSize().divideBy(scale * 2); 11343 11344 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 11345 }, 11346 11347 // Private method to load tiles in the grid's active zoom level according to map bounds 11348 _update: function (center) { 11349 var map = this._map; 11350 if (!map) { return; } 11351 var zoom = this._clampZoom(map.getZoom()); 11352 11353 if (center === undefined) { center = map.getCenter(); } 11354 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom 11355 11356 var pixelBounds = this._getTiledPixelBounds(center), 11357 tileRange = this._pxBoundsToTileRange(pixelBounds), 11358 tileCenter = tileRange.getCenter(), 11359 queue = [], 11360 margin = this.options.keepBuffer, 11361 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), 11362 tileRange.getTopRight().add([margin, -margin])); 11363 11364 // Sanity check: panic if the tile range contains Infinity somewhere. 11365 if (!(isFinite(tileRange.min.x) && 11366 isFinite(tileRange.min.y) && 11367 isFinite(tileRange.max.x) && 11368 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 11369 11370 for (var key in this._tiles) { 11371 var c = this._tiles[key].coords; 11372 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 11373 this._tiles[key].current = false; 11374 } 11375 } 11376 11377 // _update just loads more tiles. If the tile zoom level differs too much 11378 // from the map's, let _setView reset levels and prune old tiles. 11379 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; } 11380 11381 // create a queue of coordinates to load tiles from 11382 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 11383 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 11384 var coords = new Point(i, j); 11385 coords.z = this._tileZoom; 11386 11387 if (!this._isValidTile(coords)) { continue; } 11388 11389 var tile = this._tiles[this._tileCoordsToKey(coords)]; 11390 if (tile) { 11391 tile.current = true; 11392 } else { 11393 queue.push(coords); 11394 } 11395 } 11396 } 11397 11398 // sort tile queue to load tiles in order of their distance to center 11399 queue.sort(function (a, b) { 11400 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 11401 }); 11402 11403 if (queue.length !== 0) { 11404 // if it's the first batch of tiles to load 11405 if (!this._loading) { 11406 this._loading = true; 11407 // @event loading: Event 11408 // Fired when the grid layer starts loading tiles. 11409 this.fire('loading'); 11410 } 11411 11412 // create DOM fragment to append tiles in one batch 11413 var fragment = document.createDocumentFragment(); 11414 11415 for (i = 0; i < queue.length; i++) { 11416 this._addTile(queue[i], fragment); 11417 } 11418 11419 this._level.el.appendChild(fragment); 11420 } 11421 }, 11422 11423 _isValidTile: function (coords) { 11424 var crs = this._map.options.crs; 11425 11426 if (!crs.infinite) { 11427 // don't load tile if it's out of bounds and not wrapped 11428 var bounds = this._globalTileRange; 11429 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) || 11430 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; } 11431 } 11432 11433 if (!this.options.bounds) { return true; } 11434 11435 // don't load tile if it doesn't intersect the bounds in options 11436 var tileBounds = this._tileCoordsToBounds(coords); 11437 return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 11438 }, 11439 11440 _keyToBounds: function (key) { 11441 return this._tileCoordsToBounds(this._keyToTileCoords(key)); 11442 }, 11443 11444 _tileCoordsToNwSe: function (coords) { 11445 var map = this._map, 11446 tileSize = this.getTileSize(), 11447 nwPoint = coords.scaleBy(tileSize), 11448 sePoint = nwPoint.add(tileSize), 11449 nw = map.unproject(nwPoint, coords.z), 11450 se = map.unproject(sePoint, coords.z); 11451 return [nw, se]; 11452 }, 11453 11454 // converts tile coordinates to its geographical bounds 11455 _tileCoordsToBounds: function (coords) { 11456 var bp = this._tileCoordsToNwSe(coords), 11457 bounds = new LatLngBounds(bp[0], bp[1]); 11458 11459 if (!this.options.noWrap) { 11460 bounds = this._map.wrapLatLngBounds(bounds); 11461 } 11462 return bounds; 11463 }, 11464 // converts tile coordinates to key for the tile cache 11465 _tileCoordsToKey: function (coords) { 11466 return coords.x + ':' + coords.y + ':' + coords.z; 11467 }, 11468 11469 // converts tile cache key to coordinates 11470 _keyToTileCoords: function (key) { 11471 var k = key.split(':'), 11472 coords = new Point(+k[0], +k[1]); 11473 coords.z = +k[2]; 11474 return coords; 11475 }, 11476 11477 _removeTile: function (key) { 11478 var tile = this._tiles[key]; 11479 if (!tile) { return; } 11480 11481 remove(tile.el); 11482 11483 delete this._tiles[key]; 11484 11485 // @event tileunload: TileEvent 11486 // Fired when a tile is removed (e.g. when a tile goes off the screen). 11487 this.fire('tileunload', { 11488 tile: tile.el, 11489 coords: this._keyToTileCoords(key) 11490 }); 11491 }, 11492 11493 _initTile: function (tile) { 11494 addClass(tile, 'leaflet-tile'); 11495 11496 var tileSize = this.getTileSize(); 11497 tile.style.width = tileSize.x + 'px'; 11498 tile.style.height = tileSize.y + 'px'; 11499 11500 tile.onselectstart = falseFn; 11501 tile.onmousemove = falseFn; 11502 11503 // update opacity on tiles in IE7-8 because of filter inheritance problems 11504 if (Browser.ielt9 && this.options.opacity < 1) { 11505 setOpacity(tile, this.options.opacity); 11506 } 11507 }, 11508 11509 _addTile: function (coords, container) { 11510 var tilePos = this._getTilePos(coords), 11511 key = this._tileCoordsToKey(coords); 11512 11513 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 11514 11515 this._initTile(tile); 11516 11517 // if createTile is defined with a second argument ("done" callback), 11518 // we know that tile is async and will be ready later; otherwise 11519 if (this.createTile.length < 2) { 11520 // mark tile as ready, but delay one frame for opacity animation to happen 11521 requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 11522 } 11523 11524 setPosition(tile, tilePos); 11525 11526 // save tile in cache 11527 this._tiles[key] = { 11528 el: tile, 11529 coords: coords, 11530 current: true 11531 }; 11532 11533 container.appendChild(tile); 11534 // @event tileloadstart: TileEvent 11535 // Fired when a tile is requested and starts loading. 11536 this.fire('tileloadstart', { 11537 tile: tile, 11538 coords: coords 11539 }); 11540 }, 11541 11542 _tileReady: function (coords, err, tile) { 11543 if (err) { 11544 // @event tileerror: TileErrorEvent 11545 // Fired when there is an error loading a tile. 11546 this.fire('tileerror', { 11547 error: err, 11548 tile: tile, 11549 coords: coords 11550 }); 11551 } 11552 11553 var key = this._tileCoordsToKey(coords); 11554 11555 tile = this._tiles[key]; 11556 if (!tile) { return; } 11557 11558 tile.loaded = +new Date(); 11559 if (this._map._fadeAnimated) { 11560 setOpacity(tile.el, 0); 11561 cancelAnimFrame(this._fadeFrame); 11562 this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 11563 } else { 11564 tile.active = true; 11565 this._pruneTiles(); 11566 } 11567 11568 if (!err) { 11569 addClass(tile.el, 'leaflet-tile-loaded'); 11570 11571 // @event tileload: TileEvent 11572 // Fired when a tile loads. 11573 this.fire('tileload', { 11574 tile: tile.el, 11575 coords: coords 11576 }); 11577 } 11578 11579 if (this._noTilesToLoad()) { 11580 this._loading = false; 11581 // @event load: Event 11582 // Fired when the grid layer loaded all visible tiles. 11583 this.fire('load'); 11584 11585 if (Browser.ielt9 || !this._map._fadeAnimated) { 11586 requestAnimFrame(this._pruneTiles, this); 11587 } else { 11588 // Wait a bit more than 0.2 secs (the duration of the tile fade-in) 11589 // to trigger a pruning. 11590 setTimeout(bind(this._pruneTiles, this), 250); 11591 } 11592 } 11593 }, 11594 11595 _getTilePos: function (coords) { 11596 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 11597 }, 11598 11599 _wrapCoords: function (coords) { 11600 var newCoords = new Point( 11601 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 11602 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y); 11603 newCoords.z = coords.z; 11604 return newCoords; 11605 }, 11606 11607 _pxBoundsToTileRange: function (bounds) { 11608 var tileSize = this.getTileSize(); 11609 return new Bounds( 11610 bounds.min.unscaleBy(tileSize).floor(), 11611 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); 11612 }, 11613 11614 _noTilesToLoad: function () { 11615 for (var key in this._tiles) { 11616 if (!this._tiles[key].loaded) { return false; } 11617 } 11618 return true; 11619 } 11620 }); 11621 11622 // @factory L.gridLayer(options?: GridLayer options) 11623 // Creates a new instance of GridLayer with the supplied options. 11624 function gridLayer(options) { 11625 return new GridLayer(options); 11626 } 11627 11628 /* 11629 * @class TileLayer 11630 * @inherits GridLayer 11631 * @aka L.TileLayer 11632 * 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`. 11633 * 11634 * @example 11635 * 11636 * ```js 11637 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map); 11638 * ``` 11639 * 11640 * @section URL template 11641 * @example 11642 * 11643 * A string of the following form: 11644 * 11645 * ``` 11646 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' 11647 * ``` 11648 * 11649 * `{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. 11650 * 11651 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: 11652 * 11653 * ``` 11654 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); 11655 * ``` 11656 */ 11657 11658 11659 var TileLayer = GridLayer.extend({ 11660 11661 // @section 11662 // @aka TileLayer options 11663 options: { 11664 // @option minZoom: Number = 0 11665 // The minimum zoom level down to which this layer will be displayed (inclusive). 11666 minZoom: 0, 11667 11668 // @option maxZoom: Number = 18 11669 // The maximum zoom level up to which this layer will be displayed (inclusive). 11670 maxZoom: 18, 11671 11672 // @option subdomains: String|String[] = 'abc' 11673 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings. 11674 subdomains: 'abc', 11675 11676 // @option errorTileUrl: String = '' 11677 // URL to the tile image to show in place of the tile that failed to load. 11678 errorTileUrl: '', 11679 11680 // @option zoomOffset: Number = 0 11681 // The zoom number used in tile URLs will be offset with this value. 11682 zoomOffset: 0, 11683 11684 // @option tms: Boolean = false 11685 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 11686 tms: false, 11687 11688 // @option zoomReverse: Boolean = false 11689 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 11690 zoomReverse: false, 11691 11692 // @option detectRetina: Boolean = false 11693 // 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. 11694 detectRetina: false, 11695 11696 // @option crossOrigin: Boolean|String = false 11697 // Whether the crossOrigin attribute will be added to the tiles. 11698 // 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. 11699 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 11700 crossOrigin: false, 11701 11702 // @option referrerPolicy: Boolean|String = false 11703 // Whether the referrerPolicy attribute will be added to the tiles. 11704 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided. 11705 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer 11706 // (e.g. to validate an API token). 11707 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values. 11708 referrerPolicy: false 11709 }, 11710 11711 initialize: function (url, options) { 11712 11713 this._url = url; 11714 11715 options = setOptions(this, options); 11716 11717 // detecting retina displays, adjusting tileSize and zoom levels 11718 if (options.detectRetina && Browser.retina && options.maxZoom > 0) { 11719 11720 options.tileSize = Math.floor(options.tileSize / 2); 11721 11722 if (!options.zoomReverse) { 11723 options.zoomOffset++; 11724 options.maxZoom--; 11725 } else { 11726 options.zoomOffset--; 11727 options.minZoom++; 11728 } 11729 11730 options.minZoom = Math.max(0, options.minZoom); 11731 } 11732 11733 if (typeof options.subdomains === 'string') { 11734 options.subdomains = options.subdomains.split(''); 11735 } 11736 11737 this.on('tileunload', this._onTileRemove); 11738 }, 11739 11740 // @method setUrl(url: String, noRedraw?: Boolean): this 11741 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 11742 // If the URL does not change, the layer will not be redrawn unless 11743 // the noRedraw parameter is set to false. 11744 setUrl: function (url, noRedraw) { 11745 if (this._url === url && noRedraw === undefined) { 11746 noRedraw = true; 11747 } 11748 11749 this._url = url; 11750 11751 if (!noRedraw) { 11752 this.redraw(); 11753 } 11754 return this; 11755 }, 11756 11757 // @method createTile(coords: Object, done?: Function): HTMLElement 11758 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 11759 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 11760 // callback is called when the tile has been loaded. 11761 createTile: function (coords, done) { 11762 var tile = document.createElement('img'); 11763 11764 on(tile, 'load', bind(this._tileOnLoad, this, done, tile)); 11765 on(tile, 'error', bind(this._tileOnError, this, done, tile)); 11766 11767 if (this.options.crossOrigin || this.options.crossOrigin === '') { 11768 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; 11769 } 11770 11771 // for this new option we follow the documented behavior 11772 // more closely by only setting the property when string 11773 if (typeof this.options.referrerPolicy === 'string') { 11774 tile.referrerPolicy = this.options.referrerPolicy; 11775 } 11776 11777 /* 11778 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons 11779 https://www.w3.org/TR/WCAG20-TECHS/H67 11780 */ 11781 tile.alt = ''; 11782 11783 /* 11784 Set role="presentation" to force screen readers to ignore this 11785 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation 11786 */ 11787 tile.setAttribute('role', 'presentation'); 11788 11789 tile.src = this.getTileUrl(coords); 11790 11791 return tile; 11792 }, 11793 11794 // @section Extension methods 11795 // @uninheritable 11796 // Layers extending `TileLayer` might reimplement the following method. 11797 // @method getTileUrl(coords: Object): String 11798 // Called only internally, returns the URL for a tile given its coordinates. 11799 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 11800 getTileUrl: function (coords) { 11801 var data = { 11802 r: Browser.retina ? '@2x' : '', 11803 s: this._getSubdomain(coords), 11804 x: coords.x, 11805 y: coords.y, 11806 z: this._getZoomForUrl() 11807 }; 11808 if (this._map && !this._map.options.crs.infinite) { 11809 var invertedY = this._globalTileRange.max.y - coords.y; 11810 if (this.options.tms) { 11811 data['y'] = invertedY; 11812 } 11813 data['-y'] = invertedY; 11814 } 11815 11816 return template(this._url, extend(data, this.options)); 11817 }, 11818 11819 _tileOnLoad: function (done, tile) { 11820 // For https://github.com/Leaflet/Leaflet/issues/3332 11821 if (Browser.ielt9) { 11822 setTimeout(bind(done, this, null, tile), 0); 11823 } else { 11824 done(null, tile); 11825 } 11826 }, 11827 11828 _tileOnError: function (done, tile, e) { 11829 var errorUrl = this.options.errorTileUrl; 11830 if (errorUrl && tile.getAttribute('src') !== errorUrl) { 11831 tile.src = errorUrl; 11832 } 11833 done(e, tile); 11834 }, 11835 11836 _onTileRemove: function (e) { 11837 e.tile.onload = null; 11838 }, 11839 11840 _getZoomForUrl: function () { 11841 var zoom = this._tileZoom, 11842 maxZoom = this.options.maxZoom, 11843 zoomReverse = this.options.zoomReverse, 11844 zoomOffset = this.options.zoomOffset; 11845 11846 if (zoomReverse) { 11847 zoom = maxZoom - zoom; 11848 } 11849 11850 return zoom + zoomOffset; 11851 }, 11852 11853 _getSubdomain: function (tilePoint) { 11854 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 11855 return this.options.subdomains[index]; 11856 }, 11857 11858 // stops loading all tiles in the background layer 11859 _abortLoading: function () { 11860 var i, tile; 11861 for (i in this._tiles) { 11862 if (this._tiles[i].coords.z !== this._tileZoom) { 11863 tile = this._tiles[i].el; 11864 11865 tile.onload = falseFn; 11866 tile.onerror = falseFn; 11867 11868 if (!tile.complete) { 11869 tile.src = emptyImageUrl; 11870 var coords = this._tiles[i].coords; 11871 remove(tile); 11872 delete this._tiles[i]; 11873 // @event tileabort: TileEvent 11874 // Fired when a tile was loading but is now not wanted. 11875 this.fire('tileabort', { 11876 tile: tile, 11877 coords: coords 11878 }); 11879 } 11880 } 11881 } 11882 }, 11883 11884 _removeTile: function (key) { 11885 var tile = this._tiles[key]; 11886 if (!tile) { return; } 11887 11888 // Cancels any pending http requests associated with the tile 11889 tile.el.setAttribute('src', emptyImageUrl); 11890 11891 return GridLayer.prototype._removeTile.call(this, key); 11892 }, 11893 11894 _tileReady: function (coords, err, tile) { 11895 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) { 11896 return; 11897 } 11898 11899 return GridLayer.prototype._tileReady.call(this, coords, err, tile); 11900 } 11901 }); 11902 11903 11904 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) 11905 // Instantiates a tile layer object given a `URL template` and optionally an options object. 11906 11907 function tileLayer(url, options) { 11908 return new TileLayer(url, options); 11909 } 11910 11911 /* 11912 * @class TileLayer.WMS 11913 * @inherits TileLayer 11914 * @aka L.TileLayer.WMS 11915 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`. 11916 * 11917 * @example 11918 * 11919 * ```js 11920 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", { 11921 * layers: 'nexrad-n0r-900913', 11922 * format: 'image/png', 11923 * transparent: true, 11924 * attribution: "Weather data é 2012 IEM Nexrad" 11925 * }); 11926 * ``` 11927 */ 11928 11929 var TileLayerWMS = TileLayer.extend({ 11930 11931 // @section 11932 // @aka TileLayer.WMS options 11933 // If any custom options not documented here are used, they will be sent to the 11934 // WMS server as extra parameters in each request URL. This can be useful for 11935 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 11936 defaultWmsParams: { 11937 service: 'WMS', 11938 request: 'GetMap', 11939 11940 // @option layers: String = '' 11941 // **(required)** Comma-separated list of WMS layers to show. 11942 layers: '', 11943 11944 // @option styles: String = '' 11945 // Comma-separated list of WMS styles. 11946 styles: '', 11947 11948 // @option format: String = 'image/jpeg' 11949 // WMS image format (use `'image/png'` for layers with transparency). 11950 format: 'image/jpeg', 11951 11952 // @option transparent: Boolean = false 11953 // If `true`, the WMS service will return images with transparency. 11954 transparent: false, 11955 11956 // @option version: String = '1.1.1' 11957 // Version of the WMS service to use 11958 version: '1.1.1' 11959 }, 11960 11961 options: { 11962 // @option crs: CRS = null 11963 // Coordinate Reference System to use for the WMS requests, defaults to 11964 // map CRS. Don't change this if you're not sure what it means. 11965 crs: null, 11966 11967 // @option uppercase: Boolean = false 11968 // If `true`, WMS request parameter keys will be uppercase. 11969 uppercase: false 11970 }, 11971 11972 initialize: function (url, options) { 11973 11974 this._url = url; 11975 11976 var wmsParams = extend({}, this.defaultWmsParams); 11977 11978 // all keys that are not TileLayer options go to WMS params 11979 for (var i in options) { 11980 if (!(i in this.options)) { 11981 wmsParams[i] = options[i]; 11982 } 11983 } 11984 11985 options = setOptions(this, options); 11986 11987 var realRetina = options.detectRetina && Browser.retina ? 2 : 1; 11988 var tileSize = this.getTileSize(); 11989 wmsParams.width = tileSize.x * realRetina; 11990 wmsParams.height = tileSize.y * realRetina; 11991 11992 this.wmsParams = wmsParams; 11993 }, 11994 11995 onAdd: function (map) { 11996 11997 this._crs = this.options.crs || map.options.crs; 11998 this._wmsVersion = parseFloat(this.wmsParams.version); 11999 12000 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; 12001 this.wmsParams[projectionKey] = this._crs.code; 12002 12003 TileLayer.prototype.onAdd.call(this, map); 12004 }, 12005 12006 getTileUrl: function (coords) { 12007 12008 var tileBounds = this._tileCoordsToNwSe(coords), 12009 crs = this._crs, 12010 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), 12011 min = bounds.min, 12012 max = bounds.max, 12013 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? 12014 [min.y, min.x, max.y, max.x] : 12015 [min.x, min.y, max.x, max.y]).join(','), 12016 url = TileLayer.prototype.getTileUrl.call(this, coords); 12017 return url + 12018 getParamString(this.wmsParams, url, this.options.uppercase) + 12019 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; 12020 }, 12021 12022 // @method setParams(params: Object, noRedraw?: Boolean): this 12023 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 12024 setParams: function (params, noRedraw) { 12025 12026 extend(this.wmsParams, params); 12027 12028 if (!noRedraw) { 12029 this.redraw(); 12030 } 12031 12032 return this; 12033 } 12034 }); 12035 12036 12037 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) 12038 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. 12039 function tileLayerWMS(url, options) { 12040 return new TileLayerWMS(url, options); 12041 } 12042 12043 TileLayer.WMS = TileLayerWMS; 12044 tileLayer.wms = tileLayerWMS; 12045 12046 /* 12047 * @class Renderer 12048 * @inherits Layer 12049 * @aka L.Renderer 12050 * 12051 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the 12052 * DOM container of the renderer, its bounds, and its zoom animation. 12053 * 12054 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer 12055 * itself can be added or removed to the map. All paths use a renderer, which can 12056 * be implicit (the map will decide the type of renderer and use it automatically) 12057 * or explicit (using the [`renderer`](#path-renderer) option of the path). 12058 * 12059 * Do not use this class directly, use `SVG` and `Canvas` instead. 12060 * 12061 * @event update: Event 12062 * Fired when the renderer updates its bounds, center and zoom, for example when 12063 * its map has moved 12064 */ 12065 12066 var Renderer = Layer.extend({ 12067 12068 // @section 12069 // @aka Renderer options 12070 options: { 12071 // @option padding: Number = 0.1 12072 // How much to extend the clip area around the map view (relative to its size) 12073 // e.g. 0.1 would be 10% of map view in each direction 12074 padding: 0.1 12075 }, 12076 12077 initialize: function (options) { 12078 setOptions(this, options); 12079 stamp(this); 12080 this._layers = this._layers || {}; 12081 }, 12082 12083 onAdd: function () { 12084 if (!this._container) { 12085 this._initContainer(); // defined by renderer implementations 12086 12087 if (this._zoomAnimated) { 12088 addClass(this._container, 'leaflet-zoom-animated'); 12089 } 12090 } 12091 12092 this.getPane().appendChild(this._container); 12093 this._update(); 12094 this.on('update', this._updatePaths, this); 12095 }, 12096 12097 onRemove: function () { 12098 this.off('update', this._updatePaths, this); 12099 this._destroyContainer(); 12100 }, 12101 12102 getEvents: function () { 12103 var events = { 12104 viewreset: this._reset, 12105 zoom: this._onZoom, 12106 moveend: this._update, 12107 zoomend: this._onZoomEnd 12108 }; 12109 if (this._zoomAnimated) { 12110 events.zoomanim = this._onAnimZoom; 12111 } 12112 return events; 12113 }, 12114 12115 _onAnimZoom: function (ev) { 12116 this._updateTransform(ev.center, ev.zoom); 12117 }, 12118 12119 _onZoom: function () { 12120 this._updateTransform(this._map.getCenter(), this._map.getZoom()); 12121 }, 12122 12123 _updateTransform: function (center, zoom) { 12124 var scale = this._map.getZoomScale(zoom, this._zoom), 12125 position = getPosition(this._container), 12126 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), 12127 currentCenterPoint = this._map.project(this._center, zoom), 12128 destCenterPoint = this._map.project(center, zoom), 12129 centerOffset = destCenterPoint.subtract(currentCenterPoint), 12130 12131 topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset); 12132 12133 if (Browser.any3d) { 12134 setTransform(this._container, topLeftOffset, scale); 12135 } else { 12136 setPosition(this._container, topLeftOffset); 12137 } 12138 }, 12139 12140 _reset: function () { 12141 this._update(); 12142 this._updateTransform(this._center, this._zoom); 12143 12144 for (var id in this._layers) { 12145 this._layers[id]._reset(); 12146 } 12147 }, 12148 12149 _onZoomEnd: function () { 12150 for (var id in this._layers) { 12151 this._layers[id]._project(); 12152 } 12153 }, 12154 12155 _updatePaths: function () { 12156 for (var id in this._layers) { 12157 this._layers[id]._update(); 12158 } 12159 }, 12160 12161 _update: function () { 12162 // Update pixel bounds of renderer container (for positioning/sizing/clipping later) 12163 // Subclasses are responsible of firing the 'update' event. 12164 var p = this.options.padding, 12165 size = this._map.getSize(), 12166 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 12167 12168 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 12169 12170 this._center = this._map.getCenter(); 12171 this._zoom = this._map.getZoom(); 12172 } 12173 }); 12174 12175 /* 12176 * @class Canvas 12177 * @inherits Renderer 12178 * @aka L.Canvas 12179 * 12180 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API). 12181 * Inherits `Renderer`. 12182 * 12183 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not 12184 * available in all web browsers, notably IE8, and overlapping geometries might 12185 * not display properly in some edge cases. 12186 * 12187 * @example 12188 * 12189 * Use Canvas by default for all paths in the map: 12190 * 12191 * ```js 12192 * var map = L.map('map', { 12193 * renderer: L.canvas() 12194 * }); 12195 * ``` 12196 * 12197 * Use a Canvas renderer with extra padding for specific vector geometries: 12198 * 12199 * ```js 12200 * var map = L.map('map'); 12201 * var myRenderer = L.canvas({ padding: 0.5 }); 12202 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12203 * var circle = L.circle( center, { renderer: myRenderer } ); 12204 * ``` 12205 */ 12206 12207 var Canvas = Renderer.extend({ 12208 12209 // @section 12210 // @aka Canvas options 12211 options: { 12212 // @option tolerance: Number = 0 12213 // How much to extend the click tolerance around a path/object on the map. 12214 tolerance: 0 12215 }, 12216 12217 getEvents: function () { 12218 var events = Renderer.prototype.getEvents.call(this); 12219 events.viewprereset = this._onViewPreReset; 12220 return events; 12221 }, 12222 12223 _onViewPreReset: function () { 12224 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once 12225 this._postponeUpdatePaths = true; 12226 }, 12227 12228 onAdd: function () { 12229 Renderer.prototype.onAdd.call(this); 12230 12231 // Redraw vectors since canvas is cleared upon removal, 12232 // in case of removing the renderer itself from the map. 12233 this._draw(); 12234 }, 12235 12236 _initContainer: function () { 12237 var container = this._container = document.createElement('canvas'); 12238 12239 on(container, 'mousemove', this._onMouseMove, this); 12240 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); 12241 on(container, 'mouseout', this._handleMouseOut, this); 12242 container['_leaflet_disable_events'] = true; 12243 12244 this._ctx = container.getContext('2d'); 12245 }, 12246 12247 _destroyContainer: function () { 12248 cancelAnimFrame(this._redrawRequest); 12249 delete this._ctx; 12250 remove(this._container); 12251 off(this._container); 12252 delete this._container; 12253 }, 12254 12255 _updatePaths: function () { 12256 if (this._postponeUpdatePaths) { return; } 12257 12258 var layer; 12259 this._redrawBounds = null; 12260 for (var id in this._layers) { 12261 layer = this._layers[id]; 12262 layer._update(); 12263 } 12264 this._redraw(); 12265 }, 12266 12267 _update: function () { 12268 if (this._map._animatingZoom && this._bounds) { return; } 12269 12270 Renderer.prototype._update.call(this); 12271 12272 var b = this._bounds, 12273 container = this._container, 12274 size = b.getSize(), 12275 m = Browser.retina ? 2 : 1; 12276 12277 setPosition(container, b.min); 12278 12279 // set canvas size (also clearing it); use double size on retina 12280 container.width = m * size.x; 12281 container.height = m * size.y; 12282 container.style.width = size.x + 'px'; 12283 container.style.height = size.y + 'px'; 12284 12285 if (Browser.retina) { 12286 this._ctx.scale(2, 2); 12287 } 12288 12289 // translate so we use the same path coordinates after canvas element moves 12290 this._ctx.translate(-b.min.x, -b.min.y); 12291 12292 // Tell paths to redraw themselves 12293 this.fire('update'); 12294 }, 12295 12296 _reset: function () { 12297 Renderer.prototype._reset.call(this); 12298 12299 if (this._postponeUpdatePaths) { 12300 this._postponeUpdatePaths = false; 12301 this._updatePaths(); 12302 } 12303 }, 12304 12305 _initPath: function (layer) { 12306 this._updateDashArray(layer); 12307 this._layers[stamp(layer)] = layer; 12308 12309 var order = layer._order = { 12310 layer: layer, 12311 prev: this._drawLast, 12312 next: null 12313 }; 12314 if (this._drawLast) { this._drawLast.next = order; } 12315 this._drawLast = order; 12316 this._drawFirst = this._drawFirst || this._drawLast; 12317 }, 12318 12319 _addPath: function (layer) { 12320 this._requestRedraw(layer); 12321 }, 12322 12323 _removePath: function (layer) { 12324 var order = layer._order; 12325 var next = order.next; 12326 var prev = order.prev; 12327 12328 if (next) { 12329 next.prev = prev; 12330 } else { 12331 this._drawLast = prev; 12332 } 12333 if (prev) { 12334 prev.next = next; 12335 } else { 12336 this._drawFirst = next; 12337 } 12338 12339 delete layer._order; 12340 12341 delete this._layers[stamp(layer)]; 12342 12343 this._requestRedraw(layer); 12344 }, 12345 12346 _updatePath: function (layer) { 12347 // Redraw the union of the layer's old pixel 12348 // bounds and the new pixel bounds. 12349 this._extendRedrawBounds(layer); 12350 layer._project(); 12351 layer._update(); 12352 // The redraw will extend the redraw bounds 12353 // with the new pixel bounds. 12354 this._requestRedraw(layer); 12355 }, 12356 12357 _updateStyle: function (layer) { 12358 this._updateDashArray(layer); 12359 this._requestRedraw(layer); 12360 }, 12361 12362 _updateDashArray: function (layer) { 12363 if (typeof layer.options.dashArray === 'string') { 12364 var parts = layer.options.dashArray.split(/[, ]+/), 12365 dashArray = [], 12366 dashValue, 12367 i; 12368 for (i = 0; i < parts.length; i++) { 12369 dashValue = Number(parts[i]); 12370 // Ignore dash array containing invalid lengths 12371 if (isNaN(dashValue)) { return; } 12372 dashArray.push(dashValue); 12373 } 12374 layer.options._dashArray = dashArray; 12375 } else { 12376 layer.options._dashArray = layer.options.dashArray; 12377 } 12378 }, 12379 12380 _requestRedraw: function (layer) { 12381 if (!this._map) { return; } 12382 12383 this._extendRedrawBounds(layer); 12384 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 12385 }, 12386 12387 _extendRedrawBounds: function (layer) { 12388 if (layer._pxBounds) { 12389 var padding = (layer.options.weight || 0) + 1; 12390 this._redrawBounds = this._redrawBounds || new Bounds(); 12391 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 12392 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 12393 } 12394 }, 12395 12396 _redraw: function () { 12397 this._redrawRequest = null; 12398 12399 if (this._redrawBounds) { 12400 this._redrawBounds.min._floor(); 12401 this._redrawBounds.max._ceil(); 12402 } 12403 12404 this._clear(); // clear layers in redraw bounds 12405 this._draw(); // draw layers 12406 12407 this._redrawBounds = null; 12408 }, 12409 12410 _clear: function () { 12411 var bounds = this._redrawBounds; 12412 if (bounds) { 12413 var size = bounds.getSize(); 12414 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 12415 } else { 12416 this._ctx.save(); 12417 this._ctx.setTransform(1, 0, 0, 1, 0, 0); 12418 this._ctx.clearRect(0, 0, this._container.width, this._container.height); 12419 this._ctx.restore(); 12420 } 12421 }, 12422 12423 _draw: function () { 12424 var layer, bounds = this._redrawBounds; 12425 this._ctx.save(); 12426 if (bounds) { 12427 var size = bounds.getSize(); 12428 this._ctx.beginPath(); 12429 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 12430 this._ctx.clip(); 12431 } 12432 12433 this._drawing = true; 12434 12435 for (var order = this._drawFirst; order; order = order.next) { 12436 layer = order.layer; 12437 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) { 12438 layer._updatePath(); 12439 } 12440 } 12441 12442 this._drawing = false; 12443 12444 this._ctx.restore(); // Restore state before clipping. 12445 }, 12446 12447 _updatePoly: function (layer, closed) { 12448 if (!this._drawing) { return; } 12449 12450 var i, j, len2, p, 12451 parts = layer._parts, 12452 len = parts.length, 12453 ctx = this._ctx; 12454 12455 if (!len) { return; } 12456 12457 ctx.beginPath(); 12458 12459 for (i = 0; i < len; i++) { 12460 for (j = 0, len2 = parts[i].length; j < len2; j++) { 12461 p = parts[i][j]; 12462 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y); 12463 } 12464 if (closed) { 12465 ctx.closePath(); 12466 } 12467 } 12468 12469 this._fillStroke(ctx, layer); 12470 12471 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature 12472 }, 12473 12474 _updateCircle: function (layer) { 12475 12476 if (!this._drawing || layer._empty()) { return; } 12477 12478 var p = layer._point, 12479 ctx = this._ctx, 12480 r = Math.max(Math.round(layer._radius), 1), 12481 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 12482 12483 if (s !== 1) { 12484 ctx.save(); 12485 ctx.scale(1, s); 12486 } 12487 12488 ctx.beginPath(); 12489 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 12490 12491 if (s !== 1) { 12492 ctx.restore(); 12493 } 12494 12495 this._fillStroke(ctx, layer); 12496 }, 12497 12498 _fillStroke: function (ctx, layer) { 12499 var options = layer.options; 12500 12501 if (options.fill) { 12502 ctx.globalAlpha = options.fillOpacity; 12503 ctx.fillStyle = options.fillColor || options.color; 12504 ctx.fill(options.fillRule || 'evenodd'); 12505 } 12506 12507 if (options.stroke && options.weight !== 0) { 12508 if (ctx.setLineDash) { 12509 ctx.setLineDash(layer.options && layer.options._dashArray || []); 12510 } 12511 ctx.globalAlpha = options.opacity; 12512 ctx.lineWidth = options.weight; 12513 ctx.strokeStyle = options.color; 12514 ctx.lineCap = options.lineCap; 12515 ctx.lineJoin = options.lineJoin; 12516 ctx.stroke(); 12517 } 12518 }, 12519 12520 // Canvas obviously doesn't have mouse events for individual drawn objects, 12521 // so we emulate that by calculating what's under the mouse on mousemove/click manually 12522 12523 _onClick: function (e) { 12524 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 12525 12526 for (var order = this._drawFirst; order; order = order.next) { 12527 layer = order.layer; 12528 if (layer.options.interactive && layer._containsPoint(point)) { 12529 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) { 12530 clickedLayer = layer; 12531 } 12532 } 12533 } 12534 this._fireEvent(clickedLayer ? [clickedLayer] : false, e); 12535 }, 12536 12537 _onMouseMove: function (e) { 12538 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; } 12539 12540 var point = this._map.mouseEventToLayerPoint(e); 12541 this._handleMouseHover(e, point); 12542 }, 12543 12544 12545 _handleMouseOut: function (e) { 12546 var layer = this._hoveredLayer; 12547 if (layer) { 12548 // if we're leaving the layer, fire mouseout 12549 removeClass(this._container, 'leaflet-interactive'); 12550 this._fireEvent([layer], e, 'mouseout'); 12551 this._hoveredLayer = null; 12552 this._mouseHoverThrottled = false; 12553 } 12554 }, 12555 12556 _handleMouseHover: function (e, point) { 12557 if (this._mouseHoverThrottled) { 12558 return; 12559 } 12560 12561 var layer, candidateHoveredLayer; 12562 12563 for (var order = this._drawFirst; order; order = order.next) { 12564 layer = order.layer; 12565 if (layer.options.interactive && layer._containsPoint(point)) { 12566 candidateHoveredLayer = layer; 12567 } 12568 } 12569 12570 if (candidateHoveredLayer !== this._hoveredLayer) { 12571 this._handleMouseOut(e); 12572 12573 if (candidateHoveredLayer) { 12574 addClass(this._container, 'leaflet-interactive'); // change cursor 12575 this._fireEvent([candidateHoveredLayer], e, 'mouseover'); 12576 this._hoveredLayer = candidateHoveredLayer; 12577 } 12578 } 12579 12580 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e); 12581 12582 this._mouseHoverThrottled = true; 12583 setTimeout(bind(function () { 12584 this._mouseHoverThrottled = false; 12585 }, this), 32); 12586 }, 12587 12588 _fireEvent: function (layers, e, type) { 12589 this._map._fireDOMEvent(e, type || e.type, layers); 12590 }, 12591 12592 _bringToFront: function (layer) { 12593 var order = layer._order; 12594 12595 if (!order) { return; } 12596 12597 var next = order.next; 12598 var prev = order.prev; 12599 12600 if (next) { 12601 next.prev = prev; 12602 } else { 12603 // Already last 12604 return; 12605 } 12606 if (prev) { 12607 prev.next = next; 12608 } else if (next) { 12609 // Update first entry unless this is the 12610 // single entry 12611 this._drawFirst = next; 12612 } 12613 12614 order.prev = this._drawLast; 12615 this._drawLast.next = order; 12616 12617 order.next = null; 12618 this._drawLast = order; 12619 12620 this._requestRedraw(layer); 12621 }, 12622 12623 _bringToBack: function (layer) { 12624 var order = layer._order; 12625 12626 if (!order) { return; } 12627 12628 var next = order.next; 12629 var prev = order.prev; 12630 12631 if (prev) { 12632 prev.next = next; 12633 } else { 12634 // Already first 12635 return; 12636 } 12637 if (next) { 12638 next.prev = prev; 12639 } else if (prev) { 12640 // Update last entry unless this is the 12641 // single entry 12642 this._drawLast = prev; 12643 } 12644 12645 order.prev = null; 12646 12647 order.next = this._drawFirst; 12648 this._drawFirst.prev = order; 12649 this._drawFirst = order; 12650 12651 this._requestRedraw(layer); 12652 } 12653 }); 12654 12655 // @factory L.canvas(options?: Renderer options) 12656 // Creates a Canvas renderer with the given options. 12657 function canvas(options) { 12658 return Browser.canvas ? new Canvas(options) : null; 12659 } 12660 12661 /* 12662 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! 12663 */ 12664 12665 12666 var vmlCreate = (function () { 12667 try { 12668 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); 12669 return function (name) { 12670 return document.createElement('<lvml:' + name + ' class="lvml">'); 12671 }; 12672 } catch (e) { 12673 // Do not return fn from catch block so `e` can be garbage collected 12674 // See https://github.com/Leaflet/Leaflet/pull/7279 12675 } 12676 return function (name) { 12677 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 12678 }; 12679 })(); 12680 12681 12682 /* 12683 * @class SVG 12684 * 12685 * 12686 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility 12687 * with old versions of Internet Explorer. 12688 */ 12689 12690 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences 12691 var vmlMixin = { 12692 12693 _initContainer: function () { 12694 this._container = create$1('div', 'leaflet-vml-container'); 12695 }, 12696 12697 _update: function () { 12698 if (this._map._animatingZoom) { return; } 12699 Renderer.prototype._update.call(this); 12700 this.fire('update'); 12701 }, 12702 12703 _initPath: function (layer) { 12704 var container = layer._container = vmlCreate('shape'); 12705 12706 addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); 12707 12708 container.coordsize = '1 1'; 12709 12710 layer._path = vmlCreate('path'); 12711 container.appendChild(layer._path); 12712 12713 this._updateStyle(layer); 12714 this._layers[stamp(layer)] = layer; 12715 }, 12716 12717 _addPath: function (layer) { 12718 var container = layer._container; 12719 this._container.appendChild(container); 12720 12721 if (layer.options.interactive) { 12722 layer.addInteractiveTarget(container); 12723 } 12724 }, 12725 12726 _removePath: function (layer) { 12727 var container = layer._container; 12728 remove(container); 12729 layer.removeInteractiveTarget(container); 12730 delete this._layers[stamp(layer)]; 12731 }, 12732 12733 _updateStyle: function (layer) { 12734 var stroke = layer._stroke, 12735 fill = layer._fill, 12736 options = layer.options, 12737 container = layer._container; 12738 12739 container.stroked = !!options.stroke; 12740 container.filled = !!options.fill; 12741 12742 if (options.stroke) { 12743 if (!stroke) { 12744 stroke = layer._stroke = vmlCreate('stroke'); 12745 } 12746 container.appendChild(stroke); 12747 stroke.weight = options.weight + 'px'; 12748 stroke.color = options.color; 12749 stroke.opacity = options.opacity; 12750 12751 if (options.dashArray) { 12752 stroke.dashStyle = isArray(options.dashArray) ? 12753 options.dashArray.join(' ') : 12754 options.dashArray.replace(/( *, *)/g, ' '); 12755 } else { 12756 stroke.dashStyle = ''; 12757 } 12758 stroke.endcap = options.lineCap.replace('butt', 'flat'); 12759 stroke.joinstyle = options.lineJoin; 12760 12761 } else if (stroke) { 12762 container.removeChild(stroke); 12763 layer._stroke = null; 12764 } 12765 12766 if (options.fill) { 12767 if (!fill) { 12768 fill = layer._fill = vmlCreate('fill'); 12769 } 12770 container.appendChild(fill); 12771 fill.color = options.fillColor || options.color; 12772 fill.opacity = options.fillOpacity; 12773 12774 } else if (fill) { 12775 container.removeChild(fill); 12776 layer._fill = null; 12777 } 12778 }, 12779 12780 _updateCircle: function (layer) { 12781 var p = layer._point.round(), 12782 r = Math.round(layer._radius), 12783 r2 = Math.round(layer._radiusY || r); 12784 12785 this._setPath(layer, layer._empty() ? 'M0 0' : 12786 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360)); 12787 }, 12788 12789 _setPath: function (layer, path) { 12790 layer._path.v = path; 12791 }, 12792 12793 _bringToFront: function (layer) { 12794 toFront(layer._container); 12795 }, 12796 12797 _bringToBack: function (layer) { 12798 toBack(layer._container); 12799 } 12800 }; 12801 12802 var create = Browser.vml ? vmlCreate : svgCreate; 12803 12804 /* 12805 * @class SVG 12806 * @inherits Renderer 12807 * @aka L.SVG 12808 * 12809 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG). 12810 * Inherits `Renderer`. 12811 * 12812 * Due to [technical limitations](https://caniuse.com/svg), SVG is not 12813 * available in all web browsers, notably Android 2.x and 3.x. 12814 * 12815 * Although SVG is not available on IE7 and IE8, these browsers support
12816 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language) 12817 * (a now deprecated technology), and the SVG renderer will fall back to VML in 12818 * this case. 12819 * 12820 * @example 12821 * 12822 * Use SVG by default for all paths in the map: 12823 * 12824 * ```js 12825 * var map = L.map('map', { 12826 * renderer: L.svg() 12827 * }); 12828 * ``` 12829 * 12830 * Use a SVG renderer with extra padding for specific vector geometries: 12831 * 12832 * ```js 12833 * var map = L.map('map'); 12834 * var myRenderer = L.svg({ padding: 0.5 }); 12835 * var line = L.polyline( coordinates, { renderer: myRenderer } ); 12836 * var circle = L.circle( center, { renderer: myRenderer } ); 12837 * ``` 12838 */ 12839 12840 var SVG = Renderer.extend({ 12841 12842 getEvents: function () { 12843 var events = Renderer.prototype.getEvents.call(this); 12844 events.zoomstart = this._onZoomStart; 12845 return events; 12846 }, 12847 12848 _initContainer: function () { 12849 this._container = create('svg'); 12850 12851 // makes it possible to click through svg root; we'll reset it back in individual paths 12852 this._container.setAttribute('pointer-events', 'none'); 12853 12854 this._rootGroup = create('g'); 12855 this._container.appendChild(this._rootGroup); 12856 }, 12857 12858 _destroyContainer: function () { 12859 remove(this._container); 12860 off(this._container); 12861 delete this._container; 12862 delete this._rootGroup; 12863 delete this._svgSize; 12864 }, 12865 12866 _onZoomStart: function () { 12867 // Drag-then-pinch interactions might mess up the center and zoom. 12868 // In this case, the easiest way to prevent this is re-do the renderer 12869 // bounds and padding when the zooming starts. 12870 this._update(); 12871 }, 12872 12873 _update: function () { 12874 if (this._map._animatingZoom && this._bounds) { return; } 12875 12876 Renderer.prototype._update.call(this); 12877 12878 var b = this._bounds, 12879 size = b.getSize(), 12880 container = this._container; 12881 12882 // set size of svg-container if changed 12883 if (!this._svgSize || !this._svgSize.equals(size)) { 12884 this._svgSize = size; 12885 container.setAttribute('width', size.x); 12886 container.setAttribute('height', size.y); 12887 } 12888 12889 // movement: update container viewBox so that we don't have to change coordinates of individual layers 12890 setPosition(container, b.min); 12891 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); 12892 12893 this.fire('update'); 12894 }, 12895 12896 // methods below are called by vector layers implementations 12897 12898 _initPath: function (layer) { 12899 var path = layer._path = create('path'); 12900 12901 // @namespace Path 12902 // @option className: String = null 12903 // Custom class name set on an element. Only for SVG renderer. 12904 if (layer.options.className) { 12905 addClass(path, layer.options.className); 12906 } 12907 12908 if (layer.options.interactive) { 12909 addClass(path, 'leaflet-interactive'); 12910 } 12911 12912 this._updateStyle(layer); 12913 this._layers[stamp(layer)] = layer; 12914 }, 12915 12916 _addPath: function (layer) { 12917 if (!this._rootGroup) { this._initContainer(); } 12918 this._rootGroup.appendChild(layer._path); 12919 layer.addInteractiveTarget(layer._path); 12920 }, 12921 12922 _removePath: function (layer) { 12923 remove(layer._path); 12924 layer.removeInteractiveTarget(layer._path); 12925 delete this._layers[stamp(layer)]; 12926 }, 12927 12928 _updatePath: function (layer) { 12929 layer._project(); 12930 layer._update(); 12931 }, 12932 12933 _updateStyle: function (layer) { 12934 var path = layer._path, 12935 options = layer.options; 12936 12937 if (!path) { return; } 12938 12939 if (options.stroke) { 12940 path.setAttribute('stroke', options.color); 12941 path.setAttribute('stroke-opacity', options.opacity); 12942 path.setAttribute('stroke-width', options.weight); 12943 path.setAttribute('stroke-linecap', options.lineCap); 12944 path.setAttribute('stroke-linejoin', options.lineJoin); 12945 12946 if (options.dashArray) { 12947 path.setAttribute('stroke-dasharray', options.dashArray);
vendor: 4,103 bytes, lines 12948-13057
12948 } else { 12949 path.removeAttribute('stroke-dasharray'); 12950 } 12951 12952 if (options.dashOffset) { 12953 path.setAttribute('stroke-dashoffset', options.dashOffset); 12954 } else { 12955 path.removeAttribute('stroke-dashoffset'); 12956 } 12957 } else { 12958 path.setAttribute('stroke', 'none'); 12959 } 12960 12961 if (options.fill) { 12962 path.setAttribute('fill', options.fillColor || options.color); 12963 path.setAttribute('fill-opacity', options.fillOpacity); 12964 path.setAttribute('fill-rule', options.fillRule || 'evenodd'); 12965 } else { 12966 path.setAttribute('fill', 'none'); 12967 } 12968 }, 12969 12970 _updatePoly: function (layer, closed) { 12971 this._setPath(layer, pointsToPath(layer._parts, closed)); 12972 }, 12973 12974 _updateCircle: function (layer) { 12975 var p = layer._point, 12976 r = Math.max(Math.round(layer._radius), 1), 12977 r2 = Math.max(Math.round(layer._radiusY), 1) || r, 12978 arc = 'a' + r + ',' + r2 + ' 0 1,0 '; 12979 12980 // drawing a circle with two half-arcs 12981 var d = layer._empty() ? 'M0 0' : 12982 'M' + (p.x - r) + ',' + p.y + 12983 arc + (r * 2) + ',0 ' + 12984 arc + (-r * 2) + ',0 '; 12985 12986 this._setPath(layer, d); 12987 }, 12988 12989 _setPath: function (layer, path) { 12990 layer._path.setAttribute('d', path); 12991 }, 12992 12993 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 12994 _bringToFront: function (layer) { 12995 toFront(layer._path); 12996 }, 12997 12998 _bringToBack: function (layer) { 12999 toBack(layer._path); 13000 } 13001 }); 13002 13003 if (Browser.vml) { 13004 SVG.include(vmlMixin); 13005 } 13006 13007 // @namespace SVG 13008 // @factory L.svg(options?: Renderer options) 13009 // Creates a SVG renderer with the given options. 13010 function svg(options) { 13011 return Browser.svg || Browser.vml ? new SVG(options) : null; 13012 } 13013 13014 Map.include({ 13015 // @namespace Map; @method getRenderer(layer: Path): Renderer 13016 // Returns the instance of `Renderer` that should be used to render the given 13017 // `Path`. It will ensure that the `renderer` options of the map and paths 13018 // are respected, and that the renderers do exist on the map. 13019 getRenderer: function (layer) { 13020 // @namespace Path; @option renderer: Renderer 13021 // Use this specific instance of `Renderer` for this path. Takes 13022 // precedence over the map's [default renderer](#map-renderer). 13023 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 13024 13025 if (!renderer) { 13026 renderer = this._renderer = this._createRenderer(); 13027 } 13028 13029 if (!this.hasLayer(renderer)) { 13030 this.addLayer(renderer); 13031 } 13032 return renderer; 13033 }, 13034 13035 _getPaneRenderer: function (name) { 13036 if (name === 'overlayPane' || name === undefined) { 13037 return false; 13038 } 13039 13040 var renderer = this._paneRenderers[name]; 13041 if (renderer === undefined) { 13042 renderer = this._createRenderer({pane: name}); 13043 this._paneRenderers[name] = renderer; 13044 } 13045 return renderer; 13046 }, 13047 13048 _createRenderer: function (options) { 13049 // @namespace Map; @option preferCanvas: Boolean = false 13050 // Whether `Path`s should be rendered on a `Canvas` renderer. 13051 // By default, all `Path`s are rendered in a `SVG` renderer. 13052 return (this.options.preferCanvas && canvas(options)) || svg(options); 13053 } 13054 }); 13055 13056 /* 13057 * L.Rectangle extends Polygon and creates a rectangle when passed a
13057LatLngBounds object. 13058 */ 13059 13060 /* 13061 * @class Rectangle 13062 * @aka L.Rectangle 13063 * @inherits Polygon 13064 * 13065 * A class for drawing rectangle overlays on a map. Extends `Polygon`. 13066 * 13067 * @example 13068 * 13069 * ```js 13070 * // define rectangle geographical bounds 13071 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]]; 13072 * 13073 * // create an orange rectangle 13074 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map); 13075 * 13076 * // zoom the map to the rectangle bounds 13077 * map.fitBounds(bounds); 13078 * ``` 13079 * 13080 */ 13081 13082 13083 var Rectangle = Polygon.extend({ 13084 initialize: function (latLngBounds, options) { 13085 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 13086 }, 13087 13088 // @method setBounds(latLngBounds: LatLngBounds): this 13089 // Redraws the rectangle with the passed bounds. 13090 setBounds: function (latLngBounds) { 13091 return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 13092 }, 13093 13094 _boundsToLatLngs: function (latLngBounds) { 13095 latLngBounds = toLatLngBounds(latLngBounds); 13096 return [
vendor: 29,212 bytes, lines 13097-13923
13097 latLngBounds.getSouthWest(), 13098 latLngBounds.getNorthWest(), 13099 latLngBounds.getNorthEast(), 13100 latLngBounds.getSouthEast() 13101 ]; 13102 } 13103 }); 13104 13105 13106 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) 13107 function rectangle(latLngBounds, options) { 13108 return new Rectangle(latLngBounds, options); 13109 } 13110 13111 SVG.create = create; 13112 SVG.pointsToPath = pointsToPath; 13113 13114 GeoJSON.geometryToLayer = geometryToLayer; 13115 GeoJSON.coordsToLatLng = coordsToLatLng; 13116 GeoJSON.coordsToLatLngs = coordsToLatLngs; 13117 GeoJSON.latLngToCoords = latLngToCoords; 13118 GeoJSON.latLngsToCoords = latLngsToCoords; 13119 GeoJSON.getFeature = getFeature; 13120 GeoJSON.asFeature = asFeature; 13121 13122 /* 13123 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map 13124 * (zoom to a selected bounding box), enabled by default. 13125 */ 13126 13127 // @namespace Map 13128 // @section Interaction Options 13129 Map.mergeOptions({ 13130 // @option boxZoom: Boolean = true 13131 // Whether the map can be zoomed to a rectangular area specified by 13132 // dragging the mouse while pressing the shift key. 13133 boxZoom: true 13134 }); 13135 13136 var BoxZoom = Handler.extend({ 13137 initialize: function (map) { 13138 this._map = map; 13139 this._container = map._container; 13140 this._pane = map._panes.overlayPane; 13141 this._resetStateTimeout = 0; 13142 map.on('unload', this._destroy, this); 13143 }, 13144 13145 addHooks: function () { 13146 on(this._container, 'mousedown', this._onMouseDown, this); 13147 }, 13148 13149 removeHooks: function () { 13150 off(this._container, 'mousedown', this._onMouseDown, this); 13151 }, 13152 13153 moved: function () { 13154 return this._moved; 13155 }, 13156 13157 _destroy: function () { 13158 remove(this._pane); 13159 delete this._pane; 13160 }, 13161 13162 _resetState: function () { 13163 this._resetStateTimeout = 0; 13164 this._moved = false; 13165 }, 13166 13167 _clearDeferredResetState: function () { 13168 if (this._resetStateTimeout !== 0) { 13169 clearTimeout(this._resetStateTimeout); 13170 this._resetStateTimeout = 0; 13171 } 13172 }, 13173 13174 _onMouseDown: function (e) { 13175 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } 13176 13177 // Clear the deferred resetState if it hasn't executed yet, otherwise it 13178 // will interrupt the interaction and orphan a box element in the container. 13179 this._clearDeferredResetState(); 13180 this._resetState(); 13181 13182 disableTextSelection(); 13183 disableImageDrag(); 13184 13185 this._startPoint = this._map.mouseEventToContainerPoint(e); 13186 13187 on(document, { 13188 contextmenu: stop, 13189 mousemove: this._onMouseMove, 13190 mouseup: this._onMouseUp, 13191 keydown: this._onKeyDown 13192 }, this); 13193 }, 13194 13195 _onMouseMove: function (e) { 13196 if (!this._moved) { 13197 this._moved = true; 13198 13199 this._box = create$1('div', 'leaflet-zoom-box', this._container); 13200 addClass(this._container, 'leaflet-crosshair'); 13201 13202 this._map.fire('boxzoomstart'); 13203 } 13204 13205 this._point = this._map.mouseEventToContainerPoint(e); 13206 13207 var bounds = new Bounds(this._point, this._startPoint), 13208 size = bounds.getSize(); 13209 13210 setPosition(this._box, bounds.min); 13211 13212 this._box.style.width = size.x + 'px'; 13213 this._box.style.height = size.y + 'px'; 13214 }, 13215 13216 _finish: function () { 13217 if (this._moved) { 13218 remove(this._box); 13219 removeClass(this._container, 'leaflet-crosshair'); 13220 } 13221 13222 enableTextSelection(); 13223 enableImageDrag(); 13224 13225 off(document, { 13226 contextmenu: stop, 13227 mousemove: this._onMouseMove, 13228 mouseup: this._onMouseUp, 13229 keydown: this._onKeyDown 13230 }, this); 13231 }, 13232 13233 _onMouseUp: function (e) { 13234 if ((e.which !== 1) && (e.button !== 1)) { return; } 13235 13236 this._finish(); 13237 13238 if (!this._moved) { return; } 13239 // Postpone to next JS tick so internal click event handling 13240 // still see it as "moved". 13241 this._clearDeferredResetState(); 13242 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 13243 13244 var bounds = new LatLngBounds( 13245 this._map.containerPointToLatLng(this._startPoint), 13246 this._map.containerPointToLatLng(this._point)); 13247 13248 this._map 13249 .fitBounds(bounds) 13250 .fire('boxzoomend', {boxZoomBounds: bounds}); 13251 }, 13252 13253 _onKeyDown: function (e) { 13254 if (e.keyCode === 27) { 13255 this._finish(); 13256 this._clearDeferredResetState(); 13257 this._resetState(); 13258 } 13259 } 13260 }); 13261 13262 // @section Handlers 13263 // @property boxZoom: Handler 13264 // Box (shift-drag with mouse) zoom handler. 13265 Map.addInitHook('addHandler', 'boxZoom', BoxZoom); 13266 13267 /* 13268 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. 13269 */ 13270 13271 // @namespace Map 13272 // @section Interaction Options 13273 13274 Map.mergeOptions({ 13275 // @option doubleClickZoom: Boolean|String = true 13276 // Whether the map can be zoomed in by double clicking on it and 13277 // zoomed out by double clicking while holding shift. If passed 13278 // `'center'`, double-click zoom will zoom to the center of the 13279 // view regardless of where the mouse was. 13280 doubleClickZoom: true 13281 }); 13282 13283 var DoubleClickZoom = Handler.extend({ 13284 addHooks: function () { 13285 this._map.on('dblclick', this._onDoubleClick, this); 13286 }, 13287 13288 removeHooks: function () { 13289 this._map.off('dblclick', this._onDoubleClick, this); 13290 }, 13291 13292 _onDoubleClick: function (e) { 13293 var map = this._map, 13294 oldZoom = map.getZoom(), 13295 delta = map.options.zoomDelta, 13296 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 13297 13298 if (map.options.doubleClickZoom === 'center') { 13299 map.setZoom(zoom); 13300 } else { 13301 map.setZoomAround(e.containerPoint, zoom); 13302 } 13303 } 13304 }); 13305 13306 // @section Handlers 13307 // 13308 // Map properties include interaction handlers that allow you to control 13309 // interaction behavior in runtime, enabling or disabling certain features such 13310 // as dragging or touch zoom (see `Handler` methods). For example: 13311 // 13312 // ```js 13313 // map.doubleClickZoom.disable(); 13314 // ``` 13315 // 13316 // @property doubleClickZoom: Handler 13317 // Double click zoom handler. 13318 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); 13319 13320 /* 13321 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. 13322 */ 13323 13324 // @namespace Map 13325 // @section Interaction Options 13326 Map.mergeOptions({ 13327 // @option dragging: Boolean = true 13328 // Whether the map be draggable with mouse/touch or not. 13329 dragging: true, 13330 13331 // @section Panning Inertia Options 13332 // @option inertia: Boolean = * 13333 // If enabled, panning of the map will have an inertia effect where 13334 // the map builds momentum while dragging and continues moving in 13335 // the same direction for some time. Feels especially nice on touch 13336 // devices. Enabled by default. 13337 inertia: true, 13338 13339 // @option inertiaDeceleration: Number = 3000 13340 // The rate with which the inertial movement slows down, in pixels/secondò. 13341 inertiaDeceleration: 3400, // px/s^2 13342 13343 // @option inertiaMaxSpeed: Number = Infinity 13344 // Max speed of the inertial movement, in pixels/second. 13345 inertiaMaxSpeed: Infinity, // px/s 13346 13347 // @option easeLinearity: Number = 0.2 13348 easeLinearity: 0.2, 13349 13350 // TODO refactor, move to CRS 13351 // @option worldCopyJump: Boolean = false 13352 // With this option enabled, the map tracks when you pan to another "copy" 13353 // of the world and seamlessly jumps to the original one so that all overlays 13354 // like markers and vector layers are still visible. 13355 worldCopyJump: false, 13356 13357 // @option maxBoundsViscosity: Number = 0.0 13358 // If `maxBounds` is set, this option will control how solid the bounds 13359 // are when dragging the map around. The default value of `0.0` allows the 13360 // user to drag outside the bounds at normal speed, higher values will 13361 // slow down map dragging outside bounds, and `1.0` makes the bounds fully 13362 // solid, preventing the user from dragging outside the bounds. 13363 maxBoundsViscosity: 0.0 13364 }); 13365 13366 var Drag = Handler.extend({ 13367 addHooks: function () { 13368 if (!this._draggable) { 13369 var map = this._map; 13370 13371 this._draggable = new Draggable(map._mapPane, map._container); 13372 13373 this._draggable.on({ 13374 dragstart: this._onDragStart, 13375 drag: this._onDrag, 13376 dragend: this._onDragEnd 13377 }, this); 13378 13379 this._draggable.on('predrag', this._onPreDragLimit, this); 13380 if (map.options.worldCopyJump) { 13381 this._draggable.on('predrag', this._onPreDragWrap, this); 13382 map.on('zoomend', this._onZoomEnd, this); 13383 13384 map.whenReady(this._onZoomEnd, this); 13385 } 13386 } 13387 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); 13388 this._draggable.enable(); 13389 this._positions = []; 13390 this._times = []; 13391 }, 13392 13393 removeHooks: function () { 13394 removeClass(this._map._container, 'leaflet-grab'); 13395 removeClass(this._map._container, 'leaflet-touch-drag'); 13396 this._draggable.disable(); 13397 }, 13398 13399 moved: function () { 13400 return this._draggable && this._draggable._moved; 13401 }, 13402 13403 moving: function () { 13404 return this._draggable && this._draggable._moving; 13405 }, 13406 13407 _onDragStart: function () { 13408 var map = this._map; 13409 13410 map._stop(); 13411 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 13412 var bounds = toLatLngBounds(this._map.options.maxBounds); 13413 13414 this._offsetLimit = toBounds( 13415 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 13416 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) 13417 .add(this._map.getSize())); 13418 13419 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity)); 13420 } else { 13421 this._offsetLimit = null; 13422 } 13423 13424 map 13425 .fire('movestart') 13426 .fire('dragstart'); 13427 13428 if (map.options.inertia) { 13429 this._positions = []; 13430 this._times = []; 13431 } 13432 }, 13433 13434 _onDrag: function (e) { 13435 if (this._map.options.inertia) { 13436 var time = this._lastTime = +new Date(), 13437 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 13438 13439 this._positions.push(pos); 13440 this._times.push(time); 13441 13442 this._prunePositions(time); 13443 } 13444 13445 this._map 13446 .fire('move', e) 13447 .fire('drag', e); 13448 }, 13449 13450 _prunePositions: function (time) { 13451 while (this._positions.length > 1 && time - this._times[0] > 50) { 13452 this._positions.shift(); 13453 this._times.shift(); 13454 } 13455 }, 13456 13457 _onZoomEnd: function () { 13458 var pxCenter = this._map.getSize().divideBy(2), 13459 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 13460 13461 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 13462 this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 13463 }, 13464 13465 _viscousLimit: function (value, threshold) { 13466 return value - (value - threshold) * this._viscosity; 13467 }, 13468 13469 _onPreDragLimit: function () { 13470 if (!this._viscosity || !this._offsetLimit) { return; } 13471 13472 var offset = this._draggable._newPos.subtract(this._draggable._startPos); 13473 13474 var limit = this._offsetLimit; 13475 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); } 13476 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); } 13477 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); } 13478 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); } 13479 13480 this._draggable._newPos = this._draggable._startPos.add(offset); 13481 }, 13482 13483 _onPreDragWrap: function () { 13484 // TODO refactor to be able to adjust map pane position after zoom 13485 var worldWidth = this._worldWidth, 13486 halfWidth = Math.round(worldWidth / 2), 13487 dx = this._initialWorldOffset, 13488 x = this._draggable._newPos.x, 13489 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, 13490 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, 13491 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 13492 13493 this._draggable._absPos = this._draggable._newPos.clone(); 13494 this._draggable._newPos.x = newX; 13495 }, 13496 13497 _onDragEnd: function (e) { 13498 var map = this._map, 13499 options = map.options, 13500 13501 noInertia = !options.inertia || this._times.length < 2; 13502 13503 map.fire('dragend', e); 13504 13505 if (noInertia) { 13506 map.fire('moveend'); 13507 13508 } else { 13509 this._prunePositions(+new Date()); 13510 13511 var direction = this._lastPos.subtract(this._positions[0]), 13512 duration = (this._lastTime - this._times[0]) / 1000, 13513 ease = options.easeLinearity, 13514 13515 speedVector = direction.multiplyBy(ease / duration), 13516 speed = speedVector.distanceTo([0, 0]), 13517 13518 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), 13519 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), 13520 13521 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), 13522 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 13523 13524 if (!offset.x && !offset.y) { 13525 map.fire('moveend'); 13526 13527 } else { 13528 offset = map._limitOffset(offset, map.options.maxBounds); 13529 13530 requestAnimFrame(function () { 13531 map.panBy(offset, { 13532 duration: decelerationDuration, 13533 easeLinearity: ease, 13534 noMoveStart: true, 13535 animate: true 13536 }); 13537 }); 13538 } 13539 } 13540 } 13541 }); 13542 13543 // @section Handlers 13544 // @property dragging: Handler 13545 // Map dragging handler (by both mouse and touch). 13546 Map.addInitHook('addHandler', 'dragging', Drag); 13547 13548 /* 13549 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. 13550 */ 13551 13552 // @namespace Map 13553 // @section Keyboard Navigation Options 13554 Map.mergeOptions({ 13555 // @option keyboard: Boolean = true 13556 // Makes the map focusable and allows users to navigate the map with keyboard 13557 // arrows and `+`/`-` keys. 13558 keyboard: true, 13559 13560 // @option keyboardPanDelta: Number = 80 13561 // Amount of pixels to pan when pressing an arrow key. 13562 keyboardPanDelta: 80 13563 }); 13564 13565 var Keyboard = Handler.extend({ 13566 13567 keyCodes: { 13568 left: [37], 13569 right: [39], 13570 down: [40], 13571 up: [38], 13572 zoomIn: [187, 107, 61, 171], 13573 zoomOut: [189, 109, 54, 173] 13574 }, 13575 13576 initialize: function (map) { 13577 this._map = map; 13578 13579 this._setPanDelta(map.options.keyboardPanDelta); 13580 this._setZoomDelta(map.options.zoomDelta); 13581 }, 13582 13583 addHooks: function () { 13584 var container = this._map._container; 13585 13586 // make the container focusable by tabbing 13587 if (container.tabIndex <= 0) { 13588 container.tabIndex = '0'; 13589 } 13590 13591 on(container, { 13592 focus: this._onFocus, 13593 blur: this._onBlur, 13594 mousedown: this._onMouseDown 13595 }, this); 13596 13597 this._map.on({ 13598 focus: this._addHooks, 13599 blur: this._removeHooks 13600 }, this); 13601 }, 13602 13603 removeHooks: function () { 13604 this._removeHooks(); 13605 13606 off(this._map._container, { 13607 focus: this._onFocus, 13608 blur: this._onBlur, 13609 mousedown: this._onMouseDown 13610 }, this); 13611 13612 this._map.off({ 13613 focus: this._addHooks, 13614 blur: this._removeHooks 13615 }, this); 13616 }, 13617 13618 _onMouseDown: function () { 13619 if (this._focused) { return; } 13620 13621 var body = document.body, 13622 docEl = document.documentElement, 13623 top = body.scrollTop || docEl.scrollTop, 13624 left = body.scrollLeft || docEl.scrollLeft; 13625 13626 this._map._container.focus(); 13627 13628 window.scrollTo(left, top); 13629 }, 13630 13631 _onFocus: function () { 13632 this._focused = true; 13633 this._map.fire('focus'); 13634 }, 13635 13636 _onBlur: function () { 13637 this._focused = false; 13638 this._map.fire('blur'); 13639 }, 13640 13641 _setPanDelta: function (panDelta) { 13642 var keys = this._panKeys = {}, 13643 codes = this.keyCodes, 13644 i, len; 13645 13646 for (i = 0, len = codes.left.length; i < len; i++) { 13647 keys[codes.left[i]] = [-1 * panDelta, 0]; 13648 } 13649 for (i = 0, len = codes.right.length; i < len; i++) { 13650 keys[codes.right[i]] = [panDelta, 0]; 13651 } 13652 for (i = 0, len = codes.down.length; i < len; i++) { 13653 keys[codes.down[i]] = [0, panDelta]; 13654 } 13655 for (i = 0, len = codes.up.length; i < len; i++) { 13656 keys[codes.up[i]] = [0, -1 * panDelta]; 13657 } 13658 }, 13659 13660 _setZoomDelta: function (zoomDelta) { 13661 var keys = this._zoomKeys = {}, 13662 codes = this.keyCodes, 13663 i, len; 13664 13665 for (i = 0, len = codes.zoomIn.length; i < len; i++) { 13666 keys[codes.zoomIn[i]] = zoomDelta; 13667 } 13668 for (i = 0, len = codes.zoomOut.length; i < len; i++) { 13669 keys[codes.zoomOut[i]] = -zoomDelta; 13670 } 13671 }, 13672 13673 _addHooks: function () { 13674 on(document, 'keydown', this._onKeyDown, this); 13675 }, 13676 13677 _removeHooks: function () { 13678 off(document, 'keydown', this._onKeyDown, this); 13679 }, 13680 13681 _onKeyDown: function (e) { 13682 if (e.altKey || e.ctrlKey || e.metaKey) { return; } 13683 13684 var key = e.keyCode, 13685 map = this._map, 13686 offset; 13687 13688 if (key in this._panKeys) { 13689 if (!map._panAnim || !map._panAnim._inProgress) { 13690 offset = this._panKeys[key]; 13691 if (e.shiftKey) { 13692 offset = toPoint(offset).multiplyBy(3); 13693 } 13694 13695 map.panBy(offset); 13696 13697 if (map.options.maxBounds) { 13698 map.panInsideBounds(map.options.maxBounds); 13699 } 13700 } 13701 } else if (key in this._zoomKeys) { 13702 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 13703 13704 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) { 13705 map.closePopup(); 13706 13707 } else { 13708 return; 13709 } 13710 13711 stop(e); 13712 } 13713 }); 13714 13715 // @section Handlers 13716 // @section Handlers 13717 // @property keyboard: Handler 13718 // Keyboard navigation handler. 13719 Map.addInitHook('addHandler', 'keyboard', Keyboard); 13720 13721 /* 13722 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. 13723 */ 13724 13725 // @namespace Map 13726 // @section Interaction Options 13727 Map.mergeOptions({ 13728 // @section Mouse wheel options 13729 // @option scrollWheelZoom: Boolean|String = true 13730 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 13731 // it will zoom to the center of the view regardless of where the mouse was. 13732 scrollWheelZoom: true, 13733 13734 // @option wheelDebounceTime: Number = 40 13735 // Limits the rate at which a wheel can fire (in milliseconds). By default 13736 // user can't zoom via wheel more often than once per 40 ms. 13737 wheelDebounceTime: 40, 13738 13739 // @option wheelPxPerZoomLevel: Number = 60 13740 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 13741 // mean a change of one full zoom level. Smaller values will make wheel-zooming 13742 // faster (and vice versa). 13743 wheelPxPerZoomLevel: 60 13744 }); 13745 13746 var ScrollWheelZoom = Handler.extend({ 13747 addHooks: function () { 13748 on(this._map._container, 'wheel', this._onWheelScroll, this); 13749 13750 this._delta = 0; 13751 }, 13752 13753 removeHooks: function () { 13754 off(this._map._container, 'wheel', this._onWheelScroll, this); 13755 }, 13756 13757 _onWheelScroll: function (e) { 13758 var delta = getWheelDelta(e); 13759 13760 var debounce = this._map.options.wheelDebounceTime; 13761 13762 this._delta += delta; 13763 this._lastMousePos = this._map.mouseEventToContainerPoint(e); 13764 13765 if (!this._startTime) { 13766 this._startTime = +new Date(); 13767 } 13768 13769 var left = Math.max(debounce - (+new Date() - this._startTime), 0); 13770 13771 clearTimeout(this._timer); 13772 this._timer = setTimeout(bind(this._performZoom, this), left); 13773 13774 stop(e); 13775 }, 13776 13777 _performZoom: function () { 13778 var map = this._map, 13779 zoom = map.getZoom(), 13780 snap = this._map.options.zoomSnap || 0; 13781 13782 map._stop(); // stop panning and fly animations if any 13783 13784 // map the delta with a sigmoid function to -4..4 range leaning on -1..1 13785 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), 13786 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, 13787 d4 = snap ? Math.ceil(d3 / snap) * snap : d3, 13788 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom; 13789 13790 this._delta = 0; 13791 this._startTime = null; 13792 13793 if (!delta) { return; } 13794 13795 if (map.options.scrollWheelZoom === 'center') { 13796 map.setZoom(zoom + delta); 13797 } else { 13798 map.setZoomAround(this._lastMousePos, zoom + delta); 13799 } 13800 } 13801 }); 13802 13803 // @section Handlers 13804 // @property scrollWheelZoom: Handler 13805 // Scroll wheel zoom handler. 13806 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); 13807 13808 /* 13809 * L.Map.TapHold is used to simulate `contextmenu` event on long hold, 13810 * which otherwise is not fired by mobile Safari. 13811 */ 13812 13813 var tapHoldDelay = 600; 13814 13815 // @namespace Map 13816 // @section Interaction Options 13817 Map.mergeOptions({ 13818 // @section Touch interaction options 13819 // @option tapHold: Boolean 13820 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari. 13821 tapHold: Browser.touchNative && Browser.safari && Browser.mobile, 13822 13823 // @option tapTolerance: Number = 15 13824 // The max number of pixels a user can shift his finger during touch 13825 // for it to be considered a valid tap. 13826 tapTolerance: 15 13827 }); 13828 13829 var TapHold = Handler.extend({ 13830 addHooks: function () { 13831 on(this._map._container, 'touchstart', this._onDown, this); 13832 }, 13833 13834 removeHooks: function () { 13835 off(this._map._container, 'touchstart', this._onDown, this); 13836 }, 13837 13838 _onDown: function (e) { 13839 clearTimeout(this._holdTimeout); 13840 if (e.touches.length !== 1) { return; } 13841 13842 var first = e.touches[0]; 13843 this._startPos = this._newPos = new Point(first.clientX, first.clientY); 13844 13845 this._holdTimeout = setTimeout(bind(function () { 13846 this._cancel(); 13847 if (!this._isTapValid()) { return; } 13848 13849 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events 13850 on(document, 'touchend', preventDefault); 13851 on(document, 'touchend touchcancel', this._cancelClickPrevent); 13852 this._simulateEvent('contextmenu', first); 13853 }, this), tapHoldDelay); 13854 13855 on(document, 'touchend touchcancel contextmenu', this._cancel, this); 13856 on(document, 'touchmove', this._onMove, this); 13857 }, 13858 13859 _cancelClickPrevent: function cancelClickPrevent() { 13860 off(document, 'touchend', preventDefault); 13861 off(document, 'touchend touchcancel', cancelClickPrevent); 13862 }, 13863 13864 _cancel: function () { 13865 clearTimeout(this._holdTimeout); 13866 off(document, 'touchend touchcancel contextmenu', this._cancel, this); 13867 off(document, 'touchmove', this._onMove, this); 13868 }, 13869 13870 _onMove: function (e) { 13871 var first = e.touches[0]; 13872 this._newPos = new Point(first.clientX, first.clientY); 13873 }, 13874 13875 _isTapValid: function () { 13876 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 13877 }, 13878 13879 _simulateEvent: function (type, e) { 13880 var simulatedEvent = new MouseEvent(type, { 13881 bubbles: true, 13882 cancelable: true, 13883 view: window, 13884 // detail: 1, 13885 screenX: e.screenX, 13886 screenY: e.screenY, 13887 clientX: e.clientX, 13888 clientY: e.clientY, 13889 // button: 2, 13890 // buttons: 2 13891 }); 13892 13893 simulatedEvent._simulated = true; 13894 13895 e.target.dispatchEvent(simulatedEvent); 13896 } 13897 }); 13898 13899 // @section Handlers 13900 // @property tapHold: Handler 13901 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari). 13902 Map.addInitHook('addHandler', 'tapHold', TapHold); 13903 13904 /* 13905 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. 13906 */ 13907 13908 // @namespace Map 13909 // @section Interaction Options 13910 Map.mergeOptions({ 13911 // @section Touch interaction options 13912 // @option touchZoom: Boolean|String = * 13913 // Whether the map can be zoomed by touch-dragging with two fingers. If 13914 // passed `'center'`, it will zoom to the center of the view regardless of 13915 // where the touch events (fingers) were. Enabled for touch-capable web 13916 // browsers. 13917 touchZoom: Browser.touch, 13918 13919 // @option bounceAtZoomLimits: Boolean = true 13920 // Set it to false if you don't want the map to zoom beyond min/max zoom 13921 // and then bounce back when pinch-zooming. 13922 bounceAtZoomLimits: true 13923 });
vendor: 7,170 bytes, lines 13924-14120
13924 13925 var TouchZoom = Handler.extend({ 13926 addHooks: function () { 13927 addClass(this._map._container, 'leaflet-touch-zoom'); 13928 on(this._map._container, 'touchstart', this._onTouchStart, this); 13929 }, 13930 13931 removeHooks: function () { 13932 removeClass(this._map._container, 'leaflet-touch-zoom'); 13933 off(this._map._container, 'touchstart', this._onTouchStart, this); 13934 }, 13935 13936 _onTouchStart: function (e) { 13937 var map = this._map; 13938 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } 13939 13940 var p1 = map.mouseEventToContainerPoint(e.touches[0]), 13941 p2 = map.mouseEventToContainerPoint(e.touches[1]); 13942 13943 this._centerPoint = map.getSize()._divideBy(2); 13944 this._startLatLng = map.containerPointToLatLng(this._centerPoint); 13945 if (map.options.touchZoom !== 'center') { 13946 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); 13947 } 13948 13949 this._startDist = p1.distanceTo(p2); 13950 this._startZoom = map.getZoom(); 13951 13952 this._moved = false; 13953 this._zooming = true; 13954 13955 map._stop(); 13956 13957 on(document, 'touchmove', this._onTouchMove, this); 13958 on(document, 'touchend touchcancel', this._onTouchEnd, this); 13959 13960 preventDefault(e); 13961 }, 13962 13963 _onTouchMove: function (e) { 13964 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } 13965 13966 var map = this._map, 13967 p1 = map.mouseEventToContainerPoint(e.touches[0]), 13968 p2 = map.mouseEventToContainerPoint(e.touches[1]), 13969 scale = p1.distanceTo(p2) / this._startDist; 13970 13971 this._zoom = map.getScaleZoom(scale, this._startZoom); 13972 13973 if (!map.options.bounceAtZoomLimits && ( 13974 (this._zoom < map.getMinZoom() && scale < 1) || 13975 (this._zoom > map.getMaxZoom() && scale > 1))) { 13976 this._zoom = map._limitZoom(this._zoom); 13977 } 13978 13979 if (map.options.touchZoom === 'center') { 13980 this._center = this._startLatLng; 13981 if (scale === 1) { return; } 13982 } else { 13983 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng 13984 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 13985 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } 13986 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 13987 } 13988 13989 if (!this._moved) { 13990 map._moveStart(true, false); 13991 this._moved = true; 13992 } 13993 13994 cancelAnimFrame(this._animRequest); 13995 13996 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); 13997 this._animRequest = requestAnimFrame(moveFn, this, true); 13998 13999 preventDefault(e); 14000 }, 14001 14002 _onTouchEnd: function () { 14003 if (!this._moved || !this._zooming) { 14004 this._zooming = false; 14005 return; 14006 } 14007 14008 this._zooming = false; 14009 cancelAnimFrame(this._animRequest); 14010 14011 off(document, 'touchmove', this._onTouchMove, this); 14012 off(document, 'touchend touchcancel', this._onTouchEnd, this); 14013 14014 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. 14015 if (this._map.options.zoomAnimation) { 14016 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 14017 } else { 14018 this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 14019 } 14020 } 14021 }); 14022 14023 // @section Handlers 14024 // @property touchZoom: Handler 14025 // Touch zoom handler. 14026 Map.addInitHook('addHandler', 'touchZoom', TouchZoom); 14027 14028 Map.BoxZoom = BoxZoom; 14029 Map.DoubleClickZoom = DoubleClickZoom; 14030 Map.Drag = Drag; 14031 Map.Keyboard = Keyboard; 14032 Map.ScrollWheelZoom = ScrollWheelZoom; 14033 Map.TapHold = TapHold; 14034 Map.TouchZoom = TouchZoom; 14035 14036 exports.Bounds = Bounds; 14037 exports.Browser = Browser; 14038 exports.CRS = CRS; 14039 exports.Canvas = Canvas; 14040 exports.Circle = Circle; 14041 exports.CircleMarker = CircleMarker; 14042 exports.Class = Class; 14043 exports.Control = Control; 14044 exports.DivIcon = DivIcon; 14045 exports.DivOverlay = DivOverlay; 14046 exports.DomEvent = DomEvent; 14047 exports.DomUtil = DomUtil; 14048 exports.Draggable = Draggable; 14049 exports.Evented = Evented; 14050 exports.FeatureGroup = FeatureGroup; 14051 exports.GeoJSON = GeoJSON; 14052 exports.GridLayer = GridLayer; 14053 exports.Handler = Handler; 14054 exports.Icon = Icon; 14055 exports.ImageOverlay = ImageOverlay; 14056 exports.LatLng = LatLng; 14057 exports.LatLngBounds = LatLngBounds; 14058 exports.Layer = Layer; 14059 exports.LayerGroup = LayerGroup; 14060 exports.LineUtil = LineUtil; 14061 exports.Map = Map; 14062 exports.Marker = Marker; 14063 exports.Mixin = Mixin; 14064 exports.Path = Path; 14065 exports.Point = Point; 14066 exports.PolyUtil = PolyUtil; 14067 exports.Polygon = Polygon; 14068 exports.Polyline = Polyline; 14069 exports.Popup = Popup; 14070 exports.PosAnimation = PosAnimation; 14071 exports.Projection = index; 14072 exports.Rectangle = Rectangle; 14073 exports.Renderer = Renderer; 14074 exports.SVG = SVG; 14075 exports.SVGOverlay = SVGOverlay; 14076 exports.TileLayer = TileLayer; 14077 exports.Tooltip = Tooltip; 14078 exports.Transformation = Transformation; 14079 exports.Util = Util; 14080 exports.VideoOverlay = VideoOverlay; 14081 exports.bind = bind; 14082 exports.bounds = toBounds; 14083 exports.canvas = canvas; 14084 exports.circle = circle; 14085 exports.circleMarker = circleMarker; 14086 exports.control = control; 14087 exports.divIcon = divIcon; 14088 exports.extend = extend; 14089 exports.featureGroup = featureGroup; 14090 exports.geoJSON = geoJSON; 14091 exports.geoJson = geoJson; 14092 exports.gridLayer = gridLayer; 14093 exports.icon = icon; 14094 exports.imageOverlay = imageOverlay; 14095 exports.latLng = toLatLng; 14096 exports.latLngBounds = toLatLngBounds; 14097 exports.layerGroup = layerGroup; 14098 exports.map = createMap; 14099 exports.marker = marker; 14100 exports.point = toPoint; 14101 exports.polygon = polygon; 14102 exports.polyline = polyline; 14103 exports.popup = popup; 14104 exports.rectangle = rectangle; 14105 exports.setOptions = setOptions; 14106 exports.stamp = stamp; 14107 exports.svg = svg; 14108 exports.svgOverlay = svgOverlay; 14109 exports.tileLayer = tileLayer; 14110 exports.tooltip = tooltip; 14111 exports.transformation = toTransformation; 14112 exports.version = version; 14113 exports.videoOverlay = videoOverlay; 14114 14115 var oldL = window.L; 14116 exports.noConflict = function() { 14117 window.L = oldL; 14118 return this; 14119 } 14120 // Always export us to window global (see #2364)
14121 window.L = exports; 14122 14123 })); 14124 //# sourceMappingURL=leaflet-src.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.